Further blueprint improvements

This commit is contained in:
tobspr 2020-05-27 15:03:36 +02:00
parent 19c770201f
commit dfe1e64b27
17 changed files with 159 additions and 152 deletions

View File

@ -20,8 +20,7 @@
@include SuperSmallText;
@include S(padding-left, 20px);
strong {
background: $colorBlueBright;
color: #fff;
color: #aaa;
text-transform: uppercase;
@include S(padding, 1px, 2px);
@include S(margin-right, 3px);

View File

@ -3,24 +3,27 @@ export const CHANGELOG = [
version: "1.1.0",
date: "unreleased",
entries: [
"<strong>UX</strong> Added background to toolbar to increase contrast",
"<strong>UX</strong> Added confirmation when deleting more than 500 buildings at a time",
"Allow changing all keybindings, including CTRL, ALT and SHIFT",
"Allow holding SHIFT to rotate counter clockwise",
"Added confirmation when deleting more than 500 buildings at a time",
"Added background to toolbar to increase contrast",
"Allow placing extractors anywhere again, but they don't work at all if not placed on a resource",
],
},
{
version: "1.0.4",
date: "26.05.2020",
entries: [
"<strong>Balancing</strong> Reduce cost of first painting upgrade, and change 'Shape Processing' to 'Cutting, Rotating & Stacking'",
"<strong>Tutorial</strong> Add dialog after completing level 2 to check out the upgrades tab.",
"<strong>Misc</strong> Allow changing the keybindings in the demo version",
"Reduce cost of first painting upgrade, and change 'Shape Processing' to 'Cutting, Rotating & Stacking'",
"Add dialog after completing level 2 to check out the upgrades tab.",
"Allow changing the keybindings in the demo version",
],
},
{
version: "1.0.3",
date: "24.05.2020",
entries: [
"<strong>Balancing</strong> Reduced the amount of shapes required for the first 5 levels to make it easier to get into the game.",
"Reduced the amount of shapes required for the first 5 levels to make it easier to get into the game.",
],
},
{

View File

@ -40,7 +40,7 @@ export const globalConfig = {
// Map
mapChunkSize: 16,
mapChunkPrerenderMinZoom: 1.3,
mapChunkPrerenderMinZoom: 1.15,
mapChunkOverviewMinZoom: 0.7,
// Belt speeds

View File

@ -23,10 +23,6 @@ export class InputDistributor {
/** @type {Array<function(any) : boolean>} */
this.filters = [];
this.shiftIsDown = false;
this.altIsDown = false;
this.ctrlIsDown = false;
this.bindToEvents();
}
@ -176,27 +172,13 @@ export class InputDistributor {
* Handles when the page got blurred
*/
handleBlur() {
this.ctrlIsDown = false;
this.shiftIsDown = false;
this.altIsDown = false;
this.forwardToReceiver("pageBlur", {});
this.forwardToReceiver("shiftUp", {});
}
/**
* @param {KeyboardEvent} event
*/
handleKeydown(event) {
if (event.keyCode === 16) {
this.shiftIsDown = true;
}
if (event.keyCode === 17) {
this.ctrlIsDown = true;
}
if (event.keyCode === 18) {
this.altIsDown = true;
}
if (
// TAB
event.keyCode === 9 ||
@ -230,19 +212,6 @@ export class InputDistributor {
* @param {KeyboardEvent} event
*/
handleKeyup(event) {
if (event.keyCode === 16) {
this.shiftIsDown = false;
this.forwardToReceiver("shiftUp", {});
}
if (event.keyCode === 17) {
this.ctrlIsDown = false;
this.forwardToReceiver("ctrlUp", {});
}
if (event.keyCode === 18) {
this.altIsDown = false;
this.forwardToReceiver("altUp", {});
}
this.forwardToReceiver("keyup", {
keyCode: event.keyCode,
shift: event.shiftKey,

View File

@ -9,9 +9,6 @@ export class InputReceiver {
this.keydown = new Signal();
this.keyup = new Signal();
this.pageBlur = new Signal();
this.shiftUp = new Signal();
this.altUp = new Signal();
this.ctrlUp = new Signal();
// Dispatched on destroy
this.destroyed = new Signal();

View File

@ -41,23 +41,6 @@ export class MetaMinerBuilding extends MetaBuilding {
return super.getAvailableVariants(root);
}
/**
* @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 }) {
// Make sure its placed above a resource
const lowerLayer = root.map.getLowerLayerContentXY(origin.x, origin.y);
if (!lowerLayer) {
return false;
}
return true;
}
/**
* Creates the entity at the given location
* @param {Entity} entity

View File

@ -34,6 +34,7 @@ export class GameSystemWithFilter extends GameSystem {
this.root.signals.entityQueuedForDestroy.add(this.internalPopEntityIfMatching, this);
this.root.signals.postLoadHook.add(this.internalPostLoadHook, this);
this.root.signals.bulkOperationFinished.add(this.refreshCaches, this);
}
/**
@ -175,7 +176,7 @@ export class GameSystemWithFilter extends GameSystem {
internalRegisterEntity(entity) {
this.allEntities.push(entity);
if (this.root.gameInitialized) {
if (this.root.gameInitialized && !this.root.bulkOperationRunning) {
// Sort entities by uid so behaviour is predictable
this.allEntities.sort((a, b) => a.uid - b.uid);
}

View File

@ -1,10 +1,9 @@
import { GameRoot } from "../../root";
import { DrawParameters } from "../../../core/draw_parameters";
import { Loader } from "../../../core/loader";
import { createLogger } from "../../../core/logging";
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";
import { GameRoot } from "../../root";
const logger = createLogger("blueprint");
@ -17,6 +16,7 @@ export class Blueprint {
}
/**
* Creates a new blueprint from the given entity uids
* @param {GameRoot} root
* @param {Array<number>} 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;
}
}
}
if (placeable) {
for (let x = rect.x; x < rect.right(); ++x) {
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) {
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;
}
if (contents && !contents.components.ReplaceableMapEntity) {
placeable = false;
break placementCheck;
}
}
}
const clone = entity.duplicateWithoutContents();
clone.components.StaticMapEntity.origin.addInplace(tile);
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;
}
}
}
}
root.map.placeStaticEntity(clone);
root.entityMgr.registerEntity(clone);
anyPlaced = true;
const clone = entity.duplicateWithoutContents();
clone.components.StaticMapEntity.origin.addInplace(tile);
root.map.placeStaticEntity(clone);
root.entityMgr.registerEntity(clone);
anyPlaced = true;
}
}
}
return anyPlaced;
return anyPlaced;
});
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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 {
</div>
<div class="binding placementOnly">
<code class="keybinding builtinKey shift"> ${T.global.keys.shift}</code>
<code class="keybinding builtinKey shift"> ${getKeycode(
KEYMAPPINGS.placementModifiers.placeMultiple
)}</code>
<label>${T.ingame.keybindingsOverlay.placeMultiple}</label>
</div>
`
@ -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() {}
}

View File

@ -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
},

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])) {

View File

@ -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