} uids
*/
@@ -48,7 +48,7 @@ export class Blueprint {
}
/**
- *
+ * Draws the blueprint at the given origin
* @param {DrawParameters} parameters
*/
draw(parameters, tile) {
@@ -93,6 +93,31 @@ export class Blueprint {
}
/**
+ * Rotates the blueprint clockwise
+ */
+ rotateCw() {
+ for (let i = 0; i < this.entities.length; ++i) {
+ const entity = this.entities[i];
+ const staticComp = entity.components.StaticMapEntity;
+
+ staticComp.rotation = (staticComp.rotation + 90) % 360;
+ staticComp.originalRotation = (staticComp.originalRotation + 90) % 360;
+ staticComp.origin = staticComp.origin.rotateFastMultipleOf90(90);
+ }
+ }
+
+ /**
+ * Rotates the blueprint counter clock wise
+ */
+ rotateCcw() {
+ // Well ...
+ for (let i = 0; i < 3; ++i) {
+ this.rotateCw();
+ }
+ }
+
+ /**
+ * Checks if the blueprint can be placed at the given tile
* @param {GameRoot} root
* @param {Vector} tile
*/
@@ -123,54 +148,57 @@ export class Blueprint {
}
/**
+ * Attempts to place the blueprint at the given tile
* @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;
+ return root.logic.performBulkOperation(() => {
+ 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"
+ 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"
);
- return false;
+ 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);
+ const clone = entity.duplicateWithoutContents();
+ clone.components.StaticMapEntity.origin.addInplace(tile);
- root.map.placeStaticEntity(clone);
- root.entityMgr.registerEntity(clone);
- anyPlaced = true;
+ root.map.placeStaticEntity(clone);
+ root.entityMgr.registerEntity(clone);
+ anyPlaced = true;
+ }
}
- }
- return anyPlaced;
+ return anyPlaced;
+ });
}
}
diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js
index 32993ffc..a3ff0f14 100644
--- a/src/js/game/hud/parts/blueprint_placer.js
+++ b/src/js/game/hud/parts/blueprint_placer.js
@@ -21,6 +21,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
keyActionMapper
.getBinding(KEYMAPPINGS.placement.abortBuildingPlacement)
.add(this.abortPlacement, this);
+ keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this);
this.root.camera.downPreHandler.add(this.onMouseDown, this);
this.root.camera.movePreHandler.add(this.onMouseMove, this);
@@ -54,13 +55,13 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
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);
- }
+ // This actually feels weird
+ // if (!this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeMultiple).currentlyDown) {
+ // this.currentBlueprint.set(null);
+ // }
}
}
@@ -81,6 +82,16 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
this.currentBlueprint.set(Blueprint.fromUids(this.root, uids));
}
+ rotateBlueprint() {
+ if (this.currentBlueprint.get()) {
+ if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).currentlyDown) {
+ this.currentBlueprint.get().rotateCcw();
+ } else {
+ this.currentBlueprint.get().rotateCw();
+ }
+ }
+ }
+
/**
*
* @param {DrawParameters} parameters
diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js
index 1e09dbd4..ab3ebae4 100644
--- a/src/js/game/hud/parts/building_placer.js
+++ b/src/js/game/hud/parts/building_placer.js
@@ -161,14 +161,19 @@ export class HUDBuildingPlacer extends BaseHUDPart {
if (
metaBuilding &&
metaBuilding.getRotateAutomaticallyWhilePlacing(this.currentVariant.get()) &&
- !this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation).currentlyDown
+ !this.root.keyMapper.getBinding(
+ KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation
+ ).currentlyDown
) {
const delta = newPos.sub(oldPos);
const angleDeg = Math_degrees(delta.angle());
this.currentBaseRotation = (Math.round(angleDeg / 90) * 90 + 360) % 360;
// Holding alt inverts the placement
- if (this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeInverse).currentlyDown) {
+ if (
+ this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeInverse)
+ .currentlyDown
+ ) {
this.currentBaseRotation = (180 + this.currentBaseRotation) % 360;
}
}
@@ -389,7 +394,12 @@ export class HUDBuildingPlacer extends BaseHUDPart {
tryRotate() {
const selectedBuilding = this.currentMetaBuilding.get();
if (selectedBuilding) {
- this.currentBaseRotation = (this.currentBaseRotation + 90) % 360;
+ if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).currentlyDown) {
+ this.currentBaseRotation = (this.currentBaseRotation + 270) % 360;
+ } else {
+ this.currentBaseRotation = (this.currentBaseRotation + 90) % 360;
+ }
+
const staticComp = this.fakeEntity.components.StaticMapEntity;
staticComp.rotation = this.currentBaseRotation;
}
@@ -468,8 +478,10 @@ export class HUDBuildingPlacer extends BaseHUDPart {
// Succesfully placed
if (
- metaBuilding.getFlipOrientationAfterPlacement() &&
- !this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation).currentlyDown
+ metaBuilding.getFlipOrientationAfterPlacement() &&
+ !this.root.keyMapper.getBinding(
+ KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation
+ ).currentlyDown
) {
this.currentBaseRotation = (180 + this.currentBaseRotation) % 360;
}
diff --git a/src/js/game/hud/parts/keybinding_overlay.js b/src/js/game/hud/parts/keybinding_overlay.js
index b143186a..300c0885 100644
--- a/src/js/game/hud/parts/keybinding_overlay.js
+++ b/src/js/game/hud/parts/keybinding_overlay.js
@@ -1,24 +1,16 @@
-import { BaseHUDPart } from "../base_hud_part";
import { makeDiv } from "../../../core/utils";
-import { getStringForKeyCode, KEYMAPPINGS } from "../../key_action_mapper";
-import { TrackedState } from "../../../core/tracked_state";
-import { queryParamOptions } from "../../../core/query_parameters";
import { T } from "../../../translations";
+import { getStringForKeyCode, KEYMAPPINGS } from "../../key_action_mapper";
+import { BaseHUDPart } from "../base_hud_part";
export class HUDKeybindingOverlay extends BaseHUDPart {
initialize() {
- this.shiftDownTracker = new TrackedState(this.onShiftStateChanged, this);
-
this.root.hud.signals.selectedPlacementBuildingChanged.add(
this.onSelectedBuildingForPlacementChanged,
this
);
}
- onShiftStateChanged(shiftDown) {
- this.element.classList.toggle("shiftDown", shiftDown);
- }
-
createElements(parent) {
const mapper = this.root.keyMapper;
@@ -70,7 +62,9 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
- ⇧ ${T.global.keys.shift}
+ ⇧ ${getKeycode(
+ KEYMAPPINGS.placementModifiers.placeMultiple
+ )}
`
@@ -81,7 +75,5 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
this.element.classList.toggle("placementActive", !!selectedMetaBuilding);
}
- update() {
- this.shiftDownTracker.set(this.root.app.inputMgr.shiftIsDown);
- }
+ update() {}
}
diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js
index acc78024..2c575501 100644
--- a/src/js/game/key_action_mapper.js
+++ b/src/js/game/key_action_mapper.js
@@ -53,6 +53,7 @@ export const KEYMAPPINGS = {
placement: {
abortBuildingPlacement: { keyCode: key("Q") },
rotateWhilePlacing: { keyCode: key("R") },
+ rotateInverseModifier: { keyCode: 16 }, // SHIFT
cycleBuildingVariants: { keyCode: key("T") },
cycleBuildings: { keyCode: 9 }, // TAB
},
diff --git a/src/js/game/logic.js b/src/js/game/logic.js
index dc89f8c2..90597e52 100644
--- a/src/js/game/logic.js
+++ b/src/js/game/logic.js
@@ -3,10 +3,11 @@ import { Entity } from "./entity";
import { Vector, enumDirectionToVector, enumDirection } from "../core/vector";
import { MetaBuilding } from "./meta_building";
import { StaticMapEntityComponent } from "./components/static_map_entity";
-import { Math_abs } from "../core/builtins";
+import { Math_abs, performanceNow } from "../core/builtins";
import { createLogger } from "../core/logging";
import { MetaBeltBaseBuilding, arrayBeltVariantToRotation } from "./buildings/belt_base";
import { SOUNDS } from "../platform/sound";
+import { round2Digits } from "../core/utils";
const logger = createLogger("ingame/logic");
@@ -132,17 +133,6 @@ export class GameLogic {
return false;
}
- if (
- !building.performAdditionalPlacementChecks(this.root, {
- origin,
- rotation,
- rotationVariant,
- variant,
- })
- ) {
- return false;
- }
-
return this.isAreaFreeToBuild({
origin,
rotation,
@@ -202,6 +192,24 @@ export class GameLogic {
return false;
}
+ /**
+ * Performs a bulk operation, not updating caches in the meantime
+ * @param {function} operation
+ */
+ performBulkOperation(operation) {
+ logger.log("Running bulk operation ...");
+ assert(!this.root.bulkOperationRunning, "Can not run two bulk operations twice");
+ this.root.bulkOperationRunning = true;
+ const now = performanceNow();
+ const returnValue = operation();
+ const duration = performanceNow() - now;
+ logger.log("Done in", round2Digits(duration), "ms");
+ assert(this.root.bulkOperationRunning, "Bulk operation = false while bulk operation was running");
+ this.root.bulkOperationRunning = false;
+ this.root.signals.bulkOperationFinished.dispatch();
+ return returnValue;
+ }
+
/**
* Returns whether the given building can get removed
* @param {Entity} building
diff --git a/src/js/game/meta_building.js b/src/js/game/meta_building.js
index 8753aac5..262a33cb 100644
--- a/src/js/game/meta_building.js
+++ b/src/js/game/meta_building.js
@@ -129,19 +129,6 @@ export class MetaBuilding {
return null;
}
- /**
- * Should perform additional placement checks
- * @param {GameRoot} root
- * @param {object} param0
- * @param {Vector} param0.origin
- * @param {number} param0.rotation
- * @param {number} param0.rotationVariant
- * @param {string} param0.variant
- */
- performAdditionalPlacementChecks(root, { origin, rotation, rotationVariant, variant }) {
- return true;
- }
-
/**
* Creates the entity at the given location
* @param {object} param0
diff --git a/src/js/game/root.js b/src/js/game/root.js
index 0c4e6792..91efd137 100644
--- a/src/js/game/root.js
+++ b/src/js/game/root.js
@@ -70,6 +70,11 @@ export class GameRoot {
/** @type {boolean} */
this.gameInitialized = false;
+ /**
+ * Whether a bulk operation is running
+ */
+ this.bulkOperationRunning = false;
+
//////// Other properties ///////
/** @type {Camera} */
@@ -151,6 +156,8 @@ export class GameRoot {
shapeDelivered: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
+
+ bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
};
// RNG's
diff --git a/src/js/game/systems/miner.js b/src/js/game/systems/miner.js
index 4ecf1e2d..5420cf36 100644
--- a/src/js/game/systems/miner.js
+++ b/src/js/game/systems/miner.js
@@ -17,9 +17,16 @@ export class MinerSystem extends GameSystemWithFilter {
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
+ // Check if miner is above an actual tile
+
const minerComp = entity.components.Miner;
const staticComp = entity.components.StaticMapEntity;
+ const tileBelow = this.root.map.getLowerLayerContentXY(staticComp.origin.x, staticComp.origin.y);
+ if (!tileBelow) {
+ continue;
+ }
+
// First, try to get rid of chained items
if (minerComp.itemChainBuffer.length > 0) {
if (this.tryPerformMinerEject(entity, minerComp.itemChainBuffer[0])) {
diff --git a/translations/base-en.yaml b/translations/base-en.yaml
index 889e6583..c12e81e9 100644
--- a/translations/base-en.yaml
+++ b/translations/base-en.yaml
@@ -557,6 +557,8 @@ keybindings:
abortBuildingPlacement: Abort Placement
rotateWhilePlacing: Rotate
+ rotateInverseModifier: >-
+ Modifier: Rotate CCW instead
cycleBuildingVariants: Cycle Variants
confirmMassDelete: Confirm Mass Delete
cycleBuildings: Cycle Buildings