Fix being able to create constant signals without value

pull/609/head
tobspr 4 years ago
parent 7cbe5bb8e7
commit a67448fbdf

@ -1,434 +1,443 @@
/* typehints:start */
import { Application } from "../application";
/* typehints:end */
import { Signal, STOP_PROPAGATION } from "./signal";
import { arrayDeleteValue, waitNextFrame } from "./utils";
import { ClickDetector } from "./click_detector";
import { SOUNDS } from "../platform/sound";
import { InputReceiver } from "./input_receiver";
import { FormElement } from "./modal_dialog_forms";
import { globalConfig } from "./config";
import { getStringForKeyCode } from "../game/key_action_mapper";
import { createLogger } from "./logging";
import { T } from "../translations";
const kbEnter = 13;
const kbCancel = 27;
const logger = createLogger("dialogs");
/**
* Basic text based dialog
*/
export class Dialog {
/**
*
* Constructs a new dialog with the given options
* @param {object} param0
* @param {Application} param0.app
* @param {string} param0.title Title of the dialog
* @param {string} param0.contentHTML Inner dialog html
* @param {Array<string>} param0.buttons
* Button list, each button contains of up to 3 parts seperated by ':'.
* Part 0: The id, one of the one defined in dialog_buttons.yaml
* Part 1: The style, either good, bad or misc
* Part 2 (optional): Additional parameters seperated by '/', available are:
* timeout: This button is only available after some waiting time
* kb_enter: This button is triggered by the enter key
* kb_escape This button is triggered by the escape key
* @param {string=} param0.type The dialog type, either "info" or "warn"
* @param {boolean=} param0.closeButton Whether this dialog has a close button
*/
constructor({ app, title, contentHTML, buttons, type = "info", closeButton = false }) {
this.app = app;
this.title = title;
this.contentHTML = contentHTML;
this.type = type;
this.buttonIds = buttons;
this.closeButton = closeButton;
this.closeRequested = new Signal();
this.buttonSignals = {};
for (let i = 0; i < buttons.length; ++i) {
if (G_IS_DEV && globalConfig.debug.disableTimedButtons) {
this.buttonIds[i] = this.buttonIds[i].replace(":timeout", "");
}
const buttonId = this.buttonIds[i].split(":")[0];
this.buttonSignals[buttonId] = new Signal();
}
this.timeouts = [];
this.clickDetectors = [];
this.inputReciever = new InputReceiver("dialog-" + this.title);
this.inputReciever.keydown.add(this.handleKeydown, this);
this.enterHandler = null;
this.escapeHandler = null;
}
/**
* Internal keydown handler
* @param {object} param0
* @param {number} param0.keyCode
* @param {boolean} param0.shift
* @param {boolean} param0.alt
*/
handleKeydown({ keyCode, shift, alt }) {
if (keyCode === kbEnter && this.enterHandler) {
this.internalButtonHandler(this.enterHandler);
return STOP_PROPAGATION;
}
if (keyCode === kbCancel && this.escapeHandler) {
this.internalButtonHandler(this.escapeHandler);
return STOP_PROPAGATION;
}
}
internalButtonHandler(id, ...payload) {
this.app.inputMgr.popReciever(this.inputReciever);
if (id !== "close-button") {
this.buttonSignals[id].dispatch(...payload);
}
this.closeRequested.dispatch();
}
createElement() {
const elem = document.createElement("div");
elem.classList.add("ingameDialog");
this.dialogElem = document.createElement("div");
this.dialogElem.classList.add("dialogInner");
if (this.type) {
this.dialogElem.classList.add(this.type);
}
elem.appendChild(this.dialogElem);
const title = document.createElement("h1");
title.innerText = this.title;
title.classList.add("title");
this.dialogElem.appendChild(title);
if (this.closeButton) {
this.dialogElem.classList.add("hasCloseButton");
const closeBtn = document.createElement("button");
closeBtn.classList.add("closeButton");
this.trackClicks(closeBtn, () => this.internalButtonHandler("close-button"), {
applyCssClass: "pressedSmallElement",
});
title.appendChild(closeBtn);
this.inputReciever.backButton.add(() => this.internalButtonHandler("close-button"));
}
const content = document.createElement("div");
content.classList.add("content");
content.innerHTML = this.contentHTML;
this.dialogElem.appendChild(content);
if (this.buttonIds.length > 0) {
const buttons = document.createElement("div");
buttons.classList.add("buttons");
// Create buttons
for (let i = 0; i < this.buttonIds.length; ++i) {
const [buttonId, buttonStyle, rawParams] = this.buttonIds[i].split(":");
const button = document.createElement("button");
button.classList.add("button");
button.classList.add("styledButton");
button.classList.add(buttonStyle);
button.innerText = T.dialogs.buttons[buttonId];
const params = (rawParams || "").split("/");
const useTimeout = params.indexOf("timeout") >= 0;
const isEnter = params.indexOf("enter") >= 0;
const isEscape = params.indexOf("escape") >= 0;
if (isEscape && this.closeButton) {
logger.warn("Showing dialog with close button, and additional cancel button");
}
if (useTimeout) {
button.classList.add("timedButton");
const timeout = setTimeout(() => {
button.classList.remove("timedButton");
arrayDeleteValue(this.timeouts, timeout);
}, 5000);
this.timeouts.push(timeout);
}
if (isEnter || isEscape) {
// if (this.app.settings.getShowKeyboardShortcuts()) {
// Show keybinding
const spacer = document.createElement("code");
spacer.classList.add("keybinding");
spacer.innerHTML = getStringForKeyCode(isEnter ? kbEnter : kbCancel);
button.appendChild(spacer);
// }
if (isEnter) {
this.enterHandler = buttonId;
}
if (isEscape) {
this.escapeHandler = buttonId;
}
}
this.trackClicks(button, () => this.internalButtonHandler(buttonId));
buttons.appendChild(button);
}
this.dialogElem.appendChild(buttons);
} else {
this.dialogElem.classList.add("buttonless");
}
this.element = elem;
this.app.inputMgr.pushReciever(this.inputReciever);
return this.element;
}
setIndex(index) {
this.element.style.zIndex = index;
}
destroy() {
if (!this.element) {
assert(false, "Tried to destroy dialog twice");
return;
}
// We need to do this here, because if the backbutton event gets
// dispatched to the modal dialogs, it will not call the internalButtonHandler,
// and thus our receiver stays attached the whole time
this.app.inputMgr.destroyReceiver(this.inputReciever);
for (let i = 0; i < this.clickDetectors.length; ++i) {
this.clickDetectors[i].cleanup();
}
this.clickDetectors = [];
this.element.remove();
this.element = null;
for (let i = 0; i < this.timeouts.length; ++i) {
clearTimeout(this.timeouts[i]);
}
this.timeouts = [];
}
hide() {
this.element.classList.remove("visible");
}
show() {
this.element.classList.add("visible");
}
/**
* Helper method to track clicks on an element
* @param {Element} elem
* @param {function():void} handler
* @param {import("./click_detector").ClickDetectorConstructorArgs=} args
* @returns {ClickDetector}
*/
trackClicks(elem, handler, args = {}) {
const detector = new ClickDetector(elem, args);
detector.click.add(handler, this);
this.clickDetectors.push(detector);
return detector;
}
}
/**
* Dialog which simply shows a loading spinner
*/
export class DialogLoading extends Dialog {
constructor(app) {
super({
app,
title: "",
contentHTML: "",
buttons: [],
type: "loading",
});
// Loading dialog can not get closed with back button
this.inputReciever.backButton.removeAll();
this.inputReciever.context = "dialog-loading";
}
createElement() {
const elem = document.createElement("div");
elem.classList.add("ingameDialog");
elem.classList.add("loadingDialog");
this.element = elem;
const loader = document.createElement("div");
loader.classList.add("prefab_LoadingTextWithAnim");
loader.classList.add("loadingIndicator");
loader.innerText = T.global.loading;
elem.appendChild(loader);
this.app.inputMgr.pushReciever(this.inputReciever);
return elem;
}
}
export class DialogOptionChooser extends Dialog {
constructor({ app, title, options }) {
let html = "<div class='optionParent'>";
options.options.forEach(({ value, text, desc = null, iconPrefix = null }) => {
const descHtml = desc ? `<span class="desc">${desc}</span>` : "";
let iconHtml = iconPrefix ? `<span class="icon icon-${iconPrefix}-${value}"></span>` : "";
html += `
<div class='option ${value === options.active ? "active" : ""} ${
iconPrefix ? "hasIcon" : ""
}' data-optionvalue='${value}'>
${iconHtml}
<span class='title'>${text}</span>
${descHtml}
</div>
`;
});
html += "</div>";
super({
app,
title,
contentHTML: html,
buttons: [],
type: "info",
closeButton: true,
});
this.options = options;
this.initialOption = options.active;
this.buttonSignals.optionSelected = new Signal();
}
createElement() {
const div = super.createElement();
this.dialogElem.classList.add("optionChooserDialog");
div.querySelectorAll("[data-optionvalue]").forEach(handle => {
const value = handle.getAttribute("data-optionvalue");
if (!handle) {
logger.error("Failed to bind option value in dialog:", value);
return;
}
// Need click detector here to forward elements, otherwise scrolling does not work
const detector = new ClickDetector(handle, {
consumeEvents: false,
preventDefault: false,
clickSound: null,
applyCssClass: "pressedOption",
targetOnly: true,
});
this.clickDetectors.push(detector);
if (value !== this.initialOption) {
detector.click.add(() => {
const selected = div.querySelector(".option.active");
if (selected) {
selected.classList.remove("active");
} else {
logger.warn("No selected option");
}
handle.classList.add("active");
this.app.sound.playUiSound(SOUNDS.uiClick);
this.internalButtonHandler("optionSelected", value);
});
}
});
return div;
}
}
export class DialogWithForm extends Dialog {
/**
*
* @param {object} param0
* @param {Application} param0.app
* @param {string} param0.title
* @param {string} param0.desc
* @param {array=} param0.buttons
* @param {string=} param0.confirmButtonId
* @param {string=} param0.extraButton
* @param {Array<FormElement>} param0.formElements
*/
constructor({ app, title, desc, formElements, buttons = ["cancel", "ok:good"], confirmButtonId = "ok" }) {
let html = "";
html += desc + "<br>";
for (let i = 0; i < formElements.length; ++i) {
html += formElements[i].getHtml();
}
super({
app,
title: title,
contentHTML: html,
buttons: buttons,
type: "info",
closeButton: true,
});
this.confirmButtonId = confirmButtonId;
this.formElements = formElements;
this.enterHandler = confirmButtonId;
}
internalButtonHandler(id, ...payload) {
if (id === this.confirmButtonId) {
if (this.hasAnyInvalid()) {
this.dialogElem.classList.remove("errorShake");
waitNextFrame().then(() => {
if (this.dialogElem) {
this.dialogElem.classList.add("errorShake");
}
});
this.app.sound.playUiSound(SOUNDS.uiError);
return;
}
}
super.internalButtonHandler(id, payload);
}
hasAnyInvalid() {
for (let i = 0; i < this.formElements.length; ++i) {
if (!this.formElements[i].isValid()) {
return true;
}
}
return false;
}
createElement() {
const div = super.createElement();
for (let i = 0; i < this.formElements.length; ++i) {
const elem = this.formElements[i];
elem.bindEvents(div, this.clickDetectors);
}
waitNextFrame().then(() => {
this.formElements[0].focus();
});
return div;
}
}
/* typehints:start */
import { Application } from "../application";
/* typehints:end */
import { Signal, STOP_PROPAGATION } from "./signal";
import { arrayDeleteValue, waitNextFrame } from "./utils";
import { ClickDetector } from "./click_detector";
import { SOUNDS } from "../platform/sound";
import { InputReceiver } from "./input_receiver";
import { FormElement } from "./modal_dialog_forms";
import { globalConfig } from "./config";
import { getStringForKeyCode } from "../game/key_action_mapper";
import { createLogger } from "./logging";
import { T } from "../translations";
const kbEnter = 13;
const kbCancel = 27;
const logger = createLogger("dialogs");
/**
* Basic text based dialog
*/
export class Dialog {
/**
*
* Constructs a new dialog with the given options
* @param {object} param0
* @param {Application} param0.app
* @param {string} param0.title Title of the dialog
* @param {string} param0.contentHTML Inner dialog html
* @param {Array<string>} param0.buttons
* Button list, each button contains of up to 3 parts seperated by ':'.
* Part 0: The id, one of the one defined in dialog_buttons.yaml
* Part 1: The style, either good, bad or misc
* Part 2 (optional): Additional parameters seperated by '/', available are:
* timeout: This button is only available after some waiting time
* kb_enter: This button is triggered by the enter key
* kb_escape This button is triggered by the escape key
* @param {string=} param0.type The dialog type, either "info" or "warn"
* @param {boolean=} param0.closeButton Whether this dialog has a close button
*/
constructor({ app, title, contentHTML, buttons, type = "info", closeButton = false }) {
this.app = app;
this.title = title;
this.contentHTML = contentHTML;
this.type = type;
this.buttonIds = buttons;
this.closeButton = closeButton;
this.closeRequested = new Signal();
this.buttonSignals = {};
for (let i = 0; i < buttons.length; ++i) {
if (G_IS_DEV && globalConfig.debug.disableTimedButtons) {
this.buttonIds[i] = this.buttonIds[i].replace(":timeout", "");
}
const buttonId = this.buttonIds[i].split(":")[0];
this.buttonSignals[buttonId] = new Signal();
}
this.timeouts = [];
this.clickDetectors = [];
this.inputReciever = new InputReceiver("dialog-" + this.title);
this.inputReciever.keydown.add(this.handleKeydown, this);
this.enterHandler = null;
this.escapeHandler = null;
}
/**
* Internal keydown handler
* @param {object} param0
* @param {number} param0.keyCode
* @param {boolean} param0.shift
* @param {boolean} param0.alt
*/
handleKeydown({ keyCode, shift, alt }) {
if (keyCode === kbEnter && this.enterHandler) {
this.internalButtonHandler(this.enterHandler);
return STOP_PROPAGATION;
}
if (keyCode === kbCancel && this.escapeHandler) {
this.internalButtonHandler(this.escapeHandler);
return STOP_PROPAGATION;
}
}
internalButtonHandler(id, ...payload) {
this.app.inputMgr.popReciever(this.inputReciever);
if (id !== "close-button") {
this.buttonSignals[id].dispatch(...payload);
}
this.closeRequested.dispatch();
}
createElement() {
const elem = document.createElement("div");
elem.classList.add("ingameDialog");
this.dialogElem = document.createElement("div");
this.dialogElem.classList.add("dialogInner");
if (this.type) {
this.dialogElem.classList.add(this.type);
}
elem.appendChild(this.dialogElem);
const title = document.createElement("h1");
title.innerText = this.title;
title.classList.add("title");
this.dialogElem.appendChild(title);
if (this.closeButton) {
this.dialogElem.classList.add("hasCloseButton");
const closeBtn = document.createElement("button");
closeBtn.classList.add("closeButton");
this.trackClicks(closeBtn, () => this.internalButtonHandler("close-button"), {
applyCssClass: "pressedSmallElement",
});
title.appendChild(closeBtn);
this.inputReciever.backButton.add(() => this.internalButtonHandler("close-button"));
}
const content = document.createElement("div");
content.classList.add("content");
content.innerHTML = this.contentHTML;
this.dialogElem.appendChild(content);
if (this.buttonIds.length > 0) {
const buttons = document.createElement("div");
buttons.classList.add("buttons");
// Create buttons
for (let i = 0; i < this.buttonIds.length; ++i) {
const [buttonId, buttonStyle, rawParams] = this.buttonIds[i].split(":");
const button = document.createElement("button");
button.classList.add("button");
button.classList.add("styledButton");
button.classList.add(buttonStyle);
button.innerText = T.dialogs.buttons[buttonId];
const params = (rawParams || "").split("/");
const useTimeout = params.indexOf("timeout") >= 0;
const isEnter = params.indexOf("enter") >= 0;
const isEscape = params.indexOf("escape") >= 0;
if (isEscape && this.closeButton) {
logger.warn("Showing dialog with close button, and additional cancel button");
}
if (useTimeout) {
button.classList.add("timedButton");
const timeout = setTimeout(() => {
button.classList.remove("timedButton");
arrayDeleteValue(this.timeouts, timeout);
}, 5000);
this.timeouts.push(timeout);
}
if (isEnter || isEscape) {
// if (this.app.settings.getShowKeyboardShortcuts()) {
// Show keybinding
const spacer = document.createElement("code");
spacer.classList.add("keybinding");
spacer.innerHTML = getStringForKeyCode(isEnter ? kbEnter : kbCancel);
button.appendChild(spacer);
// }
if (isEnter) {
this.enterHandler = buttonId;
}
if (isEscape) {
this.escapeHandler = buttonId;
}
}
this.trackClicks(button, () => this.internalButtonHandler(buttonId));
buttons.appendChild(button);
}
this.dialogElem.appendChild(buttons);
} else {
this.dialogElem.classList.add("buttonless");
}
this.element = elem;
this.app.inputMgr.pushReciever(this.inputReciever);
return this.element;
}
setIndex(index) {
this.element.style.zIndex = index;
}
destroy() {
if (!this.element) {
assert(false, "Tried to destroy dialog twice");
return;
}
// We need to do this here, because if the backbutton event gets
// dispatched to the modal dialogs, it will not call the internalButtonHandler,
// and thus our receiver stays attached the whole time
this.app.inputMgr.destroyReceiver(this.inputReciever);
for (let i = 0; i < this.clickDetectors.length; ++i) {
this.clickDetectors[i].cleanup();
}
this.clickDetectors = [];
this.element.remove();
this.element = null;
for (let i = 0; i < this.timeouts.length; ++i) {
clearTimeout(this.timeouts[i]);
}
this.timeouts = [];
}
hide() {
this.element.classList.remove("visible");
}
show() {
this.element.classList.add("visible");
}
/**
* Helper method to track clicks on an element
* @param {Element} elem
* @param {function():void} handler
* @param {import("./click_detector").ClickDetectorConstructorArgs=} args
* @returns {ClickDetector}
*/
trackClicks(elem, handler, args = {}) {
const detector = new ClickDetector(elem, args);
detector.click.add(handler, this);
this.clickDetectors.push(detector);
return detector;
}
}
/**
* Dialog which simply shows a loading spinner
*/
export class DialogLoading extends Dialog {
constructor(app) {
super({
app,
title: "",
contentHTML: "",
buttons: [],
type: "loading",
});
// Loading dialog can not get closed with back button
this.inputReciever.backButton.removeAll();
this.inputReciever.context = "dialog-loading";
}
createElement() {
const elem = document.createElement("div");
elem.classList.add("ingameDialog");
elem.classList.add("loadingDialog");
this.element = elem;
const loader = document.createElement("div");
loader.classList.add("prefab_LoadingTextWithAnim");
loader.classList.add("loadingIndicator");
loader.innerText = T.global.loading;
elem.appendChild(loader);
this.app.inputMgr.pushReciever(this.inputReciever);
return elem;
}
}
export class DialogOptionChooser extends Dialog {
constructor({ app, title, options }) {
let html = "<div class='optionParent'>";
options.options.forEach(({ value, text, desc = null, iconPrefix = null }) => {
const descHtml = desc ? `<span class="desc">${desc}</span>` : "";
let iconHtml = iconPrefix ? `<span class="icon icon-${iconPrefix}-${value}"></span>` : "";
html += `
<div class='option ${value === options.active ? "active" : ""} ${
iconPrefix ? "hasIcon" : ""
}' data-optionvalue='${value}'>
${iconHtml}
<span class='title'>${text}</span>
${descHtml}
</div>
`;
});
html += "</div>";
super({
app,
title,
contentHTML: html,
buttons: [],
type: "info",
closeButton: true,
});
this.options = options;
this.initialOption = options.active;
this.buttonSignals.optionSelected = new Signal();
}
createElement() {
const div = super.createElement();
this.dialogElem.classList.add("optionChooserDialog");
div.querySelectorAll("[data-optionvalue]").forEach(handle => {
const value = handle.getAttribute("data-optionvalue");
if (!handle) {
logger.error("Failed to bind option value in dialog:", value);
return;
}
// Need click detector here to forward elements, otherwise scrolling does not work
const detector = new ClickDetector(handle, {
consumeEvents: false,
preventDefault: false,
clickSound: null,
applyCssClass: "pressedOption",
targetOnly: true,
});
this.clickDetectors.push(detector);
if (value !== this.initialOption) {
detector.click.add(() => {
const selected = div.querySelector(".option.active");
if (selected) {
selected.classList.remove("active");
} else {
logger.warn("No selected option");
}
handle.classList.add("active");
this.app.sound.playUiSound(SOUNDS.uiClick);
this.internalButtonHandler("optionSelected", value);
});
}
});
return div;
}
}
export class DialogWithForm extends Dialog {
/**
*
* @param {object} param0
* @param {Application} param0.app
* @param {string} param0.title
* @param {string} param0.desc
* @param {array=} param0.buttons
* @param {string=} param0.confirmButtonId
* @param {string=} param0.extraButton
* @param {boolean=} param0.closeButton
* @param {Array<FormElement>} param0.formElements
*/
constructor({
app,
title,
desc,
formElements,
buttons = ["cancel", "ok:good"],
confirmButtonId = "ok",
closeButton = true,
}) {
let html = "";
html += desc + "<br>";
for (let i = 0; i < formElements.length; ++i) {
html += formElements[i].getHtml();
}
super({
app,
title: title,
contentHTML: html,
buttons: buttons,
type: "info",
closeButton,
});
this.confirmButtonId = confirmButtonId;
this.formElements = formElements;
this.enterHandler = confirmButtonId;
}
internalButtonHandler(id, ...payload) {
if (id === this.confirmButtonId) {
if (this.hasAnyInvalid()) {
this.dialogElem.classList.remove("errorShake");
waitNextFrame().then(() => {
if (this.dialogElem) {
this.dialogElem.classList.add("errorShake");
}
});
this.app.sound.playUiSound(SOUNDS.uiError);
return;
}
}
super.internalButtonHandler(id, payload);
}
hasAnyInvalid() {
for (let i = 0; i < this.formElements.length; ++i) {
if (!this.formElements[i].isValid()) {
return true;
}
}
return false;
}
createElement() {
const div = super.createElement();
for (let i = 0; i < this.formElements.length; ++i) {
const elem = this.formElements[i];
elem.bindEvents(div, this.clickDetectors);
}
waitNextFrame().then(() => {
this.formElements[0].focus();
});
return div;
}
}

