Compare commits
10 Commits
f971295825
...
3eb94f2ef8
Author | SHA1 | Date | |
---|---|---|---|
|
3eb94f2ef8 | ||
|
cbe5f473a6 | ||
|
49f46b4b00 | ||
|
a0943c8bbb | ||
|
80c1f78c6a | ||
|
52f19b1d89 | ||
|
3d7b0fc14e | ||
|
c45d24b909 | ||
|
f197658199 | ||
|
fb1a286a86 |
|
@ -3,6 +3,32 @@ import Component from '@/ecs/component.js';
|
|||
export default class Controlled extends Component {
|
||||
instanceFromSchema() {
|
||||
return class ControlledInstance extends super.instanceFromSchema() {
|
||||
directionMove(direction) {
|
||||
const x = Math.cos(direction);
|
||||
if (x > 0) {
|
||||
this.moveLeft = 0;
|
||||
this.moveRight = x;
|
||||
}
|
||||
else {
|
||||
this.moveLeft = -x;
|
||||
this.moveRight = 0;
|
||||
}
|
||||
const y = Math.sin(direction);
|
||||
if (y > 0) {
|
||||
this.moveUp = 0;
|
||||
this.moveDown = y;
|
||||
}
|
||||
else {
|
||||
this.moveUp = -y;
|
||||
this.moveDown = 0;
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
this.moveRight = 0;
|
||||
this.moveDown = 0;
|
||||
this.moveLeft = 0;
|
||||
this.moveUp = 0;
|
||||
}
|
||||
toJSON() {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import {HALF_PI, TAU} from '@/util/math.js';
|
||||
|
||||
export default class Direction extends Component {
|
||||
instanceFromSchema() {
|
||||
return class DirectionInstance extends super.instanceFromSchema() {
|
||||
static quantize(d, n) {
|
||||
return Math.floor(((d + (TAU / (n * 2))) % TAU) / (TAU / n));
|
||||
}
|
||||
quantize(n) {
|
||||
return this.constructor.quantize(this.direction, n);
|
||||
}
|
||||
};
|
||||
}
|
||||
static properties = {
|
||||
direction: {type: 'uint8'},
|
||||
direction: {defaultValue: HALF_PI, type: 'float32'},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Emitter extends Component {
|
||||
mergeDiff(original, update) {
|
||||
const merged = {};
|
||||
if (update.emit) {
|
||||
merged.emit = {
|
||||
...original.emit,
|
||||
...update.emit,
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
instanceFromSchema() {
|
||||
const Component = this;
|
||||
return class EmitterInstance extends super.instanceFromSchema() {
|
||||
|
@ -21,4 +11,14 @@ export default class Emitter extends Component {
|
|||
}
|
||||
};
|
||||
}
|
||||
mergeDiff(original, update) {
|
||||
const merged = {};
|
||||
if (update.emit) {
|
||||
merged.emit = {
|
||||
...original.emit,
|
||||
...update.emit,
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
}
|
|
@ -8,18 +8,19 @@ export default class Interacts extends Component {
|
|||
const {Direction, Position} = ecs.get(this.entity);
|
||||
let x0 = Position.x - 8;
|
||||
let y0 = Position.y - 8;
|
||||
if (0 === Direction.direction) {
|
||||
y0 -= 12
|
||||
}
|
||||
if (1 === Direction.direction) {
|
||||
const direction = Direction.quantize(4);
|
||||
if (0 === direction) {
|
||||
x0 += 12
|
||||
}
|
||||
if (2 === Direction.direction) {
|
||||
if (1 === direction) {
|
||||
y0 += 12
|
||||
}
|
||||
if (3 === Direction.direction) {
|
||||
if (2 === direction) {
|
||||
x0 -= 12
|
||||
}
|
||||
if (3 === direction) {
|
||||
y0 -= 12
|
||||
}
|
||||
return {x0, x1: x0 + 15, y0, y1: y0 + 15};
|
||||
}
|
||||
toJSON() {
|
||||
|
|
|
@ -33,21 +33,21 @@ class ItemProxy {
|
|||
let startY = position.y;
|
||||
switch (direction) {
|
||||
case 0:
|
||||
startX += projection.distance[1];
|
||||
startY -= projection.distance[0];
|
||||
break;
|
||||
case 1:
|
||||
startX += projection.distance[0];
|
||||
startY += projection.distance[1];
|
||||
break;
|
||||
case 2:
|
||||
case 1:
|
||||
startX -= projection.distance[1];
|
||||
startY += projection.distance[0];
|
||||
break;
|
||||
case 3:
|
||||
case 2:
|
||||
startX -= projection.distance[0];
|
||||
startY -= projection.distance[1];
|
||||
break;
|
||||
case 3:
|
||||
startX += projection.distance[1];
|
||||
startY -= projection.distance[0];
|
||||
break;
|
||||
}
|
||||
const projected = [];
|
||||
for (const row in projection.grid) {
|
||||
|
@ -58,17 +58,17 @@ class ItemProxy {
|
|||
let axe;
|
||||
switch (direction) {
|
||||
case 0:
|
||||
axe = [column, row];
|
||||
break;
|
||||
case 1:
|
||||
axe = [-row, column];
|
||||
break;
|
||||
case 2:
|
||||
case 1:
|
||||
axe = [-column, -row];
|
||||
break;
|
||||
case 3:
|
||||
case 2:
|
||||
axe = [row, -column];
|
||||
break;
|
||||
case 3:
|
||||
axe = [column, row];
|
||||
break;
|
||||
}
|
||||
const x = startX + parseInt(axe[0]);
|
||||
const y = startY + parseInt(axe[1]);
|
||||
|
@ -91,6 +91,9 @@ class ItemProxy {
|
|||
get icon() {
|
||||
return this.json.icon;
|
||||
}
|
||||
get label() {
|
||||
return this.json.label;
|
||||
}
|
||||
get projection() {
|
||||
return this.json.projection;
|
||||
}
|
||||
|
@ -191,6 +194,12 @@ export default class Inventory extends Component {
|
|||
const tmp = [$$items[l], slots[l]];
|
||||
[$$items[l], slots[l]] = [$$items[r], slots[r]];
|
||||
[$$items[r], slots[r]] = tmp;
|
||||
if (undefined === slots[l]) {
|
||||
delete slots[l];
|
||||
}
|
||||
if (undefined === slots[r]) {
|
||||
delete slots[r];
|
||||
}
|
||||
Component.markChange(this.entity, 'swapped', [[l, r]]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {System} from '@/ecs/index.js';
|
||||
import {HALF_PI} from '@/util/math.js';
|
||||
|
||||
export default class ControlDirection extends System {
|
||||
|
||||
|
@ -9,16 +10,16 @@ export default class ControlDirection extends System {
|
|||
continue;
|
||||
}
|
||||
if (moveUp > 0) {
|
||||
Direction.direction = 0;
|
||||
Direction.direction = HALF_PI * 3;
|
||||
}
|
||||
if (moveDown > 0) {
|
||||
Direction.direction = 2;
|
||||
Direction.direction = HALF_PI * 1;
|
||||
}
|
||||
if (moveLeft > 0) {
|
||||
Direction.direction = 3;
|
||||
Direction.direction = HALF_PI * 2;
|
||||
}
|
||||
if (moveRight > 0) {
|
||||
Direction.direction = 1;
|
||||
Direction.direction = HALF_PI * 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,12 @@ export default class SpriteDirection extends System {
|
|||
}
|
||||
if (Direction) {
|
||||
const name = {
|
||||
0: 'up',
|
||||
1: 'right',
|
||||
2: 'down',
|
||||
3: 'left',
|
||||
0: 'right',
|
||||
1: 'down',
|
||||
2: 'left',
|
||||
3: 'up',
|
||||
};
|
||||
parts.push(name[Direction.direction]);
|
||||
parts.push(name[Direction.quantize(4)]);
|
||||
}
|
||||
if (parts.length > 0) {
|
||||
Sprite.animation = parts.join(':');
|
||||
|
|
|
@ -20,11 +20,11 @@ export default function Tiles({eventsChannel}) {
|
|||
const [ecs] = useEcs();
|
||||
useEffect(() => {
|
||||
if (!ecs) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
if (!master) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
const {TileLayers} = master;
|
||||
const {area, tileSize} = TileLayers.layer(0);
|
||||
|
|
54
app/react/components/dom/bag.jsx
Normal file
54
app/react/components/dom/bag.jsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import styles from './bag.module.css';
|
||||
import Slot from './slot.jsx';
|
||||
|
||||
/**
|
||||
* Inventory bag. 10-40 slots of inventory.
|
||||
*/
|
||||
export default function Bag({
|
||||
isInventoryOpen,
|
||||
slots,
|
||||
}) {
|
||||
const Slots = slots.map((slot, i) => (
|
||||
<div
|
||||
className={
|
||||
[styles.slotWrapper]
|
||||
.filter(Boolean).join(' ')
|
||||
}
|
||||
key={i}
|
||||
>
|
||||
<Slot
|
||||
icon={slot?.icon}
|
||||
// onMouseDown={(event) => {
|
||||
// onActivate(i)
|
||||
// event.stopPropagation();
|
||||
// }}
|
||||
// onMouseUp={(event) => {
|
||||
// event.stopPropagation();
|
||||
// }}
|
||||
// onDragOver={(event) => {
|
||||
// event.preventDefault();
|
||||
// }}
|
||||
// onDragStart={(event) => {
|
||||
// if (!slot) {
|
||||
// event.preventDefault();
|
||||
// }
|
||||
// event.dataTransfer.setData('silphius/item', i);
|
||||
// onActivate(i);
|
||||
// }}
|
||||
// onDrop={(event) => {
|
||||
// event.preventDefault();
|
||||
// onActivate(i);
|
||||
// }}
|
||||
qty={slot?.qty}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div
|
||||
className={styles.bag}
|
||||
style={isInventoryOpen ? {transition: 'none'} : {left: '-440px'}}
|
||||
>
|
||||
{Slots}
|
||||
</div>
|
||||
);
|
||||
}
|
31
app/react/components/dom/bag.module.css
Normal file
31
app/react/components/dom/bag.module.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
.bag {
|
||||
align-self: left;
|
||||
--border: calc(var(--unit) * 3px);
|
||||
background-color: rgba(02, 02, 28, 0.6);
|
||||
border: var(--border) solid #444444;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
left: calc(var(--unit) * 20px);
|
||||
line-height: 0;
|
||||
position: absolute;
|
||||
top: calc(var(--unit) * 90px);
|
||||
transition: left 150ms;
|
||||
max-width: 430.5px;
|
||||
}
|
||||
|
||||
.slotWrapper {
|
||||
border: var(--border) solid #999999;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
padding: 0;
|
||||
&:not(:nth-child(10n)) {
|
||||
border-right: none;
|
||||
}
|
||||
&:not(:nth-last-of-type(-n+10)) {
|
||||
border-bottom: none;
|
||||
}
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
|
@ -9,8 +9,3 @@
|
|||
user-select: text;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Cookbook";
|
||||
src: url("/assets/fonts/Cookbook.woff") format("woff");
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
background-color: #00000044;
|
||||
border: 1px solid #333333;
|
||||
color: #ffffff;
|
||||
font-family: "Cookbook";
|
||||
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||
font-size: 16px;
|
||||
margin: 4px;
|
||||
padding: 0;
|
||||
|
|
|
@ -97,7 +97,7 @@ export default function Dialogue({
|
|||
position.y * scale - camera.y,
|
||||
RESOLUTION.y - bounds.y * scale - 16,
|
||||
),
|
||||
bounds.y * scale + 88,
|
||||
bounds.y * scale + 16,
|
||||
);
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -14,6 +14,9 @@ export default function Dialogues({camera, dialogues, scale}) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
if (0 === elements.length) {
|
||||
return false;
|
||||
}
|
||||
return <div className={styles.dialogues}>{elements}</div>;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,3 @@
|
|||
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Cookbook";
|
||||
src: url("/assets/fonts/Cookbook.woff") format("woff");
|
||||
}
|
||||
|
|
|
@ -4,7 +4,12 @@ import Slot from './slot.jsx';
|
|||
/**
|
||||
* The hotbar. 10 slots of inventory with an active selection.
|
||||
*/
|
||||
export default function Hotbar({active, onActivate, slots}) {
|
||||
export default function Hotbar({
|
||||
active,
|
||||
hotbarIsHidden,
|
||||
onActivate,
|
||||
slots,
|
||||
}) {
|
||||
const Slots = slots.map((slot, i) => (
|
||||
<div
|
||||
className={
|
||||
|
@ -43,7 +48,9 @@ export default function Hotbar({active, onActivate, slots}) {
|
|||
return (
|
||||
<div
|
||||
className={styles.hotbar}
|
||||
style={hotbarIsHidden ? {top: '-50px'} : {transition: 'none'}}
|
||||
>
|
||||
<p className={styles.label}>{slots[active] && slots[active].label}</p>
|
||||
{Slots}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
.hotbar {
|
||||
align-self: center;
|
||||
align-self: left;
|
||||
--border: calc(var(--unit) * 3px);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(02, 02, 57, 0.6);
|
||||
border: var(--border) solid #444444;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
left: calc(var(--unit) * 20px);
|
||||
line-height: 0;
|
||||
position: absolute;
|
||||
top: calc(var(--unit) * 25px);
|
||||
top: calc(var(--unit) * 20px);
|
||||
transition: top 150ms;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||
left: 50%;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
text-shadow:
|
||||
0px -1px 0px black,
|
||||
1px 0px 0px black,
|
||||
0px 1px 0px black,
|
||||
-1px 0px 0px black
|
||||
;
|
||||
top: -17.5px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.slotWrapper {
|
||||
|
|
|
@ -108,7 +108,7 @@ export default function Ecs({applyFilters, camera, monopolizers, scale}) {
|
|||
if (entity) {
|
||||
const {Direction, Position, Wielder} = entity;
|
||||
setPosition(Position.toJSON());
|
||||
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.direction));
|
||||
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.quantize(4)));
|
||||
}
|
||||
}, [ecs, mainEntity, scale]);
|
||||
useEffect(() => {
|
||||
|
|
|
@ -7,6 +7,8 @@ import {Sprite} from '@pixi/sprite';
|
|||
|
||||
import {useRadians} from '@/react/context/radians.js';
|
||||
|
||||
import {deferredLighting} from './lights.js';
|
||||
|
||||
const tileSize = {x: 16, y: 16};
|
||||
const radius = 9;
|
||||
|
||||
|
@ -68,6 +70,7 @@ const TargetingGridInternal = PixiComponent('TargetingGrid', {
|
|||
container.mask = area;
|
||||
const top = new Container();
|
||||
top.addChild(container, area);
|
||||
top.parentGroup = deferredLighting.diffuseGroup;
|
||||
return top;
|
||||
},
|
||||
applyProps: ({children: [container]}, oldProps, {x, y, radians}) => {
|
||||
|
|
|
@ -11,6 +11,7 @@ import addKeyListener from './add-key-listener.js';
|
|||
import ClientEcs from './client-ecs.js';
|
||||
import Disconnected from './dom/disconnected.jsx';
|
||||
import Chat from './dom/chat/chat.jsx';
|
||||
import Bag from './dom/bag.jsx';
|
||||
import Dom from './dom/dom.jsx';
|
||||
import Entities from './dom/entities.jsx';
|
||||
import HotBar from './dom/hotbar.jsx';
|
||||
|
@ -18,6 +19,11 @@ import Pixi from './pixi/pixi.jsx';
|
|||
import Devtools from './devtools.jsx';
|
||||
import styles from './ui.module.css';
|
||||
|
||||
const KEY_MAP = {
|
||||
keyDown: 1,
|
||||
keyUp: 0,
|
||||
};
|
||||
|
||||
function emptySlots() {
|
||||
return Array(10).fill(undefined);
|
||||
}
|
||||
|
@ -50,6 +56,9 @@ function Ui({disconnected}) {
|
|||
const [chatHistoryCaret, setChatHistoryCaret] = useState(-1);
|
||||
const [chatMessages, setChatMessages] = useState({});
|
||||
const [pendingMessage, setPendingMessage] = useState('');
|
||||
const [hotbarIsHidden, setHotbarIsHidden] = useState(true);
|
||||
const [hotbarHideHandle, setHotbarHideHandle] = useState();
|
||||
const [isInventoryOpen, setIsInventoryOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
async function setEcsStuff() {
|
||||
const {default: Components} = await import('@/ecs/components/index.js');
|
||||
|
@ -76,6 +85,44 @@ 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,
|
||||
]);
|
||||
useEffect(() => {
|
||||
return addKeyListener(document.body, ({event, type, payload}) => {
|
||||
if ('Escape' === payload && 'keyDown' === type && chatIsOpen) {
|
||||
|
@ -88,10 +135,6 @@ function Ui({disconnected}) {
|
|||
if (chatIsOpen) {
|
||||
return;
|
||||
}
|
||||
const KEY_MAP = {
|
||||
keyDown: 1,
|
||||
keyUp: 0,
|
||||
};
|
||||
let actionPayload;
|
||||
switch (payload) {
|
||||
case '-':
|
||||
|
@ -123,26 +166,25 @@ function Ui({disconnected}) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
case ' ': {
|
||||
actionPayload = {type: 'use', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 'Tab': {
|
||||
if ('keyDown' === type) {
|
||||
if (isInventoryOpen) {
|
||||
setHotbarIsHidden(true);
|
||||
}
|
||||
else {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
}
|
||||
setIsInventoryOpen(!isInventoryOpen);
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Enter': {
|
||||
if ('keyDown' === type) {
|
||||
setChatIsOpen(true);
|
||||
|
@ -161,60 +203,150 @@ function Ui({disconnected}) {
|
|||
}
|
||||
case '1': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 1};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '2': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 2};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '3': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 3};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '4': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 4};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '5': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 5};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '6': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 6};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '7': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 7};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '8': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 8};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '9': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 9};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '0': {
|
||||
if ('keyDown' === type) {
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
actionPayload = {type: 'changeSlot', value: 10};
|
||||
}
|
||||
break;
|
||||
|
@ -227,7 +359,17 @@ function Ui({disconnected}) {
|
|||
});
|
||||
}
|
||||
});
|
||||
}, [chatIsOpen, client, debug, devtoolsIsOpen, monopolizers, setDebug, setScale]);
|
||||
}, [
|
||||
chatIsOpen,
|
||||
client,
|
||||
debug,
|
||||
devtoolsIsOpen,
|
||||
hotbarHideHandle,
|
||||
isInventoryOpen,
|
||||
monopolizers,
|
||||
setDebug,
|
||||
setScale,
|
||||
]);
|
||||
usePacket('EcsChange', async () => {
|
||||
setEcs(new ClientEcs({Components, Systems}));
|
||||
setMainEntity(undefined);
|
||||
|
@ -387,6 +529,15 @@ function Ui({disconnected}) {
|
|||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (!isInventoryOpen) {
|
||||
setHotbarIsHidden(false);
|
||||
if (hotbarHideHandle) {
|
||||
clearTimeout(hotbarHideHandle);
|
||||
}
|
||||
setHotbarHideHandle(setTimeout(() => {
|
||||
setHotbarIsHidden(true);
|
||||
}, 4000));
|
||||
}
|
||||
if (event.deltaY > 0) {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
|
@ -411,6 +562,7 @@ function Ui({disconnected}) {
|
|||
<Dom>
|
||||
<HotBar
|
||||
active={activeSlot}
|
||||
hotbarIsHidden={hotbarIsHidden}
|
||||
onActivate={(i) => {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
|
@ -419,6 +571,10 @@ function Ui({disconnected}) {
|
|||
}}
|
||||
slots={hotbarSlots}
|
||||
/>
|
||||
<Bag
|
||||
isInventoryOpen={isInventoryOpen}
|
||||
slots={Array(30).fill(undefined)}
|
||||
/>
|
||||
<Entities
|
||||
camera={camera}
|
||||
scale={scale}
|
||||
|
|
|
@ -26,3 +26,8 @@ body {
|
|||
border-radius: 20px;
|
||||
border: 3px solid #333;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Cookbook";
|
||||
src: url("/assets/fonts/Cookbook.woff") format("woff");
|
||||
}
|
||||
|
|
|
@ -116,7 +116,8 @@ export default async function createHomestead(id) {
|
|||
],
|
||||
},
|
||||
Controlled: {},
|
||||
Direction: {direction: 2},
|
||||
Direction: {},
|
||||
Emitter: {},
|
||||
Forces: {},
|
||||
Interactive: {
|
||||
interacting: 1,
|
||||
|
@ -167,16 +168,18 @@ export default async function createHomestead(id) {
|
|||
},
|
||||
],
|
||||
collisionStartScript: `
|
||||
ecs.switchEcs(
|
||||
other,
|
||||
'town',
|
||||
{
|
||||
Position: {
|
||||
x: 940,
|
||||
y: 480,
|
||||
if (other.Player) {
|
||||
ecs.switchEcs(
|
||||
other,
|
||||
'town',
|
||||
{
|
||||
Position: {
|
||||
x: 940,
|
||||
y: 480,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
);
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export default async function createPlayer(id) {
|
|||
],
|
||||
},
|
||||
Controlled: {},
|
||||
Direction: {direction: 2},
|
||||
Direction: {},
|
||||
Ecs: {path: ['homesteads', `${id}`].join('/')},
|
||||
Emitter: {},
|
||||
Forces: {},
|
||||
|
|
|
@ -38,16 +38,18 @@ export default async function createTown() {
|
|||
},
|
||||
],
|
||||
collisionStartScript: `
|
||||
ecs.switchEcs(
|
||||
other,
|
||||
['homesteads', '0'].join('/'),
|
||||
{
|
||||
Position: {
|
||||
x: 20,
|
||||
y: 438,
|
||||
if (other.Player) {
|
||||
ecs.switchEcs(
|
||||
other,
|
||||
['homesteads', other.Player.id].join('/'),
|
||||
{
|
||||
Position: {
|
||||
x: 20,
|
||||
y: 438,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
);
|
||||
}
|
||||
`,
|
||||
},
|
||||
Position: {x: 952, y: 480},
|
||||
|
|
|
@ -63,6 +63,7 @@ export default class Engine {
|
|||
if (entity !== connectedPlayer.entity) {
|
||||
continue;
|
||||
}
|
||||
const {id} = entity.Player;
|
||||
// remove entity link to connection to start queueing actions and pause updates
|
||||
delete connectedPlayer.entity;
|
||||
// forget previous state
|
||||
|
@ -93,6 +94,7 @@ export default class Engine {
|
|||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -226,11 +228,12 @@ export default class Engine {
|
|||
await this.loadEcs(entityJson.Ecs.path);
|
||||
}
|
||||
const ecs = this.ecses[entityJson.Ecs.path];
|
||||
const entity = await ecs.create(entityJson);
|
||||
const entity = ecs.get(await ecs.create(entityJson));
|
||||
entity.Player.id = id
|
||||
this.connectedPlayers.set(
|
||||
connection,
|
||||
{
|
||||
entity: ecs.get(entity),
|
||||
entity,
|
||||
id,
|
||||
memory: {
|
||||
chunks: new Map(),
|
||||
|
|
|
@ -45,7 +45,12 @@ export const {
|
|||
SQRT2,
|
||||
} = Math;
|
||||
|
||||
export const SQRT_2_2 = Math.sqrt(2) / 2;
|
||||
export const EIGHTH_PI = Math.PI / 8;
|
||||
export const QUARTER_PI = Math.PI / 4;
|
||||
export const HALF_PI = Math.PI / 2;
|
||||
export const TAU = Math.PI * 2;
|
||||
export const PI_180 = Math.PI / 180;
|
||||
|
||||
export function bresenham({x: x1, y: y1}, {x: x2, y: y2}) {
|
||||
const points = [];
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"icon": "/assets/brush/brush.png",
|
||||
"label": "Brush",
|
||||
"start": "/assets/brush/start.js"
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
const {Collider, Controlled, Interacts, Inventory, Sound, Sprite} = wielder
|
||||
const entities = Collider.closest(Interacts.aabb());
|
||||
for (const entity of entities) {
|
||||
const {Tags} = entity;
|
||||
const {Emitter, Position, Tags} = entity;
|
||||
if (Tags && Tags.has('kittan')) {
|
||||
Controlled.locked = 1
|
||||
const [, direction] = Sprite.animation.split(':')
|
||||
|
@ -17,6 +17,94 @@ for (const entity of entities) {
|
|||
source: '/assets/furball/furball.json',
|
||||
});
|
||||
Controlled.locked = 0;
|
||||
|
||||
const heartParticles = {
|
||||
behaviors: [
|
||||
{
|
||||
type: 'moveAcceleration',
|
||||
config: {
|
||||
accel: {
|
||||
x: 0,
|
||||
y: -100,
|
||||
},
|
||||
minStart: 0,
|
||||
maxStart: 0,
|
||||
rotate: false,
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'moveSpeed',
|
||||
config: {
|
||||
speed: {
|
||||
list: [
|
||||
{
|
||||
time: 0,
|
||||
value: 30
|
||||
},
|
||||
{
|
||||
time: 1,
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'scale',
|
||||
config: {
|
||||
scale: {
|
||||
list: [
|
||||
{
|
||||
value: 0.5,
|
||||
time: 0,
|
||||
},
|
||||
{
|
||||
value: 0.125,
|
||||
time: 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'textureSingle',
|
||||
config: {
|
||||
texture: '/assets/heart/heart.png',
|
||||
}
|
||||
},
|
||||
],
|
||||
lifetime: {
|
||||
min: 0.5,
|
||||
max: 0.5,
|
||||
},
|
||||
frequency: 0.1,
|
||||
emitterLifetime: 0.25,
|
||||
pos: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
rotation: 180,
|
||||
};
|
||||
|
||||
|
||||
Emitter.emit({
|
||||
...heartParticles,
|
||||
behaviors: [
|
||||
...heartParticles.behaviors,
|
||||
{
|
||||
type: 'spawnShape',
|
||||
config: {
|
||||
type: 'rect',
|
||||
data: {
|
||||
x: Position.x - 8,
|
||||
y: Position.y,
|
||||
w: 16,
|
||||
h: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"icon": "/assets/furball/furball.png"
|
||||
"icon": "/assets/furball/furball.png",
|
||||
"label": "Fur Ball"
|
||||
}
|
||||
|
|
BIN
public/assets/heart/heart.png
Normal file
BIN
public/assets/heart/heart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 570 B |
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"icon": "/assets/hoe/icon.png",
|
||||
"label": "Hoe",
|
||||
"projectionCheck": "/assets/hoe/projection-check.js",
|
||||
"projection": {
|
||||
"distance": [3, -1],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const {Direction, Position, Wielder} = wielder
|
||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction)
|
||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.quantize(4))
|
||||
if (projected?.length > 0) {
|
||||
|
||||
const {Controlled, Emitter, Sound, Sprite} = wielder
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
entity.Direction.direction = Math.floor(Math.random() * 4);
|
||||
entity.Direction.direction = Math.random() * Math.TAU;
|
||||
|
||||
entity.Controlled.directionMove(entity.Direction.direction);
|
||||
|
||||
const map = {0: 'moveUp', 1: 'moveRight', 2: 'moveDown', 3: 'moveLeft'};
|
||||
entity.Controlled[map[entity.Direction.direction]] = 1;
|
||||
await wait(0.25 + Math.random() * 2.25);
|
||||
entity.Controlled[map[entity.Direction.direction]] = 0;
|
||||
|
||||
entity.Controlled.stop();
|
||||
|
||||
entity.Sprite.isAnimating = 0;
|
||||
|
||||
await wait(1 + Math.random() * 3);
|
||||
|
||||
entity.Direction.direction = Math.floor(Math.random() * 4);
|
||||
entity.Direction.direction = Math.random() * Math.TAU;
|
||||
|
||||
await wait(0.5 + Math.random() * 2.5);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"icon": "/assets/potion/icon.png",
|
||||
"label": "Potion",
|
||||
"start": "/assets/potion/start.js"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
const {Direction, Position, Wielder} = wielder
|
||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction)
|
||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.quantize(4))
|
||||
if (projected?.length > 0) {
|
||||
const {Controlled, Emitter, Sound, Sprite} = wielder
|
||||
const {TileLayers} = ecs.get(1)
|
||||
|
@ -120,9 +120,10 @@ if (projected?.length > 0) {
|
|||
Sound.play('/assets/sow.wav');
|
||||
Sprite.animation = ['moving', direction].join(':');
|
||||
|
||||
const directionMap = {0: 'right', 1: 'down', 2: 'left', 3: 'up'};
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
Direction.direction = Math.floor(Math.random() * 4);
|
||||
Sprite.animation = ['moving', 0 === Direction.direction ? 'up' : (1 === Direction.direction ? 'right' : (2 === Direction.direction ? 'down' : 'left'))].join(':');
|
||||
Direction.direction = Math.HALF_PI * Math.floor(Math.random() * 4);
|
||||
Sprite.animation = ['moving', directionMap[Direction.quantize(4)]].join(':');
|
||||
await wait(0.125);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"icon": "/assets/tomato-seeds/icon.png",
|
||||
"label": "Tomato Seeds",
|
||||
"projection": {
|
||||
"distance": [1, -1],
|
||||
"grid": [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const {Direction, Position, Wielder} = wielder
|
||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction)
|
||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.quantize(4))
|
||||
if (projected?.length > 0) {
|
||||
|
||||
const {Controlled, Emitter, Sound, Sprite} = wielder
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"icon": "/assets/watering-can/icon.png",
|
||||
"label": "Watering Can",
|
||||
"projectionCheck": "/assets/watering-can/projection-check.js",
|
||||
"projection": {
|
||||
"distance": [3, -1],
|
||||
|
|
49
server.js
49
server.js
|
@ -3,6 +3,9 @@ import compression from 'compression';
|
|||
import express from 'express';
|
||||
import morgan from 'morgan';
|
||||
|
||||
// patch pixi server context
|
||||
import('./app/server/pixi-context.js');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isInsecure = process.env.SILPHIUS_INSECURE_HTTP;
|
||||
|
||||
|
@ -32,6 +35,20 @@ else {
|
|||
server = createServer(serverOptions, app);
|
||||
}
|
||||
|
||||
// immediately start listening and queueing up connections
|
||||
let resolve, promise = new Promise((res) => {
|
||||
resolve = res;
|
||||
});
|
||||
app.use(async (req, res, next) => {
|
||||
await promise;
|
||||
next();
|
||||
});
|
||||
const port = process.env.PORT || 3000;
|
||||
server.listen(port, () =>
|
||||
console.log(`Express server listening at http${isInsecure ? '' : 's'}://localhost:${port}`)
|
||||
);
|
||||
|
||||
// possibly load dev server and build the request handler up front
|
||||
const viteDevServer = isProduction
|
||||
? undefined
|
||||
: await import('vite').then((vite) =>
|
||||
|
@ -39,26 +56,18 @@ const viteDevServer = isProduction
|
|||
server: {middlewareMode: {server}},
|
||||
})
|
||||
);
|
||||
|
||||
let websocketBuilt = false;
|
||||
|
||||
const build = () => (
|
||||
viteDevServer
|
||||
? viteDevServer.ssrLoadModule('virtual:remix/server-build')
|
||||
: import('./build/server/index.js')
|
||||
);
|
||||
const ssr = await build();
|
||||
await ssr.entry.module.websocket(server, viteDevServer);
|
||||
const remixHandler = createRequestHandler({
|
||||
build: async () => {
|
||||
// patch pixi server context
|
||||
import('./app/server/pixi-context.js');
|
||||
const ssr = await (
|
||||
viteDevServer
|
||||
? viteDevServer.ssrLoadModule('virtual:remix/server-build')
|
||||
: import('./build/server/index.js')
|
||||
);
|
||||
if (!websocketBuilt) {
|
||||
await ssr.entry.module.websocket(server, viteDevServer);
|
||||
websocketBuilt = true;
|
||||
}
|
||||
return ssr;
|
||||
},
|
||||
build: () => ssr,
|
||||
});
|
||||
|
||||
// configure middleware
|
||||
app.use(compression());
|
||||
|
||||
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
|
||||
|
@ -85,7 +94,5 @@ app.use(morgan('tiny'));
|
|||
// handle SSR requests
|
||||
app.all('*', remixHandler);
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
server.listen(port, () =>
|
||||
console.log(`Express server listening at http${isInsecure ? '' : 's'}://localhost:${port}`)
|
||||
);
|
||||
// finally let requests resolve
|
||||
resolve();
|
||||
|
|
Loading…
Reference in New Issue
Block a user