Fix as per CSV sheet tracking + creature explanation

This commit is contained in:
2026-05-17 17:43:33 +02:00
parent a572c66678
commit 374854cc8b
99 changed files with 2716 additions and 464 deletions
+2
View File
@@ -1,6 +1,7 @@
export { default as MGNECharacter } from "./character.mjs"
export { default as MGNECreature } from "./creature.mjs"
export { default as MGNECompanion } from "./companion.mjs"
export { default as MGNEParty } from "./party.mjs"
export { default as MGNEWeapon } from "./weapon.mjs"
export { default as MGNEArmor } from "./armor.mjs"
export { default as MGNEShield } from "./shield.mjs"
@@ -8,3 +9,4 @@ export { default as MGNEEquipment } from "./equipment.mjs"
export { default as MGNEResonanceCore } from "./resonance-core.mjs"
export { default as MGNEArtifact } from "./artifact.mjs"
export { default as MGNEFeature } from "./feature.mjs"
export { default as MGNECreatureTrait } from "./creature-trait.mjs"
+8
View File
@@ -12,8 +12,16 @@ export default class MGNEArmor extends foundry.abstract.TypeDataModel {
choices: SYSTEM.armorDieChoices,
}),
penalty: numberField(0, 0, 6),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "heavy",
choices: SYSTEM.weightCategories,
}),
equipped: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
}
}
+8 -6
View File
@@ -5,12 +5,6 @@ export default class MGNEArtifact extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: htmlField(""),
artifactId: new foundry.data.fields.StringField({
required: true,
nullable: false,
initial: "shiver-lens",
choices: SYSTEM.artifactChoices,
}),
synchronized: booleanField(false),
synchronizedTo: stringField(""),
usageDie: new foundry.data.fields.StringField({
@@ -20,6 +14,14 @@ export default class MGNEArtifact extends foundry.abstract.TypeDataModel {
choices: SYSTEM.usageDieChoices,
}),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
}
}
+28
View File
@@ -45,6 +45,34 @@ export default class MGNECharacter extends foundry.abstract.TypeDataModel {
this.syncLimit = Math.max(0, this.abilities.toughness?.value ?? 0)
this.syncRemaining = Math.max(0, this.syncLimit - (this.artifactSync.used ?? 0))
this.armorFormula = this.parent?.getArmorRollFormula?.() ?? "0"
// Compute current load per RAW:
// trivial = 0, light = 10 per slot, normal = 1, heavy = fills remaining capacity (max 1)
let normalLoad = 0
let lightCount = 0
let heavyCount = 0
for (const item of (this.parent?.items ?? [])) {
if (item.system?.carried === false) continue // not being carried
const w = item.system?.weight ?? "normal"
if (w === "trivial") continue
else if (w === "light") lightCount++
else if (w === "normal") normalLoad++
else if (w === "heavy") heavyCount++
}
normalLoad += Math.floor(lightCount / 10)
this.lightItemCount = lightCount
this.heavyItemCount = heavyCount
if (heavyCount >= 2) {
// Can't carry two heavy items — automatically overloaded
this.currentLoad = this.carryCapacity + (heavyCount - 1)
} else if (heavyCount === 1) {
// Heavy fills remaining capacity; other items fit alongside it
this.currentLoad = Math.max(normalLoad, this.carryCapacity)
} else {
this.currentLoad = normalLoad
}
this.overloaded = this.currentLoad > this.carryCapacity
}
/** @override */
+1 -2
View File
@@ -1,12 +1,11 @@
import { SYSTEM } from "../config/system.mjs"
import { abilitySchema, htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
import { htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
export default class MGNECompanion extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
abilities: abilitySchema(),
hp: trackSchema(1, 1),
morale: numberField(7, 2, 12),
armor: new fields.SchemaField({
+13
View File
@@ -0,0 +1,13 @@
import { htmlField, stringField } from "./shared.mjs"
export default class MGNECreatureTrait extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: htmlField(""),
trigger: stringField(""),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.CreatureTrait"]
}
+20 -8
View File
@@ -1,27 +1,39 @@
import { SYSTEM } from "../config/system.mjs"
import { abilitySchema, htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
import { htmlField, numberField, stringField, trackSchema } from "./shared.mjs"
const CREATURE_TYPES = ["human", "construct", "animal"]
export default class MGNECreature extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
abilities: abilitySchema(),
hp: trackSchema(1, 1),
morale: numberField(7, 2, 12),
armor: new fields.SchemaField({
die: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.armorDieChoices }),
}),
attack: new fields.SchemaField({
label: stringField("Attack"),
damage: stringField("1d4"),
}),
creatureType: new fields.SetField(
new fields.StringField({ required: true, choices: CREATURE_TYPES }),
{ required: true, nullable: false, initial: [] }
),
number: stringField("1"),
actionTableUuid: stringField(""),
description: htmlField(""),
special: htmlField(""),
notes: htmlField(""),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.Creature"]
/** @override */
static migrateData(source) {
// Remove old attack field if present (no longer part of the schema)
if ("attack" in source) delete source.attack
// Form submissions send null for unchecked checkboxes in array fields — filter them out
if (Array.isArray(source.creatureType)) {
source.creatureType = source.creatureType.filter(v => v != null && v !== "")
}
return super.migrateData(source)
}
}
+9
View File
@@ -21,6 +21,15 @@ export default class MGNEEquipment extends foundry.abstract.TypeDataModel {
choices: SYSTEM.usageDieChoices,
}),
consumable: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
}
}
+20
View File
@@ -0,0 +1,20 @@
import { htmlField } from "./shared.mjs"
export default class MGNEParty extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
memberRefs: new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({ required: true, nullable: false, blank: false }),
})
),
credits: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
notes: htmlField(""),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.Party"]
}
+9
View File
@@ -18,6 +18,15 @@ export default class MGNEResonanceCore extends foundry.abstract.TypeDataModel {
choices: SYSTEM.usageDieChoices,
}),
burnedOut: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "trivial",
choices: SYSTEM.weightCategories,
}),
}
}
+8
View File
@@ -12,8 +12,16 @@ export default class MGNEShield extends foundry.abstract.TypeDataModel {
choices: SYSTEM.armorDieChoices,
}),
penalty: numberField(0, 0, 4),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
equipped: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
}
}
+28 -1
View File
@@ -13,7 +13,14 @@ export default class MGNEWeapon extends foundry.abstract.TypeDataModel {
}),
damage: stringField("1d4"),
range: stringField("Touch"),
properties: stringField(""),
properties: new foundry.data.fields.SetField(
new foundry.data.fields.StringField({ required: true, nullable: false, blank: false }),
{ required: true, nullable: false, initial: [] }
),
weight: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "normal",
choices: SYSTEM.weightCategories,
}),
usageDie: new foundry.data.fields.StringField({
required: true,
nullable: false,
@@ -23,9 +30,29 @@ export default class MGNEWeapon extends foundry.abstract.TypeDataModel {
quantity: numberField(1, 0),
equipped: booleanField(false),
broken: booleanField(false),
durabilityDie: new foundry.data.fields.StringField({
required: true, nullable: false, initial: "d6",
choices: SYSTEM.usageDieChoices,
}),
}
}
/** @override */
static LOCALIZATION_PREFIXES = ["MGNE.Weapon"]
/**
* Migrate old string-based properties field to the new SetField format.
* @override
*/
static migrateData(source) {
// Old data stored properties as a plain string; convert to empty array
if (typeof source.properties === "string") {
source.properties = []
}
// Remove any null/undefined/blank entries that may have crept in
if (Array.isArray(source.properties)) {
source.properties = source.properties.filter(p => p != null && p !== "")
}
return super.migrateData(source)
}
}