refactor: water
This commit is contained in:
parent
4faf685029
commit
b2ab8858dc
22
packages/farm/src/packets/room-water.js
Normal file
22
packages/farm/src/packets/room-water.js
Normal 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;
|
||||
}
|
||||
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
50
packages/farm/src/resources/decorators/room/renderable.js
Normal file
50
packages/farm/src/resources/decorators/room/renderable.js
Normal 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];
|
||||
}
|
||||
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user