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() {}
toNet() {
return Component.constructor.filterDefaults(this);
toNet(recipient, data) {
return data || Component.constructor.filterDefaults(this);
}
toJSON() {
return Component.constructor.filterDefaults(this);

View File

@ -137,12 +137,14 @@ export default class Inventory extends Component {
}
}
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]];
[$$items[l], slots[l]] = [$$items[r], slots[r]];
[$$items[r], slots[r]] = tmp;
if ($$items[r]) {
$$items[r].slot = r;
[$$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;
@ -189,18 +191,26 @@ export default class Inventory extends Component {
}
}
}
swapSlots(l, r) {
swapSlots(l, OtherInventory, r) {
const {$$items, slots} = this;
const {$$items: $$otherItems, slots: otherSlots} = OtherInventory;
const tmp = [$$items[l], slots[l]];
[$$items[l], slots[l]] = [$$items[r], slots[r]];
[$$items[r], slots[r]] = tmp;
[$$items[l], slots[l]] = [$$otherItems[r], otherSlots[r]];
[$$otherItems[r], otherSlots[r]] = tmp;
if (undefined === slots[l]) {
delete slots[l];
}
if (undefined === slots[r]) {
delete slots[r];
if (undefined === otherSlots[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';
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() {
return {x: this.scaleX, y: this.scaleY};
}
toNet() {
toNet(recipient, data) {
// eslint-disable-next-line no-unused-vars
const {elapsed, ...rest} = super.toNet();
const {elapsed, ...rest} = super.toNet(recipient, data);
return rest;
}
};

View File

@ -43,14 +43,23 @@ export default class EntityFactory {
};
}
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('', `
return {
${sorted.map((type) => `${type}: this.${type}.toJSON()`).join(', ')}
};
`);
Entity.prototype.toNet = new Function('', `
Entity.prototype.toNet = new Function('recipient', `
return {
${sorted.map((type) => `${type}: this.${type}.toNet()`).join(', ')}
${sorted.map((type) => `${type}: this.${type}.toNet(recipient)`).join(', ')}
};
`);
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}
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, left: '-440px'}}
>
<Grid
color="rgba(02, 02, 28, 0.6)"
columns={10}

View File

@ -1,8 +1,5 @@
.bag {
align-self: left;
display: inline-block;
left: 20px;
line-height: 0;
opacity: 1;
position: absolute;
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 (
<div className={styles.gridWrapper}>
<p className={styles.label}>{label}</p>
<p className={styles.label}>&nbsp;{label}&nbsp;</p>
<div
className={styles.grid}
style={{

View File

@ -1,27 +1,7 @@
.hotbar {
align-self: left;
display: inline-block;
left: 20px;
line-height: 0;
opacity: 1;
position: absolute;
top: 4px;
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 Dom from './dom/dom.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 Pixi from './pixi/pixi.jsx';
import Devtools from './devtools.jsx';
@ -26,7 +26,7 @@ const KEY_MAP = {
};
function emptySlots() {
return Array(40).fill(undefined);
return Array(50).fill(undefined);
}
const devEventsChannel = new EventEmitter();
@ -60,6 +60,8 @@ function Ui({disconnected}) {
const [hotbarIsHidden, setHotbarIsHidden] = useState(true);
const [hotbarHideHandle, setHotbarHideHandle] = useState();
const [isInventoryOpen, setIsInventoryOpen] = useState(false);
const [externalInventory, setExternalInventory] = useState();
const [externalInventorySlots, setExternalInventorySlots] = useState();
useEffect(() => {
async function setEcsStuff() {
const {default: Components} = await import('@/ecs/components/index.js');
@ -333,8 +335,8 @@ function Ui({disconnected}) {
if (update.MainEntity) {
setMainEntity(localMainEntity = id);
}
if (localMainEntity === id) {
if (update.Inventory) {
if (update.Inventory) {
if (localMainEntity === id) {
setBufferSlot(entity.Inventory.item(0));
const newInventorySlots = emptySlots();
for (let i = 1; i < 41; ++i) {
@ -342,6 +344,25 @@ function Ui({disconnected}) {
}
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) {
setActiveSlot(update.Wielder.activeSlot);
}
@ -355,7 +376,7 @@ function Ui({disconnected}) {
setCamera({x, y});
}
}
}, [camera, ecs, mainEntity, scale]);
}, [camera, ecs, hotbarHideHandle, mainEntity, scale]);
useEffect(() => {
function onContextMenu(event) {
event.preventDefault();
@ -507,7 +528,7 @@ function Ui({disconnected}) {
keepHotbarOpen();
client.send({
type: 'Action',
payload: {type: 'swapSlots', value: [0, i + 1]},
payload: {type: 'swapSlots', value: [0, mainEntity, i + 1]},
});
}}
slots={inventorySlots.slice(0, 10)}
@ -515,14 +536,25 @@ function Ui({disconnected}) {
<Bag
isInventoryOpen={isInventoryOpen}
onActivate={(i) => {
keepHotbarOpen();
client.send({
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
camera={camera}
scale={scale}

View File

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

View File

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

View File

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