</div>
</div>
-<div id="cell-editor-container">
-</div>
+<div id="cell-editor-container" class="editor-container hidden"></div>
+
+<div id="object-editor-container" class="editor-container hidden"></div>
</body>
</html>
import GameMap from './map';
import CellEditor, { CellEditorCallback } from './celleditor';
+import ObjectEditor from './objecteditor';
import MapView from './mapview';
import TextView from './textview';
import Actions from './actions';
textView: TextView;
cellEditor: CellEditor;
+ objectEditor: ObjectEditor;
mapView: MapView;
actions: Actions;
editMode: boolean = true;
this.textView = new TextView();
this.cellEditor = new CellEditor();
+ this.objectEditor = new ObjectEditor();
this.mapView = new MapView();
this.mapView.updateAll();
this.actions = new Actions();
if (this.editMode) {
this.actions.addAction('Toggle Navigable', () => this.map.toggleNavigable());
this.actions.addAction('Edit Cell', () => this.map.editDescription());
+ this.actions.addAction('Object Editor', () => this.objectEditor.open());
}
}
this.printCellText();
this.showCellActions();
}
+
+ addObject(o: GameObject) {
+ this.objects.set(o.id, o);
+ }
+
+ removeObject(o: GameObject | ID) {
+ if (o instanceof GameObject) {
+ this.objects.delete(o.id);
+ } else {
+ this.objects.delete(o);
+ }
+ }
}
// Create a default GameState object and export it
e.preventDefault();
gs.map.editDescription();
break;
+ case 'KeyO':
+ e.preventDefault();
+ gs.objectEditor.open();
+ break;
case 'KeyS':
localStorage['map'] = JSON.stringify(gs.map.store());
console.log(localStorage['map']);
export interface GameObjectProperties {
id: ID
type: GameObjectType
+ name: string
+ description: string
+}
+
+interface PropertyBag {
+ [k: string]: number | string
}
export class GameObject extends Storable {
id: ID
type: GameObjectType
- x: number
- y: number
- saveProperties = ['type']
+ name: string
+ description: string
+ properties: PropertyBag
+ saveProperties = ['id', 'type', 'properties']
constructor(t: GameObjectType = GameObjectType.Unknown) {
super();
this.id = generateID();
this.type = t;
+ this.objectProperties = {};
+ }
+
+ set(k: string, v: number | string) {
+ this.properties[k] = v;
}
- moveTo(x: number, y: number) {
- this.x = x;
- this.y = y;
+ get(k: string): number | string {
+ return this.properties[k];
}
}
+import FragmentManager from './fragmentmanager';
+import { GameObject } from './object';
+import { ID } from './id';
+import gs from './gamestate';
+
+export default class ObjectEditor {
+ container: FragmentManager
+ listRef: HTMLSelectElement
+ idRef: HTMLInputElement
+ nameRef: HTMLInputElement
+ descriptionRef: HTMLTextAreaElement
+ originalID: ID
+
+ constructor() {
+ this.container = new FragmentManager(`
+ <div class="box vflex">
+ <h1>Object Editor</h1>
+ <div class="hflex flex-auto">
+ <div class="vflex" style="width: 200px; margin-right: 8px">
+ <select multiple name="objectlist" class="flex-auto" style="width: calc(100% - 8px)">
+ </select>
+ <div class="hflex">
+ <button id="object-add" class="flex-auto">+</button>
+ <button id="object-remove" class="flex-auto">-</button>
+ </div>
+ </div>
+ <div class="vflex flex-auto">
+ <h2>ID</h2>
+ <input name="id">
+ <h2>Name</h2>
+ <input name="name">
+ <h2>Description</h2>
+ <textarea name="description" class="flex-auto"></textarea>
+ </div>
+ </div>
+ <div class="hflex">
+ <button id="object-editor-close">Close</button>
+ </div>
+ </div>
+ `);
+ this.container.mountInto('object-editor-container');
+
+ this.container.elem.addEventListener('keydown', function(e: KeyboardEvent) {
+ e.cancelBubble = true;
+ });
+
+ this.listRef = <HTMLSelectElement>this.container.q('[name="objectlist"]');
+ this.idRef = <HTMLInputElement>this.container.q('[name="id"]');
+ this.nameRef = <HTMLInputElement>this.container.q('[name="name"]');
+ this.descriptionRef = <HTMLTextAreaElement>this.container.q('[name=description]');
+
+ const closeButton = this.container.q('#object-editor-close');
+ closeButton.addEventListener('click', this.closeHandler.bind(this));
+
+ const objectAddButton = this.container.q('#object-add');
+ objectAddButton.addEventListener('click', this.createObject.bind(this));
+
+ const objectRemoveButton = this.container.q('#object-remove');
+ objectRemoveButton.addEventListener('click', this.deleteObject.bind(this));
+
+ this.listRef.addEventListener('change', event => {
+ const id = (<HTMLInputElement>event.target).value;
+ this.commitObject();
+ this.selectObject(id);
+ });
+ }
+
+ open(object?: GameObject) {
+ this.refreshObjectList();
+
+ if (object) {
+ this.selectObject(object.id);
+ }
+
+ this.container.elem.classList.add('visible');
+ this.nameRef.focus();
+ }
+
+ refreshObjectList() {
+ while (this.listRef.firstChild)
+ this.listRef.removeChild(this.listRef.firstChild);
+
+ for (let [k, v] of gs.objects) {
+ const option = document.createElement('option');
+ option.value = k;
+ option.innerText = v.name;
+ this.listRef.appendChild(option);
+ }
+ }
+
+ selectObject(id?: ID) {
+ if (id) {
+ if (!gs.objects.has(id)) {
+ throw new Error('Invalid object ID: ' + id);
+ }
+ }
+
+ for (let i = 0; i < this.listRef.children.length; i++) {
+ const child = <HTMLOptionElement>this.listRef.children[i];
+ child.selected = child.value == id;
+ }
+
+ if (id) {
+ const object = gs.objects.get(id);
+
+ this.idRef.value = this.originalID = object.id;
+ this.nameRef.value = object.name || 'Unnamed Object';
+ this.descriptionRef.value = object.description || '';
+ } else {
+ this.originalID = undefined;
+ this.idRef.value = '';
+ this.nameRef.value = '';
+ this.descriptionRef.value = '';
+ }
+ }
+
+ createObject() {
+ this.commitObject();
+ const obj = new GameObject();
+ obj.name = 'Unnamed Object';
+ gs.addObject(obj);
+ this.refreshObjectList();
+ this.selectObject(obj.id);
+ }
+
+ deleteObject() {
+ this.commitObject();
+ const objID = this.originalID;
+ const obj = gs.objects.get(objID);
+ if (window.confirm("Really delete " + obj.name + "?")) {
+ gs.removeObject(objID);
+ this.refreshObjectList();
+ this.selectObject(null);
+ }
+ }
+
+ commitObject() {
+ if (!this.originalID) return;
+ const obj = gs.objects.get(this.originalID);
+ obj.name = this.nameRef.value;
+ obj.description = this.descriptionRef.value;
+
+ const objID = this.idRef.value
+ if (objID != this.originalID) {
+ gs.removeObject(this.originalID);
+ obj.id = objID;
+ gs.addObject(obj);
+ this.refreshObjectList();
+ this.selectObject(objID);
+ } else {
+ this.refreshObjectList();
+ }
+ }
+
+ close() {
+ this.closeHandler();
+ }
+
+ closeHandler() {
+ this.commitObject();
+ this.container.elem.classList.remove('visible');
+ }
+}
flex: auto;
}
+.hidden {
+ display: none;
+}
+
+.visible {
+ display: initial;
+}
+
#map-container {
position: relative;
border: 1px solid green;
padding: 16px;
}
-#cell-editor-container {
- display: none;
+.editor-container {
position: fixed;
left: 0;
top: 0;
height: 100vh;
}
-#cell-editor-container .box {
+.editor-container .box {
width: 800px;
height: 600px;
margin-top: calc(50vh - 300px);
background-color: black;
}
-#cell-editor-container.visible {
- display: initial;
-}
-
-#cell-editor-container textarea[name="description"] {
+.editor-container textarea[name="description"] {
height: 80px;
}