Compare commits

..

10 Commits

Author SHA1 Message Date
cha0s
3eb94f2ef8 refactor: continuous direction 2024-07-24 09:28:35 -05:00
cha0s
cbe5f473a6 perf: nothing for nothing 2024-07-24 02:46:44 -05:00
cha0s
49f46b4b00 fix: clear undefined slots 2024-07-24 02:39:25 -05:00
cha0s
a0943c8bbb refactor: upper chat bounds 2024-07-24 02:31:06 -05:00
cha0s
80c1f78c6a fix: server logic 2024-07-24 02:23:12 -05:00
cha0s
52f19b1d89 fix: to each their own 2024-07-24 01:50:33 -05:00
cha0s
3d7b0fc14e fun: hearts 2024-07-24 01:40:10 -05:00
cha0s
c45d24b909 fun: inventory + bag 2024-07-23 23:27:08 -05:00
cha0s
f197658199 fix: grid group 2024-07-23 23:22:07 -05:00
cha0s
fb1a286a86 fix: destroyer 2024-07-23 21:40:28 -05:00
39 changed files with 565 additions and 133 deletions

View File

@ -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 {};
}

View File

@ -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'},
};
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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]]);
}
}

View File

@ -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;
}
}
}

View File

@ -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(':');

View File

@ -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);

View 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>
);
}

View 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);
}
}

View File

@ -9,8 +9,3 @@
user-select: text;
width: 100%;
}
@font-face {
font-family: "Cookbook";
src: url("/assets/fonts/Cookbook.woff") format("woff");
}

View File

@ -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;

View File

@ -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

View File

@ -14,6 +14,9 @@ export default function Dialogues({camera, dialogues, scale}) {
/>
);
}
if (0 === elements.length) {
return false;
}
return <div className={styles.dialogues}>{elements}</div>;
}

View File

@ -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");
}

View File

@ -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>
);

View File

@ -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 {

View File

@ -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(() => {

View File

@ -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}) => {

View File

@ -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}

View File

@ -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");
}

View File

@ -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,
},
},
},
);
);
}
`,
},

View File

@ -14,7 +14,7 @@ export default async function createPlayer(id) {
],
},
Controlled: {},
Direction: {direction: 2},
Direction: {},
Ecs: {path: ['homesteads', `${id}`].join('/')},
Emitter: {},
Forces: {},

View File

@ -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},

View File

@ -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(),

View File

@ -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 = [];

View File

@ -1,4 +1,5 @@
{
"icon": "/assets/brush/brush.png",
"label": "Brush",
"start": "/assets/brush/start.js"
}

View File

@ -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;
}
}

View File

@ -1,3 +1,4 @@
{
"icon": "/assets/furball/furball.png"
"icon": "/assets/furball/furball.png",
"label": "Fur Ball"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

View File

@ -1,5 +1,6 @@
{
"icon": "/assets/hoe/icon.png",
"label": "Hoe",
"projectionCheck": "/assets/hoe/projection-check.js",
"projection": {
"distance": [3, -1],

View File

@ -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

View File

@ -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);

View File

@ -1,4 +1,5 @@
{
"icon": "/assets/potion/icon.png",
"label": "Potion",
"start": "/assets/potion/start.js"
}

View File

@ -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);
}

View File

@ -1,5 +1,6 @@
{
"icon": "/assets/tomato-seeds/icon.png",
"label": "Tomato Seeds",
"projection": {
"distance": [1, -1],
"grid": [

View File

@ -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

View File

@ -1,5 +1,6 @@
{
"icon": "/assets/watering-can/icon.png",
"label": "Watering Can",
"projectionCheck": "/assets/watering-can/projection-check.js",
"projection": {
"distance": [3, -1],

View File

@ -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();