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

Mirror buildings and blueprints

This commit is contained in:
Gerd Wachsmuth 2020-06-25 20:33:01 +02:00
parent 66eac93460
commit 12539f8045
18 changed files with 268 additions and 93 deletions

View File

@ -519,6 +519,49 @@ export class Vector {
} }
} }
/**
* Mirrors this vector
* @param {boolean} mirror
* @returns {Vector} new vector
*/
mirror(mirror = true) {
if (mirror) {
return new Vector(-this.x, this.y);
}
else {
return new Vector(this.x, this.y);
}
}
/**
* Helper method to mirror a direction
* @param {enumDirection} direction
* @param {boolean} mirror
* @returns {enumDirection}
*/
static mirrorDirection(direction, mirror = true) {
if (mirror) {
switch (direction) {
case enumDirection.left: return enumDirection.right;
case enumDirection.right: return enumDirection.left;
}
}
return direction;
}
/**
* Helper method to mirror an angle
* @param {number} angle
* @param {boolean} mirror
* @returns {number}
*/
static mirrorAngle(angle, mirror = true) {
if (mirror && (angle == 90 || angle == 270) ) {
return 360 - angle;
}
return angle;
}
/** /**
* Helper method to rotate a direction * Helper method to rotate a direction
* @param {enumDirection} direction * @param {enumDirection} direction

View File

@ -141,7 +141,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
* @param {string} variant * @param {string} variant
* @return {{ rotation: number, rotationVariant: number }} * @return {{ rotation: number, rotationVariant: number }}
*/ */
computeOptimalDirectionAndRotationVariantAtTile(root, tile, rotation, variant) { computeOptimalDirectionAndRotationVariantAtTile(root, tile, rotation, mirrored, variant) {
const topDirection = enumAngleToDirection[rotation]; const topDirection = enumAngleToDirection[rotation];
const rightDirection = enumAngleToDirection[(rotation + 90) % 360]; const rightDirection = enumAngleToDirection[(rotation + 90) % 360];
const bottomDirection = enumAngleToDirection[(rotation + 180) % 360]; const bottomDirection = enumAngleToDirection[(rotation + 180) % 360];
@ -191,6 +191,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
if (hasRightEjector && !hasLeftEjector) { if (hasRightEjector && !hasLeftEjector) {
return { return {
rotation: (rotation + 270) % 360, rotation: (rotation + 270) % 360,
mirrored: false,
rotationVariant: 2, rotationVariant: 2,
}; };
} }
@ -200,6 +201,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
if (hasLeftEjector && !hasRightEjector) { if (hasLeftEjector && !hasRightEjector) {
return { return {
rotation: (rotation + 90) % 360, rotation: (rotation + 90) % 360,
mirrored: false,
rotationVariant: 1, rotationVariant: 1,
}; };
} }
@ -213,6 +215,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
if (hasRightAcceptor && !hasLeftAcceptor) { if (hasRightAcceptor && !hasLeftAcceptor) {
return { return {
rotation, rotation,
mirrored: false,
rotationVariant: 2, rotationVariant: 2,
}; };
} }
@ -222,6 +225,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
if (hasLeftAcceptor && !hasRightAcceptor) { if (hasLeftAcceptor && !hasRightAcceptor) {
return { return {
rotation, rotation,
mirrored: false,
rotationVariant: 1, rotationVariant: 1,
}; };
} }
@ -229,6 +233,7 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
return { return {
rotation, rotation,
mirrored: false,
rotationVariant: 0, rotationVariant: 0,
}; };
} }

View File

@ -24,6 +24,10 @@ export class MetaHubBuilding extends MetaBuilding {
return false; return false;
} }
isMirrorable(variant) {
return false;
}
getBlueprintSprite() { getBlueprintSprite() {
return null; return null;
} }

View File

@ -18,6 +18,10 @@ export class MetaRotaterBuilding extends MetaBuilding {
super("rotater"); super("rotater");
} }
isMirrorable(variant) {
return false;
}
getSilhouetteColor() { getSilhouetteColor() {
return "#7dc6cd"; return "#7dc6cd";
} }

View File

