Skip to content

Commit

Permalink
[#342] Turn senses into full object, rework how config apps are called
Browse files Browse the repository at this point in the history
  • Loading branch information
arbron committed Oct 23, 2023
1 parent 8a76cd7 commit 0c9da9b
Show file tree
Hide file tree
Showing 14 changed files with 163 additions and 140 deletions.
13 changes: 6 additions & 7 deletions module/applications/actor/movement-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export default class ActorMovementConfig extends BaseConfigSheet {
template: "systems/dnd5e/templates/apps/movement-config.hbs",
width: 300,
height: "auto",
sheetConfig: false
sheetConfig: false,
keyPath: "system.attributes.movement"
});
}

Expand All @@ -28,11 +29,9 @@ export default class ActorMovementConfig extends BaseConfigSheet {
/** @inheritdoc */
getData(options={}) {
const source = this.document.toObject();
const isActor = this.document instanceof Actor;
const keyPath = isActor ? "system.attributes.movement" : "system.movement";

// Current movement values
const movement = foundry.utils.getProperty(source, keyPath) ?? {};
const movement = foundry.utils.getProperty(source, this.options.keyPath) ?? {};
for ( let [k, v] of Object.entries(movement) ) {
if ( ["units", "hover"].includes(k) ) continue;
movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : null;
Expand All @@ -55,10 +54,10 @@ export default class ActorMovementConfig extends BaseConfigSheet {
return {
speeds,
movement,
selectUnits: source.type !== "group",
canHover: isActor && (source.type !== "group"),
selectUnits: Object.hasOwn(movement, "units"),
canHover: Object.hasOwn(movement, "hover"),
units: CONFIG.DND5E.movementUnits,
keyPath
keyPath: this.options.keyPath
};
}
}
28 changes: 13 additions & 15 deletions module/applications/actor/senses-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export default class ActorSensesConfig extends BaseConfigSheet {
classes: ["dnd5e"],
template: "systems/dnd5e/templates/apps/senses-config.hbs",
width: 300,
height: "auto"
height: "auto",
keyPath: "system.attributes.senses"
});
}

Expand All @@ -26,19 +27,16 @@ export default class ActorSensesConfig extends BaseConfigSheet {

/** @inheritdoc */
getData(options) {
const source = this.document.toObject().system.attributes?.senses || {};
const data = {
senses: {},
special: source.special ?? "",
units: source.units, movementUnits: CONFIG.DND5E.movementUnits
};
for ( let [name, label] of Object.entries(CONFIG.DND5E.senses) ) {
const v = Number(source[name]);
data.senses[name] = {
label: game.i18n.localize(label),
value: Number.isNumeric(v) ? v.toNearest(0.1) : 0
};
}
return data;
const source = this.document.toObject();
const senses = foundry.utils.getProperty(source, this.options.keyPath) ?? {};
return foundry.utils.mergeObject(super.getData(options), {
senses: Object.entries(CONFIG.DND5E.senses).reduce((obj, [k, label]) => {
obj[k] = { label, value: senses[k] ?? 0 };
return obj;
}, {}),
special: senses.special ?? "",
units: senses.units, movementUnits: CONFIG.DND5E.movementUnits,
keyPath: this.options.keyPath
});
}
}
17 changes: 4 additions & 13 deletions module/applications/actor/type-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export default class ActorTypeConfig extends DocumentSheet {
allowCustom: true,
minimum: 0,
maximum: null,
sheetConfig: false
sheetConfig: false,
keyPath: "system.details.type"
});
}

