import Component from '@/ecs/component.js'; class ItemProxy { constructor(Component, instance, slot) { this.Component = Component; this.instance = instance; this.slot = slot; } async load(source) { const {ecs} = this.Component; const json = await ecs.readJson(source); const scripts = {}; if (json.projectionCheck) { scripts.projectionCheckInstance = await ecs.readScript(json.projectionCheck); } if (json.start) { scripts.startInstance = await ecs.readScript(json.start); } if (json.stop) { scripts.stopInstance = await ecs.readScript(json.stop); } this.json = json; this.scripts = scripts; } project(position, direction) { const {TileLayers} = this.Component.ecs.get(1); const layer = TileLayers.layer(0); const {projection} = this; if (!projection) { return undefined; } let startX = position.x; let startY = position.y; switch (direction) { case 0: startX += projection.distance[0]; startY += projection.distance[1]; break; case 1: startX -= projection.distance[1]; startY += projection.distance[0]; break; case 2: startX -= projection.distance[0]; startY -= projection.distance[1]; break; case 3: startX += projection.distance[1]; startY -= projection.distance[0]; break; } const projected = []; for (const row in projection.grid) { const columns = projection.grid[row]; for (const column in columns) { const targeted = projection.grid[row][column]; if (targeted) { let axe; switch (direction) { case 0: axe = [-row, column]; break; case 1: axe = [-column, -row]; break; case 2: axe = [row, -column]; break; case 3: axe = [column, row]; break; } const x = startX + parseInt(axe[0]); const y = startY + parseInt(axe[1]); if (x < 0 || y < 0 || x >= layer.area.x || y >= layer.area.y) { continue; } projected.push({x, y}); } } } if (this.scripts.projectionCheckInstance) { this.scripts.projectionCheckInstance.context.ecs = this.Component.ecs; this.scripts.projectionCheckInstance.context.projected = projected; return this.scripts.projectionCheckInstance.evaluate(); } else { return projected; } } get icon() { return this.json.icon; } get label() { return this.json.label; } get projection() { return this.json.projection; } get qty() { return this.instance.slots[this.slot].qty; } set qty(qty) { const {instance} = this; if (qty <= 0) { this.Component.markChange(instance.entity, 'cleared', {[this.slot]: true}); delete instance.slots[this.slot]; delete instance.$$items[this.slot]; } else { instance.slots[this.slot].qty = qty; this.Component.markChange(instance.entity, 'qtyUpdated', {[this.slot]: qty}); } } } export default class Inventory extends Component { async updateMany(entities) { for (const [id, {cleared, given, qtyUpdated, swapped}] of entities) { const instance = this.get(id); const {$$items, slots} = instance; if (cleared) { for (const slot in cleared) { delete slots[slot]; } } if (given) { for (const slot in given) { $$items[slot] = new ItemProxy(this, instance, slot); await $$items[slot].load(given[slot].source); slots[slot] = given[slot]; } } if (qtyUpdated) { for (const slot in qtyUpdated) { slots[slot].qty = qtyUpdated[slot]; } } if (swapped) { for (const [l, otherEntityId, r] of swapped) { const otherInstance = this.get(otherEntityId); const {$$items: $$otherItems, slots: otherSlots} = otherInstance; const tmp = [$$items[l], slots[l]]; [$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]]; [$$otherItems[r], otherSlots[r]] = tmp; if ($$otherItems[r]) { $$otherItems[r].slot = r; } if ($$items[l]) { $$items[l].slot = l; } } } } await super.updateMany(entities); for (const [id, {slots}] of entities) { if (slots) { const instance = this.get(id); instance.$$items = {}; for (const slot in slots) { instance.$$items[slot] = new ItemProxy(this, instance, slot); await instance.$$items[slot].load(instance.slots[slot].source); } } } } instanceFromSchema() { const Instance = super.instanceFromSchema(); const Component = this; return class InventoryInstance extends Instance { $$items = {}; item(slot) { return this.$$items[slot]; } async give(stack) { const {slots} = this; for (let slot = 1; slot < 11; ++slot) { if (slots[slot]?.source === stack.source) { slots[slot].qty += stack.qty; Component.markChange(this.entity, 'qtyUpdated', {[slot]: slots[slot].qty}); return; } } for (let slot = 1; slot < 11; ++slot) { if (!slots[slot]) { slots[slot] = stack; this.$$items[slot] = new ItemProxy(Component, this, slot); await this.$$items[slot].load(); Component.markChange(this.entity, 'given', {[slot]: slots[slot]}); return; } } } swapSlots(l, OtherInventory, r) { const {$$items, slots} = this; const {$$items: $$otherItems, slots: otherSlots} = OtherInventory; const tmp = [$$items[l], slots[l]]; [$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]]; [$$otherItems[r], otherSlots[r]] = tmp; if (undefined === slots[l]) { delete slots[l]; } if (undefined === otherSlots[r]) { delete otherSlots[r]; } Component.markChange(this.entity, 'swapped', [[l, OtherInventory.entity, r]]); Component.markChange(OtherInventory.entity, 'swapped', [[r, this.entity, l]]); } toNet(recipient, data) { if (recipient.id !== this.entity && this !== recipient.Player.openInventory) { return {}; } return super.toNet(data); } } } async load(instance) { for (const slot in instance.slots) { instance.$$items[slot] = new ItemProxy(this, instance, slot); await instance.$$items[slot].load(instance.slots[slot].source); } } mergeDiff(original, update) { const merged = { ...original, qtyUpdated: { ...original.qtyUpdated, ...update.qtyUpdated, }, cleared: { ...original.cleared, ...update.cleared, }, given: { ...original.given, ...update.given, }, swapped: [ ...(original.swapped || []), ...(update.swapped || []), ], }; return merged; } static properties = { slots: { type: 'map', value: { type: 'object', properties: { quantity: {type: 'uint16'}, source: {type: 'string'}, }, }, }, }; }