Huge refactoring of the whole placement logic

pull/403/head
tobspr 4 years ago
parent ca8745b1c0
commit d09bd0bcd7

@ -90,17 +90,7 @@ export class Blueprint {
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, entity.layer)) {
placeable = false;
break placementCheck;
}
}
}
if (!placeable) {
if (!parameters.root.logic.checkCanPlaceEntity(entity, tile)) {
parameters.context.globalAlpha = 0.3;
} else {
parameters.context.globalAlpha = 1;
@ -150,21 +140,8 @@ export class Blueprint {
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, entity.layer)) {
placeable = false;
break placementCheck;
}
}
}
if (placeable) {
if (root.logic.checkCanPlaceEntity(entity, tile)) {
anyPlaceable = true;
}
}
@ -188,48 +165,17 @@ export class Blueprint {
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.getLayerContentXY(x, y, entity.layer);
if (contents && !contents.components.ReplaceableMapEntity) {
placeable = false;
break placementCheck;
}
}
if (!root.logic.checkCanPlaceEntity(entity, tile)) {
continue;
}
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();
clone.components.StaticMapEntity.origin.addInplace(tile);
root.map.placeStaticEntity(clone);
root.entityMgr.registerEntity(clone);
anyPlaced = true;
}
const clone = entity.duplicateWithoutContents();
clone.components.StaticMapEntity.origin.addInplace(tile);
root.logic.freeEntityAreaBeforeBuild(clone);
root.map.placeStaticEntity(clone);
root.entityMgr.registerEntity(clone);
anyPlaced = true;
}
return anyPlaced;
});

@ -130,7 +130,8 @@ export class GameCore {
this.root.gameIsFresh = true;
this.root.map.seed = randomInt(0, 100000);
gMetaBuildingRegistry.findByClass(MetaHubBuilding).createAndPlaceEntity({
// Place the hub
const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
root: this.root,
origin: new Vector(-2, -2),
rotation: 0,
@ -138,6 +139,8 @@ export class GameCore {
rotationVariant: 0,
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());
metaBuilding.updateVariants(this.fakeEntity, rotationVariant, this.currentVariant.get());
// Check if we could place the buildnig
const canBuild = this.root.logic.checkCanPlaceBuilding({
origin: mouseTile,
rotation,
rotationVariant,
building: metaBuilding,
variant: this.currentVariant.get(),
});
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
// Fade in / out
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 { MetaBeltBaseBuilding, arrayBeltVariantToRotation } from "./buildings/belt_base";
import { SOUNDS } from "../platform/sound";
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");
@ -47,171 +45,94 @@ export class GameLogic {
}
/**
* @param {object} param0
* @param {Vector} param0.origin
* @param {number} param0.rotation
* @param {number} param0.rotationVariant
* @param {string} param0.variant
* @param {MetaBuilding} param0.building
* @returns {boolean}
* Checks if the given entity can be placed
* @param {Entity} entity
* @param {Vector=} offset Optional, move the entity by the given offset first
* @returns {boolean} true if the entity could be placed there
*/
isAreaFreeToBuild({ origin, rotation, rotationVariant, variant, building }) {
const checker = new StaticMapEntityComponent({
origin,
tileSize: building.getDimensions(variant),
rotation,
blueprintSpriteKey: "",
});
const layer = building.getLayer();
const rect = checker.getTileSpaceBounds();
checkCanPlaceEntity(entity, offset = null) {
// Compute area of the building
const rect = entity.components.StaticMapEntity.getTileSpaceBounds();
if (offset) {
rect.x += offset.x;
rect.y += offset.y;
}
// Check the whole area of the building
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.checkCanReplaceBuilding({
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;
}
}
}
}
// Check if there is any direct collision
const otherEntity = this.root.map.getLayerContentXY(x, y, entity.layer);
if (otherEntity && !otherEntity.components.ReplaceableMapEntity) {
// This one is a direct blocker
return false;
}
}
}
return true;
}
/**
* Checks if the given building can be replaced by another
* @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;
}
if (building.getLayer() !== original.layer) {
// Layer mismatch
// Perform additional placement checks
if (this.root.signals.prePlacementCheck.dispatch(entity, offset) === STOP_PROPAGATION) {
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;
}
/**
* Attempts to place the given building
* @param {object} param0
* @param {Vector} param0.origin
* @param {number} param0.rotation
* @param {number} param0.originalRotation
* @param {number} param0.rotationVariant
* @param {string} param0.variant
* @param {MetaBuilding} param0.building
* @returns {Entity}
*/
checkCanPlaceBuilding({ origin, rotation, rotationVariant, variant, building }) {
if (!building.getIsUnlocked(this.root)) {
return false;
}
return this.isAreaFreeToBuild({
tryPlaceBuilding({ origin, rotation, rotationVariant, originalRotation, variant, building }) {
const entity = building.createEntity({
root: this.root,
origin,
rotation,
originalRotation,
rotationVariant,
variant,
building,
});
if (this.checkCanPlaceEntity(entity)) {
this.freeEntityAreaBeforeBuild(entity);
this.root.map.placeStaticEntity(entity);
this.root.entityMgr.registerEntity(entity);
return entity;
}
return null;
}
/**
*
* @param {object} param0
* @param {Vector} param0.origin
* @param {number} param0.rotation
* @param {number} param0.originalRotation
* @param {number} param0.rotationVariant
* @param {string} param0.variant
* @param {MetaBuilding} param0.building
* @returns {Entity}
* Removes all entities with a RemovableMapEntityComponent which need to get
* removed before placing this entity
* @param {Entity} entity
*/
tryPlaceBuilding({ origin, rotation, rotationVariant, originalRotation, variant, building }) {
if (this.checkCanPlaceBuilding({ origin, rotation, rotationVariant, variant, building })) {
// 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;
}
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");
}
}
}
const entity = building.createAndPlaceEntity({
root: this.root,
origin,
rotation,
rotationVariant,
originalRotation,
variant,
});
return entity;
}
return null;
// Perform other callbacks
this.root.signals.freeEntityAreaBeforeBuild.dispatch(entity);
}
/**
@ -266,6 +187,7 @@ export class GameLogic {
/** @type {AcceptorsAffectingTile} */
let acceptors = [];
// Well .. please ignore this code! :D
for (let dx = -1; dx <= 1; ++dx) {
for (let dy = -1; dy <= 1; ++dy) {
if (Math.abs(dx) + Math.abs(dy) !== 1) {

@ -144,30 +144,6 @@ export class MetaBuilding {
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
* @param {object} param0

@ -27,6 +27,7 @@ import { ShapeDefinition } from "./shape_definition";
import { BaseItem } from "./base_item";
import { DynamicTickrate } from "./dynamic_tickrate";
import { KeyActionMapper } from "./key_action_mapper";
import { Vector } from "../core/vector";
/* typehints:end */
const logger = createLogger("game/root");
@ -169,6 +170,14 @@ export class GameRoot {
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (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

@ -1,10 +1,11 @@
import { globalConfig } from "../../core/config";
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 { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunkView } from "../map_chunk_view";
import { Loader } from "../../core/loader";
import { enumLayer } from "../root";
export class WiredPinsSystem extends GameSystemWithFilter {
constructor(root) {
@ -20,6 +21,127 @@ export class WiredPinsSystem extends GameSystemWithFilter {
"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() {

Loading…
Cancel
Save