From b2ab8858dc848062eba9af86fd28cb55390021d5 Mon Sep 17 00:00:00 2001 From: cha0s Date: Sun, 9 May 2021 23:47:55 -0500 Subject: [PATCH] refactor: water --- packages/farm/src/packets/room-water.js | 22 ++++ .../farm/src/resources/decorators/room.js | 122 ++++++++++++++++-- .../resources/decorators/room/renderable.js | 50 +++++++ packages/farm/src/traits/plant.js | 25 ++-- .../src/components/play/renderable/index.jsx | 17 ++- 5 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 packages/farm/src/packets/room-water.js create mode 100644 packages/farm/src/resources/decorators/room/renderable.js diff --git a/packages/farm/src/packets/room-water.js b/packages/farm/src/packets/room-water.js new file mode 100644 index 0000000..462d43a --- /dev/null +++ b/packages/farm/src/packets/room-water.js @@ -0,0 +1,22 @@ +import {Packet} from '@latus/socket'; + +export default () => class RoomWaterPacket extends Packet { + + static get data() { + return [ + { + hash: 'uint32', + amount: 'int8', + }, + ]; + } + + static pack(data) { + for (let i = 0; i < data.length; i++) { + // eslint-disable-next-line no-param-reassign + data[i].amount = Math.floor(data[i].amount); + } + return data; + } + +}; diff --git a/packages/farm/src/resources/decorators/room.js b/packages/farm/src/resources/decorators/room.js index 528fc21..1604dbe 100644 --- a/packages/farm/src/resources/decorators/room.js +++ b/packages/farm/src/resources/decorators/room.js @@ -1,11 +1,36 @@ -import {Rectangle, Vector} from '@avocado/math'; +import { + Rectangle, + spontaneous, + Vector, +} from '@avocado/math'; + +import Renderable from './room/renderable'; const DIRT = 6; const TILLED = 7; export default (Room) => class FarmableRoom extends Room { - #water = {}; + static Renderable = Renderable(Room); + + #evaporation = 0; + + water = {}; + + #waterChanged = false; + + #waterChanges = []; + + acceptPacket(packet) { + super.acceptPacket(packet); + if ('RoomWater' === packet.constructor.type) { + for (let i = 0; i < packet.data.length; i++) { + const {amount, hash} = packet.data[i]; + this.water[hash] = amount; + } + this.emit('waterChanged'); + } + } adjustWaterAt(target, increase) { const [tiles] = this.tiles; @@ -16,18 +41,28 @@ export default (Room) => class FarmableRoom extends Room { return; } const hash = Vector.packToUint32(target); - if (!(hash in this.#water)) { - this.#water[hash] = 0; + if (!(hash in this.water)) { + this.water[hash] = 0; } - this.#water[hash] += increase; - if (this.#water[hash] < 0) { + const oldWater = this.water[hash]; + this.water[hash] += increase; + if (this.water[hash] < 0) { // Drought - this.#water[hash] = Math.max(this.#water[hash], -128); + this.water[hash] = Math.max(this.water[hash], -128); } - else if (this.#water[hash] > 0) { + else if (this.water[hash] > 0) { // Flood - this.#water[hash] = Math.min(this.#water[hash], 127); + this.water[hash] = Math.min(this.water[hash], 127); } + if (this.water[hash] !== oldWater) { + this.#waterChanged = true; + this.#waterChanges.push({hash, amount: this.water[hash]}); + } + } + + cleanPackets() { + super.cleanPackets(); + this.#waterChanges = []; } isPlantAt(target) { @@ -41,10 +76,23 @@ export default (Room) => class FarmableRoom extends Room { return false; } + async load(json) { + await super.load(json); + const { + evaporation, + water, + } = json; + if (evaporation) { + this.#evaporation = evaporation / 144; + } + if (water) { + this.water = water; + } + } + mayPrepareSoilAt(target) { const [tiles] = this.tiles; return DIRT === tiles.tileAt(target); - // return -1 !== GRASS.indexOf(tiles.tileAt(target)); } maySowAt(target) { @@ -56,11 +104,65 @@ export default (Room) => class FarmableRoom extends Room { ); } + packetsFor(informed) { + const packets = super.packetsFor(informed); + if (this.#waterChanges.length > 0) { + packets.push(['RoomWater', this.#waterChanges]); + } + return packets; + } + prepareSoilAt(target) { if ('client' !== process.env.SIDE) { const [tiles] = this.tiles; tiles.setTileAt(target, TILLED); + this.adjustWaterAt(target, 0); } } + tick(elapsed) { + super.tick(elapsed); + this.tickWater(elapsed); + if (this.#waterChanged) { + this.#waterChanged = false; + this.emit('waterChanged'); + } + } + + tickWater(elapsed) { + if ('client' === process.env.SIDE) { + return; + } + const water = Object.entries(this.water); + for (let i = 0; i < water.length; i++) { + const [hash] = water[i]; + if (spontaneous(elapsed, 10)) { + this.adjustWaterAt(Vector.unpackFromUint32(hash), -this.#evaporation); + } + } + } + + toJSON() { + return { + ...super.toJSON(), + water: this.water, + }; + } + + toNetwork(informed) { + return { + ...super.toNetwork(informed), + water: this.water, + }; + } + + waterAt(target) { + const [tiles] = this.tiles; + if (TILLED !== tiles.tileAt(target)) { + return 0; + } + const hash = Vector.packToUint32(target); + return hash in this.water ? this.water[hash] : 0; + } + }; diff --git a/packages/farm/src/resources/decorators/room/renderable.js b/packages/farm/src/resources/decorators/room/renderable.js new file mode 100644 index 0000000..67e0c40 --- /dev/null +++ b/packages/farm/src/resources/decorators/room/renderable.js @@ -0,0 +1,50 @@ +import {Color, Primitives} from '@avocado/graphics'; +import {Vector} from '@avocado/math'; + +export default (Room) => class extends Room.Renderable { + + #primitives = {}; + + constructor(room, renderer) { + super(room, renderer); + this.room = room; + this.room.on('waterChanged', this.onWaterChange, this); + this.onWaterChange(); + } + + onWaterChange() { + const water = Object.entries(this.room.water); + const [tiles] = this.room.tiles; + const [renderable] = this.tilesRenderables; + for (let i = 0; i < water.length; i++) { + const [hash, amount] = water[i]; + const [tw, th] = tiles.tileSize; + const [x, y] = Vector.mul(Vector.unpackFromUint32(hash), [tw, th]); + const primitives = this.primitivesFor(hash); + renderable.addChild(primitives); + primitives.clear(); + if (amount > 0) { + primitives.drawRectangle( + [x, y, tw, th], + Primitives.lineStyle(new Color(0, 0, 0, amount / 256), 0), + Primitives.fillStyle(new Color(0, 0, 0, amount / 256)), + ); + } + else if (amount < 0) { + primitives.drawRectangle( + [x, y, tw, th], + Primitives.lineStyle(new Color(180, 180, 32, Math.abs(amount) / 512), 0), + Primitives.fillStyle(new Color(180, 180, 32, Math.abs(amount) / 512)), + ); + } + } + } + + primitivesFor(hash) { + if (!this.#primitives[hash]) { + this.#primitives[hash] = new Primitives(); + } + return this.#primitives[hash]; + } + +}; diff --git a/packages/farm/src/traits/plant.js b/packages/farm/src/traits/plant.js index 33a8e01..0e6d469 100644 --- a/packages/farm/src/traits/plant.js +++ b/packages/farm/src/traits/plant.js @@ -1,4 +1,4 @@ -import {spontaneous} from '@avocado/math'; +import {spontaneous, Vector} from '@avocado/math'; import {StateProperty, Trait} from '@avocado/traits'; import {compose} from '@latus/core'; @@ -10,10 +10,7 @@ const decorate = compose( export default (latus) => class Plant extends decorate(Trait) { - constructor() { - super(); - this.growthElapsed = 0; - } + #growthElapsed = 0; acceptPacket(packet) { if ('TraitUpdatePlant' === packet.constructor.type) { @@ -26,6 +23,8 @@ export default (latus) => class Plant extends decorate(Trait) { growthCheck: '', growthScript: '', stageSpecs: {}, + waterIdeal: 64, + waterTolerance: 32, }; } @@ -54,7 +53,7 @@ export default (latus) => class Plant extends decorate(Trait) { this.entity.currentImage = image; this.entity.isInteractive = !!ripe; if (reset) { - this.growthElapsed = 0; + this.#growthElapsed = 0; } if (this.growthScript) { this.entity.addTickingPromise(this.growthScript.tickingPromise()); @@ -112,12 +111,20 @@ export default (latus) => class Plant extends decorate(Trait) { if ('client' === process.env.SIDE) { return; } - if (spontaneous(elapsed, 15)) { - this.growthElapsed += 0.25; + const {room} = this.entity; + const {tileSize} = room.tiles[0]; + const tile = Vector.floor(Vector.div(this.entity.position, tileSize)); + const water = room.waterAt(tile); + const drift = Math.abs(this.params.waterIdeal - water); + if (this.params.waterTolerance <= drift) { + return; + } + if (spontaneous(elapsed, 9)) { + this.#growthElapsed += 0.15; const {growthStage} = this.entity; const stageSpec = this.params.stageSpecs[growthStage]; if ('growAt' in stageSpec) { - if (this.growthElapsed >= stageSpec.growAt) { + if (this.#growthElapsed >= stageSpec.growAt) { const grow = () => { const {growTo} = stageSpec; this.entity.growthStage = 'undefined' !== typeof growTo ? growTo : growthStage + 1; diff --git a/packages/universe/src/components/play/renderable/index.jsx b/packages/universe/src/components/play/renderable/index.jsx index 84e5b69..ca1f5af 100644 --- a/packages/universe/src/components/play/renderable/index.jsx +++ b/packages/universe/src/components/play/renderable/index.jsx @@ -10,7 +10,6 @@ import { import {Container, Renderer, Stage} from '@avocado/graphics'; import {Vector} from '@avocado/math'; import {createLoop, destroyLoop} from '@avocado/timing'; -import {RoomView} from '@avocado/topdown'; import {useRoom, useSelfEntity} from '@humus/app/client'; const renderer = new Renderer(); @@ -18,7 +17,7 @@ const renderer = new Renderer(); const RoomStage = () => { const selfEntity = useSelfEntity(); const [container] = useState(new Container()); - const [roomView, setRoomView] = useState(); + const [roomRenderable, setRoomRenderable] = useState(); const [viewport] = useState([320, 180]); const room = useRoom(); useEffect(() => { @@ -28,22 +27,22 @@ const RoomStage = () => { if (!room) { return undefined; } - const roomView = new RoomView(room, renderer); - setRoomView(roomView); - container.addChild(roomView); + const roomRenderable = new (room.constructor.Renderable)(room, renderer); + setRoomRenderable(roomRenderable); + container.addChild(roomRenderable); return () => { - container.removeChild(roomView); + container.removeChild(roomRenderable); }; }, [container, room]); useEffect(() => { - if (!roomView || !selfEntity || !selfEntity.camera) { + if (!roomRenderable || !selfEntity || !selfEntity.camera) { return undefined; } const {camera} = selfEntity; const halfViewport = Vector.scale(viewport, 0.5); camera.realPosition = camera.position; const onCameraRealOffsetChanged = () => { - roomView.renderChunksForExtent(camera.rectangle); + roomRenderable.renderChunksForExtent(camera.rectangle); container.pivot = Vector.sub(halfViewport, Vector.scale(camera.realOffset, -1)); }; const onCameraRotationChanged = () => { @@ -63,7 +62,7 @@ const RoomStage = () => { camera.off('rotationChanged', onCameraRotationChanged); camera.off('scaleChanged', onCameraScaleChanged); }; - }, [container, roomView, selfEntity, viewport]); + }, [container, roomRenderable, selfEntity, viewport]); const ticker = useCallback( (elapsed) => { container.renderTick(elapsed);