Working on Skills and some fixes

- Added Skills to character sheet
- Added migration to old skills
- Added icon on skills
- Added actor.isNpc
- Money out of system
This commit is contained in:
Vlyan
2023-01-08 15:12:22 +01:00
parent 1ec9e65ca5
commit 710afd9804
22 changed files with 302 additions and 145 deletions

View File

@@ -16,66 +16,96 @@ export class ActorL5r5e extends Actor {
docData.img = `${CONFIG.l5r5e.paths.assets}icons/actors/${docData.type}.svg`;
}
// Some tweak on actors prototypeToken
docData.prototypeToken = docData.prototypeToken || {};
switch (docData.type) {
case "character":
foundry.utils.mergeObject(
docData.prototypeToken,
{
// vision: true,
// dimSight: 30,
// brightSight: 0,
actorLink: true,
disposition: 1, // friendly
bar1: {
attribute: "fatigue",
},
bar2: {
attribute: "strife",
},
},
{ overwrite: false }
);
break;
// Only for new actors (New actor has no items, duplicates does)
if (!docData.items) {
// Some tweak on actors prototypeToken
docData.prototypeToken = docData.prototypeToken || {};
switch (docData.type) {
case "character":
// Load skills from core compendiums (only for pc character)
docData.items = [];
await ActorL5r5e.addSkillsFromCompendiums(docData);
case "npc":
foundry.utils.mergeObject(
docData.prototypeToken,
{
actorLink: true,
disposition: 0, // neutral
bar1: {
attribute: "fatigue",
// Set token properties
foundry.utils.mergeObject(
docData.prototypeToken,
{
// vision: true,
// dimSight: 30,
// brightSight: 0,
actorLink: true,
disposition: 1, // friendly
bar1: {
attribute: "fatigue",
},
bar2: {
attribute: "strife",
},
},
bar2: {
attribute: "strife",
},
},
{ overwrite: false }
);
break;
{ overwrite: false }
);
break;
case "army":
foundry.utils.mergeObject(
docData.prototypeToken,
{
actorLink: true,
disposition: 0, // neutral
bar1: {
attribute: "battle_readiness.casualties_strength",
case "npc":
foundry.utils.mergeObject(
docData.prototypeToken,
{
actorLink: true,
disposition: 0, // neutral
bar1: {
attribute: "fatigue",
},
bar2: {
attribute: "strife",
},
},
bar2: {
attribute: "battle_readiness.panic_discipline",
{ overwrite: false }
);
break;
case "army":
foundry.utils.mergeObject(
docData.prototypeToken,
{
actorLink: true,
disposition: 0, // neutral
bar1: {
attribute: "battle_readiness.casualties_strength",
},
bar2: {
attribute: "battle_readiness.panic_discipline",
},
},
},
{ overwrite: false }
);
break;
{ overwrite: false }
);
break;
}
}
await super.create(docData, options);
}
/**
* Add all the skills from compendiums to "items"
*/
static async addSkillsFromCompendiums(docData) {
console.log(`L5R5E | Adding skills to ${docData.name}`);
const packName = CONFIG.l5r5e.systemName + ".core-skills";
const skills = await game.l5r5e.HelpersL5r5e.loadCompendium(packName);
if (skills.length < 1) {
console.log(`L5R5E | No items found in Pack [${packName}]`);
return;
}
// Get the json data and replace the object id
skills.forEach(item => {
const tmpData = item.toObject();
tmpData._id = foundry.utils.randomID();
docData.items.push(tmpData);
});
}
/**
* Entity-specific actions that should occur when the Entity is updated
* @override
@@ -94,7 +124,7 @@ export class ActorL5r5e extends Actor {
context.pack = this.pack;
// NPC switch between types : Linked actor for Adversary, unlinked for Minion
if (!!docData["system.type"] && this.type === "npc" && docData["system.type"] !== this.system.type) {
if (!!docData["system.type"] && this.isNpc && docData["system.type"] !== this.system.type) {
docData["prototypeToken.actorLink"] = docData["system.type"] === "adversary";
}
@@ -254,12 +284,20 @@ export class ActorL5r5e extends Actor {
return this.type === "character";
}
/**
* Return true if this actor is a NPC
* @return {boolean}
*/
get isNpc() {
return this.type === "npc";
}
/**
* Return true if this actor is an Adversary
* @return {boolean}
*/
get isAdversary() {
return this.type === "npc" && this.system.type === "adversary";
return this.isNpc && this.system.type === "adversary";
}
/**
@@ -267,7 +305,7 @@ export class ActorL5r5e extends Actor {
* @return {boolean}
*/
get isMinion() {
return this.type === "npc" && this.system.type === "minion";
return this.isNpc && this.system.type === "minion";
}
/**
@@ -363,7 +401,7 @@ export class ActorL5r5e extends Actor {
if (!this.isCharacterType) {
return null;
}
return this.type === "npc" ? this.system.conflict_rank.social : this.system.identity.school_rank;
return this.isNpc ? this.system.conflict_rank.social : this.system.identity.school_rank;
}
/**
@@ -374,6 +412,6 @@ export class ActorL5r5e extends Actor {
if (!this.isCharacterType) {
return null;
}
return this.type === "npc" ? this.system.conflict_rank.martial : this.system.identity.school_rank;
return this.isNpc ? this.system.conflict_rank.martial : this.system.identity.school_rank;
}
}

View File

@@ -230,6 +230,10 @@ export class BaseCharacterSheetL5r5e extends BaseSheetL5r5e {
itemData = item.toObject(true);
break;
case "skill":
itemData.system.rank = 0;
break;
case "technique":
// School_ability and mastery_ability, allow only 1 per type
if (CONFIG.l5r5e.techniques.get(itemData.system.technique_type)?.type === "school") {

View File

@@ -98,7 +98,7 @@ export class CharacterGeneratorDialog extends FormApplication {
}));
return {
...(await super.getData(options)),
isNpc: this.actor.type === "npc",
isNpc: this.actor.isNpc,
clanList: [{ id: "random", label: game.i18n.localize("l5r5e.global.random") }, ...clans],
genderList: [
{ id: "random", label: game.i18n.localize("l5r5e.global.random") },

View File

@@ -292,7 +292,7 @@ export class CharacterGenerator {
}
) {
const actorDatas = actor.system;
const isNpc = actor.type === "npc";
const isNpc = actor.isNpc;
// Need to set some required values
this.data.age = actorDatas.identity.age || CharacterGenerator.genAge(this.data.avgRingsValue);

View File

@@ -47,8 +47,11 @@ export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
// Min rank = 1
this.actor.system.identity.school_rank = Math.max(1, this.actor.system.identity.school_rank);
// Split Skills
sheetData.data.skillCategories = this._splitSkills(sheetData);
// Split Money
sheetData.data.system.money = this._zeniToMoney(this.actor.system.zeni);
sheetData.data.money = this._zeniToMoney(this.actor.system.zeni);
// Split school advancements by rank, and calculate xp spent and add it to total
this._prepareSchoolAdvancement(sheetData);
@@ -64,6 +67,28 @@ export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
return sheetData;
}
/**
* Split Skills item by categories
* @private
*/
_splitSkills(sheetData) {
const skill = CONFIG.l5r5e.skillCategories.reduce((acc,curr) => (acc[curr] = [], acc), {});
sheetData.items.forEach((item) => {
if (item.type === "skill") {
const cat = item.system.category ?? "artisan";
skill[cat].push(item);
}
});
// Sort Items by name
Object.values(skill).forEach(section => {
section.sort((a, b) => a.name.localeCompare(b.name));
});
return skill;
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
@@ -180,6 +205,12 @@ export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
* @param formData
*/
_updateObject(event, formData) {
// Update items ranks
const formDataObj = foundry.utils.expandObject(formData);
if (formDataObj.skillsValues) {
this._updateItemsRank(foundry.utils.expandObject(formDataObj.skillsValues));
}
// Clan tag trim if autocomplete in school name
if (
formData["autoCompleteListName"] === "system.identity.school" &&
@@ -194,16 +225,12 @@ export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
}
// Store money in Zeni
if (formData["system.money.koku"] || formData["system.money.bu"] || formData["system.money.zeni"]) {
if (formData["money.koku"] || formData["money.bu"] || formData["money.zeni"]) {
formData["system.zeni"] = this._moneyToZeni(
formData["system.money.koku"] || 0,
formData["system.money.bu"] || 0,
formData["system.money.zeni"] || 0
formData["money.koku"] || 0,
formData["money.bu"] || 0,
formData["money.zeni"] || 0
);
// Remove fake money object
delete formData["system.money.koku"];
delete formData["system.money.bu"];
delete formData["system.money.zeni"];
}
// Save computed values
@@ -219,6 +246,20 @@ export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
return super._updateObject(event, formData);
}
/**
* Update embedded items ranks
* @param {Object<String:String>} itemsValues items new values "ids: rank"
*/
_updateItemsRank(itemsValues) {
Object.entries(itemsValues).forEach(([key, rank]) => {
const item = this.actor.items.get(key);
if (!item || item.system.rank === rank) {
return;
}
item.update({ "system.rank": rank });
});
}
/**
* Convert a sum in Zeni to Zeni, Bu and Koku
* @param {number} zeni

View File

@@ -199,6 +199,6 @@ export class CombatL5r5e extends Combat {
* @private
*/
static _getWeightByActorType(actor) {
return actor.type === "npc" ? (actor.type === "minion" ? 3 : 2) : 1;
return actor.isNpc ? (actor.type === "minion" ? 3 : 2) : 1;
}
}

View File

@@ -385,7 +385,7 @@ export class DicePickerDialog extends FormApplication {
* @return {boolean}
*/
get useCategory() {
return !!this._actor && this._actor.type === "npc";
return !!this._actor && this._actor.isNpc;
}
/**

View File

@@ -91,7 +91,7 @@ export class GmMonitor extends FormApplication {
actors = game.actors.filter((e) => ids.includes(e.id));
} else {
// If empty add pc with owner
actors = game.actors.filter((actor) => actor.type === "character" && actor.hasPlayerOwnerActive);
actors = game.actors.filter((actor) => actor.isCharacter && actor.hasPlayerOwnerActive);
this._saveActorsIds();
}

View File

@@ -745,7 +745,7 @@ export class HelpersL5r5e {
* @return {Promise<{RollTableDraw}>} The drawn results
*/
static async drawManyFromPack(pack, tableName, retrieve = 5, opt = { rollMode: "selfroll" }) {
const comp = await game.packs.get(pack);
const comp = game.packs.get(pack);
if (!comp) {
console.log(`L5R5E | Pack not found[${pack}]`);
return;
@@ -760,6 +760,23 @@ export class HelpersL5r5e {
return await table.drawMany(retrieve, opt);
}
/**
* Load all compendium data from his id
*
* @param {String} compendium
* @param {Function} filter
* @returns {Promise<Item[]>}
*/
static async loadCompendium(compendium, filter = item => true) {
const pack = game.packs.get(compendium);
if (!pack) {
console.log(`L5R5E | Pack not found[${compendium}]`);
return;
}
const data = (await pack.getDocuments()) ?? [];
return data.filter(filter);
}
/**
* Return the string simplified for comparaison
* @param {string} str

View File

@@ -6,7 +6,7 @@ export class MigrationL5r5e {
* Minimum Version needed for migration stuff to trigger
* @type {string}
*/
static NEEDED_VERSION = "1.3.0";
static NEEDED_VERSION = "2.0.0";
/**
* Return true if the version need some updates
@@ -28,6 +28,11 @@ export class MigrationL5r5e {
return;
}
// Wait for translation to load
if (!options.force && typeof Babele !== "undefined") {
await new Promise((r) => setTimeout(r, 5000));
}
// if (MigrationL5r5e.needUpdate("1.3.0")) {
// ChatMessage.create({"content": "<strong>L5R5E v1.3.0 :</strong><br>"});
// }
@@ -42,7 +47,7 @@ export class MigrationL5r5e {
// Migrate World Actors
for (let actor of game.actors.contents) {
try {
const updateData = MigrationL5r5e._migrateActorData(actor, options);
const updateData = await MigrationL5r5e._migrateActorData(actor, options);
if (!foundry.utils.isEmpty(updateData)) {
console.log(`L5R5E | Migrating Actor document ${actor.name}[${actor._id}]`);
await actor.update(updateData);
@@ -227,7 +232,7 @@ export class MigrationL5r5e {
* @param options
* @return {Object} The updateData to apply
*/
static _migrateActorData(actor, options = { force: false }) {
static async _migrateActorData(actor, options = { force: false }) {
const updateData = {};
const system = actor.system;
@@ -245,7 +250,7 @@ export class MigrationL5r5e {
}
// NPC are now without autostats, we need to save the value
if (actor.type === "npc") {
if (actor.isNpc) {
if (system.endurance < 1) {
updateData["system.endurance"] = (Number(system.rings.earth) + Number(system.rings.fire)) * 2;
updateData["system.composure"] = (Number(system.rings.earth) + Number(system.rings.water)) * 2;
@@ -266,7 +271,7 @@ export class MigrationL5r5e {
}
// NPC have now more than a Strength and a Weakness
if (actor.type === "npc" && system.rings_affinities?.strength) {
if (actor.isNpc && system.rings_affinities?.strength) {
const aff = system.rings_affinities;
updateData["system.rings_affinities." + aff.strength.ring] = aff.strength.value;
updateData["system.rings_affinities." + aff.weakness.ring] = aff.weakness.value;
@@ -278,6 +283,28 @@ export class MigrationL5r5e {
}
// ***** End of 1.3.0 *****
// ***** Start of 2.0.0 *****
if (options?.force || MigrationL5r5e.needUpdate("2.0.0")) {
// Only PC : Convert fixed skills to items list
if (actor.isCharacter && Array.from(actor.items).every(i => i.type !== "skill")) {
// Add skills items
const update = {items: [], name: actor.name};
await game.l5r5e.ActorL5r5e.addSkillsFromCompendiums(update);
// Set actor value
update.items.forEach(item => {
const skillId = item.flags?.l5r5e?.skillCoreId;
const skillCatId = CONFIG.l5r5e.skills.get(skillId);
if (skillCatId && system.skills[skillCatId][skillId]) {
item.system.rank = system.skills[skillCatId][skillId];
}
});
updateData.items = update.items;
}
}
// ***** End of 2.0.0 *****
return updateData;
}

View File

@@ -13,7 +13,6 @@ export const PreloadTemplates = async function () {
`${tpl}actors/character/inventory.html`,
`${tpl}actors/character/narrative.html`,
`${tpl}actors/character/rings.html`,
`${tpl}actors/character/skill.html`,
`${tpl}actors/character/social.html`,
`${tpl}actors/character/stance.html`,
`${tpl}actors/character/techniques.html`,