@ -24,6 +24,10 @@ export class MetaTrashBuilding extends MetaBuilding {
return variant !== defaultBuildingVariant; return variant !== defaultBuildingVariant;
} }
isMirrorable(variant) {
return variant !== defaultBuildingVariant;
}
getSilhouetteColor() { getSilhouetteColor() {
return "#cd7d86"; return "#cd7d86";
} }

View File

@ -137,7 +137,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
* @param {string} variant * @param {string} variant
* @return {{ rotation: number, rotationVariant: number, connectedEntities?: Array<Entity> }} * @return {{ rotation: number, rotationVariant: number, connectedEntities?: Array<Entity> }}
*/ */
computeOptimalDirectionAndRotationVariantAtTile(root, tile, rotation, variant) { computeOptimalDirectionAndRotationVariantAtTile(root, tile, rotation, mirrored, variant) {
const searchDirection = enumAngleToDirection[rotation]; const searchDirection = enumAngleToDirection[rotation];
const searchVector = enumDirectionToVector[searchDirection]; const searchVector = enumDirectionToVector[searchDirection];
const tier = enumUndergroundBeltVariantToTier[variant]; const tier = enumUndergroundBeltVariantToTier[variant];
@ -164,6 +164,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
} }
return { return {
rotation: targetRotation, rotation: targetRotation,
mirrored: false,
rotationVariant: 1, rotationVariant: 1,
connectedEntities: [contents], connectedEntities: [contents],
}; };
@ -172,6 +173,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
if (undergroundComp.mode === enumUndergroundBeltMode.receiver) { if (undergroundComp.mode === enumUndergroundBeltMode.receiver) {
return { return {
rotation: rotation, rotation: rotation,
mirrored: false,
rotationVariant: 0, rotationVariant: 0,
connectedEntities: [contents], connectedEntities: [contents],
}; };
@ -185,6 +187,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
return { return {
rotation, rotation,
mirrored: false,
rotationVariant: 0, rotationVariant: 0,
}; };
} }

View File

