diff --git a/app/ecs-components/interactive.js b/app/ecs-components/interactive.js
new file mode 100644
index 0000000..aec4f83
--- /dev/null
+++ b/app/ecs-components/interactive.js
@@ -0,0 +1,37 @@
+import Component from '@/ecs/component.js';
+
+export default class Interactive extends Component {
+ instanceFromSchema() {
+ const {ecs} = this;
+ return class ControlledInstance extends super.instanceFromSchema() {
+ interact(initiator) {
+ this.interactScriptInstance.context.initiator = initiator;
+ const {Ticking} = ecs.get(this.entity);
+ Ticking.addTickingPromise(this.interactScriptInstance.tickingPromise());
+ }
+ get interacting() {
+ return !!this.$$interacting;
+ }
+ set interacting(interacting) {
+ this.$$interacting = interacting ? 1 : 0;
+ }
+ }
+ }
+ async load(instance) {
+ // heavy handed...
+ if ('undefined' !== typeof window) {
+ return;
+ }
+ instance.interactScriptInstance = await this.ecs.readScript(
+ instance.interactScript,
+ {
+ ecs: this.ecs,
+ subject: this.ecs.get(instance.entity),
+ },
+ );
+ }
+ static properties = {
+ interacting: {type: 'uint8'},
+ interactScript: {type: 'string'},
+ };
+}
diff --git a/app/ecs-components/interacts.js b/app/ecs-components/interacts.js
new file mode 100644
index 0000000..1ce774c
--- /dev/null
+++ b/app/ecs-components/interacts.js
@@ -0,0 +1,14 @@
+import Component from '@/ecs/component.js';
+
+export default class Interacts extends Component {
+ instanceFromSchema() {
+ return class ControlledInstance extends super.instanceFromSchema() {
+ toJSON() {
+ return {};
+ }
+ }
+ }
+ static properties = {
+ willInteractWith: {type: 'uint32'},
+ };
+}
diff --git a/app/ecs-systems/interactions.js b/app/ecs-systems/interactions.js
new file mode 100644
index 0000000..541be26
--- /dev/null
+++ b/app/ecs-systems/interactions.js
@@ -0,0 +1,47 @@
+import {System} from '@/ecs/index.js';
+
+export default class PlantGrowth extends System {
+
+ static queries() {
+ return {
+ default: ['Interacts'],
+ };
+ }
+
+ tick() {
+ for (const {Direction, Interacts, Position} of this.select('default')) {
+ Interacts.willInteractWith = 0
+ const box = {
+ x: Position.x - 8,
+ y: Position.y - 8,
+ w: 16,
+ h: 16,
+ }
+ if (0 === Direction.direction) {
+ box.y -= 16
+ }
+ if (1 === Direction.direction) {
+ box.x += 16
+ }
+ if (2 === Direction.direction) {
+ box.y += 16
+ }
+ if (3 === Direction.direction) {
+ box.x -= 16
+ }
+ // todo sort
+ const entities = Array.from(this.ecs.system('UpdateSpatialHash').within(
+ box.x,
+ box.y,
+ box.w,
+ box.h,
+ ));
+ for (let j = 0; j < entities.length; ++j) {
+ if (entities[j].Interactive && entities[j].Interactive.interacting) {
+ Interacts.willInteractWith = entities[0].id;
+ }
+ }
+ }
+ }
+
+}
diff --git a/app/engine.js b/app/engine.js
index 95af89c..e3a9527 100644
--- a/app/engine.js
+++ b/app/engine.js
@@ -58,7 +58,7 @@ export default class Engine {
entity,
payload,
] of this.incomingActions) {
- const {Controlled, Inventory, Wielder} = entity;
+ const {Controlled, Ecs, Interacts, Inventory, Wielder} = entity;
switch (payload.type) {
case 'changeSlot': {
if (!Controlled.locked) {
@@ -85,6 +85,18 @@ export default class Engine {
}
break;
}
+ case 'interact': {
+ if (!Controlled.locked) {
+ if (payload.value) {
+ if (Interacts.willInteractWith) {
+ const ecs = this.ecses[Ecs.path];
+ const subject = ecs.get(Interacts.willInteractWith);
+ subject.Interactive.interact(entity);
+ }
+ }
+ }
+ break;
+ }
}
}
this.incomingActions = [];
@@ -153,6 +165,7 @@ export default class Engine {
'RunAnimations',
'RunTickingPromises',
'Water',
+ 'Interactions',
];
defaultSystems.forEach((defaultSystem) => {
const System = ecs.system(defaultSystem);
@@ -171,6 +184,7 @@ export default class Engine {
Ecs: {path: join('homesteads', `${id}`)},
Emitter: {},
Forces: {},
+ Interacts: {},
Inventory: {
slots: {
// 1: {
diff --git a/app/packets/action.js b/app/packets/action.js
index c6ff220..132a4b4 100644
--- a/app/packets/action.js
+++ b/app/packets/action.js
@@ -7,6 +7,7 @@ const WIRE_MAP = {
'moveLeft': 3,
'use': 4,
'changeSlot': 5,
+ 'interact': 6,
};
Object.entries(WIRE_MAP)
.forEach(([k, v]) => {
diff --git a/app/react-components/entities.jsx b/app/react-components/entities.jsx
index d193383..706b3d8 100644
--- a/app/react-components/entities.jsx
+++ b/app/react-components/entities.jsx
@@ -1,12 +1,43 @@
+import {AdjustmentFilter} from '@pixi/filter-adjustment';
+import {GlowFilter} from '@pixi/filter-glow';
import {Container} from '@pixi/react';
+import {useEffect, useState} from 'react';
+
+import {useEcs} from '@/context/ecs.js';
+import {useMainEntity} from '@/context/main-entity.js';
import Entity from './entity.jsx';
export default function Entities({entities}) {
+ const [ecs] = useEcs();
+ const [mainEntity] = useMainEntity();
+ const [radians, setRadians] = useState(0);
+ const [willInteractWith, setWillInteractWith] = useState(0);
+ const [filters] = useState([new AdjustmentFilter(), new GlowFilter({color: 0x0})]);
+ const pulse = (Math.cos(radians) + 1) * 0.5;
+ filters[0].brightness = (pulse * 0.75) + 1;
+ filters[1].outerStrength = pulse * 0.5;
+ useEffect(() => {
+ setRadians(0);
+ const handle = setInterval(() => {
+ setRadians((radians) => (radians + 0.1) % (Math.PI * 2))
+ }, 50);
+ return () => {
+ clearInterval(handle);
+ };
+ }, [willInteractWith]);
+ useEffect(() => {
+ if (!mainEntity) {
+ return;
+ }
+ setWillInteractWith(ecs.get(mainEntity).Interacts.willInteractWith);
+ }, [entities, ecs, mainEntity]);
const renderables = [];
for (const id in entities) {
+ const isHighlightedInteraction = id == willInteractWith;
renderables.push(
diff --git a/app/react-components/entity.jsx b/app/react-components/entity.jsx
index 39793ce..a1988e9 100644
--- a/app/react-components/entity.jsx
+++ b/app/react-components/entity.jsx
@@ -29,7 +29,7 @@ function Crosshair({x, y}) {
);
}
-function Entities({entity}) {
+function Entity({entity, ...rest}) {
const [debug] = useDebug();
if (!entity) {
return false;
@@ -41,6 +41,7 @@ function Entities({entity}) {
{entity.Sprite && (
)}
{entity.Emitter && (
@@ -55,4 +56,4 @@ function Entities({entity}) {
);
}
-export default memo(Entities);
+export default memo(Entity);
diff --git a/app/react-components/sprite.jsx b/app/react-components/sprite.jsx
index b9619d3..16bb23d 100644
--- a/app/react-components/sprite.jsx
+++ b/app/react-components/sprite.jsx
@@ -2,7 +2,7 @@ import {Sprite as PixiSprite} from '@pixi/react';
import {useAsset} from '@/context/assets.js';
-export default function Sprite({entity}) {
+export default function Sprite({entity, ...rest}) {
const asset = useAsset(entity.Sprite.source);
if (!asset) {
return false;
@@ -21,6 +21,7 @@ export default function Sprite({entity}) {
texture={texture}
x={Math.round(entity.Position.x)}
y={Math.round(entity.Position.y)}
+ {...rest}
/>
);
}
\ No newline at end of file
diff --git a/app/react-components/ui.jsx b/app/react-components/ui.jsx
index 5919b0d..ad8b3af 100644
--- a/app/react-components/ui.jsx
+++ b/app/react-components/ui.jsx
@@ -92,6 +92,10 @@ export default function Ui({disconnected}) {
actionPayload = {type: 'use', value: KEY_MAP[type]};
break;
}
+ case 'e': {
+ actionPayload = {type: 'interact', value: KEY_MAP[type]};
+ break;
+ }
case '1': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 1};
diff --git a/package-lock.json b/package-lock.json
index baff630..ad799eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,8 @@
"name": "silphius-next",
"dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
+ "@pixi/filter-adjustment": "^5.1.1",
+ "@pixi/filter-glow": "^5.2.1",
"@pixi/particle-emitter": "^5.0.8",
"@pixi/react": "^7.1.2",
"@pixi/spritesheet": "^7.4.2",
@@ -3302,6 +3304,14 @@
"@pixi/core": "7.4.2"
}
},
+ "node_modules/@pixi/filter-adjustment": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-5.1.1.tgz",
+ "integrity": "sha512-AUHe03rmqXwV1ylAHq62t19AolPWOOYomCcL+Qycb1tf+LbM8FWpGXC6wmU1PkUrhgNc958uM9TrA9nRpplViA==",
+ "peerDependencies": {
+ "@pixi/core": "^7.0.0-X"
+ }
+ },
"node_modules/@pixi/filter-alpha": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.4.2.tgz",
@@ -3342,6 +3352,14 @@
"@pixi/core": "7.4.2"
}
},
+ "node_modules/@pixi/filter-glow": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/@pixi/filter-glow/-/filter-glow-5.2.1.tgz",
+ "integrity": "sha512-94I4XePDF9yqqA6KQuhPSphEHPJ2lXfqJLn0Bes8VVdwft0Ianj1wALqjoSUeBWqiJbhjBEXGDNkRZhPHvY3Xg==",
+ "peerDependencies": {
+ "@pixi/core": "^7.0.0-X"
+ }
+ },
"node_modules/@pixi/filter-noise": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.4.2.tgz",
diff --git a/package.json b/package.json
index 562e2c9..7f4e734 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,8 @@
},
"dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
+ "@pixi/filter-adjustment": "^5.1.1",
+ "@pixi/filter-glow": "^5.2.1",
"@pixi/particle-emitter": "^5.0.8",
"@pixi/react": "^7.1.2",
"@pixi/spritesheet": "^7.4.2",
diff --git a/public/assets/tomato-plant/grow.js b/public/assets/tomato-plant/grow.js
index f6eea88..17935d6 100644
--- a/public/assets/tomato-plant/grow.js
+++ b/public/assets/tomato-plant/grow.js
@@ -1,4 +1,6 @@
-const {Sprite} = ecs.get(plant.entity);
+const {Interactive, Sprite} = ecs.get(plant.entity);
+
+plant.growth = 0
if (plant.stage < 3) {
plant.stage += 1
@@ -6,8 +8,8 @@ if (plant.stage < 3) {
if (4 === plant.stage) {
plant.stage = 3
}
-if (3 !== plant.stage) {
- plant.growth = 0
+if (3 === plant.stage) {
+ Interactive.interacting = true;
}
Sprite.animation = ['stage', plant.stage].join('/')
diff --git a/public/assets/tomato-plant/interact.js b/public/assets/tomato-plant/interact.js
new file mode 100644
index 0000000..340ddaa
--- /dev/null
+++ b/public/assets/tomato-plant/interact.js
@@ -0,0 +1,4 @@
+const {Interactive, Plant, Sprite} = subject;
+Interactive.interacting = false;
+Plant.stage = 4;
+Sprite.animation = ['stage', Plant.stage].join('/')
diff --git a/public/assets/tomato-seeds/start.js b/public/assets/tomato-seeds/start.js
index 834315b..0b5d511 100644
--- a/public/assets/tomato-seeds/start.js
+++ b/public/assets/tomato-seeds/start.js
@@ -9,10 +9,13 @@ if (projected?.length > 0) {
const [, direction] = Sprite.animation.split(':')
const plant = {
+ Interactive: {
+ interactScript: '/assets/tomato-plant/interact.js',
+ },
Plant: {
growScript: '/assets/tomato-plant/grow.js',
mayGrowScript: '/assets/tomato-plant/may-grow.js',
- stages: Array(5).fill(60),
+ stages: Array(5).fill(5),
},
Sprite: {
anchor: {x: 0.5, y: 0.75},