mirror of
				https://github.com/tobspr/shapez.io.git
				synced 2025-06-13 13:04:03 +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();
 | 
			
		||||
            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,125 +45,41 @@ 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
 | 
			
		||||
        // Perform additional placement checks
 | 
			
		||||
        if (this.root.signals.prePlacementCheck.dispatch(entity, offset) === STOP_PROPAGATION) {
 | 
			
		||||
            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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {object} param0
 | 
			
		||||
     * @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,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * Attempts to place the given building
 | 
			
		||||
     * @param {object} param0
 | 
			
		||||
     * @param {Vector} param0.origin
 | 
			
		||||
     * @param {number} param0.rotation
 | 
			
		||||
@ -176,44 +90,51 @@ export class GameLogic {
 | 
			
		||||
     * @returns {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;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const entity = building.createAndPlaceEntity({
 | 
			
		||||
                root: this.root,
 | 
			
		||||
                origin,
 | 
			
		||||
                rotation,
 | 
			
		||||
                rotationVariant,
 | 
			
		||||
                originalRotation,
 | 
			
		||||
                variant,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        const entity = building.createEntity({
 | 
			
		||||
            root: this.root,
 | 
			
		||||
            origin,
 | 
			
		||||
            rotation,
 | 
			
		||||
            originalRotation,
 | 
			
		||||
            rotationVariant,
 | 
			
		||||
            variant,
 | 
			
		||||
        });
 | 
			
		||||
        if (this.checkCanPlaceEntity(entity)) {
 | 
			
		||||
            this.freeEntityAreaBeforeBuild(entity);
 | 
			
		||||
            this.root.map.placeStaticEntity(entity);
 | 
			
		||||
            this.root.entityMgr.registerEntity(entity);
 | 
			
		||||
            return entity;
 | 
			
		||||
        }
 | 
			
		||||
        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
 | 
			
		||||
     * @param {function} operation
 | 
			
		||||
@ -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…
	
		Reference in New Issue
	
	Block a user