import * as I from 'immutable'; import {compose, EventEmitter, Property} from '@avocado/core'; import {EntityCreatePacket, EntityPacket, EntityRemovePacket} from '@avocado/entity'; import {Vector} from '@avocado/math'; import {RectangleShape} from '@avocado/physics'; import {Synchronized} from '@avocado/state'; import {Layers} from './layers'; import {LayerCreatePacket} from './packets/layer-create.packet'; import {RoomSizeUpdatePacket} from './packets/room-size-update.packet'; import {TileUpdatePacket} from './packets/tile-update.packet'; const ROOM_BOUND_SIZE = 64; const HALF_ROOM_BOUND_SIZE = ROOM_BOUND_SIZE / 2; const decorate = compose( EventEmitter, Property('world', { track: true, }), Vector.Mixin('size', 'width', 'height', { default: [0, 0], track: true, }), Synchronized, ); export class Room extends decorate(class {}) { constructor(json) { super(); this.bounds = []; const layerJSON = 'undefined' !== typeof json ? json.layers : undefined; this.layers = new Layers(layerJSON); this.queuedEntityPackets = {}; // Listeners. this.layers.on('entityAdded', this.onEntityAddedToRoom, this); this.layers.on('entityRemoved', this.onEntityRemovedFromRoom, this); this.layers.on('layerAdded', this.onLayerAdded, this); this.on('sizeChanged', this.onSizeChanged, this); this.on('worldChanged', this.onWorldChanged, this); if ('undefined' !== typeof json && json.size) { this.size = json.size; } } acceptPacket(packet) { if (packet instanceof EntityCreatePacket) { this.queuedEntityPackets[packet.data.uuid] = []; this.layers.acceptPacket(packet); } if ( packet instanceof LayerCreatePacket || packet instanceof TileUpdatePacket ) { this.layers.acceptPacket(packet); } if (packet instanceof EntityRemovePacket) { const entity = this.findEntity(packet.data.uuid); if (entity) { entity.destroyGently(); } return; } if (packet instanceof EntityPacket) { const entity = this.findEntity(packet.data.uuid); if (entity) { entity.acceptPacket(packet); } else { const queuedPackets = this.queuedEntityPackets[packet.data.uuid]; if (queuedPackets) { queuedPackets.push(packet); } } } if (packet instanceof RoomSizeUpdatePacket) { const x = packet.data & 0xFFFF; const y = packet.data >> 16; this.size = [x, y]; } } addEntityToLayer(entity, layerIndex = 0) { this.layers.addEntityToLayer(entity, layerIndex); } allEntities() { return this.layers.allEntities(); } destroy() { this.layers.destroy(); this.layers.off('entityAdded', this.onEntityAddedToRoom); this.layers.off('entityRemoved', this.onEntityRemovedFromRoom); this.off('worldChanged', this.onWorldChanged); } findEntity(uuid) { return this.layers.findEntity(uuid); } fromJSON(json) { if (json.layers) { this.layers.fromJSON(json.layers); } if (json.size) { this.size = json.size; } return this; } layer(index) { return this.layers.layer(index); } onEntityAddedToRoom(entity) { entity.setRoom(this); if (AVOCADO_CLIENT) { const queuedPackets = this.queuedEntityPackets[entity.instanceUuid]; for (let i = 0; i < queuedPackets.length; i++) { entity.acceptPacket(queuedPackets[i]); } this.queuedEntityPackets[entity.instanceUuid] = undefined; } this.emit('entityAdded', entity) } onEntityRemovedFromRoom(entity) { this.emit('entityRemoved', entity); } onLayerAdded(layer) { layer.world = this.world; } onSizeChanged() { this.updateBounds(); } onWorldChanged() { const world = this.world; for (const layer of this.layers) { layer.world = world; for (const entity of layer.entityList) { if (entity.is('physical')) { entity.world = world; } } } // Update bounds. this.updateBounds(); } packetsForUpdate(force = false) { const packets = []; if (force) { const packed = (this.height << 16) | (this.width); packets.push(new RoomSizeUpdatePacket(packed)); } // Layers packets. const layersPacketsForUpdate = this.layers.packetsForUpdate(force); for (let i = 0; i < layersPacketsForUpdate.length; i++) { packets.push(layersPacketsForUpdate[i]); } return packets; } removeEntityFromLayer(entity, layerIndex) { this.layers.removeEntityFromLayer(entity, layerIndex); } updateBounds() { const world = this.world; if (!world) { return; } for (const bound of this.bounds) { world.removeBody(bound); } if (Vector.isZero(this.size)) { return; } this.bounds = [ // Top. world.createBody((new RectangleShape()).fromJSON({ position: [this.width / 2, -HALF_ROOM_BOUND_SIZE], size: [this.width + ROOM_BOUND_SIZE, ROOM_BOUND_SIZE], })), // Right. world.createBody((new RectangleShape()).fromJSON({ position: [this.width + HALF_ROOM_BOUND_SIZE, this.height / 2], size: [ROOM_BOUND_SIZE, this.height + ROOM_BOUND_SIZE], })), // Bottom. world.createBody((new RectangleShape()).fromJSON({ position: [this.width / 2, this.height + HALF_ROOM_BOUND_SIZE], size: [this.width + ROOM_BOUND_SIZE, ROOM_BOUND_SIZE], })), // Left. world.createBody((new RectangleShape()).fromJSON({ position: [-HALF_ROOM_BOUND_SIZE, this.height / 2], size: [ROOM_BOUND_SIZE, this.height + ROOM_BOUND_SIZE], })), ]; this.bounds.forEach((bound) => { bound.static = true; world.addBody(bound); }); } tick(elapsed) { this.layers.tick(elapsed); if (this.world) { this.world.tick(elapsed); } } visibleEntities(query) { return this.layers.visibleEntities(query); } }