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