fun: an actual chest

This commit is contained in:
cha0s 2024-07-28 18:42:28 -05:00
parent 99b8d0f633
commit 4777a2a3a5
16 changed files with 213 additions and 67 deletions

View File

@ -152,8 +152,8 @@ export default class Component {
} }
} }
destroy() {} destroy() {}
toNet() { toNet(recipient, data) {
return Component.constructor.filterDefaults(this); return data || Component.constructor.filterDefaults(this);
} }
toJSON() { toJSON() {
return Component.constructor.filterDefaults(this); return Component.constructor.filterDefaults(this);

View File

@ -137,12 +137,14 @@ export default class Inventory extends Component {
} }
} }
if (swapped) { if (swapped) {
for (const [l, r] of 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]]; const tmp = [$$items[l], slots[l]];
[$$items[l], slots[l]] = [$$items[r], slots[r]]; [$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]];
[$$items[r], slots[r]] = tmp; [$$otherItems[r], otherSlots[r]] = tmp;
if ($$items[r]) { if ($$otherItems[r]) {
$$items[r].slot = r; $$otherItems[r].slot = r;
} }
if ($$items[l]) { if ($$items[l]) {
$$items[l].slot = l; $$items[l].slot = l;
@ -189,18 +191,26 @@ export default class Inventory extends Component {
} }
} }
} }
swapSlots(l, r) { swapSlots(l, OtherInventory, r) {
const {$$items, slots} = this; const {$$items, slots} = this;
const {$$items: $$otherItems, slots: otherSlots} = OtherInventory;
const tmp = [$$items[l], slots[l]]; const tmp = [$$items[l], slots[l]];
[$$items[l], slots[l]] = [$$items[r], slots[r]]; [$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]];
[$$items[r], slots[r]] = tmp; [$$otherItems[r], otherSlots[r]] = tmp;
if (undefined === slots[l]) { if (undefined === slots[l]) {
delete slots[l]; delete slots[l];
} }
if (undefined === slots[r]) { if (undefined === otherSlots[r]) {
delete slots[r]; delete otherSlots[r];
} }
Component.markChange(this.entity, 'swapped', [[l, 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);
} }
} }
} }

View File

@ -1,3 +1,51 @@
import Component from '@/ecs/component.js'; import Component from '@/ecs/component.js';
export default class Player extends Component {} const State = {
CLOSED: 0,
OPENING: 1,
OPEN: 2,
CLOSING: 3,
};
export default class Player extends Component {
instanceFromSchema() {
const {ecs} = this;
return class PlayerInstance extends super.instanceFromSchema() {
$$openInventory;
$$openInventoryState = State.CLOSED;
closeInventory() {
this.$$openInventoryState = State.CLOSING;
}
updateAttachments(update) {
if (!this.$$openInventory) {
return;
}
if (![State.OPENING, State.CLOSING].includes(this.$$openInventoryState)) {
return;
}
if (!ecs.get(this.$$openInventory.entity)) {
return;
}
if (!update[this.$$openInventory.entity]) {
update[this.$$openInventory.entity] = {};
}
if (this.$$openInventoryState === State.OPENING) {
update[this.$$openInventory.entity].Inventory = this.$$openInventory.toNet(ecs.get(this.entity))
this.$$openInventoryState = State.OPEN;
}
else {
update[this.$$openInventory.entity].Inventory = {closed: true};
this.$$openInventory = undefined;
this.$$openInventoryState = State.CLOSED;
}
}
get openInventory() {
return this.$$openInventory;
}
set openInventory(Inventory) {
this.$$openInventoryState = State.OPENING;
this.$$openInventory = Inventory;
}
};
}
}

View File

@ -58,9 +58,9 @@ export default class Sprite extends Component {
get scale() { get scale() {
return {x: this.scaleX, y: this.scaleY}; return {x: this.scaleX, y: this.scaleY};
} }
toNet() { toNet(recipient, data) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {elapsed, ...rest} = super.toNet(); const {elapsed, ...rest} = super.toNet(recipient, data);
return rest; return rest;
} }
}; };

View File