@ -18,6 +18,7 @@ export class StaticMapEntityComponent extends Component {
tileSize: types.tileVector, tileSize: types.tileVector,
rotation: types.float, rotation: types.float,
originalRotation: types.float, originalRotation: types.float,
mirrored: types.bool,
spriteKey: types.nullable(types.string), spriteKey: types.nullable(types.string),
blueprintSpriteKey: types.string, blueprintSpriteKey: types.string,
silhouetteColor: types.nullable(types.string), silhouetteColor: types.nullable(types.string),
@ -30,6 +31,7 @@ export class StaticMapEntityComponent extends Component {
tileSize: this.tileSize.copy(), tileSize: this.tileSize.copy(),
rotation: this.rotation, rotation: this.rotation,
originalRotation: this.originalRotation, originalRotation: this.originalRotation,
mirrored: this.mirrored,
spriteKey: this.spriteKey, spriteKey: this.spriteKey,
silhouetteColor: this.silhouetteColor, silhouetteColor: this.silhouetteColor,
blueprintSpriteKey: this.blueprintSpriteKey, blueprintSpriteKey: this.blueprintSpriteKey,
@ -52,6 +54,7 @@ export class StaticMapEntityComponent extends Component {
tileSize = new Vector(1, 1), tileSize = new Vector(1, 1),
rotation = 0, rotation = 0,
originalRotation = 0, originalRotation = 0,
mirrored = false,
spriteKey = null, spriteKey = null,
silhouetteColor = null, silhouetteColor = null,
blueprintSpriteKey = null, blueprintSpriteKey = null,
@ -67,6 +70,7 @@ export class StaticMapEntityComponent extends Component {
this.spriteKey = spriteKey; this.spriteKey = spriteKey;
this.rotation = rotation; this.rotation = rotation;
this.originalRotation = originalRotation; this.originalRotation = originalRotation;
this.mirrored = mirrored;
this.silhouetteColor = silhouetteColor; this.silhouetteColor = silhouetteColor;
this.blueprintSpriteKey = blueprintSpriteKey; this.blueprintSpriteKey = blueprintSpriteKey;
} }
@ -76,32 +80,62 @@ export class StaticMapEntityComponent extends Component {
* @returns {Rectangle} * @returns {Rectangle}
*/ */
getTileSpaceBounds() { getTileSpaceBounds() {
switch (this.rotation) { if (!this.mirrored) {
case 0: switch (this.rotation) {
return new Rectangle(this.origin.x, this.origin.y, this.tileSize.x, this.tileSize.y); case 0:
case 90: return new Rectangle(this.origin.x, this.origin.y, this.tileSize.x, this.tileSize.y);
return new Rectangle( case 90:
this.origin.x - this.tileSize.y + 1, return new Rectangle(
this.origin.y, this.origin.x - this.tileSize.y + 1,
this.tileSize.y, this.origin.y,
this.tileSize.x this.tileSize.y,
); this.tileSize.x
case 180: );
return new Rectangle( case 180:
this.origin.x - this.tileSize.x + 1, return new Rectangle(
this.origin.y - this.tileSize.y + 1, this.origin.x - this.tileSize.x + 1,
this.tileSize.x, this.origin.y - this.tileSize.y + 1,
this.tileSize.y this.tileSize.x,
); this.tileSize.y
case 270: );
return new Rectangle( case 270:
this.origin.x, return new Rectangle(
this.origin.y - this.tileSize.x + 1, this.origin.x,
this.tileSize.y, this.origin.y - this.tileSize.x + 1,
this.tileSize.x this.tileSize.y,
); this.tileSize.x
default: );
assert(false, "Invalid rotation"); default:
assert(false, "Invalid rotation");
}
} else {
switch (this.rotation) {
case 0:
return new Rectangle(this.origin.x - this.tileSize.x + 1, this.origin.y, this.tileSize.x, this.tileSize.y);
case 90:
return new Rectangle(
this.origin.x - this.tileSize.y + 1,
this.origin.y - this.tileSize.x + 1,
this.tileSize.y,
this.tileSize.x
);
case 180:
return new Rectangle(
this.origin.x,
this.origin.y - this.tileSize.y + 1,
this.tileSize.x,
this.tileSize.y
);
case 270:
return new Rectangle(
this.origin.x,
this.origin.y,
this.tileSize.y,
this.tileSize.x
);
default:
assert(false, "Invalid rotation");
}
} }
} }
@ -111,7 +145,7 @@ export class StaticMapEntityComponent extends Component {
* @returns {Vector} * @returns {Vector}
*/ */
applyRotationToVector(vector) { applyRotationToVector(vector) {
return vector.rotateFastMultipleOf90(this.rotation); return vector.mirror(this.mirrored).rotateFastMultipleOf90(this.rotation);
} }
/** /**
@ -120,7 +154,7 @@ export class StaticMapEntityComponent extends Component {
* @returns {Vector} * @returns {Vector}
*/ */
unapplyRotationToVector(vector) { unapplyRotationToVector(vector) {
return vector.rotateFastMultipleOf90(360 - this.rotation); return vector.rotateFastMultipleOf90(360 - this.rotation).mirror(this.mirrored);
} }
/** /**
@ -129,7 +163,7 @@ export class StaticMapEntityComponent extends Component {
* @returns {enumDirection} * @returns {enumDirection}
*/ */
localDirectionToWorld(direction) { localDirectionToWorld(direction) {
return Vector.transformDirectionFromMultipleOf90(direction, this.rotation); return Vector.transformDirectionFromMultipleOf90(Vector.mirrorDirection(direction, this.mirrored), this.rotation);
} }
/** /**
@ -138,7 +172,7 @@ export class StaticMapEntityComponent extends Component {
* @returns {enumDirection} * @returns {enumDirection}
*/ */
worldDirectionToLocal(direction) { worldDirectionToLocal(direction) {
return Vector.transformDirectionFromMultipleOf90(direction, 360 - this.rotation); return Vector.mirrorDirection(Vector.transformDirectionFromMultipleOf90(direction, 360 - this.rotation), this.mirrored);
} }
/** /**
@ -166,49 +200,13 @@ export class StaticMapEntityComponent extends Component {
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
*/ */
shouldBeDrawn(parameters) { shouldBeDrawn(parameters) {
let x = 0; const rect = this.getTileSpaceBounds();
let y = 0;
let w = 0;
let h = 0;
switch (this.rotation) {
case 0: {
x = this.origin.x;
y = this.origin.y;
w = this.tileSize.x;
h = this.tileSize.y;
break;
}
case 90: {
x = this.origin.x - this.tileSize.y + 1;
y = this.origin.y;
w = this.tileSize.y;
h = this.tileSize.x;
break;
}
case 180: {
x = this.origin.x - this.tileSize.x + 1;
y = this.origin.y - this.tileSize.y + 1;
w = this.tileSize.x;
h = this.tileSize.y;
break;
}
case 270: {
x = this.origin.x;
y = this.origin.y - this.tileSize.x + 1;
w = this.tileSize.y;
h = this.tileSize.x;
break;
}
default:
assert(false, "Invalid rotation");
}
return parameters.visibleRect.containsRect4Params( return parameters.visibleRect.containsRect4Params(
x * globalConfig.tileSize, rect.x * globalConfig.tileSize,
y * globalConfig.tileSize, rect.y * globalConfig.tileSize,
w * globalConfig.tileSize, rect.w * globalConfig.tileSize,
h * globalConfig.tileSize rect.h * globalConfig.tileSize
); );
} }
@ -238,7 +236,7 @@ export class StaticMapEntityComponent extends Component {
worldY = overridePosition.y * globalConfig.tileSize; worldY = overridePosition.y * globalConfig.tileSize;
} }
if (this.rotation === 0) { if (this.rotation === 0 && this.mirrored == false) {
// Early out, is faster // Early out, is faster
sprite.drawCached( sprite.drawCached(
parameters, parameters,
@ -254,6 +252,9 @@ export class StaticMapEntityComponent extends Component {
parameters.context.translate(rotationCenterX, rotationCenterY); parameters.context.translate(rotationCenterX, rotationCenterY);
parameters.context.rotate(Math_radians(this.rotation)); parameters.context.rotate(Math_radians(this.rotation));
if (this.mirrored == true) {
parameters.context.scale(-1.0, 1.0);
}
sprite.drawCached( sprite.drawCached(
parameters, parameters,
@ -264,6 +265,9 @@ export class StaticMapEntityComponent extends Component {
false false
); );
if (this.mirrored == true) {
parameters.context.scale(-1.0, 1.0);
}
parameters.context.rotate(-Math_radians(this.rotation)); parameters.context.rotate(-Math_radians(this.rotation));
parameters.context.translate(-rotationCenterX, -rotationCenterY); parameters.context.translate(-rotationCenterX, -rotationCenterY);
} }

View File

@ -8,6 +8,7 @@ import { findNiceIntegerValue } from "../../../core/utils";
import { Math_pow } from "../../../core/builtins"; import { Math_pow } from "../../../core/builtins";
import { blueprintShape } from "../../upgrades"; import { blueprintShape } from "../../upgrades";
import { globalConfig } from "../../../core/config"; import { globalConfig } from "../../../core/config";
import { enumItemProcessorTypes } from "../../components/item_processor";
const logger = createLogger("blueprint"); const logger = createLogger("blueprint");
@ -130,6 +131,36 @@ export class Blueprint {
} }
} }
/**
* Mirrors the blueprint horizontally
*/
mirror() {
for (let i = 0; i < this.entities.length; ++i) {
const entity = this.entities[i];
const staticComp = entity.components.StaticMapEntity;
const beltComp = entity.components.Belt;
const itemProcComp = entity.components.ItemProcessor;
staticComp.rotation = Vector.mirrorAngle(staticComp.rotation)
staticComp.originalRotation = Vector.mirrorAngle(staticComp.originalRotation)
staticComp.origin = staticComp.origin.mirror();
if (beltComp) {
// It is a belt! Flipping the direction is enough.
beltComp.direction = Vector.mirrorDirection( beltComp.direction );
staticComp.blueprintSpriteKey = "sprites/blueprints/belt_" + beltComp.direction + ".png";
} else if ( itemProcComp && (
itemProcComp.type == enumItemProcessorTypes.rotater ||
itemProcComp.type == enumItemProcessorTypes.rotaterCCW
) ) {
// Don't flip the rotater
} else {
staticComp.mirrored = !staticComp.mirrored;
}
}
}
/** /**
* Checks if the blueprint can be placed at the given tile * Checks if the blueprint can be placed at the given tile
* @param {GameRoot} root * @param {GameRoot} root

View File

@ -37,6 +37,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this); keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.abortPlacement, this); keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.abortPlacement, this);
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this); keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this);
keyActionMapper.getBinding(KEYMAPPINGS.placement.mirrorWhilePlacing).add(this.mirrorBlueprint, this);
keyActionMapper.getBinding(KEYMAPPINGS.massSelect.pasteLastBlueprint).add(this.pasteBlueprint, this); keyActionMapper.getBinding(KEYMAPPINGS.massSelect.pasteLastBlueprint).add(this.pasteBlueprint, this);
this.root.camera.downPreHandler.add(this.onMouseDown, this); this.root.camera.downPreHandler.add(this.onMouseDown, this);
@ -139,6 +140,20 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
} }
} }
mirrorBlueprint() {
if (this.currentBlueprint.get()) {
if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
// Mirror vertically
this.currentBlueprint.get().rotateCw();
this.currentBlueprint.get().mirror();
this.currentBlueprint.get().rotateCcw();
} else {
// Mirror horizontally
this.currentBlueprint.get().mirror();
}
}
}
pasteBlueprint() { pasteBlueprint() {
if (this.lastBlueprintUsed !== null) { if (this.lastBlueprintUsed !== null) {
this.root.hud.signals.pasteBlueprintRequested.dispatch(); this.root.hud.signals.pasteBlueprintRequested.dispatch();

View File

@ -229,11 +229,13 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
const { const {
rotation, rotation,
rotationVariant, rotationVariant,
mirrored,
connectedEntities, connectedEntities,
} = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile( } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile(
this.root, this.root,
mouseTile, mouseTile,
this.currentBaseRotation, this.currentBaseRotation,
this.currentMirrored,
this.currentVariant.get() this.currentVariant.get()
); );
@ -272,6 +274,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
const staticComp = this.fakeEntity.components.StaticMapEntity; const staticComp = this.fakeEntity.components.StaticMapEntity;
staticComp.origin = mouseTile; staticComp.origin = mouseTile;
staticComp.rotation = rotation; staticComp.rotation = rotation;
staticComp.mirrored = mirrored;
staticComp.tileSize = metaBuilding.getDimensions(this.currentVariant.get()); staticComp.tileSize = metaBuilding.getDimensions(this.currentVariant.get());
metaBuilding.updateVariants(this.fakeEntity, rotationVariant, this.currentVariant.get()); metaBuilding.updateVariants(this.fakeEntity, rotationVariant, this.currentVariant.get());
@ -280,6 +283,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
origin: mouseTile, origin: mouseTile,
rotation, rotation,
rotationVariant, rotationVariant,
mirrored,
building: metaBuilding, building: metaBuilding,
variant: this.currentVariant.get(), variant: this.currentVariant.get(),
}); });

View File

@ -56,6 +56,12 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
*/ */
this.preferredBaseRotations = {}; this.preferredBaseRotations = {};
/**
* The current mirror state
* @type {boolean}
*/
this.currentMirrored = false;
/** /**
* Whether we are currently dragging * Whether we are currently dragging
* @type {boolean} * @type {boolean}
@ -103,6 +109,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
// KEYBINDINGS // KEYBINDINGS
const keyActionMapper = this.root.keyMapper; const keyActionMapper = this.root.keyMapper;
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this); keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this);
keyActionMapper.getBinding(KEYMAPPINGS.placement.mirrorWhilePlacing).add(this.tryMirror, this);
keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this); keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this);
keyActionMapper keyActionMapper
.getBinding(KEYMAPPINGS.placement.switchDirectionLockSide) .getBinding(KEYMAPPINGS.placement.switchDirectionLockSide)
@ -265,6 +272,25 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
staticComp.rotation = this.currentBaseRotation; staticComp.rotation = this.currentBaseRotation;
} }
} }
/**
* Tries to mirror the current building
*/
tryMirror() {
const selectedBuilding = this.currentMetaBuilding.get();
if (selectedBuilding) {
this.currentMirrored = !this.currentMirrored;
this.currentBaseRotation = Vector.mirrorAngle(this.currentBaseRotation);
const staticComp = this.fakeEntity.components.StaticMapEntity;
staticComp.mirrored = this.currentMirrored;
staticComp.rotation = this.currentBaseRotation;
if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
// Rotate twice to get a vertical mirroring
this.tryRotate();
this.tryRotate();
}
}
}
/** /**
* Tries to delete the building under the mouse * Tries to delete the building under the mouse
*/ */
@ -331,6 +357,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
this.currentMetaBuilding.set(extracted.metaBuilding); this.currentMetaBuilding.set(extracted.metaBuilding);
this.currentVariant.set(extracted.variant); this.currentVariant.set(extracted.variant);
this.currentBaseRotation = contents.components.StaticMapEntity.rotation; this.currentBaseRotation = contents.components.StaticMapEntity.rotation;
this.currentMirrored = contents.components.StaticMapEntity.mirrored;
// Make sure we selected something, and also make sure it's not a special entity // Make sure we selected something, and also make sure it's not a special entity
// if (contents && !contents.components.Unremovable) { // if (contents && !contents.components.Unremovable) {
@ -444,10 +471,11 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
} }
const metaBuilding = this.currentMetaBuilding.get(); const metaBuilding = this.currentMetaBuilding.get();
const { rotation, rotationVariant } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile( const { rotation, rotationVariant, mirrored } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile(
this.root, this.root,
tile, tile,
this.currentBaseRotation, this.currentBaseRotation,
this.currentMirrored,
this.currentVariant.get() this.currentVariant.get()
); );
@ -456,6 +484,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
rotation, rotation,
rotationVariant, rotationVariant,
originalRotation: this.currentBaseRotation, originalRotation: this.currentBaseRotation,
mirrored,
building: this.currentMetaBuilding.get(), building: this.currentMetaBuilding.get(),
variant: this.currentVariant.get(), variant: this.currentVariant.get(),
}); });

