From a3a517688a650d584ab195a6ebe9f1da07cb5b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soup=C3=A9?= Date: Thu, 21 Mar 2024 00:17:37 +0100 Subject: [PATCH 1/7] Adding description preparation to WFRP3eItems --- modules/data/items/WFRP3eAbilityDataModel.js | 22 +++++++++++++++++++ modules/data/items/WFRP3eCareerDataModel.js | 16 ++++++++++++++ .../data/items/WFRP3eConditionDataModel.js | 22 +++++++++++++++++++ .../items/WFRP3eCriticalWoundDataModel.js | 22 +++++++++++++++++++ modules/data/items/WFRP3eDiseaseDataModel.js | 22 +++++++++++++++++++ modules/data/items/WFRP3eInsanityDataModel.js | 22 +++++++++++++++++++ modules/data/items/WFRP3eMiscastDataModel.js | 22 +++++++++++++++++++ modules/data/items/WFRP3eMoneyDataModel.js | 22 +++++++++++++++++++ modules/data/items/WFRP3eMutationDataModel.js | 22 +++++++++++++++++++ modules/data/items/WFRP3eSkillDataModel.js | 22 +++++++++++++++++++ 10 files changed, 214 insertions(+) diff --git a/modules/data/items/WFRP3eAbilityDataModel.js b/modules/data/items/WFRP3eAbilityDataModel.js index b2d6c72..1671250 100644 --- a/modules/data/items/WFRP3eAbilityDataModel.js +++ b/modules/data/items/WFRP3eAbilityDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eAbilityDataModel extends foundry.abstract.TypeDataModel { @@ -10,4 +12,24 @@ export default class WFRP3eAbilityDataModel extends foundry.abstract.TypeDataMod description: new fields.HTMLField() }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Ability's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eCareerDataModel.js b/modules/data/items/WFRP3eCareerDataModel.js index 35bec81..ebdbe77 100644 --- a/modules/data/items/WFRP3eCareerDataModel.js +++ b/modules/data/items/WFRP3eCareerDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eCareerDataModel extends foundry.abstract.TypeDataModel { @@ -56,6 +58,8 @@ export default class WFRP3eCareerDataModel extends foundry.abstract.TypeDataMode { super.prepareBaseData(); + this._prepareDescription(); + const updates = {advances: {}}; // Ensure there is six open career advances available for each Career. @@ -76,6 +80,18 @@ export default class WFRP3eCareerDataModel extends foundry.abstract.TypeDataMode this.updateSource(updates); } + /** + * Prepares the description of the Career's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } + /** @inheritDoc */ static migrateData(source) { diff --git a/modules/data/items/WFRP3eConditionDataModel.js b/modules/data/items/WFRP3eConditionDataModel.js index 395104d..ef60c3a 100644 --- a/modules/data/items/WFRP3eConditionDataModel.js +++ b/modules/data/items/WFRP3eConditionDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eConditionDataModel extends foundry.abstract.TypeDataModel { @@ -11,4 +13,24 @@ export default class WFRP3eConditionDataModel extends foundry.abstract.TypeDataM duration: new fields.StringField({initial: "brief", required: true, nullable: false}) }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Condition's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eCriticalWoundDataModel.js b/modules/data/items/WFRP3eCriticalWoundDataModel.js index 026d9c3..fd4b93b 100644 --- a/modules/data/items/WFRP3eCriticalWoundDataModel.js +++ b/modules/data/items/WFRP3eCriticalWoundDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eCriticalWoundDataModel extends foundry.abstract.TypeDataModel { @@ -11,4 +13,24 @@ export default class WFRP3eCriticalWoundDataModel extends foundry.abstract.TypeD severityRating: new fields.NumberField({required: true, nullable: false, integer: true, initial: 1, min: 1}) }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Critical Wound's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eDiseaseDataModel.js b/modules/data/items/WFRP3eDiseaseDataModel.js index 8e33068..01c7bb5 100644 --- a/modules/data/items/WFRP3eDiseaseDataModel.js +++ b/modules/data/items/WFRP3eDiseaseDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eDiseaseDataModel extends foundry.abstract.TypeDataModel { @@ -15,4 +17,24 @@ export default class WFRP3eDiseaseDataModel extends foundry.abstract.TypeDataMod traits: new fields.StringField({...requiredInteger, initial: ", "}), }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Disease's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eInsanityDataModel.js b/modules/data/items/WFRP3eInsanityDataModel.js index b892ae6..56946fe 100644 --- a/modules/data/items/WFRP3eInsanityDataModel.js +++ b/modules/data/items/WFRP3eInsanityDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eInsanityDataModel extends foundry.abstract.TypeDataModel { @@ -14,4 +16,24 @@ export default class WFRP3eInsanityDataModel extends foundry.abstract.TypeDataMo traits: new fields.StringField({...requiredInteger, initial: ", "}), }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Insanity's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eMiscastDataModel.js b/modules/data/items/WFRP3eMiscastDataModel.js index febe9e1..ce61a6b 100644 --- a/modules/data/items/WFRP3eMiscastDataModel.js +++ b/modules/data/items/WFRP3eMiscastDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eMiscastDataModel extends foundry.abstract.TypeDataModel { @@ -10,4 +12,24 @@ export default class WFRP3eMiscastDataModel extends foundry.abstract.TypeDataMod description: new fields.HTMLField() }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Miscast's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eMoneyDataModel.js b/modules/data/items/WFRP3eMoneyDataModel.js index 15131be..39dda2a 100644 --- a/modules/data/items/WFRP3eMoneyDataModel.js +++ b/modules/data/items/WFRP3eMoneyDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eMoneyDataModel extends foundry.abstract.TypeDataModel { @@ -13,4 +15,24 @@ export default class WFRP3eMoneyDataModel extends foundry.abstract.TypeDataModel value: new fields.NumberField({...requiredInteger, initial: 1, min: 0}) }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Money's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eMutationDataModel.js b/modules/data/items/WFRP3eMutationDataModel.js index b085203..5faaa93 100644 --- a/modules/data/items/WFRP3eMutationDataModel.js +++ b/modules/data/items/WFRP3eMutationDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eMutationDataModel extends foundry.abstract.TypeDataModel { @@ -14,4 +16,24 @@ export default class WFRP3eMutationDataModel extends foundry.abstract.TypeDataMo traits: new fields.StringField({...requiredInteger, initial: "Chaos"}), }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Mutation's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file diff --git a/modules/data/items/WFRP3eSkillDataModel.js b/modules/data/items/WFRP3eSkillDataModel.js index f294e6d..3c86f1b 100644 --- a/modules/data/items/WFRP3eSkillDataModel.js +++ b/modules/data/items/WFRP3eSkillDataModel.js @@ -1,3 +1,5 @@ +import DataHelper from "../DataHelper.js"; + /** @inheritDoc */ export default class WFRP3eSkillDataModel extends foundry.abstract.TypeDataModel { @@ -14,4 +16,24 @@ export default class WFRP3eSkillDataModel extends foundry.abstract.TypeDataModel trainingLevel: new fields.NumberField({required: true, nullable: false, integer: true, initial: 0, min: 0}) }; } + + /** @inheritDoc */ + prepareBaseData() + { + super.prepareBaseData(); + + this._prepareDescription(); + } + + /** + * Prepares the description of the Skill's description. + * @private + */ + _prepareDescription() + { + const cleanedUpDescription = DataHelper._getCleanedupDescription(this.description); + + if(cleanedUpDescription) + this.updateSource({description: cleanedUpDescription}); + } } \ No newline at end of file From 6ccf5dfbb5712b274b6c609fc8cab9d93c291f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soup=C3=A9?= Date: Thu, 21 Mar 2024 00:19:48 +0100 Subject: [PATCH 2/7] Fixing Career sheet verso's race restrictions display --- templates/partials/item-career-sheet.hbs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/templates/partials/item-career-sheet.hbs b/templates/partials/item-career-sheet.hbs index f663600..283657d 100644 --- a/templates/partials/item-career-sheet.hbs +++ b/templates/partials/item-career-sheet.hbs @@ -104,7 +104,14 @@ {{else}} {{localize "CAREER.SHEET.BasicCareer"}} {{/if}} - : {{career.system.raceRestrictions}} + : + {{#each career.system.raceRestrictions}} + {{#if (and (and (sameAs @first true) (sameAs @last true)) (notEqualTo this "any"))}} + {{localize "CAREER.SHEET.OneRaceOnly" race=(localize (concat "RACE." (capitalize this)))}} + {{else}} + {{localize (concat "RACE." (capitalize this))}}{{#unless @last}}, {{/unless}} + {{/if}} + {{/each}}
{{career.system.summary}}
From 6e8c852116da48e6cb2a793dda30464e62ab5076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soup=C3=A9?= Date: Sat, 23 Mar 2024 22:59:20 +0100 Subject: [PATCH 3/7] Revamping Rolls and Dice --- lang/en-EN.json | 36 +- lang/fr-FR.json | 36 +- modules/CheckHelper.js | 122 +++-- modules/DicePool.js | 150 ++++-- modules/WFRP3eRoll.js | 392 ++++++--------- .../{CheckBuilder.js => DicePoolBuilder.js} | 474 +++++++----------- modules/dice/ChallengeDie.js | 28 +- modules/dice/CharacteristicDie.js | 28 +- modules/dice/ConservativeDie.js | 27 +- modules/dice/ExpertiseDie.js | 38 +- modules/dice/FortuneDie.js | 28 +- modules/dice/MisfortuneDie.js | 28 +- modules/dice/RecklessDie.js | 28 +- modules/dice/WFRP3eDie.js | 102 ++-- ...ck_builder.less => dice_pool_builder.less} | 7 +- styles/less/components/roll_message.less | 4 +- styles/less/wfrp3e.less | 2 +- templates/applications/check-builder.hbs | 206 -------- templates/applications/dice-pool-builder.hbs | 259 ++++++++++ templates/chatmessages/roll-tooltip.hbs | 6 +- templates/chatmessages/roll.hbs | 58 +-- wfrp3e.js | 6 +- 22 files changed, 910 insertions(+), 1155 deletions(-) rename modules/applications/{CheckBuilder.js => DicePoolBuilder.js} (50%) rename styles/less/components/{check_builder.less => dice_pool_builder.less} (97%) delete mode 100644 templates/applications/check-builder.hbs create mode 100644 templates/applications/dice-pool-builder.hbs diff --git a/lang/en-EN.json b/lang/en-EN.json index 21df670..250237b 100644 --- a/lang/en-EN.json +++ b/lang/en-EN.json @@ -348,7 +348,7 @@ "RACE.Human": "Human", "RACE.WoodElf": "Wood Elf", - "ROLL.CheckBuilder": "Check Builder", + "ROLL.DicePoolBuilder": "Dice Pool Builder", "ROLL.ActionCheckBuilder": "{action} Check Builder", "ROLL.InitiativeCheckBuilder": "Initiative Check Builder", "ROLL.SkillCheckBuilder": "{skill} Check Builder", @@ -392,23 +392,23 @@ "ROLL.CHALLENGELEVEL.Hard": "Hard", "ROLL.CHALLENGELEVEL.Daunting": "Daunting", "ROLL.CHALLENGELEVEL.Heroic": "Heroic", - "ROLL.CHECKBUILDER.AggressionDice": "Aggression Dice", - "ROLL.CHECKBUILDER.ChallengeLevel": "Challenge Level", - "ROLL.CHECKBUILDER.Characteristic": "Characteristic", - "ROLL.CHECKBUILDER.ConvertConservative": "Convert to conservative die", - "ROLL.CHECKBUILDER.ConvertReckless": "Convert to reckless die", - "ROLL.CHECKBUILDER.ConvertWarning": "There is no {type} die left to convert.", - "ROLL.CHECKBUILDER.ConvertBackWarning": "There is no {type} die left to convert back.", - "ROLL.CHECKBUILDER.CunningDice": "Cunning Dice", - "ROLL.CHECKBUILDER.DicePool": "Dice Pool", - "ROLL.CHECKBUILDER.EncounterType": "Encounter Type", - "ROLL.CHECKBUILDER.ExpertiseDice": "Expertise Dice", - "ROLL.CHECKBUILDER.Hint": "Left click to increase dice, right click to decrease.", - "ROLL.CHECKBUILDER.Options": "Options", - "ROLL.CHECKBUILDER.RollCheck": "Roll check", - "ROLL.CHECKBUILDER.Skill": "Skill", - "ROLL.CHECKBUILDER.StartingResults": "Starting results", - "ROLL.CHECKBUILDER.Weapon": "Weapon", + "ROLL.DICEPOOLBUILDER.AggressionDice": "Aggression Dice", + "ROLL.DICEPOOLBUILDER.ChallengeLevel": "Challenge Level", + "ROLL.DICEPOOLBUILDER.Characteristic": "Characteristic", + "ROLL.DICEPOOLBUILDER.ConvertConservative": "Convert to conservative die", + "ROLL.DICEPOOLBUILDER.ConvertReckless": "Convert to reckless die", + "ROLL.DICEPOOLBUILDER.ConvertWarning": "There is no {type} die to convert.", + "ROLL.DICEPOOLBUILDER.ConvertBackWarning": "There is no {type} die left to convert back.", + "ROLL.DICEPOOLBUILDER.CunningDice": "Cunning Dice", + "ROLL.DICEPOOLBUILDER.DicePool": "Dice Pool", + "ROLL.DICEPOOLBUILDER.EncounterType": "Encounter Type", + "ROLL.DICEPOOLBUILDER.ExpertiseDice": "Expertise Dice", + "ROLL.DICEPOOLBUILDER.Hint": "Left click to increase dice, right click to decrease.", + "ROLL.DICEPOOLBUILDER.Options": "Options", + "ROLL.DICEPOOLBUILDER.RollCheck": "Roll check", + "ROLL.DICEPOOLBUILDER.Skill": "Skill", + "ROLL.DICEPOOLBUILDER.StartingResults": "Starting results", + "ROLL.DICEPOOLBUILDER.Weapon": "Weapon", "ROLL.DIE.ChallengeDice": "challenge dice", "ROLL.DIE.CharacteristicDice": "characteristic dice", "ROLL.DIE.ConservativeDice": "conservative dice", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index 49a50a2..ed56992 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -348,7 +348,7 @@ "RACE.Human": "Humain", "RACE.WoodElf": "Elfe Sylvain", - "ROLL.CheckBuilder": "Préparateur de test", + "ROLL.DicePoolBuilder": "Préparateur de réserve de dés", "ROLL.ActionCheckBuilder": "Préparateur du test de {action}", "ROLL.InitiativeCheckBuilder": "Préparateur du test d'initiative", "ROLL.SkillCheckBuilder": "Préparateur du test de {skill}", @@ -392,23 +392,23 @@ "ROLL.CHALLENGELEVEL.Hard": "Hard", "ROLL.CHALLENGELEVEL.Daunting": "Daunting", "ROLL.CHALLENGELEVEL.Heroic": "Heroic", - "ROLL.CHECKBUILDER.AggressionDice": "Aggression Dice", - "ROLL.CHECKBUILDER.ChallengeLevel": "Challenge Level", - "ROLL.CHECKBUILDER.Characteristic": "Caractéristique", - "ROLL.CHECKBUILDER.ConvertConservative": "Convertir en dé de prudence", - "ROLL.CHECKBUILDER.ConvertReckless": "Convertir en dé de témérité", - "ROLL.CHECKBUILDER.ConvertWarning": "Il n'y a plus de dé de {type} à convertir.", - "ROLL.CHECKBUILDER.ConvertBackWarning": "Il n'y a plus de dé de {type} à reconvertir.", - "ROLL.CHECKBUILDER.CunningDice": "Cunning Dice", - "ROLL.CHECKBUILDER.DicePool": "Réserve de dés", - "ROLL.CHECKBUILDER.EncounterType": "Type de rencontre", - "ROLL.CHECKBUILDER.ExpertiseDice": "Expertise Dice", - "ROLL.CHECKBUILDER.Hint": "Clic gauche pour ajouter des dés, clic droit pour en retirer.", - "ROLL.CHECKBUILDER.Options": "Options", - "ROLL.CHECKBUILDER.RollCheck": "Lancer le test", - "ROLL.CHECKBUILDER.Skill": "Skill", - "ROLL.CHECKBUILDER.StartingResults": "Pictogrammes de départ", - "ROLL.CHECKBUILDER.Weapon": "Arme", + "ROLL.DICEPOOLBUILDER.AggressionDice": "Aggression Dice", + "ROLL.DICEPOOLBUILDER.ChallengeLevel": "Challenge Level", + "ROLL.DICEPOOLBUILDER.Characteristic": "Caractéristique", + "ROLL.DICEPOOLBUILDER.ConvertConservative": "Convertir en dé de prudence", + "ROLL.DICEPOOLBUILDER.ConvertReckless": "Convertir en dé de témérité", + "ROLL.DICEPOOLBUILDER.ConvertWarning": "Il n'y a pas de dé de {type} à convertir.", + "ROLL.DICEPOOLBUILDER.ConvertBackWarning": "Il n'y a plus de dé de {type} à reconvertir.", + "ROLL.DICEPOOLBUILDER.CunningDice": "Cunning Dice", + "ROLL.DICEPOOLBUILDER.DicePool": "Réserve de dés", + "ROLL.DICEPOOLBUILDER.EncounterType": "Type de rencontre", + "ROLL.DICEPOOLBUILDER.ExpertiseDice": "Expertise Dice", + "ROLL.DICEPOOLBUILDER.Hint": "Clic gauche pour ajouter des dés, clic droit pour en retirer.", + "ROLL.DICEPOOLBUILDER.Options": "Options", + "ROLL.DICEPOOLBUILDER.RollCheck": "Lancer le test", + "ROLL.DICEPOOLBUILDER.Skill": "Skill", + "ROLL.DICEPOOLBUILDER.StartingResults": "Pictogrammes de départ", + "ROLL.DICEPOOLBUILDER.Weapon": "Arme", "ROLL.DIE.ChallengeDice": "dés de défi", "ROLL.DIE.CharacteristicDice": "dés de caractéristique", "ROLL.DIE.ConservativeDice": "dés de prudence", diff --git a/modules/CheckHelper.js b/modules/CheckHelper.js index ad63436..19ecc5a 100644 --- a/modules/CheckHelper.js +++ b/modules/CheckHelper.js @@ -1,4 +1,4 @@ -import CheckBuilder from "./applications/CheckBuilder.js"; +import DicePoolBuilder from "./applications/DicePoolBuilder.js"; import DicePool from "./DicePool.js"; /** @@ -7,42 +7,49 @@ import DicePool from "./DicePool.js"; export default class CheckHelper { /** - * Prepares a Skill check then opens the CheckBuilder dialog afterwards. - * @param {WFRP3eActor} actor The Character using the Action. + * Prepares a Skill check then opens the Dice Pool Builder afterwards. + * @param {WFRP3eActor} actor The Character using the Action. * @param {WFRP3eItem} skill The Skill check has been triggered. - * @param {string} [rollFlavor] Some flavor text to add to the Skill check's outcome description. - * @param {string} [rollSound] Some sound to play after the Skill check completion. + * @param {Object} [options] + * @param {String} [options.flavor] Some flavor text to add to the Skill check's outcome description. + * @param {String} [options.sound] Some sound to play after the Skill check completion. * @returns {Promise} */ - static async prepareSkillCheck(actor, skill, rollFlavor = null, rollSound = null) + static async prepareSkillCheck(actor, skill, {flavor = null, sound = null} = {}) { const characteristic = actor.system.characteristics[skill.system.characteristic]; const stance = actor.system.stance.current; - await new CheckBuilder( + await new DicePoolBuilder( new DicePool({ - characteristicDice: characteristic.rating - Math.abs(stance), - fortuneDice: characteristic.fortune, - expertiseDice: skill.system.trainingLevel, - conservativeDice: stance < 0 ? Math.abs(stance) : 0, - recklessDice: stance > 0 ? stance : 0 - }), - game.i18n.format("ROLL.SkillCheck", {skill: skill.name}), - {actor: actor, skill: skill, characteristic: skill.system.characteristic}, - rollFlavor, - rollSound + dice: { + characteristic: characteristic.rating - Math.abs(stance), + fortune: characteristic.fortune, + expertise: skill.system.trainingLevel, + conservative: stance < 0 ? Math.abs(stance) : 0, + reckless: stance > 0 ? stance : 0 + } + }, { + name: game.i18n.format("ROLL.SkillCheck", {skill: skill.name}), + checkData: {actor: actor, skill: skill, characteristic: skill.system.characteristic}, + flavor, + sound + }) ).render(true); } /** - * Prepares an Action check then opens the CheckBuilder dialog afterwards. + * Prepares an Action check then opens the Dice Pool Builder afterwards. * @param {WFRP3eActor} actor The Character using the Action. * @param {WFRP3eItem} action The Action that is used. * @param {string} face The Action face. - * @param {Object} [options]. + * @param {Object} [options] + * @param {WFRP3eItem} [options.weapon] The weapon used alongside the Action. + * @param {String} [options.flavor] Some flavor text to add to the Action check's outcome description. + * @param {String} [options.sound] Some sound to play after the Action check completion. * @returns {Promise} */ - static async prepareActionCheck(actor, action, face, options = {}) + static async prepareActionCheck(actor, action, face, {weapon = null, flavor = null, sound = null} = {}) { const match = action.system[face].check.match(new RegExp(/(([\w\s]+) \((\w+)\))( vs\.? ([\w\s]+))?/)); let skill = actor.itemTypes.skill[0] ?? null; @@ -57,7 +64,7 @@ export default class CheckHelper } const characteristic = actor.system.characteristics[characteristicName]; - const rollData = {actor: actor, action: action, face: face, skill: skill, characteristic: characteristicName}; + const checkData = {actor: actor, action: action, face: face, skill: skill, characteristic: characteristicName}; let stance = 0; if(actor.type === "character") @@ -65,29 +72,32 @@ export default class CheckHelper else if(actor.type === "creature") stance = actor.system.stance - if(options.weapon) - rollData.weapon = options.weapon; + if(weapon) + checkData.weapon = weapon; - await new CheckBuilder( + await new DicePoolBuilder( new DicePool({ - characteristicDice: characteristic?.rating - Math.abs(stance) ?? 0, - fortuneDice: characteristic?.fortune ?? 0, - expertiseDice: skill?.system.trainingLevel ?? 0, - conservativeDice: stance < 0 ? Math.abs(stance) : 0, - recklessDice: stance > 0 ? stance : 0, - challengeDice: action.system[face].difficultyModifiers.challengeDice + - (["melee", "ranged"].includes(action.system.type) - ? CONFIG.WFRP3e.challengeLevels.easy.challengeDice - : 0), - misfortuneDice: action.system[face].difficultyModifiers.misfortuneDice + - ((match ? match[5] === game.i18n.localize("ACTION.CHECK.TargetDefence") : false) - ? [...game.user.targets][0]?.actor.system.totalDefence ?? 0 - : 0) - }), - game.i18n.format("ROLL.ActionCheck", {action: action.name}), - rollData, - options.rollFlavor, - options.rollSound + dice: { + characteristic: characteristic?.rating - Math.abs(stance) ?? 0, + fortune: characteristic?.fortune ?? 0, + expertise: skill?.system.trainingLevel ?? 0, + conservative: stance < 0 ? Math.abs(stance) : 0, + reckless: stance > 0 ? stance : 0, + challenge: action.system[face].difficultyModifiers.challengeDice + + (["melee", "ranged"].includes(action.system.type) + ? CONFIG.WFRP3e.challengeLevels.easy.challengeDice + : 0), + misfortune: action.system[face].difficultyModifiers.misfortuneDice + + ((match ? match[5] === game.i18n.localize("ACTION.CHECK.TargetDefence") : false) + ? [...game.user.targets][0]?.actor.system.totalDefence ?? 0 + : 0) + } + }, { + name: game.i18n.format("ROLL.ActionCheck", {action: action.name}), + checkData, + flavor, + sound + }) ).render(true); } @@ -95,18 +105,20 @@ export default class CheckHelper * Prepares an initiative check. * @param {WFRP3eActor} actor The Character making the initiative check. * @param {string} characteristicName The Characteristic used for the initiative check. - * @returns {Promise} + * @returns {DicePool} */ - static async prepareInitiativeCheck(actor, characteristicName) + static prepareInitiativeCheck(actor, characteristicName) { const characteristic = actor.system.characteristics[characteristicName]; const stance = actor.system.stance.current ?? actor.system.stance; return new DicePool({ - characteristicDice: characteristic.rating - Math.abs(stance), - fortuneDice: characteristic.fortune, - conservativeDice: stance < 0 ? Math.abs(stance) : 0, - recklessDice: stance > 0 ? stance : 0 + dice: { + characteristic: characteristic.rating - Math.abs(stance), + fortune: characteristic.fortune, + conservative: stance < 0 ? Math.abs(stance) : 0, + reckless: stance > 0 ? stance : 0 + } }); } @@ -154,7 +166,7 @@ export default class CheckHelper /** * Get the universal Sigmar's comet effect. */ - static getUniversalSigmarsCometEffect(isMental) + static getUniversalSigmarsCometEffect() { return { symbolAmount: 1, @@ -173,18 +185,18 @@ export default class CheckHelper const chatMessage = game.messages.get(chatMessageId); const changes = {rolls: chatMessage.rolls}; - if(changes.rolls[0].data.effects[symbol][index].active === true) - changes.rolls[0].data.effects[symbol][index].active = false; - else if(changes.rolls[0].data.remainingSymbols[CONFIG.WFRP3e.symbols[symbol].plural] >= changes.rolls[0].data.effects[symbol][index].symbolAmount) - changes.rolls[0].data.effects[symbol][index].active = true; + if(changes.rolls[0].effects[symbol][index].active === true) + changes.rolls[0].effects[symbol][index].active = false; + else if(changes.rolls[0].remainingSymbols[CONFIG.WFRP3e.symbols[symbol].plural] >= changes.rolls[0].effects[symbol][index].symbolAmount) + changes.rolls[0].effects[symbol][index].active = true; else ui.notifications.warn(game.i18n.format("ROLL.NotEnoughSymbolToTriggerEffect", {symbol: game.i18n.localize(CONFIG.WFRP3e.symbols[symbol].name)})); - changes.rolls[0].data.remainingSymbols[CONFIG.WFRP3e.symbols[symbol].plural] = changes.rolls[0].data.effects[symbol].reduce((accumulator, effect) => { + changes.rolls[0].remainingSymbols[CONFIG.WFRP3e.symbols[symbol].plural] = changes.rolls[0].effects[symbol].reduce((accumulator, effect) => { if(effect.active) accumulator -= effect.symbolAmount; return accumulator; - }, changes.rolls[0].symbols[CONFIG.WFRP3e.symbols[symbol].plural]); + }, changes.rolls[0].resultSymbols[CONFIG.WFRP3e.symbols[symbol].plural]); chatMessage.update(changes); } diff --git a/modules/DicePool.js b/modules/DicePool.js index d696186..0b219ef 100644 --- a/modules/DicePool.js +++ b/modules/DicePool.js @@ -5,36 +5,86 @@ import ExpertiseDie from "./dice/ExpertiseDie.js"; import FortuneDie from "./dice/FortuneDie.js"; import MisfortuneDie from "./dice/MisfortuneDie.js"; import RecklessDie from "./dice/RecklessDie.js"; +import WFRP3eRoll from "./WFRP3eRoll.js"; /** * DicePool utility helps prepare WFRP3e's special dice pools. + * + * @param {Object} [startingPool] + * @param {Object} [options] + * @param {String} [options.name] + * @param {Object} [options.data] + * @param {String} [options.flavor] + * @param {String} [options.sound] */ export default class DicePool { + constructor(startingPool = {}, options = {name: game.i18n.localize("ROLL.FreeCheck"), checkData: null, flavor: null, sound: null}) + { + this.dice = Object.keys(CONFIG.WFRP3e.dice).reduce((dice, dieName) => { + dice[dieName] = startingPool.dice ? startingPool.dice[dieName] ?? 0 : 0; + return dice; + }, {}); + + this.symbols = Object.values(CONFIG.WFRP3e.symbols).reduce((symbols, symbol) => { + symbols[symbol.plural] = startingPool.symbols ? startingPool.symbols[symbol.plural] ?? 0 : 0; + return symbols; + }, {}); + + this.creatureDice = Object.keys(CONFIG.WFRP3e.attributes).reduce((creatureDice, attributeName) => { + creatureDice[attributeName] = startingPool.creatureDice ? startingPool.creatureDice[attributeName] ?? 0 : 0; + return creatureDice; + }, {}); + + mergeObject(this, options); + } + /** - * @param {Object} [dicePool] + * Transforms the DicePool values into a rollable formula. + * @returns {string} A rollable formula. */ - constructor(dicePool = {}) + get formula() { - Object.keys(CONFIG.WFRP3e.dice).forEach((diceName) => { - this[diceName + "Dice"] = dicePool[diceName + "Dice"] ?? 0; - }); + return [ + this.dice.characteristic + "d" + CharacteristicDie.DENOMINATION, + (this.dice.fortune + this.creatureDice.aggression + this.creatureDice.cunning) + "d" + FortuneDie.DENOMINATION, + (this.dice.expertise + this.creatureDice.expertise) + "d" + ExpertiseDie.DENOMINATION, + this.dice.conservative + "d" + ConservativeDie.DENOMINATION, + this.dice.reckless + "d" + RecklessDie.DENOMINATION, + this.dice.challenge + "d" + ChallengeDie.DENOMINATION, + this.dice.misfortune + "d" + MisfortuneDie.DENOMINATION + ].filter((d) => { + const test = d.split(/([0-9]+)/); + return test[1] > 0; + }).join("+"); + } - Object.values(CONFIG.WFRP3e.symbols).forEach((symbol) => { - this[symbol.plural] = dicePool[symbol.plural] ?? 0; - }); + /** + * Adds another DicePool to the current one. + * @param {DicePool} [otherDicePool] + */ + addDicePool(otherDicePool) + { + if(otherDicePool.dice) + Object.keys(CONFIG.WFRP3e.dice).forEach((dieName) => { + this.dice[dieName] += otherDicePool.dice[dieName]; + }); - Object.keys(CONFIG.WFRP3e.attributes).forEach((attribute) => { - const attributeName = attribute[0].toUpperCase() + attribute.slice(1, attribute.length); + if(otherDicePool.symbols) + Object.values(CONFIG.WFRP3e.symbols).forEach((symbol) => { + this.symbols[symbol.plural] += otherDicePool.symbols[symbol.plural]; + }); - this["creatures" + attributeName + "Dice"] = dicePool["creatures" + attributeName + "Dice"] ?? 0; - }); + if(otherDicePool.creatureDice) + Object.keys(CONFIG.WFRP3e.attributes).forEach((attributeName) => { + this.creatureDice[attributeName] += otherDicePool.creatureDice[attributeName]; + }); } /** * Converts any remaining Characteristic Die from the Dice Pool into a Conservative/Reckless Die. If no Characteristic Die remains, adds a Conservative/Reckless Die instead. * @param {string} type The type of die the Characteristic Die must be converted to. - * @param {number} times The amount of conversion to perform(defaults to 1). + * @param {number} times The amount of conversion to perform (defaults to 1). */ convertCharacteristicDie(type, times = 1) { @@ -47,62 +97,58 @@ export default class DicePool for(let i = 0; i < times; i++) { if(revert) { - if(this[type + "Dice"] > 0) { - this[type + "Dice"]--; - this.characteristicDice++; + if(this.dice[type] > 0) { + this.dice[type]--; + this.dice.characteristic++; } else - ui.notifications.warn(game.i18n.format("ROLL.CHECKBUILDER.ConvertBackWarning", {type: type})); + ui.notifications.warn(game.i18n.format("ROLL.DICEPOOLBUILDER.ConvertBackWarning", {type: type})); } else { - if(this.characteristicDice > 0) { - this.characteristicDice--; - this[type + "Dice"]++; + if(this.dice.characteristic > 0) { + this.dice.characteristic--; + this.dice[type]++; } else - ui.notifications.warn(game.i18n.format("ROLL.CHECKBUILDER.ConvertWarning", {type: "characteristic"})); + ui.notifications.warn(game.i18n.format("ROLL.DICEPOOLBUILDER.ConvertWarning", {type: "characteristic"})); } } } /** - * Adds another DicePool to the current one. - * @param {DicePool} [otherDicePool] + * */ - addDicePool(otherDicePool) + async roll() { - Object.keys(CONFIG.WFRP3e.dice).forEach((diceName) => { - this[diceName + "Dice"] += otherDicePool[diceName + "Dice"] ?? 0; - }); + const roll = WFRP3eRoll.create( + this.renderDiceExpression(), + this.checkData?.actor ? this.checkData.actor.getRollData() : {}, + {checkData: this.checkData, flavor: this.flavor, startingSymbols: this.symbols} + ); - Object.values(CONFIG.WFRP3e.symbols).forEach((symbol) => { - this[symbol.plural] += otherDicePool[symbol.plural] ?? 0; + roll.toMessage({ + flavor: this.name, + speaker: {actor: this.checkData?.actor}, + user: game.user.id }); - Object.keys(CONFIG.WFRP3e.attributes).forEach((attribute) => { - const attributeName = attribute[0].toUpperCase() + attribute.slice(1, attribute.length); + if(this.sound) + AudioHelper.play({src: this.sound}, true); - this["creatures" + attributeName + "Dice"] += otherDicePool["creatures" + attributeName + "Dice"] ?? 0; - }); - } + if(this.checkData?.actor?.type === "creature") { + const updates = {system: {attributes: {}}}; - /** - * Transforms the DicePool into a rollable expression. - * @returns {string} a applications expression that can be used to roll the applications pool - */ - renderDiceExpression() - { - return [ - this.characteristicDice + "d" + CharacteristicDie.DENOMINATION, - (this.fortuneDice + this.creaturesAggressionDice + this.creaturesCunningDice) + "d" + FortuneDie.DENOMINATION, - (this.expertiseDice + this.creaturesExpertiseDice) + "d" + ExpertiseDie.DENOMINATION, - this.conservativeDice + "d" + ConservativeDie.DENOMINATION, - this.recklessDice + "d" + RecklessDie.DENOMINATION, - this.challengeDice + "d" + ChallengeDie.DENOMINATION, - this.misfortuneDice + "d" + MisfortuneDie.DENOMINATION - ].filter((d) => { - const test = d.split(/([0-9]+)/); - return test[1] > 0; - }).join("+"); + for(const [attributeName, creatureDice] of Object.entries(this.creatureDice)) { + if(creatureDice > 0) { + updates.system.attributes[attributeName] = { + current: this.checkData.actor.system.attributes[attributeName].current - creatureDice + }; + } + } + + this.checkData.actor.update(updates); + } + + return roll; } } \ No newline at end of file diff --git a/modules/WFRP3eRoll.js b/modules/WFRP3eRoll.js index 69a1f33..4ebcca0 100644 --- a/modules/WFRP3eRoll.js +++ b/modules/WFRP3eRoll.js @@ -1,4 +1,5 @@ import CheckHelper from "./CheckHelper.js"; +import WFRP3eDie from "./dice/WFRP3eDie.js"; /** @inheritDoc */ export default class WFRP3eRoll extends Roll @@ -6,315 +7,254 @@ export default class WFRP3eRoll extends Roll static CHAT_TEMPLATE = "systems/wfrp3e/templates/chatmessages/roll.hbs"; static TOOLTIP_TEMPLATE = "systems/wfrp3e/templates/chatmessages/roll-tooltip.hbs"; - /** - * @param {string} formula - * @param {DicePool} dicePool - * @param {Object} [options] - */ - constructor(formula, dicePool, options = {}) + constructor(formula, data= {}, options = {}) { - super(formula); - - this.symbols = { - ...Object.values(CONFIG.WFRP3e.symbols).reduce((object, symbol) => { - object[symbol.plural] = isNaN(dicePool[symbol.plural]) ? 0 : +dicePool[symbol.plural]; - return object; - }, {}) - }; + super(formula, data, options); this.hasSpecialDice = false; this.hasStandardDice = false; - this.addedResults = []; - this.data.symbols = CONFIG.WFRP3e.symbols; - if(options.data) { - this.data = mergeObject(this.data, options.data); + if(options.startingSymbols) + this.resultSymbols = options.startingSymbols; + else + this.resultSymbols = Object.values(CONFIG.WFRP3e.symbols).reduce((object, symbol) => { + object[symbol.plural] = 0; + return object; + }, {}); - if(this.data.action) { - this.data.effects = { - ...Object.entries(this.data.action.system[this.data.face].effects).reduce((object, effectArray) => { + if(this.options.checkData?.action) { + this.effects = { + ...Object.entries(this.options.checkData.action.system[this.options.checkData.face].effects) + .reduce((object, effectArray) => { object[effectArray[0]] = effectArray[1].reduce((array, effect) => { effect.active = false; array.push(effect); return array; }, []); - return object; }, {}) - }; - this.data.effects.boon.push(CheckHelper.getUniversalBoonEffect(CONFIG.WFRP3e.characteristics[this.data.characteristic].type === "mental")); - this.data.effects.bane.push(CheckHelper.getUniversalBaneEffect(CONFIG.WFRP3e.characteristics[this.data.characteristic].type === "mental")); + }; + this.effects.boon.push(CheckHelper.getUniversalBoonEffect(CONFIG.WFRP3e.characteristics[this.options.checkData.characteristic].type === "mental")); + this.effects.bane.push(CheckHelper.getUniversalBaneEffect(CONFIG.WFRP3e.characteristics[this.options.checkData.characteristic].type === "mental")); - if(["melee", "ranged"].includes(this.data.action.system.type)) - this.data.effects.sigmarsComet.push(CheckHelper.getUniversalSigmarsCometEffect()); - } + if(["melee", "ranged"].includes(this.options.checkData.action.system.type)) + this.effects.sigmarsComet.push(CheckHelper.getUniversalSigmarsCometEffect()); } - - if(Object.hasOwn(options, 'flavor')) - this.flavor = options.flavor; } /** @inheritDoc */ - evaluate({minimize = false, maximize = false} = {}) + async _evaluate({minimize = false, maximize = false} = {}) { - if(this._evaluated) - throw new Error("This Roll object has already been rolled."); + // Step 1 - Replace intermediate terms with evaluated numbers + const intermediate = []; + for(let term of this.terms) { + if(!(term instanceof RollTerm)) + throw new Error("Roll evaluation encountered an invalid term which was not a RollTerm instance"); - // Step 1 - evaluate any inner Rolls and recompile the formula - let hasInner = false; + if(term instanceof WFRP3eDie) + this.hasSpecialDice = true; + else if(term instanceof DiceTerm) + this.hasStandardDice = true; - this.terms = this.terms.map((term) => { - if(term instanceof WFRP3eRoll) { - hasInner = true; - term.evaluate({minimize, maximize}); + if(term.isIntermediate) { + await term.evaluate({minimize, maximize, async: true}); this._dice = this._dice.concat(term.dice); - return `${term.total}`; + term = new NumericTerm({number: term.total, options: term.options}); } - return term; - }); - - // Step 2 - if inner rolls occurred, re-compile the formula and re-identify terms - if(hasInner) { - const formula = this.constructor.cleanFormula(this.terms); - this.terms = this._identifyTerms(formula); + intermediate.push(term); } + this.terms = intermediate; - // Step 3 - evaluate any remaining terms and return any non-special dialogs to the total. - this.results = this.terms.map((term) => { - if(game.symbols.diceterms.includes(term.constructor)) { - if(term.evaluate) - term.evaluate({minimize, maximize}); + // Step 2 - Simplify remaining terms + this.terms = this.constructor.simplifyTerms(this.terms); - this.hasSpecialDice = true; + // Step 3 - Evaluate remaining terms + for(const term of this.terms) + if(!term._evaluated) + await term.evaluate({minimize, maximize, async: true}); + + // Step 4 - Retrieve all FFG results and combine into a single total. + if(this.hasSpecialDice) { + this.terms.forEach((term) => { + if(term instanceof WFRP3eDie) { + Object.keys(this.resultSymbols).forEach((symbolPlural) => { + this.resultSymbols[symbolPlural] += parseInt(term.resultSymbols[symbolPlural]); + + if(symbolPlural === "successes") + this.resultSymbols[symbolPlural] += parseInt(term.resultSymbols.righteousSuccesses); + }); + } + }); - return 0; + // Step 5 - Calculate actual results by cancelling out successes with challenges, boons with banes, etc. + if(this.resultSymbols.successes < this.resultSymbols.challenges) { + this.resultSymbols.challenges -= parseInt(this.resultSymbols.successes); + this.resultSymbols.successes = 0; } else { - if(term.evaluate) { - if(!(term instanceof OperatorTerm)) - this.hasStandardDice = true; + this.resultSymbols.successes -= parseInt(this.resultSymbols.challenges); + this.resultSymbols.challenges = 0; + } - return term.evaluate({minimize, maximize}).total; - } - else - return term; + if(this.resultSymbols.boons < this.resultSymbols.banes) { + this.resultSymbols.banes -= parseInt(this.resultSymbols.boons); + this.resultSymbols.boons = 0; + } + else { + this.resultSymbols.boons -= parseInt(this.resultSymbols.banes); + this.resultSymbols.banes = 0; } + } + + this.remainingSymbols = this.resultSymbols; + + if(this.resultSymbols.successes && this.options.checkData?.action) + this.options.checkData.action.exhaustAction(this.options.checkData.face); + + // Step 6 - Evaluate the final expression + this._total = this._evaluateTotal(); + + return this; + } + + /** @inheritDoc */ + _evaluateSync({minimize = false, maximize = false} = {}) + { + // Step 1 - Replace intermediate terms with evaluated numbers + this.terms = this.terms.map(term => { + if(!(term instanceof RollTerm)) + throw new Error("Roll evaluation encountered an invalid term which was not a RollTerm instance"); + + if(term instanceof WFRP3eDie) + this.hasSpecialDice = true; + else if(term instanceof DiceTerm) + this.hasStandardDice = true; + + if(term.isIntermediate) { + term.evaluate({minimize, maximize, async: false}); + this._dice = this._dice.concat(term.dice); + return new NumericTerm({number: term.total, options: term.options}); + } + + return term; }); - // Step 4 - safely evaluate the final total - const total = Roll.safeEval(this.results.join(" ")); + // Step 2 - Simplify remaining terms + this.terms = this.constructor.simplifyTerms(this.terms); - if(!Number.isNumeric(total)) - throw new Error(game.i18n.format("DICE.ErrorNonNumeric", {formula: this.formula})); + // Step 3 - Evaluate remaining terms + for(const term of this.terms) + if(!term._evaluated) + term.evaluate({minimize, maximize, async: false}); - // Step 5 - Retrieve all FFG results and combine into a single total. + // Step 4 - Retrieve all FFG results and combine into a single total. if(this.hasSpecialDice) { this.terms.forEach((term) => { - if(game.symbols.diceterms.includes(term.constructor)) { - Object.values(CONFIG.WFRP3e.symbols).forEach((symbol) => { - this.symbols[symbol.plural] += parseInt(term.symbols[symbol.plural]); + if(term instanceof WFRP3eDie) { + Object.keys(this.resultSymbols).forEach((symbolPlural) => { + this.resultSymbols[symbolPlural] += parseInt(term.resultSymbols[symbolPlural]); - if(symbol.plural === "successes") - this.symbols[symbol.plural] += parseInt(term.symbols.righteousSuccesses); + if(symbolPlural === "successes") + this.resultSymbols[symbolPlural] += parseInt(term.resultSymbols.righteousSuccesses); }); } }); - // Step 6 - Calculate actual results by cancelling out successes with challenges, boons with banes, etc. - if(this.symbols.successes < this.symbols.challenges) { - this.symbols.challenges -= parseInt(this.symbols.successes); - this.symbols.successes = 0; + // Step 5 - Calculate actual results by cancelling out successes with challenges, boons with banes, etc. + if(this.resultSymbols.successes < this.resultSymbols.challenges) { + this.resultSymbols.challenges -= parseInt(this.resultSymbols.successes); + this.resultSymbols.successes = 0; } else { - this.symbols.successes -= parseInt(this.symbols.challenges); - this.symbols.challenges = 0; + this.resultSymbols.successes -= parseInt(this.resultSymbols.challenges); + this.resultSymbols.challenges = 0; } - if(this.symbols.boons < this.symbols.banes) { - this.symbols.banes -= parseInt(this.symbols.boons); - this.symbols.boons = 0; + if(this.resultSymbols.boons < this.resultSymbols.banes) { + this.resultSymbols.banes -= parseInt(this.resultSymbols.boons); + this.resultSymbols.boons = 0; } else { - this.symbols.boons -= parseInt(this.symbols.banes); - this.symbols.banes = 0; + this.resultSymbols.boons -= parseInt(this.resultSymbols.banes); + this.resultSymbols.banes = 0; } } - // Store final outputs - this._total = total; - this._evaluated = true; + this.remainingSymbols = this.resultSymbols; - this.data.remainingSymbols = this.symbols; + if(this.resultSymbols.successes && this.options.checkData?.action) + this.action.exhaustAction(this.options.checkData.face); - if(this.symbols.successes && this.data?.action) - this.data.action.exhaustAction(this.data.face); + // Step 6 - Evaluate the final expression + this._total = this._evaluateTotal(); return this; } /** @inheritDoc */ - roll() - { - return this.evaluate(); - } + _evaluateTotal() { + const expression = this.terms.map(term => term instanceof WFRP3eDie ? 0 : term.total).join(" "); + const total = this.constructor.safeEval(expression); - /** @inheritDoc */ - getTooltip() - { - const parts = this.dice.map((die) => { - const cls = die.constructor; - let isSpecial = "notSpecial"; - - if(game.symbols.diceterms.includes(cls)) - isSpecial = "isSpecial"; - - let countText = game.i18n.format("ROLL.AMOUNT." + cls.name, {nb: die.results.length}); - - if(die.results.length > die.number) - countText += " " + game.i18n.format("ROLL.AMOUNT.ExplodedDie", {nb: die.results.length - die.number}); - - return { - formula: die.formula, - total: die.total, - faces: die.faces, - flavor: die.options.flavor, - countText: countText, - isSpecial: game.symbols.diceterms.includes(cls), - notSpecial: !game.symbols.diceterms.includes(cls), - rolls: die.results.map((roll) => { - return { - result: die.getResultLabel(roll), - classes: [cls.name.toLowerCase(), isSpecial, "d" + die.faces, roll.rerolled ? "rerolled" : null, roll.exploded ? "exploded" : null, roll.discarded ? "discarded" : null].filterJoin(" ") - }; - }) - }; - }); - - parts.addedResults = this.addedResults; - parts.flavor = this.flavor; - - return renderTemplate(this.constructor.TOOLTIP_TEMPLATE, {parts}); + if(!Number.isNumeric(total)) { + throw new Error(game.i18n.format("DICE.ErrorNonNumeric", {formula: this.formula})); + } + return total; } /** @inheritDoc */ - async render(chatOptions = {}) + async render({flavor, template = this.constructor.CHAT_TEMPLATE, isPrivate = false} = {}) { - chatOptions = mergeObject({ - user: game.user.id, - flavor: null, - template: this.constructor.CHAT_TEMPLATE, - blind: false, - }, chatOptions); - - const isPrivate = chatOptions.isPrivate; - - // Execute the roll, if needed if(!this._evaluated) - this.roll(); - - // Define chat data - if(this.data) { - this.data.additionalFlavorText = this.flavor; - this.data.symbols = CONFIG.WFRP3e.symbols; - } - else - this.data = { - additionalFlavorText: this.flavor, - symbols: CONFIG.WFRP3e.symbols - }; + await this.evaluate({async: true}); const chatData = { formula: isPrivate ? "???" : this._formula, - flavor: isPrivate ? null : chatOptions.flavor, - user: chatOptions.user, + flavor: isPrivate ? null : flavor, + user: game.user.id, tooltip: isPrivate ? "" : await this.getTooltip(), total: isPrivate ? "?" : Math.round(this.total * 100) / 100, - symbols: isPrivate ? {} : this.symbols, - specialDice: isPrivate ? {} : this.dice.map((die) => { - const cls = die.constructor; - - return { - isSpecialDie: game.symbols.diceterms.includes(cls), - rolls: die.results.map((roll) => {return {result: die.getResultLabel(roll)};}) - }; - }), + action: this.options?.data?.action, + effects: this.effects, + face: this.options?.data?.face, hasSpecialDice: this.hasSpecialDice, hasStandardDice: this.hasStandardDice, - hasSuccess: this.dice.length > 0, - data: this.data, - addedResults: this.addedResults, - publicRoll: !chatOptions.isPrivate, + publicRoll: !isPrivate, + remainingSymbols: isPrivate ? {} : this.remainingSymbols, + resultSymbols: isPrivate ? {} : this.resultSymbols, + specialDieResultLabels: isPrivate ? {} : this.dice.filter(die => die instanceof WFRP3eDie) + .reduce((resultLabels, die) => { + die.results.forEach(result => resultLabels.push(die.getResultLabel(result))); + return resultLabels; + }, []), + symbols: CONFIG.WFRP3e.symbols, }; - // Render the roll display template - return renderTemplate(chatOptions.template, chatData); - } - - /** @inheritDoc */ - toMessage(messageData = {}, {rollMode = null, create = true} = {}) - { - // Perform the roll, if it has not yet been rolled - if(!this._evaluated) - this.evaluate(); - - const rMode = rollMode || messageData.rollMode || game.settings.get("core", "rollMode"); - const template = CONST.CHAT_MESSAGE_TYPES.ROLL; - - if(["gmroll", "blindroll"].includes(rMode)) - messageData.whisper = ChatMessage.getWhisperRecipients("GM"); - if(rMode === "blindroll") - messageData.blind = true; - if(rMode === "selfroll") - messageData.whisper = [game.user.id]; - - // Prepare chat data - messageData = mergeObject({ - user: game.user.id, - type: template, - content: this.total - }, messageData, messageData.data); - - messageData.roll = this; - - Hooks.call("specialDiceMessage", this); - - const message = new ChatMessage(messageData); - - if(rMode) - message.applyRollMode(rollMode); - - // Either create or return the data - return create ? ChatMessage.create(message) : message; + return renderTemplate(template, chatData); } /** @inheritDoc */ toJSON() { - const json = super.toJSON(); - - json.symbols = this.symbols; - json.hasSpecialDice = this.hasSpecialDice; - json.hasStandardDice = this.hasStandardDice; - json.data = this.data; - json.addedResults = this.addedResults; - json.flavor = this.flavor; - - return json; + return mergeObject(super.toJSON(), { + effects: this.effects, + hasSpecialDice: this.hasSpecialDice, + hasStandardDice: this.hasStandardDice, + remainingSymbols: this.remainingSymbols, + resultSymbols: this.resultSymbols + }); } /** @inheritDoc */ static fromData(data) { - const roll = super.fromData(data); - - roll.symbols = data.symbols; - roll.hasSpecialDice = data.hasSpecialDice; - roll.hasStandardDice = data.hasStandardDice; - roll.data = data.data; - roll.addedResults = data.addedResults; - roll.flavor = data.flavor; - - return roll; + return mergeObject(super.fromData(data), { + effects: data.effects, + hasSpecialDice: data.hasSpecialDice, + hasStandardDice: data.hasStandardDice, + remainingSymbols: data.remainingSymbols, + resultSymbols: data.resultSymbols + }); } } \ No newline at end of file diff --git a/modules/applications/CheckBuilder.js b/modules/applications/DicePoolBuilder.js similarity index 50% rename from modules/applications/CheckBuilder.js rename to modules/applications/DicePoolBuilder.js index a1ae48e..8f11b76 100644 --- a/modules/applications/CheckBuilder.js +++ b/modules/applications/DicePoolBuilder.js @@ -1,40 +1,22 @@ -import CheckHelper from "../CheckHelper.js"; import DicePool from "../DicePool.js"; -import WFRP3eRoll from "../WFRP3eRoll.js"; /** @inheritDoc */ -export default class CheckBuilder extends FormApplication +export default class DicePoolBuilder extends FormApplication { - /** - * @param {DicePool} [object] - * @param {string} [rollName] - * @param {Object} [rollData] - * @param {?string} [rollFlavor] - * @param {?string} [rollSound] - */ - constructor(object = new DicePool(), rollName = game.i18n.localize("ROLL.FreeCheck"), rollData = null, rollFlavor= null, rollSound= null) + constructor(object = new DicePool(), options = {}) { - super(); - - this.object = object; - - this.roll = { - data: rollData, - name: rollName, - sound: rollSound, - flavor: rollFlavor, - }; + super(object, options); } /** @inheritDoc */ get title() { - if(this.roll.data) { - if(this.roll.data.action) - return game.i18n.format("ROLL.ActionCheckBuilder", {action: this.roll.data.action.name}); - else if(this.roll.data.skill) - return game.i18n.format("ROLL.SkillCheckBuilder", {skill: this.roll.data.skill.name}); - else if(this.roll.data.combat) + if(this.checkData) { + if(this.checkData.action) + return game.i18n.format("ROLL.ActionCheckBuilder", {action: this.check.data.action.name}); + else if(this.checkData.skill) + return game.i18n.format("ROLL.SkillCheckBuilder", {skill: this.check.data.skill.name}); + else if(this.checkData.combat) return game.i18n.localize("ROLL.InitiativeCheckBuilder"); } @@ -46,8 +28,8 @@ export default class CheckBuilder extends FormApplication { return { ...super.defaultOptions, - classes: ["wfrp3e", "check-builder"], - template: "systems/wfrp3e/templates/applications/check-builder.hbs", + classes: ["wfrp3e", "dice-pool-builder"], + template: "systems/wfrp3e/templates/applications/dice-pool-builder.hbs", width: 640 }; } @@ -57,7 +39,7 @@ export default class CheckBuilder extends FormApplication { const data = { ...super.getData(), - challengeLevel: ["melee", "ranged"].includes(this.roll.data?.action?.system.type) ? "easy" : "simple", + challengeLevel: ["melee", "ranged"].includes(this.object.checkData?.action?.system.type) ? "easy" : "simple", challengeLevels: CONFIG.WFRP3e.challengeLevels, diceIcons: { ...Object.entries(CONFIG.WFRP3e.dice).reduce((object, dieType) => { @@ -65,7 +47,7 @@ export default class CheckBuilder extends FormApplication return object; }, {}) }, - flavor: this.roll.flavor, + flavor: this.object.flavor, isGM: game.user.isGM, sounds: [], users: [{name: "Send To All", id: "all"}] @@ -78,11 +60,11 @@ export default class CheckBuilder extends FormApplication }); } - if(this.roll.data) { - this.roll.data.challengeLevel = data.challengeLevel; + if(this.object.checkData) { + this.object.checkData.challengeLevel = data.challengeLevel; - if(this.roll.data.actor) { - data.skills = this.roll.data.actor.itemTypes.skill; + if(this.object.checkData.actor) { + data.skills = this.object.checkData.actor.itemTypes.skill; data.characteristics = Object.entries(CONFIG.WFRP3e.characteristics).reduce((object, characteristic) => { if(characteristic[0] !== "varies") @@ -90,18 +72,18 @@ export default class CheckBuilder extends FormApplication return object; }, {}); - if(this.roll.data.actor.type === "creature") - data.attributes = this.roll.data.actor.system.attributes; + if(this.object.checkData.actor.type === "creature") + data.attributes = this.object.checkData.actor.system.attributes; } - if(this.roll.data.skill) - data.skill = this.roll.data.skill; + if(this.object.checkData.skill) + data.skill = this.object.checkData.skill; - if(this.roll.data.characteristic) - data.characteristic = this.roll.data.characteristic; + if(this.object.checkData.characteristic) + data.characteristic = this.object.checkData.characteristic; - if(this.roll.data.action) { - data.action = this.roll.data.action + if(this.object.checkData.action) { + data.action = this.object.checkData.action if(["melee", "ranged"].includes(data.action.system.type)) { data.availableWeapons = data.action.actor.itemTypes.weapon.filter(weapon => { @@ -112,12 +94,12 @@ export default class CheckBuilder extends FormApplication }, []).includes(weapon.system.group); }); - data.weapon = this.roll.data.weapon ?? Object.values(data.availableWeapons)[0]; - this.roll.data.weapon = Object.values(data.availableWeapons)[0]; + data.weapon = this.object.checkData.weapon ?? Object.values(data.availableWeapons)[0]; + this.object.checkData.weapon = Object.values(data.availableWeapons)[0]; } } - if(this.roll.data.combat) + if(this.object.checkData.combat) data.encounterTypes = CONFIG.WFRP3e.encounterTypes; } @@ -139,15 +121,99 @@ export default class CheckBuilder extends FormApplication .click(this._onConversionButtonLeftClick.bind(this, html)) .contextmenu(this._onConversionButtonRightClick.bind(this, html)); - html.find(".attribute-dice input").change(this._onAttributeDiceChange.bind(this, html)); - html.find(".challenge-level-select").change(this._onChallengeLevelSelectChange.bind(this, html)); html.find(".characteristic-select").change(this._onCharacteristicSelectChange.bind(this, html)); html.find(".skill-select").change(this._onSkillSelectChange.bind(this, html)); html.find(".weapon-select").change(this._onWeaponSelectChange.bind(this)); html.find(".extend-button").click(this._onExtendButtonClick.bind(this)); - html.find(".roll-check-button").click(this._onRollCheckButtonClick.bind(this, html)); + } + + /** @inheritDoc */ + async _onChangeInput(event) + { + await super._onChangeInput(event); + + setProperty( + this.object, + event.currentTarget.name, + isNaN(event.currentTarget.value) ? event.currentTarget.value : Number(event.currentTarget.value) + ); + + this._updatePreview(); + } + + /** @inheritDoc */ + _updateObject() + { + // if sound was not passed search for sound dropdown value + if(!this.sound) { + const sound = html.find(".sound-selection")?.[0]?.value; + + if(sound) { + this.sound = sound; + + if(this?.checkData?.item) { + let entity; + let entityData; + + if(!this?.check?.item?.flags?.uuid) { + entity = CONFIG["Actor"].documentClass.collection.get(this.check.data.actor.id); + entityData = {_id: this.check.item.id}; + } + else { + const parts = this.check.item.flags.uuid.split("."); + const [entityName, entityId, embeddedName, embeddedId] = parts; + entity = CONFIG[entityName].documentClass.collection.get(entityId); + + if(parts.length === 4) + entityData = {_id: embeddedId}; + } + + setProperty(entityData, "flags.ffgsound", sound); + entity.updateOwnedItem(entityData); + } + } + } + + if(!this.flavor) { + const flavor = html.find(".flavor-text")?.[0]?.value; + + if(flavor) + this.flavor = flavor; + } + + const sentToPlayer = html.find(".user-selection")?.[0]?.value; + + if(sentToPlayer) { + let container = $(`
`)[0]; + this.object.renderAdvancedPreview(container); + + const messageText = `
+
${game.i18n.localize("WFRP3e.SentDicePoolRollHint")}
+ ${$(container).html()} + +
`; + + let chatOptions = { + user: game.user.id, + content: messageText, + flags: { + specialDice: { + roll: this.check, + dicePool: this.object, + description: this.description, + }, + }, + }; + + if(sentToPlayer !== "all") + chatOptions.whisper = [sentToPlayer]; + + ChatMessage.create(chatOptions); + } + + this.object.roll(); } /** @@ -157,19 +223,13 @@ export default class CheckBuilder extends FormApplication */ _synchronizeInputs(html) { - html.find(".dice-pool-table input").each((key, value) => { - value.value = this.object[value.name]; + html.find(".dice-pool-table input, .symbols-pool-container input").each((key, input) => { + input.value = getProperty(this.object, input.name); }); - html.find(".symbols-pool-container input").each((key, value) => { - value.value = this.object[value.name]; - }); - - this._updatePreview(html); + this._updatePreview(); } - _updateObject() {} - /** * Gets the clicked input element. * @param {HTMLElement} target The clicked element. @@ -200,12 +260,16 @@ export default class CheckBuilder extends FormApplication */ _onPoolContainerInputLeftClick(html, event) { - const input = this._getClickedInput(event.currentTarget); + event.preventDefault(); + event.stopPropagation(); - input.value++; + const input = this._getClickedInput(event.currentTarget); - this.object[input.name] = parseInt(input.value); - this._updatePreview(html); + if(event.target !== input) { + input.value++; + setProperty(this.object, input.name, parseInt(input.value)); + this._updatePreview(); + } } /** @@ -216,12 +280,14 @@ export default class CheckBuilder extends FormApplication */ _onPoolContainerInputRightClick(html, event) { + event.stopPropagation(); + const input = this._getClickedInput(event.currentTarget); - if(input.value > 0) { + if(event.target !== input && input.value > 0) { input.value--; - this.object[input.name] = parseInt(input.value); - this._updatePreview(html); + setProperty(this.object, input.name, parseInt(input.value)); + this._updatePreview(); } } @@ -263,33 +329,20 @@ export default class CheckBuilder extends FormApplication this._synchronizeInputs(html); } - /** - * Performs follow-up operations after changes on an attribute dice. - * @param {Event} event - * @param {any} html - * @private - */ - _onAttributeDiceChange(html, event) - { - const input = event.currentTarget; - - this.object[input.name] = parseInt(input.value); - - this._updatePreview(html); - } - /** * Updates the dice pool and symbol pool previews. - * @param {any} html * @private */ - _updatePreview(html) + _updatePreview() { - const dicePoolDiv = html.find(".dice-pool")[0]; - const symbolsPoolDiv = html.find(".symbols-pool")[0]; + const dicePoolDiv = $(this.element).find(".dice-pool")[0]; + const symbolsPoolDiv = $(this.element).find(".symbols-pool")[0]; - this._renderDicePoolPreview(dicePoolDiv); - this._renderSymbolsPoolPreview(symbolsPoolDiv); + if(this.object.dice) + this._renderDicePoolPreview(dicePoolDiv); + + if(this.object.symbols) + this._renderSymbolsPoolPreview(symbolsPoolDiv); } /** @@ -301,7 +354,7 @@ export default class CheckBuilder extends FormApplication { container.innerHTML = ""; - const totalDice = Object.keys(CONFIG.WFRP3e.dice).reduce((accumulator, diceName) => accumulator + +this.object[diceName + "Dice"], 0); + const totalDice = Object.values(this.object.dice).reduce((accumulator, dice) => accumulator + +dice, 0); // Adjust dice icons' size. let height = 48; @@ -321,19 +374,19 @@ export default class CheckBuilder extends FormApplication } // Add as many dice icons as needed. - Object.entries(CONFIG.WFRP3e.dice).forEach((dieType, index) => { - let diceAmount = this.object[dieType[0] + "Dice"]; - - if(dieType[0] === "fortune") - diceAmount += this.object.creaturesAggressionDice + this.object.creaturesCunningDice; - else if(dieType[0] === "expertise") - diceAmount += this.object.creaturesExpertiseDice; + Object.entries(this.object.dice).forEach((dice, index) => { + if(this.object.creatureDice) { + if(dice[0] === "fortune") + dice[1] += this.object.creatureDice.aggression + this.object.creatureDice.cunning; + else if(dice[0] === "expertise") + dice[1] += this.object.creatureDice.expertise; + } - for(let i = 0; i < diceAmount; i++) { + for(let i = 0; i < dice[1]; i++) { const img = document.createElement("img"); - img.classList.add("special-die"); - img.src = dieType[1].icon; + img.classList.add("special-die", dice[0]); + img.src = CONFIG.WFRP3e.dice[dice[0]].icon; img.width = width; img.height = height; @@ -354,7 +407,7 @@ export default class CheckBuilder extends FormApplication container.innerHTML = ""; Object.values(CONFIG.WFRP3e.symbols).forEach((symbol) => { - for(let i = 0; i < this.object[symbol.plural]; i++) { + for(let i = 0; i < this.object.symbols[symbol.plural]; i++) { const span = document.createElement("span"); span.classList.add("wfrp3e-font"); @@ -376,11 +429,13 @@ export default class CheckBuilder extends FormApplication */ _onChallengeLevelSelectChange(html, event) { + event.preventDefault(); + const challengeLevel = CONFIG.WFRP3e.challengeLevels[event.currentTarget.value]; - this.roll.data.challengeLevel = event.currentTarget.value; - this.object.challengeDice = this.roll.action?.system[this.roll.face].difficultyModifiers.challengeDice ?? 0 + - (["melee", "ranged"].includes(this.roll.action?.system.type) ? 1 : 0) + + this.object.checkData.challengeLevel = event.currentTarget.value; + this.object.dice.challenge = this.object.checkData.action?.system[this.object.checkData.face].difficultyModifiers.challengeDice ?? 0 + + (["melee", "ranged"].includes(this.object.checkData.action?.system.type) ? 1 : 0) + challengeLevel.challengeDice; this._synchronizeInputs(html, html); @@ -394,14 +449,18 @@ export default class CheckBuilder extends FormApplication */ _onCharacteristicSelectChange(html, event) { - const characteristic = this.roll.data.actor.system.characteristics[event.currentTarget.value]; - const stance = this.roll.data.actor.system.stance.current; + event.preventDefault(); - this.roll.data.skill = event.currentTarget.value; - this.object.characteristicDice = characteristic.value - Math.abs(stance); - this.object.fortuneDice = characteristic.fortune; - this.object.conservativeDice = stance > 0 ? stance : 0; - this.object.recklessDice = stance < 0 ? Math.abs(stance) : 0; + const characteristic = this.object.checkData.actor.system.characteristics[event.currentTarget.value]; + const stance = this.object.checkData.actor.system.stance.current; + + this.object.checkData.skill = event.currentTarget.value; + mergeObject(this.object.dice, { + characteristic: characteristic.value - Math.abs(stance), + fortune: characteristic.fortune, + conservative: stance < 0 ? Math.abs(stance) : 0, + reckless: stance > 0 ? stance : 0 + }); this._synchronizeInputs(html); } @@ -414,10 +473,12 @@ export default class CheckBuilder extends FormApplication */ _onSkillSelectChange(html, event) { - const skill = this.roll.data.actor.itemTypes.skill.find(skill => skill._id === event.currentTarget.value) + event.preventDefault(); - this.roll.data.skill = skill; - this.object.expertiseDice = skill.system.trainingLevel; + const skill = this.object.checkData.actor.itemTypes.skill.find(skill => skill._id === event.currentTarget.value) + + this.object.checkData.skill = skill; + this.object.dice.expertise = skill.system.trainingLevel; html.find(".characteristic-select") .val(skill.system.characteristic) @@ -433,7 +494,9 @@ export default class CheckBuilder extends FormApplication */ _onWeaponSelectChange(event) { - this.roll.data.weapon = this.roll.data.actor.itemTypes.weapon(weapon => weapon._id === event.currentTarget.value); + event.preventDefault(); + + this.object.checkData.weapon = this.object.checkData.actor.itemTypes.weapon(weapon => weapon._id === event.currentTarget.value); } /** @@ -455,191 +518,4 @@ export default class CheckBuilder extends FormApplication if(!$(event.currentTarget).hasClass("minimize")) $(selector).val(""); } - - /** - * Performs follow-up operations after left-clicks on a roll check button. - * @param {any} html - * @param {MouseEvent} event - * @returns {WFRP3eRoll} - * @private - */ - async _onRollCheckButtonClick(html, event) - { - // if sound was not passed search for sound dropdown value - if(!this.roll.sound) { - const sound = html.find(".sound-selection")?.[0]?.value; - - if(sound) { - this.roll.sound = sound; - - if(this?.roll?.item) { - let entity; - let entityData; - - if(!this?.roll?.item?.flags?.uuid) { - entity = CONFIG["Actor"].documentClass.collection.get(this.roll.data.actor.id); - entityData = {_id: this.roll.item.id}; - } - else { - const parts = this.roll.item.flags.uuid.split("."); - const [entityName, entityId, embeddedName, embeddedId] = parts; - entity = CONFIG[entityName].documentClass.collection.get(entityId); - - if(parts.length === 4) - entityData = {_id: embeddedId}; - } - - setProperty(entityData, "flags.ffgsound", sound); - entity.updateOwnedItem(entityData); - } - } - } - - if(!this.roll.flavor) { - const flavor = html.find(".flavor-text")?.[0]?.value; - - if(flavor) - this.roll.flavor = flavor; - } - - const sentToPlayer = html.find(".user-selection")?.[0]?.value; - - if(sentToPlayer) { - let container = $(`
`)[0]; - this.object.renderAdvancedPreview(container); - - const messageText = `
-
${game.i18n.localize("WFRP3e.SentDicePoolRollHint")}
- ${$(container).html()} - -
`; - - let chatOptions = { - user: game.user.id, - content: messageText, - flags: { - specialDice: { - roll: this.roll, - dicePool: this.object, - description: this.description, - }, - }, - }; - - if(sentToPlayer !== "all") - chatOptions.whisper = [sentToPlayer]; - - ChatMessage.create(chatOptions); - } - else if(this.roll.data?.combat) { - const currentCombatantId = this.roll.data.combat.combatant?.id; - const encounterType = html.find('.encounter-type input:checked').val(); - const initiativeCharacteristic = encounterType === "social" ? "fellowship" : "agility"; - - // Iterate over Combatants, performing an initiative roll for each. - const [updates, messages] = await this.roll.data.combatantIds.reduce(async(results, id, i) => { - const [updates, messages] = await results; - - // Get Combatant data - const combatant = this.roll.data.combat.getCombatantByToken( - this.roll.data.combat.combatants - .map(combatant => combatant) - .filter(combatantData => combatantData._id === id)[0].tokenId - ); - - if(!combatant || !combatant.isOwner) - return results; - - // Determine formula. - this.object.addDicePool(await CheckHelper.prepareInitiativeCheck(combatant.actor, initiativeCharacteristic)); - const initiativeRoll = new WFRP3eRoll( - this.object.renderDiceExpression(), - this.object, - {data: combatant.actor ? combatant.actor.getRollData() : {}} - ).roll(); - - const totalInitiative = initiativeRoll.symbols.successes; - - initiativeRoll._result = totalInitiative; - initiativeRoll._total = totalInitiative; - - // Roll initiative - updates.push({_id: id, initiative: initiativeRoll.total}); - - // Determine the roll mode - let rollMode = this.roll.data.messageOptions.rollMode || game.settings.get("core", "rollMode"); - if((combatant.token.hidden || combatant.hidden) && rollMode === "roll") - rollMode = "gmroll"; - - // Construct chat message data - const messageData = mergeObject({ - speaker: { - scene: canvas.scene.id, - actor: combatant.actor ? combatant.actor.id : null, - token: combatant.token.id, - alias: combatant.token.name, - }, - flavor: game.i18n.localize("ROLL." + (encounterType[0].toUpperCase() + encounterType.slice(1)) + "EncounterInitiativeCheck"), - flags: { "core.initiativeRoll": true }, - }, this.roll.data.messageOptions); - - const chatData = await initiativeRoll.toMessage(messageData, {create: false, rollMode}); - - // Play 1 sound for the whole rolled set - if(i > 0) - chatData.sound = null; - - messages.push(chatData); - - // Return the Roll and the chat data - return results; - }, [[], []]); - - if(!updates.length) - return this.roll.data.combat; - - // Update multiple combatants - await this.roll.data.combat.updateEmbeddedDocuments("Combatant", updates); - - // Ensure the turn order remains with the same combatant if there was one active - if(this.roll.data.updateTurn && !!currentCombatantId) - await this.roll.data.combat.update({turn: this.roll.data.combat.turns.findIndex((turn) => turn.id === currentCombatantId)}); - - // Create multiple chat messages - await CONFIG.ChatMessage.documentClass.create(messages); - - return this.roll.data.combat; - } - else { - const roll = new WFRP3eRoll(this.object.renderDiceExpression(), this.object, { - data: this.roll.data, - flavor: this.roll.flavor - }); - - roll.toMessage({ - flavor: this.roll.name, - speaker: {actor: this.roll.data?.actor}, - user: game.user.id - }); - - if(this.roll?.sound) - AudioHelper.play({src: this.roll.sound}, true); - - if(this.roll.data?.actor.type === "creature") - Object.keys(CONFIG.WFRP3e.attributes).forEach((attribute) => { - const attributeDiceAmount = this.object["creatures" + (attribute[0].toUpperCase() + attribute.slice(1, attribute.length)) + "Dice"]; - - if(attributeDiceAmount > 0) { - const changes = {"system.attributes": {}}; - changes.system.attributes[attribute] = {current: this.roll.data.actor.system.attributes[attribute].current - attributeDiceAmount}; - - console.log(changes) - - this.roll.data.actor.update(changes); - } - }); - - return roll; - } - } } \ No newline at end of file diff --git a/modules/dice/ChallengeDie.js b/modules/dice/ChallengeDie.js index 01b0b5c..8fbda31 100644 --- a/modules/dice/ChallengeDie.js +++ b/modules/dice/ChallengeDie.js @@ -1,9 +1,9 @@ import WFRP3eDie from "./WFRP3eDie.js"; +/** @inheritDoc */ export default class ChallengeDie extends WFRP3eDie { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { termData.faces = 8; super(termData); @@ -12,27 +12,5 @@ export default class ChallengeDie extends WFRP3eDie /** @inheritDoc */ static DENOMINATION = "h"; - /** - * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term - * @param {Object} options - * @return {DiceTermResult} - * @inheritDoc - */ - roll(options) - { - const roll = super.roll(options); - roll.symbols = CONFIG.WFRP3e.dice.challenge.results[roll.result]; - return roll; - } - - /** - * Return a string used as the label for each rolled result - * @param {DiceTermResult} result The rolled result - * @return {string} The result label - */ - getResultLabel(result) - { - const die = CONFIG.WFRP3e.dice.challenge.results[result.result]; - return ``; - } + static NAME = "challenge"; } \ No newline at end of file diff --git a/modules/dice/CharacteristicDie.js b/modules/dice/CharacteristicDie.js index 415dc68..726e359 100644 --- a/modules/dice/CharacteristicDie.js +++ b/modules/dice/CharacteristicDie.js @@ -1,9 +1,9 @@ import WFRP3eDie from "./WFRP3eDie.js"; +/** @inheritDoc */ export default class CharacteristicDie extends WFRP3eDie { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { termData.faces = 8; super(termData); @@ -12,27 +12,5 @@ export default class CharacteristicDie extends WFRP3eDie /** @inheritDoc */ static DENOMINATION = "a"; - /** - * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term - * @param {Object} options - * @return {DiceTermResult} - * @inheritDoc - */ - roll(options) - { - const roll = super.roll(options); - roll.symbols = CONFIG.WFRP3e.dice.characteristic.results[roll.result]; - return roll; - } - - /** - * Return a string used as the label for each rolled result - * @param {DiceTermResult} result The rolled result - * @return {string} The result label - */ - getResultLabel(result) - { - const die = CONFIG.WFRP3e.dice.characteristic.results[result.result]; - return ``; - } + static NAME = "characteristic"; } \ No newline at end of file diff --git a/modules/dice/ConservativeDie.js b/modules/dice/ConservativeDie.js index 58acb3c..22f9756 100644 --- a/modules/dice/ConservativeDie.js +++ b/modules/dice/ConservativeDie.js @@ -1,9 +1,9 @@ import WFRP3eDie from "./WFRP3eDie.js"; +/** @inheritDoc */ export default class ConservativeDie extends WFRP3eDie { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { termData.faces = 10; super(termData); @@ -12,26 +12,5 @@ export default class ConservativeDie extends WFRP3eDie /** @inheritDoc */ static DENOMINATION = "o"; - /** - * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term - * @param {Object} options - * @inheritDoc - */ - roll(options) - { - const roll = super.roll(options); - roll.symbols = CONFIG.WFRP3e.dice.conservative.results[roll.result]; - return roll; - } - - /** - * Return a string used as the label for each rolled result - * @param {DiceTermResult} result The rolled result - * @return {string} The result label - */ - getResultLabel(result) - { - const die = CONFIG.WFRP3e.dice.conservative.results[result.result]; - return ``; - } + static NAME = "conservative"; } \ No newline at end of file diff --git a/modules/dice/ExpertiseDie.js b/modules/dice/ExpertiseDie.js index 05e318e..901e058 100644 --- a/modules/dice/ExpertiseDie.js +++ b/modules/dice/ExpertiseDie.js @@ -1,9 +1,9 @@ import WFRP3eDie from "./WFRP3eDie.js"; +/** @inheritDoc */ export default class ExpertiseDie extends WFRP3eDie { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { termData.faces = 6; termData.modifiers = ["x3"] @@ -13,37 +13,5 @@ export default class ExpertiseDie extends WFRP3eDie /** @inheritDoc */ static DENOMINATION = "e"; - /** - * A string representation of the formula expression for this RollTerm, prior to evaluation. - * @type {string} - * @inheritDoc - */ - get expression() - { - return `${this.number}d${this.constructor.DENOMINATION}${this.modifiers.join("")}`; - } - - /** - * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term - * @param {Object} options - * @return {DiceTermResult} - * @inheritDoc - */ - roll(options) - { - const roll = super.roll(options); - roll.symbols = CONFIG.WFRP3e.dice.expertise.results[roll.result]; - return roll; - } - - /** - * Return a string used as the label for each rolled result - * @param {DiceTermResult} result The rolled result - * @return {string} The result label - */ - getResultLabel(result) - { - const die = CONFIG.WFRP3e.dice.expertise.results[result.result]; - return ``; - } + static NAME = "expertise"; } \ No newline at end of file diff --git a/modules/dice/FortuneDie.js b/modules/dice/FortuneDie.js index 8a37c23..fff39a4 100644 --- a/modules/dice/FortuneDie.js +++ b/modules/dice/FortuneDie.js @@ -1,9 +1,9 @@ import WFRP3eDie from "./WFRP3eDie.js"; +/** @inheritDoc */ export default class FortuneDie extends WFRP3eDie { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { termData.faces = 6; super(termData); @@ -12,27 +12,5 @@ export default class FortuneDie extends WFRP3eDie /** @inheritDoc */ static DENOMINATION = "f"; - /** - * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term - * @param {Object} options - * @return {DiceTermResult} - * @inheritDoc - */ - roll(options) - { - const roll = super.roll(options); - roll.symbols = CONFIG.WFRP3e.dice.fortune.results[roll.result]; - return roll; - } - - /** - * Return a string used as the label for each rolled result - * @param {DiceTermResult} result The rolled result - * @return {string} The result label - */ - getResultLabel(result) - { - const die = CONFIG.WFRP3e.dice.fortune.results[result.result]; - return ``; - } + static NAME = "fortune"; } \ No newline at end of file diff --git a/modules/dice/MisfortuneDie.js b/modules/dice/MisfortuneDie.js index f880496..6c97ef5 100644 --- a/modules/dice/MisfortuneDie.js +++ b/modules/dice/MisfortuneDie.js @@ -1,9 +1,9 @@ import WFRP3eDie from "./WFRP3eDie.js"; +/** @inheritDoc */ export default class MisfortuneDie extends WFRP3eDie { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { termData.faces = 6; super(termData); @@ -12,27 +12,5 @@ export default class MisfortuneDie extends WFRP3eDie /** @inheritDoc */ static DENOMINATION = "m"; - /** - * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term - * @param {Object} options - * @return {DiceTermResult} - * @inheritDoc - */ - roll(options) - { - const roll = super.roll(options); - roll.symbols = CONFIG.WFRP3e.dice.misfortune.results[roll.result]; - return roll; - } - - /** - * Return a string used as the label for each rolled result - * @param {DiceTermResult} result The rolled result - * @return {string} The result label - */ - getResultLabel(result) - { - const die = CONFIG.WFRP3e.dice.misfortune.results[result.result]; - return ``; - } + static NAME = "misfortune"; } \ No newline at end of file diff --git a/modules/dice/RecklessDie.js b/modules/dice/RecklessDie.js index 649fb39..7901c5d 100644 --- a/modules/dice/RecklessDie.js +++ b/modules/dice/RecklessDie.js @@ -1,9 +1,9 @@ import WFRP3eDie from "./WFRP3eDie.js"; +/** @inheritDoc */ export default class RecklessDie extends WFRP3eDie { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { termData.faces = 10; super(termData); @@ -12,27 +12,5 @@ export default class RecklessDie extends WFRP3eDie /** @inheritDoc */ static DENOMINATION = "r"; - /** - * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term - * @param {Object} options - * @return {DiceTermResult} - * @inheritDoc - */ - roll(options) - { - const roll = super.roll(options); - roll.symbols = CONFIG.WFRP3e.dice.reckless.results[roll.result]; - return roll; - } - - /** - * Return a string used as the label for each rolled result - * @param {DiceTermResult} result The rolled result - * @return {string} The result label - */ - getResultLabel(result) - { - const die = CONFIG.WFRP3e.dice.reckless.results[result.result]; - return ``; - } + static NAME = "reckless"; } \ No newline at end of file diff --git a/modules/dice/WFRP3eDie.js b/modules/dice/WFRP3eDie.js index 62238fa..dc9cbee 100644 --- a/modules/dice/WFRP3eDie.js +++ b/modules/dice/WFRP3eDie.js @@ -1,68 +1,80 @@ /** @inheritDoc */ export default class WFRP3eDie extends Die { - /** @inheritDoc */ - constructor(termData) + constructor(termData = {}) { super(termData); - } - - /** - * A string representation of the formula expression for this RollTerm, prior to evaluation. - * @type {string} - * @inheritDoc - */ - get expression() - { - return `${this.number}d${this.constructor.DENOMINATION}${this.modifiers.join("")}`; - } - /** - * Return a standardized representation for the displayed formula associated with this DiceTerm - * @return {string} - * @inheritDoc - */ - get formula() - { - return this.expression; + this.resultSymbols = { + ...Object.values(CONFIG.WFRP3e.symbols).reduce((object, symbol) => { + object[symbol.plural] = 0; + return object; + }, {}) + }; } + static NAME = "wfrp3e-die"; - /** - * Evaluate the term, processing its inputs and finalizing its total. - * @param {boolean} minimize Minimize the result, obtaining the smallest possible value. - * @param {boolean} maximize Maximize the result, obtaining the largest possible value. - * @param {boolean} async Evaluate the term asynchronously, receiving a Promise as the returned value. This will become the default behavior in version 10.x - * @return {WFRP3eDie} The evaluated RollTerm - * @inheritDoc - */ - evaluate({minimize = false, maximize = false, async = true} = {}) - { - if(this._evaluated) - throw new Error(`This ${this.constructor.name} has already been evaluated and is immutable`); + /** @inheritDoc */ + _evaluateSync({minimize = false, maximize = false} = {}) { + if((this.number > 999)) + throw new Error(`You may not evaluate a DiceTerm with more than 999 requested results`); for(let n = 1; n <= this.number; n++) this.roll({minimize, maximize}); - this.symbols = { - ...Object.values(CONFIG.WFRP3e.symbols).reduce((object, symbol) => { - object[symbol.plural] = 0; - return object; - }, {}) - }; + this._evaluateModifiers(); this.results.forEach((result) => { - Object.keys(this.symbols).forEach((symbolName, index) => { - this.symbols[symbolName] += parseInt(result.symbols[symbolName]); + Object.keys(this.resultSymbols).forEach((symbolName, index) => { + this.resultSymbols[symbolName] += parseInt(result.symbols[symbolName]); }, {}); if(result.symbols.righteousSuccesses > 0) result.exploded = true; }); - // Return the evaluated term - this._evaluated = true; - this._isSpecialDie = true; - return this; } + + /** @inheritDoc */ + roll({minimize = false, maximize = false} = {}) + { + const roll = super.roll({minimize = false, maximize = false} = {}); + + roll.symbols = CONFIG.WFRP3e.dice[this.constructor.NAME].results[roll.result]; + + return roll; + } + + /** @inheritDoc */ + getResultLabel(result) + { + const die = CONFIG.WFRP3e.dice[this.constructor.NAME].results[result.result]; + return ``; + } + + /** @inheritDoc */ + getResultCSS(result) { + return [ + "wfrp3e-die", + `${this.constructor.NAME}-die`, + result.rerolled ? "rerolled" : null, + result.exploded ? "exploded" : null, + result.discarded ? "discarded" : null + ] + } + + /** @inheritDoc */ + getTooltipData() + { + let countText = game.i18n.format("ROLL.AMOUNT." + this.constructor.name, {nb: this.results.length}); + if(this.results.length > this.number) + countText += ` ${game.i18n.format("ROLL.AMOUNT.ExplodedDie", {nb: this.results.length - this.number})}`; + + return { + ...super.getTooltipData(), + countText, + isSpecial: true + } + } } \ No newline at end of file diff --git a/styles/less/components/check_builder.less b/styles/less/components/dice_pool_builder.less similarity index 97% rename from styles/less/components/check_builder.less rename to styles/less/components/dice_pool_builder.less index 0ddb3ee..f49df6c 100644 --- a/styles/less/components/check_builder.less +++ b/styles/less/components/dice_pool_builder.less @@ -1,4 +1,4 @@ -.check-builder { +.dice-pool-builder { form { display: flex; @@ -87,7 +87,8 @@ background-color: #ddd; border: 1px solid black; font: var(--font-size-20) var(--font-primary); - margin: 0 1rem; + height: initial; + margin: 0.25rem 1rem 0; padding-top: 0.5rem; text-align: center; width: 3.5rem; @@ -235,7 +236,7 @@ & .quick-settings { flex: 1 1 200px; - & > div { + & > label { margin-bottom: 0.5rem; & > * { diff --git a/styles/less/components/roll_message.less b/styles/less/components/roll_message.less index ff7c6c2..2522f1b 100644 --- a/styles/less/components/roll_message.less +++ b/styles/less/components/roll_message.less @@ -34,7 +34,9 @@ .dice-tooltip { & .dice-rolls { - & .roll { + & .roll.wfrp3e-die { + background: none; + & > .special-die { width: 44px; } diff --git a/styles/less/wfrp3e.less b/styles/less/wfrp3e.less index d86cee2..0a0c1f0 100644 --- a/styles/less/wfrp3e.less +++ b/styles/less/wfrp3e.less @@ -5,7 +5,7 @@ @import "components/career_edit_sheet"; @import "components/character_generator"; @import "components/character_sheet"; -@import "components/check_builder"; +@import "components/dice_pool_builder"; @import "components/condition_card"; @import "components/creature_sheet"; @import "components/disease_card"; diff --git a/templates/applications/check-builder.hbs b/templates/applications/check-builder.hbs deleted file mode 100644 index 1de1437..0000000 --- a/templates/applications/check-builder.hbs +++ /dev/null @@ -1,206 +0,0 @@ -
-
- {{#if encounterTypes}} -
-

{{localize "ROLL.CHECKBUILDER.EncounterType"}}

- - {{#each encounterTypes as |encounter enc|}} -
- -
- {{/each}} -
- {{/if}} - -
-
- - {{#if encounterTypes}} -
- {{localize "ROLL.CHECKBUILDER.DicePool"}} - {{/if}} - -

- {{localize "ROLL.CHECKBUILDER.Hint"}} -

- -
- {{#each diceIcons as |die d|}} -
- {{capitalize d}} die - - -
- {{/each}} -
- -
- - -
- - {{#if encounterTypes}} -
- {{/if}} - - {{#if attributes}} -
- {{#each attributes as |attribute attr|}} -
- - -
- {{/each}} -
- {{/if}} - -
- {{localize "ROLL.CHECKBUILDER.StartingResults"}} - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- -
- {{localize "ROLL.CHECKBUILDER.Options"}} - -
- {{#if (or (eq isGM true) (eq canUserAddAudio true)) }} -
- - -
- {{/if}} - -
- - -
- - {{#if isGM}} -
- - -
- {{/if}} -
-
- -
- -
-
- - {{#if skill}} -
-
- - -
- -
- - -
- -
- - -
- - {{#if availableWeapons}} -
- - -
- {{/if}} -
- {{/if}} -
\ No newline at end of file diff --git a/templates/applications/dice-pool-builder.hbs b/templates/applications/dice-pool-builder.hbs new file mode 100644 index 0000000..0529d38 --- /dev/null +++ b/templates/applications/dice-pool-builder.hbs @@ -0,0 +1,259 @@ +
+
+ {{#if encounterTypes}} +
+

{{localize "ROLL.DICEPOOLBUILDER.EncounterType"}}

+ + {{#each encounterTypes as |encounter enc|}} + + {{/each}} +
+ {{/if}} + +
+
+ + {{#if encounterTypes}} +
+ {{localize "ROLL.DICEPOOLBUILDER.DicePool"}} + {{/if}} + +

+ {{localize "ROLL.DICEPOOLBUILDER.Hint"}} +

+ +
+ {{#each diceIcons as |die d|}} + + {{/each}} +
+ +
+ + +
+ + {{#if encounterTypes}} +
+ {{/if}} + + {{#if attributes}} +
+ {{#each attributes as |attribute attr|}} + + {{/each}} +
+ {{/if}} + +
+ {{localize "ROLL.DICEPOOLBUILDER.StartingResults"}} + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+
+ +
+ {{localize "ROLL.DICEPOOLBUILDER.Options"}} + +
+ {{#if (or (eq isGM true) (eq canUserAddAudio true)) }} + + {{/if}} + + + + {{#if isGM}} + + {{/if}} +
+
+ +
+ +
+
+ + {{#if skill}} +
+ + + + + + + + + {{#if availableWeapons}} + + {{/if}} +
+ {{/if}} +
\ No newline at end of file diff --git a/templates/chatmessages/roll-tooltip.hbs b/templates/chatmessages/roll-tooltip.hbs index 3e65a75..4db730e 100644 --- a/templates/chatmessages/roll-tooltip.hbs +++ b/templates/chatmessages/roll-tooltip.hbs @@ -5,7 +5,7 @@
{{this.countText}} {{#if this.flavor}}{{this.flavor}}{{/if}} - {{#if this.notSpecial}}{{this.total}}{{/if}} + {{#unless this.isSpecial}}{{this.total}}{{/unless}}
    @@ -13,10 +13,6 @@
  1. {{{this.result}}}
  2. {{/each}}
- -
    -
  1. {{{addedResults.success}}}
  2. -
{{/each}} diff --git a/templates/chatmessages/roll.hbs b/templates/chatmessages/roll.hbs index 07c4205..aac56b7 100644 --- a/templates/chatmessages/roll.hbs +++ b/templates/chatmessages/roll.hbs @@ -7,73 +7,55 @@
{{#if hasStandardDice}}
{{formula}}
- {{/if}} - {{#if hasStandardDice}}

{{total}}

{{/if}} {{#if hasSpecialDice}}
    - {{#each specialDice}} - {{#if this.isSpecialDie}} - {{#each this.rolls}} -
  1. {{{this.result}}}
  2. - {{/each}} - {{/if}} + {{#each specialDieResultLabels}} +
  3. {{{this}}}
  4. {{/each}}
- -
- {{#each addedResults}} - {{#if (ne this.value 0)}} -
{{#if this.negative}}-{{else}}+{{/if}}{{this.value}}{{{this.symbol}}}
- {{/if}} - {{/each}} -
{{/if}} {{{tooltip}}} {{#if hasSpecialDice}}
    - {{#if hasSuccess}} - {{#if symbols.successes}} -
  • {{localize "ROLL.OUTCOME.Successful"}}
  • - {{else}} -
  • {{localize "ROLL.OUTCOME.Failed"}}
  • - {{/if}} + {{#if resultSymbols.successes}} +
  • {{localize "ROLL.OUTCOME.Successful"}}
  • + {{else}} +
  • {{localize "ROLL.OUTCOME.Failed"}}
  • {{/if}} - {{#each @root.data.symbols as |symbol sym|}} - {{#if (lookup @root.symbols symbol.plural)}} - {{#unless (equalTo sym "righteousSuccess")}} + {{#each @root.symbols as |symbol sym|}} + {{#with (lookup @root.resultSymbols symbol.plural)}} + {{#if (and (superiorTo this 0) (notEqualTo sym "righteousSuccess"))}}
  • - {{localize symbol.result nb=(lookup @root.symbols symbol.plural)}} + {{localize symbol.result nb=this}} - {{#if (equalTo sym "success")}} - {{#if (superiorTo @root.symbols.righteousSuccesses 0)}} - {{localize @root.data.symbols.righteousSuccess.result nb=@root.symbols.righteousSuccesses}} - {{/if}} + {{#if (and (equalTo sym "success") (superiorTo @root.resultSymbols.righteousSuccesses 0))}} + {{localize @root.symbols.righteousSuccess.result nb=@root.resultSymbols.righteousSuccesses}} {{/if}} - {{#for 0 (lookup @root.data.remainingSymbols symbol.plural) 1}} + {{#for 0 (lookup @root.remainingSymbols symbol.plural) 1}} {{/for}}
  • - {{/unless}} - {{/if}} + {{/if}} + {{/with}} {{/each}}
- {{#if data.effects}} + {{#if effects}} {{>systems/wfrp3e/templates/partials/action-effects.hbs - action=data.action - face=data.face - symbols=data.symbols - effects=data.effects}} + action=action + face=face + symbols=symbols + effects=effects}} {{/if}} {{/if}}
diff --git a/wfrp3e.js b/wfrp3e.js index 12dc564..bbde7d3 100644 --- a/wfrp3e.js +++ b/wfrp3e.js @@ -1,6 +1,6 @@ import {WFRP3e} from "./modules/config.js"; import CharacterGenerator from "./modules/applications/CharacterGenerator.js"; -import CheckBuilder from "./modules/applications/CheckBuilder.js"; +import DicePoolBuilder from "./modules/applications/DicePoolBuilder.js"; import WFRP3eCharacterSheet from "./modules/applications/actors/WFRP3eCharacterSheet.js"; import WFRP3eCreatureSheet from "./modules/applications/actors/WFRP3eCreatureSheet.js"; import WFRP3eGroupSheet from "./modules/applications/actors/WFRP3eGroupSheet.js"; @@ -89,8 +89,6 @@ async function preloadHandlebarsTemplates() Hooks.once("init", () => { console.log("WFRP3e | Initialising Warhammer Fantasy Roleplay - 3rd Edition System"); - game.symbols = {diceterms: [ChallengeDie, CharacteristicDie, ConservativeDie, ExpertiseDie, FortuneDie, MisfortuneDie, RecklessDie]}; - CONFIG.WFRP3e = WFRP3e; Object.assign(CONFIG.Actor.dataModels, { @@ -181,7 +179,7 @@ Hooks.on('renderSidebarTab', (app, html, data) => { ); html.find("#chat-controls > .control-buttons > .wfrp3e-dice-roller").click(async () => { - await new CheckBuilder().render(true); + await new DicePoolBuilder().render(true); }); } }); From f8f6be512f831300781d466fafdcc865ac868ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soup=C3=A9?= Date: Tue, 26 Mar 2024 21:25:25 +0100 Subject: [PATCH 4/7] Revamping Combat and initiative rollss --- modules/CheckHelper.js | 14 +- modules/applications/DicePoolBuilder.js | 3 - .../sidebar/WFRP3eCombatTracker.js | 37 +++ modules/combat/WFRP3eCombat.js | 84 ++++++ modules/combat/WFRP3eCombatant.js | 17 ++ modules/documents/WFRP3eCombat.js | 44 --- styles/less/wfrp3e.less | 8 + templates/applications/dice-pool-builder.hbs | 23 -- .../applications/sidebar/combat-tracker.hbs | 262 ++++++++++++++++++ templates/chatmessages/roll.hbs | 4 - wfrp3e.js | 11 +- 11 files changed, 427 insertions(+), 80 deletions(-) create mode 100644 modules/applications/sidebar/WFRP3eCombatTracker.js create mode 100644 modules/combat/WFRP3eCombat.js create mode 100644 modules/combat/WFRP3eCombatant.js delete mode 100644 modules/documents/WFRP3eCombat.js create mode 100644 templates/applications/sidebar/combat-tracker.hbs diff --git a/modules/CheckHelper.js b/modules/CheckHelper.js index 19ecc5a..a8d50fc 100644 --- a/modules/CheckHelper.js +++ b/modules/CheckHelper.js @@ -104,12 +104,15 @@ export default class CheckHelper /** * Prepares an initiative check. * @param {WFRP3eActor} actor The Character making the initiative check. - * @param {string} characteristicName The Characteristic used for the initiative check. + * @param {WFRP3eCombat} combat The Combat document associated with the initiative check. + * @param {Object} [options] + * @param {String} [options.flavor] Some flavor text to add to the Skill check's outcome description. + * @param {String} [options.sound] Some sound to play after the Skill check completion. * @returns {DicePool} */ - static prepareInitiativeCheck(actor, characteristicName) + static prepareInitiativeCheck(actor, combat, {flavor = null, sound = null} = {}) { - const characteristic = actor.system.characteristics[characteristicName]; + const characteristic = actor.system.characteristics[combat.initiativeCharacteristic]; const stance = actor.system.stance.current ?? actor.system.stance; return new DicePool({ @@ -119,6 +122,11 @@ export default class CheckHelper conservative: stance < 0 ? Math.abs(stance) : 0, reckless: stance > 0 ? stance : 0 } + }, { + name: game.i18n.localize("ROLL.InitiativeCheckBuilder"), + checkData: {actor: actor, characteristic: characteristic, combat}, + flavor, + sound }); } diff --git a/modules/applications/DicePoolBuilder.js b/modules/applications/DicePoolBuilder.js index 8f11b76..814de9a 100644 --- a/modules/applications/DicePoolBuilder.js +++ b/modules/applications/DicePoolBuilder.js @@ -98,9 +98,6 @@ export default class DicePoolBuilder extends FormApplication this.object.checkData.weapon = Object.values(data.availableWeapons)[0]; } } - - if(this.object.checkData.combat) - data.encounterTypes = CONFIG.WFRP3e.encounterTypes; } return data; diff --git a/modules/applications/sidebar/WFRP3eCombatTracker.js b/modules/applications/sidebar/WFRP3eCombatTracker.js new file mode 100644 index 0000000..dec3b3c --- /dev/null +++ b/modules/applications/sidebar/WFRP3eCombatTracker.js @@ -0,0 +1,37 @@ +/** @inheritDoc */ +export default class WFRP3eCombatTracker extends CombatTracker +{ + /** @inheritdoc */ + static get defaultOptions() + { + return mergeObject(super.defaultOptions, {template: "systems/wfrp3e/templates/applications/sidebar/combat-tracker.hbs"}); + } + + /** @inheritdoc */ + async getData(options = {}) + { + return mergeObject(await super.getData(options), {encounterTypes: CONFIG.WFRP3e.encounterTypes}); + } + + /** @inheritdoc */ + activateListeners(html) + { + super.activateListeners(html); + html.find(".combat-input").change(this._onCombatInput.bind(this)); + } + + /** + * Handle changes events on Combat inputs. + * @private + * @param {Event} event The originating event + */ + async _onCombatInput(event) + { + event.preventDefault(); + + const updates = {}; + updates[event.currentTarget.name] = isNaN(event.currentTarget.value) ? event.currentTarget.value : Number(event.currentTarget.value); + + this.viewed.update(updates); + } +} diff --git a/modules/combat/WFRP3eCombat.js b/modules/combat/WFRP3eCombat.js new file mode 100644 index 0000000..c33a997 --- /dev/null +++ b/modules/combat/WFRP3eCombat.js @@ -0,0 +1,84 @@ +/** + * Extends the base Combat document. + */ +export default class WFRP3eCombat extends Combat +{ + constructor(data, context) + { + super(data, context); + + this.flags.encounterType = this.flags.encounterType || "combat"; + } + + get initiativeCharacteristic() + { + switch(this.flags.encounterType) { + case "combat": + return "agility"; + case "social": + return "fellowship"; + } + } + + /** @inheritDoc */ + async rollInitiative(ids, {formula = null, updateTurn = true, messageOptions = {}} = {}) + { + // Structure input data + ids = typeof ids === "string" ? [ids] : ids; + const currentId = this.combatant?.id; + const chatRollMode = game.settings.get("core", "rollMode"); + + // Iterate over Combatants, performing an initiative roll for each + const updates = []; + const messages = []; + + for(let [i, id] of ids.entries()) { + // Get Combatant data (non-strictly) + const combatant = this.combatants.get(id); + + if(!combatant?.isOwner) + continue; + + // Produce an initiative roll for the Combatant + const roll = combatant.getInitiativeRoll(formula); + await roll.evaluate({async: true}); + updates.push({_id: id, initiative: roll.resultSymbols.successes}); + + // Construct chat message data + let messageData = foundry.utils.mergeObject({ + speaker: ChatMessage.getSpeaker({ + actor: combatant.actor, + token: combatant.token, + alias: combatant.name + }), + flavor: game.i18n.format("COMBAT.RollsInitiative", {name: combatant.name}), + flags: {"core.initiativeRoll": true} + }, messageOptions); + const chatData = await roll.toMessage(messageData, {create: false}); + + // If the combatant is hidden, use a private roll unless an alternative rollMode was explicitly requested + chatData.rollMode = "rollMode" in messageOptions ? messageOptions.rollMode + : (combatant.hidden ? CONST.DICE_ROLL_MODES.PRIVATE : chatRollMode ); + + // Play 1 sound for the whole rolled set + if(i > 0) + chatData.sound = null; + + messages.push(chatData); + } + + if(!updates.length) + return this; + + // Update multiple combatants + await this.updateEmbeddedDocuments("Combatant", updates); + + // Ensure the turn order remains with the same combatant + if(updateTurn && currentId) + await this.update({turn: this.turns.findIndex(t => t.id === currentId)}); + + // Create multiple chat messages + await ChatMessage.implementation.create(messages); + return this; + } +} \ No newline at end of file diff --git a/modules/combat/WFRP3eCombatant.js b/modules/combat/WFRP3eCombatant.js new file mode 100644 index 0000000..8809149 --- /dev/null +++ b/modules/combat/WFRP3eCombatant.js @@ -0,0 +1,17 @@ +import CheckHelper from "../CheckHelper.js"; + +/** + * Extends the base Combatant document. + */ +export default class WFRP3eCombatant extends Combatant +{ + /** @inheritDoc */ + _getInitiativeFormula() + { + return String( + CONFIG.Combat.initiative.formula + || game.system.initiative + || CheckHelper.prepareInitiativeCheck(this.actor, this.combat).formula + ); + } +} \ No newline at end of file diff --git a/modules/documents/WFRP3eCombat.js b/modules/documents/WFRP3eCombat.js deleted file mode 100644 index 59650a7..0000000 --- a/modules/documents/WFRP3eCombat.js +++ /dev/null @@ -1,44 +0,0 @@ -import CheckHelper from "../CheckHelper.js"; -import DicePool from "../DicePool.js"; -import WFRP3eRoll from "../WFRP3eRoll.js"; -import CheckBuilder from "../applications/CheckBuilder.js"; - -/** - * Extends the base Combat entity. - */ -export default class WFRP3eCombat extends Combat -{ - /** @inheritDoc */ - async rollInitiative(ids, {formula = null, updateTurn = true, messageOptions = {}} = {}) - { - // Make sure we are dealing with an array of ids - if(!Array.isArray(ids)) - ids = typeof ids === "string" ? [ids] : ids; - - return await new CheckBuilder(new DicePool(), - game.i18n.localize("ROLL.InitiativeCheck"), - {combat: this, combatantIds: ids, updateTurn: updateTurn, messageOptions: messageOptions} - ).render(true); - } - /** @inheritDoc */ - _getInitiativeRoll(combatant, formula) - { - const initiativeDicePool = CheckHelper.prepareInitiativeCheck( - combatant.actor, - formula === "social" ? "fellowship" : "agility" - ); - - const initiativeRoll = new WFRP3eRoll( - initiativeDicePool.renderDiceExpression(), - initiativeDicePool, - {data: combatant.actor ? combatant.actor.getRollData() : {}} - ).roll(); - - const totalInitiative = initiativeRoll.symbols.successes; - - initiativeRoll._result = totalInitiative; - initiativeRoll._total = totalInitiative; - - return initiativeRoll; - } -} \ No newline at end of file diff --git a/styles/less/wfrp3e.less b/styles/less/wfrp3e.less index 0a0c1f0..d9092da 100644 --- a/styles/less/wfrp3e.less +++ b/styles/less/wfrp3e.less @@ -123,6 +123,14 @@ th { } } +.combat-sidebar .combat-tracker-header div.encounter-inputs { + font-size: 10pt; + + &:not(.combat) { + display: none; + } +} + .conservative-stance-meter { background-color: green; } diff --git a/templates/applications/dice-pool-builder.hbs b/templates/applications/dice-pool-builder.hbs index 0529d38..15844b1 100644 --- a/templates/applications/dice-pool-builder.hbs +++ b/templates/applications/dice-pool-builder.hbs @@ -1,26 +1,8 @@
- {{#if encounterTypes}} -
-

{{localize "ROLL.DICEPOOLBUILDER.EncounterType"}}

- - {{#each encounterTypes as |encounter enc|}} - - {{/each}} -
- {{/if}} -
- {{#if encounterTypes}} -
- {{localize "ROLL.DICEPOOLBUILDER.DicePool"}} - {{/if}} -

{{localize "ROLL.DICEPOOLBUILDER.Hint"}}

@@ -39,11 +21,6 @@
- - {{#if encounterTypes}} - - {{/if}} - {{#if attributes}}
{{#each attributes as |attribute attr|}} diff --git a/templates/applications/sidebar/combat-tracker.hbs b/templates/applications/sidebar/combat-tracker.hbs new file mode 100644 index 0000000..485a1b4 --- /dev/null +++ b/templates/applications/sidebar/combat-tracker.hbs @@ -0,0 +1,262 @@ +
+
+ {{#if user.isGM}} + + {{/if}} + +
+ {{#each encounterTypes as |encounterType type|}} + + {{/each}} +
+ +
+ {{#if user.isGM}} + + + + + + + + {{/if}} + + {{#if combatCount}} + {{#if combat.round}} +

{{localize 'COMBAT.Round'}} {{combat.round}}

+ {{else}} +

{{localize 'COMBAT.NotStarted'}}

+ {{/if}} + {{else}} +

{{localize "COMBAT.None"}}

+ {{/if}} + + {{#if user.isGM}} + + + + + + + + {{/if}} + + + + +
+
+ +
    + {{#each turns}} +
  1. + {{this.name}} + +
    +

    {{this.name}}

    + +
    + {{#if ../user.isGM}} + + + + + + + + {{/if}} + + {{#if this.canPing}} + + + + {{/if}} + +
    + {{#each this.effects}} + + {{/each}} +
    +
    +
    + + {{#if this.hasResource}} +
    + {{this.resource}} +
    + {{/if}} + +
    + {{#if this.hasRolled}} + {{this.initiative}} + {{else if this.owner}} + + {{/if}} +
    +
  2. + {{/each}} +
+ + +
diff --git a/templates/chatmessages/roll.hbs b/templates/chatmessages/roll.hbs index aac56b7..90f2ff9 100644 --- a/templates/chatmessages/roll.hbs +++ b/templates/chatmessages/roll.hbs @@ -1,8 +1,4 @@
- {{#if data.additionalFlavourText}} -
{{data.additionalFlavourText}}
- {{/if}} - {{#if publicRoll}}
{{#if hasStandardDice}} diff --git a/wfrp3e.js b/wfrp3e.js index bbde7d3..6b02c31 100644 --- a/wfrp3e.js +++ b/wfrp3e.js @@ -20,6 +20,9 @@ import WFRP3eSkillSheet from "./modules/applications/items/WFRP3eSkillSheet.js"; import WFRP3eTalentSheet from "./modules/applications/items/WFRP3eTalentSheet.js"; import WFRP3eTrappingSheet from "./modules/applications/items/WFRP3eTrappingSheet.js"; import WFRP3eWeaponSheet from "./modules/applications/items/WFRP3eWeaponSheet.js"; +import WFRP3eCombatTracker from "./modules/applications/sidebar/WFRP3eCombatTracker.js"; +import WFRP3eCombat from "./modules/combat/WFRP3eCombat.js"; +import WFRP3eCombatant from "./modules/combat/WFRP3eCombatant.js"; import WFRP3eCharacterDataModel from "./modules/data/actors/WFRP3eCharacterDataModel.js"; import WFRP3eCreatureDataModel from "./modules/data/actors/WFRP3eCreatureDataModel.js"; import WFRP3eGroupDataModel from "./modules/data/actors/WFRP3eGroupDataModel.js"; @@ -46,12 +49,11 @@ import ExpertiseDie from "./modules/dice/ExpertiseDie.js"; import FortuneDie from "./modules/dice/FortuneDie.js"; import MisfortuneDie from "./modules/dice/MisfortuneDie.js"; import RecklessDie from "./modules/dice/RecklessDie.js"; +import WFRP3eActor from "./modules/documents/WFRP3eActor.js" +import WFRP3eItem from "./modules/documents/WFRP3eItem.js" import CheckHelper from "./modules/CheckHelper.js"; import DicePool from "./modules/DicePool.js"; import WFRP3eRoll from "./modules/WFRP3eRoll.js"; -import WFRP3eActor from "./modules/documents/WFRP3eActor.js" -import WFRP3eCombat from "./modules/documents/WFRP3eCombat.js"; -import WFRP3eItem from "./modules/documents/WFRP3eItem.js" import * as handlebarsHelpers from "./modules/handlebars.js"; async function preloadHandlebarsTemplates() @@ -119,6 +121,7 @@ Hooks.once("init", () => { CONFIG.Item.documentClass = WFRP3eItem; CONFIG.Combat.documentClass = WFRP3eCombat; + CONFIG.Combatant.documentClass = WFRP3eCombatant; CONFIG.Dice.rolls.push(CONFIG.Dice.rolls[0]); CONFIG.Dice.rolls[0] = WFRP3eRoll; @@ -142,6 +145,8 @@ Hooks.once("init", () => { }; CONFIG.fontDefinitions["Caslon Antique"] = {editor: true, fonts: []}; + CONFIG.ui.combat = WFRP3eCombatTracker; + Actors.unregisterSheet("core", ActorSheet); Actors.registerSheet("wfrp3e", WFRP3eCharacterSheet, {label: "Character Sheet", types: ["character"], makeDefault: true}); Actors.registerSheet("wfrp3e", WFRP3ePartySheet, {label: "Party Sheet", types: ["party"], makeDefault: true}); From 5e0705e81455c5f8bc561de0dd7c22a9bc3e824d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soup=C3=A9?= Date: Tue, 26 Mar 2024 21:51:55 +0100 Subject: [PATCH 5/7] Fixing Creatures' stance --- .../data/actors/WFRP3eCreatureDataModel.js | 13 ++++- .../applications/actors/creature-sheet.hbs | 54 ++++++++++--------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/modules/data/actors/WFRP3eCreatureDataModel.js b/modules/data/actors/WFRP3eCreatureDataModel.js index 5dd6444..c59025e 100644 --- a/modules/data/actors/WFRP3eCreatureDataModel.js +++ b/modules/data/actors/WFRP3eCreatureDataModel.js @@ -12,7 +12,9 @@ export default class WFRP3eCreatureDataModel extends foundry.abstract.TypeDataMo attributes: new fields.SchemaField(Object.keys(CONFIG.WFRP3e.attributes).reduce((object, attribute) => { object[attribute] = new fields.SchemaField({ budget: new fields.NumberField({initial: 0, integer: true, min: 0, nullable: false, required: true}), - current: new fields.NumberField({initial: 0, integer: true, min: 0, nullable: false, required: true}) + current: new fields.NumberField({initial: 0, integer: true, min: 0, nullable: false, required: true}), + max: new fields.NumberField({initial: 0, integer: true, min: 0, nullable: false, required: true}), + value: new fields.NumberField({initial: 0, integer: true, min: 0, nullable: false, required: true}) }, {label: attribute}); return object; @@ -61,6 +63,13 @@ export default class WFRP3eCreatureDataModel extends foundry.abstract.TypeDataMo source.characteristics[characteristic].rating = source.characteristics[characteristic].value; }); + Object.keys(CONFIG.WFRP3e.attributes).forEach((attribute) => { + if(!source.attributes[attribute].max) + source.attributes[attribute].max = source.attributes[attribute].budget; + if(!source.attributes[attribute].value) + source.attributes[attribute].value = source.attributes[attribute].current; + }); + return super.migrateData(source); } @@ -84,7 +93,7 @@ export default class WFRP3eCreatureDataModel extends foundry.abstract.TypeDataMo { this.defaultStance = "conservative"; - if(this.stance < 0) { + if(this.stance > 0) { this.defaultStance = "reckless"; } } diff --git a/templates/applications/actors/creature-sheet.hbs b/templates/applications/actors/creature-sheet.hbs index ca5651c..1dc5a0b 100644 --- a/templates/applications/actors/creature-sheet.hbs +++ b/templates/applications/actors/creature-sheet.hbs @@ -93,44 +93,46 @@
- {{#if (inferiorTo actor.system.stance 0)}} - reckless">R{{abs actor.system.stance}} + C{{abs actor.system.stance}} {{else}} {{#if (superiorTo actor.system.stance 0)}} - conservative">C{{actor.system.stance}} + R{{actor.system.stance}} {{else}} - ">N/A + N/A {{/if}} {{/if}}
- {{editor actor.system.specialRuleSummary target="system.specialRuleSummary" button=true owner=owner editable=editable}} -
+ {{editor actor.system.specialRuleSummary target="system.specialRuleSummary" button=true owner=owner editable=editable}} +
{{#each actor.system.attributes as |attribute attr|}} -
- {{localize (lookup (lookup @root.attributes attr) "name")}} - -
- - - / - - + {{#with (lookup @root.attributes attr)}} +
+ {{localize this.name}} + +
+ + + / + + +
-
+ {{/with}} {{/each}}
@@ -149,7 +151,9 @@ {{#if (lookup @root.items.actions ty)}}
{{#each (lookup @root.items.actions ty) as |action act|}} - {{>systems/wfrp3e/templates/partials/item-action-card.hbs action=action}} + {{>systems/wfrp3e/templates/partials/item-action-card.hbs + action=action + currentStance=@root.actor.system.defaultStance}} {{/each}}
{{/if}} From b7dde54625a1ea7284f26f6b949f1995b9d0f030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soup=C3=A9?= Date: Tue, 26 Mar 2024 21:54:39 +0100 Subject: [PATCH 6/7] Fixing Characters' impairments --- modules/applications/CharacteristicUpgrader.js | 15 ++++++--------- .../applications/actors/WFRP3eCharacterSheet.js | 10 ++++++---- modules/documents/WFRP3eActor.js | 9 +++++---- modules/documents/WFRP3eItem.js | 7 ++++--- modules/handlebars.js | 4 +++- modules/helpers.js | 9 +++++++++ 6 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 modules/helpers.js diff --git a/modules/applications/CharacteristicUpgrader.js b/modules/applications/CharacteristicUpgrader.js index f652fbc..92b911d 100644 --- a/modules/applications/CharacteristicUpgrader.js +++ b/modules/applications/CharacteristicUpgrader.js @@ -1,14 +1,11 @@ +import {capitalize} from "../helpers.js"; + /** @inheritDoc */ export default class CharacteristicUpgrader extends FormApplication { - /** - * @param {WFRP3eActor} object - * @param {WFRP3eItem} career - * @param {boolean} [nonCareer] - */ - constructor(object, career, nonCareer = false) + constructor(object, career, nonCareer = false, options = {}) { - super(object); + super(object, options); this.career = career; this.nonCareer = nonCareer; @@ -81,7 +78,7 @@ export default class CharacteristicUpgrader extends FormApplication const matches = [...formData.upgrade.matchAll(new RegExp(/(^\w*)_(\w*$)/, "g"))][0]; const advancementName = game.i18n.format( - `CHARACTERISTICUPGRADER.Characteristic${matches[2][0].toUpperCase() + matches[2].slice(1, matches[2].length)}`, { + `CHARACTERISTICUPGRADER.Characteristic${capitalize(matches[2])}`, { characteristic: game.i18n.localize(CONFIG.WFRP3e.characteristics[matches[1]].name) }); const actorUpdates = {}; @@ -136,7 +133,7 @@ export default class CharacteristicUpgrader extends FormApplication html.find(".selection").text( game.i18n.format( - `CHARACTERISTICUPGRADER.Characteristic${matches[2][0].toUpperCase() + matches[2].slice(1, matches[2].length)}`, { + `CHARACTERISTICUPGRADER.Characteristic${capitalize(matches[2])}`, { characteristic: game.i18n.localize(CONFIG.WFRP3e.characteristics[matches[1]].name) }) ); diff --git a/modules/applications/actors/WFRP3eCharacterSheet.js b/modules/applications/actors/WFRP3eCharacterSheet.js index 6b2c2f7..48851c8 100644 --- a/modules/applications/actors/WFRP3eCharacterSheet.js +++ b/modules/applications/actors/WFRP3eCharacterSheet.js @@ -1,3 +1,5 @@ +import {capitalize} from "../../helpers.js"; + /** * Provides the data and general interaction with Actor Sheets - Abstract class. * WFRP3CharacterSheet provides the general interaction and data organization shared among all actors sheets, as this is an abstract class, inherited by either Character or NPC specific actors sheet classes. When rendering an actors sheet, getData() is called, which is a large and key that prepares the actors data for display, processing the raw data and items and compiling them into data to display on the sheet. Additionally, this class contains all the main events that respond to sheet interaction in activateListeners() @@ -180,11 +182,11 @@ export default class WFRP3eCharacterSheet extends ActorSheet talentSocketsByType[talentSocket]["career_" + this.actor.system.currentCareer._id + "_" + index] = this.actor.system.currentCareer.name + (talent ? " - " + game.i18n.format("TALENT.TakenSocket", { - type: game.i18n.localize(`TALENT.TYPE.${talentSocket[0].toUpperCase() + talentSocket.slice(1, talentSocket.length)}`), + type: game.i18n.localize(`TALENT.TYPE.${capitalize(talentSocket[0])}`), talent: talent.name }) : " - " + game.i18n.format("TALENT.AvailableSocket", { - type: game.i18n.localize(`TALENT.TYPE.${talentSocket[0].toUpperCase() + talentSocket.slice(1, talentSocket.length)}`) + type: game.i18n.localize(`TALENT.TYPE.${capitalize(talentSocket[0])}`) })); }); } @@ -204,11 +206,11 @@ export default class WFRP3eCharacterSheet extends ActorSheet talentSocketsByType[talentSocket]["party_" + this.actor.system.currentParty._id + "_" + index] = this.actor.system.currentParty.name + (talent ? " - " + game.i18n.format("TALENT.TakenSocket", { - type: game.i18n.localize(`TALENT.TYPE.${talentSocket[0].toUpperCase() + talentSocket.slice(1, talentSocket.length)}`), + type: game.i18n.localize(`TALENT.TYPE.${capitalize(talentSocket[0])}`), talent: talent.name }) : " - " + game.i18n.format("TALENT.AvailableSocket", { - type: game.i18n.localize(`TALENT.TYPE.${talentSocket[0].toUpperCase() + talentSocket.slice(1, talentSocket.length)}`) + type: game.i18n.localize(`TALENT.TYPE.${capitalize(talentSocket[0])}`) })); }); } diff --git a/modules/documents/WFRP3eActor.js b/modules/documents/WFRP3eActor.js index 76e7623..e7240f2 100644 --- a/modules/documents/WFRP3eActor.js +++ b/modules/documents/WFRP3eActor.js @@ -3,6 +3,7 @@ import CareerSelector from "../applications/CareerSelector.js"; import CharacteristicUpgrader from "../applications/CharacteristicUpgrader.js"; import TalentSelector from "../applications/TalentSelector.js"; import TrainingSelector from "../applications/TrainingSelector.js"; +import {capitalize} from "../helpers.js"; /** * Provides the main Actor data computation and organization. @@ -16,7 +17,7 @@ export default class WFRP3eActor extends Actor { try { // Call `prepareOwned` function - let functionName = `_prepare${this.type[0].toUpperCase() + this.type.slice(1, this.type.length)}`; + let functionName = `_prepare${capitalize(this.type)}`; if(this[`${functionName}`]) this[`${functionName}`](); @@ -169,10 +170,10 @@ export default class WFRP3eActor extends Actor */ changeImpairment(impairment, value) { - const changes = {"system.impairments": {}}; - changes.system.impairments[impairment] = this.system.impairments[impairment] + value; + const updates = {system: {impairments: {}}}; + updates.system.impairments[impairment] = this.system.impairments[impairment] + value; - this.update(changes); + this.update(updates); } /** diff --git a/modules/documents/WFRP3eItem.js b/modules/documents/WFRP3eItem.js index 0853335..9ce3ef6 100644 --- a/modules/documents/WFRP3eItem.js +++ b/modules/documents/WFRP3eItem.js @@ -1,4 +1,5 @@ import CheckHelper from "../CheckHelper.js"; +import {capitalize} from "../helpers.js"; export default class WFRP3eItem extends Item { @@ -7,7 +8,7 @@ export default class WFRP3eItem extends Item { super.prepareData(); - const functionName = `_prepare${this.type[0].toUpperCase() + this.type.slice(1, this.type.length)}`; + const functionName = `_prepare${capitalize(this.type)}`; if(this[`${functionName}`]) this[`${functionName}`](); @@ -18,7 +19,7 @@ export default class WFRP3eItem extends Item */ useItem(options = {}) { - const functionName = `use${this.type[0].toUpperCase() + this.type.slice(1, this.type.length)}`; + const functionName = `use${capitalize(this.type)}`; if(this[`${functionName}`]) this[`${functionName}`](options); @@ -293,7 +294,7 @@ export default class WFRP3eItem extends Item super._onUpdate(changed, options, userId); try { - const functionName = `_on${this.type[0].toUpperCase() + this.type.slice(1, this.type.length)}Update`; + const functionName = `_on${capitalize(this.type)}Update`; if(this[`${functionName}`]) this[`${functionName}`](changed, options, userId); diff --git a/modules/handlebars.js b/modules/handlebars.js index 07cfdba..ddddc16 100644 --- a/modules/handlebars.js +++ b/modules/handlebars.js @@ -1,3 +1,5 @@ +import {capitalize} from "./helpers.js"; + export default function() { Hooks.on("init", () => { @@ -12,7 +14,7 @@ export default function() Handlebars.registerHelper("increment", (value, valueToAdd) => value + parseInt(valueToAdd)); Handlebars.registerHelper("multiply", (value, multiplier) => value * multiplier); Handlebars.registerHelper("concat", (value, otherValue) => value.toString() + otherValue.toString()); - Handlebars.registerHelper("capitalize", (string) => string[0].toUpperCase() + string.slice(1)); + Handlebars.registerHelper("capitalize", (string) => capitalize(string)); Handlebars.registerHelper("abs", (number) => Math.abs(number)); Handlebars.registerHelper("in", (value, array) => array.includes(value)); diff --git a/modules/helpers.js b/modules/helpers.js new file mode 100644 index 0000000..c519162 --- /dev/null +++ b/modules/helpers.js @@ -0,0 +1,9 @@ +/** + * Replaces the first letter of a string with its capital. + * @param {String} string The string to capitalize. + * @returns {String} + */ +export function capitalize(string) +{ + return string[0].toUpperCase() + string.slice(1); +} \ No newline at end of file From 84739d88aa74866b0961a41e01ef2cd563161e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soup=C3=A9?= Date: Tue, 26 Mar 2024 22:30:05 +0100 Subject: [PATCH 7/7] Translating Actor and Item types --- lang/en-EN.json | 30 +++++++++++++++++++----------- lang/fr-FR.json | 30 +++++++++++++++++++----------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/lang/en-EN.json b/lang/en-EN.json index 250237b..11709c2 100644 --- a/lang/en-EN.json +++ b/lang/en-EN.json @@ -6,6 +6,10 @@ "Details": "Details", "ACTOR.GenerateACharacter": "Generate a Character", + "ACTOR.TypeCharacter": "Character", + "ACTOR.TypeCreature": "Creature", + "ACTOR.TypeGroup": "Group", + "ACTOR.TypeParty": "Party", "ACTION.Art": "Art", "ACTION.ChallengeDice": "Challenge Dice", @@ -281,17 +285,21 @@ "INSANITY.Traits": "Traits", "INSANITY.Description": "Description", - "ITEM.Career": "Career", - "ITEM.Skill": "Skill", - "ITEM.TrainingLevel": "Training Level", - "ITEM.Talent": "Talent", - "ITEM.Type": "Type", - "ITEM.Action": "Action", - "ITEM.Trapping": "Trapping", - "ITEM.Weapon": "Weapon", - "ITEM.Armour": "Armour", - "ITEM.Money": "Money", - "ITEM.Wound": "Wound", + "ITEM.TypeAbility": "Ability", + "ITEM.TypeAction": "Action", + "ITEM.TypeArmour": "Armour", + "ITEM.TypeCareer": "Career", + "ITEM.TypeCondition": "Condition", + "ITEM.TypeCriticalwound": "Critical Wound", + "ITEM.TypeDisease": "Disease", + "ITEM.TypeInsanity": "Insanity", + "ITEM.TypeMoney": "Money", + "ITEM.TypeMiscast": "Miscast", + "ITEM.TypeMutation": "Mutation", + "ITEM.TypeSkill": "Skill", + "ITEM.TypeTalent": "Talent", + "ITEM.TypeTrapping": "Trapping", + "ITEM.TypeWeapon": "Weapon", "MONEY.Value": "Value", "MONEY.Brass": "Brass", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index ed56992..40366c2 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -6,6 +6,10 @@ "Details": "Détails", "ACTOR.GenerateACharacter": "Générer un Personnage", + "ACTOR.TypeCharacter": "Personnage", + "ACTOR.TypeCreature": "Créature", + "ACTOR.TypeGroup": "Bande", + "ACTOR.TypeParty": "Groupe", "ACTION.Art": "Illustration", "ACTION.ChallengeDice": "Dés de Défi", @@ -281,17 +285,21 @@ "INSANITY.Traits": "Traits", "INSANITY.Description": "Description", - "ITEM.Career": "Carrière", - "ITEM.Skill": "Compétence", - "ITEM.TrainingLevel": "Niveau de formation", - "ITEM.Talent": "Talent", - "ITEM.Type": "Type", - "ITEM.Action": "Action", - "ITEM.Trapping": "Dotation", - "ITEM.Weapon": "Arme", - "ITEM.Armour": "Armure", - "ITEM.Money": "Monnaie", - "ITEM.Wound": "Blessure", + "ITEM.TypeAbility": "Aptitude", + "ITEM.TypeAction": "Action", + "ITEM.TypeArmour": "Armure", + "ITEM.TypeCareer": "Carrière", + "ITEM.TypeCondition": "État", + "ITEM.TypeCriticalwound": "Blessure", + "ITEM.TypeDisease": "Maladie", + "ITEM.TypeInsanity": "Folie", + "ITEM.TypeMoney": "Monnaie", + "ITEM.TypeMiscast": "Revers Magique", + "ITEM.TypeMutation": "Mutation", + "ITEM.TypeSkill": "Compétence", + "ITEM.TypeTalent": "Talent", + "ITEM.TypeTrapping": "Dotation", + "ITEM.TypeWeapon": "Arme", "MONEY.Value": "Valeur", "MONEY.Brass": "Brass",