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-source-stream": "^2.0.0",
"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.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;

View File

@ -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<M5RollData>
static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs"
export class M5Roll {
// extends Roll<M5RollData>
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<M5RollData>
// @ts-ignore
//override evaluate(options?: InexactPartial<RollTerm.EvaluationOptions>): Evaluated<Roll<M5RollData>> | Promise<Evaluated<Roll<M5RollData>>> {
evaluate() {
const indexMap = new Map<number, string>()
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<number, string>();
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 (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<string> {
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<M5RollData>
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<M5RollData>
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<M5RollData>
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,8 +188,8 @@ export class M5Roll { // extends Roll<M5RollData>
total: 0,
totalStr: "",
dice: {},
css: ""
} as M5RollResult
css: "",
} as M5RollResult;
rollData.rolls["1"] = {
formula: "1d6 - 4 + @c.calc.stats.damageBonus + @i.damageBonus",
@ -206,40 +199,100 @@ export class M5Roll { // extends Roll<M5RollData>
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;
}

View File

@ -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",

View File

@ -126,7 +126,16 @@
<tbody>
<tr>
<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><input name="data.info.gender" type="text" value="{{data.info.gender}}" data-dtype="String" /></td>
</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>
</tr>
{{/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>
</table>