1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Mod Support - 1.5.0 Update (#1361)

* initial modloader draft

* modloader features

* Refactor mods to use signals

* Add support for modifying and registering new transltions

* Minor adjustments

* Support for string building ids for mods

* Initial support for adding new buildings

* Refactor how mods are loaded to resolve circular dependencies and prepare for future mod loading

* Lazy Load mods to make sure all dependencies are loaded

* Expose all exported members automatically to mods

* Fix duplicate exports

* Allow loading mods from standalone

* update changelog

* Fix mods folder incorrect path

* Fix modloading in standalone

* Fix sprites not getting replaced, update demo mod

* Load dev mod via raw loader

* Improve mod developing so mods are directly ready to be deployed, load mods from local file server

* Proper mods ui

* Allow mods to register game systems and draw stuff

* Change mods path

* Fix sprites not loading

* Minor adjustments, closes #1333

* Add support for loading atlases via mods

* Add support for loading mods from external sources in DEV

* Add confirmation when loading mods

* Fix circular dependency

* Minor Keybindings refactor, add support for keybindings to mods, add support for dialogs to mods

* Add some mod signals

* refactor game loading states

* Make shapez exports global

* Start to make mods safer

* Refactor file system electron event handling

* Properly isolate electron renderer process

* Update to latest electron

* Show errors when loading mods

* Update confirm dialgo

* Minor restructure, start to add mod examples

* Allow adding custom themesw

* Add more examples and allow defining custom item processor operations

* Add interface to register new buildings

* Fixed typescript type errors (#1335)

* Refactor building registry, make it easier for mods to add new buildings

* Allow overriding existing methods

* Add more examples and more features

* More mod examples

* Make mod loading simpler

* Add example how to add custom drawings

* Remove unused code

* Minor modloader adjustments

* Support for rotation variants in mods (was broken previously)

* Allow mods to replace builtin sub shapes

* Add helper methods to extend classes

* Fix menu bar on mac os

* Remember window state

* Add support for paste signals

* Add example how to add custom components and systems

* Support for mod settings

* Add example for adding a new item type

* Update class extensions

* Minor adjustments

* Fix typo

* Add notification blocks mod example

* Add small tutorial

* Update readme

* Add better instructions

* Update JSDoc for Replacing Methods (#1336)

* upgraded types for overriding methods

* updated comments

Co-authored-by: Edward Badel <you@example.com>

* Direction lock now indicates when there is a building inbetween

* Fix mod examples

* Fix linter error

* Game state register (#1341)

* Added a gamestate register helper

Added a gamestate register helper

* Update mod_interface.js

* export build options

* Fix runBeforeMethod and runAfterMethod

* Minor game system code cleanup

* Belt path drawing optimization

* Fix belt path optimization

* Belt drawing improvements, again

* Do not render belts in statics disabled view

* Allow external URL to load more than one mod (#1337)

* Allow external URL to load more than one mod

Instead of loading the text returned from the remote server, load a JSON object with a `mods` field, containing strings of all the mods. This lets us work on more than one mod at a time or without separate repos. This will break tooling such as `create-shapezio-mod` though.

* Update modloader.js

* Prettier fixes

* Added link to create-shapezio-mod npm page (#1339)

Added link to create-shapezio-mod npm page: https://www.npmjs.com/package/create-shapezio-mod

* allow command line switch to load more than one mod (#1342)

* Fixed class handle type (#1345)

* Fixed class handle type

* Fixed import game state

* Minor adjustments

* Refactor item acceptor to allow only single direction slots

* Allow specifying minimumGameVersion

* Add sandbox example

* Replaced concatenated strings with template literals (#1347)

* Mod improvements

* Make wired pins component optional on the storage

* Fix mod examples

* Bind `this` for method overriding JSDoc (#1352)

* fix entity debugger reaching HTML elements (#1353)

* Store mods in savegame and show warning when it differs

* Closes #1357

* Fix All Shapez Exports Being Const (#1358)

* Allowed setting of variables inside webpack modules

* remove console log

* Fix stringification of things inside of eval

Co-authored-by: Edward Badel <you@example.com>

* Fix building placer intersection warning

* Add example for storing data in the savegame

* Fix double painter bug (#1349)

* Add example on how to extend builtin buildings

* update readme

* Disable steam achievements when playing with mods

* Update translations

Co-authored-by: Thomas (DJ1TJOO) <44841260+DJ1TJOO@users.noreply.github.com>
Co-authored-by: Bagel03 <70449196+Bagel03@users.noreply.github.com>
Co-authored-by: Edward Badel <you@example.com>
Co-authored-by: Emerald Block <69981203+EmeraldBlock@users.noreply.github.com>
Co-authored-by: saile515 <63782477+saile515@users.noreply.github.com>
Co-authored-by: Sense101 <67970865+Sense101@users.noreply.github.com>
This commit is contained in:
tobspr
2022-02-01 16:35:49 +01:00
committed by GitHub
parent a7a2aad2b6
commit c41aaa1fc5
170 changed files with 5935 additions and 1551 deletions

View File

@@ -1,6 +1,7 @@
import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters";
import { Signal } from "../../core/signal";
import { MOD_SIGNALS } from "../../mods/mod_signals";
import { KEYMAPPINGS } from "../key_action_mapper";
import { MetaBuilding } from "../meta_building";
import { GameRoot } from "../root";
@@ -64,7 +65,7 @@ export class GameHUD {
/* typehints:end */
};
if (G_IS_DEV && globalConfig.debug.enableEntityInspector) {
if (G_IS_DEV) {
this.parts.entityDebugger = new HUDEntityDebugger(this.root);
}
@@ -89,8 +90,11 @@ export class GameHUD {
this.parts[partId] = new part(this.root);
}
MOD_SIGNALS.hudInitializer.dispatch(this.root);
const frag = document.createDocumentFragment();
for (const key in this.parts) {
MOD_SIGNALS.hudElementInitialized.dispatch(this.parts[key]);
this.parts[key].createElements(frag);
}
@@ -98,6 +102,7 @@ export class GameHUD {
for (const key in this.parts) {
this.parts[key].initialize();
MOD_SIGNALS.hudElementFinalized.dispatch(this.parts[key]);
}
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.toggleHud).add(this.toggleUi, this);

View File

@@ -1,4 +1,5 @@
import { gMetaBuildingRegistry } from "../../../core/global_registries";
import { globalWarn } from "../../../core/logging";
import { STOP_PROPAGATION } from "../../../core/signal";
import { makeDiv, safeModulo } from "../../../core/utils";
import { MetaBlockBuilding } from "../../buildings/block";
@@ -101,7 +102,12 @@ export class HUDBaseToolbar extends BaseHUDPart {
rawBinding = KEYMAPPINGS.buildings[metaBuilding.getId()];
}
const binding = actionMapper.getBinding(rawBinding);
if (rawBinding) {
const binding = actionMapper.getBinding(rawBinding);
binding.add(() => this.selectBuildingForPlacement(metaBuilding));
} else {
globalWarn("Building has no keybinding:", metaBuilding.getId());
}
const itemContainer = makeDiv(
this.primaryBuildings.includes(allBuildings[i]) ? rowPrimary : rowSecondary,
@@ -110,7 +116,6 @@ export class HUDBaseToolbar extends BaseHUDPart {
);
itemContainer.setAttribute("data-icon", "building_icons/" + metaBuilding.getId() + ".png");
itemContainer.setAttribute("data-id", metaBuilding.getId());
binding.add(() => this.selectBuildingForPlacement(metaBuilding));
const icon = makeDiv(itemContainer, null, ["icon"]);

View File

@@ -61,7 +61,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
this.currentInterpolatedCornerTile = new Vector();
this.lockIndicatorSprites = {};
layers.forEach(layer => {
[...layers, "error"].forEach(layer => {
this.lockIndicatorSprites[layer] = this.makeLockIndicatorSprite(layer);
});
@@ -76,7 +76,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
/**
* Makes the lock indicator sprite for the given layer
* @param {Layer} layer
* @param {string} layer
*/
makeLockIndicatorSprite(layer) {
const dims = 48;
@@ -126,12 +126,15 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
rawBinding = KEYMAPPINGS.buildings[metaBuilding.getId()];
}
const binding = this.root.keyMapper.getBinding(rawBinding);
this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace(
"<key>",
"<code class='keybinding'>" + binding.getKeyCodeString() + "</code>"
);
if (rawBinding) {
const binding = this.root.keyMapper.getBinding(rawBinding);
this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace(
"<key>",
"<code class='keybinding'>" + binding.getKeyCodeString() + "</code>"
);
} else {
this.buildingInfoElements.hotkey.innerHTML = "";
}
this.buildingInfoElements.tutorialImage.setAttribute(
"data-icon",
@@ -355,7 +358,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
rotationVariant
);
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity, {});
// Fade in / out
parameters.context.lineWidth = 1;
@@ -394,6 +397,46 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
}
}
/**
* Checks if there are any entities in the way, returns true if there are
* @param {Vector} from
* @param {Vector} to
* @param {Vector[]=} ignorePositions
* @returns
*/
checkForObstales(from, to, ignorePositions = []) {
assert(from.x === to.x || from.y === to.y, "Must be a straight line");
const prop = from.x === to.x ? "y" : "x";
const current = from.copy();
const metaBuilding = this.currentMetaBuilding.get();
this.fakeEntity.layer = metaBuilding.getLayer();
const staticComp = this.fakeEntity.components.StaticMapEntity;
staticComp.origin = current;
staticComp.rotation = 0;
metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get());
staticComp.code = getCodeFromBuildingData(
this.currentMetaBuilding.get(),
this.currentVariant.get(),
0
);
const start = Math.min(from[prop], to[prop]);
const end = Math.max(from[prop], to[prop]);
for (let i = start; i <= end; i++) {
current[prop] = i;
if (ignorePositions.some(p => p.distanceSquare(current) < 0.1)) {
continue;
}
if (!this.root.logic.checkCanPlaceEntity(this.fakeEntity, { allowReplaceBuildings: false })) {
return true;
}
}
return false;
}
/**
* @param {DrawParameters} parameters
*/
@@ -404,55 +447,76 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
return;
}
const applyStyles = look => {
parameters.context.fillStyle = THEME.map.directionLock[look].color;
parameters.context.strokeStyle = THEME.map.directionLock[look].background;
parameters.context.lineWidth = 10;
};
if (!this.lastDragTile) {
// Not dragging yet
applyStyles(this.root.currentLayer);
const mouseWorld = this.root.camera.screenToWorld(mousePosition);
parameters.context.beginCircle(mouseWorld.x, mouseWorld.y, 4);
parameters.context.fill();
return;
}
const mouseWorld = this.root.camera.screenToWorld(mousePosition);
const mouseTile = mouseWorld.toTileSpace();
parameters.context.fillStyle = THEME.map.directionLock[this.root.currentLayer].color;
parameters.context.strokeStyle = THEME.map.directionLock[this.root.currentLayer].background;
parameters.context.lineWidth = 10;
const startLine = this.lastDragTile.toWorldSpaceCenterOfTile();
const endLine = mouseTile.toWorldSpaceCenterOfTile();
const midLine = this.currentDirectionLockCorner.toWorldSpaceCenterOfTile();
const anyObstacle =
this.checkForObstales(this.lastDragTile, this.currentDirectionLockCorner, [
this.lastDragTile,
mouseTile,
]) ||
this.checkForObstales(this.currentDirectionLockCorner, mouseTile, [this.lastDragTile, mouseTile]);
if (anyObstacle) {
applyStyles("error");
} else {
applyStyles(this.root.currentLayer);
}
parameters.context.beginCircle(mouseWorld.x, mouseWorld.y, 4);
parameters.context.fill();
if (this.lastDragTile) {
const startLine = this.lastDragTile.toWorldSpaceCenterOfTile();
const endLine = mouseTile.toWorldSpaceCenterOfTile();
const midLine = this.currentDirectionLockCorner.toWorldSpaceCenterOfTile();
parameters.context.beginCircle(startLine.x, startLine.y, 8);
parameters.context.fill();
parameters.context.beginCircle(startLine.x, startLine.y, 8);
parameters.context.fill();
parameters.context.beginPath();
parameters.context.moveTo(startLine.x, startLine.y);
parameters.context.lineTo(midLine.x, midLine.y);
parameters.context.lineTo(endLine.x, endLine.y);
parameters.context.stroke();
parameters.context.beginPath();
parameters.context.moveTo(startLine.x, startLine.y);
parameters.context.lineTo(midLine.x, midLine.y);
parameters.context.lineTo(endLine.x, endLine.y);
parameters.context.stroke();
parameters.context.beginCircle(endLine.x, endLine.y, 5);
parameters.context.fill();
parameters.context.beginCircle(endLine.x, endLine.y, 5);
parameters.context.fill();
// Draw arrow
const arrowSprite = this.lockIndicatorSprites[anyObstacle ? "error" : this.root.currentLayer];
const path = this.computeDirectionLockPath();
for (let i = 0; i < path.length - 1; i += 1) {
const { rotation, tile } = path[i];
const worldPos = tile.toWorldSpaceCenterOfTile();
const angle = Math.radians(rotation);
// Draw arrow
const arrowSprite = this.lockIndicatorSprites[this.root.currentLayer];
const path = this.computeDirectionLockPath();
for (let i = 0; i < path.length - 1; i += 1) {
const { rotation, tile } = path[i];
const worldPos = tile.toWorldSpaceCenterOfTile();
const angle = Math.radians(rotation);
parameters.context.translate(worldPos.x, worldPos.y);
parameters.context.rotate(angle);
parameters.context.drawImage(
arrowSprite,
-6,
-globalConfig.halfTileSize -
clamp((this.root.time.realtimeNow() * 1.5) % 1.0, 0, 1) * 1 * globalConfig.tileSize +
globalConfig.halfTileSize -
6,
12,
12
);
parameters.context.rotate(-angle);
parameters.context.translate(-worldPos.x, -worldPos.y);
}
parameters.context.translate(worldPos.x, worldPos.y);
parameters.context.rotate(angle);
parameters.context.drawImage(
arrowSprite,
-6,
-globalConfig.halfTileSize -
clamp((this.root.time.realtimeNow() * 1.5) % 1.0, 0, 1) * 1 * globalConfig.tileSize +
globalConfig.halfTileSize -
6,
12,
12
);
parameters.context.rotate(-angle);
parameters.context.translate(-worldPos.x, -worldPos.y);
}
}
@@ -473,7 +537,13 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
const offsetShift = 10;
/**
* @type {Array<import("../../components/item_acceptor").ItemAcceptorSlot>}
*/
let acceptorSlots = [];
/**
* @type {Array<import("../../components/item_ejector").ItemEjectorSlot>}
*/
let ejectorSlots = [];
if (ejectorComp) {
@@ -491,71 +561,65 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
acceptorSlots.push(fakeAcceptorSlot);
}
for (let acceptorSlotIndex = 0; acceptorSlotIndex < acceptorSlots.length; ++acceptorSlotIndex) {
const slot = acceptorSlots[acceptorSlotIndex];
// Go over all slots
for (let i = 0; i < acceptorSlots.length; ++i) {
const slot = acceptorSlots[i];
const acceptorSlotWsTile = staticComp.localTileToWorld(slot.pos);
const acceptorSlotWsPos = acceptorSlotWsTile.toWorldSpaceCenterOfTile();
// Go over all slots
for (
let acceptorDirectionIndex = 0;
acceptorDirectionIndex < slot.directions.length;
++acceptorDirectionIndex
) {
const direction = slot.directions[acceptorDirectionIndex];
const worldDirection = staticComp.localDirectionToWorld(direction);
const direction = slot.direction;
const worldDirection = staticComp.localDirectionToWorld(direction);
// Figure out which tile ejects to this slot
const sourceTile = acceptorSlotWsTile.add(enumDirectionToVector[worldDirection]);
// Figure out which tile ejects to this slot
const sourceTile = acceptorSlotWsTile.add(enumDirectionToVector[worldDirection]);
let isBlocked = false;
let isConnected = false;
let isBlocked = false;
let isConnected = false;
// Find all entities which are on that tile
const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
// Find all entities which are on that tile
const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
// Check for every entity:
for (let i = 0; i < sourceEntities.length; ++i) {
const sourceEntity = sourceEntities[i];
const sourceEjector = sourceEntity.components.ItemEjector;
const sourceBeltComp = sourceEntity.components.Belt;
const sourceStaticComp = sourceEntity.components.StaticMapEntity;
const ejectorAcceptLocalTile = sourceStaticComp.worldToLocalTile(acceptorSlotWsTile);
// Check for every entity:
for (let j = 0; j < sourceEntities.length; ++j) {
const sourceEntity = sourceEntities[j];
const sourceEjector = sourceEntity.components.ItemEjector;
const sourceBeltComp = sourceEntity.components.Belt;
const sourceStaticComp = sourceEntity.components.StaticMapEntity;
const ejectorAcceptLocalTile = sourceStaticComp.worldToLocalTile(acceptorSlotWsTile);
// If this entity is on the same layer as the slot - if so, it can either be
// connected, or it can not be connected and thus block the input
if (sourceEjector && sourceEjector.anySlotEjectsToLocalTile(ejectorAcceptLocalTile)) {
// This one is connected, all good
isConnected = true;
} else if (
sourceBeltComp &&
sourceStaticComp.localDirectionToWorld(sourceBeltComp.direction) ===
enumInvertedDirections[worldDirection]
) {
// Belt connected
isConnected = true;
} else {
// This one is blocked
isBlocked = true;
}
// If this entity is on the same layer as the slot - if so, it can either be
// connected, or it can not be connected and thus block the input
if (sourceEjector && sourceEjector.anySlotEjectsToLocalTile(ejectorAcceptLocalTile)) {
// This one is connected, all good
isConnected = true;
} else if (
sourceBeltComp &&
sourceStaticComp.localDirectionToWorld(sourceBeltComp.direction) ===
enumInvertedDirections[worldDirection]
) {
// Belt connected
isConnected = true;
} else {
// This one is blocked
isBlocked = true;
}
const alpha = isConnected || isBlocked ? 1.0 : 0.3;
const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
parameters.context.globalAlpha = alpha;
drawRotatedSprite({
parameters,
sprite,
x: acceptorSlotWsPos.x,
y: acceptorSlotWsPos.y,
angle: Math.radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
size: 13,
offsetY: offsetShift + 13,
});
parameters.context.globalAlpha = 1;
}
const alpha = isConnected || isBlocked ? 1.0 : 0.3;
const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
parameters.context.globalAlpha = alpha;
drawRotatedSprite({
parameters,
sprite,
x: acceptorSlotWsPos.x,
y: acceptorSlotWsPos.y,
angle: Math.radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
size: 13,
offsetY: offsetShift + 13,
});
parameters.context.globalAlpha = 1;
}
// Go over all slots

View File

@@ -1,7 +1,19 @@
import { THIRDPARTY_URLS } from "../../../core/config";
import { DialogWithForm } from "../../../core/modal_dialog_elements";
import { FormElementInput, FormElementItemChooser } from "../../../core/modal_dialog_forms";
import { STOP_PROPAGATION } from "../../../core/signal";
import { fillInLinkIntoTranslation } from "../../../core/utils";
import { Vector } from "../../../core/vector";
import { T } from "../../../translations";
import { BaseItem } from "../../base_item";
import { enumMouseButton } from "../../camera";
import { Entity } from "../../entity";
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../../items/boolean_item";
import { COLOR_ITEM_SINGLETONS } from "../../items/color_item";
import { BaseHUDPart } from "../base_hud_part";
import trim from "trim";
import { enumColors } from "../../colors";
import { ShapeDefinition } from "../../shape_definition";
export class HUDConstantSignalEdit extends BaseHUDPart {
initialize() {
@@ -23,7 +35,7 @@ export class HUDConstantSignalEdit extends BaseHUDPart {
const constantComp = contents.components.ConstantSignal;
if (constantComp) {
if (button === enumMouseButton.left) {
this.root.systemMgr.systems.constantSignal.editConstantSignal(contents, {
this.editConstantSignal(contents, {
deleteOnCancel: false,
});
return STOP_PROPAGATION;
@@ -31,4 +43,171 @@ export class HUDConstantSignalEdit extends BaseHUDPart {
}
}
}
/**
* Asks the entity to enter a valid signal code
* @param {Entity} entity
* @param {object} param0
* @param {boolean=} param0.deleteOnCancel
*/
editConstantSignal(entity, { deleteOnCancel = true }) {
if (!entity.components.ConstantSignal) {
return;
}
// Ok, query, but also save the uid because it could get stale
const uid = entity.uid;
const signal = entity.components.ConstantSignal.signal;
const signalValueInput = new FormElementInput({
id: "signalValue",
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
placeholder: "",
defaultValue: signal ? signal.getAsCopyableKey() : "",
validator: val => this.parseSignalCode(entity, val),
});
const items = [...Object.values(COLOR_ITEM_SINGLETONS)];
if (entity.components.WiredPins) {
items.unshift(BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON);
items.push(
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(
this.root.gameMode.getBlueprintShapeKey()
)
);
} else {
// producer which can produce virtually anything
const shapes = ["CuCuCuCu", "RuRuRuRu", "WuWuWuWu", "SuSuSuSu"];
items.unshift(
...shapes.reverse().map(key => this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key))
);
}
if (this.root.gameMode.hasHub()) {
items.push(
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
this.root.hubGoals.currentGoal.definition
)
);
}
if (this.root.hud.parts.pinnedShapes) {
items.push(
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
)
);
}
const itemInput = new FormElementItemChooser({
id: "signalItem",
label: null,
items,
});
const dialog = new DialogWithForm({
app: this.root.app,
title: T.dialogs.editConstantProducer.title,
desc: T.dialogs.editSignal.descItems,
formElements: [itemInput, signalValueInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
closeButton: false,
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
// When confirmed, set the signal
const closeHandler = () => {
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;
}
if (itemInput.chosenItem) {
constantComp.signal = itemInput.chosenItem;
} else {
constantComp.signal = this.parseSignalCode(entity, signalValueInput.getValue());
}
};
dialog.buttonSignals.ok.add(() => {
closeHandler();
});
dialog.valueChosen.add(() => {
dialog.closeRequested.dispatch();
closeHandler();
});
// When cancelled, destroy the entity again
if (deleteOnCancel) {
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 {Entity} entity
* @param {string} code
* @returns {BaseItem}
*/
parseSignalCode(entity, code) {
if (!this.root || !this.root.shapeDefinitionMgr) {
// Stale reference
return null;
}
code = trim(code);
const codeLower = code.toLowerCase();
if (enumColors[codeLower]) {
return COLOR_ITEM_SINGLETONS[codeLower];
}
if (entity.components.WiredPins) {
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;
}
}

View File

@@ -94,11 +94,12 @@ export class HUDEntityDebugger extends BaseHUDPart {
<div>`;
for (const property in val) {
const isRoot = val[property] == this.root;
const isRecursive = recursion.includes(val[property]);
let hiddenValue = isRoot ? "<root>" : null;
if (isRecursive) {
let hiddenValue = null;
if (val[property] == this.root) {
hiddenValue = "<root>";
} else if (val[property] instanceof Node) {
hiddenValue = `<${val[property].constructor.name}>`;
} else if (recursion.includes(val[property])) {
// Avoid recursion by not "expanding" object more than once
hiddenValue = "<recursion>";
}

View File

@@ -150,7 +150,7 @@ export class HUDModalDialogs extends BaseHUDPart {
internalShowDialog(dialog) {
const elem = dialog.createElement();
dialog.setIndex(this.dialogStack.length);
dialog.setIndex(1000 + this.dialogStack.length);
// Hide last dialog in queue
if (this.dialogStack.length > 0) {

View File

@@ -7,6 +7,9 @@ export const enumNotificationType = {
saved: "saved",
upgrade: "upgrade",
success: "success",
info: "info",
warning: "warning",
error: "error",
};
const notificationDuration = 3;
@@ -17,14 +20,14 @@ export class HUDNotifications extends BaseHUDPart {
}
initialize() {
this.root.hud.signals.notification.add(this.onNotification, this);
this.root.hud.signals.notification.add(this.internalShowNotification, this);
/** @type {Array<{ element: HTMLElement, expireAt: number}>} */
this.notificationElements = [];
// Automatic notifications
this.root.signals.gameSaved.add(() =>
this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
this.internalShowNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
);
}
@@ -32,7 +35,7 @@ export class HUDNotifications extends BaseHUDPart {
* @param {string} message
* @param {enumNotificationType} type
*/
onNotification(message, type) {
internalShowNotification(message, type) {
const element = makeDiv(this.element, null, ["notification", "type-" + type], message);
element.setAttribute("data-icon", "icons/notification_" + type + ".png");