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:
@@ -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);
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>";
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user