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