Compare commits
18 Commits
29df4c3dcb
...
9853edec44
Author | SHA1 | Date | |
---|---|---|---|
|
9853edec44 | ||
|
6fd6565c8b | ||
|
89044ab2cf | ||
|
fc96f637ff | ||
|
a54aed9b89 | ||
|
51d8f333ed | ||
|
565c2a4095 | ||
|
e8ba520a5c | ||
|
b9428658b5 | ||
|
1e0d5d0b95 | ||
|
04a0230248 | ||
|
336ec04f66 | ||
|
de8ff49270 | ||
|
3bed694b2a | ||
|
2e7d1af189 | ||
|
503a7d2514 | ||
|
3b88ab8969 | ||
|
b912fee2e7 |
|
@ -1,47 +1,27 @@
|
|||
export default class Interpolator {
|
||||
duration = 0;
|
||||
latest;
|
||||
location = 0;
|
||||
penultimate;
|
||||
tracking = [];
|
||||
accept(state) {
|
||||
const packet = state;
|
||||
if ('Tick' !== packet.type) {
|
||||
let authoritative = [];
|
||||
let duration = 0;
|
||||
let last = performance.now();
|
||||
let latest = null;
|
||||
let location = 0;
|
||||
let penultimate;
|
||||
let tracking = [];
|
||||
|
||||
const interpolate = () => {
|
||||
const now = performance.now();
|
||||
const elapsed = (now - last) / 1000;
|
||||
last = now;
|
||||
if (authoritative.length > 0) {
|
||||
for (const packet of authoritative) {
|
||||
postMessage(packet);
|
||||
return;
|
||||
}
|
||||
this.penultimate = this.latest;
|
||||
this.latest = packet;
|
||||
this.tracking = [];
|
||||
if (this.penultimate) {
|
||||
this.duration = this.penultimate.payload.elapsed;
|
||||
const [from, to] = [this.penultimate.payload.ecs, this.latest.payload.ecs];
|
||||
for (const entityId in from) {
|
||||
for (const componentName in from[entityId]) {
|
||||
if (
|
||||
['Camera', 'Position'].includes(componentName)
|
||||
&& to[entityId]?.[componentName]
|
||||
) {
|
||||
this.tracking.push({
|
||||
entityId,
|
||||
componentName,
|
||||
properties: ['x', 'y'],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.location = 0;
|
||||
authoritative = [];
|
||||
}
|
||||
interpolate(elapsed) {
|
||||
if (0 === this.tracking.length) {
|
||||
return undefined;
|
||||
}
|
||||
this.location += elapsed;
|
||||
const fraction = Math.min(1, this.location / this.duration);
|
||||
const [from, to] = [this.penultimate.payload.ecs, this.latest.payload.ecs];
|
||||
if (tracking.length > 0) {
|
||||
location += elapsed;
|
||||
const fraction = location / duration;
|
||||
const [from, to] = [penultimate.payload.ecs, latest.payload.ecs];
|
||||
const interpolated = {};
|
||||
for (const {entityId, componentName, properties} of this.tracking) {
|
||||
for (const {entityId, componentName, properties} of tracking) {
|
||||
if (!interpolated[entityId]) {
|
||||
interpolated[entityId] = {};
|
||||
}
|
||||
|
@ -64,48 +44,76 @@ export default class Interpolator {
|
|||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'Tick',
|
||||
payload: {
|
||||
ecs: interpolated,
|
||||
elapsed,
|
||||
frame: this.penultimate.payload.frame + fraction,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let handle;
|
||||
const interpolator = new Interpolator();
|
||||
let last;
|
||||
|
||||
const interpolate = (now) => {
|
||||
const elapsed = (now - last) / 1000;
|
||||
last = now;
|
||||
const interpolated = interpolator.interpolate(elapsed);
|
||||
if (interpolated) {
|
||||
handle = requestAnimationFrame(interpolate);
|
||||
postMessage(interpolated);
|
||||
}
|
||||
else {
|
||||
handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
onmessage = async (event) => {
|
||||
interpolator.accept(event.data);
|
||||
if (interpolator.penultimate && 'Tick' === event.data.type) {
|
||||
postMessage({
|
||||
type: 'Tick',
|
||||
payload: {
|
||||
ecs: interpolator.penultimate.payload.ecs,
|
||||
elapsed: last ? (performance.now() - last) / 1000 : 0,
|
||||
frame: interpolator.penultimate.payload.frame,
|
||||
...penultimate.payload,
|
||||
ecs: interpolated,
|
||||
elapsed,
|
||||
frame: penultimate.payload.frame + fraction,
|
||||
},
|
||||
});
|
||||
if (!handle) {
|
||||
last = performance.now();
|
||||
handle = requestAnimationFrame(interpolate);
|
||||
}
|
||||
requestAnimationFrame(interpolate);
|
||||
}
|
||||
requestAnimationFrame(interpolate);
|
||||
|
||||
onmessage = async (event) => {
|
||||
const packet = event.data;
|
||||
switch (packet.type) {
|
||||
case 'EcsChange': {
|
||||
authoritative = [];
|
||||
latest = null;
|
||||
tracking = [];
|
||||
postMessage(packet);
|
||||
break;
|
||||
}
|
||||
case 'Tick': {
|
||||
penultimate = latest;
|
||||
latest = packet;
|
||||
if (penultimate) {
|
||||
duration = penultimate.payload.elapsed;
|
||||
location = 0;
|
||||
tracking = [];
|
||||
const [from, to] = [penultimate.payload.ecs, latest.payload.ecs];
|
||||
for (const entityId in from) {
|
||||
for (const componentName in from[entityId]) {
|
||||
if (
|
||||
['Camera', 'Position'].includes(componentName)
|
||||
&& to[entityId]?.[componentName]
|
||||
) {
|
||||
tracking.push({
|
||||
entityId,
|
||||
componentName,
|
||||
properties: ['x', 'y'],
|
||||
});
|
||||
}
|
||||
if (
|
||||
['Sprite'].includes(componentName)
|
||||
&& to[entityId]?.[componentName]
|
||||
) {
|
||||
tracking.push({
|
||||
entityId,
|
||||
componentName,
|
||||
properties: ['alpha', 'scaleX', 'scaleY'],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
authoritative.push({
|
||||
type: 'Tick',
|
||||
payload: {
|
||||
...penultimate.payload,
|
||||
elapsed: last ? (performance.now() - last) / 1000 : 0,
|
||||
},
|
||||
});
|
||||
last = performance.now();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
postMessage(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,7 +17,13 @@ export default class LocalClient extends Client {
|
|||
{type: 'module'},
|
||||
);
|
||||
this.interpolator.addEventListener('message', (event) => {
|
||||
this.accept(event.data);
|
||||
const packet = event.data;
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([1, packet]);
|
||||
}
|
||||
else {
|
||||
this.accept(packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (CLIENT_PREDICTION) {
|
||||
|
@ -35,12 +41,7 @@ export default class LocalClient extends Client {
|
|||
break;
|
||||
}
|
||||
case 1: {
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator.postMessage(packet);
|
||||
}
|
||||
else {
|
||||
this.accept(packet);
|
||||
}
|
||||
this.accept(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -54,12 +55,12 @@ export default class LocalClient extends Client {
|
|||
}
|
||||
this.throughput.$$down += event.data.byteLength;
|
||||
const packet = decode(event.data);
|
||||
if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([1, packet]);
|
||||
}
|
||||
else if (CLIENT_INTERPOLATION) {
|
||||
if (CLIENT_INTERPOLATION) {
|
||||
this.interpolator.postMessage(packet);
|
||||
}
|
||||
else if (CLIENT_PREDICTION) {
|
||||
this.predictor.postMessage([1, packet]);
|
||||
}
|
||||
else {
|
||||
this.accept(packet);
|
||||
}
|
||||
|
|
|
@ -27,17 +27,8 @@ const Flow = {
|
|||
DOWN: 1,
|
||||
};
|
||||
|
||||
const Stage = {
|
||||
UNACK: 0,
|
||||
ACK: 1,
|
||||
FINISHING: 2,
|
||||
FINISHED: 3,
|
||||
};
|
||||
|
||||
const actions = new Map();
|
||||
|
||||
let ecs = new PredictionEcs({Components, Systems});
|
||||
|
||||
let mainEntityId = 0;
|
||||
|
||||
function applyClientActions(elapsed) {
|
||||
|
@ -46,7 +37,7 @@ function applyClientActions(elapsed) {
|
|||
const {Controlled} = main;
|
||||
const finished = [];
|
||||
for (const [id, action] of actions) {
|
||||
if (Stage.UNACK === action.stage) {
|
||||
if (0 === action.finished && !action.ack) {
|
||||
if (!Controlled.locked) {
|
||||
switch (action.action.type) {
|
||||
case 'moveUp':
|
||||
|
@ -60,7 +51,7 @@ function applyClientActions(elapsed) {
|
|||
}
|
||||
action.steps.push(elapsed);
|
||||
}
|
||||
if (Stage.FINISHING === action.stage) {
|
||||
if (1 === action.finished) {
|
||||
if (!Controlled.locked) {
|
||||
switch (action.action.type) {
|
||||
case 'moveUp':
|
||||
|
@ -72,9 +63,9 @@ function applyClientActions(elapsed) {
|
|||
}
|
||||
}
|
||||
}
|
||||
action.stage = Stage.FINISHED;
|
||||
action.finished = 2;
|
||||
}
|
||||
if (Stage.FINISHED === action.stage) {
|
||||
if (action.ack && 2 === action.finished) {
|
||||
action.steps.shift();
|
||||
if (0 === action.steps.length) {
|
||||
finished.push(id);
|
||||
|
@ -113,13 +104,13 @@ onmessage = async (event) => {
|
|||
if (0 === packet.payload.value) {
|
||||
const ack = pending.get(packet.payload.type);
|
||||
const action = actions.get(ack);
|
||||
action.stage = Stage.FINISHING;
|
||||
action.finished = 1;
|
||||
pending.delete(packet.payload.type);
|
||||
}
|
||||
else {
|
||||
const tx = {
|
||||
action: packet.payload,
|
||||
stage: Stage.UNACK,
|
||||
ack: false,finished: 0,
|
||||
steps: [],
|
||||
};
|
||||
packet.payload.ack = Math.random();
|
||||
|
@ -139,11 +130,12 @@ onmessage = async (event) => {
|
|||
switch (packet.type) {
|
||||
case 'ActionAck': {
|
||||
const action = actions.get(packet.payload.ack);
|
||||
action.stage = Stage.ACK;
|
||||
action.ack = true;
|
||||
return;
|
||||
}
|
||||
case 'EcsChange': {
|
||||
ecs = new PredictionEcs({Components, Systems});
|
||||
mainEntityId = 0;
|
||||
break;
|
||||
}
|
||||
case 'Tick': {
|
||||
|
|
|
@ -18,6 +18,6 @@ export default class Time extends Component {
|
|||
};
|
||||
}
|
||||
static properties = {
|
||||
irlSeconds: {defaultValue: 10 * realSecondsPerGameHour, type: 'uint16'},
|
||||
irlSeconds: {defaultValue: 6 * realSecondsPerGameHour, type: 'uint16'},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,14 +14,14 @@ export default function Devtools({
|
|||
eventsChannel,
|
||||
}) {
|
||||
const client = useClient();
|
||||
const [mainEntity] = useMainEntity();
|
||||
const mainEntityRef = useMainEntity();
|
||||
const [mainEntityJson, setMainEntityJson] = useState('');
|
||||
const onEcsTick = useCallback((payload, ecs) => {
|
||||
if (!mainEntity) {
|
||||
if (!mainEntityRef.current) {
|
||||
return;
|
||||
}
|
||||
setMainEntityJson(JSON.stringify(ecs.get(mainEntity), null, 2));
|
||||
}, [mainEntity]);
|
||||
setMainEntityJson(JSON.stringify(ecs.get(mainEntityRef.current), null, 2));
|
||||
}, [mainEntityRef]);
|
||||
useEcsTick(onEcsTick);
|
||||
return (
|
||||
<div className={styles.devtools}>
|
||||
|
|
|
@ -17,12 +17,12 @@ export default function Tiles({eventsChannel}) {
|
|||
const [layer, setLayer] = useState(0);
|
||||
const [brush, setBrush] = useState(0);
|
||||
const [stamp, setStamp] = useState([]);
|
||||
const [ecs] = useEcs();
|
||||
const ecsRef = useEcs();
|
||||
useEffect(() => {
|
||||
if (!ecs) {
|
||||
if (!ecsRef.current) {
|
||||
return;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
const master = ecsRef.current.get(1);
|
||||
if (!master) {
|
||||
return;
|
||||
}
|
||||
|
@ -56,10 +56,10 @@ export default function Tiles({eventsChannel}) {
|
|||
eventsChannel.removeListener('click', onClick);
|
||||
};
|
||||
});
|
||||
if (!ecs) {
|
||||
if (!ecsRef.current) {
|
||||
return false;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
const master = ecsRef.current.get(1);
|
||||
if (!master) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import {memo} from 'react';
|
||||
|
||||
import styles from './bag.module.css';
|
||||
|
||||
import Grid from './grid.jsx';
|
||||
|
@ -5,7 +7,7 @@ import Grid from './grid.jsx';
|
|||
/**
|
||||
* Inventory bag. 10-40 slots of inventory.
|
||||
*/
|
||||
export default function Bag({
|
||||
function Bag({
|
||||
isInventoryOpen,
|
||||
onActivate,
|
||||
slots,
|
||||
|
@ -25,3 +27,5 @@ export default function Bag({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Bag);
|
||||
|
|
|
@ -8,7 +8,6 @@ export default function Chat({
|
|||
chatHistoryCaret,
|
||||
chatInputRef,
|
||||
chatMessages,
|
||||
onClose,
|
||||
message,
|
||||
pendingMessage,
|
||||
setChatHistoryCaret,
|
||||
|
@ -33,7 +32,6 @@ export default function Chat({
|
|||
<Input
|
||||
chatHistory={chatHistory}
|
||||
chatHistoryCaret={chatHistoryCaret}
|
||||
onClose={onClose}
|
||||
message={message}
|
||||
chatInputRef={chatInputRef}
|
||||
pendingMessage={pendingMessage}
|
||||
|
|
|
@ -59,5 +59,5 @@
|
|||
}
|
||||
|
||||
.damages {
|
||||
font-family: Joystix, 'Courier New', Courier, monospace;
|
||||
font-family: Joystix;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.caret {
|
||||
position: absolute;
|
||||
fill: #ffffff;
|
||||
stroke: #00000044;
|
||||
fill: #999999;
|
||||
stroke: #02023999;
|
||||
stroke-width: 2px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
|
|
|
@ -18,36 +18,63 @@ export default function Dialogue({
|
|||
const ref = useRef();
|
||||
const [dimensions, setDimensions] = useState({h: 0, w: 0});
|
||||
const [caret, setCaret] = useState(0);
|
||||
const [page, setPage] = useState(0);
|
||||
const radians = useRadians();
|
||||
const [pageLetters, setPageLetters] = useState([]);
|
||||
useEffect(() => {
|
||||
setCaret(0);
|
||||
setPageLetters(dialogue.letters.filter((letter) => letter.page === page));
|
||||
}, [dialogue, page]);
|
||||
useEffect(() => {
|
||||
if (0 === pageLetters.length) {
|
||||
return;
|
||||
}
|
||||
return dialogue.addSkipListener(() => {
|
||||
if (caret >= dialogue.letters.length - 1) {
|
||||
dialogue.onClose();
|
||||
// at end
|
||||
if (caret >= pageLetters.length - 1) {
|
||||
if (page < dialogue.pages - 1) {
|
||||
setPage((page) => page + 1);
|
||||
}
|
||||
else {
|
||||
dialogue.onClose();
|
||||
}
|
||||
}
|
||||
// skip to end
|
||||
else {
|
||||
setCaret(dialogue.letters.length - 1);
|
||||
setCaret(pageLetters.length - 1);
|
||||
}
|
||||
});
|
||||
}, [caret, dialogue]);
|
||||
}, [caret, dialogue, page, pageLetters]);
|
||||
useEffect(() => {
|
||||
const {params} = dialogue.letters[caret];
|
||||
if (0 === pageLetters.length) {
|
||||
return;
|
||||
}
|
||||
const {params} = pageLetters[caret];
|
||||
let handle;
|
||||
if (caret >= dialogue.letters.length - 1) {
|
||||
// start lingering timeout
|
||||
if (caret >= pageLetters.length - 1) {
|
||||
const {linger} = dialogue;
|
||||
if (!linger) {
|
||||
return;
|
||||
}
|
||||
handle = setTimeout(() => {
|
||||
dialogue.onClose();
|
||||
if (page < dialogue.pages - 1) {
|
||||
setPage((page) => page + 1);
|
||||
}
|
||||
else {
|
||||
dialogue.onClose();
|
||||
}
|
||||
}, linger * 1000);
|
||||
}
|
||||
else {
|
||||
// jump to next caret spot
|
||||
let jump = caret;
|
||||
while (0 === dialogue.letters[jump].params.rate.frequency && jump < dialogue.letters.length - 1) {
|
||||
while (0 === pageLetters[jump].params.rate.frequency && jump < pageLetters.length - 1) {
|
||||
jump += 1;
|
||||
}
|
||||
setCaret(jump);
|
||||
if (jump < dialogue.letters.length - 1) {
|
||||
if (jump < pageLetters.length - 1) {
|
||||
// wait for next jump
|
||||
handle = setTimeout(() => {
|
||||
setCaret(caret + 1);
|
||||
}, params.rate.frequency * 1000)
|
||||
|
@ -56,7 +83,7 @@ export default function Dialogue({
|
|||
return () => {
|
||||
clearTimeout(handle);
|
||||
}
|
||||
}, [caret, dialogue]);
|
||||
}, [caret, dialogue, page, pageLetters]);
|
||||
const updateDimensions = useCallback(() => {
|
||||
if (ref.current) {
|
||||
const {height, width} = ref.current.getBoundingClientRect();
|
||||
|
@ -65,8 +92,8 @@ export default function Dialogue({
|
|||
}, [domScale]);
|
||||
useAnimationFrame(updateDimensions);
|
||||
const localRender = useMemo(
|
||||
() => render(dialogue.letters, styles.letter),
|
||||
[dialogue.letters],
|
||||
() => render(pageLetters, styles.letter),
|
||||
[pageLetters],
|
||||
);
|
||||
let position = 'function' === typeof dialogue.position
|
||||
? dialogue.position()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.dialogue {
|
||||
background-color: #02023999;
|
||||
border: solid 3px white;
|
||||
border: solid 1px #999999;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
left: 0;
|
||||
|
|
|
@ -9,9 +9,9 @@ import Entity from './entity.jsx';
|
|||
|
||||
export default function Entities({
|
||||
camera,
|
||||
monopolizers,
|
||||
scale,
|
||||
setChatMessages,
|
||||
setMonopolizers,
|
||||
}) {
|
||||
const [entities, setEntities] = useState({});
|
||||
usePacket('EcsChange', async () => {
|
||||
|
@ -29,9 +29,9 @@ export default function Entities({
|
|||
deleting[id] = true;
|
||||
continue;
|
||||
}
|
||||
updating[id] = ecs.get(id);
|
||||
const {dialogue} = update.Interlocutor || {};
|
||||
if (dialogue) {
|
||||
updating[id] = ecs.get(id);
|
||||
const {dialogues} = updating[id].Interlocutor;
|
||||
for (const key in dialogue) {
|
||||
dialogues[key] = dialogue[key];
|
||||
|
@ -45,6 +45,7 @@ export default function Entities({
|
|||
dialogues[key].position = () => updating[id].Position;
|
||||
}
|
||||
dialogues[key].letters = parseLetters(dialogues[key].body);
|
||||
dialogues[key].pages = 1 + dialogues[key].letters.at(-1).page;
|
||||
setChatMessages((chatMessages) => ({
|
||||
[[id, key].join('-')]: dialogues[key].letters,
|
||||
...chatMessages,
|
||||
|
@ -64,7 +65,7 @@ export default function Entities({
|
|||
},
|
||||
};
|
||||
if (dialogues[key].monopolizer) {
|
||||
setMonopolizers((monopolizers) => [...monopolizers, monopolizer]);
|
||||
monopolizers.push(monopolizer);
|
||||
}
|
||||
dialogues[key].onClose = () => {
|
||||
setEntities((entities) => ({
|
||||
|
@ -72,14 +73,10 @@ export default function Entities({
|
|||
[id]: ecs.rebuild(id),
|
||||
}));
|
||||
if (dialogues[key].monopolizer) {
|
||||
setMonopolizers((monopolizers) => {
|
||||
const index = monopolizers.indexOf(monopolizer);
|
||||
if (-1 === index) {
|
||||
return monopolizers;
|
||||
}
|
||||
const index = monopolizers.indexOf(monopolizer);
|
||||
if (-1 !== index) {
|
||||
monopolizers.splice(index, 1);
|
||||
return [...monopolizers];
|
||||
});
|
||||
}
|
||||
}
|
||||
delete dialogues[key];
|
||||
};
|
||||
|
@ -95,7 +92,7 @@ export default function Entities({
|
|||
...updating,
|
||||
};
|
||||
});
|
||||
}, [setChatMessages, setMonopolizers]);
|
||||
}, [monopolizers, setChatMessages]);
|
||||
useEcsTick(onEcsTick);
|
||||
const renderables = [];
|
||||
for (const id in entities) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import {memo} from 'react';
|
||||
|
||||
import styles from './hotbar.module.css';
|
||||
import gridStyles from './grid.module.css';
|
||||
|
||||
|
@ -6,7 +8,7 @@ import Grid from './grid.jsx';
|
|||
/**
|
||||
* The hotbar. 10 slots of inventory with an active selection.
|
||||
*/
|
||||
export default function Hotbar({
|
||||
function Hotbar({
|
||||
active,
|
||||
hotbarIsHidden,
|
||||
onActivate,
|
||||
|
@ -33,3 +35,5 @@ export default function Hotbar({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Hotbar);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Container} from '@pixi/react';
|
||||
import {Container, useApp} from '@pixi/react';
|
||||
import {useCallback, useState} from 'react';
|
||||
|
||||
import {useEcsTick} from '@/react/context/ecs.js';
|
||||
|
@ -11,14 +11,14 @@ import TileLayer from './tile-layer.jsx';
|
|||
import Water from './water.jsx';
|
||||
|
||||
export default function Ecs({camera, monopolizers, particleWorker, scale}) {
|
||||
const [mainEntity] = useMainEntity();
|
||||
const app = useApp();
|
||||
const mainEntityRef = useMainEntity();
|
||||
const [layers, setLayers] = useState([]);
|
||||
const [hour, setHour] = useState(10);
|
||||
const [projected, setProjected] = useState([]);
|
||||
const [position, setPosition] = useState({x: 0, y: 0});
|
||||
const [water, setWater] = useState();
|
||||
const onEcsTick = useCallback((payload, ecs) => {
|
||||
const entity = ecs.get(mainEntity);
|
||||
const entity = ecs.get(mainEntityRef.current);
|
||||
for (const id in payload) {
|
||||
const update = payload[id];
|
||||
switch (id) {
|
||||
|
@ -28,7 +28,88 @@ export default function Ecs({camera, monopolizers, particleWorker, scale}) {
|
|||
setLayers(Object.values(master.TileLayers.$$layersProxies));
|
||||
}
|
||||
if (update.Time) {
|
||||
setHour(Math.round(ecs.get(1).Time.hour * 60) / 60);
|
||||
const {hour} = ecs.get(1).Time;
|
||||
let brightness, color;
|
||||
// 0 - 5 night
|
||||
// 21 - 0 night
|
||||
if (
|
||||
(hour >= 0 && hour < 5)
|
||||
|| hour >= 21
|
||||
) {
|
||||
brightness = 0.25;
|
||||
color = 0x2244cc;
|
||||
}
|
||||
// 5 - 6 blue
|
||||
// 20 - 21 blue
|
||||
if (
|
||||
(hour >= 5 && hour < 6)
|
||||
|| (hour >= 20 && hour < 21)
|
||||
) {
|
||||
const mag = hour < 20 ? (hour - 5) : (21 - hour);
|
||||
brightness = 0.25 + (0.75 * mag);
|
||||
color = 0x2244cc;
|
||||
}
|
||||
// 6 - 7 morning golden
|
||||
if (hour >= 6 && hour < 7) {
|
||||
if (hour < 6.25) {
|
||||
const [r, g, b] = [0xff - 0x22, 0xd3 - 0x44, 0x7a - 0xcc];
|
||||
const fraction = ((hour - 6) * 4);
|
||||
brightness = 1 + (fraction * 0.25);
|
||||
color = (
|
||||
(0x22 + (r * fraction)) << 16
|
||||
| (0x44 + (g * fraction)) << 8
|
||||
| (0xcc + (b * fraction))
|
||||
);
|
||||
}
|
||||
else if (hour >= 6.75) {
|
||||
const [r, g, b] = [0xff - 0xff, 0xd3 - 0xff, 0x7a - 0xff];
|
||||
const fraction = ((7 - hour) * 4);
|
||||
brightness = 1 + (fraction * 0.25);
|
||||
color = (
|
||||
(0xff + (r * fraction)) << 16
|
||||
| (0xff + (g * fraction)) << 8
|
||||
| (0xff + (b * fraction))
|
||||
);
|
||||
}
|
||||
else {
|
||||
brightness = 1.25;
|
||||
color = 0xffd37a;
|
||||
}
|
||||
}
|
||||
// 19 - 20 evening golden
|
||||
if (hour >= 19 && hour < 20) {
|
||||
if (hour < 19.25) {
|
||||
const [r, g, b] = [0xff - 0xff, 0xd3 - 0xff, 0x7a - 0xff];
|
||||
const fraction = ((hour - 19) * 4);
|
||||
brightness = 1 + (fraction * 0.25);
|
||||
color = (
|
||||
(0xff + (r * fraction)) << 16
|
||||
| (0xff + (g * fraction)) << 8
|
||||
| (0xff + (b * fraction))
|
||||
);
|
||||
}
|
||||
else if (hour >= 19.75) {
|
||||
const [r, g, b] = [0xff - 0x22, 0xd3 - 0x44, 0x7a - 0xcc];
|
||||
const fraction = ((20 - hour) * 4);
|
||||
brightness = 1 + (fraction * 0.25);
|
||||
color = (
|
||||
(0x22 + (r * fraction)) << 16
|
||||
| (0x44 + (g * fraction)) << 8
|
||||
| (0xcc + (b * fraction))
|
||||
);
|
||||
}
|
||||
else {
|
||||
brightness = 1.25;
|
||||
color = 0xffd37a;
|
||||
}
|
||||
}
|
||||
// 7 - 19 day
|
||||
if (hour >= 7 && hour < 19) {
|
||||
brightness = 1;
|
||||
color = 0xffffff;
|
||||
}
|
||||
app.ambientLight.brightness = brightness;
|
||||
app.ambientLight.color = color;
|
||||
}
|
||||
if (update.Water) {
|
||||
setWater(master.Water.water);
|
||||
|
@ -42,7 +123,7 @@ export default function Ecs({camera, monopolizers, particleWorker, scale}) {
|
|||
setPosition(Position.toJSON());
|
||||
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.quantize(4)));
|
||||
}
|
||||
}, [mainEntity]);
|
||||
}, [app.ambientLight, mainEntityRef]);
|
||||
useEcsTick(onEcsTick);
|
||||
return (
|
||||
<Container
|
||||
|
|
|
@ -13,12 +13,12 @@ import Entity from './entity.js';
|
|||
|
||||
export default function Entities({monopolizers, particleWorker}) {
|
||||
const [debug] = useDebug();
|
||||
const [ecs] = useEcs();
|
||||
const ecsRef = useEcs();
|
||||
const containerRef = useRef();
|
||||
const latestTick = useRef();
|
||||
const entities = useRef({});
|
||||
const pool = useRef([]);
|
||||
const [mainEntity] = useMainEntity();
|
||||
const mainEntityRef = useMainEntity();
|
||||
const radians = useRadians();
|
||||
const willInteractWith = useRef(0);
|
||||
const [interactionFilters] = useState([
|
||||
|
@ -28,7 +28,7 @@ export default function Entities({monopolizers, particleWorker}) {
|
|||
const pulse = (Math.cos(radians / 4) + 1) * 0.5;
|
||||
interactionFilters[0].brightness = (pulse * 0.75) + 1;
|
||||
interactionFilters[1].outerStrength = pulse * 0.5;
|
||||
const updateEntities = useCallback((payload) => {
|
||||
const updateEntities = useCallback((payload, ecs) => {
|
||||
for (const id in payload) {
|
||||
if ('1' === id) {
|
||||
continue;
|
||||
|
@ -43,14 +43,15 @@ export default function Entities({monopolizers, particleWorker}) {
|
|||
entities.current[id] = pool.current.length > 0
|
||||
? pool.current.pop()
|
||||
: new Entity();
|
||||
entities.current[id].reset(ecs.get(id), debug);
|
||||
if (mainEntity === id) {
|
||||
entities.current[id].reset(ecs.get(id));
|
||||
entities.current[id].setDebug(false);
|
||||
if (mainEntityRef.current === id) {
|
||||
entities.current[id].setMainEntity();
|
||||
}
|
||||
}
|
||||
entities.current[id].update(payload[id], containerRef.current);
|
||||
}
|
||||
}, [debug, ecs, mainEntity])
|
||||
}, [mainEntityRef])
|
||||
useEffect(() => {
|
||||
if (0 === pool.current.length) {
|
||||
for (let i = 0; i < 1000; ++i) {
|
||||
|
@ -69,25 +70,25 @@ export default function Entities({monopolizers, particleWorker}) {
|
|||
}
|
||||
entities.current = {};
|
||||
});
|
||||
const onEcsTickEntities = useCallback((payload) => {
|
||||
updateEntities(payload);
|
||||
}, [updateEntities]);
|
||||
useEcsTick(onEcsTickEntities);
|
||||
useEcsTick(updateEntities);
|
||||
useEffect(() => {
|
||||
if (!ecs || !particleWorker) {
|
||||
if (!particleWorker) {
|
||||
return;
|
||||
}
|
||||
async function onMessage(diff) {
|
||||
if (!ecsRef.current) {
|
||||
return;
|
||||
}
|
||||
latestTick.current = Promise.resolve(latestTick.current).then(async () => {
|
||||
await ecs.apply(diff.data);
|
||||
updateEntities(diff.data);
|
||||
await ecsRef.current.apply(diff.data);
|
||||
updateEntities(diff.data, ecsRef.current);
|
||||
});
|
||||
}
|
||||
particleWorker.addEventListener('message', onMessage);
|
||||
return () => {
|
||||
particleWorker.removeEventListener('message', onMessage);
|
||||
};
|
||||
}, [ecs, particleWorker, updateEntities]);
|
||||
}, [ecsRef, particleWorker, updateEntities]);
|
||||
const onEcsTickParticles = useCallback((payload) => {
|
||||
for (const id in payload) {
|
||||
const update = payload[id];
|
||||
|
@ -100,7 +101,7 @@ export default function Entities({monopolizers, particleWorker}) {
|
|||
}, [particleWorker]);
|
||||
useEcsTick(onEcsTickParticles);
|
||||
const onEcsTickInteractions = useCallback((payload, ecs) => {
|
||||
const main = ecs.get(mainEntity);
|
||||
const main = ecs.get(mainEntityRef.current);
|
||||
if (main) {
|
||||
if (willInteractWith.current !== main.Interacts.willInteractWith) {
|
||||
if (entities.current[willInteractWith.current]) {
|
||||
|
@ -115,7 +116,7 @@ export default function Entities({monopolizers, particleWorker}) {
|
|||
: [];
|
||||
}
|
||||
}
|
||||
}, [interactionFilters, mainEntity, monopolizers]);
|
||||
}, [interactionFilters, mainEntityRef, monopolizers]);
|
||||
useEcsTick(onEcsTickInteractions);
|
||||
return (
|
||||
<Container
|
||||
|
|
|
@ -43,13 +43,12 @@ export default class Entity {
|
|||
this.debug.parent.removeChild(this.debug);
|
||||
this.attached = false;
|
||||
}
|
||||
reset(entity, debug) {
|
||||
reset(entity) {
|
||||
this.entity = entity;
|
||||
if (this.light) {
|
||||
this.container.removeChild(this.light);
|
||||
this.light = undefined;
|
||||
}
|
||||
this.setDebug(debug);
|
||||
this.isMainEntity = false;
|
||||
if (this.interactionAabb) {
|
||||
this.debug.removeChild(this.interactionAabb);
|
||||
|
@ -97,6 +96,7 @@ export default class Entity {
|
|||
0xffffff - 0x2244cc,
|
||||
0,
|
||||
);
|
||||
this.container.addChild(this.light);
|
||||
}
|
||||
this.light.brightness = Light.brightness;
|
||||
}
|
||||
|
@ -178,14 +178,16 @@ export default class Entity {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (this.entity.Collider) {
|
||||
this.colliderAabb.redraw(this.entity.Collider.aabb);
|
||||
}
|
||||
if (VisibleAabb) {
|
||||
this.visibleAabb.redraw(this.entity.VisibleAabb);
|
||||
}
|
||||
if (this.isMainEntity) {
|
||||
this.interactionAabb.redraw(this.entity.Interacts.aabb());
|
||||
if (this.debug.alpha) {
|
||||
if (this.entity.Collider) {
|
||||
this.colliderAabb.redraw(this.entity.Collider.aabb);
|
||||
}
|
||||
if (VisibleAabb) {
|
||||
this.visibleAabb.redraw(this.entity.VisibleAabb);
|
||||
}
|
||||
if (this.isMainEntity) {
|
||||
this.interactionAabb.redraw(this.entity.Interacts.aabb());
|
||||
}
|
||||
}
|
||||
if (this.attached || !container) {
|
||||
return;
|
||||
|
|
|
@ -24,9 +24,8 @@ export const ApplicationStageLights = {
|
|||
init: function() {
|
||||
const {stage} = this;
|
||||
deferredLighting.addToStage(stage);
|
||||
// const ambientLight = new AmbientLight(0x2244cc, 0.25);
|
||||
const ambientLight = new AmbientLight(0xffffff, 1);
|
||||
stage.addChild(ambientLight);
|
||||
this.ambientLight = new AmbientLight(0xffffff, 1);
|
||||
stage.addChild(this.ambientLight);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ const KEY_MAP = {
|
|||
};
|
||||
|
||||
function emptySlots() {
|
||||
return Array(50).fill(undefined);
|
||||
return Array(10).fill(undefined);
|
||||
}
|
||||
|
||||
const devEventsChannel = new EventEmitter();
|
||||
|
@ -37,20 +37,21 @@ function Ui({disconnected}) {
|
|||
const chatInputRef = useRef();
|
||||
const latestTick = useRef();
|
||||
const gameRef = useRef();
|
||||
const [mainEntity, setMainEntity] = useMainEntity();
|
||||
const [debug, setDebug] = useDebug();
|
||||
const [ecs, setEcs] = useEcs();
|
||||
const mainEntityRef = useMainEntity();
|
||||
const [, setDebug] = useDebug();
|
||||
const ecsRef = useEcs();
|
||||
const [showDisconnected, setShowDisconnected] = useState(false);
|
||||
const [bufferSlot, setBufferSlot] = useState();
|
||||
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
|
||||
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
|
||||
const [camera, setCamera] = useState({x: 0, y: 0});
|
||||
const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
|
||||
const [inventorySlots, setInventorySlots] = useState(emptySlots());
|
||||
const [activeSlot, setActiveSlot] = useState(0);
|
||||
const [scale, setScale] = useState(2);
|
||||
const [Components, setComponents] = useState();
|
||||
const [Systems, setSystems] = useState();
|
||||
const [monopolizers, setMonopolizers] = useState([]);
|
||||
const monopolizers = useRef([]);
|
||||
const [message, setMessage] = useState('');
|
||||
const [chatIsOpen, setChatIsOpen] = useState(false);
|
||||
const [chatHistory, setChatHistory] = useState([]);
|
||||
|
@ -58,7 +59,7 @@ function Ui({disconnected}) {
|
|||
const [chatMessages, setChatMessages] = useState({});
|
||||
const [pendingMessage, setPendingMessage] = useState('');
|
||||
const [hotbarIsHidden, setHotbarIsHidden] = useState(true);
|
||||
const [hotbarHideHandle, setHotbarHideHandle] = useState();
|
||||
const hotbarHideHandle = useRef();
|
||||
const [isInventoryOpen, setIsInventoryOpen] = useState(false);
|
||||
const [externalInventory, setExternalInventory] = useState();
|
||||
const [externalInventorySlots, setExternalInventorySlots] = useState();
|
||||
|
@ -67,8 +68,8 @@ function Ui({disconnected}) {
|
|||
class ClientEcsPerf extends ClientEcs {
|
||||
markChange() {}
|
||||
}
|
||||
setEcs(new ClientEcsPerf({Components, Systems}));
|
||||
}, [Components, Systems, setEcs]);
|
||||
ecsRef.current = new ClientEcsPerf({Components, Systems});
|
||||
}, [ecsRef, Components, Systems]);
|
||||
useEffect(() => {
|
||||
async function setEcsStuff() {
|
||||
const {default: Components} = await import('@/ecs/components/index.js');
|
||||
|
@ -95,201 +96,137 @@ function Ui({disconnected}) {
|
|||
clearTimeout(handle);
|
||||
};
|
||||
}, [disconnected]);
|
||||
useEffect(() => {
|
||||
return addKeyListener(document.body, ({type, payload}) => {
|
||||
if (chatInputRef.current) {
|
||||
chatInputRef.current.focus();
|
||||
}
|
||||
if (chatIsOpen) {
|
||||
return;
|
||||
}
|
||||
let actionPayload;
|
||||
switch (payload) {
|
||||
case 'w': {
|
||||
actionPayload = {type: 'moveUp', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
actionPayload = {type: 'moveLeft', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
actionPayload = {type: 'moveDown', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
actionPayload = {type: 'moveRight', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (actionPayload) {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: actionPayload,
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [
|
||||
chatIsOpen,
|
||||
client,
|
||||
]);
|
||||
const keepHotbarOpen = useCallback(() => {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
if (hotbarHideHandle.current) {
|
||||
clearTimeout(hotbarHideHandle.current);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
hotbarHideHandle.current = setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}, 4000);
|
||||
}
|
||||
}, [hotbarHideHandle, isInventoryOpen]);
|
||||
}, [isInventoryOpen]);
|
||||
useEffect(() => {
|
||||
return addKeyListener(document.body, ({event, type, payload}) => {
|
||||
if ('Escape' === payload && 'keyDown' === type && chatIsOpen) {
|
||||
setChatIsOpen(false);
|
||||
return addKeyListener(document.body, ({event, payload, type}) => {
|
||||
const actionMap = {
|
||||
'd': {type: 'moveRight'},
|
||||
's': {type: 'moveDown'},
|
||||
'a': {type: 'moveLeft'},
|
||||
'w': {type: 'moveUp'},
|
||||
'e': {type: 'interact'},
|
||||
'1': {type: 'changeSlot', payload: 1},
|
||||
'2': {type: 'changeSlot', payload: 2},
|
||||
'3': {type: 'changeSlot', payload: 3},
|
||||
'4': {type: 'changeSlot', payload: 4},
|
||||
'5': {type: 'changeSlot', payload: 5},
|
||||
'6': {type: 'changeSlot', payload: 6},
|
||||
'7': {type: 'changeSlot', payload: 7},
|
||||
'8': {type: 'changeSlot', payload: 8},
|
||||
'9': {type: 'changeSlot', payload: 9},
|
||||
'0': {type: 'changeSlot', payload: 10},
|
||||
'`': {type: 'openInventory'},
|
||||
'-': {type: 'zoomOut'},
|
||||
'+': {type: 'zoomIn'},
|
||||
'=': {type: 'zoomIn'},
|
||||
'F3': {type: 'debug'},
|
||||
'F4': {type: 'devtools'},
|
||||
'Enter': {type: 'openChat'},
|
||||
'Escape': {type: 'closeChat'},
|
||||
};
|
||||
const action = actionMap[payload];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
if (chatInputRef.current) {
|
||||
if ('closeChat' === action.type && 'keyDown' === type) {
|
||||
setChatIsOpen(false);
|
||||
return;
|
||||
}
|
||||
chatInputRef.current.focus();
|
||||
}
|
||||
if (chatIsOpen) {
|
||||
return;
|
||||
}
|
||||
let actionPayload;
|
||||
switch (payload) {
|
||||
case '-':
|
||||
switch (action.type) {
|
||||
case 'interact': {
|
||||
if ('keyDown' === type) {
|
||||
setScale((scale) => scale > 1 ? scale - 1 : 1);
|
||||
}
|
||||
break;
|
||||
case '=':
|
||||
case '+':
|
||||
if ('keyDown' === type) {
|
||||
setScale((scale) => scale < 4 ? Math.floor(scale + 1) : 4);
|
||||
}
|
||||
break;
|
||||
case 'F3': {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if ('keyDown' === type) {
|
||||
setDebug(!debug);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'F4': {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if ('keyDown' === type) {
|
||||
setDevtoolsIsOpen(!devtoolsIsOpen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '`': {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if ('keyDown' === type) {
|
||||
if (isInventoryOpen) {
|
||||
setHotbarIsHidden(true);
|
||||
}
|
||||
else {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
}
|
||||
setIsInventoryOpen(!isInventoryOpen);
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Enter': {
|
||||
if ('keyDown' === type) {
|
||||
setChatIsOpen(true);
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'e': {
|
||||
if (KEY_MAP[type]) {
|
||||
if (monopolizers.length > 0) {
|
||||
monopolizers[0].trigger();
|
||||
if (monopolizers.current.length > 0) {
|
||||
monopolizers.current[0].trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
actionPayload = {type: 'interact', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case '1': {
|
||||
case 'moveRight':
|
||||
case 'moveDown':
|
||||
case 'moveLeft':
|
||||
case 'moveUp': {
|
||||
actionPayload = {type: action.type, value: 'keyDown' === type ? 1 : 0};
|
||||
break;
|
||||
}
|
||||
case 'changeSlot': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 1};
|
||||
actionPayload = {type: 'changeSlot', value: action.payload};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '2': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 2};
|
||||
case 'openInventory': {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
if ('keyDown' === type) {
|
||||
setIsInventoryOpen((isInventoryOpen) => {
|
||||
if (isInventoryOpen) {
|
||||
setHotbarIsHidden(true);
|
||||
}
|
||||
else {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle.current) {
|
||||
clearTimeout(hotbarHideHandle.current);
|
||||
}
|
||||
}
|
||||
return !isInventoryOpen;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
case '3': {
|
||||
case 'zoomIn': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 3};
|
||||
setScale((scale) => scale < 4 ? Math.floor(scale + 1) : 4);
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
case '4': {
|
||||
case 'zoomOut': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 4};
|
||||
setScale((scale) => scale > 1 ? scale - 1 : 1);
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
case '5': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 5};
|
||||
case 'debug': {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
if ('keyDown' === type) {
|
||||
setDebug((debug) => !debug);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case '6': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 6};
|
||||
case 'devtools': {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
if ('keyDown' === type) {
|
||||
setDevtoolsIsOpen((devtoolsIsOpen) => !devtoolsIsOpen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case '7': {
|
||||
case 'openChat': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 7};
|
||||
setChatIsOpen(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '8': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 8};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '9': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 9};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '0': {
|
||||
if ('keyDown' === type) {
|
||||
keepHotbarOpen();
|
||||
actionPayload = {type: 'changeSlot', value: 10};
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (actionPayload) {
|
||||
|
@ -299,36 +236,24 @@ function Ui({disconnected}) {
|
|||
});
|
||||
}
|
||||
});
|
||||
}, [
|
||||
chatIsOpen,
|
||||
client,
|
||||
debug,
|
||||
devtoolsIsOpen,
|
||||
hotbarHideHandle,
|
||||
isInventoryOpen,
|
||||
keepHotbarOpen,
|
||||
monopolizers,
|
||||
setDebug,
|
||||
setScale,
|
||||
]);
|
||||
}, [client, keepHotbarOpen, setDebug]);
|
||||
const onEcsChangePacket = useCallback(() => {
|
||||
refreshEcs();
|
||||
setMainEntity(undefined);
|
||||
setMonopolizers([]);
|
||||
}, [refreshEcs, setMainEntity]);
|
||||
mainEntityRef.current = undefined;
|
||||
monopolizers.current = [];
|
||||
}, [refreshEcs, mainEntityRef]);
|
||||
usePacket('EcsChange', onEcsChangePacket);
|
||||
const onTickPacket = useCallback(async (payload, client) => {
|
||||
if (0 === Object.keys(payload.ecs).length) {
|
||||
return;
|
||||
}
|
||||
latestTick.current = Promise.resolve(latestTick.current).then(async () => {
|
||||
await ecs.apply(payload.ecs);
|
||||
await ecsRef.current.apply(payload.ecs);
|
||||
client.emitter.invoke(':Ecs', payload.ecs);
|
||||
});
|
||||
}, [ecs]);
|
||||
}, [ecsRef]);
|
||||
usePacket('Tick', onTickPacket);
|
||||
const onEcsTick = useCallback((payload, ecs) => {
|
||||
let localMainEntity = mainEntity;
|
||||
for (const id in payload) {
|
||||
const entity = ecs.get(id);
|
||||
const update = payload[id];
|
||||
|
@ -336,14 +261,21 @@ function Ui({disconnected}) {
|
|||
continue;
|
||||
}
|
||||
if (update.MainEntity) {
|
||||
setMainEntity(localMainEntity = id);
|
||||
mainEntityRef.current = id;
|
||||
}
|
||||
if (update.Inventory) {
|
||||
if (localMainEntity === id) {
|
||||
if (mainEntityRef.current === id) {
|
||||
setBufferSlot(entity.Inventory.item(0));
|
||||
setHotbarSlots(() => {
|
||||
const newHotbarSlots = [];
|
||||
for (let i = 1; i < 11; ++i) {
|
||||
newHotbarSlots.push(entity.Inventory.item(i));
|
||||
}
|
||||
return newHotbarSlots;
|
||||
});
|
||||
const newInventorySlots = emptySlots();
|
||||
for (let i = 1; i < 41; ++i) {
|
||||
newInventorySlots[i - 1] = entity.Inventory.item(i);
|
||||
for (let i = 11; i < 41; ++i) {
|
||||
newInventorySlots[i - 11] = entity.Inventory.item(i);
|
||||
}
|
||||
setInventorySlots(newInventorySlots);
|
||||
}
|
||||
|
@ -356,8 +288,8 @@ function Ui({disconnected}) {
|
|||
setExternalInventorySlots(newInventorySlots);
|
||||
setIsInventoryOpen(true);
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
if (hotbarHideHandle.current) {
|
||||
clearTimeout(hotbarHideHandle.current);
|
||||
}
|
||||
}
|
||||
else if (update.Inventory.closed) {
|
||||
|
@ -365,13 +297,13 @@ function Ui({disconnected}) {
|
|||
setExternalInventorySlots();
|
||||
}
|
||||
}
|
||||
if (localMainEntity === id) {
|
||||
if (mainEntityRef.current === id) {
|
||||
if (update.Wielder && 'activeSlot' in update.Wielder) {
|
||||
setActiveSlot(update.Wielder.activeSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [hotbarHideHandle, mainEntity, setMainEntity]);
|
||||
}, [mainEntityRef]);
|
||||
useEcsTick(onEcsTick);
|
||||
const onEcsTickParticles = useCallback((payload, ecs) => {
|
||||
if (!('1' in payload) || particleWorker) {
|
||||
|
@ -415,15 +347,15 @@ function Ui({disconnected}) {
|
|||
}, []);
|
||||
useEcsTick(onEcsTickAabbs);
|
||||
const onEcsTickCamera = useCallback((payload, ecs) => {
|
||||
if (mainEntity) {
|
||||
const mainEntityEntity = ecs.get(mainEntity);
|
||||
if (mainEntityRef.current) {
|
||||
const mainEntityEntity = ecs.get(mainEntityRef.current);
|
||||
const x = Math.round((mainEntityEntity.Camera.x * scale) - RESOLUTION.x / 2);
|
||||
const y = Math.round((mainEntityEntity.Camera.y * scale) - RESOLUTION.y / 2);
|
||||
if (x !== camera.x || y !== camera.y) {
|
||||
setCamera({x, y});
|
||||
}
|
||||
}
|
||||
}, [camera, mainEntity, scale]);
|
||||
}, [camera, mainEntityRef, scale]);
|
||||
useEcsTick(onEcsTickCamera);
|
||||
useEffect(() => {
|
||||
function onContextMenu(event) {
|
||||
|
@ -435,15 +367,15 @@ function Ui({disconnected}) {
|
|||
};
|
||||
}, []);
|
||||
const computePosition = useCallback(({clientX, clientY}) => {
|
||||
if (!gameRef.current || !mainEntity) {
|
||||
if (!gameRef.current || !mainEntityRef.current) {
|
||||
return;
|
||||
}
|
||||
const {top, left, width} = gameRef.current.getBoundingClientRect();
|
||||
const master = ecs.get(1);
|
||||
const master = ecsRef.current.get(1);
|
||||
if (!master) {
|
||||
return;
|
||||
}
|
||||
const {Camera} = ecs.get(mainEntity);
|
||||
const {Camera} = ecsRef.current.get(mainEntityRef.current);
|
||||
const size = width / RESOLUTION.x;
|
||||
const camera = {
|
||||
x: ((Camera.x * scale) - (RESOLUTION.x / 2)),
|
||||
|
@ -454,10 +386,23 @@ function Ui({disconnected}) {
|
|||
y: (((clientY - top) / size) + camera.y) / scale,
|
||||
};
|
||||
}, [
|
||||
ecs,
|
||||
mainEntity,
|
||||
ecsRef,
|
||||
mainEntityRef,
|
||||
scale,
|
||||
]);
|
||||
const hotbarOnActivate = useCallback((i) => {
|
||||
keepHotbarOpen();
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 1]},
|
||||
});
|
||||
}, [client, keepHotbarOpen, mainEntityRef]);
|
||||
const bagOnActivate = useCallback((i) => {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 11]},
|
||||
});
|
||||
}, [client, mainEntityRef]);
|
||||
return (
|
||||
<div
|
||||
className={styles.ui}
|
||||
|
@ -479,7 +424,7 @@ function Ui({disconnected}) {
|
|||
<div
|
||||
className={[styles.game, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}
|
||||
onMouseDown={(event) => {
|
||||
if (chatIsOpen) {
|
||||
if (chatInputRef.current) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
@ -500,8 +445,8 @@ function Ui({disconnected}) {
|
|||
}
|
||||
break;
|
||||
case 2:
|
||||
if (monopolizers.length > 0) {
|
||||
monopolizers[0].trigger();
|
||||
if (monopolizers.current.length > 0) {
|
||||
monopolizers.current[0].trigger();
|
||||
break;
|
||||
}
|
||||
client.send({
|
||||
|
@ -512,7 +457,7 @@ function Ui({disconnected}) {
|
|||
}
|
||||
}}
|
||||
onMouseUp={(event) => {
|
||||
if (chatIsOpen) {
|
||||
if (chatInputRef.current) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
@ -534,18 +479,18 @@ function Ui({disconnected}) {
|
|||
event.preventDefault();
|
||||
}}
|
||||
onWheel={(event) => {
|
||||
if (chatIsOpen) {
|
||||
if (chatInputRef.current) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
if (hotbarHideHandle.current) {
|
||||
clearTimeout(hotbarHideHandle.current);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
hotbarHideHandle.current = setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}, 4000);
|
||||
}
|
||||
if (event.deltaY > 0) {
|
||||
client.send({
|
||||
|
@ -564,7 +509,7 @@ function Ui({disconnected}) {
|
|||
>
|
||||
<Pixi
|
||||
camera={camera}
|
||||
monopolizers={monopolizers}
|
||||
monopolizers={monopolizers.current}
|
||||
particleWorker={particleWorker}
|
||||
scale={scale}
|
||||
/>
|
||||
|
@ -572,24 +517,13 @@ function Ui({disconnected}) {
|
|||
<HotBar
|
||||
active={activeSlot}
|
||||
hotbarIsHidden={hotbarIsHidden}
|
||||
onActivate={(i) => {
|
||||
keepHotbarOpen();
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntity, i + 1]},
|
||||
});
|
||||
}}
|
||||
slots={inventorySlots.slice(0, 10)}
|
||||
onActivate={hotbarOnActivate}
|
||||
slots={hotbarSlots}
|
||||
/>
|
||||
<Bag
|
||||
isInventoryOpen={isInventoryOpen}
|
||||
onActivate={(i) => {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntity, i + 11]},
|
||||
});
|
||||
}}
|
||||
slots={inventorySlots.slice(10, 20)}
|
||||
onActivate={bagOnActivate}
|
||||
slots={inventorySlots}
|
||||
/>
|
||||
{externalInventory && (
|
||||
<External
|
||||
|
@ -607,7 +541,7 @@ function Ui({disconnected}) {
|
|||
camera={camera}
|
||||
scale={scale}
|
||||
setChatMessages={setChatMessages}
|
||||
setMonopolizers={setMonopolizers}
|
||||
monopolizers={monopolizers.current}
|
||||
/>
|
||||
{chatIsOpen && (
|
||||
<Chat
|
||||
|
@ -621,9 +555,6 @@ function Ui({disconnected}) {
|
|||
setChatHistoryCaret={setChatHistoryCaret}
|
||||
setMessage={setMessage}
|
||||
setPendingMessage={setPendingMessage}
|
||||
onClose={() => {
|
||||
setChatIsOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showDisconnected && (
|
||||
|
|
|
@ -11,12 +11,12 @@ export function useEcs() {
|
|||
}
|
||||
|
||||
export function useEcsTick(fn) {
|
||||
const [ecs] = useEcs();
|
||||
const ecsRef = useEcs();
|
||||
const memo = useCallback((payload) => {
|
||||
if (!ecs) {
|
||||
if (!ecsRef.current) {
|
||||
return;
|
||||
}
|
||||
fn(payload, ecs);
|
||||
}, [ecs, fn]);
|
||||
fn(payload, ecsRef.current);
|
||||
}, [ecsRef, fn]);
|
||||
usePacket(':Ecs', memo);
|
||||
}
|
|
@ -29,10 +29,10 @@ body {
|
|||
|
||||
@font-face {
|
||||
font-family: "Cookbook";
|
||||
src: url("/assets/fonts/Cookbook.woff") format("woff");
|
||||
src: url("/assets/fonts/Cookbook.woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Joystix";
|
||||
src: url("/assets/fonts/Joystix.ttf") format("ttf");
|
||||
src: url("/assets/fonts/Joystix.woff");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {json} from "@remix-run/node";
|
||||
import {useCallback, useEffect, useState} from 'react';
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {useOutletContext, useParams} from 'react-router-dom';
|
||||
|
||||
import Ui from '@/react/components/ui.jsx';
|
||||
|
@ -22,10 +22,9 @@ export default function PlaySpecific() {
|
|||
const Client = useOutletContext();
|
||||
const assetsTuple = useState({});
|
||||
const [client, setClient] = useState();
|
||||
const mainEntityTuple = useState();
|
||||
const setMainEntity = mainEntityTuple[1];
|
||||
const mainEntityRef = useRef();
|
||||
const debugTuple = useState(false);
|
||||
const ecsTuple = useState();
|
||||
const ecsRef = useRef();
|
||||
const [disconnected, setDisconnected] = useState(false);
|
||||
const params = useParams();
|
||||
const [type, url] = params['*'].split('/');
|
||||
|
@ -109,7 +108,7 @@ export default function PlaySpecific() {
|
|||
if (!client || !disconnected) {
|
||||
return;
|
||||
}
|
||||
setMainEntity(undefined);
|
||||
mainEntityRef.current = undefined;
|
||||
async function reconnect() {
|
||||
await client.connect(url);
|
||||
}
|
||||
|
@ -118,7 +117,7 @@ export default function PlaySpecific() {
|
|||
return () => {
|
||||
clearInterval(handle);
|
||||
};
|
||||
}, [client, disconnected, setMainEntity, url]);
|
||||
}, [client, disconnected, mainEntityRef, url]);
|
||||
// useEffect(() => {
|
||||
// let source = true;
|
||||
// async function play() {
|
||||
|
@ -144,8 +143,8 @@ export default function PlaySpecific() {
|
|||
// }, [])
|
||||
return (
|
||||
<ClientContext.Provider value={client}>
|
||||
<MainEntityContext.Provider value={mainEntityTuple}>
|
||||
<EcsContext.Provider value={ecsTuple}>
|
||||
<MainEntityContext.Provider value={mainEntityRef}>
|
||||
<EcsContext.Provider value={ecsRef}>
|
||||
<DebugContext.Provider value={debugTuple}>
|
||||
<AssetsContext.Provider value={assetsTuple}>
|
||||
<RadiansContext.Provider value={radians}>
|
||||
|
|
|
@ -289,11 +289,7 @@ export default async function createHomestead(id) {
|
|||
interacting: 1,
|
||||
interactScript: `
|
||||
const lines = [
|
||||
'mrowwr',
|
||||
'p<shake>rrr</shake>o<wave>wwwww</wave>',
|
||||
'mew<rate frequency={0.5}> </rate>mew!',
|
||||
'me<wave>wwwww</wave>',
|
||||
'\\\\*pu<shake>rrrrr</shake>\\\\*',
|
||||
'Mind your own business, buddy.\\n\\ner, I mean, <shake>MEEHHHHHH</shake>',
|
||||
];
|
||||
const line = lines[Math.floor(Math.random() * lines.length)];
|
||||
subject.Interlocutor.dialogue({
|
||||
|
|
|
@ -27,6 +27,7 @@ const textEncoder = new TextEncoder();
|
|||
|
||||
export default class Engine {
|
||||
|
||||
ackingActions = new Map();
|
||||
connectedPlayers = new Map();
|
||||
ecses = {};
|
||||
frame = 0;
|
||||
|
@ -89,7 +90,13 @@ export default class Engine {
|
|||
// dump entity state with updates for the transition
|
||||
const dumped = {
|
||||
...entity.toJSON(),
|
||||
Controlled: entity.Controlled,
|
||||
// manually transfer control
|
||||
Controlled: {
|
||||
moveUp: entity.Controlled.moveUp,
|
||||
moveRight: entity.Controlled.moveRight,
|
||||
moveDown: entity.Controlled.moveDown,
|
||||
moveLeft: entity.Controlled.moveLeft,
|
||||
},
|
||||
Ecs: {path},
|
||||
...updates,
|
||||
};
|
||||
|
@ -103,7 +110,7 @@ export default class Engine {
|
|||
Promise.all(promises).then(async () => {
|
||||
// recreate the entity in the new ECS and again associate it with the connection
|
||||
connectedPlayer.entity = engine.ecses[path].get(await engine.ecses[path].create(dumped));
|
||||
connectedPlayer.entity.Player.id = id
|
||||
connectedPlayer.entity.Player.id = id;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -250,6 +257,15 @@ export default class Engine {
|
|||
}
|
||||
}
|
||||
if (payload.ack) {
|
||||
if (!this.ackingActions.has(connection)) {
|
||||
this.ackingActions.set(connection, []);
|
||||
}
|
||||
this.ackingActions.get(connection).push({
|
||||
type: 'ActionAck',
|
||||
payload: {
|
||||
ack: payload.ack,
|
||||
},
|
||||
})
|
||||
this.server.send(
|
||||
connection,
|
||||
{
|
||||
|
@ -303,6 +319,7 @@ export default class Engine {
|
|||
if (!connectedPlayer) {
|
||||
return;
|
||||
}
|
||||
this.ackingActions.delete(connection);
|
||||
this.connectedPlayers.delete(connection);
|
||||
this.incomingActions.delete(connection);
|
||||
const {entity, heartbeat, id} = connectedPlayer;
|
||||
|
@ -445,6 +462,12 @@ export default class Engine {
|
|||
|
||||
update(elapsed) {
|
||||
for (const [connection, {entity}] of this.connectedPlayers) {
|
||||
if (this.ackingActions.has(connection)) {
|
||||
for (const ack of this.ackingActions.get(connection)) {
|
||||
this.server.send(connection, ack);
|
||||
}
|
||||
this.ackingActions.delete(connection);
|
||||
}
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -17,4 +17,4 @@ export const SERVER_LATENCY = 0;
|
|||
|
||||
export const TPS = 60;
|
||||
|
||||
export const UPS = 15;
|
||||
export const UPS = 30;
|
||||
|
|
|
@ -27,6 +27,7 @@ function computeParams(ancestors) {
|
|||
|
||||
export function parseLetters(source) {
|
||||
let letters = [];
|
||||
let page = 0;
|
||||
try {
|
||||
const tree = parser.parse(source);
|
||||
tree.dialogue = {
|
||||
|
@ -43,6 +44,10 @@ export function parseLetters(source) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'paragraph': {
|
||||
page += 1;
|
||||
break;
|
||||
}
|
||||
case 'text': {
|
||||
const params = computeParams(ancestors);
|
||||
const split = node.value.split('');
|
||||
|
@ -51,7 +56,12 @@ export function parseLetters(source) {
|
|||
for (const name in params) {
|
||||
indices[name] = i + params[name].length;
|
||||
}
|
||||
letters.push({character: split[i], indices, params});
|
||||
letters.push({
|
||||
character: split[i],
|
||||
indices,
|
||||
page: page - 1,
|
||||
params,
|
||||
});
|
||||
}
|
||||
for (const name in params) {
|
||||
params[name].length += split.length;
|
||||
|
|
|
@ -48,10 +48,20 @@ export default class Script {
|
|||
Math: MathUtil,
|
||||
Promise: PromiseUtil,
|
||||
transition,
|
||||
wait: (seconds) => (
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, seconds * 1000);
|
||||
})
|
||||
wait: (seconds = 0) => (
|
||||
new PromiseUtil.Ticker(
|
||||
(resolve) => {
|
||||
if (0 === seconds) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
(elapsed, resolve) => {
|
||||
seconds -= elapsed;
|
||||
if (seconds <= 0) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
11
package-lock.json
generated
11
package-lock.json
generated
|
@ -23,6 +23,7 @@
|
|||
"acorn": "^8.12.0",
|
||||
"alea": "^1.0.1",
|
||||
"compression": "^1.7.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"express": "^4.18.2",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"isbot": "^4.1.0",
|
||||
|
@ -53,7 +54,6 @@
|
|||
"@storybook/test": "^8.1.6",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"@vitest/coverage-v8": "^1.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
|
@ -8989,7 +8989,6 @@
|
|||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
|
@ -9007,7 +9006,6 @@
|
|||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
|
@ -9021,7 +9019,6 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
|
@ -12405,8 +12402,7 @@
|
|||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/ismobilejs": {
|
||||
"version": "1.1.1",
|
||||
|
@ -16868,7 +16864,6 @@
|
|||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
@ -19208,7 +19203,6 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
|
@ -19220,7 +19214,6 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"acorn": "^8.12.0",
|
||||
"alea": "^1.0.1",
|
||||
"compression": "^1.7.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"express": "^4.18.2",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"isbot": "^4.1.0",
|
||||
|
@ -60,7 +61,6 @@
|
|||
"@storybook/test": "^8.1.6",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"@vitest/coverage-v8": "^1.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
|
|
Binary file not shown.
BIN
public/assets/fonts/Joystix.woff
Normal file
BIN
public/assets/fonts/Joystix.woff
Normal file
Binary file not shown.
|
@ -87,5 +87,5 @@ while (shots.length > 0) {
|
|||
for (let i = 0; i < destroying.length; ++i) {
|
||||
shots.splice(shots.indexOf(destroying[i]), 1);
|
||||
}
|
||||
await wait(0);
|
||||
await wait();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user