automatically calculate base combat values
This commit is contained in:
parent
a020cb7403
commit
ecfbe9fa2a
15 changed files with 195 additions and 27 deletions
|
@ -175,6 +175,9 @@
|
||||||
"DS4.WarningActorCannotOwnItem": "Der Aktor '{actorName}' vom Typ '{actorType}' kann das Item '{itemName}' vom Typ '{itemType}' nicht besitzen.",
|
"DS4.WarningActorCannotOwnItem": "Der Aktor '{actorName}' vom Typ '{actorType}' kann das Item '{itemName}' vom Typ '{itemType}' nicht besitzen.",
|
||||||
"DS4.ErrorDiceCritOverlap": "Es gibt eine Überlappung zwischen Patzern und Immersiegen.",
|
"DS4.ErrorDiceCritOverlap": "Es gibt eine Überlappung zwischen Patzern und Immersiegen.",
|
||||||
"DS4.ErrorExplodingRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.",
|
"DS4.ErrorExplodingRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.",
|
||||||
|
"DS4.ErrorDuringMigration": "Fehler während der Aktualisierung des DS4 Systems von Version {currentVersion} auf {targetVersion}. Der Fehler trat während der Ausführung des Migrationsskripts mit der Version {migrationVersion} auf. Spätere Migrationsskripte wurden nicht ausgeführt. Mehr Details finden Sie in der Entwicklerkonsole (F12).",
|
||||||
|
"DS4.InfoSystemUpdateStart": "Aktualisiere DS4 System von Version {currentVersion} auf {targetVersion}. Bitte haben Sie etwas Geduld, schließen Sie nicht das Spiel und fahren Sie nicht den Server herunter.",
|
||||||
|
"DS4.InfoSystemUpdateCompleted": "Aktualisierung des DS4 Systems von Version {currentVersion} auf {targetVersion} erfolgreich!",
|
||||||
"DS4.UnitRounds": "Runden",
|
"DS4.UnitRounds": "Runden",
|
||||||
"DS4.UnitRoundsAbbr": "Rnd",
|
"DS4.UnitRoundsAbbr": "Rnd",
|
||||||
"DS4.UnitMinutes": "Minuten",
|
"DS4.UnitMinutes": "Minuten",
|
||||||
|
|
|
@ -175,6 +175,9 @@
|
||||||
"DS4.WarningActorCannotOwnItem": "The actor '{actorName}' of type '{actorType}' cannot own the item '{itemName}' of type '{itemType}'.",
|
"DS4.WarningActorCannotOwnItem": "The actor '{actorName}' of type '{actorType}' cannot own the item '{itemName}' of type '{itemType}'.",
|
||||||
"DS4.ErrorDiceCritOverlap": "There's an overlap between Fumbles and Coups",
|
"DS4.ErrorDiceCritOverlap": "There's an overlap between Fumbles and Coups",
|
||||||
"DS4.ErrorExplodingRecursionLimitExceeded": "Maximum recursion depth for exploding dice roll exceeded",
|
"DS4.ErrorExplodingRecursionLimitExceeded": "Maximum recursion depth for exploding dice roll exceeded",
|
||||||
|
"DS4.ErrorDuringMigration": "Error while migrating DS4 system from version {currentVersion} to {targetVersion}. The error occurred during execution of migration script with version {migrationVersion}. Later migrations have not been executed. For more details, please look at the development console (F12).",
|
||||||
|
"DS4.InfoSystemUpdateStart": "Migrating DS4 system from version {currentVersion} to {targetVersion}. Please be patient and do not close your game or shut down your server.",
|
||||||
|
"DS4.InfoSystemUpdateCompleted": "Migration of DS4 system from version {currentVersion} to {targetVersion} successful!",
|
||||||
"DS4.UnitRounds": "Rounds",
|
"DS4.UnitRounds": "Rounds",
|
||||||
"DS4.UnitRoundsAbbr": "rnd",
|
"DS4.UnitRoundsAbbr": "rnd",
|
||||||
"DS4.UnitMinutes": "Minutes",
|
"DS4.UnitMinutes": "Minutes",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ModifiableData } from "../common/common-data";
|
import { ModifiableData } from "../common/common-data";
|
||||||
import { DS4Item } from "../item/item";
|
import { DS4Item } from "../item/item";
|
||||||
import { DS4ItemDataType, ItemType } from "../item/item-data";
|
import { DS4Armor, DS4ItemDataType, DS4Shield, ItemType } from "../item/item-data";
|
||||||
import { DS4ActorDataType } from "./actor-data";
|
import { DS4ActorDataType } from "./actor-data";
|
||||||
|
|
||||||
export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item> {
|
export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item> {
|
||||||
|
@ -15,12 +15,7 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
||||||
const traits = data.data.traits;
|
const traits = data.data.traits;
|
||||||
Object.values(traits).forEach((trait: ModifiableData<number>) => (trait.total = trait.base + trait.mod));
|
Object.values(traits).forEach((trait: ModifiableData<number>) => (trait.total = trait.base + trait.mod));
|
||||||
|
|
||||||
const combatValues = data.data.combatValues;
|
this._prepareCombatValues();
|
||||||
Object.values(combatValues).forEach(
|
|
||||||
(combatValue: ModifiableData<number>) => (combatValue.total = combatValue.base + combatValue.mod),
|
|
||||||
);
|
|
||||||
|
|
||||||
combatValues.hitPoints.max = combatValues.hitPoints.total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,4 +50,44 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
||||||
canOwnItemType(itemType: ItemType): boolean {
|
canOwnItemType(itemType: ItemType): boolean {
|
||||||
return this.ownableItemTypes.includes(itemType);
|
return this.ownableItemTypes.includes(itemType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the combat values of the actor.
|
||||||
|
*/
|
||||||
|
private _prepareCombatValues(): void {
|
||||||
|
const data = this.data.data;
|
||||||
|
const armorValueOfEquippedItems = this._calculateArmorValueOfEquippedItems();
|
||||||
|
data.combatValues.hitPoints.base =
|
||||||
|
(data.attributes.body.total ?? 0) + (data.traits.constitution.total ?? 0) + 10;
|
||||||
|
|
||||||
|
data.combatValues.defense.base =
|
||||||
|
(data.attributes.body.total ?? 0) + (data.traits.constitution.total ?? 0) + armorValueOfEquippedItems;
|
||||||
|
data.combatValues.initiative.base = (data.attributes.mobility.total ?? 0) + (data.traits.agility.total ?? 0);
|
||||||
|
data.combatValues.movement.base = (data.attributes.mobility.total ?? 0) / 2 + 1;
|
||||||
|
data.combatValues.meleeAttack.base = (data.attributes.body.total ?? 0) + (data.traits.strength.total ?? 0);
|
||||||
|
data.combatValues.rangedAttack.base =
|
||||||
|
(data.attributes.mobility.total ?? 0) + (data.traits.dexterity.total ?? 0);
|
||||||
|
data.combatValues.spellcasting.base =
|
||||||
|
(data.attributes.mind.total ?? 0) + (data.traits.aura.total ?? 0) - armorValueOfEquippedItems;
|
||||||
|
data.combatValues.targetedSpellcasting.base =
|
||||||
|
(data.attributes.mind.total ?? 0) + (data.traits.dexterity.total ?? 0) - armorValueOfEquippedItems;
|
||||||
|
|
||||||
|
Object.values(data.combatValues).forEach(
|
||||||
|
(combatValue: ModifiableData<number>) => (combatValue.total = combatValue.base + combatValue.mod),
|
||||||
|
);
|
||||||
|
|
||||||
|
data.combatValues.hitPoints.max = data.combatValues.hitPoints.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the total armor value of all equipped items.
|
||||||
|
*/
|
||||||
|
private _calculateArmorValueOfEquippedItems(): number {
|
||||||
|
return this.items
|
||||||
|
.filter((item) => ["armor", "shield"].includes(item.type))
|
||||||
|
.map((item) => item.data.data as DS4Armor | DS4Shield)
|
||||||
|
.filter((itemData) => itemData.equipped)
|
||||||
|
.map((itemData) => itemData.armorValue)
|
||||||
|
.reduce((a, b) => a + b, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
||||||
classes: ["ds4", "sheet", "actor"],
|
classes: ["ds4", "sheet", "actor"],
|
||||||
width: 745,
|
width: 745,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
scrollY: [".sheet-body"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,13 @@ export interface ModifiableData<T> {
|
||||||
total?: T;
|
total?: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceData<T> extends ModifiableData<T> {
|
export interface ModifiableMaybeData<T> {
|
||||||
|
base?: T;
|
||||||
|
mod: T;
|
||||||
|
total?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceData<T> extends ModifiableMaybeData<T> {
|
||||||
value: T;
|
value: T;
|
||||||
max?: T;
|
max?: T;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { DS4Check } from "./rolls/check";
|
||||||
import { DS4CharacterActorSheet } from "./actor/sheets/character-sheet";
|
import { DS4CharacterActorSheet } from "./actor/sheets/character-sheet";
|
||||||
import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet";
|
import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet";
|
||||||
import { createCheckRoll } from "./rolls/check-factory";
|
import { createCheckRoll } from "./rolls/check-factory";
|
||||||
|
import { registerSystemSettings } from "./settings";
|
||||||
|
import { migrate } from "./migrations";
|
||||||
|
|
||||||
Hooks.once("init", async function () {
|
Hooks.once("init", async function () {
|
||||||
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
|
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
|
||||||
|
@ -37,6 +39,9 @@ Hooks.once("init", async function () {
|
||||||
s: DS4Check,
|
s: DS4Check,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Register system settings
|
||||||
|
registerSystemSettings();
|
||||||
|
|
||||||
// Register sheet application classes
|
// Register sheet application classes
|
||||||
Actors.unregisterSheet("core", ActorSheet);
|
Actors.unregisterSheet("core", ActorSheet);
|
||||||
Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true });
|
Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true });
|
||||||
|
@ -123,3 +128,7 @@ Hooks.once("setup", function () {
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Hooks.once("ready", function () {
|
||||||
|
migrate();
|
||||||
|
});
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface DS4Weapon extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {
|
||||||
opponentDefense: number;
|
opponentDefense: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DS4Armor extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {
|
export interface DS4Armor extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {
|
||||||
armorMaterialType: "cloth" | "leather" | "chain" | "plate";
|
armorMaterialType: "cloth" | "leather" | "chain" | "plate";
|
||||||
armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves";
|
armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves";
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ interface DS4Spell extends DS4ItemBase, DS4ItemEquipable {
|
||||||
scrollPrice: number;
|
scrollPrice: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DS4Shield extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {}
|
export interface DS4Shield extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {}
|
||||||
interface DS4Trinket extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {}
|
interface DS4Trinket extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {}
|
||||||
interface DS4Equipment extends DS4ItemBase, DS4ItemPhysical {}
|
interface DS4Equipment extends DS4ItemBase, DS4ItemPhysical {}
|
||||||
type DS4RacialAbility = DS4ItemBase;
|
type DS4RacialAbility = DS4ItemBase;
|
||||||
|
|
|
@ -13,6 +13,7 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
||||||
height: 400,
|
height: 400,
|
||||||
classes: ["ds4", "sheet", "item"],
|
classes: ["ds4", "sheet", "item"],
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
|
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
|
||||||
|
scrollY: [".sheet-body"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
77
src/module/migrations.ts
Normal file
77
src/module/migrations.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { migrate as migrate0_1_0 } from "./migrations/0-1-0";
|
||||||
|
|
||||||
|
export async function migrate(): Promise<void> {
|
||||||
|
if (!game.user.isGM) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVersion: string = game.settings.get("ds4", "systemMigrationVersion");
|
||||||
|
const targetVersion = game.system.data.version;
|
||||||
|
|
||||||
|
if (isFirstWorldStart(currentVersion)) {
|
||||||
|
game.settings.set("ds4", "systemMigrationVersion", targetVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNewerVersion(targetVersion, currentVersion)) {
|
||||||
|
ui.notifications.info(
|
||||||
|
game.i18n.format("DS4.InfoSystemUpdateStart", {
|
||||||
|
currentVersion: currentVersion,
|
||||||
|
targetVersion: targetVersion,
|
||||||
|
}),
|
||||||
|
{ permanent: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const migrationFunctionsToExecute = Object.entries(migrations)
|
||||||
|
.filter(([version]) => !isNewerVersion(currentVersion, version)) // currentVersion <= version of migration
|
||||||
|
.filter(([version]) => isNewerVersion(targetVersion, version)); // targetVersion > version of migration
|
||||||
|
|
||||||
|
for (const migration of migrationFunctionsToExecute) {
|
||||||
|
try {
|
||||||
|
await migration[1]();
|
||||||
|
} catch (err) {
|
||||||
|
ui.notifications.error(
|
||||||
|
game.i18n.format("DS4.ErrorDuringMigration", {
|
||||||
|
currentVersion: currentVersion,
|
||||||
|
targetVersion: targetVersion,
|
||||||
|
migrationVersion: migration[0],
|
||||||
|
}),
|
||||||
|
{ permanent: true },
|
||||||
|
);
|
||||||
|
err.message = `Failed ds4 system migration: ${err.message}`;
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.notifications.info(
|
||||||
|
game.i18n.format("DS4.InfoSystemUpdateCompleted", {
|
||||||
|
currentVersion: currentVersion,
|
||||||
|
targetVersion: targetVersion,
|
||||||
|
}),
|
||||||
|
{ permanent: true },
|
||||||
|
);
|
||||||
|
game.settings.set("ds4", "systemMigrationVersion", targetVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The migrations to (potentially) execute when a world is loaded. The key
|
||||||
|
* specifies the version number _from_ which is being migrated to the next
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* Given the migration version numbers "0.0.1", "0.0.2", "0.1.2", "0.4.9", and
|
||||||
|
* "0.5.0" an update from version "0.0.2" to "0.5.0" would execute the
|
||||||
|
* migrations with version "0.0.2", "0.1.2", and "0.4.9". In particular,
|
||||||
|
* migrations of a version lower than the current version or higher or equal to
|
||||||
|
* the target version are _not_ executed while all others are.
|
||||||
|
*/
|
||||||
|
const migrations: Record<string, () => Promise<void>> = {
|
||||||
|
"0.1.0": migrate0_1_0,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isFirstWorldStart(version: string): boolean {
|
||||||
|
return version === "";
|
||||||
|
}
|
28
src/module/migrations/0-1-0.ts
Normal file
28
src/module/migrations/0-1-0.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
export async function migrate(): Promise<void> {
|
||||||
|
for (const a of game.actors.entities) {
|
||||||
|
const updateData = getActorUpdateData();
|
||||||
|
console.log(`Migrating actor ${a.name}`);
|
||||||
|
await a.update(updateData, { enforceTypes: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActorUpdateData(): Record<string, unknown> {
|
||||||
|
const updateData = {
|
||||||
|
data: {
|
||||||
|
combatValues: [
|
||||||
|
"hitPoints",
|
||||||
|
"defense",
|
||||||
|
"initiative",
|
||||||
|
"movement",
|
||||||
|
"meleeAttack",
|
||||||
|
"rangedAttack",
|
||||||
|
"spellcasting",
|
||||||
|
"targetedSpellcasting",
|
||||||
|
].reduce((acc, curr) => {
|
||||||
|
acc[curr] = { "-=base": null };
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return updateData;
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ class CheckFactory {
|
||||||
|
|
||||||
private checkOptions: DS4CheckFactoryOptions;
|
private checkOptions: DS4CheckFactoryOptions;
|
||||||
|
|
||||||
async execute(): Promise<ChatMessage | any> {
|
async execute(): Promise<ChatMessage | unknown> {
|
||||||
const rollCls: typeof Roll = CONFIG.Dice.rolls[0];
|
const rollCls: typeof Roll = CONFIG.Dice.rolls[0];
|
||||||
|
|
||||||
const formula = [
|
const formula = [
|
||||||
|
@ -82,7 +82,7 @@ class CheckFactory {
|
||||||
export async function createCheckRoll(
|
export async function createCheckRoll(
|
||||||
targetValue: number,
|
targetValue: number,
|
||||||
options: Partial<DS4CheckFactoryOptions> = {},
|
options: Partial<DS4CheckFactoryOptions> = {},
|
||||||
): Promise<ChatMessage | any> {
|
): Promise<ChatMessage | unknown> {
|
||||||
// Ask for additional required data;
|
// Ask for additional required data;
|
||||||
const gmModifierData = await askGmModifier(targetValue, options);
|
const gmModifierData = await askGmModifier(targetValue, options);
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ export async function createCheckRoll(
|
||||||
// Possibly additional processing
|
// Possibly additional processing
|
||||||
|
|
||||||
// Execute roll
|
// Execute roll
|
||||||
await cf.execute();
|
return cf.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
12
src/module/settings.ts
Normal file
12
src/module/settings.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export function registerSystemSettings(): void {
|
||||||
|
/**
|
||||||
|
* Track the system version upon which point a migration was last applied
|
||||||
|
*/
|
||||||
|
game.settings.register("ds4", "systemMigrationVersion", {
|
||||||
|
name: "System Migration Version",
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
});
|
||||||
|
}
|
|
@ -40,8 +40,9 @@
|
||||||
|
|
||||||
.combat-value-formula {
|
.combat-value-formula {
|
||||||
width: $size;
|
width: $size;
|
||||||
input {
|
text-align: center;
|
||||||
text-align: center;
|
span {
|
||||||
|
line-height: $default-input-height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,36 +45,28 @@
|
||||||
},
|
},
|
||||||
"combatValues": {
|
"combatValues": {
|
||||||
"hitPoints": {
|
"hitPoints": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0,
|
"mod": 0,
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
"defense": {
|
"defense": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0
|
"mod": 0
|
||||||
},
|
},
|
||||||
"initiative": {
|
"initiative": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0
|
"mod": 0
|
||||||
},
|
},
|
||||||
"movement": {
|
"movement": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0
|
"mod": 0
|
||||||
},
|
},
|
||||||
"meleeAttack": {
|
"meleeAttack": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0
|
"mod": 0
|
||||||
},
|
},
|
||||||
"rangedAttack": {
|
"rangedAttack": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0
|
"mod": 0
|
||||||
},
|
},
|
||||||
"spellcasting": {
|
"spellcasting": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0
|
"mod": 0
|
||||||
},
|
},
|
||||||
"targetedSpellcasting": {
|
"targetedSpellcasting": {
|
||||||
"base": 0,
|
|
||||||
"mod": 0
|
"mod": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
<div class="combat-value-with-formula">
|
<div class="combat-value-with-formula">
|
||||||
<div class="combat-value {{combat-value-key}}"><span class="combat-value-total">{{combat-value-data.total}}</span>
|
<div class="combat-value {{combat-value-key}}"><span class="combat-value-total">{{combat-value-data.total}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="combat-value-formula flexrow"><input type="number" name="data.combatValues.{{combat-value-key}}.base"
|
<div class="combat-value-formula flexrow"><span class="combat-value-base">{{combat-value-data.base}}</span><span>+</span><input
|
||||||
value='{{combat-value-data.base}}' data-dtype="Number" /><input type="number"
|
type="number" name="data.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}'
|
||||||
name="data.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}' data-dtype="Number" />
|
data-dtype="Number" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/inline}}
|
{{/inline}}
|
||||||
|
|
Loading…
Reference in a new issue