refactor: water

This commit is contained in:
cha0s 2021-05-09 23:47:55 -05:00
parent 4faf685029
commit b2ab8858dc
5 changed files with 208 additions and 28 deletions

View File

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

View File

@ -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 DIRT = 6;
const TILLED = 7; const TILLED = 7;
export default (Room) => class FarmableRoom extends Room { 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) { adjustWaterAt(target, increase) {
const [tiles] = this.tiles; const [tiles] = this.tiles;
@ -16,18 +41,28 @@ export default (Room) => class FarmableRoom extends Room {
return; return;
} }
const hash = Vector.packToUint32(target); const hash = Vector.packToUint32(target);
if (!(hash in this.#water)) { if (!(hash in this.water)) {
this.#water[hash] = 0; this.water[hash] = 0;
} }
this.#water[hash] += increase; const oldWater = this.water[hash];
if (this.#water[hash] < 0) { this.water[hash] += increase;
if (this.water[hash] < 0) {
// Drought // 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 // 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) { isPlantAt(target) {
@ -41,10 +76,23 @@ export default (Room) => class FarmableRoom extends Room {
return false; 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) { mayPrepareSoilAt(target) {
const [tiles] = this.tiles; const [tiles] = this.tiles;
return DIRT === tiles.tileAt(target); return DIRT === tiles.tileAt(target);
// return -1 !== GRASS.indexOf(tiles.tileAt(target));
} }
maySowAt(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) { prepareSoilAt(target) {
if ('client' !== process.env.SIDE) { if ('client' !== process.env.SIDE) {
const [tiles] = this.tiles; const [tiles] = this.tiles;
tiles.setTileAt(target, TILLED); 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;
}
}; };

View File

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

View File

@ -1,4 +1,4 @@
import {spontaneous} from '@avocado/math'; import {spontaneous, Vector} from '@avocado/math';
import {StateProperty, Trait} from '@avocado/traits'; import {StateProperty, Trait} from '@avocado/traits';
import {compose} from '@latus/core'; import {compose} from '@latus/core';
@ -10,10 +10,7 @@ const decorate = compose(
export default (latus) => class Plant extends decorate(Trait) { export default (latus) => class Plant extends decorate(Trait) {
constructor() { #growthElapsed = 0;
super();
this.growthElapsed = 0;
}
acceptPacket(packet) { acceptPacket(packet) {
if ('TraitUpdatePlant' === packet.constructor.type) { if ('TraitUpdatePlant' === packet.constructor.type) {
@ -26,6 +23,8 @@ export default (latus) => class Plant extends decorate(Trait) {
growthCheck: '', growthCheck: '',
growthScript: '', growthScript: '',
stageSpecs: {}, stageSpecs: {},
waterIdeal: 64,
waterTolerance: 32,
}; };
} }
@ -54,7 +53,7 @@ export default (latus) => class Plant extends decorate(Trait) {
this.entity.currentImage = image; this.entity.currentImage = image;
this.entity.isInteractive = !!ripe; this.entity.isInteractive = !!ripe;
if (reset) { if (reset) {
this.growthElapsed = 0; this.#growthElapsed = 0;
} }
if (this.growthScript) { if (this.growthScript) {
this.entity.addTickingPromise(this.growthScript.tickingPromise()); this.entity.addTickingPromise(this.growthScript.tickingPromise());
@ -112,12 +111,20 @@ export default (latus) => class Plant extends decorate(Trait) {
if ('client' === process.env.SIDE) { if ('client' === process.env.SIDE) {
return; return;
} }
if (spontaneous(elapsed, 15)) { const {room} = this.entity;
this.growthElapsed += 0.25; 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 {growthStage} = this.entity;
const stageSpec = this.params.stageSpecs[growthStage]; const stageSpec = this.params.stageSpecs[growthStage];
if ('growAt' in stageSpec) { if ('growAt' in stageSpec) {
if (this.growthElapsed >= stageSpec.growAt) { if (this.#growthElapsed >= stageSpec.growAt) {
const grow = () => { const grow = () => {
const {growTo} = stageSpec; const {growTo} = stageSpec;
this.entity.growthStage = 'undefined' !== typeof growTo ? growTo : growthStage + 1; this.entity.growthStage = 'undefined' !== typeof growTo ? growTo : growthStage + 1;

View File

@ -10,7 +10,6 @@ import {
import {Container, Renderer, Stage} from '@avocado/graphics'; import {Container, Renderer, Stage} from '@avocado/graphics';
import {Vector} from '@avocado/math'; import {Vector} from '@avocado/math';
import {createLoop, destroyLoop} from '@avocado/timing'; import {createLoop, destroyLoop} from '@avocado/timing';
import {RoomView} from '@avocado/topdown';
import {useRoom, useSelfEntity} from '@humus/app/client'; import {useRoom, useSelfEntity} from '@humus/app/client';
const renderer = new Renderer(); const renderer = new Renderer();
@ -18,7 +17,7 @@ const renderer = new Renderer();
const RoomStage = () => { const RoomStage = () => {
const selfEntity = useSelfEntity(); const selfEntity = useSelfEntity();
const [container] = useState(new Container()); const [container] = useState(new Container());
const [roomView, setRoomView] = useState(); const [roomRenderable, setRoomRenderable] = useState();
const [viewport] = useState([320, 180]); const [viewport] = useState([320, 180]);
const room = useRoom(); const room = useRoom();
useEffect(() => { useEffect(() => {
@ -28,22 +27,22 @@ const RoomStage = () => {
if (!room) { if (!room) {
return undefined; return undefined;
} }
const roomView = new RoomView(room, renderer); const roomRenderable = new (room.constructor.Renderable)(room, renderer);
setRoomView(roomView); setRoomRenderable(roomRenderable);
container.addChild(roomView); container.addChild(roomRenderable);
return () => { return () => {
container.removeChild(roomView); container.removeChild(roomRenderable);
}; };
}, [container, room]); }, [container, room]);
useEffect(() => { useEffect(() => {
if (!roomView || !selfEntity || !selfEntity.camera) { if (!roomRenderable || !selfEntity || !selfEntity.camera) {
return undefined; return undefined;
} }
const {camera} = selfEntity; const {camera} = selfEntity;
const halfViewport = Vector.scale(viewport, 0.5); const halfViewport = Vector.scale(viewport, 0.5);
camera.realPosition = camera.position; camera.realPosition = camera.position;
const onCameraRealOffsetChanged = () => { const onCameraRealOffsetChanged = () => {
roomView.renderChunksForExtent(camera.rectangle); roomRenderable.renderChunksForExtent(camera.rectangle);
container.pivot = Vector.sub(halfViewport, Vector.scale(camera.realOffset, -1)); container.pivot = Vector.sub(halfViewport, Vector.scale(camera.realOffset, -1));
}; };
const onCameraRotationChanged = () => { const onCameraRotationChanged = () => {
@ -63,7 +62,7 @@ const RoomStage = () => {
camera.off('rotationChanged', onCameraRotationChanged); camera.off('rotationChanged', onCameraRotationChanged);
camera.off('scaleChanged', onCameraScaleChanged); camera.off('scaleChanged', onCameraScaleChanged);
}; };
}, [container, roomView, selfEntity, viewport]); }, [container, roomRenderable, selfEntity, viewport]);
const ticker = useCallback( const ticker = useCallback(
(elapsed) => { (elapsed) => {
container.renderTick(elapsed); container.renderTick(elapsed);