Implements inventory system, wip

This commit is contained in:
2026-05-22 11:03:17 +02:00
parent 4ff46865c2
commit dd3fe0e38e
15 changed files with 431 additions and 40 deletions
+103 -16
View File
@@ -32,6 +32,8 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
useConsumable: PrismRPGCharacterSheet.#onUseConsumable,
toggleContainerEquipped: PrismRPGCharacterSheet.#onToggleContainerEquipped,
toggleEquipped: PrismRPGCharacterSheet.#onToggleEquipped,
assignToContainer: PrismRPGCharacterSheet.#onAssignToContainer,
removeFromContainer: PrismRPGCharacterSheet.#onRemoveFromContainer,
},
}
@@ -162,25 +164,45 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
break
case "equipment":
context.tab = context.tabs.equipment
context.weapons = doc.itemTypes.weapon
context.armors = [...doc.itemTypes.armor, ...doc.itemTypes.shield]
context.consumables = doc.itemTypes.consumable
context.kits = doc.itemTypes.equipment.filter(i => i.system.isKit)
context.equipmentItems = doc.itemTypes.equipment.filter(i => !i.system.isKit)
context.loots = doc.itemTypes.loot
context.containers = doc.itemTypes.container
context.packBurdenMax = doc.itemTypes.container
.filter(c => c.system.equipped)
.reduce((sum, c) => sum + (c.system.packBurden ?? 0), 0)
context.packBurdenUsed = [
...doc.itemTypes.equipment,
...doc.itemTypes.consumable,
...doc.itemTypes.loot,
...doc.itemTypes.container,
// All items that can be stored in containers
const allStorable = [
...doc.itemTypes.weapon,
...doc.itemTypes.armor,
...doc.itemTypes.shield,
].reduce((sum, i) => sum + (i.system.encLoad ?? 0), 0)
...doc.itemTypes.equipment,
...doc.itemTypes.consumable,
...doc.itemTypes.loot,
]
// Build a map: containerId → items[]
const containerGroups = {}
for (const container of doc.itemTypes.container) {
containerGroups[container.id] = { container, items: [] }
}
for (const item of allStorable) {
const cid = item.system.containerId
if (cid && containerGroups[cid]) {
containerGroups[cid].items.push(item)
}
}
context.containerGroups = Object.values(containerGroups)
// Items are "uncontained" if they have no containerId, or if their container was deleted
const isUncontained = i => !i.system.containerId || !containerGroups[i.system.containerId]
context.weapons = doc.itemTypes.weapon.filter(isUncontained)
context.armors = [...doc.itemTypes.armor, ...doc.itemTypes.shield].filter(isUncontained)
context.consumables = doc.itemTypes.consumable.filter(isUncontained)
context.kits = doc.itemTypes.equipment.filter(i => i.system.isKit && isUncontained(i))
context.equipmentItems = doc.itemTypes.equipment.filter(i => !i.system.isKit && isUncontained(i))
context.loots = doc.itemTypes.loot.filter(isUncontained)
context.containers = doc.itemTypes.container
context.packBurdenMax = doc.itemTypes.container
.filter(c => c.system.equipped)
.reduce((sum, c) => sum + (c.system.packBurden ?? 0), 0)
// Pack burden = items stored in an existing container
context.packBurdenUsed = allStorable
.filter(i => i.system.containerId && containerGroups[i.system.containerId])
.reduce((sum, i) => sum + (i.system.encLoad ?? 0), 0)
break
case "biography":
context.tab = context.tabs.biography
@@ -205,6 +227,18 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
// Handle different data types
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
// Check if dropped onto a container row
const containerEl = event.target.closest("[data-container-id]")
if (containerEl && item?.parent === this.document) {
const containerId = containerEl.dataset.containerId
// Don't store containers inside containers
if (item.type !== "container") {
await item.update({ "system.containerId": containerId })
return
}
}
return this._onDropItem(item)
}
}
@@ -331,6 +365,48 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
await item.update({ "system.equipped": !item.system.equipped })
}
static async #onAssignToContainer(event, target) {
const itemElement = target.closest("[data-item-id]")
if (!itemElement) return
const item = this.document.items.get(itemElement.dataset.itemId)
if (!item || item.type === "container") return
const containers = this.document.itemTypes.container
if (!containers.length) {
ui.notifications.warn(game.i18n.localize("PRISMRPG.Message.noContainers"))
return
}
const options = containers.map(c => {
const escapedName = foundry.utils.escapeHTML(c.name)
return `<option value="${c.id}">${escapedName}</option>`
}).join("")
const content = `<div class="form-group">
<label>${game.i18n.localize("PRISMRPG.Label.container")}</label>
<div class="form-fields">
<select name="containerId">${options}</select>
</div>
</div>`
const containerId = await foundry.applications.api.DialogV2.prompt({
window: { title: game.i18n.localize("PRISMRPG.Dialog.assignToContainer") },
classes: ["prismrpg"],
content,
ok: {
callback: (event, button) => button.form.elements.containerId.value,
},
})
if (containerId) await item.update({ "system.containerId": containerId })
}
static async #onRemoveFromContainer(event, target) {
const itemElement = target.closest("[data-item-id]")
if (!itemElement) return
const item = this.document.items.get(itemElement.dataset.itemId)
if (!item) return
await item.update({ "system.containerId": "" })
}
static async #onPostItemToChat(event, target) {
console.log("PRISM RPG | PostItemToChat action triggered", { event: event, target: target })
@@ -495,6 +571,17 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
this.actor.update({ "system.hp.wounds": tab });
})
}
// Container drag-over highlight
this.element.querySelectorAll("[data-container-id]").forEach(el => {
el.addEventListener("dragover", (e) => {
e.preventDefault()
el.classList.add("drag-over")
})
el.addEventListener("dragleave", () => el.classList.remove("drag-over"))
el.addEventListener("drop", () => el.classList.remove("drag-over"))
})
super._onRender();
}
+1
View File
@@ -9,6 +9,7 @@ export const defaultItemImg = {
race: "systems/fvtt-prism-rpg/assets/icons/icon_race.webp",
class: "systems/fvtt-prism-rpg/assets/icons/icon_class.webp",
"character-path": "systems/fvtt-prism-rpg/assets/icons/icon_character_path.webp",
container: "icons/containers/bags/pack-leather-brown.webp",
}
export default class PrismRPGItem extends Item {
+1
View File
@@ -25,6 +25,7 @@ export default class PrismRPGArmor extends foundry.abstract.TypeDataModel {
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
+1
View File
@@ -10,6 +10,7 @@ export default class PrismRPGConsumable extends foundry.abstract.TypeDataModel {
schema.encLoad = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.notes = new fields.HTMLField({ required: true })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
+1
View File
@@ -13,6 +13,7 @@ export default class PrismRPGEquipment extends foundry.abstract.TypeDataModel {
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.equipped = new fields.BooleanField({ required: true, initial: false })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
// Kit properties
schema.isKit = new fields.BooleanField({
+1
View File
@@ -8,6 +8,7 @@ export default class PrismRPGLoot extends foundry.abstract.TypeDataModel {
schema.encLoad = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.notes = new fields.HTMLField({ required: true })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
+1
View File
@@ -63,6 +63,7 @@ export default class PrismRPGShield extends foundry.abstract.TypeDataModel {
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.equipped = new fields.BooleanField({ required: true, initial: false })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}
+1
View File
@@ -127,6 +127,7 @@ export default class PrismRPGWeapon extends foundry.abstract.TypeDataModel {
schema.money = new fields.StringField({ required: true, initial: "coppercoin", choices: SYSTEM.MONEY })
schema.equipped = new fields.BooleanField({ required: true, initial: false })
schema.isImplement = new fields.BooleanField({ required: true, initial: false })
schema.containerId = new fields.StringField({ required: false, initial: "", nullable: false })
return schema
}