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