refactor: distribution
This commit is contained in:
parent
0bfd4b3a9f
commit
85e252e423
|
@ -1,5 +1,7 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
import {distribute} from '@/util/inventory.js';
|
||||
|
||||
class ItemProxy {
|
||||
constructor(Component, instance, slot) {
|
||||
this.Component = Component;
|
||||
|
@ -94,6 +96,9 @@ class ItemProxy {
|
|||
get label() {
|
||||
return this.json.label;
|
||||
}
|
||||
get maximumStack() {
|
||||
return this.json.maximumStack ?? Infinity;
|
||||
}
|
||||
get projection() {
|
||||
return this.json.projection;
|
||||
}
|
||||
|
@ -105,7 +110,10 @@ class ItemProxy {
|
|||
}
|
||||
set qty(qty) {
|
||||
const {instance} = this;
|
||||
if (qty <= 0) {
|
||||
if (qty === this.qty) {
|
||||
return;
|
||||
}
|
||||
else if (qty <= 0) {
|
||||
instance.clear(this.slot);
|
||||
}
|
||||
else {
|
||||
|
@ -121,7 +129,7 @@ class ItemProxy {
|
|||
|
||||
export default class Inventory extends Component {
|
||||
async updateMany(entities) {
|
||||
for (const [id, {cleared, distributed, given, qtyUpdated, swapped}] of entities) {
|
||||
for (const [id, {cleared, given, qtyUpdated, swapped}] of entities) {
|
||||
const instance = this.get(id);
|
||||
const {$$items, slots} = instance;
|
||||
if (cleared) {
|
||||
|
@ -186,56 +194,62 @@ export default class Inventory extends Component {
|
|||
if (!slots[slot]) {
|
||||
return;
|
||||
}
|
||||
const destinations = [];
|
||||
const given = [];
|
||||
const updated = [];
|
||||
for (const [entityId, destination] of potentialDestinations) {
|
||||
const destinations = potentialDestinations
|
||||
.filter(([entityId, destination]) => {
|
||||
const {Inventory} = ecs.get(entityId);
|
||||
return (
|
||||
!Inventory.slots[destination]
|
||||
|| Inventory.slots[destination].source === slots[slot].source
|
||||
);
|
||||
});
|
||||
const {maximumStack, qty, source} = this.item(slot);
|
||||
const item = {
|
||||
maximumStack,
|
||||
qty,
|
||||
};
|
||||
const qtys = distribute(
|
||||
item,
|
||||
destinations.map(([entityId, destination]) => {
|
||||
const {Inventory} = ecs.get(entityId);
|
||||
if (entityId == Inventory.entity && destination === slot) {
|
||||
return 0;
|
||||
}
|
||||
return Inventory.slots[destination]
|
||||
? Inventory.slots[destination].qty
|
||||
: 0;
|
||||
}),
|
||||
);
|
||||
for (let i = 0; i < qtys.length; ++i) {
|
||||
const qty = qtys[i];
|
||||
const [entityId, destination] = destinations[i];
|
||||
const {Inventory} = ecs.get(entityId);
|
||||
const {slots} = Inventory;
|
||||
if (!slots[destination]) {
|
||||
slots[destination] = {
|
||||
qty: 0,
|
||||
source: slots[slot].source,
|
||||
};
|
||||
this.$$items[destination] = new ItemProxy(Component, this, destination);
|
||||
await this.$$items[destination].load(slots[destination].source);
|
||||
destinations.push([entityId, destination]);
|
||||
given.push([entityId, destination]);
|
||||
// create new
|
||||
if (!Inventory.slots[destination]) {
|
||||
if (qty > 0) {
|
||||
const stack = {qty, source};
|
||||
Inventory.slots[destination] = {...stack};
|
||||
Inventory.$$items[destination] = new ItemProxy(Component, Inventory, destination);
|
||||
Component.markChange(entityId, 'given', {[destination]: {...stack}});
|
||||
await Inventory.$$items[destination].load(source);
|
||||
}
|
||||
}
|
||||
else if (slots[slot].source === slots[destination].source) {
|
||||
destinations.push([entityId, destination]);
|
||||
updated.push([entityId, destination, slots[destination].qty]);
|
||||
// update qty of existing
|
||||
else if (Inventory.slots[destination].qty !== qty) {
|
||||
Inventory.item(destination).qty = qty;
|
||||
}
|
||||
}
|
||||
const {qty} = slots[slot];
|
||||
slots[slot].qty -= qty;
|
||||
for (let i = 0; i < qty; ++i) {
|
||||
const [entityId, destination] = destinations[i % destinations.length];
|
||||
const {Inventory} = ecs.get(entityId);
|
||||
const {slots} = Inventory;
|
||||
slots[destination].qty += 1;
|
||||
}
|
||||
if (0 === slots[slot].qty) {
|
||||
this.clear(slot);
|
||||
}
|
||||
else {
|
||||
if (
|
||||
!destinations.find(([entityId, destination]) => {
|
||||
return entityId == this.entity && destination === slot;
|
||||
})
|
||||
) {
|
||||
Component.markChange(this.entity, 'qtyUpdated', {[slot]: -qty});
|
||||
// handle source separately if not also destination
|
||||
if (
|
||||
!destinations.find(([entityId, destination]) => {
|
||||
return entityId == this.entity && destination === slot;
|
||||
})
|
||||
) {
|
||||
if (0 === item.qty) {
|
||||
this.clear(slot);
|
||||
}
|
||||
else if (item.qty !== qty) {
|
||||
this.item(slot).qty = item.qty;
|
||||
}
|
||||
}
|
||||
for (const [entityId, destination] of given) {
|
||||
const {Inventory} = ecs.get(entityId);
|
||||
const {slots} = Inventory;
|
||||
Component.markChange(entityId, 'given', {[destination]: {qty: slots[destination].qty, source: slots[destination].source}});
|
||||
}
|
||||
for (const [entityId, destination, qty] of updated) {
|
||||
const {Inventory} = ecs.get(entityId);
|
||||
const {slots} = Inventory;
|
||||
Component.markChange(entityId, 'qtyUpdated', {[destination]: slots[destination].qty - qty});
|
||||
}
|
||||
}
|
||||
item(slot) {
|
||||
|
@ -269,9 +283,15 @@ export default class Inventory extends Component {
|
|||
if (undefined === slots[l]) {
|
||||
delete slots[l];
|
||||
}
|
||||
else {
|
||||
$$items[l].slot = l;
|
||||
}
|
||||
if (undefined === otherSlots[r]) {
|
||||
delete otherSlots[r];
|
||||
}
|
||||
else {
|
||||
$$items[r].slot = r;
|
||||
}
|
||||
Component.markChange(this.entity, 'swapped', [[l, OtherInventory.entity, r]]);
|
||||
if (this.entity !== OtherInventory.entity) {
|
||||
Component.markChange(OtherInventory.entity, 'swapped', [[r, this.entity, l]]);
|
||||
|
|
|
@ -44,6 +44,7 @@ export default function Grid({
|
|||
onDragStart={(event) => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
temporary={slot?.temporary}
|
||||
qty={slot?.qty}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -13,6 +13,7 @@ export default function Slot({
|
|||
onMouseDown,
|
||||
onMouseMove,
|
||||
onMouseUp,
|
||||
temporary = false,
|
||||
qty = 1,
|
||||
}) {
|
||||
return (
|
||||
|
@ -38,7 +39,11 @@ export default function Slot({
|
|||
{qty > 1 && (
|
||||
<span
|
||||
className={
|
||||
[styles.qty, `q-${Math.floor(Math.log10(qty))}`]
|
||||
[
|
||||
styles.qty,
|
||||
temporary && styles.temporary,
|
||||
`q-${Math.floor(Math.log10(qty))}`,
|
||||
]
|
||||
.filter(Boolean).join(' ')
|
||||
}
|
||||
>
|
||||
|
|
|
@ -28,24 +28,34 @@
|
|||
|
||||
.qty {
|
||||
bottom: calc(var(--space) * 0.125);
|
||||
font-family: monospace;
|
||||
color: white;
|
||||
font-family: joystix;
|
||||
font-size: calc(var(--space) * 2);
|
||||
line-height: 1;
|
||||
position: absolute;
|
||||
right: calc(var(--space) * -0.25);
|
||||
right: calc(var(--space) * -0.5);
|
||||
text-shadow:
|
||||
0px -1px 0px white,
|
||||
1px 0px 0px white,
|
||||
0px 1px 0px white,
|
||||
-1px 0px 0px white
|
||||
0px -1px 0px black,
|
||||
1px 0px 0px black,
|
||||
0px 1px 0px black,
|
||||
-1px 0px 0px black
|
||||
;
|
||||
&:global(.q-2) {
|
||||
font-size: calc(var(--space) * 1.75);
|
||||
}
|
||||
&:global(.q-3) {
|
||||
font-size: calc(var(--space) * 1.5);
|
||||
}
|
||||
&:global(.q-4) {
|
||||
&:global(.q-3) {
|
||||
font-size: calc(var(--space) * 1.25);
|
||||
}
|
||||
&:global(.q-4) {
|
||||
font-size: calc(var(--space) * 1.125);
|
||||
}
|
||||
&.temporary {
|
||||
color: gold;
|
||||
text-shadow:
|
||||
0px -1px 0px black,
|
||||
1px 0px 0px black,
|
||||
0px 1px 0px black,
|
||||
-1px 0px 0px black
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
|||
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||
import {RESOLUTION} from '@/util/constants.js';
|
||||
import EventEmitter from '@/util/event-emitter.js';
|
||||
import {distribute} from '@/util/inventory.js';
|
||||
|
||||
import addKeyListener from './add-key-listener.js';
|
||||
import Disconnected from './dom/disconnected.jsx';
|
||||
|
@ -268,7 +269,12 @@ function Ui({disconnected}) {
|
|||
setHotbarSlots(() => {
|
||||
const newHotbarSlots = [];
|
||||
for (let i = 1; i < 11; ++i) {
|
||||
newHotbarSlots.push(entity.Inventory.item(i));
|
||||
const item = entity.Inventory.item(i);
|
||||
newHotbarSlots.push(
|
||||
item
|
||||
? {icon: item.icon, qty: item.qty}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
return newHotbarSlots;
|
||||
});
|
||||
|
@ -439,11 +445,57 @@ function Ui({disconnected}) {
|
|||
const hotbarOnSlotMouseMove = useCallback((i) => {
|
||||
if (hadBufferSlot.current) {
|
||||
setDistributing((distributing) => ({
|
||||
[[mainEntityRef.current, i + 1].join(':')]: true,
|
||||
...distributing,
|
||||
}))
|
||||
[[mainEntityRef.current, i + 1].join(':')]: true,
|
||||
}));
|
||||
}
|
||||
}, [mainEntityRef]);
|
||||
useEffect(() => {
|
||||
if (!bufferSlot) {
|
||||
return;
|
||||
}
|
||||
const entity = ecsRef.current.get(mainEntityRef.current);
|
||||
setHotbarSlots(() => {
|
||||
const newHotbarSlots = [];
|
||||
for (let i = 1; i < 11; ++i) {
|
||||
const item = entity.Inventory.item(i);
|
||||
newHotbarSlots.push(
|
||||
item
|
||||
? {icon: item.icon, qty: item.qty}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
const qtys = [];
|
||||
for (const key in distributing) {
|
||||
const [entityId, slot] = key.split(':');
|
||||
const {Inventory} = ecsRef.current.get(entityId);
|
||||
qtys.push(Inventory.slots[slot]?.qty ?? 0);
|
||||
}
|
||||
const item = {
|
||||
maximumStack: bufferSlot.maximumStack,
|
||||
qty: bufferSlot.qty,
|
||||
};
|
||||
const distributed = distribute(item, qtys);
|
||||
let i = 0;
|
||||
for (const key in distributing) {
|
||||
const [entityId, slot] = key.split(':');
|
||||
if (
|
||||
entityId == mainEntityRef.current
|
||||
&& (slot >= 1 && slot <= 11)
|
||||
) {
|
||||
if (!newHotbarSlots[slot - 1]) {
|
||||
newHotbarSlots[slot - 1] = {
|
||||
icon: bufferSlot.icon,
|
||||
};
|
||||
}
|
||||
newHotbarSlots[slot - 1].qty = distributed[i];
|
||||
newHotbarSlots[slot - 1].temporary = true;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return newHotbarSlots;
|
||||
});
|
||||
}, [bufferSlot, distributing, ecsRef, mainEntityRef])
|
||||
const hotbarOnSlotMouseUp = useCallback((i, event) => {
|
||||
keepHotbarOpen();
|
||||
if (trading) {
|
||||
|
|
|
@ -43,6 +43,14 @@ export default async function createPlayer(id) {
|
|||
qty: 1,
|
||||
source: '/resources/hoe/hoe.json',
|
||||
},
|
||||
6: {
|
||||
qty: 95,
|
||||
source: '/resources/potion/potion.json',
|
||||
},
|
||||
7: {
|
||||
qty: 95,
|
||||
source: '/resources/potion/potion.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
Health: {health: 100},
|
||||
|
|
23
app/util/inventory.js
Normal file
23
app/util/inventory.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
export function distribute(item, qtys) {
|
||||
const {maximumStack} = item;
|
||||
qtys = [...qtys];
|
||||
const eligible = qtys.map((_, i) => i);
|
||||
let i = 0;
|
||||
while (item.qty > 0) {
|
||||
const key = eligible[i];
|
||||
if (qtys[key] < maximumStack) {
|
||||
qtys[key] += 1;
|
||||
i = (i + 1) % eligible.length;
|
||||
item.qty -= 1;
|
||||
}
|
||||
else {
|
||||
eligible.splice(i, 1);
|
||||
i = i % eligible.length;
|
||||
if (0 === eligible.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return qtys;
|
||||
}
|
||||
|
19
app/util/inventory.test.js
Normal file
19
app/util/inventory.test.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import {distribute} from './inventory.js';
|
||||
|
||||
test('distributes', async () => {
|
||||
let item;
|
||||
item = {maximumStack: 20, qty: 10};
|
||||
expect(distribute(
|
||||
item,
|
||||
[15, 15, 0],
|
||||
)).to.deep.equal([19, 18, 3]);
|
||||
expect(item.qty).to.equal(0);
|
||||
item = {maximumStack: 20, qty: 20};
|
||||
expect(distribute(
|
||||
item,
|
||||
[15, 15, 0],
|
||||
)).to.deep.equal([20, 20, 10]);
|
||||
expect(item.qty).to.equal(0);
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"icon": "/resources/potion/icon.png",
|
||||
"label": "Potion",
|
||||
"maximumStack": 100,
|
||||
"price": 50,
|
||||
"start": "/resources/potion/start.js"
|
||||
}
|
Loading…
Reference in New Issue
Block a user