@ -1,129 +1,130 @@
import trim from "trim";
import { DialogWithForm } from "../../core/modal_dialog_elements";
import { FormElementInput } from "../../core/modal_dialog_forms";
import { BaseItem } from "../base_item";
import { enumColors } from "../colors";
import { ConstantSignalComponent } from "../components/constant_signal";
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeDefinition } from "../shape_definition";
export class ConstantSignalSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [ConstantSignalComponent]);
this.root.signals.entityManuallyPlaced.add(this.querySigalValue, this);
}
update() {
// Set signals
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const pinsComp = entity.components.WiredPins;
const signalComp = entity.components.ConstantSignal;
pinsComp.slots[0].value = signalComp.signal;
}
}
/**
* Asks the entity to enter a valid signal code
* @param {Entity} entity
*/
querySigalValue(entity) {
if (!entity.components.ConstantSignal) {
return;
}
// Ok, query, but also save the uid because it could get stale
const uid = entity.uid;
const signalValueInput = new FormElementInput({
id: "signalValue",
label: null,
placeholder: "",
defaultValue: "",
validator: val => this.parseSignalCode(val),
});
const dialog = new DialogWithForm({
app: this.root.app,
title: "Set Signal",
desc: "Enter a shape code, color or '0' or '1'",
formElements: [signalValueInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
// When confirmed, set the signal
dialog.buttonSignals.ok.add(() => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
});
// When cancelled, destroy the entity again
dialog.buttonSignals.cancel.add(() => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
this.root.logic.tryDeleteBuilding(entityRef);
});
}
/**
* Tries to parse a signal code
* @param {string} code
* @returns {BaseItem}
*/
parseSignalCode(code) {
code = trim(code);
const codeLower = code.toLowerCase();
if (enumColors[codeLower]) {
return COLOR_ITEM_SINGLETONS[codeLower];
}
if (code === "1" || codeLower === "true") {
return BOOL_TRUE_SINGLETON;
}
if (code === "0" || codeLower === "false") {
return BOOL_FALSE_SINGLETON;
}
if (ShapeDefinition.isValidShortKey(code)) {
return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
}
return null;
}
}
import trim from "trim";
import { DialogWithForm } from "../../core/modal_dialog_elements";
import { FormElementInput } from "../../core/modal_dialog_forms";
import { BaseItem } from "../base_item";
import { enumColors } from "../colors";
import { ConstantSignalComponent } from "../components/constant_signal";
import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeDefinition } from "../shape_definition";
export class ConstantSignalSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [ConstantSignalComponent]);
this.root.signals.entityManuallyPlaced.add(this.querySigalValue, this);
}
update() {
// Set signals
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const pinsComp = entity.components.WiredPins;
const signalComp = entity.components.ConstantSignal;
pinsComp.slots[0].value = signalComp.signal;
}
}
/**
* Asks the entity to enter a valid signal code
* @param {Entity} entity
*/
querySigalValue(entity) {
if (!entity.components.ConstantSignal) {
return;
}
// Ok, query, but also save the uid because it could get stale
const uid = entity.uid;
const signalValueInput = new FormElementInput({
id: "signalValue",
label: null,
placeholder: "",
defaultValue: "",
validator: val => this.parseSignalCode(val),
});
const dialog = new DialogWithForm({
app: this.root.app,
title: "Set Signal",
desc: "Enter a shape code, color or '0' or '1'",
formElements: [signalValueInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
closeButton: false,
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
// When confirmed, set the signal
dialog.buttonSignals.ok.add(() => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
});
// When cancelled, destroy the entity again
dialog.buttonSignals.cancel.add(() => {
if (!this.root || !this.root.entityMgr) {
// Game got stopped
return;
}
const entityRef = this.root.entityMgr.findByUid(uid, false);
if (!entityRef) {
// outdated
return;
}
const constantComp = entityRef.components.ConstantSignal;
if (!constantComp) {
// no longer interesting
return;
}
this.root.logic.tryDeleteBuilding(entityRef);
});
}
/**
* Tries to parse a signal code
* @param {string} code
* @returns {BaseItem}
*/
parseSignalCode(code) {
code = trim(code);
const codeLower = code.toLowerCase();
if (enumColors[codeLower]) {
return COLOR_ITEM_SINGLETONS[codeLower];
}
if (code === "1" || codeLower === "true") {
return BOOL_TRUE_SINGLETON;
}
if (code === "0" || codeLower === "false") {
return BOOL_FALSE_SINGLETON;
}
if (ShapeDefinition.isValidShortKey(code)) {
return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
}
return null;
}
}

Loading…
Cancel
Save