@ -43,14 +43,23 @@ export default class EntityFactory {
}; };
} }
Object.defineProperties(Entity.prototype, properties); Object.defineProperties(Entity.prototype, properties);
Entity.prototype.updateAttachments = new Function('update', `
${
sorted
.filter((componentName) => (
Components[componentName].Instance.prototype.updateAttachments
))
.map((type) => `this.${type}.updateAttachments(update)`).join('; ')
}
`);
Entity.prototype.toJSON = new Function('', ` Entity.prototype.toJSON = new Function('', `
return { return {
${sorted.map((type) => `${type}: this.${type}.toJSON()`).join(', ')} ${sorted.map((type) => `${type}: this.${type}.toJSON()`).join(', ')}
}; };
`); `);
Entity.prototype.toNet = new Function('', ` Entity.prototype.toNet = new Function('recipient', `
return { return {
${sorted.map((type) => `${type}: this.${type}.toNet()`).join(', ')} ${sorted.map((type) => `${type}: this.${type}.toNet(recipient)`).join(', ')}
}; };
`); `);
walk.class = Entity; walk.class = Entity;

View File

@ -0,0 +1,24 @@
import {System} from '@/ecs/index.js';
import {distance} from '@/util/math.js';
export default class InventoryCloser extends System {
static queries() {
return {
default: ['Player'],
};
}
tick() {
for (const {Player, Position} of this.select('default')) {
const {openInventory} = Player;
if (openInventory) {
const {Position: inventoryPosition} = this.ecs.get(openInventory.entity);
if (distance(Position, inventoryPosition) > 64) {
Player.closeInventory();
}
}
}
}
}

View File

@ -15,7 +15,6 @@ export default function Bag({
className={styles.bag} className={styles.bag}
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, left: '-440px'}} style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, left: '-440px'}}
> >
<Grid <Grid
color="rgba(02, 02, 28, 0.6)" color="rgba(02, 02, 28, 0.6)"
columns={10} columns={10}

View File

@ -1,8 +1,5 @@
.bag { .bag {
align-self: left;
display: inline-block;
left: 20px; left: 20px;
line-height: 0;
opacity: 1; opacity: 1;
position: absolute; position: absolute;
top: 74px; top: 74px;

View File

@ -0,0 +1,27 @@
import styles from './external.module.css';
import Grid from './grid.jsx';
/**
* External inventory.
*/
export default function External({
isInventoryOpen,
onActivate,
slots,
}) {
return (
<div
className={styles.external}
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, top: '450px'}}
>
<Grid
color="rgba(57, 02, 02, 0.6)"
columns={10}
label="Chest"
onActivate={onActivate}
slots={slots}
/>
</div>
);
}

View File

@ -0,0 +1,7 @@
.external {
left: 20px;
opacity: 1;
position: absolute;
top: 274px;
transition: top 150ms, opacity 200ms;
}

View File

