From 96030c9b76f3295d9f197c2a151bc6f9ec0b121a Mon Sep 17 00:00:00 2001
From: tobspr <>
Date: Wed, 27 May 2020 14:30:59 +0200
Subject: [PATCH] Initial support for blueprints (Buggy)
---
shapez.code-workspace | 1 +
src/css/ingame_hud/buildings_toolbar.scss | 6 +-
src/html/index.html | 2 +-
src/js/changelog.js | 8 +
src/js/core/config.js | 4 +-
src/js/core/vector.js | 11 ++
src/js/game/buildings/hub.js | 4 +
src/js/game/component.js | 8 +
src/js/game/components/belt.js | 4 +
src/js/game/components/item_acceptor.js | 26 +++
src/js/game/components/item_ejector.js | 16 ++
src/js/game/components/item_processor.js | 7 +
src/js/game/components/miner.js | 6 +
.../game/components/replaceable_map_entity.js | 4 +
src/js/game/components/static_map_entity.js | 37 +++-
src/js/game/components/storage.js | 4 +
src/js/game/components/underground_belt.js | 7 +
src/js/game/components/unremovable.js | 4 +
src/js/game/entity.js | 11 +-
src/js/game/hub_goals.js | 4 +-
src/js/game/hud/hud.js | 5 +-
src/js/game/hud/parts/blueprint.js | 176 ++++++++++++++++++
src/js/game/hud/parts/blueprint_placer.js | 103 ++++++++++
src/js/game/hud/parts/building_placer.js | 3 +
src/js/game/hud/parts/mass_selector.js | 94 +++++++---
src/js/game/hud/parts/tutorial_hints.js | 3 +-
src/js/game/key_action_mapper.js | 1 +
src/js/game/logic.js | 2 +
src/js/game/map.js | 11 ++
src/js/game/meta_building.js | 4 +
src/js/game/themes/dark.json | 2 +-
src/js/game/themes/light.json | 4 +
src/js/savegame/savegame.js | 26 ++-
.../savegame/savegame_interface_registry.js | 2 +
src/js/savegame/savegame_typedefs.js | 11 +-
src/js/savegame/schemas/1001.js | 52 ++++++
src/js/savegame/schemas/1001.json | 5 +
src/js/savegame/serializer_internal.js | 2 +-
src/js/states/main_menu.js | 12 +-
src/js/states/preload.js | 5 +
translations/base-en.yaml | 13 +-
41 files changed, 633 insertions(+), 77 deletions(-)
create mode 100644 src/js/game/hud/parts/blueprint.js
create mode 100644 src/js/game/hud/parts/blueprint_placer.js
create mode 100644 src/js/savegame/schemas/1001.js
create mode 100644 src/js/savegame/schemas/1001.json
diff --git a/shapez.code-workspace b/shapez.code-workspace
index e0766264..29dae2a2 100644
--- a/shapez.code-workspace
+++ b/shapez.code-workspace
@@ -8,6 +8,7 @@
"files.exclude": {
"**/build": true,
"**/node_modules": true,
+ "**/tmp_standalone_files": true,
"**/typedefs_gen": true
},
"vetur.format.defaultFormatter.js": "vscode-typescript",
diff --git a/src/css/ingame_hud/buildings_toolbar.scss b/src/css/ingame_hud/buildings_toolbar.scss
index d9f91fcc..a16acfb3 100644
--- a/src/css/ingame_hud/buildings_toolbar.scss
+++ b/src/css/ingame_hud/buildings_toolbar.scss
@@ -4,7 +4,6 @@
left: 50%;
transform: translateX(-50%);
- $toolbarBg: rgba($accentColorBright, 0.9);
display: flex;
flex-direction: column;
background-color: rgb(255, 255, 255);
@@ -12,8 +11,7 @@
border-bottom-width: 0;
transition: transform 0.12s ease-in-out;
- background: uiResource("toolbar_bg.lossless.png") center center / 100% 100% no-repeat;
- @include S(padding, 20px, 100px, 0);
+ background: rgba(mix(#ddd, $colorBlueBright, 80%), 0.89);
&:not(.visible) {
transform: translateX(-50%) translateY(#{D(100px)});
@@ -59,7 +57,7 @@
@include S(border-radius, $globalBorderRadius);
&.selected {
- background-color: rgba($colorBlueBright, 0.3) !important;
+ background-color: rgba($colorBlueBright, 0.6) !important;
transform: scale(1.05);
.keybinding {
color: #111;
diff --git a/src/html/index.html b/src/html/index.html
index ffcab1d7..b1d89377 100644
--- a/src/html/index.html
+++ b/src/html/index.html
@@ -1,7 +1,7 @@
- shapez.io - Build your own shape factory!
+ shapez.io - Build automated factories to build, combine and color shapes!
diff --git a/src/js/changelog.js b/src/js/changelog.js
index 44475e63..10c78d3a 100644
--- a/src/js/changelog.js
+++ b/src/js/changelog.js
@@ -1,4 +1,12 @@
export const CHANGELOG = [
+ {
+ version: "1.1.0",
+ date: "unreleased",
+ entries: [
+ "UX Added background to toolbar to increase contrast",
+ "UX Added confirmation when deleting more than 500 buildings at a time",
+ ],
+ },
{
version: "1.0.4",
date: "26.05.2020",
diff --git a/src/js/core/config.js b/src/js/core/config.js
index 99770c4b..f5bdbf76 100644
--- a/src/js/core/config.js
+++ b/src/js/core/config.js
@@ -93,7 +93,7 @@ export const globalConfig = {
// disableZoomLimits: true,
// showChunkBorders: true,
// rewardsInstant: true,
- // allBuildingsUnlocked: true,
+ allBuildingsUnlocked: true,
// upgradesNoCost: true,
// disableUnlockDialog: true,
// disableLogicTicks: true,
@@ -103,6 +103,8 @@ export const globalConfig = {
// enableEntityInspector: true,
// testAds: true,
// disableMapOverview: true,
+ disableTutorialHints: true,
+ disableUpgradeNotification: true,
/* dev:end */
},
diff --git a/src/js/core/vector.js b/src/js/core/vector.js
index 2bd6cfe9..2a02f75d 100644
--- a/src/js/core/vector.js
+++ b/src/js/core/vector.js
@@ -103,6 +103,17 @@ export class Vector {
return new Vector(this.x - other.x, this.y - other.y);
}
+ /**
+ * Subs a vector
+ * @param {Vector} other
+ * @returns {Vector}
+ */
+ subInplace(other) {
+ this.x -= other.x;
+ this.y -= other.y;
+ return this;
+ }
+
/**
* Multiplies with a vector and return a new vector
* @param {Vector} other
diff --git a/src/js/game/buildings/hub.js b/src/js/game/buildings/hub.js
index b7b960de..49d95005 100644
--- a/src/js/game/buildings/hub.js
+++ b/src/js/game/buildings/hub.js
@@ -24,6 +24,10 @@ export class MetaHubBuilding extends MetaBuilding {
return false;
}
+ getBlueprintSprite() {
+ return null;
+ }
+
/**
* Creates the entity at the given location
* @param {Entity} entity
diff --git a/src/js/game/component.js b/src/js/game/component.js
index 8c492351..1d44d60f 100644
--- a/src/js/game/component.js
+++ b/src/js/game/component.js
@@ -17,6 +17,14 @@ export class Component extends BasicSerializableObject {
return {};
}
+ /**
+ * Should duplicate the component but without its contents
+ * @returns {object}
+ */
+ duplicateWithoutContents() {
+ abstract;
+ }
+
/* dev:start */
/**
diff --git a/src/js/game/components/belt.js b/src/js/game/components/belt.js
index 9d64187c..dcac6ecb 100644
--- a/src/js/game/components/belt.js
+++ b/src/js/game/components/belt.js
@@ -18,6 +18,10 @@ export class BeltComponent extends Component {
};
}
+ duplicateWithoutContents() {
+ return new BeltComponent({ direction: this.direction });
+ }
+
/**
*
* @param {object} param0
diff --git a/src/js/game/components/item_acceptor.js b/src/js/game/components/item_acceptor.js
index d5546d4b..d9505d18 100644
--- a/src/js/game/components/item_acceptor.js
+++ b/src/js/game/components/item_acceptor.js
@@ -54,6 +54,32 @@ export class ItemAcceptorComponent extends Component {
};
}
+ duplicateWithoutContents() {
+ const slotsCopy = [];
+ for (let i = 0; i < this.slots.length; ++i) {
+ const slot = this.slots[i];
+ slotsCopy.push({
+ pos: slot.pos.copy(),
+ directions: slot.directions.slice(),
+ });
+ }
+
+ const beltUnderlaysCopy = [];
+ for (let i = 0; i < this.beltUnderlays.length; ++i) {
+ const underlay = this.beltUnderlays[i];
+ beltUnderlaysCopy.push({
+ pos: underlay.pos.copy(),
+ direction: underlay.direction,
+ });
+ }
+
+ return new ItemAcceptorComponent({
+ slots: slotsCopy,
+ beltUnderlays: beltUnderlaysCopy,
+ animated: this.animated,
+ });
+ }
+
/**
*
* @param {object} param0
diff --git a/src/js/game/components/item_ejector.js b/src/js/game/components/item_ejector.js
index 5cf96754..d5881a7d 100644
--- a/src/js/game/components/item_ejector.js
+++ b/src/js/game/components/item_ejector.js
@@ -32,6 +32,22 @@ export class ItemEjectorComponent extends Component {
};
}
+ duplicateWithoutContents() {
+ const slotsCopy = [];
+ for (let i = 0; i < this.slots.length; ++i) {
+ const slot = this.slots[i];
+ slotsCopy.push({
+ pos: slot.pos.copy(),
+ direction: slot.direction,
+ });
+ }
+
+ return new ItemEjectorComponent({
+ slots: slotsCopy,
+ instantEject: false,
+ });
+ }
+
/**
*
* @param {object} param0
diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js
index 0c4e90c6..eab51ae2 100644
--- a/src/js/game/components/item_processor.js
+++ b/src/js/game/components/item_processor.js
@@ -48,6 +48,13 @@ export class ItemProcessorComponent extends Component {
};
}
+ duplicateWithoutContents() {
+ return new ItemProcessorComponent({
+ processorType: this.type,
+ inputsPerCharge: this.inputsPerCharge,
+ });
+ }
+
/**
*
* @param {object} param0
diff --git a/src/js/game/components/miner.js b/src/js/game/components/miner.js
index e08d2906..57de7e2f 100644
--- a/src/js/game/components/miner.js
+++ b/src/js/game/components/miner.js
@@ -19,6 +19,12 @@ export class MinerComponent extends Component {
};
}
+ duplicateWithoutContents() {
+ return new MinerComponent({
+ chainable: this.chainable,
+ });
+ }
+
/**
*/
constructor({ chainable = false }) {
diff --git a/src/js/game/components/replaceable_map_entity.js b/src/js/game/components/replaceable_map_entity.js
index e6fd95d5..78861caf 100644
--- a/src/js/game/components/replaceable_map_entity.js
+++ b/src/js/game/components/replaceable_map_entity.js
@@ -8,4 +8,8 @@ export class ReplaceableMapEntityComponent extends Component {
static getId() {
return "ReplaceableMapEntity";
}
+
+ duplicateWithoutContents() {
+ return new ReplaceableMapEntityComponent();
+ }
}
diff --git a/src/js/game/components/static_map_entity.js b/src/js/game/components/static_map_entity.js
index 6f9abb87..ed616213 100644
--- a/src/js/game/components/static_map_entity.js
+++ b/src/js/game/components/static_map_entity.js
@@ -19,10 +19,23 @@ export class StaticMapEntityComponent extends Component {
rotation: types.float,
originalRotation: types.float,
spriteKey: types.nullable(types.string),
+ blueprintSpriteKey: types.string,
silhouetteColor: types.nullable(types.string),
};
}
+ duplicateWithoutContents() {
+ return new StaticMapEntityComponent({
+ origin: this.origin.copy(),
+ tileSize: this.tileSize.copy(),
+ rotation: this.rotation,
+ originalRotation: this.originalRotation,
+ spriteKey: this.spriteKey,
+ silhouetteColor: this.silhouetteColor,
+ blueprintSpriteKey: this.blueprintSpriteKey,
+ });
+ }
+
/**
*
* @param {object} param0
@@ -31,6 +44,7 @@ export class StaticMapEntityComponent extends Component {
* @param {number=} param0.rotation Rotation in degrees. Must be multiple of 90
* @param {number=} param0.originalRotation Original Rotation in degrees. Must be multiple of 90
* @param {string=} param0.spriteKey Optional sprite
+ * @param {string} param0.blueprintSpriteKey Blueprint sprite, required
* @param {string=} param0.silhouetteColor Optional silhouette color override
*/
constructor({
@@ -40,6 +54,7 @@ export class StaticMapEntityComponent extends Component {
originalRotation = 0,
spriteKey = null,
silhouetteColor = null,
+ blueprintSpriteKey = null,
}) {
super();
assert(
@@ -53,6 +68,7 @@ export class StaticMapEntityComponent extends Component {
this.rotation = rotation;
this.originalRotation = originalRotation;
this.silhouetteColor = silhouetteColor;
+ this.blueprintSpriteKey = blueprintSpriteKey;
}
/**
@@ -202,14 +218,25 @@ export class StaticMapEntityComponent extends Component {
* @param {AtlasSprite} sprite
* @param {number=} extrudePixels How many pixels to extrude the sprite
* @param {boolean=} clipping Whether to clip
+ * @param {Vector=} overridePosition Whether to drwa the entity at a different location
*/
- drawSpriteOnFullEntityBounds(parameters, sprite, extrudePixels = 0, clipping = true) {
- const worldX = this.origin.x * globalConfig.tileSize;
- const worldY = this.origin.y * globalConfig.tileSize;
-
- if (!this.shouldBeDrawn(parameters)) {
+ drawSpriteOnFullEntityBounds(
+ parameters,
+ sprite,
+ extrudePixels = 0,
+ clipping = true,
+ overridePosition = null
+ ) {
+ if (!this.shouldBeDrawn(parameters) && !overridePosition) {
return;
}
+ let worldX = this.origin.x * globalConfig.tileSize;
+ let worldY = this.origin.y * globalConfig.tileSize;
+
+ if (overridePosition) {
+ worldX = overridePosition.x * globalConfig.tileSize;
+ worldY = overridePosition.y * globalConfig.tileSize;
+ }
if (this.rotation === 0) {
// Early out, is faster
diff --git a/src/js/game/components/storage.js b/src/js/game/components/storage.js
index e024d522..69f4e367 100644
--- a/src/js/game/components/storage.js
+++ b/src/js/game/components/storage.js
@@ -19,6 +19,10 @@ export class StorageComponent extends Component {
};
}
+ duplicateWithoutContents() {
+ return new StorageComponent({ maximumStorage: this.maximumStorage });
+ }
+
/**
* @param {object} param0
* @param {number=} param0.maximumStorage How much this storage can hold
diff --git a/src/js/game/components/underground_belt.js b/src/js/game/components/underground_belt.js
index e581ebe9..4fcbbb48 100644
--- a/src/js/game/components/underground_belt.js
+++ b/src/js/game/components/underground_belt.js
@@ -23,6 +23,13 @@ export class UndergroundBeltComponent extends Component {
};
}
+ duplicateWithoutContents() {
+ return new UndergroundBeltComponent({
+ mode: this.mode,
+ tier: this.tier,
+ });
+ }
+
/**
*
* @param {object} param0
diff --git a/src/js/game/components/unremovable.js b/src/js/game/components/unremovable.js
index 17e9f36b..f3864cf8 100644
--- a/src/js/game/components/unremovable.js
+++ b/src/js/game/components/unremovable.js
@@ -8,4 +8,8 @@ export class UnremovableComponent extends Component {
static getSchema() {
return {};
}
+
+ duplicateWithoutContents() {
+ return new UnremovableComponent();
+ }
}
diff --git a/src/js/game/entity.js b/src/js/game/entity.js
index 50f4cae5..dc849851 100644
--- a/src/js/game/entity.js
+++ b/src/js/game/entity.js
@@ -77,11 +77,14 @@ export class Entity extends BasicSerializableObject {
}
/**
- * Returns whether the entity is still alive
- * @returns {boolean}
+ * Returns a clone of this entity without contents
*/
- isAlive() {
- return !this.destroyed && !this.queuedForDestroy;
+ duplicateWithoutContents() {
+ const clone = new Entity(this.root);
+ for (const key in this.components) {
+ clone.components[key] = this.components[key].duplicateWithoutContents();
+ }
+ return clone;
}
/**
diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js
index f1fc15c9..a6f24284 100644
--- a/src/js/game/hub_goals.js
+++ b/src/js/game/hub_goals.js
@@ -97,8 +97,8 @@ export class HubGoals extends BasicSerializableObject {
// Allow quickly switching goals in dev mode with key "C"
if (G_IS_DEV) {
this.root.gameState.inputReciever.keydown.add(key => {
- if (key.keyCode === 67) {
- // Key: c
+ if (key.keyCode === 66) {
+ // Key: b
this.onGoalCompleted();
}
});
diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js
index 2d317b7f..2294b4f1 100644
--- a/src/js/game/hud/hud.js
+++ b/src/js/game/hud/hud.js
@@ -8,6 +8,7 @@ import { HUDProcessingOverlay } from "./parts/processing_overlay";
import { HUDBuildingsToolbar } from "./parts/buildings_toolbar";
import { HUDBuildingPlacer } from "./parts/building_placer";
import { HUDBetaOverlay } from "./parts/beta_overlay";
+import { HUDBlueprintPlacer } from "./parts/blueprint_placer";
import { HUDKeybindingOverlay } from "./parts/keybinding_overlay";
import { HUDUnlockNotification } from "./parts/unlock_notification";
import { HUDGameMenu } from "./parts/game_menu";
@@ -45,6 +46,7 @@ export class GameHUD {
buildingsToolbar: new HUDBuildingsToolbar(this.root),
buildingPlacer: new HUDBuildingPlacer(this.root),
+ blueprintPlacer: new HUDBlueprintPlacer(this.root),
unlockNotification: new HUDUnlockNotification(this.root),
@@ -72,6 +74,7 @@ export class GameHUD {
selectedPlacementBuildingChanged: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()),
shapePinRequested: /** @type {TypedSignal<[ShapeDefinition, number]>} */ (new Signal()),
notification: /** @type {TypedSignal<[string, enumNotificationType]>} */ (new Signal()),
+ buildingsSelectedForCopy: /** @type {TypedSignal<[Array]>} */ (new Signal()),
};
if (!IS_MOBILE) {
@@ -185,7 +188,7 @@ export class GameHUD {
* @param {DrawParameters} parameters
*/
draw(parameters) {
- const partsOrder = ["massSelector", "buildingPlacer"];
+ const partsOrder = ["massSelector", "buildingPlacer", "blueprintPlacer"];
for (let i = 0; i < partsOrder.length; ++i) {
if (this.parts[partsOrder[i]]) {
diff --git a/src/js/game/hud/parts/blueprint.js b/src/js/game/hud/parts/blueprint.js
new file mode 100644
index 00000000..f6dccc4c
--- /dev/null
+++ b/src/js/game/hud/parts/blueprint.js
@@ -0,0 +1,176 @@
+import { GameRoot } from "../../root";
+import { Vector } from "../../../core/vector";
+import { Entity } from "../../entity";
+import { DrawParameters } from "../../../core/draw_parameters";
+import { StaticMapEntityComponent } from "../../components/static_map_entity";
+import { createLogger } from "../../../core/logging";
+import { Loader } from "../../../core/loader";
+
+const logger = createLogger("blueprint");
+
+export class Blueprint {
+ /**
+ * @param {Array} entities
+ */
+ constructor(entities) {
+ this.entities = entities;
+ }
+
+ /**
+ * @param {GameRoot} root
+ * @param {Array} uids
+ */
+ static fromUids(root, uids) {
+ const newEntities = [];
+
+ let averagePosition = new Vector();
+
+ // First, create a copy
+ for (let i = 0; i < uids.length; ++i) {
+ const entity = root.entityMgr.findByUid(uids[i]);
+ assert(entity, "Entity for blueprint not found:" + uids[i]);
+
+ const clone = entity.duplicateWithoutContents();
+ newEntities.push(clone);
+
+ const pos = entity.components.StaticMapEntity.getTileSpaceBounds().getCenter();
+ averagePosition.addInplace(pos);
+ }
+
+ averagePosition.divideScalarInplace(uids.length);
+ const blueprintOrigin = averagePosition.floor();
+ for (let i = 0; i < uids.length; ++i) {
+ newEntities[i].components.StaticMapEntity.origin.subInplace(blueprintOrigin);
+ }
+
+ // Now, make sure the origin is 0,0
+ return new Blueprint(newEntities);
+ }
+
+ /**
+ *
+ * @param {DrawParameters} parameters
+ */
+ draw(parameters, tile) {
+ parameters.context.globalAlpha = 0.8;
+ for (let i = 0; i < this.entities.length; ++i) {
+ const entity = this.entities[i];
+ const staticComp = entity.components.StaticMapEntity;
+ if (!staticComp.blueprintSpriteKey) {
+ logger.warn("Blueprint entity without sprite!");
+ return;
+ }
+ const newPos = staticComp.origin.add(tile);
+
+ const rect = staticComp.getTileSpaceBounds();
+ rect.moveBy(tile.x, tile.y);
+
+ let placeable = true;
+ placementCheck: for (let x = rect.x; x < rect.right(); ++x) {
+ for (let y = rect.y; y < rect.bottom(); ++y) {
+ if (parameters.root.map.isTileUsedXY(x, y)) {
+ placeable = false;
+ break placementCheck;
+ }
+ }
+ }
+
+ if (!placeable) {
+ parameters.context.globalAlpha = 0.3;
+ } else {
+ parameters.context.globalAlpha = 1;
+ }
+
+ staticComp.drawSpriteOnFullEntityBounds(
+ parameters,
+ Loader.getSprite(staticComp.blueprintSpriteKey),
+ 0,
+ true,
+ newPos
+ );
+ }
+ parameters.context.globalAlpha = 1;
+ }
+
+ /**
+ * @param {GameRoot} root
+ * @param {Vector} tile
+ */
+ canPlace(root, tile) {
+ let anyPlaceable = false;
+
+ for (let i = 0; i < this.entities.length; ++i) {
+ let placeable = true;
+ const entity = this.entities[i];
+ const staticComp = entity.components.StaticMapEntity;
+ const rect = staticComp.getTileSpaceBounds();
+ rect.moveBy(tile.x, tile.y);
+ placementCheck: for (let x = rect.x; x < rect.right(); ++x) {
+ for (let y = rect.y; y < rect.bottom(); ++y) {
+ if (root.map.isTileUsedXY(x, y)) {
+ placeable = false;
+ break placementCheck;
+ }
+ }
+ }
+
+ if (placeable) {
+ anyPlaceable = true;
+ }
+ }
+
+ return anyPlaceable;
+ }
+
+ /**
+ * @param {GameRoot} root
+ * @param {Vector} tile
+ */
+ tryPlace(root, tile) {
+ let anyPlaced = false;
+ for (let i = 0; i < this.entities.length; ++i) {
+ let placeable = true;
+ const entity = this.entities[i];
+ const staticComp = entity.components.StaticMapEntity;
+ const rect = staticComp.getTileSpaceBounds();
+ rect.moveBy(tile.x, tile.y);
+ placementCheck: for (let x = rect.x; x < rect.right(); ++x) {
+ for (let y = rect.y; y < rect.bottom(); ++y) {
+ const contents = root.map.getTileContentXY(x, y);
+ if (contents && !contents.components.ReplaceableMapEntity) {
+ placeable = false;
+ break placementCheck;
+ }
+ }
+ }
+
+ if (placeable) {
+ for (let x = rect.x; x < rect.right(); ++x) {
+ for (let y = rect.y; y < rect.bottom(); ++y) {
+ const contents = root.map.getTileContentXY(x, y);
+ if (contents) {
+ assert(
+ contents.components.ReplaceableMapEntity,
+ "Can not delete entity for blueprint"
+ );
+ if (!root.logic.tryDeleteBuilding(contents)) {
+ logger.error(
+ "Building has replaceable component but is also unremovable in blueprint"
+ );
+ return false;
+ }
+ }
+ }
+ }
+
+ const clone = entity.duplicateWithoutContents();
+ clone.components.StaticMapEntity.origin.addInplace(tile);
+
+ root.map.placeStaticEntity(clone);
+ root.entityMgr.registerEntity(clone);
+ anyPlaced = true;
+ }
+ }
+ return anyPlaced;
+ }
+}
diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js
new file mode 100644
index 00000000..32993ffc
--- /dev/null
+++ b/src/js/game/hud/parts/blueprint_placer.js
@@ -0,0 +1,103 @@
+import { DrawParameters } from "../../../core/draw_parameters";
+import { STOP_PROPAGATION } from "../../../core/signal";
+import { TrackedState } from "../../../core/tracked_state";
+import { Vector } from "../../../core/vector";
+import { enumMouseButton } from "../../camera";
+import { KEYMAPPINGS } from "../../key_action_mapper";
+import { BaseHUDPart } from "../base_hud_part";
+import { Blueprint } from "./blueprint";
+
+export class HUDBlueprintPlacer extends BaseHUDPart {
+ createElements(parent) {}
+
+ initialize() {
+ this.root.hud.signals.buildingsSelectedForCopy.add(this.onBuildingsSelected, this);
+
+ /** @type {TypedTrackedState} */
+ this.currentBlueprint = new TrackedState(this.onBlueprintChanged, this);
+
+ const keyActionMapper = this.root.keyMapper;
+ keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
+ keyActionMapper
+ .getBinding(KEYMAPPINGS.placement.abortBuildingPlacement)
+ .add(this.abortPlacement, this);
+
+ this.root.camera.downPreHandler.add(this.onMouseDown, this);
+ this.root.camera.movePreHandler.add(this.onMouseMove, this);
+
+ this.root.hud.signals.selectedPlacementBuildingChanged.add(this.abortPlacement, this);
+ }
+
+ abortPlacement() {
+ if (this.currentBlueprint.get()) {
+ this.currentBlueprint.set(null);
+
+ return STOP_PROPAGATION;
+ }
+ }
+
+ onBlueprintChanged(blueprint) {}
+
+ /**
+ * mouse down pre handler
+ * @param {Vector} pos
+ * @param {enumMouseButton} button
+ */
+ onMouseDown(pos, button) {
+ if (button === enumMouseButton.right) {
+ this.abortPlacement();
+ return STOP_PROPAGATION;
+ }
+
+ const blueprint = this.currentBlueprint.get();
+ if (!blueprint) {
+ return;
+ }
+
+ console.log("down");
+ const worldPos = this.root.camera.screenToWorld(pos);
+ const tile = worldPos.toTileSpace();
+ if (blueprint.tryPlace(this.root, tile)) {
+ if (!this.root.app.inputMgr.shiftIsDown) {
+ this.currentBlueprint.set(null);
+ }
+ }
+ }
+
+ onMouseMove() {
+ // Prevent movement while blueprint is selected
+ if (this.currentBlueprint.get()) {
+ return STOP_PROPAGATION;
+ }
+ }
+
+ /**
+ * @param {Array} uids
+ */
+ onBuildingsSelected(uids) {
+ if (uids.length === 0) {
+ return;
+ }
+ this.currentBlueprint.set(Blueprint.fromUids(this.root, uids));
+ }
+
+ /**
+ *
+ * @param {DrawParameters} parameters
+ */
+ draw(parameters) {
+ const blueprint = this.currentBlueprint.get();
+ if (!blueprint) {
+ return;
+ }
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return;
+ }
+
+ const worldPos = this.root.camera.screenToWorld(mousePosition);
+ const tile = worldPos.toTileSpace();
+ blueprint.draw(parameters, tile);
+ }
+}
diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js
index fa477dbd..39fae8c9 100644
--- a/src/js/game/hud/parts/building_placer.js
+++ b/src/js/game/hud/parts/building_placer.js
@@ -39,6 +39,8 @@ export class HUDBuildingPlacer extends BaseHUDPart {
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this);
keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this);
+ this.root.hud.signals.buildingsSelectedForCopy.add(this.abortPlacement, this);
+
this.domAttach = new DynamicDomAttach(this.root, this.element, {});
this.root.camera.downPreHandler.add(this.onMouseDown, this);
@@ -255,6 +257,7 @@ export class HUDBuildingPlacer extends BaseHUDPart {
origin: new Vector(0, 0),
rotation: 0,
tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(),
+ blueprintSpriteKey: "",
})
);
metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get());
diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js
index ddcf9117..3c251d66 100644
--- a/src/js/game/hud/parts/mass_selector.js
+++ b/src/js/game/hud/parts/mass_selector.js
@@ -5,12 +5,13 @@ import { DrawParameters } from "../../../core/draw_parameters";
import { Entity } from "../../entity";
import { Loader } from "../../../core/loader";
import { globalConfig } from "../../../core/config";
-import { makeDiv } from "../../../core/utils";
+import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { createLogger } from "../../../core/logging";
import { enumMouseButton } from "../../camera";
import { T } from "../../../translations";
import { KEYMAPPINGS } from "../../key_action_mapper";
+import { THEME } from "../../theme";
const logger = createLogger("hud/mass_selector");
@@ -20,13 +21,17 @@ export class HUDMassSelector extends BaseHUDPart {
.getBinding(KEYMAPPINGS.massSelect.confirmMassDelete)
.getKeyCodeString();
const abortKeybinding = this.root.keyMapper.getBinding(KEYMAPPINGS.general.back).getKeyCodeString();
+ const copyKeybinding = this.root.keyMapper
+ .getBinding(KEYMAPPINGS.massSelect.massSelectCopy)
+ .getKeyCodeString();
this.element = makeDiv(
parent,
"ingame_HUD_MassSelector",
[],
- T.ingame.massDelete.infoText
+ T.ingame.massSelect.infoText
.replace("", removalKeybinding)
+ .replace("", copyKeybinding)
.replace("", abortKeybinding)
);
}
@@ -36,7 +41,7 @@ export class HUDMassSelector extends BaseHUDPart {
this.currentSelectionStart = null;
this.currentSelectionEnd = null;
- this.entityUidsMarkedForDeletion = new Set();
+ this.selectedUids = new Set();
this.root.signals.entityQueuedForDestroy.add(this.onEntityDestroyed, this);
@@ -48,6 +53,7 @@ export class HUDMassSelector extends BaseHUDPart {
this.root.keyMapper
.getBinding(KEYMAPPINGS.massSelect.confirmMassDelete)
.add(this.confirmDelete, this);
+ this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCopy).add(this.startCopy, this);
this.domAttach = new DynamicDomAttach(this.root, this.element);
}
@@ -57,7 +63,7 @@ export class HUDMassSelector extends BaseHUDPart {
* @param {Entity} entity
*/
onEntityDestroyed(entity) {
- this.entityUidsMarkedForDeletion.delete(entity.uid);
+ this.selectedUids.delete(entity.uid);
}
/**
@@ -65,24 +71,50 @@ export class HUDMassSelector extends BaseHUDPart {
*/
onBack() {
// Clear entities on escape
- if (this.entityUidsMarkedForDeletion.size > 0) {
- this.entityUidsMarkedForDeletion = new Set();
+ if (this.selectedUids.size > 0) {
+ this.selectedUids = new Set();
return STOP_PROPAGATION;
}
}
confirmDelete() {
- const entityUids = Array.from(this.entityUidsMarkedForDeletion);
+ if (this.selectedUids.size > 500) {
+ const { ok } = this.root.hud.parts.dialogs.showWarning(
+ T.dialogs.massDeleteConfirm.title,
+ T.dialogs.massDeleteConfirm.desc.replace(
+ "",
+ "" + formatBigNumberFull(this.selectedUids.size)
+ ),
+ ["cancel:good", "ok:bad"]
+ );
+ ok.add(() => this.doDelete());
+ } else {
+ this.doDelete();
+ }
+ }
+
+ doDelete() {
+ const entityUids = Array.from(this.selectedUids);
for (let i = 0; i < entityUids.length; ++i) {
const uid = entityUids[i];
const entity = this.root.entityMgr.findByUid(uid);
if (!this.root.logic.tryDeleteBuilding(entity)) {
logger.error("Error in mass delete, could not remove building");
- this.entityUidsMarkedForDeletion.delete(uid);
+ this.selectedUids.delete(uid);
}
}
}
+ startCopy() {
+ if (this.selectedUids.size > 0) {
+ this.root.hud.signals.buildingsSelectedForCopy.dispatch(Array.from(this.selectedUids));
+ this.selectedUids = new Set();
+ this.root.soundProxy.playUiClick();
+ } else {
+ this.root.soundProxy.playUiError();
+ }
+ }
+
/**
* mouse down pre handler
* @param {Vector} pos
@@ -99,7 +131,7 @@ export class HUDMassSelector extends BaseHUDPart {
if (!this.root.app.inputMgr.shiftIsDown) {
// Start new selection
- this.entityUidsMarkedForDeletion = new Set();
+ this.selectedUids = new Set();
}
this.currentSelectionStart = pos.copy();
@@ -132,7 +164,7 @@ export class HUDMassSelector extends BaseHUDPart {
for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
const contents = this.root.map.getTileContentXY(x, y);
if (contents && this.root.logic.canDeleteBuilding(contents)) {
- this.entityUidsMarkedForDeletion.add(contents.uid);
+ this.selectedUids.add(contents.uid);
}
}
}
@@ -143,7 +175,7 @@ export class HUDMassSelector extends BaseHUDPart {
}
update() {
- this.domAttach.update(this.entityUidsMarkedForDeletion.size > 0);
+ this.domAttach.update(this.selectedUids.size > 0);
}
/**
@@ -151,6 +183,8 @@ export class HUDMassSelector extends BaseHUDPart {
* @param {DrawParameters} parameters
*/
draw(parameters) {
+ const boundsBorder = 2;
+
if (this.currentSelectionStart) {
const worldStart = this.root.camera.screenToWorld(this.currentSelectionStart);
const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd);
@@ -165,8 +199,8 @@ export class HUDMassSelector extends BaseHUDPart {
const realTileEnd = tileStart.max(tileEnd);
parameters.context.lineWidth = 1;
- parameters.context.fillStyle = "rgba(255, 127, 127, 0.2)";
- parameters.context.strokeStyle = "rgba(255, 127, 127, 0.5)";
+ parameters.context.fillStyle = THEME.map.selectionBackground;
+ parameters.context.strokeStyle = THEME.map.selectionOutline;
parameters.context.beginPath();
parameters.context.rect(
realWorldStart.x,
@@ -177,34 +211,40 @@ export class HUDMassSelector extends BaseHUDPart {
parameters.context.fill();
parameters.context.stroke();
+ parameters.context.fillStyle = THEME.map.selectionOverlay;
+
for (let x = realTileStart.x; x <= realTileEnd.x; ++x) {
for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
const contents = this.root.map.getTileContentXY(x, y);
if (contents && this.root.logic.canDeleteBuilding(contents)) {
const staticComp = contents.components.StaticMapEntity;
- const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
- this.deletionMarker.drawCachedCentered(
- parameters,
- center.x,
- center.y,
- globalConfig.tileSize * 0.5
+ const bounds = staticComp.getTileSpaceBounds();
+ parameters.context.beginRoundedRect(
+ bounds.x * globalConfig.tileSize + boundsBorder,
+ bounds.y * globalConfig.tileSize + boundsBorder,
+ bounds.w * globalConfig.tileSize - 2 * boundsBorder,
+ bounds.h * globalConfig.tileSize - 2 * boundsBorder,
+ 2
);
+ parameters.context.fill();
}
}
}
}
- this.entityUidsMarkedForDeletion.forEach(uid => {
+ parameters.context.fillStyle = THEME.map.selectionOverlay;
+ this.selectedUids.forEach(uid => {
const entity = this.root.entityMgr.findByUid(uid);
const staticComp = entity.components.StaticMapEntity;
- const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
-
- this.deletionMarker.drawCachedCentered(
- parameters,
- center.x,
- center.y,
- globalConfig.tileSize * 0.5
+ const bounds = staticComp.getTileSpaceBounds();
+ parameters.context.beginRoundedRect(
+ bounds.x * globalConfig.tileSize + boundsBorder,
+ bounds.y * globalConfig.tileSize + boundsBorder,
+ bounds.w * globalConfig.tileSize - 2 * boundsBorder,
+ bounds.h * globalConfig.tileSize - 2 * boundsBorder,
+ 2
);
+ parameters.context.fill();
});
}
}
diff --git a/src/js/game/hud/parts/tutorial_hints.js b/src/js/game/hud/parts/tutorial_hints.js
index 853d054f..27fd5530 100644
--- a/src/js/game/hud/parts/tutorial_hints.js
+++ b/src/js/game/hud/parts/tutorial_hints.js
@@ -6,6 +6,7 @@ import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { T } from "../../../translations";
+import { globalConfig } from "../../../core/config";
const tutorialVideos = [1, 2, 3, 4, 5, 6, 7, 9, 10, 11];
@@ -56,7 +57,7 @@ export class HUDPartTutorialHints extends BaseHUDPart {
this.currentShownLevel = new TrackedState(this.updateVideoUrl, this);
this.root.signals.postLoadHook.add(() => {
- if (this.root.hubGoals.level === 1) {
+ if (this.root.hubGoals.level === 1 && !(G_IS_DEV && globalConfig.debug.disableTutorialHints)) {
this.root.hud.parts.dialogs.showInfo(
T.dialogs.hintDescription.title,
T.dialogs.hintDescription.desc
diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js
index 9ab089c3..34df29c8 100644
--- a/src/js/game/key_action_mapper.js
+++ b/src/js/game/key_action_mapper.js
@@ -60,6 +60,7 @@ export const KEYMAPPINGS = {
massSelect: {
massSelectStart: { keyCode: 17, builtin: true }, // CTRL
massSelectSelectMultiple: { keyCode: 16, builtin: true }, // SHIFT
+ massSelectCopy: { keyCode: key("C") },
confirmMassDelete: { keyCode: key("X") },
},
diff --git a/src/js/game/logic.js b/src/js/game/logic.js
index c96364da..dc89f8c2 100644
--- a/src/js/game/logic.js
+++ b/src/js/game/logic.js
@@ -60,6 +60,7 @@ export class GameLogic {
origin,
tileSize: building.getDimensions(variant),
rotation,
+ blueprintSpriteKey: "",
});
const rect = checker.getTileSpaceBounds();
@@ -168,6 +169,7 @@ export class GameLogic {
origin,
tileSize: building.getDimensions(variant),
rotation,
+ blueprintSpriteKey: "",
});
const rect = checker.getTileSpaceBounds();
diff --git a/src/js/game/map.js b/src/js/game/map.js
index 3fd82844..ef745c6d 100644
--- a/src/js/game/map.js
+++ b/src/js/game/map.js
@@ -147,6 +147,17 @@ export class BaseMap extends BasicSerializableObject {
return chunk && chunk.getTileContentFromWorldCoords(tile.x, tile.y) != null;
}
+ /**
+ * Checks if the tile is used
+ * @param {number} x
+ * @param {number} y
+ * @returns {boolean}
+ */
+ isTileUsedXY(x, y) {
+ const chunk = this.getChunkAtTileOrNull(x, y);
+ return chunk && chunk.getTileContentFromWorldCoords(x, y) != null;
+ }
+
/**
* Sets the tiles content
* @param {Vector} tile
diff --git a/src/js/game/meta_building.js b/src/js/game/meta_building.js
index ad360ac0..8753aac5 100644
--- a/src/js/game/meta_building.js
+++ b/src/js/game/meta_building.js
@@ -154,6 +154,9 @@ export class MetaBuilding {
*/
createAndPlaceEntity({ root, origin, rotation, originalRotation, rotationVariant, variant }) {
const entity = new Entity(root);
+
+ const blueprintSprite = this.getBlueprintSprite(rotationVariant, variant);
+
entity.addComponent(
new StaticMapEntityComponent({
spriteKey:
@@ -166,6 +169,7 @@ export class MetaBuilding {
originalRotation,
tileSize: this.getDimensions(variant).copy(),
silhouetteColor: this.getSilhouetteColor(),
+ blueprintSpriteKey: blueprintSprite ? blueprintSprite.spriteName : "",
})
);
diff --git a/src/js/game/themes/dark.json b/src/js/game/themes/dark.json
index caf28bfb..fa4c22a8 100644
--- a/src/js/game/themes/dark.json
+++ b/src/js/game/themes/dark.json
@@ -4,7 +4,7 @@
"background": "#2e2f37",
"grid": "rgba(255, 255, 255, 0.02)",
"gridLineWidth": 0.5,
-
+ "selectionColor": "rgba(127, 127, 255, 0.5)",
"resources": {
"shape": "#3d3f4a",
"red": "#4a3d3f",
diff --git a/src/js/game/themes/light.json b/src/js/game/themes/light.json
index 4837574c..59e9e58f 100644
--- a/src/js/game/themes/light.json
+++ b/src/js/game/themes/light.json
@@ -5,6 +5,10 @@
"grid": "#fafafa",
"gridLineWidth": 1,
+ "selectionOverlay": "rgba(74, 163, 223, 0.7)",
+ "selectionOutline": "rgba(74, 163, 223, 0.5)",
+ "selectionBackground": "rgba(74, 163, 223, 0.2)",
+
"resources": {
"shape": "#eaebec",
"red": "#ffbfc1",
diff --git a/src/js/savegame/savegame.js b/src/js/savegame/savegame.js
index 8b9d2b3b..8027c188 100644
--- a/src/js/savegame/savegame.js
+++ b/src/js/savegame/savegame.js
@@ -11,8 +11,7 @@ import { createLogger } from "../core/logging";
import { globalConfig } from "../core/config";
import { SavegameInterface_V1000 } from "./schemas/1000";
import { getSavegameInterface } from "./savegame_interface_registry";
-import { compressObject } from "./savegame_compressor";
-import { compressX64 } from "../core/lzstring";
+import { SavegameInterface_V1001 } from "./schemas/1001";
const logger = createLogger("savegame");
@@ -29,7 +28,7 @@ export class Savegame extends ReadWriteProxy {
this.internalId = internalId;
this.metaDataRef = metaDataRef;
- /** @type {SavegameData} */
+ /** @type {import("./savegame_typedefs").SavegameData} */
this.currentData = this.getDefaultData();
}
@@ -39,14 +38,14 @@ export class Savegame extends ReadWriteProxy {
* @returns {number}
*/
static getCurrentVersion() {
- return 1000;
+ return 1001;
}
/**
* @returns {typeof BaseSavegameInterface}
*/
static getReaderClass() {
- return SavegameInterface_V1000;
+ return SavegameInterface_V1001;
}
/**
@@ -58,7 +57,7 @@ export class Savegame extends ReadWriteProxy {
/**
* Returns the savegames default data
- * @returns {SavegameData}
+ * @returns {import("./savegame_typedefs").SavegameData}
*/
getDefaultData() {
return {
@@ -73,18 +72,25 @@ export class Savegame extends ReadWriteProxy {
/**
* Migrates the savegames data
- * @param {SavegameData} data
+ * @param {import("./savegame_typedefs").SavegameData} data
*/
migrate(data) {
if (data.version < 1000) {
return ExplainedResult.bad("Can not migrate savegame, too old");
}
+
+ console.log("TODO: Migrate from", data.version);
+ if (data.version === 1000) {
+ SavegameInterface_V1001.migrate1000to1001(data);
+ data.version = 1001;
+ }
+
return ExplainedResult.good();
}
/**
* Verifies the savegames data
- * @param {SavegameData} data
+ * @param {import("./savegame_typedefs").SavegameData} data
*/
verify(data) {
if (!data.dump) {
@@ -109,7 +115,7 @@ export class Savegame extends ReadWriteProxy {
}
/**
* Returns the statistics of the savegame
- * @returns {SavegameStats}
+ * @returns {import("./savegame_typedefs").SavegameStats}
*/
getStatistics() {
return this.currentData.stats;
@@ -132,7 +138,7 @@ export class Savegame extends ReadWriteProxy {
/**
* Returns the current game dump
- * @returns {SerializedGame}
+ * @returns {import("./savegame_typedefs").SerializedGame}
*/
getCurrentDump() {
return this.currentData.dump;
diff --git a/src/js/savegame/savegame_interface_registry.js b/src/js/savegame/savegame_interface_registry.js
index 6ad22a42..2560b23e 100644
--- a/src/js/savegame/savegame_interface_registry.js
+++ b/src/js/savegame/savegame_interface_registry.js
@@ -1,10 +1,12 @@
import { BaseSavegameInterface } from "./savegame_interface";
import { SavegameInterface_V1000 } from "./schemas/1000";
import { createLogger } from "../core/logging";
+import { SavegameInterface_V1001 } from "./schemas/1001";
/** @type {Object.} */
const interfaces = {
1000: SavegameInterface_V1000,
+ 1001: SavegameInterface_V1001,
};
const logger = createLogger("savegame_interface_registry");
diff --git a/src/js/savegame/savegame_typedefs.js b/src/js/savegame/savegame_typedefs.js
index ca72d856..821306a4 100644
--- a/src/js/savegame/savegame_typedefs.js
+++ b/src/js/savegame/savegame_typedefs.js
@@ -4,14 +4,7 @@
* }} SavegameStats
*/
-/**
- * @typedef {{
- * x: number,
- * y: number,
- * uid: number,
- * key: string
- * }} SerializedMapResource
- */
+import { Entity } from "../game/entity";
/**
* @typedef {{
@@ -20,7 +13,7 @@
* entityMgr: any,
* map: any,
* hubGoals: any,
- * entities: Array
+ * entities: Array
* }} SerializedGame
*/
diff --git a/src/js/savegame/schemas/1001.js b/src/js/savegame/schemas/1001.js
new file mode 100644
index 00000000..7604dec4
--- /dev/null
+++ b/src/js/savegame/schemas/1001.js
@@ -0,0 +1,52 @@
+import { SavegameInterface_V1000 } from "./1000.js";
+import { createLogger } from "../../core/logging.js";
+
+const schema = require("./1001.json");
+
+const logger = createLogger("savegame_interface/1001");
+
+export class SavegameInterface_V1001 extends SavegameInterface_V1000 {
+ getVersion() {
+ return 1001;
+ }
+
+ getSchemaUncached() {
+ return schema;
+ }
+
+ /**
+ * @param {import("../savegame_typedefs.js").SavegameData} data
+ */
+ static migrate1000to1001(data) {
+ logger.log("Migrating 1000 to 1001");
+ const dump = data.dump;
+ if (!dump) {
+ return true;
+ }
+
+ const entities = dump.entities;
+ for (let i = 0; i < entities.length; ++i) {
+ const entity = entities[i];
+
+ const staticComp = entity.components.StaticMapEntity;
+ const beltComp = entity.components.Belt;
+ if (staticComp) {
+ if (staticComp.spriteKey) {
+ staticComp.blueprintSpriteKey = staticComp.spriteKey.replace(
+ "sprites/buildings",
+ "sprites/blueprints"
+ );
+ } else {
+ if (entity.components.Hub) {
+ staticComp.blueprintSpriteKey = "";
+ } else if (beltComp) {
+ const direction = beltComp.direction;
+ staticComp.blueprintSpriteKey = "sprites/blueprints/belt_" + direction + ".png";
+ } else {
+ assertAlways(false, "Could not deduct entity type for migrating 1000 -> 1001");
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/js/savegame/schemas/1001.json b/src/js/savegame/schemas/1001.json
new file mode 100644
index 00000000..6682f615
--- /dev/null
+++ b/src/js/savegame/schemas/1001.json
@@ -0,0 +1,5 @@
+{
+ "type": "object",
+ "required": [],
+ "additionalProperties": true
+}
diff --git a/src/js/savegame/serializer_internal.js b/src/js/savegame/serializer_internal.js
index 3eb0f72b..ec761beb 100644
--- a/src/js/savegame/serializer_internal.js
+++ b/src/js/savegame/serializer_internal.js
@@ -43,7 +43,7 @@ export class SerializerInternal {
* @param {Entity} payload
*/
deserializeEntity(root, payload) {
- const entity = new Entity(null);
+ const entity = new Entity(root);
this.deserializeComponents(entity, payload.components);
root.entityMgr.registerEntity(entity, payload.uid);
diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js
index 9dd18909..d95f520e 100644
--- a/src/js/states/main_menu.js
+++ b/src/js/states/main_menu.js
@@ -198,12 +198,12 @@ export class MainMenuState extends GameState {
this.trackClicks(qs(".mainContainer .importButton"), this.requestImportSavegame);
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
- // // const games = this.app.savegameMgr.getSavegamesMetaData();
- // if (games.length > 0) {
- // this.resumeGame(games[0]);
- // } else {
- this.onPlayButtonClicked();
- // }
+ const games = this.app.savegameMgr.getSavegamesMetaData();
+ if (games.length > 0) {
+ this.resumeGame(games[0]);
+ } else {
+ this.onPlayButtonClicked();
+ }
}
// Initialize video
diff --git a/src/js/states/preload.js b/src/js/states/preload.js
index 43c84eb0..29d5bdd4 100644
--- a/src/js/states/preload.js
+++ b/src/js/states/preload.js
@@ -6,6 +6,7 @@ import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
import { T } from "../translations";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { CHANGELOG } from "../changelog";
+import { globalConfig } from "../core/config";
const logger = createLogger("state/preload");
@@ -179,6 +180,10 @@ export class PreloadState extends GameState {
.then(() => this.setStatus("Checking changelog"))
.then(() => {
+ if (G_IS_DEV && globalConfig.debug.disableUpgradeNotification) {
+ return;
+ }
+
return this.app.storage
.readFileAsync("lastversion.bin")
.catch(err => {
diff --git a/translations/base-en.yaml b/translations/base-en.yaml
index 2318c342..889e6583 100644
--- a/translations/base-en.yaml
+++ b/translations/base-en.yaml
@@ -172,6 +172,11 @@ dialogs:
All shapes you produce can be used to unlock upgrades - Don't destroy your old factories!
The upgrades tab can be found on the top right corner of the screen.
+ massDeleteConfirm:
+ title: Confirm delete
+ desc: >-
+ You are deleting a lot of buildings ( to be exact)! Are you sure you want to do this?
+
ingame:
# This is shown in the top left corner and displays useful keybindings in
# every situation
@@ -221,10 +226,10 @@ ingame:
newUpgrade: A new upgrade is available!
gameSaved: Your game has been saved.
- # Mass delete information, this is when you hold CTRL and then drag with your mouse
- # to select multiple buildings to delete
- massDelete:
- infoText: Press to remove selected buildings and to cancel.
+ # Mass select information, this is when you hold CTRL and then drag with your mouse
+ # to select multiple buildings
+ massSelect:
+ infoText: Press to copy, to remove and to cancel.
# The "Upgrades" window
shop: