import * as I from 'immutable'; import {compose, EventEmitter, Property} from '@avocado/core'; import {Vector} from '@avocado/math'; import {SynchronizedMixin} from '@avocado/net'; import {RectangleShape} from '@avocado/physics'; import {Resource} from '@avocado/resource'; import {Layers} from './layers'; import {RoomUpdateSizePacket} from './packets/room-update-size.packet'; import {RoomUpdateLayersPacket} from './packets/room-update-layers.packet'; const ROOM_BOUND_SIZE = 64; const HALF_ROOM_BOUND_SIZE = ROOM_BOUND_SIZE / 2; const decorate = compose( EventEmitter, SynchronizedMixin, Property('world', { track: true, }), Vector.Mixin('size', 'width', 'height', { default: [0, 0], track: true, }), ); let synchronizationId = 1; export class Room extends decorate(Resource) { constructor(json) { super(); this.bounds = []; this._sizeChanged = false; this._synchronizationId = synchronizationId++; const layerJSON = 'undefined' !== typeof json ? json.layers : undefined; this.layers = new Layers(layerJSON); // Listeners. this.layers.on('entityAdded', this.onEntityAddedToRoom, this); this.layers.on('entityRemoved', this.onEntityRemovedFromRoom, this); this.layers.on('layerAdded', this.onLayerAdded, this); const allEntities = this.allEntities(); for (let i = 0; i < allEntities.length; i++) { this.onEntityAddedToRoom(allEntities[i]); } this.on('sizeChanged', this.onSizeChanged, this); this.on('worldChanged', this.onWorldChanged, this); if ('undefined' !== typeof json && json.size) { this.size = json.size; } } acceptPacket(packet) { // Size update. if (packet instanceof RoomUpdateSizePacket) { this.size = packet.data.size; } // Layer updates. if (packet instanceof RoomUpdateLayersPacket) { const {layersPackets} = packet.data; for (let i = 0; i < layersPackets.length; ++i) { this.layers.acceptPacket(layersPackets[i]); } } } addEntityToLayer(entity, layerIndex = 0) { this.layers.addEntityToLayer(entity, layerIndex); } allEntities() { return this.layers.allEntities(); } cleanPackets() { super.cleanPackets(); this._sizeChanged = false; this.layers.cleanPackets(); } destroy() { super.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.setIntoRoom(this); this.emit('entityAdded', entity) } onEntityRemovedFromRoom(entity) { entity.removeFromRoom(); this.emit('entityRemoved', entity); } onLayerAdded(layer) { layer.world = this.world; } onSizeChanged() { this._sizeChanged = true; this.updateBounds(); } onWorldChanged() { const world = this.world; for (const {index, layer} of this.layers) { layer.world = world; for (const entity of layer.entityList) { if (entity.is('physical')) { entity.world = world; } } } // Update bounds. this.updateBounds(); } packets(informed) { const payload = []; if (this._sizeChanged) { payload.push(new RoomUpdateSizePacket({ size: this.size, })); } // Layer updates. const layersPackets = this.layers.packets(informed); if (layersPackets.length > 0) { payload.push(new RoomUpdateLayersPacket({ layersPackets, })); } return payload; } packetsAreIdempotent() { return false; } removeEntityFromLayer(entity, layerIndex) { this.layers.removeEntityFromLayer(entity, layerIndex); } synchronizationId() { return this._synchronizationId; } tick(elapsed) { this.layers.tick(elapsed); if (this.world) { this.world.tick(elapsed); } } toNetwork(informed) { return { layers: this.layers.toNetwork(informed), size: this.size, }; } toJSON() { return { layer: this.layers.toJSON(), size: this.size, }; } 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); }); } visibleEntities(query) { return this.layers.visibleEntities(query); } }