From 4ec3cc9570513746417e3b5eeaaf6fb6d9d51e53 Mon Sep 17 00:00:00 2001 From: Byroks Date: Fri, 17 Nov 2023 16:09:24 +0100 Subject: [PATCH] add race boni and default resistance rolls Changes: + add Race Selection Dropdown + change resistance/defense value calculation, based on race, stats and class + add default rolls for defense and resistances --- package.json | 3 + source/module/actors/M5Character.ts | 23 +- source/module/rolls/M5Roll.ts | 265 ++++++++++++--------- source/module/sheets/M5CharacterSheet.ts | 14 ++ templates/sheets/character/base_values.hbs | 11 +- templates/sheets/character/gear.hbs | 21 ++ 6 files changed, 228 insertions(+), 109 deletions(-) diff --git a/package.json b/package.json index d95d38a..25fd523 100644 --- a/package.json +++ b/package.json @@ -54,5 +54,8 @@ "vinyl-buffer": "^1.0.1", "vinyl-source-stream": "^2.0.0", "yargs": "^17.1.1" + }, + "dependencies": { + "handlebars-helpers": "^0.10.0" } } diff --git a/source/module/actors/M5Character.ts b/source/module/actors/M5Character.ts index 5177c34..af96fe8 100644 --- a/source/module/actors/M5Character.ts +++ b/source/module/actors/M5Character.ts @@ -108,8 +108,12 @@ export class M5Character extends Actor { ret.stats.attackBonus = this.modResult(ret.attributes.gs.bonus); ret.stats.defenseBonus = this.modResult(ret.attributes.gw.bonus); ret.stats.movementBonus = this.modResult(0); - ret.stats.resistanceMind = this.modResult(ret.stats.defense.value); - ret.stats.resistanceBody = this.modResult(ret.stats.defense.value + 1); + ret.stats.resistanceMind = this.modResult( + (data.info.magicUsing ? 2 : 0) + ret.stats.defense.value + (data.info.race === "Mensch" ? ret.attributes.in.bonus : this.raceBonus(data.info.race)) + ); + ret.stats.resistanceBody = this.modResult( + (data.info.magicUsing ? 2 : 1) + ret.stats.defense.value + (data.info.race === "Mensch" ? ret.attributes.ko.bonus : this.raceBonus(data.info.race)) + ); ret.stats.spellCasting = this.modResult((data.info.magicUsing ? M5Character.spellCastingFromLevel(ret.level) : 3) + ret.attributes.zt.bonus); ret.stats.brawl = this.modResult(Math.floor((ret.attributes.st.value + ret.attributes.gw.value) / 20)); ret.stats.brawlEw = ret.stats.brawl.value + ret.stats.attackBonus.value; @@ -251,6 +255,21 @@ export class M5Character extends Actor { return ret; } + raceBonus(race: string) { + switch (race) { + case "Elf": + return 2; + case "Gnom": + return 4; + case "Halbling": + return 4; + case "Zwerg": + return 3; + default: + return 0; + } + } + prepareDerivedData() { console.log("M5Character", "prepareDerivedData"); const data = (this as any).system; diff --git a/source/module/rolls/M5Roll.ts b/source/module/rolls/M5Roll.ts index 00e47b9..3a1d159 100644 --- a/source/module/rolls/M5Roll.ts +++ b/source/module/rolls/M5Roll.ts @@ -2,12 +2,13 @@ import { Evaluated } from "@league-of-foundry-developers/foundry-vtt-types/src/f import { M5Character } from "../actors/M5Character"; import { M5EwResult, M5RollData, M5RollResult, M5SkillUnlearned } from "../M5Base"; -export class M5Roll { // extends Roll - static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs" +export class M5Roll { + // extends Roll + static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs"; - public _evaluated: boolean = false - public _total: number = 0 - public pool: PoolTerm = null + public _evaluated: boolean = false; + public _total: number = 0; + public pool: PoolTerm = null; constructor(public data: M5RollData, public actor: any, public label: string) { //super(null) @@ -17,116 +18,108 @@ export class M5Roll { // extends Roll // @ts-ignore //override evaluate(options?: InexactPartial): Evaluated> | Promise>> { evaluate() { - const indexMap = new Map() - const rollNames = Object.keys(this.data.rolls) - const rolls = rollNames.filter(rollName => this.data.rolls[rollName].enabled).map((rollName, index) => { - indexMap.set(index, rollName) - const formula = this.data.rolls[rollName] - const roll = new Roll(formula.formula, this.data) - return roll - }) + const indexMap = new Map(); + const rollNames = Object.keys(this.data.rolls); + const rolls = rollNames + .filter((rollName) => this.data.rolls[rollName].enabled) + .map((rollName, index) => { + indexMap.set(index, rollName); + const formula = this.data.rolls[rollName]; + const roll = new Roll(formula.formula, this.data); + return roll; + }); - this.pool = PoolTerm.fromRolls(rolls) - console.log("evaluate", this._evaluated, this.pool) - return this.pool.evaluate({ async: true }).then(results => { - this._total = 0 + this.pool = PoolTerm.fromRolls(rolls); + console.log("evaluate", this._evaluated, this.pool); + return this.pool.evaluate({ async: true }).then((results) => { + this._total = 0; results.rolls.forEach((roll, index) => { - const rollIndex = indexMap.get(index) - const rollResult = this.data.rolls[rollIndex] as M5RollResult + const rollIndex = indexMap.get(index); + const rollResult = this.data.rolls[rollIndex] as M5RollResult; - rollResult.result = roll.result - rollResult.total = roll.total - rollResult.totalStr = roll.total.toString() + rollResult.result = roll.result; + rollResult.total = roll.total; + rollResult.totalStr = roll.total.toString(); - this._total += roll.total + this._total += roll.total; - let rowRes = M5EwResult.TBD - let face100 = -1 + let rowRes = M5EwResult.TBD; + let face100 = -1; roll.dice.forEach((d, dIndex) => { - rollResult.dice[dIndex.toString()] = d.total - + rollResult.dice[dIndex.toString()] = d.total; + if (rowRes === M5EwResult.TBD && dIndex === 0) { if (d.faces === 20) { - //if (rollResult.type === "ew") { - if (d.total === 1) - rowRes = M5EwResult.FUMBLE - else if (d.total === 20) - rowRes = M5EwResult.CRITICAL - else if (d.total >= 16) - rowRes = M5EwResult.HIGH + //if (rollResult.type === "ew") { + if (d.total === 1) rowRes = M5EwResult.FUMBLE; + else if (d.total === 20) rowRes = M5EwResult.CRITICAL; + else if (d.total >= 16) rowRes = M5EwResult.HIGH; } else if (d.faces === 100) { - face100 = d.total as number + face100 = d.total as number; } } - }) + }); - const parseResult = M5Roll.parseDiceSides(rollResult.formula) + const parseResult = M5Roll.parseDiceSides(rollResult.formula); //console.log("evaluate roll", parseResult) if (parseResult?.sides === 20) { if (roll.total < 20) { - if (rowRes === M5EwResult.TBD || rowRes === M5EwResult.HIGH) - rowRes = M5EwResult.FAIL + if (rowRes === M5EwResult.TBD || rowRes === M5EwResult.HIGH) rowRes = M5EwResult.FAIL; } else { - if (rowRes === M5EwResult.TBD) - rowRes = M5EwResult.PASS + if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS; } } else if (face100 >= 0) { - const threshold100 = roll.total + face100 - const threshold = Math.floor(threshold100 / 10) + const threshold100 = roll.total + face100; + const threshold = Math.floor(threshold100 / 10); if (face100 === 100) { - if (rowRes === M5EwResult.TBD) - rowRes = M5EwResult.FUMBLE + if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FUMBLE; } else if (roll.total < 0) { - if (rowRes === M5EwResult.TBD) - rowRes = M5EwResult.FAIL + if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FAIL; } else if (face100 <= threshold) { - if (rowRes === M5EwResult.TBD) - rowRes = M5EwResult.CRITICAL + if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.CRITICAL; } else { - if (rowRes === M5EwResult.TBD) - rowRes = M5EwResult.PASS + if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS; } } - rollResult.css = rowRes - }) + rollResult.css = rowRes; + }); - this.data.res.label = this.label + this.data.res.label = this.label; - this._evaluated = true - return this - }) + this._evaluated = true; + return this; + }); } async render(): Promise { - return renderTemplate(M5Roll.TEMPLATE_PATH, this.data) + return renderTemplate(M5Roll.TEMPLATE_PATH, this.data); } async toMessage() { - if (!this._evaluated) - await this.evaluate() + if (!this._evaluated) await this.evaluate(); - const rMode = (game as Game).settings.get("core", "rollMode") + const rMode = (game as Game).settings.get("core", "rollMode"); const chatData = { type: CONST.CHAT_MESSAGE_TYPES.ROLL, content: await this.render(), - speaker: ChatMessage.getSpeaker({actor: this.actor}), + speaker: ChatMessage.getSpeaker({ actor: this.actor }), sound: CONFIG.sounds.dice, - roll: Roll.fromTerms([this.pool]) - } + roll: Roll.fromTerms([this.pool]), + }; - ChatMessage.applyRollMode(chatData, rMode) - return ChatMessage.create(chatData) + ChatMessage.applyRollMode(chatData, rMode); + return ChatMessage.create(chatData); } static fromAttribute(actor: any, attributeKey: string) { - const character = actor as M5Character - const attribute = character.attribute(attributeKey) + const character = actor as M5Character; + const attribute = character.attribute(attributeKey); - const rollData = actor.getRollData() as M5RollData - rollData.i = attribute.value + attribute.bonus + const rollData = actor.getRollData() as M5RollData; + rollData.i = attribute.value + attribute.bonus; rollData.rolls["0"] = { formula: "@i - 1d100", enabled: true, @@ -135,15 +128,15 @@ export class M5Roll { // extends Roll total: 0, totalStr: "", dice: {}, - css: "" - } as M5RollResult + css: "", + } as M5RollResult; - return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`)) + return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`)); } static fromAttributeValue(actor: any, attributeKey: string, attributeValue: number) { - const rollData = actor.getRollData() as M5RollData - rollData.i = attributeValue + const rollData = actor.getRollData() as M5RollData; + rollData.i = attributeValue; rollData.rolls["0"] = { formula: "@i - 1d100", enabled: true, @@ -152,19 +145,19 @@ export class M5Roll { // extends Roll total: 0, totalStr: "", dice: {}, - css: "" - } as M5RollResult + css: "", + } as M5RollResult; - return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`)) + return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`)); } static fromUnlearnedSkill(actor: any, skill: M5SkillUnlearned, skillName: string) { - const rollData = actor.getRollData() as M5RollData + const rollData = actor.getRollData() as M5RollData; rollData.i = { fw: skill.fw, - bonus: actor.system.calc?.attributes[skill.attribute]?.bonus ?? 0 - } - rollData.iType = "skill" + bonus: actor.system.calc?.attributes[skill.attribute]?.bonus ?? 0, + }; + rollData.iType = "skill"; rollData.rolls["0"] = { formula: "1d20 + @i.fw + @i.bonus", @@ -174,18 +167,18 @@ export class M5Roll { // extends Roll total: 0, totalStr: "", dice: {}, - css: "" - } as M5RollResult + css: "", + } as M5RollResult; - return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.${skillName}`)) + return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.${skillName}`)); } static brawl(actor: any) { - const rollData = actor.getRollData() as M5RollData + const rollData = actor.getRollData() as M5RollData; rollData.i = { attackBonus: 0, - damageBonus: 0 - } + damageBonus: 0, + }; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.brawl + @c.calc.stats.attackBonus + @i.attackBonus", @@ -195,9 +188,9 @@ export class M5Roll { // extends Roll total: 0, totalStr: "", dice: {}, - css: "" - } as M5RollResult - + css: "", + } as M5RollResult; + rollData.rolls["1"] = { formula: "1d6 - 4 + @c.calc.stats.damageBonus + @i.damageBonus", enabled: true, @@ -206,40 +199,100 @@ export class M5Roll { // extends Roll total: 0, totalStr: "", dice: {}, - css: "" - } as M5RollResult + css: "", + } as M5RollResult; - return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.brawl")) + return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.brawl")); + } + + static defense(actor: any) { + const rollData = actor.getRollData() as M5RollData; + rollData.i = { + defenseBonus: 0, + }; + + rollData.rolls["0"] = { + formula: "1d20 + @c.calc.stats.defense.value + @c.calc.stats.defenseBonus.value", + enabled: true, + label: (game as Game).i18n.localize("midgard5.defense"), + result: "", + total: 0, + totalStr: "", + dice: {}, + css: "", + } as M5RollResult; + + return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.defense")); + } + + static resistanceMind(actor: any) { + const rollData = actor.getRollData() as M5RollData; + rollData.i = { + defenseBonus: 0, + }; + + rollData.rolls["0"] = { + formula: "1d20 + @c.calc.stats.resistanceMind.value", + enabled: true, + label: (game as Game).i18n.localize("midgard5.resistanceMind"), + result: "", + total: 0, + totalStr: "", + dice: {}, + css: "", + } as M5RollResult; + + return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.resistanceMind")); + } + + static resistanceBody(actor: any) { + const rollData = actor.getRollData() as M5RollData; + rollData.i = { + defenseBonus: 0, + }; + + rollData.rolls["0"] = { + formula: "1d20 + @c.calc.stats.resistanceBody.value", + enabled: true, + label: (game as Game).i18n.localize("midgard5.resistanceBody"), + result: "", + total: 0, + totalStr: "", + dice: {}, + css: "", + } as M5RollResult; + + return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.resistanceBody")); } static parseDiceSides(formula: string): FormulaParseResult { - const ewMatcher: RegExp = /\d*[dD]20/g - const pwMatcher: RegExp = /(\d+)\s*\-\s*\d*[dD]100/g + const ewMatcher: RegExp = /\d*[dD]20/g; + const pwMatcher: RegExp = /(\d+)\s*\-\s*\d*[dD]100/g; - let res = formula.match(ewMatcher) + let res = formula.match(ewMatcher); if (res && !!res[0]) { return { sides: 20, type: "ew", - threshold: null - } + threshold: null, + }; } - res = formula.match(pwMatcher) + res = formula.match(pwMatcher); if (res && !!res[1]) { return { sides: 100, type: "pw", - threshold: parseInt(res[1]) - } + threshold: parseInt(res[1]), + }; } - return null + return null; } } interface FormulaParseResult { - sides: number, - type: string, - threshold: number + sides: number; + type: string; + threshold: number; } diff --git a/source/module/sheets/M5CharacterSheet.ts b/source/module/sheets/M5CharacterSheet.ts index 45454f8..7d7e1f6 100644 --- a/source/module/sheets/M5CharacterSheet.ts +++ b/source/module/sheets/M5CharacterSheet.ts @@ -167,6 +167,20 @@ export default class M5CharacterSheet extends ActorSheet { await roll.toMessage(); }); + html.find(".roll-defense-button").on("click", async (event) => { + const roll = M5Roll.defense(this.actor); + await roll.toMessage(); + }); + + html.find(".roll-resistanceMind-button").on("click", async (event) => { + const roll = M5Roll.resistanceMind(this.actor); + await roll.toMessage(); + }); + + html.find(".roll-resistanceBody-button").on("click", async (event) => { + const roll = M5Roll.resistanceBody(this.actor); + await roll.toMessage(); + }); // Drag & Drop const dragDrop = new DragDrop({ dragSelector: ".items-list .item", diff --git a/templates/sheets/character/base_values.hbs b/templates/sheets/character/base_values.hbs index ddcfcf3..5a7002c 100644 --- a/templates/sheets/character/base_values.hbs +++ b/templates/sheets/character/base_values.hbs @@ -126,7 +126,16 @@ {{localize "midgard5.race"}} - + + + + + {{localize "midgard5.gender"}} diff --git a/templates/sheets/character/gear.hbs b/templates/sheets/character/gear.hbs index 70a2b91..4e94600 100644 --- a/templates/sheets/character/gear.hbs +++ b/templates/sheets/character/gear.hbs @@ -44,6 +44,27 @@ {{/each}} + + + {{localize "midgard5.defense"}} + {{add data.calc.stats.defense.value data.calc.stats.defenseBonus.value}} + + + + + + {{localize "midgard5.resistanceMind"}} + {{data.calc.stats.resistanceMind.value}} + + + + + + {{localize "midgard5.resistanceBody"}} + {{data.calc.stats.resistanceBody.value}} + + +