mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Huge refactoring of the whole placement logic
This commit is contained in:
parent
ca8745b1c0
commit
d09bd0bcd7
@ -90,17 +90,7 @@ export class Blueprint {
|
|||||||
const rect = staticComp.getTileSpaceBounds();
|
const rect = staticComp.getTileSpaceBounds();
|
||||||
rect.moveBy(tile.x, tile.y);
|
rect.moveBy(tile.x, tile.y);
|
||||||
|
|
||||||
let placeable = true;
|
if (!parameters.root.logic.checkCanPlaceEntity(entity, tile)) {
|
||||||
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, entity.layer)) {
|
|
||||||
placeable = false;
|
|
||||||
break placementCheck;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!placeable) {
|
|
||||||
parameters.context.globalAlpha = 0.3;
|
parameters.context.globalAlpha = 0.3;
|
||||||
} else {
|
} else {
|
||||||
parameters.context.globalAlpha = 1;
|
parameters.context.globalAlpha = 1;
|
||||||
@ -150,21 +140,8 @@ export class Blueprint {
|
|||||||
let anyPlaceable = false;
|
let anyPlaceable = false;
|
||||||
|
|
||||||
for (let i = 0; i < this.entities.length; ++i) {
|
for (let i = 0; i < this.entities.length; ++i) {
|
||||||
let placeable = true;
|
|
||||||
const entity = this.entities[i];
|
const entity = this.entities[i];
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
if (root.logic.checkCanPlaceEntity(entity, tile)) {
|
||||||
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, entity.layer)) {
|
|
||||||
placeable = false;
|
|
||||||
break placementCheck;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (placeable) {
|
|
||||||
anyPlaceable = true;
|
anyPlaceable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,49 +165,18 @@ export class Blueprint {
|
|||||||
return root.logic.performBulkOperation(() => {
|
return root.logic.performBulkOperation(() => {
|
||||||
let anyPlaced = false;
|
let anyPlaced = false;
|
||||||
for (let i = 0; i < this.entities.length; ++i) {
|
for (let i = 0; i < this.entities.length; ++i) {
|
||||||
let placeable = true;
|
|
||||||
const entity = this.entities[i];
|
const entity = this.entities[i];
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
if (!root.logic.checkCanPlaceEntity(entity, tile)) {
|
||||||
const rect = staticComp.getTileSpaceBounds();
|
continue;
|
||||||
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.getLayerContentXY(x, y, entity.layer);
|
|
||||||
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.getLayerContentXY(x, y, entity.layer);
|
|
||||||
if (contents) {
|
|
||||||
assert(
|
|
||||||
contents.components.ReplaceableMapEntity,
|
|
||||||
"Can not delete entity for blueprint"
|
|
||||||
);
|
|
||||||
if (!root.logic.tryDeleteBuilding(contents)) {
|
|
||||||
assertAlways(
|
|
||||||
false,
|
|
||||||
"Building has replaceable component but is also unremovable in blueprint"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const clone = entity.duplicateWithoutContents();
|
const clone = entity.duplicateWithoutContents();
|
||||||
clone.components.StaticMapEntity.origin.addInplace(tile);
|
clone.components.StaticMapEntity.origin.addInplace(tile);
|
||||||
|
root.logic.freeEntityAreaBeforeBuild(clone);
|
||||||
root.map.placeStaticEntity(clone);
|
root.map.placeStaticEntity(clone);
|
||||||
|
|
||||||
root.entityMgr.registerEntity(clone);
|
root.entityMgr.registerEntity(clone);
|
||||||
anyPlaced = true;
|
anyPlaced = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return anyPlaced;
|
return anyPlaced;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,8 @@ export class GameCore {
|
|||||||
this.root.gameIsFresh = true;
|
this.root.gameIsFresh = true;
|
||||||
this.root.map.seed = randomInt(0, 100000);
|
this.root.map.seed = randomInt(0, 100000);
|
||||||
|
|
||||||
gMetaBuildingRegistry.findByClass(MetaHubBuilding).createAndPlaceEntity({
|
// Place the hub
|
||||||
|
const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
|
||||||
root: this.root,
|
root: this.root,
|
||||||
origin: new Vector(-2, -2),
|
origin: new Vector(-2, -2),
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
@ -138,6 +139,8 @@ export class GameCore {
|
|||||||
rotationVariant: 0,
|
rotationVariant: 0,
|
||||||
variant: defaultBuildingVariant,
|
variant: defaultBuildingVariant,
|
||||||
});
|
});
|
||||||
|
this.root.map.placeStaticEntity(hub);
|
||||||
|
this.root.entityMgr.registerEntity(hub);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -276,14 +276,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||||||
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());
|
||||||
|
|
||||||
// Check if we could place the buildnig
|
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
|
||||||
const canBuild = this.root.logic.checkCanPlaceBuilding({
|
|
||||||
origin: mouseTile,
|
|
||||||
rotation,
|
|
||||||
rotationVariant,
|
|
||||||
building: metaBuilding,
|
|
||||||
variant: this.currentVariant.get(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fade in / out
|
// Fade in / out
|
||||||
parameters.context.lineWidth = 1;
|
parameters.context.lineWidth = 1;
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { GameRoot, enumLayer, arrayLayers } from "./root";
|
|
||||||
import { Entity } from "./entity";
|
|
||||||
import { Vector, enumDirectionToVector, enumDirection } from "../core/vector";
|
|
||||||
import { MetaBuilding } from "./meta_building";
|
|
||||||
import { StaticMapEntityComponent } from "./components/static_map_entity";
|
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
import { MetaBeltBaseBuilding, arrayBeltVariantToRotation } from "./buildings/belt_base";
|
|
||||||
import { SOUNDS } from "../platform/sound";
|
|
||||||
import { round2Digits } from "../core/utils";
|
import { round2Digits } from "../core/utils";
|
||||||
|
import { enumDirection, enumDirectionToVector, Vector } from "../core/vector";
|
||||||
|
import { Entity } from "./entity";
|
||||||
|
import { MetaBuilding } from "./meta_building";
|
||||||
|
import { enumLayer, GameRoot } from "./root";
|
||||||
|
import { STOP_PROPAGATION } from "../core/signal";
|
||||||
|
|
||||||
const logger = createLogger("ingame/logic");
|
const logger = createLogger("ingame/logic");
|
||||||
|
|
||||||
@ -47,125 +45,41 @@ export class GameLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} param0
|
* Checks if the given entity can be placed
|
||||||
* @param {Vector} param0.origin
|
* @param {Entity} entity
|
||||||
* @param {number} param0.rotation
|
* @param {Vector=} offset Optional, move the entity by the given offset first
|
||||||
* @param {number} param0.rotationVariant
|
* @returns {boolean} true if the entity could be placed there
|
||||||
* @param {string} param0.variant
|
|
||||||
* @param {MetaBuilding} param0.building
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
isAreaFreeToBuild({ origin, rotation, rotationVariant, variant, building }) {
|
checkCanPlaceEntity(entity, offset = null) {
|
||||||
const checker = new StaticMapEntityComponent({
|
// Compute area of the building
|
||||||
origin,
|
const rect = entity.components.StaticMapEntity.getTileSpaceBounds();
|
||||||
tileSize: building.getDimensions(variant),
|
if (offset) {
|
||||||
rotation,
|
rect.x += offset.x;
|
||||||
blueprintSpriteKey: "",
|
rect.y += offset.y;
|
||||||
});
|
}
|
||||||
|
|
||||||
const layer = building.getLayer();
|
|
||||||
const rect = checker.getTileSpaceBounds();
|
|
||||||
|
|
||||||
|
// Check the whole area of the building
|
||||||
for (let x = rect.x; x < rect.x + rect.w; ++x) {
|
for (let x = rect.x; x < rect.x + rect.w; ++x) {
|
||||||
for (let y = rect.y; y < rect.y + rect.h; ++y) {
|
for (let y = rect.y; y < rect.y + rect.h; ++y) {
|
||||||
const contents = this.root.map.getLayerContentXY(x, y, layer);
|
// Check if there is any direct collision
|
||||||
if (contents) {
|
const otherEntity = this.root.map.getLayerContentXY(x, y, entity.layer);
|
||||||
if (
|
if (otherEntity && !otherEntity.components.ReplaceableMapEntity) {
|
||||||
!this.checkCanReplaceBuilding({
|
// This one is a direct blocker
|
||||||
original: contents,
|
|
||||||
building,
|
|
||||||
rotation,
|
|
||||||
rotationVariant,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
// Content already has same rotation
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any pins which are in the way
|
|
||||||
if (layer === enumLayer.wires) {
|
|
||||||
const regularContents = this.root.map.getLayerContentXY(x, y, enumLayer.regular);
|
|
||||||
if (regularContents) {
|
|
||||||
const staticComp = regularContents.components.StaticMapEntity;
|
|
||||||
const pinComponent = regularContents.components.WiredPins;
|
|
||||||
if (pinComponent) {
|
|
||||||
const pins = pinComponent.slots;
|
|
||||||
for (let i = 0; i < pins.length; ++i) {
|
|
||||||
const pos = staticComp.localTileToWorld(pins[i].pos);
|
|
||||||
// Occupied by a pin
|
|
||||||
if (pos.x === x && pos.y === y) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Perform additional placement checks
|
||||||
* Checks if the given building can be replaced by another
|
if (this.root.signals.prePlacementCheck.dispatch(entity, offset) === STOP_PROPAGATION) {
|
||||||
* @param {object} param0
|
|
||||||
* @param {Entity} param0.original
|
|
||||||
* @param {number} param0.rotation
|
|
||||||
* @param {number} param0.rotationVariant
|
|
||||||
* @param {MetaBuilding} param0.building
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
checkCanReplaceBuilding({ original, building, rotation, rotationVariant }) {
|
|
||||||
if (!original.components.ReplaceableMapEntity) {
|
|
||||||
// Can not get replaced at all
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (building.getLayer() !== original.layer) {
|
|
||||||
// Layer mismatch
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const staticComp = original.components.StaticMapEntity;
|
|
||||||
assert(staticComp, "Building is not static");
|
|
||||||
const beltComp = original.components.Belt;
|
|
||||||
if (beltComp && building instanceof MetaBeltBaseBuilding) {
|
|
||||||
// Its a belt, check if it differs in either rotation or rotation variant
|
|
||||||
if (staticComp.rotation !== rotation) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (beltComp.direction !== arrayBeltVariantToRotation[rotationVariant]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} param0
|
* Attempts to place the given building
|
||||||
* @param {Vector} param0.origin
|
|
||||||
* @param {number} param0.rotation
|
|
||||||
* @param {number} param0.rotationVariant
|
|
||||||
* @param {string} param0.variant
|
|
||||||
* @param {MetaBuilding} param0.building
|
|
||||||
*/
|
|
||||||
checkCanPlaceBuilding({ origin, rotation, rotationVariant, variant, building }) {
|
|
||||||
if (!building.getIsUnlocked(this.root)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.isAreaFreeToBuild({
|
|
||||||
origin,
|
|
||||||
rotation,
|
|
||||||
rotationVariant,
|
|
||||||
variant,
|
|
||||||
building,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {object} param0
|
* @param {object} param0
|
||||||
* @param {Vector} param0.origin
|
* @param {Vector} param0.origin
|
||||||
* @param {number} param0.rotation
|
* @param {number} param0.rotation
|
||||||
@ -176,44 +90,51 @@ export class GameLogic {
|
|||||||
* @returns {Entity}
|
* @returns {Entity}
|
||||||
*/
|
*/
|
||||||
tryPlaceBuilding({ origin, rotation, rotationVariant, originalRotation, variant, building }) {
|
tryPlaceBuilding({ origin, rotation, rotationVariant, originalRotation, variant, building }) {
|
||||||
if (this.checkCanPlaceBuilding({ origin, rotation, rotationVariant, variant, building })) {
|
const entity = building.createEntity({
|
||||||
// Remove any removeable entities below
|
|
||||||
const checker = new StaticMapEntityComponent({
|
|
||||||
origin,
|
|
||||||
tileSize: building.getDimensions(variant),
|
|
||||||
rotation,
|
|
||||||
blueprintSpriteKey: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const layer = building.getLayer();
|
|
||||||
|
|
||||||
const rect = checker.getTileSpaceBounds();
|
|
||||||
for (let x = rect.x; x < rect.x + rect.w; ++x) {
|
|
||||||
for (let y = rect.y; y < rect.y + rect.h; ++y) {
|
|
||||||
const contents = this.root.map.getLayerContentXY(x, y, layer);
|
|
||||||
if (contents) {
|
|
||||||
if (!this.tryDeleteBuilding(contents)) {
|
|
||||||
logger.error("Building has replaceable component but is also unremovable");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = building.createAndPlaceEntity({
|
|
||||||
root: this.root,
|
root: this.root,
|
||||||
origin,
|
origin,
|
||||||
rotation,
|
rotation,
|
||||||
rotationVariant,
|
|
||||||
originalRotation,
|
originalRotation,
|
||||||
|
rotationVariant,
|
||||||
variant,
|
variant,
|
||||||
});
|
});
|
||||||
|
if (this.checkCanPlaceEntity(entity)) {
|
||||||
|
this.freeEntityAreaBeforeBuild(entity);
|
||||||
|
this.root.map.placeStaticEntity(entity);
|
||||||
|
this.root.entityMgr.registerEntity(entity);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all entities with a RemovableMapEntityComponent which need to get
|
||||||
|
* removed before placing this entity
|
||||||
|
* @param {Entity} entity
|
||||||
|
*/
|
||||||
|
freeEntityAreaBeforeBuild(entity) {
|
||||||
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
|
const rect = staticComp.getTileSpaceBounds();
|
||||||
|
// Remove any removeable colliding entities on the same layer
|
||||||
|
for (let x = rect.x; x < rect.x + rect.w; ++x) {
|
||||||
|
for (let y = rect.y; y < rect.y + rect.h; ++y) {
|
||||||
|
const contents = this.root.map.getLayerContentXY(x, y, entity.layer);
|
||||||
|
if (contents) {
|
||||||
|
assertAlways(
|
||||||
|
contents.components.ReplaceableMapEntity,
|
||||||
|
"Tried to replace non-repleaceable entity"
|
||||||
|
);
|
||||||
|
if (!this.tryDeleteBuilding(contents)) {
|
||||||
|
assertAlways(false, "Tried to replace non-repleaceable entity #2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform other callbacks
|
||||||
|
this.root.signals.freeEntityAreaBeforeBuild.dispatch(entity);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a bulk operation, not updating caches in the meantime
|
* Performs a bulk operation, not updating caches in the meantime
|
||||||
* @param {function} operation
|
* @param {function} operation
|
||||||
@ -266,6 +187,7 @@ export class GameLogic {
|
|||||||
/** @type {AcceptorsAffectingTile} */
|
/** @type {AcceptorsAffectingTile} */
|
||||||
let acceptors = [];
|
let acceptors = [];
|
||||||
|
|
||||||
|
// Well .. please ignore this code! :D
|
||||||
for (let dx = -1; dx <= 1; ++dx) {
|
for (let dx = -1; dx <= 1; ++dx) {
|
||||||
for (let dy = -1; dy <= 1; ++dy) {
|
for (let dy = -1; dy <= 1; ++dy) {
|
||||||
if (Math.abs(dx) + Math.abs(dy) !== 1) {
|
if (Math.abs(dx) + Math.abs(dy) !== 1) {
|
||||||
|
@ -144,30 +144,6 @@ export class MetaBuilding {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the entity at the given location
|
|
||||||
* @param {object} param0
|
|
||||||
* @param {GameRoot} param0.root
|
|
||||||
* @param {Vector} param0.origin Origin tile
|
|
||||||
* @param {number=} param0.rotation Rotation
|
|
||||||
* @param {number} param0.originalRotation Original Rotation
|
|
||||||
* @param {number} param0.rotationVariant Rotation variant
|
|
||||||
* @param {string} param0.variant
|
|
||||||
*/
|
|
||||||
createAndPlaceEntity({ root, origin, rotation, originalRotation, rotationVariant, variant }) {
|
|
||||||
const entity = this.createEntity({
|
|
||||||
root,
|
|
||||||
origin,
|
|
||||||
rotation,
|
|
||||||
originalRotation,
|
|
||||||
rotationVariant,
|
|
||||||
variant,
|
|
||||||
});
|
|
||||||
root.map.placeStaticEntity(entity);
|
|
||||||
root.entityMgr.registerEntity(entity);
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the entity without placing it
|
* Creates the entity without placing it
|
||||||
* @param {object} param0
|
* @param {object} param0
|
||||||
|
@ -27,6 +27,7 @@ import { ShapeDefinition } from "./shape_definition";
|
|||||||
import { BaseItem } from "./base_item";
|
import { BaseItem } from "./base_item";
|
||||||
import { DynamicTickrate } from "./dynamic_tickrate";
|
import { DynamicTickrate } from "./dynamic_tickrate";
|
||||||
import { KeyActionMapper } from "./key_action_mapper";
|
import { KeyActionMapper } from "./key_action_mapper";
|
||||||
|
import { Vector } from "../core/vector";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
const logger = createLogger("game/root");
|
const logger = createLogger("game/root");
|
||||||
@ -169,6 +170,14 @@ export class GameRoot {
|
|||||||
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||||
|
|
||||||
editModeChanged: /** @type {TypedSignal<[enumLayer]>} */ (new Signal()),
|
editModeChanged: /** @type {TypedSignal<[enumLayer]>} */ (new Signal()),
|
||||||
|
|
||||||
|
// Called to check if an entity can be placed, second parameter is an additional offset.
|
||||||
|
// Use to introduce additional placement checks
|
||||||
|
prePlacementCheck: /** @type {TypedSignal<[Entity, Vector]>} */ (new Signal()),
|
||||||
|
|
||||||
|
// Called before actually placing an entity, use to perform additional logic
|
||||||
|
// for freeing space before actually placing.
|
||||||
|
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// RNG's
|
// RNG's
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
import { globalConfig } from "../../core/config";
|
||||||
import { DrawParameters } from "../../core/draw_parameters";
|
import { DrawParameters } from "../../core/draw_parameters";
|
||||||
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
|
import { Loader } from "../../core/loader";
|
||||||
|
import { Vector } from "../../core/vector";
|
||||||
|
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||||
import { MapChunkView } from "../map_chunk_view";
|
import { enumLayer } from "../root";
|
||||||
import { Loader } from "../../core/loader";
|
|
||||||
|
|
||||||
export class WiredPinsSystem extends GameSystemWithFilter {
|
export class WiredPinsSystem extends GameSystemWithFilter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
@ -20,6 +21,127 @@ export class WiredPinsSystem extends GameSystemWithFilter {
|
|||||||
"sprites/wires/pin_negative_accept.png"
|
"sprites/wires/pin_negative_accept.png"
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.root.signals.prePlacementCheck.add(this.prePlacementCheck, this);
|
||||||
|
this.root.signals.freeEntityAreaBeforeBuild.add(this.freeEntityAreaBeforeBuild, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs pre-placement checks
|
||||||
|
* @param {Entity} entity
|
||||||
|
* @param {Vector} offset
|
||||||
|
*/
|
||||||
|
prePlacementCheck(entity, offset) {
|
||||||
|
// Compute area of the building
|
||||||
|
const rect = entity.components.StaticMapEntity.getTileSpaceBounds();
|
||||||
|
if (offset) {
|
||||||
|
rect.x += offset.x;
|
||||||
|
rect.y += offset.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this entity is placed on the wires layer, make sure we don't
|
||||||
|
// place it above a pin
|
||||||
|
if (entity.layer === enumLayer.wires) {
|
||||||
|
for (let x = rect.x; x < rect.x + rect.w; ++x) {
|
||||||
|
for (let y = rect.y; y < rect.y + rect.h; ++y) {
|
||||||
|
// Find which entities are in same tiles of both layers
|
||||||
|
const entities = this.root.map.getLayersContentsMultipleXY(x, y);
|
||||||
|
for (let i = 0; i < entities.length; ++i) {
|
||||||
|
const otherEntity = entities[i];
|
||||||
|
|
||||||
|
// Check if entity has a wired component
|
||||||
|
const pinComponent = otherEntity.components.WiredPins;
|
||||||
|
const staticComp = otherEntity.components.StaticMapEntity;
|
||||||
|
if (!pinComponent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherEntity.components.ReplaceableMapEntity) {
|
||||||
|
// Don't mind here, even if there would be a collision we
|
||||||
|
// could replace it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go over all pins and check if they are blocking
|
||||||
|
const pins = pinComponent.slots;
|
||||||
|
for (let pinSlot = 0; pinSlot < pins.length; ++pinSlot) {
|
||||||
|
const pos = staticComp.localTileToWorld(pins[pinSlot].pos);
|
||||||
|
// Occupied by a pin
|
||||||
|
if (pos.x === x && pos.y === y) {
|
||||||
|
return STOP_PROPAGATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for collisions on the wires layer
|
||||||
|
if (this.checkEntityPinsCollide(entity, offset)) {
|
||||||
|
return STOP_PROPAGATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the pins of the given entity collide on the wires layer
|
||||||
|
* @param {Entity} entity
|
||||||
|
* @param {Vector=} offset Optional, move the entity by the given offset first
|
||||||
|
* @returns {boolean} True if the pins collide
|
||||||
|
*/
|
||||||
|
checkEntityPinsCollide(entity, offset) {
|
||||||
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
if (!pinsComp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go over all slots
|
||||||
|
for (let slotIndex = 0; slotIndex < pinsComp.slots.length; ++slotIndex) {
|
||||||
|
const slot = pinsComp.slots[slotIndex];
|
||||||
|
|
||||||
|
// Figure out which tile this slot is on
|
||||||
|
const worldPos = entity.components.StaticMapEntity.localTileToWorld(slot.pos);
|
||||||
|
if (offset) {
|
||||||
|
worldPos.x += offset.x;
|
||||||
|
worldPos.y += offset.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is any entity on that tile (Wired pins are always on the wires layer)
|
||||||
|
const collidingEntity = this.root.map.getLayerContentXY(worldPos.x, worldPos.y, enumLayer.wires);
|
||||||
|
|
||||||
|
// If there's an entity, and it can't get removed -> That's a collision
|
||||||
|
if (collidingEntity && !collidingEntity.components.ReplaceableMapEntity) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to free space for the given entity
|
||||||
|
* @param {Entity} entity
|
||||||
|
*/
|
||||||
|
freeEntityAreaBeforeBuild(entity) {
|
||||||
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
if (!pinsComp) {
|
||||||
|
// Entity has no pins
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any stuff which collides with the pins
|
||||||
|
for (let i = 0; i < pinsComp.slots.length; ++i) {
|
||||||
|
const slot = pinsComp.slots[i];
|
||||||
|
const worldPos = entity.components.StaticMapEntity.localTileToWorld(slot.pos);
|
||||||
|
const collidingEntity = this.root.map.getLayerContentXY(worldPos.x, worldPos.y, enumLayer.wires);
|
||||||
|
if (collidingEntity) {
|
||||||
|
assertAlways(
|
||||||
|
collidingEntity.components.ReplaceableMapEntity,
|
||||||
|
"Tried to replace non-repleaceable entity for pins"
|
||||||
|
);
|
||||||
|
if (!this.root.logic.tryDeleteBuilding(collidingEntity)) {
|
||||||
|
assertAlways(false, "Tried to replace non-repleaceable entity for pins #2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
Loading…
Reference in New Issue
Block a user