export class RMFRPActor extends Actor { /** @override */ prepareData() { // Prepare data for the actor. Calling the super version of this executes // the following, in order: data reset (to clear active effects), // prepareBaseData(), prepareEmbeddedDocuments() (including active effects), // prepareDerivedData(). super.prepareData(); } prepareDerivedData() { const actorData = this; const systemData = actorData.system; const flags = actorData.flags.rmfrp || {}; // Make separate methods for each Actor type (character, npc, etc.) to keep // things organized. this._prepareCharacterData(actorData); this._prepareNpcData(actorData); } /** * Prepare Character specific data. * @param {Actor} actorData The NPC Object to prepare data for */ _prepareCharacterData(actorData) { if (actorData.type !== "character") return; console.log("*****Flag", this.getFlag("world", "importing")); if (this.getFlag("world", "importing")) { return; // Don't calculate skill bonuses if we are importing } this.calculateBasicStatBonus(actorData); // Calculate Stat Bonuses for the Actor this.calculateStatBonuses(actorData); // Calculate Resistance Rolls for the Actor this.calculateResistanceRolls(actorData); // Iterate through and apply Stat bonuses for Skill Category Items this.calculateSkillCategoryStatBonuses(); // Iterate through and apply Skill Category Bonuses for Skill items this.calculateSkillBonuses(); this.computeWoundsMalus(); } getStunnedModifier() { if (this.system.state.stunned) { return Math.min(-50 + (3*this.system.stats.self_discipline.stat_bonus), 0) } else { return 0; } } computeWoundsMalus() { // Compute % of wounds let percent = 100 - (this.system.attributes.hits.current*100/this.system.attributes.hits.max); let modifier = 0; if (percent > 25 && percent < 50) { modifier = -10; } else if (percent >= 51 && percent < 75) { modifier = -20; } else if (percent >= 76) { modifier = -30; } this.system.modifiers.woundsModifier = modifier; console.log(`rmfrp | actor.js | Wounds Malus: ${this.system.modifiers.woundsModifier} ${percent}`); } /** * Prepare NPC specific data. * @param {Actor} actorData The NPC Object to prepare data for */ _prepareNpcData(actorData) { if (actorData.type !== "npc") return; // Make modifications to data here. For example: const data = actorData.data; } // This checks to see if you have a Rollable Table called "Basic Stat Bonus Table" and uses it to calculate the basic stat bonuses. calculateBasicStatBonus(actorData) { const systemData = actorData.system; for (const table of game.tables) { if (table.name === "Basic Stat Bonus Table") { for (const result of table.results) { if (actorData.system.stats.agility.temp >= Number(result.range[0]) && actorData.system.stats.agility.basic_bonus <= Number(result.range[1])) { actorData.system.stats.agility.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.constitution.temp >= Number(result.range[0]) && actorData.system.stats.constitution.basic_bonus <= Number(result.range[1])) { actorData.system.stats.constitution.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.memory.temp >= Number(result.range[0]) && actorData.system.stats.memory.basic_bonus <= Number(result.range[1])) { actorData.system.stats.memory.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.reasoning.temp >= Number(result.range[0]) && actorData.system.stats.reasoning.basic_bonus <= Number(result.range[1])) { actorData.system.stats.reasoning.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.self_discipline.temp >= Number(result.range[0]) && actorData.system.stats.self_discipline.basic_bonus <= Number(result.range[1])) { actorData.system.stats.self_discipline.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.empathy.temp >= Number(result.range[0]) && actorData.system.stats.empathy.basic_bonus <= Number(result.range[1])) { actorData.system.stats.empathy.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.intuition.temp >= Number(result.range[0]) && actorData.system.stats.intuition.basic_bonus <= Number(result.range[1])) { actorData.system.stats.intuition.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.presence.temp >= Number(result.range[0]) && actorData.system.stats.presence.basic_bonus <= Number(result.range[1])) { actorData.system.stats.presence.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.quickness.temp >= Number(result.range[0]) && actorData.system.stats.quickness.basic_bonus <= Number(result.range[1])) { actorData.system.stats.quickness.basic_bonus = parseInt(result.text, 10); } if (actorData.system.stats.strength.temp >= Number(result.range[0]) && actorData.system.stats.strength.basic_bonus <= Number(result.range[1])) { actorData.system.stats.strength.basic_bonus = parseInt(result.text, 10); } } } } } // Tally each stat bonus and populate the total field. calculateStatBonuses(actorData) { const systemData = actorData.system; actorData.system.stats.agility.stat_bonus = Number(systemData.stats.agility.racial_bonus) + Number(systemData.stats.agility.special_bonus) + Number(systemData.stats.agility.basic_bonus); actorData.system.stats.constitution.stat_bonus = Number(systemData.stats.constitution.racial_bonus) + Number(systemData.stats.constitution.special_bonus) + Number(systemData.stats.constitution.basic_bonus); actorData.system.stats.memory.stat_bonus = Number(systemData.stats.memory.racial_bonus) + Number(systemData.stats.memory.special_bonus) + Number(systemData.stats.memory.basic_bonus); actorData.system.stats.reasoning.stat_bonus = Number(systemData.stats.reasoning.racial_bonus) + Number(systemData.stats.reasoning.special_bonus) + Number(systemData.stats.reasoning.basic_bonus); actorData.system.stats.self_discipline.stat_bonus = Number(systemData.stats.self_discipline.racial_bonus) + Number(systemData.stats.self_discipline.special_bonus) + Number(systemData.stats.self_discipline.basic_bonus); actorData.system.stats.empathy.stat_bonus = Number(systemData.stats.empathy.racial_bonus) + Number(systemData.stats.empathy.special_bonus) + Number(systemData.stats.empathy.basic_bonus); actorData.system.stats.intuition.stat_bonus = Number(systemData.stats.intuition.racial_bonus) + Number(systemData.stats.intuition.special_bonus) + Number(systemData.stats.intuition.basic_bonus); actorData.system.stats.presence.stat_bonus = Number(systemData.stats.presence.racial_bonus) + Number(systemData.stats.presence.special_bonus) + Number(systemData.stats.presence.basic_bonus); actorData.system.stats.quickness.stat_bonus = Number(systemData.stats.quickness.racial_bonus) + Number(systemData.stats.quickness.special_bonus) + Number(systemData.stats.quickness.basic_bonus); actorData.system.stats.strength.stat_bonus = Number(systemData.stats.strength.racial_bonus) + Number(systemData.stats.strength.special_bonus) + Number(systemData.stats.strength.basic_bonus); } // Calculate each Resistance Roll with the formula on the character sheet. calculateResistanceRolls(actorData) { const systemData = actorData.system; actorData.system.resistance_rolls.essence.value = Number(systemData.stats.empathy.stat_bonus * 3); actorData.system.resistance_rolls.channeling.value = Number(systemData.stats.intuition.stat_bonus * 3); actorData.system.resistance_rolls.mentalism.value = Number(systemData.stats.presence.stat_bonus * 3); actorData.system.resistance_rolls.fear.value = Number(systemData.stats.self_discipline.stat_bonus * 3); actorData.system.resistance_rolls.poison_disease.value = Number(systemData.stats.constitution.stat_bonus * 3); actorData.system.resistance_rolls.chann_ess.value = Number(systemData.stats.intuition.stat_bonus) + Number(systemData.stats.empathy.stat_bonus); actorData.system.resistance_rolls.chann_ment.value = Number(systemData.stats.intuition.stat_bonus) + Number(systemData.stats.presence.stat_bonus); actorData.system.resistance_rolls.ess_ment.value = Number(systemData.stats.empathy.stat_bonus) + Number(systemData.stats.presence.stat_bonus); actorData.system.resistance_rolls.arcane.value = Number(systemData.stats.empathy.stat_bonus) + Number(systemData.stats.intuition.stat_bonus) + Number(systemData.stats.presence.stat_bonus); actorData.system.resistance_rolls.essence.total = actorData.system.resistance_rolls.essence.value + actorData.system.resistance_rolls.essence.race_mod; actorData.system.resistance_rolls.channeling.total = actorData.system.resistance_rolls.channeling.value + actorData.system.resistance_rolls.channeling.race_mod; actorData.system.resistance_rolls.mentalism.total = actorData.system.resistance_rolls.mentalism.value + actorData.system.resistance_rolls.mentalism.race_mod; actorData.system.resistance_rolls.fear.total = actorData.system.resistance_rolls.fear.value + actorData.system.resistance_rolls.fear.race_mod; actorData.system.resistance_rolls.poison_disease.total = actorData.system.resistance_rolls.poison_disease.value + actorData.system.resistance_rolls.poison_disease.race_mod; actorData.system.resistance_rolls.chann_ess.total = actorData.system.resistance_rolls.chann_ess.value + actorData.system.resistance_rolls.chann_ess.race_mod; actorData.system.resistance_rolls.chann_ment.total = actorData.system.resistance_rolls.chann_ment.value + actorData.system.resistance_rolls.chann_ment.race_mod; actorData.system.resistance_rolls.ess_ment.total = actorData.system.resistance_rolls.ess_ment.value + actorData.system.resistance_rolls.ess_ment.race_mod; actorData.system.resistance_rolls.arcane.total = actorData.system.resistance_rolls.arcane.value + actorData.system.resistance_rolls.arcane.race_mod; } calculateSkillBonuses() { for (const item of this.items) { if (item.type === "skill") { console.log(`rmfrp | actor.js | Calculating skill bonus for Skill: ${item.name}`); item.calculateSelectedSkillCategoryBonus(item); item.calculateSkillTotalBonus(item); } } } // Tallys the bonus for each Stat that is applicable to the Skill Category and then updates the total calculateSkillCategoryStatBonuses() { for (const item of this.items) { if (item.type === "skill_category") { console.log(`rmfrp | actor.js | Calculating Skill Category Stat Bonuses for: ${item.name}`); // Get all the applicable stats for this skill category let app_stat_1 = item.system.app_stat_1; let app_stat_2 = item.system.app_stat_2; let app_stat_3 = item.system.app_stat_3; // If the first one is None we don't need to do anything further if (app_stat_1 === "None") { continue; } else { let applicable_stat_bonuses = []; // Iterate through the applicable stats and find their full names for (const stat in CONFIG.rmfrp.stats) { // If the configured App Stat matches the one of the stats in config if (app_stat_1 === CONFIG.rmfrp.stats[stat].shortname) { // Add the Stat Bonus to the array applicable_stat_bonuses.push(this.system.stats[stat].stat_bonus); } if (app_stat_2 === CONFIG.rmfrp.stats[stat].shortname) { // Add the Stat Bonus to the array applicable_stat_bonuses.push(this.system.stats[stat].stat_bonus); } if (app_stat_3 === CONFIG.rmfrp.stats[stat].shortname) { // Add the Stat Bonus to the array applicable_stat_bonuses.push(this.system.stats[stat].stat_bonus); } } // Compute the total bonus for the applicable stats let applicable_stat_bonus = 0; for (const bonus of applicable_stat_bonuses) { applicable_stat_bonus += bonus; } // Apply the update if we found stat bonuses for every applicable stat if ( item.system.stat_bonus != applicable_stat_bonus ) { item.system.stat_bonus = applicable_stat_bonus; } // Update the total in the Item item.calculateSkillCategoryTotalBonus(item); } } } } // For each skill category return an object in this format. // {{ _id: "skill category name"}} // This is the format that the select helper on the skill sheet needs getOwnedItemsByType(item_type) { let ownedItems = {None: "None"}; console.log(`rmfrp | actor.js | Getting owned ${item_type} for: ${this.name}`); for (const item of this.items) { if (item.type === item_type) { ownedItems[item._id] = item.name; } } // sort the ownedItems by name ownedItems = Object.fromEntries(Object.entries(ownedItems).sort((a,b) => a[1].localeCompare(b[1]))); return (ownedItems); } }