Implement movement capability system with boolean toggles
Movement types (Climb, Cling, Fly, Phase, Swim) now use boolean toggles instead of separate speed values, matching RAW where all special movement uses base speed. Changes: - Update NPC and Character data models with movement schema - Add movement section to NPC stats template (grid layout) - Add movement section to character biography template - Add localization strings with tooltip hints for each type - Style movement grid to match senses section pattern - Add rollable # Appearing label for NPC sheets - Fix NPC sheet scrollbar visibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
1e67466d95
commit
694b11f423
13
lang/en.json
13
lang/en.json
@ -181,6 +181,7 @@
|
||||
"VAGABOND.Zone": "Zone",
|
||||
"VAGABOND.Morale": "Morale",
|
||||
"VAGABOND.Appearing": "# Appearing",
|
||||
"VAGABOND.RollAppearing": "Roll # Appearing",
|
||||
"VAGABOND.Immune": "Immune",
|
||||
"VAGABOND.Weak": "Weak",
|
||||
"VAGABOND.Actions": "Actions",
|
||||
@ -337,6 +338,18 @@
|
||||
"VAGABOND.Echolocation": "Echolocation",
|
||||
"VAGABOND.Seismicsense": "Seismicsense",
|
||||
"VAGABOND.Telepathy": "Telepathy",
|
||||
|
||||
"VAGABOND.Movement": "Movement",
|
||||
"VAGABOND.Climb": "Climb",
|
||||
"VAGABOND.Cling": "Cling",
|
||||
"VAGABOND.Fly": "Fly",
|
||||
"VAGABOND.Phase": "Phase",
|
||||
"VAGABOND.Swim": "Swim",
|
||||
"VAGABOND.MovementClimbHint": "Moves at full Speed while climbing. Beings without are Vulnerable while climbing.",
|
||||
"VAGABOND.MovementClingHint": "As Climb, but can also Move on ceilings.",
|
||||
"VAGABOND.MovementFlyHint": "Can Move through the air at full Speed.",
|
||||
"VAGABOND.MovementPhaseHint": "Can Move in occupied space, but takes 5 damage if ending Turn in occupied space.",
|
||||
"VAGABOND.MovementSwimHint": "Moves at full Speed while swimming. Beings without are Vulnerable while in liquid.",
|
||||
"VAGABOND.BiographyPlaceholder": "Enter character background...",
|
||||
"VAGABOND.Notes": "Notes",
|
||||
"VAGABOND.NotesPlaceholder": "Enter notes...",
|
||||
|
||||
@ -313,22 +313,24 @@ export default class CharacterData extends VagabondActorBase {
|
||||
overburdened: new fields.BooleanField({ initial: false }),
|
||||
}),
|
||||
|
||||
// Movement speeds (multiple types like NPCs)
|
||||
// Movement speed - base walking speed plus bonus
|
||||
speed: new fields.SchemaField({
|
||||
// Walking speed (base from DEX)
|
||||
// Walking speed (base from DEX, calculated in prepareDerivedData)
|
||||
walk: new fields.NumberField({ integer: true, initial: 30, min: 0 }),
|
||||
// Flying speed (from spells, ancestry, features)
|
||||
fly: new fields.NumberField({ integer: true, initial: 0, min: 0 }),
|
||||
// Swimming speed (Hunter Rover, some ancestries)
|
||||
swim: new fields.NumberField({ integer: true, initial: 0, min: 0 }),
|
||||
// Climbing speed (Hunter Rover, some ancestries)
|
||||
climb: new fields.NumberField({ integer: true, initial: 0, min: 0 }),
|
||||
// Burrowing speed (rare, some beasts)
|
||||
burrow: new fields.NumberField({ integer: true, initial: 0, min: 0 }),
|
||||
// Bonus to walking speed from effects
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
}),
|
||||
|
||||
// Movement capabilities - boolean toggles for special movement types
|
||||
// All use base speed value when enabled per RAW
|
||||
movement: new fields.SchemaField({
|
||||
climb: new fields.BooleanField({ initial: false }), // Full Speed while climbing
|
||||
cling: new fields.BooleanField({ initial: false }), // As Climb, but can Move on ceilings
|
||||
fly: new fields.BooleanField({ initial: false }), // Move through the air at full Speed
|
||||
phase: new fields.BooleanField({ initial: false }), // Move in occupied space (5 dmg if ends turn there)
|
||||
swim: new fields.BooleanField({ initial: false }), // Full Speed while swimming
|
||||
}),
|
||||
|
||||
// Saves - difficulties will be calculated
|
||||
saves: new fields.SchemaField({
|
||||
reflex: new fields.SchemaField({
|
||||
|
||||
@ -123,12 +123,20 @@ export default class NPCData extends VagabondActorBase {
|
||||
telepathy: new fields.BooleanField({ initial: false }),
|
||||
}),
|
||||
|
||||
// Movement speed
|
||||
// Movement speed - base value plus boolean movement capabilities
|
||||
// All special movement types use the base speed value per RAW
|
||||
speed: new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true, initial: 30 }),
|
||||
fly: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
swim: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
climb: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
}),
|
||||
|
||||
// Movement capabilities - boolean toggles for special movement types
|
||||
// All use base speed value when enabled
|
||||
movement: new fields.SchemaField({
|
||||
climb: new fields.BooleanField({ initial: false }), // Full Speed while climbing
|
||||
cling: new fields.BooleanField({ initial: false }), // As Climb, but can Move on ceilings
|
||||
fly: new fields.BooleanField({ initial: false }), // Move through the air at full Speed
|
||||
phase: new fields.BooleanField({ initial: false }), // Move in occupied space (5 dmg if ends turn there)
|
||||
swim: new fields.BooleanField({ initial: false }), // Full Speed while swimming
|
||||
}),
|
||||
|
||||
// Damage immunities
|
||||
|
||||
@ -32,6 +32,7 @@ export default class VagabondNPCSheet extends VagabondActorSheet {
|
||||
...VagabondActorSheet.DEFAULT_OPTIONS.actions,
|
||||
rollMorale: VagabondNPCSheet.#onRollMorale,
|
||||
rollAction: VagabondNPCSheet.#onRollAction,
|
||||
rollAppearing: VagabondNPCSheet.#onRollAppearing,
|
||||
addAction: VagabondNPCSheet.#onAddAction,
|
||||
deleteAction: VagabondNPCSheet.#onDeleteAction,
|
||||
addAbility: VagabondNPCSheet.#onAddAbility,
|
||||
@ -107,14 +108,17 @@ export default class VagabondNPCSheet extends VagabondActorSheet {
|
||||
context.sizeOptions = CONFIG.VAGABOND?.sizes || {};
|
||||
context.beingTypeOptions = CONFIG.VAGABOND?.beingTypes || {};
|
||||
|
||||
// Speed
|
||||
context.speed = {
|
||||
walk: system.speed.value,
|
||||
fly: system.speed.fly,
|
||||
swim: system.speed.swim,
|
||||
climb: system.speed.climb,
|
||||
hasSpecialMovement: system.speed.fly > 0 || system.speed.swim > 0 || system.speed.climb > 0,
|
||||
};
|
||||
// Speed (base value only)
|
||||
context.speed = system.speed.value;
|
||||
|
||||
// Movement capabilities (boolean toggles)
|
||||
context.movement = system.movement;
|
||||
context.hasMovement =
|
||||
system.movement.climb ||
|
||||
system.movement.cling ||
|
||||
system.movement.fly ||
|
||||
system.movement.phase ||
|
||||
system.movement.swim;
|
||||
|
||||
// Senses
|
||||
context.senses = system.senses;
|
||||
@ -220,6 +224,29 @@ export default class VagabondNPCSheet extends VagabondActorSheet {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle appearing roll (# encountered).
|
||||
* @param {PointerEvent} event
|
||||
* @param {HTMLElement} _target
|
||||
*/
|
||||
static async #onRollAppearing(event, _target) {
|
||||
event.preventDefault();
|
||||
const appearing = this.actor.system.appearing;
|
||||
if (!appearing) {
|
||||
ui.notifications.warn("No appearing dice formula set");
|
||||
return;
|
||||
}
|
||||
|
||||
// Roll the appearing formula
|
||||
const roll = await new Roll(appearing).evaluate();
|
||||
|
||||
// Create chat message
|
||||
await roll.toMessage({
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
||||
flavor: `<strong>${this.actor.name}</strong> - # Appearing`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle adding a new action.
|
||||
* @param {PointerEvent} event
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -89,6 +89,48 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Movement Capabilities --}}
|
||||
<div class="biography-section movement">
|
||||
<h2 class="section-header">{{localize "VAGABOND.Movement"}}</h2>
|
||||
<div class="movement-grid">
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementClimbHint'}}">
|
||||
<input type="checkbox" name="system.movement.climb"
|
||||
{{#if system.movement.climb}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Climb"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementClingHint'}}">
|
||||
<input type="checkbox" name="system.movement.cling"
|
||||
{{#if system.movement.cling}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Cling"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementFlyHint'}}">
|
||||
<input type="checkbox" name="system.movement.fly"
|
||||
{{#if system.movement.fly}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Fly"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementPhaseHint'}}">
|
||||
<input type="checkbox" name="system.movement.phase"
|
||||
{{#if system.movement.phase}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Phase"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementSwimHint'}}">
|
||||
<input type="checkbox" name="system.movement.swim"
|
||||
{{#if system.movement.swim}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Swim"}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Biography Text --}}
|
||||
<div class="biography-section biography-text">
|
||||
<h2 class="section-header">{{localize "VAGABOND.Biography"}}</h2>
|
||||
|
||||
@ -17,35 +17,9 @@
|
||||
{{!-- Speed --}}
|
||||
<div class="stat-group speed">
|
||||
<label>{{localize "VAGABOND.Speed"}}</label>
|
||||
<div class="speed-values">
|
||||
<div class="speed-item walk">
|
||||
<i class="fa-solid fa-shoe-prints"></i>
|
||||
<input type="number" name="system.speed.value" value="{{speed.walk}}" min="0" />
|
||||
<span class="unit">ft</span>
|
||||
</div>
|
||||
{{#if speed.hasSpecialMovement}}
|
||||
{{#if speed.fly}}
|
||||
<div class="speed-item fly">
|
||||
<i class="fa-solid fa-feather"></i>
|
||||
<input type="number" name="system.speed.fly" value="{{speed.fly}}" min="0" />
|
||||
<span class="unit">ft</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if speed.swim}}
|
||||
<div class="speed-item swim">
|
||||
<i class="fa-solid fa-water"></i>
|
||||
<input type="number" name="system.speed.swim" value="{{speed.swim}}" min="0" />
|
||||
<span class="unit">ft</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if speed.climb}}
|
||||
<div class="speed-item climb">
|
||||
<i class="fa-solid fa-mountain"></i>
|
||||
<input type="number" name="system.speed.climb" value="{{speed.climb}}" min="0" />
|
||||
<span class="unit">ft</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<div class="speed-value">
|
||||
<input type="number" name="system.speed.value" value="{{speed}}" min="0" />
|
||||
<span class="unit">ft</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -75,11 +49,51 @@
|
||||
|
||||
{{!-- Appearing --}}
|
||||
<div class="stat-group appearing">
|
||||
<label>{{localize "VAGABOND.Appearing"}}</label>
|
||||
<label class="rollable" data-action="rollAppearing" data-tooltip="{{localize 'VAGABOND.RollAppearing'}}">
|
||||
<i class="fa-solid fa-dice"></i>
|
||||
{{localize "VAGABOND.Appearing"}}
|
||||
</label>
|
||||
<input type="text" name="system.appearing" value="{{appearing}}" placeholder="1d6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Movement Types --}}
|
||||
<div class="biography-section movement">
|
||||
<h2 class="section-header">{{localize "VAGABOND.Movement"}}</h2>
|
||||
<div class="movement-grid">
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementClimbHint'}}">
|
||||
<input type="checkbox" name="system.movement.climb" {{#if movement.climb}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Climb"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementClingHint'}}">
|
||||
<input type="checkbox" name="system.movement.cling" {{#if movement.cling}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Cling"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementFlyHint'}}">
|
||||
<input type="checkbox" name="system.movement.fly" {{#if movement.fly}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Fly"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementPhaseHint'}}">
|
||||
<input type="checkbox" name="system.movement.phase" {{#if movement.phase}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Phase"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="movement-field">
|
||||
<label data-tooltip="{{localize 'VAGABOND.MovementSwimHint'}}">
|
||||
<input type="checkbox" name="system.movement.swim" {{#if movement.swim}}checked{{/if}} />
|
||||
{{localize "VAGABOND.Swim"}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Senses --}}
|
||||
{{#if hasSenses}}
|
||||
<div class="senses-row">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user