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
This commit is contained in:
Byroks 2023-11-17 16:09:24 +01:00
parent 2b02f787f2
commit 4ec3cc9570
6 changed files with 228 additions and 109 deletions

View File

@ -54,5 +54,8 @@
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0", "vinyl-source-stream": "^2.0.0",
"yargs": "^17.1.1" "yargs": "^17.1.1"
},
"dependencies": {
"handlebars-helpers": "^0.10.0"
} }
} }

View File

@ -108,8 +108,12 @@ export class M5Character extends Actor {
ret.stats.attackBonus = this.modResult(ret.attributes.gs.bonus); ret.stats.attackBonus = this.modResult(ret.attributes.gs.bonus);
ret.stats.defenseBonus = this.modResult(ret.attributes.gw.bonus); ret.stats.defenseBonus = this.modResult(ret.attributes.gw.bonus);
ret.stats.movementBonus = this.modResult(0); ret.stats.movementBonus = this.modResult(0);
ret.stats.resistanceMind = this.modResult(ret.stats.defense.value); ret.stats.resistanceMind = this.modResult(
ret.stats.resistanceBody = this.modResult(ret.stats.defense.value + 1); (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.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.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; ret.stats.brawlEw = ret.stats.brawl.value + ret.stats.attackBonus.value;
@ -251,6 +255,21 @@ export class M5Character extends Actor {
return ret; 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() { prepareDerivedData() {
console.log("M5Character", "prepareDerivedData"); console.log("M5Character", "prepareDerivedData");
const data = (this as any).system; const data = (this as any).system;

View File

@ -2,12 +2,13 @@ import { Evaluated } from "@league-of-foundry-developers/foundry-vtt-types/src/f
import { M5Character } from "../actors/M5Character"; import { M5Character } from "../actors/M5Character";
import { M5EwResult, M5RollData, M5RollResult, M5SkillUnlearned } from "../M5Base"; import { M5EwResult, M5RollData, M5RollResult, M5SkillUnlearned } from "../M5Base";
export class M5Roll { // extends Roll<M5RollData> export class M5Roll {
static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs" // extends Roll<M5RollData>
static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs";
public _evaluated: boolean = false public _evaluated: boolean = false;
public _total: number = 0 public _total: number = 0;
public pool: PoolTerm = null public pool: PoolTerm = null;
constructor(public data: M5RollData, public actor: any, public label: string) { constructor(public data: M5RollData, public actor: any, public label: string) {
//super(null) //super(null)
@ -17,116 +18,108 @@ export class M5Roll { // extends Roll<M5RollData>
// @ts-ignore // @ts-ignore
//override evaluate(options?: InexactPartial<RollTerm.EvaluationOptions>): Evaluated<Roll<M5RollData>> | Promise<Evaluated<Roll<M5RollData>>> { //override evaluate(options?: InexactPartial<RollTerm.EvaluationOptions>): Evaluated<Roll<M5RollData>> | Promise<Evaluated<Roll<M5RollData>>> {
evaluate() { evaluate() {
const indexMap = new Map<number, string>() const indexMap = new Map<number, string>();
const rollNames = Object.keys(this.data.rolls) const rollNames = Object.keys(this.data.rolls);
const rolls = rollNames.filter(rollName => this.data.rolls[rollName].enabled).map((rollName, index) => { const rolls = rollNames
indexMap.set(index, rollName) .filter((rollName) => this.data.rolls[rollName].enabled)
const formula = this.data.rolls[rollName] .map((rollName, index) => {
const roll = new Roll(formula.formula, this.data) indexMap.set(index, rollName);
return roll const formula = this.data.rolls[rollName];
}) const roll = new Roll(formula.formula, this.data);
return roll;
});
this.pool = PoolTerm.fromRolls(rolls) this.pool = PoolTerm.fromRolls(rolls);
console.log("evaluate", this._evaluated, this.pool) console.log("evaluate", this._evaluated, this.pool);
return this.pool.evaluate({ async: true }).then(results => { return this.pool.evaluate({ async: true }).then((results) => {
this._total = 0 this._total = 0;
results.rolls.forEach((roll, index) => { results.rolls.forEach((roll, index) => {
const rollIndex = indexMap.get(index) const rollIndex = indexMap.get(index);
const rollResult = this.data.rolls[rollIndex] as M5RollResult const rollResult = this.data.rolls[rollIndex] as M5RollResult;
rollResult.result = roll.result rollResult.result = roll.result;
rollResult.total = roll.total rollResult.total = roll.total;
rollResult.totalStr = roll.total.toString() rollResult.totalStr = roll.total.toString();
this._total += roll.total this._total += roll.total;
let rowRes = M5EwResult.TBD let rowRes = M5EwResult.TBD;
let face100 = -1 let face100 = -1;
roll.dice.forEach((d, dIndex) => { roll.dice.forEach((d, dIndex) => {
rollResult.dice[dIndex.toString()] = d.total rollResult.dice[dIndex.toString()] = d.total;
if (rowRes === M5EwResult.TBD && dIndex === 0) { if (rowRes === M5EwResult.TBD && dIndex === 0) {
if (d.faces === 20) { if (d.faces === 20) {
//if (rollResult.type === "ew") { //if (rollResult.type === "ew") {
if (d.total === 1) if (d.total === 1) rowRes = M5EwResult.FUMBLE;
rowRes = M5EwResult.FUMBLE else if (d.total === 20) rowRes = M5EwResult.CRITICAL;
else if (d.total === 20) else if (d.total >= 16) rowRes = M5EwResult.HIGH;
rowRes = M5EwResult.CRITICAL
else if (d.total >= 16)
rowRes = M5EwResult.HIGH
} else if (d.faces === 100) { } 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) //console.log("evaluate roll", parseResult)
if (parseResult?.sides === 20) { if (parseResult?.sides === 20) {
if (roll.total < 20) { if (roll.total < 20) {
if (rowRes === M5EwResult.TBD || rowRes === M5EwResult.HIGH) if (rowRes === M5EwResult.TBD || rowRes === M5EwResult.HIGH) rowRes = M5EwResult.FAIL;
rowRes = M5EwResult.FAIL
} else { } else {
if (rowRes === M5EwResult.TBD) if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS;
rowRes = M5EwResult.PASS
} }
} else if (face100 >= 0) { } else if (face100 >= 0) {
const threshold100 = roll.total + face100 const threshold100 = roll.total + face100;
const threshold = Math.floor(threshold100 / 10) const threshold = Math.floor(threshold100 / 10);
if (face100 === 100) { if (face100 === 100) {
if (rowRes === M5EwResult.TBD) if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FUMBLE;
rowRes = M5EwResult.FUMBLE
} else if (roll.total < 0) { } else if (roll.total < 0) {
if (rowRes === M5EwResult.TBD) if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FAIL;
rowRes = M5EwResult.FAIL
} else if (face100 <= threshold) { } else if (face100 <= threshold) {
if (rowRes === M5EwResult.TBD) if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.CRITICAL;
rowRes = M5EwResult.CRITICAL
} else { } else {
if (rowRes === M5EwResult.TBD) if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS;
rowRes = M5EwResult.PASS
} }
} }
rollResult.css = rowRes rollResult.css = rowRes;
}) });
this.data.res.label = this.label this.data.res.label = this.label;
this._evaluated = true this._evaluated = true;
return this return this;
}) });
} }
async render(): Promise<string> { async render(): Promise<string> {
return renderTemplate(M5Roll.TEMPLATE_PATH, this.data) return renderTemplate(M5Roll.TEMPLATE_PATH, this.data);
} }
async toMessage() { async toMessage() {
if (!this._evaluated) if (!this._evaluated) await this.evaluate();
await this.evaluate()
const rMode = (game as Game).settings.get("core", "rollMode") const rMode = (game as Game).settings.get("core", "rollMode");
const chatData = { const chatData = {
type: CONST.CHAT_MESSAGE_TYPES.ROLL, type: CONST.CHAT_MESSAGE_TYPES.ROLL,
content: await this.render(), content: await this.render(),
speaker: ChatMessage.getSpeaker({ actor: this.actor }), speaker: ChatMessage.getSpeaker({ actor: this.actor }),
sound: CONFIG.sounds.dice, sound: CONFIG.sounds.dice,
roll: Roll.fromTerms([this.pool]) roll: Roll.fromTerms([this.pool]),
} };
ChatMessage.applyRollMode(chatData, rMode) ChatMessage.applyRollMode(chatData, rMode);
return ChatMessage.create(chatData) return ChatMessage.create(chatData);
} }
static fromAttribute(actor: any, attributeKey: string) { static fromAttribute(actor: any, attributeKey: string) {
const character = actor as M5Character const character = actor as M5Character;
const attribute = character.attribute(attributeKey) const attribute = character.attribute(attributeKey);
const rollData = actor.getRollData() as M5RollData const rollData = actor.getRollData() as M5RollData;
rollData.i = attribute.value + attribute.bonus rollData.i = attribute.value + attribute.bonus;
rollData.rolls["0"] = { rollData.rolls["0"] = {
formula: "@i - 1d100", formula: "@i - 1d100",
enabled: true, enabled: true,
@ -135,15 +128,15 @@ export class M5Roll { // extends Roll<M5RollData>
total: 0, total: 0,
totalStr: "", totalStr: "",
dice: {}, dice: {},
css: "" css: "",
} as M5RollResult } 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) { static fromAttributeValue(actor: any, attributeKey: string, attributeValue: number) {
const rollData = actor.getRollData() as M5RollData const rollData = actor.getRollData() as M5RollData;
rollData.i = attributeValue rollData.i = attributeValue;
rollData.rolls["0"] = { rollData.rolls["0"] = {
formula: "@i - 1d100", formula: "@i - 1d100",
enabled: true, enabled: true,
@ -152,19 +145,19 @@ export class M5Roll { // extends Roll<M5RollData>
total: 0, total: 0,
totalStr: "", totalStr: "",
dice: {}, dice: {},
css: "" css: "",
} as M5RollResult } 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) { static fromUnlearnedSkill(actor: any, skill: M5SkillUnlearned, skillName: string) {
const rollData = actor.getRollData() as M5RollData const rollData = actor.getRollData() as M5RollData;
rollData.i = { rollData.i = {
fw: skill.fw, fw: skill.fw,
bonus: actor.system.calc?.attributes[skill.attribute]?.bonus ?? 0 bonus: actor.system.calc?.attributes[skill.attribute]?.bonus ?? 0,
} };
rollData.iType = "skill" rollData.iType = "skill";
rollData.rolls["0"] = { rollData.rolls["0"] = {
formula: "1d20 + @i.fw + @i.bonus", formula: "1d20 + @i.fw + @i.bonus",
@ -174,18 +167,18 @@ export class M5Roll { // extends Roll<M5RollData>
total: 0, total: 0,
totalStr: "", totalStr: "",
dice: {}, dice: {},
css: "" css: "",
} as M5RollResult } 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) { static brawl(actor: any) {
const rollData = actor.getRollData() as M5RollData const rollData = actor.getRollData() as M5RollData;
rollData.i = { rollData.i = {
attackBonus: 0, attackBonus: 0,
damageBonus: 0 damageBonus: 0,
} };
rollData.rolls["0"] = { rollData.rolls["0"] = {
formula: "1d20 + @c.calc.stats.brawl + @c.calc.stats.attackBonus + @i.attackBonus", formula: "1d20 + @c.calc.stats.brawl + @c.calc.stats.attackBonus + @i.attackBonus",
@ -195,8 +188,8 @@ export class M5Roll { // extends Roll<M5RollData>
total: 0, total: 0,
totalStr: "", totalStr: "",
dice: {}, dice: {},
css: "" css: "",
} as M5RollResult } as M5RollResult;
rollData.rolls["1"] = { rollData.rolls["1"] = {
formula: "1d6 - 4 + @c.calc.stats.damageBonus + @i.damageBonus", formula: "1d6 - 4 + @c.calc.stats.damageBonus + @i.damageBonus",
@ -206,40 +199,100 @@ export class M5Roll { // extends Roll<M5RollData>
total: 0, total: 0,
totalStr: "", totalStr: "",
dice: {}, dice: {},
css: "" css: "",
} as M5RollResult } 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 { static parseDiceSides(formula: string): FormulaParseResult {
const ewMatcher: RegExp = /\d*[dD]20/g const ewMatcher: RegExp = /\d*[dD]20/g;
const pwMatcher: RegExp = /(\d+)\s*\-\s*\d*[dD]100/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]) { if (res && !!res[0]) {
return { return {
sides: 20, sides: 20,
type: "ew", type: "ew",
threshold: null threshold: null,
} };
} }
res = formula.match(pwMatcher) res = formula.match(pwMatcher);
if (res && !!res[1]) { if (res && !!res[1]) {
return { return {
sides: 100, sides: 100,
type: "pw", type: "pw",
threshold: parseInt(res[1]) threshold: parseInt(res[1]),
} };
} }
return null return null;
} }
} }
interface FormulaParseResult { interface FormulaParseResult {
sides: number, sides: number;
type: string, type: string;
threshold: number threshold: number;
} }

View File

@ -167,6 +167,20 @@ export default class M5CharacterSheet extends ActorSheet {
await roll.toMessage(); 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 // Drag & Drop
const dragDrop = new DragDrop({ const dragDrop = new DragDrop({
dragSelector: ".items-list .item", dragSelector: ".items-list .item",

View File

@ -126,7 +126,16 @@
<tbody> <tbody>
<tr> <tr>
<td>{{localize "midgard5.race"}}</td> <td>{{localize "midgard5.race"}}</td>
<td><input name="data.info.race" type="text" value="{{data.info.race}}" data-dtype="String" /></td> <td>
<input list="races" type="search" name="data.info.race" value="{{data.info.race}}" />
<datalist id="races">
<option value="Mensch">
<option value="Elf">
<option value="Gnom">
<option value="Halbling">
<option value="Zwerg">
</datalist>
</td>
<td>{{localize "midgard5.gender"}}</td> <td>{{localize "midgard5.gender"}}</td>
<td><input name="data.info.gender" type="text" value="{{data.info.gender}}" data-dtype="String" /></td> <td><input name="data.info.gender" type="text" value="{{data.info.gender}}" data-dtype="String" /></td>
</tr> </tr>

View File

@ -44,6 +44,27 @@
<td class="fixed-value"><a class="item-delete" title="Delete Item"><i class="fas fa-trash"></i></a></td> <td class="fixed-value"><a class="item-delete" title="Delete Item"><i class="fas fa-trash"></i></a></td>
</tr> </tr>
{{/each}} {{/each}}
<tr data-item="{{itemId}}">
<td class="padding edit-item">{{localize "midgard5.defense"}}</td>
<td class="center">{{add data.calc.stats.defense.value data.calc.stats.defenseBonus.value}}</td>
<td class="fixed-value"><button class="roll-button roll-defense-button"></button></td>
<td class="fixed-value"></td>
</tr>
<tr data-item="{{itemId}}">
<td class="padding edit-item">{{localize "midgard5.resistanceMind"}}</td>
<td class="center">{{data.calc.stats.resistanceMind.value}}</td>
<td class="fixed-value"><button class="roll-button roll-resistanceMind-button"></button></td>
<td class="fixed-value"></td>
</tr>
<tr data-item="{{itemId}}">
<td class="padding edit-item">{{localize "midgard5.resistanceBody"}}</td>
<td class="center">{{data.calc.stats.resistanceBody.value}}</td>
<td class="fixed-value"><button class="roll-button roll-resistanceBody-button"></button></td>
<td class="fixed-value"></td>
</tr>
</tbody> </tbody>
</table> </table>