View File

@ -207,6 +207,13 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
condition: () => this.anyPlacementActive && !this.beltPlannerActive, condition: () => this.anyPlacementActive && !this.beltPlannerActive,
}, },
{
// Mirror
label: T.ingame.keybindingsOverlay.mirrorBuilding,
keys: [k.placement.mirrorWhilePlacing],
condition: () => this.anyPlacementActive && !this.beltPlannerActive,
},
{ {
// [BELT PLANNER] Flip Side // [BELT PLANNER] Flip Side
label: T.ingame.keybindingsOverlay.plannerSwitchSide, label: T.ingame.keybindingsOverlay.plannerSwitchSide,

View File

@ -61,6 +61,7 @@ export const KEYMAPPINGS = {
placement: { placement: {
pipette: { keyCode: key("Q") }, pipette: { keyCode: key("Q") },
rotateWhilePlacing: { keyCode: key("R") }, rotateWhilePlacing: { keyCode: key("R") },
mirrorWhilePlacing: { keyCode: key("N") },
rotateInverseModifier: { keyCode: 16 }, // SHIFT rotateInverseModifier: { keyCode: 16 }, // SHIFT
cycleBuildingVariants: { keyCode: key("T") }, cycleBuildingVariants: { keyCode: key("T") },
cycleBuildings: { keyCode: 9 }, // TAB cycleBuildings: { keyCode: 9 }, // TAB

View File

@ -56,11 +56,12 @@ export class GameLogic {
* @param {MetaBuilding} param0.building * @param {MetaBuilding} param0.building
* @returns {boolean} * @returns {boolean}
*/ */
isAreaFreeToBuild({ origin, rotation, rotationVariant, variant, building }) { isAreaFreeToBuild({ origin, rotation, mirrored, rotationVariant, variant, building }) {
const checker = new StaticMapEntityComponent({ const checker = new StaticMapEntityComponent({
origin, origin,
tileSize: building.getDimensions(variant), tileSize: building.getDimensions(variant),
rotation, rotation,
mirrored,
blueprintSpriteKey: "", blueprintSpriteKey: "",
}); });
@ -76,6 +77,7 @@ export class GameLogic {
origin, origin,
building, building,
rotation, rotation,
mirrored,
rotationVariant, rotationVariant,
}) })
) { ) {
@ -95,10 +97,11 @@ export class GameLogic {
* @param {Vector} param0.origin * @param {Vector} param0.origin
* @param {number} param0.rotation * @param {number} param0.rotation
* @param {number} param0.rotationVariant * @param {number} param0.rotationVariant
* @param {boolean} param0.mirrored
* @param {MetaBuilding} param0.building * @param {MetaBuilding} param0.building
* @returns {boolean} * @returns {boolean}
*/ */
checkCanReplaceBuilding({ original, origin, building, rotation, rotationVariant }) { checkCanReplaceBuilding({ original, origin, building, rotation, mirrored, rotationVariant }) {
if (!original.components.ReplaceableMapEntity) { if (!original.components.ReplaceableMapEntity) {
// Can not get replaced at all // Can not get replaced at all
return false; return false;
@ -128,7 +131,7 @@ export class GameLogic {
* @param {string} param0.variant * @param {string} param0.variant
* @param {MetaBuilding} param0.building * @param {MetaBuilding} param0.building
*/ */
checkCanPlaceBuilding({ origin, rotation, rotationVariant, variant, building }) { checkCanPlaceBuilding({ origin, rotation, mirrored, rotationVariant, variant, building }) {
if (!building.getIsUnlocked(this.root)) { if (!building.getIsUnlocked(this.root)) {
return false; return false;
} }
@ -136,6 +139,7 @@ export class GameLogic {
return this.isAreaFreeToBuild({ return this.isAreaFreeToBuild({
origin, origin,
rotation, rotation,
mirrored,
rotationVariant, rotationVariant,
variant, variant,
building, building,
@ -153,13 +157,14 @@ export class GameLogic {
* @param {MetaBuilding} param0.building * @param {MetaBuilding} param0.building
* @returns {Entity} * @returns {Entity}
*/ */
tryPlaceBuilding({ origin, rotation, rotationVariant, originalRotation, variant, building }) { tryPlaceBuilding({ origin, rotation, rotationVariant, originalRotation, mirrored, variant, building }) {
if (this.checkCanPlaceBuilding({ origin, rotation, rotationVariant, variant, building })) { if (this.checkCanPlaceBuilding({ origin, rotation, mirrored, rotationVariant, variant, building })) {
// Remove any removeable entities below // Remove any removeable entities below
const checker = new StaticMapEntityComponent({ const checker = new StaticMapEntityComponent({
origin, origin,
tileSize: building.getDimensions(variant), tileSize: building.getDimensions(variant),
rotation, rotation,
mirrored,
blueprintSpriteKey: "", blueprintSpriteKey: "",
}); });
@ -182,6 +187,7 @@ export class GameLogic {
origin, origin,
rotation, rotation,
rotationVariant, rotationVariant,
mirrored,
originalRotation, originalRotation,
variant, variant,
}); });

View File

@ -129,6 +129,15 @@ export class MetaBuilding {
return true; return true;
} }
/**
* Returns whether this building can be mirrored
* @param {string} variant
* @returns {boolean}
*/
isMirrorable(variant) {
return true;
}
/** /**
* Returns whether this building is unlocked for the given game * Returns whether this building is unlocked for the given game
* @param {GameRoot} root * @param {GameRoot} root
@ -151,15 +160,17 @@ export class MetaBuilding {
* @param {Vector} param0.origin Origin tile * @param {Vector} param0.origin Origin tile
* @param {number=} param0.rotation Rotation * @param {number=} param0.rotation Rotation
* @param {number} param0.originalRotation Original Rotation * @param {number} param0.originalRotation Original Rotation
* @param {boolean} param0.mirrored
* @param {number} param0.rotationVariant Rotation variant * @param {number} param0.rotationVariant Rotation variant
* @param {string} param0.variant * @param {string} param0.variant
*/ */
createAndPlaceEntity({ root, origin, rotation, originalRotation, rotationVariant, variant }) { createAndPlaceEntity({ root, origin, rotation, originalRotation, mirrored, rotationVariant, variant }) {
const entity = this.createEntity({ const entity = this.createEntity({
root, root,
origin, origin,
rotation, rotation,
originalRotation, originalRotation,
mirrored,
rotationVariant, rotationVariant,
variant, variant,
}); });
@ -175,10 +186,11 @@ export class MetaBuilding {
* @param {Vector} param0.origin Origin tile * @param {Vector} param0.origin Origin tile
* @param {number=} param0.rotation Rotation * @param {number=} param0.rotation Rotation
* @param {number} param0.originalRotation Original Rotation * @param {number} param0.originalRotation Original Rotation
* @param {boolean} param0.mirrored
* @param {number} param0.rotationVariant Rotation variant * @param {number} param0.rotationVariant Rotation variant
* @param {string} param0.variant * @param {string} param0.variant
*/ */
createEntity({ root, origin, rotation, originalRotation, rotationVariant, variant }) { createEntity({ root, origin, rotation, originalRotation, mirrored, rotationVariant, variant }) {
const entity = new Entity(root); const entity = new Entity(root);
const blueprintSprite = this.getBlueprintSprite(rotationVariant, variant); const blueprintSprite = this.getBlueprintSprite(rotationVariant, variant);
entity.addComponent( entity.addComponent(
@ -191,6 +203,7 @@ export class MetaBuilding {
origin: new Vector(origin.x, origin.y), origin: new Vector(origin.x, origin.y),
rotation, rotation,
originalRotation, originalRotation,
mirrored,
tileSize: this.getDimensions(variant).copy(), tileSize: this.getDimensions(variant).copy(),
silhouetteColor: this.getSilhouetteColor(), silhouetteColor: this.getSilhouetteColor(),
blueprintSpriteKey: blueprintSprite ? blueprintSprite.spriteName : "", blueprintSpriteKey: blueprintSprite ? blueprintSprite.spriteName : "",
@ -207,17 +220,18 @@ export class MetaBuilding {
* @param {Vector} tile * @param {Vector} tile
* @param {number} rotation * @param {number} rotation
* @param {string} variant * @param {string} variant
* @return {{ rotation: number, rotationVariant: number, connectedEntities?: Array<Entity> }} * @return {{ rotation: number, rotationVariant: number, mirrored: boolean, connectedEntities?: Array<Entity> }}
*/ */
computeOptimalDirectionAndRotationVariantAtTile(root, tile, rotation, variant) { computeOptimalDirectionAndRotationVariantAtTile(root, tile, rotation, mirrored, variant) {
if (!this.isRotateable(variant)) { if (!this.isRotateable(variant)) {
return { rotation = 0;
rotation: 0, }
rotationVariant: 0, if (!this.isMirrorable(variant)) {
}; mirrored = false;
} }
return { return {
rotation, rotation,
mirrored,
rotationVariant: 0, rotationVariant: 0,
}; };
} }

View File

@ -100,6 +100,7 @@ export class BeltSystem extends GameSystemWithFilter {
this.root, this.root,
new Vector(x, y), new Vector(x, y),
targetStaticComp.originalRotation, targetStaticComp.originalRotation,
false,
defaultBuildingVariant defaultBuildingVariant
); );
targetStaticComp.rotation = rotation; targetStaticComp.rotation = rotation;

View File

@ -311,11 +311,9 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
continue; continue;
} }
const realPosition = slot.pos.rotateFastMultipleOf90(staticComp.rotation); const realPosition = staticComp.applyRotationToVector(slot.pos);
const realDirection = Vector.transformDirectionFromMultipleOf90( const realDirection = staticComp.localDirectionToWorld(slot.direction);
slot.direction,
staticComp.rotation
);
const realDirectionVector = enumDirectionToVector[realDirection]; const realDirectionVector = enumDirectionToVector[realDirection];
const tileX = const tileX =

View File

@ -276,6 +276,7 @@ ingame:
selectBuildings: Select area selectBuildings: Select area
stopPlacement: Stop placement stopPlacement: Stop placement
rotateBuilding: Rotate building rotateBuilding: Rotate building
mirrorBuilding: Mirror building
placeMultiple: Place multiple placeMultiple: Place multiple
reverseOrientation: Reverse orientation reverseOrientation: Reverse orientation
disableAutoOrientation: Disable auto orientation disableAutoOrientation: Disable auto orientation
@ -802,6 +803,7 @@ keybindings:
pipette: Pipette pipette: Pipette
rotateWhilePlacing: Rotate rotateWhilePlacing: Rotate
mirrorWhilePlacing: Mirror
rotateInverseModifier: >- rotateInverseModifier: >-
Modifier: Rotate CCW instead Modifier: Rotate CCW instead
cycleBuildingVariants: Cycle Variants cycleBuildingVariants: Cycle Variants