diff --git a/lang/en.json b/lang/en.json index e8f56d1be9..7e1a04f93c 100644 --- a/lang/en.json +++ b/lang/en.json @@ -2021,6 +2021,10 @@ "DND5E.FEATURE": { "FIELDS": { + "cover": { + "label": "Cover", + "hint": "Cover provided to crew by this vehicle feature." + }, "properties": { "label": "Feature Properties" }, @@ -2069,6 +2073,9 @@ "Blessing": "Blessing", "Charm": "Charm", "EpicBoon": "Epic Boon" + }, + "Vehicle": { + "Label": "Vehicle Feature" } }, "DND5E.Focus": { @@ -2302,7 +2309,6 @@ "DND5E.HitPointsTempMaxHint": "Temporary change to the maximum HP.", "DND5E.HitPointsTempMaxShort": "Temp Max HP", "DND5E.HP": "HP", -"DND5E.HealthConditions": "Health Conditions", "DND5E.HPFormula": "Health Formula", "DND5E.HPFormulaError": "The provided hit point formula could not be evaluated.", "DND5E.HPFormulaRollMessage": "Roll Hit Point Formula", @@ -3060,7 +3066,6 @@ "Name": "Species Name" }, "DND5E.Speed": "Speed", -"DND5E.SpeedConditions": "Speed Conditions", "DND5E.SpeedSpecial": "Special Movement", "DND5E.SpellAbility": "Spellcasting Ability", "DND5E.SpellAbilitySet": "Set as Primary Spellcasting Ability", @@ -3945,6 +3950,40 @@ }, "DND5E.Value": "Value", + +"DND5E.VEHICLE": { + "MOUNTABLE": { + "FIELDS": { + "crewed": { + "label": "Crewed" + }, + "hp": { + "label": "Hit Points", + "conditions": { + "label": "Health Conditions" + }, + "dt": { + "label": "Damage Threshold" + }, + "max": { + "label": "Max HP" + }, + "value": { + "label": "Current HP" + } + }, + "speed": { + "conditions": { + "label": "Speed Conditions" + }, + "value": { + "label": "Speed" + } + } + } + } +}, + "DND5E.Vehicle": "Vehicle", "DND5E.VehicleActions": "Actions", "DND5E.VehicleActionsHint": "Actions taken with full crew complement", @@ -3990,6 +4029,11 @@ "label": "Ammunition Type" } }, + "armor": { + "value": { + "label": "Armor Class" + } + }, "damage": { "hint": "Intrinsic damage dice from the weapon. Ability modifier and additional damage parts will be provided automatically when attacking." }, diff --git a/module/applications/actor/vehicle-sheet.mjs b/module/applications/actor/vehicle-sheet.mjs index 9a7d06b154..5ee39f0e0b 100644 --- a/module/applications/actor/vehicle-sheet.mjs +++ b/module/applications/actor/vehicle-sheet.mjs @@ -52,16 +52,16 @@ export default class ActorSheet5eVehicle extends ActorSheet5e { context.toggleTitle = game.i18n.localize(`DND5E.${isCrewed ? "Crewed" : "Uncrewed"}`); // Handle crew actions - if ( item.type === "feat" && item.system.activation.type === "crew" ) { - context.cover = game.i18n.localize(`DND5E.${item.system.cover ? "CoverTotal" : "None"}`); - if ( item.system.cover === .5 ) context.cover = "½"; + if ( (item.type === "feat") && (item.system.activation.type === "crew") ) { + if ( item.system.cover === 1 ) context.cover = game.i18n.localize("DND5E.CoverTotal"); + else if ( item.system.cover === .5 ) context.cover = "½"; else if ( item.system.cover === .75 ) context.cover = "¾"; - else if ( item.system.cover === null ) context.cover = "—"; + else context.cover = "—"; } // Prepare vehicle weapons if ( (item.type === "equipment") || (item.type === "weapon") ) { - context.threshold = item.system.hp.dt ? item.system.hp.dt : "—"; + context.threshold = item.system.hp?.dt ? item.system.hp.dt : "—"; } } diff --git a/module/config.mjs b/module/config.mjs index e2464c80ad..383a9d90d4 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -1884,6 +1884,9 @@ DND5E.featureTypes = { charm: "DND5E.Feature.SupernaturalGift.Charm", epicBoon: "DND5E.Feature.SupernaturalGift.EpicBoon" } + }, + vehicle: { + label: "DND5E.Feature.Vehicle.Label" } }; preLocalize("featureTypes", { key: "label" }); diff --git a/module/data/item/equipment.mjs b/module/data/item/equipment.mjs index 542e7ccbc6..87371deb69 100644 --- a/module/data/item/equipment.mjs +++ b/module/data/item/equipment.mjs @@ -37,7 +37,7 @@ export default class EquipmentData extends ItemDataModel.mixin( /* -------------------------------------------- */ /** @override */ - static LOCALIZATION_PREFIXES = ["DND5E.SOURCE"]; + static LOCALIZATION_PREFIXES = ["DND5E.MOUNTABLE", "DND5E.SOURCE"]; /* -------------------------------------------- */ diff --git a/module/data/item/feat.mjs b/module/data/item/feat.mjs index 6e01813cbc..c4bf839144 100644 --- a/module/data/item/feat.mjs +++ b/module/data/item/feat.mjs @@ -14,7 +14,9 @@ const { ArrayField, BooleanField, NumberField, SchemaField, SetField, StringFiel * @mixes ItemDescriptionTemplate * @mixes ItemTypeTemplate * - * @property {Advancement[]} Advancement objects for this feature. + * @property {Advancement[]} advancement Advancement objects for this feature. + * @property {number} cover Amount of cover this feature affords to its crew on a vehicle. + * @property {boolean} crewed Is this vehicle feature currently crewed? * @property {object} enchant * @property {string} enchant.max Maximum number of items that can have this enchantment. * @property {string} enchant.period Frequency at which the enchantment can be swapped. @@ -41,6 +43,8 @@ export default class FeatData extends ItemDataModel.mixin( static defineSchema() { return this.mergeSchema(super.defineSchema(), { advancement: new ArrayField(new AdvancementField(), { label: "DND5E.AdvancementTitle" }), + cover: new NumberField({ min: 0, max: 1 }), + crewed: new BooleanField(), enchant: new SchemaField({ max: new FormulaField({ deterministic: true }), period: new StringField() @@ -164,6 +168,7 @@ export default class FeatData extends ItemDataModel.mixin( placeholder: "DND5E.Requirements" } ]; context.parts = ["dnd5e.details-feat", "dnd5e.field-uses"]; + context.coverOptions = Object.entries(CONFIG.DND5E.cover).map(([value, label]) => ({ value, label })); } /* -------------------------------------------- */ diff --git a/module/data/item/templates/mountable.mjs b/module/data/item/templates/mountable.mjs index 9836718acf..30ca1f0428 100644 --- a/module/data/item/templates/mountable.mjs +++ b/module/data/item/templates/mountable.mjs @@ -5,40 +5,33 @@ const { BooleanField, NumberField, SchemaField, StringField } = foundry.data.fie /** * Data model template for equipment that can be mounted on a vehicle. * - * @property {object} armor Equipment's armor class. - * @property {number} armor.value Armor class value for equipment. - * @property {number} cover Amount of cover does this item affords to its crew on a vehicle. - * @property {boolean} crewed Is this equipment currently crewed? - * @property {object} hp Equipment's hit points. - * @property {number} hp.value Current hit point value. - * @property {number} hp.max Max hit points. - * @property {number} hp.dt Damage threshold. - * @property {string} hp.conditions Conditions that are triggered when this equipment takes damage. - * @property {object} speed Speed granted by a piece of vehicle equipment. + * @property {boolean} crewed Is this equipment currently crewed? + * @property {object} hp + * @property {number} hp.value Current hit point value. + * @property {number} hp.max Max hit points. + * @property {number} hp.dt Damage threshold. + * @property {string} hp.conditions Conditions that are triggered when this equipment takes damage. + * @property {object} speed + * @property {string} speed.conditions Conditions that may affect item's speed. * @property {number} speed.value Speed granted by this piece of equipment measured in feet or meters * depending on system setting. - * @property {string} speed.conditions Conditions that may affect item's speed. * @mixin */ export default class MountableTemplate extends SystemDataModel { /** @inheritDoc */ static defineSchema() { return { - armor: new SchemaField({ - value: new NumberField({ required: true, integer: true, min: 0, label: "DND5E.ArmorClass" }) - }, {label: "DND5E.ArmorClass"}), - cover: new NumberField({ min: 0, max: 1, label: "DND5E.Cover" }), - crewed: new BooleanField({ label: "DND5E.Crewed" }), + crewed: new BooleanField(), hp: new SchemaField({ - value: new NumberField({ required: true, integer: true, min: 0, label: "DND5E.HitPointsCurrent" }), - max: new NumberField({ required: true, integer: true, min: 0, label: "DND5E.HitPointsMax" }), - dt: new NumberField({ required: true, integer: true, min: 0, label: "DND5E.DamageThreshold" }), - conditions: new StringField({required: true, label: "DND5E.HealthConditions"}) - }, {label: "DND5E.HitPoints"}), + conditions: new StringField(), + dt: new NumberField({ integer: true, min: 0 }), + max: new NumberField({ integer: true, min: 0 }), + value: new NumberField({ integer: true, min: 0 }) + }, { required: false, initial: undefined }), speed: new SchemaField({ - value: new NumberField({required: true, min: 0, label: "DND5E.Speed"}), - conditions: new StringField({required: true, label: "DND5E.SpeedConditions"}) - }, {required: false, initial: undefined, label: "DND5E.Speed"}) + conditions: new StringField(), + value: new NumberField({ min: 0 }) + }, { required: false, initial: undefined }) }; } } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 044213cf57..ea9c55f5e2 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -25,6 +25,8 @@ const { NumberField, SchemaField, SetField, StringField } = foundry.data.fields; * * @property {object} ammunition * @property {string} ammunition.type Type of ammunition fired by this weapon. + * @property {object} armor + * @property {number} armor.value Siege or vehicle weapon's armor class. * @property {object} damage * @property {DamageData} damage.base Weapon's base damage. * @property {DamageData} damage.versatile Weapon's versatile damage. @@ -48,7 +50,7 @@ export default class WeaponData extends ItemDataModel.mixin( /* -------------------------------------------- */ /** @override */ - static LOCALIZATION_PREFIXES = ["DND5E.WEAPON", "DND5E.RANGE", "DND5E.SOURCE"]; + static LOCALIZATION_PREFIXES = ["DND5E.WEAPON", "DND5E.MOUNTABLE", "DND5E.RANGE", "DND5E.SOURCE"]; /* -------------------------------------------- */ @@ -59,6 +61,9 @@ export default class WeaponData extends ItemDataModel.mixin( ammunition: new SchemaField({ type: new StringField() }), + armor: new SchemaField({ + value: new NumberField({ integer: true, min: 0 }) + }), damage: new SchemaField({ base: new DamageField(), versatile: new DamageField() diff --git a/templates/items/details/details-feat.hbs b/templates/items/details/details-feat.hbs index 234d9e6c29..de85d18a38 100644 --- a/templates/items/details/details-feat.hbs +++ b/templates/items/details/details-feat.hbs @@ -11,6 +11,10 @@ label=(localize "DND5E.ItemFeatureSubtype" category=(lookup (lookup config.featureTypes source.type.value) "label")) }} {{/if}} + {{#if (eq system.type.value "vehicle")}} + {{ formField fields.cover value=source.cover options=coverOptions }} + {{/if}} + {{!-- Feature Prerequisites --}} {{ formField fields.prerequisites.fields.level value=source.prerequisites.level }} {{ formField fields.prerequisites.fields.repeatable value=source.prerequisites.repeatable diff --git a/templates/items/details/details-mountable.hbs b/templates/items/details/details-mountable.hbs index 5f6d5ccaab..c17cdbb74b 100644 --- a/templates/items/details/details-mountable.hbs +++ b/templates/items/details/details-mountable.hbs @@ -27,7 +27,8 @@ {{!-- Conditions --}} {{ formInput fields.hp.fields.conditions value=source.hp.conditions input=inputs.createTextInput - placeholder=(localize "DND5E.HealthConditions") localize=true classes="full-width" }} + placeholder=(localize "DND5E.VEHICLE.MOUNTABLE.FIELDS.hp.conditions.label") localize=true + classes="full-width" }} {{!-- Speed --}} @@ -43,7 +44,8 @@ {{!-- Conditions --}} {{ formInput fields.speed.fields.conditions value=source.speed.conditions input=inputs.createTextInput - placeholder=(localize "DND5E.SpeedConditions") localize=true classes="full-width" }} + placeholder=(localize "DND5E.VEHICLE.MOUNTABLE.FIELDS.speed.conditions.label") localize=true + classes="full-width" }} {{/if}} diff --git a/templates/items/parts/item-mountable.hbs b/templates/items/parts/item-mountable.hbs index 4dcb6a6ec9..c8c15a22d0 100644 --- a/templates/items/parts/item-mountable.hbs +++ b/templates/items/parts/item-mountable.hbs @@ -9,7 +9,7 @@