From f2e6adf6d7e090946ea5ac2e9cdfcd7c6dd2a3f4 Mon Sep 17 00:00:00 2001 From: Rossi Lorenzo Date: Sun, 11 Oct 2020 21:58:42 +0200 Subject: [PATCH] Add door Closes #6 --- src/app/ecs/systems/doorSystem.ts | 247 ++++++++++++++++++++++++++ src/app/ecs/systems/wallSystem.ts | 15 +- src/app/game/selectionGroup.ts | 15 ++ src/app/phase/editMap/editMapPhase.ts | 6 +- src/app/ui/ecs/compWrapper.vue | 3 +- src/app/ui/ecs/ecsDoor.vue | 88 +++++++++ 6 files changed, 368 insertions(+), 6 deletions(-) create mode 100644 src/app/ecs/systems/doorSystem.ts create mode 100644 src/app/ui/ecs/ecsDoor.vue diff --git a/src/app/ecs/systems/doorSystem.ts b/src/app/ecs/systems/doorSystem.ts new file mode 100644 index 0000000..bb8c492 --- /dev/null +++ b/src/app/ecs/systems/doorSystem.ts @@ -0,0 +1,247 @@ +import {System} from "../system"; +import {World} from "../ecs"; +import {SingleEcsStorage} from "../storage"; +import {EditMapPhase} from "../../phase/editMap/editMapPhase"; +import {Component, HideableComponent, PositionComponent} from "../component"; +import {VisibilityComponent} from "./visibilitySystem"; +import * as PointLightRender from "../../game/pointLightRenderer"; +import {DESTROY_ALL} from "../../util/pixi"; +import {newVisibilityAwareComponent, VisibilityAwareComponent} from "./visibilityAwareSystem"; +import {LightComponent, LightSettings} from "./lightSystem"; +import {Aabb} from "../../geometry/aabb"; +import PIXI from "../../PIXI"; +import {Resource} from "../resource"; +import {Line} from "../../geometry/line"; +import {WallComponent} from "./wallSystem"; +import {rotatePointByOrig} from "../../geometry/collision"; +import {EditMapDisplayPrecedence} from "../../phase/editMap/displayPrecedence"; +import {app} from "../../index"; + + +export enum DoorType { + NORMAL_LEFT = "normall", + NORMAL_RIGHT = "normalr", + ROTATE = "rotate", +} + +export interface DoorComponent extends Component, HideableComponent { + type: "door"; + doorType: DoorType; + locked: boolean; + open: boolean; + clientVisible: boolean; + _display?: PIXI.Graphics; +} + + +export class DoorSystem implements System { + readonly ecs: World; + + storage = new SingleEcsStorage('door', true, true); + phase: EditMapPhase; + + layer: PIXI.display.Layer; + displayContainer: PIXI.Container; + + constructor(ecs: World, phase: EditMapPhase) { + this.ecs = ecs; + this.phase = phase; + + this.ecs.addStorage(this.storage); + this.ecs.events.on('component_add', this.onComponentAdd, this); + this.ecs.events.on('component_edited', this.onComponentEdited, this); + this.ecs.events.on('component_remove', this.onComponentRemove, this); + } + + redrawComponent(door: DoorComponent): void { + let wall = this.ecs.getComponent(door.entity, 'wall') as WallComponent; + let pos = this.ecs.getComponent(door.entity, 'position') as PositionComponent; + + let visible = wall.visible || this.phase.lightSystem.localLightSettings.visionType === 'dm'; + if (!visible) { + door._display.clear(); + return; + } + + // TODO: use interaction shape? (might bug out on translations) + let line = new Line( + pos.x, pos.y, + pos.x + wall.vec[0], pos.y + wall.vec[1] + ); + this.drawLines(door, line); + } + + private drawLines(door: DoorComponent, line: Line): void { + let g = door._display; + if (g === undefined) return; + g.clear(); + g.lineStyle(2, 0, 0.5); + switch (door.doorType) { + case DoorType.NORMAL_LEFT: { + let r = line.distance(); + let startAngle = Math.atan2(line.toY - line.fromY, line.toX - line.fromX); + if (door.open) startAngle -= Math.PI / 2; + g.arc(line.fromX, line.fromY, r, startAngle, startAngle + Math.PI / 2); + break; + } + case DoorType.NORMAL_RIGHT: { + let r = line.distance(); + let startAngle = Math.atan2(line.fromY - line.toY, line.fromX - line.toX); + if (!door.open) startAngle -= Math.PI / 2; + g.arc(line.toX, line.toY, r, startAngle, startAngle + Math.PI / 2); + break; + } + case DoorType.ROTATE: { + let r = line.distance() / 2; + let cx = (line.fromX + line.toX) / 2; + let cy = (line.fromY + line.toY) / 2; + g.drawCircle(cx, cy, r); + break; + } + default: + console.warn("Unknown door type: " + door.doorType); + } + } + + private openDoor(door: DoorComponent, doorType: DoorType, open: boolean): void { + if (!this.ecs.isMaster) return;// This operation should only be done once! + let wall = this.ecs.getComponent(door.entity, 'wall') as WallComponent; + let pos = this.ecs.getComponent(door.entity, 'position') as PositionComponent; + + let newPos = undefined; + let newVec = undefined; + + switch (doorType) { + case DoorType.NORMAL_LEFT: { + newVec = rotatePointByOrig( + { x: 0, y: 0 }, + Math.PI / 2 * (open ? -1 : 1), + { x: wall.vec[0], y: wall.vec[1] } + ); + + break; + } + case DoorType.NORMAL_RIGHT: { + let angle = Math.PI / 2 * (open ? 1 : -1); + newPos = rotatePointByOrig( + { x: pos.x + wall.vec[0], y: pos.y + wall.vec[1] }, + angle, + { x: pos.x, y: pos.y } + ); + + newVec = rotatePointByOrig( + { x: 0, y: 0 }, + angle, + { x: wall.vec[0], y: wall.vec[1] } + ) + + break; + } + case DoorType.ROTATE: { + let hvx = wall.vec[0] / 2; + let hvy = wall.vec[1] / 2; + let origin = { x: pos.x + hvx, y: pos.y + hvy }; + let angle = Math.PI / 2 * (open ? 1 : -1); + newPos = rotatePointByOrig( + origin, angle, + { x: pos.x, y: pos.y }, + ); + newVec = rotatePointByOrig( + origin, angle, + { x: pos.x + wall.vec[0], y: pos.y + wall.vec[1] } + ); + newVec.x -= newPos.x; + newVec.y -= newPos.y; + + break; + } + default: + console.warn("Unknown door type: " + doorType); + } + + if (newPos !== undefined) { + this.ecs.editComponent(door.entity, 'position', { + x: newPos.x, + y: newPos.y, + }); + } + if (newVec !== undefined) { + this.ecs.editComponent(door.entity, 'wall', { + vec: [newVec.x, newVec.y], + }); + } + } + + + private onComponentAdd(comp: Component): void { + if (comp.type === 'door') { + let d = comp as DoorComponent; + d._display = new PIXI.Graphics(); + this.displayContainer.addChild(d._display); + if (d.open) { + this.openDoor(d, d.doorType, true); + } + this.redrawComponent(d); + + let wall = this.ecs.getComponent(comp.entity, 'wall') as WallComponent; + wall._dontMerge++; + } + } + + + private onComponentEdited(comp: Component, changed: any): void { + if (comp.type === 'door') { + let d = comp as DoorComponent; + if ('open' in changed) { + this.openDoor(d, d.doorType, d.open); + } + if ('doorType' in changed) { + if (d.open) { + this.openDoor(d, changed['doorType'], false); + this.openDoor(d, d.doorType, true); + } + } + this.redrawComponent(d); + } else if (comp.type === 'wall') { + let d = this.storage.getComponent(comp.entity); + if (d !== undefined) this.redrawComponent(d); + } else if (comp.type === 'position') { + let d = this.storage.getComponent(comp.entity); + if (d !== undefined) this.redrawComponent(d); + } + } + + private onComponentRemove(comp: Component): void { + if (comp.type === 'door') { + let d = comp as DoorComponent; + + if (d.open) { + this.openDoor(d, d.doorType, false); + } + + d._display.destroy(DESTROY_ALL); + + let wall = this.ecs.getComponent(comp.entity, 'wall') as WallComponent; + wall._dontMerge--; + } + } + + + enable(): void { + this.layer = new PIXI.display.Layer(); + this.layer.zIndex = EditMapDisplayPrecedence.WALL + 1; + this.layer.interactive = false; + app.stage.addChild(this.layer); + + this.displayContainer = new PIXI.Container(); + this.displayContainer.parentLayer = this.layer; + this.displayContainer.interactive = false; + this.displayContainer.interactiveChildren = false; + this.phase.board.addChild(this.displayContainer); + } + + destroy(): void { + this.displayContainer.destroy(DESTROY_ALL); + this.layer.destroy(DESTROY_ALL); + } +} \ No newline at end of file diff --git a/src/app/ecs/systems/wallSystem.ts b/src/app/ecs/systems/wallSystem.ts index 916394c..a024145 100644 --- a/src/app/ecs/systems/wallSystem.ts +++ b/src/app/ecs/systems/wallSystem.ts @@ -23,6 +23,7 @@ export interface WallComponent extends Component { visible: boolean, _display: PIXI.Graphics; _selected?: boolean; + _dontMerge?: number; } const SELECTION_COLOR = 0x7986CB; @@ -68,6 +69,7 @@ export class WallSystem implements System { console.warn("Found wall without position, please add first the position, then the wall"); return; } + wall._dontMerge = 0; if (wall.visible === undefined) wall.visible = false; this.addWallDisplay(pos, wall); @@ -118,6 +120,7 @@ export class WallSystem implements System { }; for (let wall of walls) { + if (wall._dontMerge !== 0) continue; let pos = posStorage.getComponent(wall.entity); let p1 = pos.x + "@" + pos.y; @@ -184,10 +187,6 @@ export class WallSystem implements System { insertInIndex(p1, wall); insertInIndex(p2, wall); } - - - // TODO: we need to remove the intersection breaks from the selected walls and also to - // remove them from the unselected walls (?). } fixWallPostTranslation(walls: WallComponent[]) { @@ -266,6 +265,14 @@ export class WallSystem implements System { this.fixWallPreTranslation([wall]); this.fixWallPostTranslation([wall]); } + if (comp.type === 'wall' && 'vec' in changed) { + this.ecs.editComponent(wall.entity, 'interaction', { + shape: shapeLine(new Line( + position.x, position.y, + position.x + wall.vec[0], position.y + wall.vec[1], + )), + }); + } if (comp.type === 'position') { wall._display.position.set(position.x, position.y); diff --git a/src/app/game/selectionGroup.ts b/src/app/game/selectionGroup.ts index cc9511e..3941149 100644 --- a/src/app/game/selectionGroup.ts +++ b/src/app/game/selectionGroup.ts @@ -2,6 +2,7 @@ import {World} from "../ecs/ecs"; import {Component, NameComponent, NoteComponent, PositionComponent} from "../ecs/component"; import {LightComponent} from "../ecs/systems/lightSystem"; import {PlayerComponent} from "../ecs/systems/playerSystem"; +import {DoorComponent, DoorType} from "../ecs/systems/doorSystem"; const MULTI_TYPES = ['name', 'note']; const ELIMINABLE_TYPES = ['name', 'note', 'player', 'light']; @@ -253,6 +254,11 @@ export class SelectionGroup { type: 'player', name: 'Player', }); + } else if (this.hasEveryoneType('wall') && !this.hasComponentType('door')) { + res.push({ + type: "door", + name: "Door" + }); } return res; @@ -313,6 +319,15 @@ export class SelectionGroup { range: 50, } as PlayerComponent; break; + case 'door': + comp = { + type: 'door', + doorType: DoorType.NORMAL_LEFT, + locked: false, + open: false, + clientVisible: true, + } as DoorComponent; + break; default: throw 'Cannot add unknown component: ' + propertyValue; } for (let entity of [...this.selectedEntities]) { diff --git a/src/app/phase/editMap/editMapPhase.ts b/src/app/phase/editMap/editMapPhase.ts index 636eb9a..5a9c060 100644 --- a/src/app/phase/editMap/editMapPhase.ts +++ b/src/app/phase/editMap/editMapPhase.ts @@ -3,7 +3,6 @@ import {BirdEyePhase} from "../birdEyePhase"; import PIXI from "../../PIXI"; import {app, stage} from "../../index"; import {BackgroundSystem} from "../../ecs/systems/backgroundSystem"; -import {PointDB} from "../../game/pointDB"; import {SelectionGroup} from "../../game/selectionGroup"; import {NetworkManager} from "../../network/networkManager"; import {HostEditMapPhase} from "./hostEditMapPhase"; @@ -17,6 +16,7 @@ import {VisibilitySystem} from "../../ecs/systems/visibilitySystem"; import {InteractionSystem} from "../../ecs/systems/interactionSystem"; import {PlayerSystem} from "../../ecs/systems/playerSystem"; import {VisibilityAwareSystem} from "../../ecs/systems/visibilityAwareSystem"; +import {DoorSystem} from "../../ecs/systems/doorSystem"; export class EditMapPhase extends BirdEyePhase { @@ -29,6 +29,7 @@ export class EditMapPhase extends BirdEyePhase { backgroundSystem: BackgroundSystem; textSystem: TextSystem; wallSystem: WallSystem; + doorSystem: DoorSystem; visibilitySystem: VisibilitySystem; visibilityAwareSystem: VisibilityAwareSystem; pinSystem: PinSystem; @@ -57,6 +58,7 @@ export class EditMapPhase extends BirdEyePhase { this.backgroundSystem = new BackgroundSystem(this.ecs, this); this.textSystem = new TextSystem(this.ecs); this.wallSystem = new WallSystem(this.ecs, this); + this.doorSystem = new DoorSystem(this.ecs, this); this.visibilitySystem = new VisibilitySystem(this.ecs, this); this.visibilityAwareSystem = new VisibilityAwareSystem(this.ecs, this); this.pinSystem = new PinSystem(this.ecs, this); @@ -284,6 +286,7 @@ export class EditMapPhase extends BirdEyePhase { this.backgroundSystem.enable(); this.textSystem.enable(); this.wallSystem.enable(); + this.doorSystem.enable(); this.visibilitySystem.enable(); this.visibilityAwareSystem.enable(); this.pinSystem.enable(); @@ -297,6 +300,7 @@ export class EditMapPhase extends BirdEyePhase { this.pinSystem.destroy(); this.visibilityAwareSystem.destroy(); this.visibilitySystem.destroy(); + this.doorSystem.destroy(); this.wallSystem.destroy(); this.textSystem.destroy(); this.backgroundSystem.destroy(); diff --git a/src/app/ui/ecs/compWrapper.vue b/src/app/ui/ecs/compWrapper.vue index dbf916d..8278af9 100644 --- a/src/app/ui/ecs/compWrapper.vue +++ b/src/app/ui/ecs/compWrapper.vue @@ -39,6 +39,7 @@ import ecsTransform from "./ecsTransform.vue"; import ecsLight from "./ecsLight.vue"; import ecsPlayer from "./ecsPlayer.vue"; + import ecsDoor from "./ecsDoor.vue"; export default { name: "ecs-component-wrapper", @@ -54,7 +55,7 @@ } }, components: { - ecsName, ecsNote, ecsPosition, ecsWall, ecsBackgroundImage, ecsPin, ecsTransform, ecsLight, ecsPlayer + ecsName, ecsNote, ecsPosition, ecsWall, ecsBackgroundImage, ecsPin, ecsTransform, ecsLight, ecsPlayer, ecsDoor } } diff --git a/src/app/ui/ecs/ecsDoor.vue b/src/app/ui/ecs/ecsDoor.vue new file mode 100644 index 0000000..07b147d --- /dev/null +++ b/src/app/ui/ecs/ecsDoor.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file