@ -49,7 +49,7 @@ export default function Grid({
)); ));
return ( return (
<div className={styles.gridWrapper}> <div className={styles.gridWrapper}>
<p className={styles.label}>{label}</p> <p className={styles.label}>&nbsp;{label}&nbsp;</p>
<div <div
className={styles.grid} className={styles.grid}
style={{ style={{

View File

@ -1,27 +1,7 @@
.hotbar { .hotbar {
align-self: left;
display: inline-block;
left: 20px; left: 20px;
line-height: 0;
opacity: 1; opacity: 1;
position: absolute; position: absolute;
top: 4px; top: 4px;
transition: top 150ms, opacity 200ms; transition: top 150ms, opacity 200ms;
} }
.label {
background-color: transparent;
color: white;
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
left: 50%;
margin: 0;
position: absolute;
text-shadow:
0px -1px 0px black,
1px 0px 0px black,
0px 1px 0px black,
-1px 0px 0px black
;
top: -17.5px;
transform: translateX(-50%);
}

View File

@ -14,7 +14,7 @@ import Chat from './dom/chat/chat.jsx';
import Bag from './dom/bag.jsx'; import Bag from './dom/bag.jsx';
import Dom from './dom/dom.jsx'; import Dom from './dom/dom.jsx';
import Entities from './dom/entities.jsx'; import Entities from './dom/entities.jsx';
import Grid from './dom/grid.jsx'; import External from './dom/external.jsx';
import HotBar from './dom/hotbar.jsx'; import HotBar from './dom/hotbar.jsx';
import Pixi from './pixi/pixi.jsx'; import Pixi from './pixi/pixi.jsx';
import Devtools from './devtools.jsx'; import Devtools from './devtools.jsx';
@ -26,7 +26,7 @@ const KEY_MAP = {
}; };
function emptySlots() { function emptySlots() {
return Array(40).fill(undefined); return Array(50).fill(undefined);
} }
const devEventsChannel = new EventEmitter(); const devEventsChannel = new EventEmitter();
@ -60,6 +60,8 @@ function Ui({disconnected}) {
const [hotbarIsHidden, setHotbarIsHidden] = useState(true); const [hotbarIsHidden, setHotbarIsHidden] = useState(true);
const [hotbarHideHandle, setHotbarHideHandle] = useState(); const [hotbarHideHandle, setHotbarHideHandle] = useState();
const [isInventoryOpen, setIsInventoryOpen] = useState(false); const [isInventoryOpen, setIsInventoryOpen] = useState(false);
const [externalInventory, setExternalInventory] = useState();
const [externalInventorySlots, setExternalInventorySlots] = useState();
useEffect(() => { useEffect(() => {
async function setEcsStuff() { async function setEcsStuff() {
const {default: Components} = await import('@/ecs/components/index.js'); const {default: Components} = await import('@/ecs/components/index.js');
@ -333,8 +335,8 @@ function Ui({disconnected}) {
if (update.MainEntity) { if (update.MainEntity) {
setMainEntity(localMainEntity = id); setMainEntity(localMainEntity = id);
} }
if (localMainEntity === id) { if (update.Inventory) {
if (update.Inventory) { if (localMainEntity === id) {
setBufferSlot(entity.Inventory.item(0)); setBufferSlot(entity.Inventory.item(0));
const newInventorySlots = emptySlots(); const newInventorySlots = emptySlots();
for (let i = 1; i < 41; ++i) { for (let i = 1; i < 41; ++i) {
@ -342,6 +344,25 @@ function Ui({disconnected}) {
} }
setInventorySlots(newInventorySlots); setInventorySlots(newInventorySlots);
} }
else if (update.Inventory.slots) {
const newInventorySlots = Array(30).fill(undefined);
for (let i = 0; i < 30; ++i) {
newInventorySlots[i] = entity.Inventory.item(i);
}
setExternalInventory(entity.id)
setExternalInventorySlots(newInventorySlots);
setIsInventoryOpen(true);
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
}
else if (update.Inventory.closed) {
setExternalInventory();
setExternalInventorySlots();
}
}
if (localMainEntity === id) {
if (update.Wielder && 'activeSlot' in update.Wielder) { if (update.Wielder && 'activeSlot' in update.Wielder) {
setActiveSlot(update.Wielder.activeSlot); setActiveSlot(update.Wielder.activeSlot);
} }
@ -355,7 +376,7 @@ function Ui({disconnected}) {
setCamera({x, y}); setCamera({x, y});
} }
} }
}, [camera, ecs, mainEntity, scale]); }, [camera, ecs, hotbarHideHandle, mainEntity, scale]);
useEffect(() => { useEffect(() => {
function onContextMenu(event) { function onContextMenu(event) {
event.preventDefault(); event.preventDefault();
@ -507,7 +528,7 @@ function Ui({disconnected}) {
keepHotbarOpen(); keepHotbarOpen();
client.send({ client.send({
type: 'Action', type: 'Action',
payload: {type: 'swapSlots', value: [0, i + 1]}, payload: {type: 'swapSlots', value: [0, mainEntity, i + 1]},
}); });
}} }}
slots={inventorySlots.slice(0, 10)} slots={inventorySlots.slice(0, 10)}
@ -515,14 +536,25 @@ function Ui({disconnected}) {
<Bag <Bag
isInventoryOpen={isInventoryOpen} isInventoryOpen={isInventoryOpen}
onActivate={(i) => { onActivate={(i) => {
keepHotbarOpen();
client.send({ client.send({
type: 'Action', type: 'Action',
payload: {type: 'swapSlots', value: [0, i + 11]}, payload: {type: 'swapSlots', value: [0, mainEntity, i + 11]},
}); });
}} }}
slots={inventorySlots.slice(10)} slots={inventorySlots.slice(10, 20)}
/> />
{externalInventory && (
<External
isInventoryOpen={isInventoryOpen}
onActivate={(i) => {
client.send({
type: 'Action',
payload: {type: 'swapSlots', value: [0, externalInventory, i]},
});
}}
slots={externalInventorySlots}
/>
)}
<Entities <Entities
camera={camera} camera={camera}
scale={scale} scale={scale}

View File

@ -22,6 +22,7 @@ export default function createEcs(Ecs) {
'RunTickingPromises', 'RunTickingPromises',
'Water', 'Water',
'Interactions', 'Interactions',
'InventoryCloser',
]; ];
defaultSystems.forEach((defaultSystem) => { defaultSystems.forEach((defaultSystem) => {
const System = ecs.system(defaultSystem); const System = ecs.system(defaultSystem);

View File

@ -98,13 +98,14 @@ export default async function createHomestead(id) {
Interactive: { Interactive: {
interacting: 1, interacting: 1,
interactScript: ` interactScript: `
subject.Interlocutor.dialogue({ initiator.Player.openInventory = subject.Inventory;
body: "Sure, I'm a treasure chest. Probably. Do you really think that means you're about to get some treasure? Hah!", // subject.Interlocutor.dialogue({
monopolizer: true, // body: "Sure, I'm a treasure chest. Probably. Do you really think that means you're about to get some treasure? Hah!",
offset: {x: 0, y: -48}, // monopolizer: true,
origin: 'track', // offset: {x: 0, y: -48},
position: 'track', // origin: 'track',
}) // position: 'track',
// })
`, `,
}, },
Interlocutor: {}, Interlocutor: {},
@ -132,7 +133,7 @@ export default async function createHomestead(id) {
Sprite: { Sprite: {
anchorX: 0.5, anchorX: 0.5,
anchorY: 0.7, anchorY: 0.7,
source: '/assets/chest.json', source: '/assets/chest/chest.json',
}, },
Ticking: {}, Ticking: {},
VisibleAabb: {}, VisibleAabb: {},

View File

@ -201,7 +201,11 @@ export default class Engine {
} }
case 'swapSlots': { case 'swapSlots': {
if (!Controlled.locked) { if (!Controlled.locked) {
Inventory.swapSlots(...payload.value); const [l, other, r] = payload.value;
const {Inventory: OtherInventory} = ecs.get(other);
if (OtherInventory) {
Inventory.swapSlots(l, OtherInventory, r);
}
} }
break; break;
} }
@ -413,7 +417,6 @@ export default class Engine {
updateFor(connection) { updateFor(connection) {
const update = {}; const update = {};
const {entity, memory} = this.connectedPlayers.get(connection); const {entity, memory} = this.connectedPlayers.get(connection);
const mainEntityId = entity.id;
const ecs = this.ecses[entity.Ecs.path]; const ecs = this.ecses[entity.Ecs.path];
// Entities within half a screen offscreen. // Entities within half a screen offscreen.
const x0 = entity.Position.x - RESOLUTION.x; const x0 = entity.Position.x - RESOLUTION.x;
@ -429,17 +432,24 @@ export default class Engine {
nearby.add(master); nearby.add(master);
const lastNearby = new Set(memory.nearby.values()); const lastNearby = new Set(memory.nearby.values());
const firstUpdate = 0 === lastNearby.size; const firstUpdate = 0 === lastNearby.size;
for (const entity of nearby) { for (const nearbyEntity of nearby) {
const {id} = entity; const {id} = nearbyEntity;
lastNearby.delete(id); lastNearby.delete(id);
if (!memory.nearby.has(id)) { if (!memory.nearby.has(id)) {
update[id] = entity.toNet(); update[id] = nearbyEntity.toNet(entity);
if (mainEntityId === id) { if (entity.id === id) {
update[id].MainEntity = {}; update[id].MainEntity = {};
} }
} }
else if (ecs.diff[id]) { else if (ecs.diff[id]) {
update[id] = ecs.diff[id]; const nearbyEntityDiff = {};
for (const componentName in ecs.diff[id]) {
nearbyEntityDiff[componentName] = nearbyEntity[componentName].toNet(
entity,
ecs.diff[id][componentName],
);
}
update[id] = nearbyEntityDiff;
} }
memory.nearby.add(id); memory.nearby.add(id);
} }
@ -447,6 +457,7 @@ export default class Engine {
memory.nearby.delete(id); memory.nearby.delete(id);
update[id] = false; update[id] = false;
} }
entity.updateAttachments(update);
// Tile layer chunking // Tile layer chunking
const {TileLayers} = master; const {TileLayers} = master;
const {layers} = TileLayers; const {layers} = TileLayers;