Expand All @@ -36,20 +37,10 @@ export default class ActorTypeConfig extends DocumentSheet {

/* -------------------------------------------- */

/**
* Key path to where the type is stored on the document.
* @type {string}
*/
get keyPath() {
return this.document instanceof Actor ? "system.details.type" : "system.type";
}

/* -------------------------------------------- */

/** @inheritdoc */
getData(options={}) {
// Get current value or new default
let attr = foundry.utils.getProperty(this.object, this.keyPath);
let attr = foundry.utils.getProperty(this.object, this.options.keyPath);
if ( foundry.utils.getType(attr) !== "Object" ) attr = {
value: (attr in CONFIG.DND5E.creatureTypes) ? attr : "humanoid",
subtype: "",
Expand Down Expand Up @@ -91,7 +82,7 @@ export default class ActorTypeConfig extends DocumentSheet {
/** @override */
async _updateObject(event, formData) {
const typeObject = foundry.utils.expandObject(formData);
return this.object.update({[this.keyPath]: typeObject});
return this.object.update({[this.options.keyPath]: typeObject});
}

/* -------------------------------------------- */
Expand Down
8 changes: 6 additions & 2 deletions module/applications/item/item-sheet.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ActorMovementConfig from "../actor/movement-config.mjs";
import ActorSensesConfig from "../actor/senses-config.mjs";
import ActorTypeConfig from "../actor/type-config.mjs";
import AdvancementManager from "../advancement/advancement-manager.mjs";
import AdvancementMigrationDialog from "../advancement/advancement-migration-dialog.mjs";
Expand Down Expand Up @@ -503,10 +504,13 @@ export default class ItemSheet5e extends ItemSheet {
let app;
switch ( button.dataset.action ) {
case "movement":
app = new ActorMovementConfig(this.item);
app = new ActorMovementConfig(this.item, { keyPath: "system.movement" });
break;
case "senses":
app = new ActorSensesConfig(this.item, { keyPath: "system.senses" });
break;
case "type":
app = new ActorTypeConfig(this.item);
app = new ActorTypeConfig(this.item, { keyPath: "system.type" });
break;
}
app?.render(true);
Expand Down
8 changes: 2 additions & 6 deletions module/data/actor/npc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import AttributesFields from "./templates/attributes.mjs";
import CreatureTemplate from "./templates/creature.mjs";
import DetailsFields from "./templates/details.mjs";
import TraitsFields from "./templates/traits.mjs";
import CreatureTypeField from "../shared/creature-type-field.mjs";

/**
* System data definition for NPCs.
Expand Down Expand Up @@ -72,12 +73,7 @@ export default class NPCData extends CreatureTemplate {
details: new foundry.data.fields.SchemaField({
...DetailsFields.common,
...DetailsFields.creature,
type: new foundry.data.fields.SchemaField({
value: new foundry.data.fields.StringField({required: true, blank: true, label: "DND5E.CreatureType"}),
subtype: new foundry.data.fields.StringField({required: true, label: "DND5E.CreatureTypeSelectorSubtype"}),
swarm: new foundry.data.fields.StringField({required: true, blank: true, label: "DND5E.CreatureSwarmSize"}),
custom: new foundry.data.fields.StringField({required: true, label: "DND5E.CreatureTypeSelectorCustom"})
}, {label: "DND5E.CreatureType"}),
type: new CreatureTypeField(),
environment: new foundry.data.fields.StringField({required: true, label: "DND5E.Environment"}),
cr: new foundry.data.fields.NumberField({
required: true, nullable: false, min: 0, initial: 1, label: "DND5E.ChallengeRating"
Expand Down
39 changes: 4 additions & 35 deletions module/data/actor/templates/attributes.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { FormulaField } from "../../fields.mjs";
import MovementField from "../../shared/movement-field.mjs";
import SensesField from "../../shared/senses-field.mjs";

/**
* Shared contents of the attributes schema between various actor types.
Expand Down Expand Up @@ -26,25 +28,7 @@ export default class AttributesFields {
ability: new foundry.data.fields.StringField({label: "DND5E.AbilityModifier"}),
bonus: new FormulaField({label: "DND5E.InitiativeBonus"})
}, { label: "DND5E.Initiative" }),
movement: new foundry.data.fields.SchemaField({
burrow: new foundry.data.fields.NumberField({
nullable: false, min: 0, step: 0.1, initial: 0, label: "DND5E.MovementBurrow"
}),
climb: new foundry.data.fields.NumberField({
nullable: false, min: 0, step: 0.1, initial: 0, label: "DND5E.MovementClimb"
}),
fly: new foundry.data.fields.NumberField({
nullable: false, min: 0, step: 0.1, initial: 0, label: "DND5E.MovementFly"
}),
swim: new foundry.data.fields.NumberField({
nullable: false, min: 0, step: 0.1, initial: 0, label: "DND5E.MovementSwim"
}),
walk: new foundry.data.fields.NumberField({
nullable: false, min: 0, step: 0.1, initial: 30, label: "DND5E.MovementWalk"
}),
units: new foundry.data.fields.StringField({initial: "ft", label: "DND5E.MovementUnits"}),
hover: new foundry.data.fields.BooleanField({label: "DND5E.MovementHover"})
}, {label: "DND5E.Movement"})
movement: new MovementField()
};
}

Expand Down Expand Up @@ -72,22 +56,7 @@ export default class AttributesFields {
required: true, nullable: false, integer: true, min: 0, initial: 3, label: "DND5E.AttunementMax"
})
}, {label: "DND5E.Attunement"}),
senses: new foundry.data.fields.SchemaField({
darkvision: new foundry.data.fields.NumberField({
required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.SenseDarkvision"
}),
blindsight: new foundry.data.fields.NumberField({
required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.SenseBlindsight"
}),
tremorsense: new foundry.data.fields.NumberField({
required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.SenseTremorsense"
}),
truesight: new foundry.data.fields.NumberField({
required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.SenseTruesight"
}),
units: new foundry.data.fields.StringField({required: true, initial: "ft", label: "DND5E.SenseUnits"}),
special: new foundry.data.fields.StringField({required: true, label: "DND5E.SenseSpecial"})
}, {label: "DND5E.Senses"}),
senses: new SensesField(),
spellcasting: new foundry.data.fields.StringField({
required: true, blank: true, initial: "int", label: "DND5E.SpellAbility"
})
Expand Down
84 changes: 50 additions & 34 deletions module/data/item/race.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Actor5e from "../../documents/actor/actor.mjs";
import SystemDataModel from "../abstract.mjs";
import { AdvancementField, IdentifierField } from "../fields.mjs";
import { CreatureTypeField, MovementField, SensesField } from "../shared/_module.mjs";
import ItemDescriptionTemplate from "./templates/item-description.mjs";

/**
Expand All @@ -8,47 +10,19 @@ import ItemDescriptionTemplate from "./templates/item-description.mjs";
*
* @property {string} identifier Identifier slug for this race.
* @property {object[]} advancement Advancement objects for this race.
* @property {number} darkvision Darkvision distance.
* @property {object} movement
* @property {number} movement.burrow
* @property {number} movement.climb
* @property {number} movement.fly
* @property {number} movement.swim
* @property {number} movement.walk
* @property {string} movement.units Units used to measure movement.
* @property {object} type
* @property {string} type.value Creature type (e.g. "humanoid", "construct").
* @property {string} type.subtype Additional type data (e.g. "elf" for Eladrin)
* @property {MovementField} movement
* @property {SensesField} senses
* @property {CreatureType} type
*/
export default class RaceData extends SystemDataModel.mixin(ItemDescriptionTemplate) {
/** @inheritdoc */
static defineSchema() {
return this.mergeSchema(super.defineSchema(), {
identifier: new IdentifierField({label: "DND5E.Identifier"}),
advancement: new foundry.data.fields.ArrayField(new AdvancementField(), {label: "DND5E.AdvancementTitle"}),
darkvision: new foundry.data.fields.NumberField({min: 0, integer: true, label: "DND5E.SenseDarkvision"}),
movement: new foundry.data.fields.SchemaField({
burrow: new foundry.data.fields.NumberField({
initial: null, min: 0, integer: true, label: "DND5E.MovementBurrow"
}),
climb: new foundry.data.fields.NumberField({
initial: null, min: 0, integer: true, label: "DND5E.MovementClimb"
}),
fly: new foundry.data.fields.NumberField({
initial: null, min: 0, integer: true, label: "DND5E.MovementFly"
}),
swim: new foundry.data.fields.NumberField({
initial: null, min: 0, integer: true, label: "DND5E.MovementSwim"
}),
walk: new foundry.data.fields.NumberField({
initial: 30, min: 0, integer: true, label: "DND5E.MovementWalk"
}),
units: new foundry.data.fields.StringField({initial: "ft", label: "DND5E.MovementUnits"})
}),
type: new foundry.data.fields.SchemaField({
value: new foundry.data.fields.StringField({initial: "humanoid", label: "DND5E.CreatureType"}),
subtype: new foundry.data.fields.StringField({label: "DND5E.CreatureTypeSelectorSubtype"})
})
movement: new MovementField(),
senses: new SensesField(),
type: new CreatureTypeField({ swarm: false })
});
}

Expand All @@ -70,4 +44,46 @@ export default class RaceData extends SystemDataModel.mixin(ItemDescriptionTempl
labels.movement[key] = `${label} ${value} ${units}`;
}
}

/* -------------------------------------------- */
/* Properties */
/* -------------------------------------------- */

/**
* Sheet labels for a race's movement.
* @returns {Object<string>}
*/
get movementLabels() {
const units = CONFIG.DND5E.movementUnits[this.movement.units];
return Object.entries(CONFIG.DND5E.movementTypes).reduce((obj, [k, label]) => {
const value = this.movement[k];
if ( value ) obj[k] = `${label} ${value} ${units}`;
return obj;
}, {});
}

/* -------------------------------------------- */

/**
* Sheet labels for a race's senses.
* @returns {Object<string>}
*/
get sensesLabels() {
const units = CONFIG.DND5E.movementUnits[this.senses.units];
return Object.entries(CONFIG.DND5E.senses).reduce((arr, [k, label]) => {
const value = this.senses[k];
if ( value ) arr.push(`${label} ${value} ${units}`);
return arr;
}, []).concat(this.senses.special.split(";").filter(l => l));
}

/* -------------------------------------------- */

/**
* Sheet label for a race's creature type.
* @returns {Object<string>}
*/
get typeLabel() {
return Actor5e.formatCreatureType(this.type);
}
}
3 changes: 3 additions & 0 deletions module/data/shared/_module.mjs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export {default as CreatureTypeField} from "./creature-type-field.mjs";
export {default as CurrencyTemplate} from "./currency.mjs";
export {default as MovementField} from "./movement-field.mjs";
export {default as SensesField} from "./senses-field.mjs";
16 changes: 16 additions & 0 deletions module/data/shared/creature-type-field.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Field for storing creature type data.
*/
export default class CreatureTypeField extends foundry.data.fields.SchemaField {
constructor(fields={}, options={}) {
fields = {
value: new foundry.data.fields.StringField({blank: true, label: "DND5E.CreatureType"}),
subtype: new foundry.data.fields.StringField({label: "DND5E.CreatureTypeSelectorSubtype"}),
swarm: new foundry.data.fields.StringField({blank: true, label: "DND5E.CreatureSwarmSize"}),
custom: new foundry.data.fields.StringField({label: "DND5E.CreatureTypeSelectorCustom"}),
...fields
};
Object.entries(fields).forEach(([k, v]) => !v ? delete fields[k] : null);
super(fields, { label: "DND5E.CreatureType", ...options });
}
}
20 changes: 20 additions & 0 deletions module/data/shared/movement-field.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Field for storing movement data.
*/
export default class MovementField extends foundry.data.fields.SchemaField {
constructor(fields={}, options={}) {
const numberConfig = { nullable: false, min: 0, step: 0.1, initial: 0 };
fields = {
burrow: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.MovementBurrow" }),
climb: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.MovementClimb" }),
fly: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.MovementFly" }),
swim: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.MovementSwim" }),
walk: new foundry.data.fields.NumberField({ ...numberConfig, initial: 30, label: "DND5E.MovementWalk" }),
units: new foundry.data.fields.StringField({initial: "ft", label: "DND5E.MovementUnits"}),
hover: new foundry.data.fields.BooleanField({label: "DND5E.MovementHover"}),
...fields
};
Object.entries(fields).forEach(([k, v]) => !v ? delete fields[k] : null);
super(fields, { label: "DND5E.Movement", ...options });
}
}
19 changes: 19 additions & 0 deletions module/data/shared/senses-field.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Field for storing senses data.
*/
export default class SensesField extends foundry.data.fields.SchemaField {
constructor(fields={}, options={}) {
const numberConfig = { required: true, nullable: false, integer: true, min: 0, initial: 0 };
fields = {
darkvision: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.SenseDarkvision" }),
blindsight: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.SenseBlindsight" }),
tremorsense: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.SenseTremorsense" }),
truesight: new foundry.data.fields.NumberField({ ...numberConfig, label: "DND5E.SenseTruesight" }),
units: new foundry.data.fields.StringField({required: true, initial: "ft", label: "DND5E.SenseUnits"}),
special: new foundry.data.fields.StringField({required: true, label: "DND5E.SenseSpecial"}),
...fields
};
Object.entries(fields).forEach(([k, v]) => !v ? delete fields[k] : null);
super(fields, { label: "DND5E.Senses", ...options });
}
}
Loading

0 comments on commit 0c9da9b

Please sign in to comment.