implements partial (display) support for SpoMods

pull/67/head
macniel 2025-11-26 20:54:32 +01:00
parent 5f85631679
commit 959f47f348
4 changed files with 222 additions and 43 deletions

View File

@ -22,8 +22,8 @@ export class SpellDataModel extends BaseItem {
hauszauber: new BooleanField(),
technik: new StringField(),
zauberdauer: new SchemaField({
min: new NumberField(),
normal: new NumberField(),
min: new StringField(),
normal: new StringField(),
additionalFormula: new StringField(),
variables: new ArrayField(new StringField()),
additionalFormulaTimeUnit: new StringField(),

View File

@ -0,0 +1,66 @@
const data = {
"Veränderte Technik": {
name: "Veränderte Technik",
description: "Verändert die Technik",
mod: -7,
castTimeMod: 3,
castTimeMode: "Addition"
},
"Veränderte Technik, zentral": {
name: "Veränderte Technik, zentral",
description: "Verändert die Technik",
mod: -12,
castTimeMod: 3,
castTimeMode: "Addition"
},
"Halbierte Zauberdauer": {
name: "Halbierte Zauberdauer",
description: "Halbiert die Zauberdauer für eine Erschwernis von 5",
mod: -5,
castTimeMod: 0.5,
castTimeMode: "Multiply"
},
"Verdoppelte Zauberdauer": {
name: "Verdoppelte Zauberdauer",
description: "Verdoppelt die Zauberdauer für eine Erleichterung von 3",
mod: 3,
castTimeMod: 2,
castTimeMode: "Multiply"
},
"Erzwingen": {
name: "Erzwingen",
description: "Verringert Erschwernis um 1 je doppelten AsP Punkt",
mod: -1,
castTimeMod: 1,
castTimeMode: "Addition"
},
"Kosten einsparen": {
name: "Kosten einsparen",
description: "Reduziert die Kosten des Zaubers um 10% für jede zusätzlich aufgewendete Aktion",
mod: 3,
castingCosts: 0.1,
castingCostsMode: "Multiply",
castTimeMod: 1,
castTimeMode: "Addition"
},
// more to come
"Vergrößerung von Reichweite oder Wirkungsradius": {
name: "Vergrößerung von Reichweite oder Wirkungsradius",
description: "Vergrößert die Reichweite oder wenn möglich den Wirkungsradius auf kosten von Aktionen",
mod: 5,
castTimeMod: 1,
castTimeMode: "Addition"
},
"Verkleinerung von Reichweite oder Wirkungsradius": {
name: "Verkleinerung von Reichweite oder Wirkungsradius",
description: "Verkleinert die Reichweite oder wenn möglich den Wirkungsradius auf kosten von Aktionen",
mod: 3,
castTimeMod: 1,
castTimeMode: "Addition"
},
}
export default data

View File

@ -1,5 +1,6 @@
import {Talent} from "../data/talent.mjs";
import {ATTRIBUTE} from "../data/attribute.mjs";
import SpoModData from "../data/spomods/spoModData.mjs";
const {
ApplicationV2,
@ -44,23 +45,32 @@ export class SpellDialog extends HandlebarsApplicationMixin(ApplicationV2) {
* @private
*/
_actor = null
_mutators = {}
_costMutators = {}
_castTimeMutators = {}
_variants = {}
_costModel = {}
_castTimeModel = {}
displayModResult = 0
constructor(actor, spellId) {
super()
this._actor = actor
this._spell = this._actor.itemTypes["Spell"].find(p => p._id === spellId)
this.displayModResult = Number(this._spell.system.zfw)
this._circumstance = 0
this._mods = []
this._mutators = {}
this.mod = 0
this._costMutators = {}
this._castTimeMutators = {}
this._selectedRepresentation = null
this._spellDie = null
this._variants = {}
this._costModel = {}
this._costModel = this._spell.system.kosten.find(c => c.repräsentation === context.selectedRepresentation) ?? this._spell.system.kosten.find(c => c.repräsentation === "")
this._castTimeModel = this._spell.system.zauberdauer
this._castTimeMutators = {}
this._costMutators = {}
this._costModel.variables.forEach(v => this._costMutators[v] = 0)
this._castTimeModel.variables.forEach(v => this._castTimeMutators[v] = 0)
this.cost = this.normalizeCastingCost() ?? 0
}
/**
@ -100,24 +110,34 @@ export class SpellDialog extends HandlebarsApplicationMixin(ApplicationV2) {
}
if (this._selectedRepresentation) {
this._costModel = this._spell.system.kosten.find(c => c.repräsentation === context.selectedRepresentation) ?? this._spell.system.kosten.find(c => c.repräsentation === "")
this._mutators = {}
this._costModel.variables.forEach(v => this._mutators[v] = 0)
this._castTimeModel = this._spell.system.zauberdauer
this._castTimeMutators = {}
this._costMutators = {}
this._costModel.variables.forEach(v => this._costMutators[v] = 0)
this._castTimeModel.variables.forEach(v => this._castTimeMutators[v] = 0)
}
let mutators = foundry.utils.expandObject(formData.object)["mutators"]
let costMutators = foundry.utils.expandObject(formData.object)["costMutators"]
if (mutators) {
this._mutators = mutators
if (costMutators) {
this._costMutators = costMutators
}
this.displayModResult = Number(this._spell.system.zfw)
this.cost = this.normalizeCastingCost()
let castTimeMutators = foundry.utils.expandObject(formData.object)["castTimeMutators"]
if (castTimeMutators) {
this._castTimeMutators = castTimeMutators
}
this.mod = 0
this._activeVariants = Object.entries(this._variants)
.filter(([key, truthiness]) => truthiness)
.map(([key, truthiness]) => this._spell.system.varianten.find(v => v.name === key))
this._activeVariants.forEach(variant => {
if (variant.mod) {
this.displayModResult += Number(variant.mod)
this.mod += Number(variant.mod)
}
})
@ -129,6 +149,23 @@ export class SpellDialog extends HandlebarsApplicationMixin(ApplicationV2) {
}
normalizeCastingCost() {
let costFormula = this._costModel.additionalFormula
this._costModel.variables.forEach(v => {
costFormula = costFormula.replace(v, this._costMutators[v])
})
costFormula = Number(eval(costFormula)) + Number(this._costModel.cost)
if (costFormula <= this._costModel.min) {
costFormula = this._costModel.min
}
return costFormula
}
/**
*
* @param spell
@ -141,20 +178,50 @@ export class SpellDialog extends HandlebarsApplicationMixin(ApplicationV2) {
// normal: Standard Zauberzeit eines Zaubers
// additionalFormulaData: enthält die zur Normalzeit zusätzlichen Zauberdauer
const castingTime = spell.system.zauberdauer
// direct actions
const aktionenRegExp = /(.*) Aktionen/
const srRegExp = /(.*) SR/
if (castingTime.match(aktionenRegExp)) {
const [_, actions] = castingTime.match(aktionenRegExp)
return actions
} else if (castingTime.match(srRegExp)) {
const [_, actions] = castingTime.match(srRegExp)
return actions * 20
const castingTime = spell.system.zauberdauer.normal ?? 0
let baseCastTime = 0
const minCastingTime = spell.system.zauberdauer.min ?? 0
let baseMinCastTime = 0
if (castingTime) {
baseCastTime = castingTime.replace(/(.*) Aktionen/g, (_, aktionen) => {
return aktionen
})
baseCastTime = baseCastTime.replace(/(.*) SR/g, (_, aktionen) => {
return aktionen * 20
})
}
return -1
if (minCastingTime) {
baseMinCastTime = minCastingTime.replace(/(.*) Aktionen/g, (_, aktionen) => {
return aktionen
})
baseMinCastTime = baseMinCastTime.replace(/(.*) SR/g, (_, aktionen) => {
return aktionen * 20
})
}
let actualCastingTime = 0
let formula = spell.system.zauberdauer.additionalFormula
if (formula) {
Object.entries(this._castTimeMutators).forEach(([variableName, variableValue]) => {
console.log(variableName, variableValue)
formula = formula.replaceAll(variableName, variableValue)
})
if (spell.system.zauberdauer.additionalFormulaTimeUnit == "Aktionen") {
actualCastingTime = (Number(baseCastTime) + Number(eval(formula)) ?? 0)
} else {
actualCastingTime = (Number(baseCastTime) + (Number(eval(formula)) * 20) ?? 0)
}
} else {
actualCastingTime = baseCastTime
}
if (Number(actualCastingTime) <= Number(baseMinCastTime)) {
actualCastingTime = baseMinCastTime
}
return actualCastingTime
}
_configureRenderOptions(options) {
@ -180,8 +247,12 @@ export class SpellDialog extends HandlebarsApplicationMixin(ApplicationV2) {
context.text = this._spell.system.wirkung
context.dice = []
context.colorfulDice = game.settings.get('DSA_4-1', 'optional_colorfuldice')
context.modResult = this.displayModResult
context.displayModResult = (context.modResult > 0 ? "+" : "") + this.displayModResult
context.modResult = this._spell.system.zfw - this.mod
context.penalty = (this.mod > 0 ? "+" : "") + this.mod
context.displayModResult = (context.modResult > 0 ? "+" : "") + context.modResult
context.castingTime = this.#normalizeCastingTime(this._spell)
// variable probe (should consider Achaz as they can replace one KL in a KL/KL/* spell with IN
this._spell.system.probe.map(p => {
if (p === "*") {
@ -214,28 +285,48 @@ export class SpellDialog extends HandlebarsApplicationMixin(ApplicationV2) {
}
})
// Repräsentation
context.representationOptions[""] = ""
Object.entries(this._spell.system.repräsentation).forEach(([key, value]) => {
context.representationOptions[key] = key
})
// Costs and Mutators
context.castingCosts = this.cost
// set probe to current held probe variables or take from _spell
context.mutators = this._mutators
context.costMutators = this._costMutators
if (this._costModel) {
context.variables = this._costModel.variables
context.costVariables = this._costModel.variables
} else {
context.variables = []
context.costVariables = []
}
// SpoMods
context.spoMods = []
if (this._spell.system.modifikationen) {
this._spell.system.modifikationen.split(",").forEach(spoMod => {
// generate a list of variables in cost and castTime
// generate a list of variants
// generate a list of SpoMods
switch (spoMod.trim()) {
case "Zauberdauer":
context.spoMods.push(SpoModData["Halbierte Zauberdauer"])
context.spoMods.push(SpoModData["Verdoppelte Zauberdauer"])
break;
case "Kosten":
context.spoMods.push(SpoModData["Kosten einsparen"])
break;
case "Reichweite":
context.spoMods.push(SpoModData["Verkleinerung von Reichweite oder Wirkungsradius"])
context.spoMods.push(SpoModData["Vergrößerung von Reichweite oder Wirkungsradius"])
break;
}
})
}
return context
}

View File

@ -50,17 +50,39 @@
</fieldset>
{{/if}}
<fieldset>
<legend>Modifikationen</legend>
{{#if this.spoMods}}
<fieldset class="scroll-y">
<legend>Spontane Modifikation</legend>
<div class="variantList">
{{#each this.spoMods}}
<div class="variant" data-tooltip="{{{description}}}">
<div class="selection">
<label>
<input type="checkbox" name="spoMods.{{name}}" {{checked variantChecked}}>
{{#if (gte mod 0)}}+{{/if}}{{mod}}
</label>
</div>
<div class="variantText">
<em>{{name}}</em>
</div>
</div>
{{/each}}
</div>
</fieldset>
{{/if}}
<fieldset>
<legend>Variablen</legend>
{{#each variables}}
{{#each castTimeVariables}}
<div>
<label for="{{this}}">{{this}}: </label>
<input type="number" name="mutators.{{this}}" value="{{lookup ../mutators this}}"/>
<input type="number" name="castTimeMutators.{{this}}" value="{{lookup ../castTimeMutators this}}"/>
</div>
{{/each}}
{{#each costVariables}}
<div>
<label for="{{this}}">{{this}}: </label>
<input type="number" name="costMutators.{{this}}" value="{{lookup ../costMutators this}}"/>
</div>
{{/each}}
</fieldset>
@ -69,10 +91,10 @@
<fieldset>
<legend>Dauer und Kosten</legend>
<div class="malus-and-mod">
<label><span>Zauberdauer</span>
<label><span>Zauberdauer (A)</span>
<output name="castingTime">{{castingTime}}</output>
</label>
<label><span>Kosten</span>
<label><span>Kosten (AsP)</span>
<output name="costs">{{castingCosts}}</output>
</label>
<label><span>Erschwernis</span>