Add slotsWhenEquipped system and equipment Active Effect sync
- Add getTotalSlots() method to base-item.mjs as unified interface - Add slotsWhenEquipped field to weapon, armor, and equipment schemas - Implement getTotalSlots() in each item type respecting equipped state - Update actor slot calculation to use getTotalSlots() uniformly - Add _onUpdate hook to sync equipment effects with equipped state - Update backpack with slotsWhenEquipped: 0 and +2 slot bonus effect Backpack now correctly: - Costs 1 slot when unequipped, 0 when equipped - Grants +2 max item slots via Active Effect when equipped 🤖 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
77706eafe2
commit
1a36139387
@ -56,6 +56,15 @@ export default class ArmorData extends VagabondItemBase {
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
// Slot cost when equipped (null means same as slots)
|
||||
// Allows magic armor to reduce slot cost when worn
|
||||
slotsWhenEquipped: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: null,
|
||||
nullable: true,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
// Monetary value (in copper)
|
||||
value: new fields.NumberField({
|
||||
integer: true,
|
||||
@ -134,12 +143,16 @@ export default class ArmorData extends VagabondItemBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate slot cost when equipped.
|
||||
* Calculate the total inventory slot cost for this armor.
|
||||
* Respects slotsWhenEquipped for magic armor that reduces slot cost when worn.
|
||||
*
|
||||
* @returns {number} Slot cost
|
||||
* @returns {number} Total slots used
|
||||
*/
|
||||
getEquippedSlots() {
|
||||
return this.equipped ? this.slots : 0;
|
||||
getTotalSlots() {
|
||||
if (this.equipped && this.slotsWhenEquipped !== null) {
|
||||
return this.slotsWhenEquipped;
|
||||
}
|
||||
return this.slots || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -64,4 +64,15 @@ export default class VagabondItemBase extends foundry.abstract.TypeDataModel {
|
||||
description: this.description,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the total inventory slot cost for this item.
|
||||
* Override in subclasses that have inventory slots (weapons, armor, equipment).
|
||||
* Items without inventory presence (features, classes, ancestries) return 0.
|
||||
*
|
||||
* @returns {number} Total slots used
|
||||
*/
|
||||
getTotalSlots() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,15 @@ export default class EquipmentData extends VagabondItemBase {
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
// Slot cost when equipped (null means same as slots)
|
||||
// Used for items like backpacks that cost 0 slots when worn
|
||||
slotsWhenEquipped: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: null,
|
||||
nullable: true,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
// Whether slots are per-item or for the whole stack
|
||||
slotsPerItem: new fields.BooleanField({ initial: false }),
|
||||
|
||||
@ -125,14 +134,20 @@ export default class EquipmentData extends VagabondItemBase {
|
||||
|
||||
/**
|
||||
* Calculate the total slot cost for this item stack.
|
||||
* Respects slotsWhenEquipped for items like backpacks that cost
|
||||
* less (or zero) slots when worn.
|
||||
*
|
||||
* @returns {number} Total slots used
|
||||
*/
|
||||
getTotalSlots() {
|
||||
// Use slotsWhenEquipped if item is equipped and the field is set
|
||||
const baseSlots =
|
||||
this.equipped && this.slotsWhenEquipped !== null ? this.slotsWhenEquipped : this.slots;
|
||||
|
||||
if (this.slotsPerItem) {
|
||||
return this.slots * this.quantity;
|
||||
return baseSlots * this.quantity;
|
||||
}
|
||||
return this.slots;
|
||||
return baseSlots;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -97,6 +97,15 @@ export default class WeaponData extends VagabondItemBase {
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
// Slot cost when equipped (null means same as slots)
|
||||
// Allows magic weapons to reduce slot cost when wielded
|
||||
slotsWhenEquipped: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: null,
|
||||
nullable: true,
|
||||
min: 0,
|
||||
}),
|
||||
|
||||
// Monetary value (in copper)
|
||||
value: new fields.NumberField({
|
||||
integer: true,
|
||||
@ -229,12 +238,15 @@ export default class WeaponData extends VagabondItemBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the slot cost when equipped.
|
||||
* Calculate the total inventory slot cost for this weapon.
|
||||
* Respects slotsWhenEquipped for magic weapons that reduce slot cost when wielded.
|
||||
*
|
||||
* @returns {number} Slot cost
|
||||
* @returns {number} Total slots used
|
||||
*/
|
||||
getEquippedSlots() {
|
||||
return this.equipped ? this.slots : 0;
|
||||
getTotalSlots() {
|
||||
const baseSlots =
|
||||
this.equipped && this.slotsWhenEquipped !== null ? this.slotsWhenEquipped : this.slots || 0;
|
||||
return baseSlots * (this.quantity || 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -205,13 +205,11 @@ export default class VagabondActor extends Actor {
|
||||
system.armor = totalArmor;
|
||||
|
||||
// Calculate used item slots from inventory
|
||||
// Each item type implements getTotalSlots() with its own logic
|
||||
let usedSlots = 0;
|
||||
for (const item of this.items) {
|
||||
// Only count items that take slots (not features, classes, etc.)
|
||||
if (["weapon", "armor", "equipment"].includes(item.type)) {
|
||||
const slots = item.system.slots || 0;
|
||||
const quantity = item.system.quantity || 1;
|
||||
usedSlots += slots * quantity;
|
||||
if (typeof item.system.getTotalSlots === "function") {
|
||||
usedSlots += item.system.getTotalSlots();
|
||||
}
|
||||
}
|
||||
system.itemSlots.used = usedSlots;
|
||||
|
||||
@ -1101,6 +1101,49 @@ export default class VagabondItem extends Item {
|
||||
/* Equipment Helpers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle item updates. Sync equipment Active Effects with equipped state.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async _onUpdate(changed, options, userId) {
|
||||
await super._onUpdate(changed, options, userId);
|
||||
|
||||
// Only process for the updating user
|
||||
if (game.user.id !== userId) return;
|
||||
|
||||
// Sync equipment effects with equipped state
|
||||
if (
|
||||
["weapon", "armor", "equipment"].includes(this.type) &&
|
||||
changed.system?.equipped !== undefined &&
|
||||
this.actor
|
||||
) {
|
||||
await this._syncEquippedEffects(changed.system.equipped);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Active Effects' disabled state with equipped state.
|
||||
* Effects should be enabled when equipped, disabled when not.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} equipped - The new equipped state
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async _syncEquippedEffects(equipped) {
|
||||
// Find effects on the actor that originated from this item
|
||||
const itemEffects = this.actor.effects.filter((e) => e.origin === this.uuid);
|
||||
if (itemEffects.length === 0) return;
|
||||
|
||||
// Update disabled state: disabled = !equipped
|
||||
const updates = itemEffects.map((effect) => ({
|
||||
_id: effect.id,
|
||||
disabled: !equipped,
|
||||
}));
|
||||
|
||||
await this.actor.updateEmbeddedDocuments("ActiveEffect", updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the equipped state of this item.
|
||||
*
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
"description": "<p>Grants +2 Slots, occupies 1 Slot (0 while worn). Can only benefit from one at a time.</p>",
|
||||
"quantity": 1,
|
||||
"slots": 1,
|
||||
"slotsWhenEquipped": 0,
|
||||
"slotsPerItem": false,
|
||||
"value": 500,
|
||||
"consumable": false,
|
||||
@ -34,7 +35,21 @@
|
||||
"lore": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Backpack Slot Bonus",
|
||||
"icon": "icons/svg/item-bag.svg",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.itemSlots.bonus",
|
||||
"mode": 2,
|
||||
"value": "2"
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"transfer": true
|
||||
}
|
||||
],
|
||||
"_key": "!items!vagabondEquipBackpack",
|
||||
"reviewed": true
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user