mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Improve placement of belts
This commit is contained in:
@@ -175,13 +175,14 @@ export class Rectangle {
|
||||
return new Rectangle(this.x * factor, this.y * factor, this.w * factor, this.h * factor);
|
||||
}
|
||||
|
||||
// Increases the rectangle in all directions
|
||||
expandInAllDirections(amount) {
|
||||
this.x -= amount;
|
||||
this.y -= amount;
|
||||
this.w += 2 * amount;
|
||||
this.h += 2 * amount;
|
||||
return this;
|
||||
/**
|
||||
* Expands the rectangle in all directions
|
||||
* @param {number} amount
|
||||
* @returns {Rectangle} new rectangle
|
||||
*/
|
||||
|
||||
expandedInAllDirections(amount) {
|
||||
return new Rectangle(this.x - amount, this.y - amount, this.w + 2 * amount, this.h + 2 * amount);
|
||||
}
|
||||
|
||||
// Culling helpers
|
||||
|
||||
@@ -19,6 +19,52 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
||||
return "#777";
|
||||
}
|
||||
|
||||
getName() {
|
||||
return "Belt";
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
return "Transports items, hold and drag to place multiple, press 'R' to rotate.";
|
||||
}
|
||||
|
||||
getPreviewSprite(rotationVariant) {
|
||||
switch (arrayBeltVariantToRotation[rotationVariant]) {
|
||||
case enumDirection.top: {
|
||||
return Loader.getSprite("sprites/buildings/belt_top.png");
|
||||
}
|
||||
case enumDirection.left: {
|
||||
return Loader.getSprite("sprites/buildings/belt_left.png");
|
||||
}
|
||||
case enumDirection.right: {
|
||||
return Loader.getSprite("sprites/buildings/belt_right.png");
|
||||
}
|
||||
default: {
|
||||
assertAlways(false, "Invalid belt rotation variant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBlueprintSprite(rotationVariant) {
|
||||
switch (arrayBeltVariantToRotation[rotationVariant]) {
|
||||
case enumDirection.top: {
|
||||
return Loader.getSprite("sprites/blueprints/belt_top.png");
|
||||
}
|
||||
case enumDirection.left: {
|
||||
return Loader.getSprite("sprites/blueprints/belt_left.png");
|
||||
}
|
||||
case enumDirection.right: {
|
||||
return Loader.getSprite("sprites/blueprints/belt_right.png");
|
||||
}
|
||||
default: {
|
||||
assertAlways(false, "Invalid belt rotation variant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getStayInPlacementMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
@@ -140,65 +186,29 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
||||
|
||||
// When there is a top acceptor, ignore sides
|
||||
// NOTICE: This makes the belt prefer side turns *way* too much!
|
||||
// if (!hasTopAcceptor) {
|
||||
// // When there is an acceptor to the right but no acceptor to the left,
|
||||
// // do a turn to the right
|
||||
// if (hasRightAcceptor && !hasLeftAcceptor) {
|
||||
// return {
|
||||
// rotation,
|
||||
// rotationVariant: 2,
|
||||
// };
|
||||
// }
|
||||
if (!hasTopAcceptor) {
|
||||
// When there is an acceptor to the right but no acceptor to the left,
|
||||
// do a turn to the right
|
||||
if (hasRightAcceptor && !hasLeftAcceptor) {
|
||||
return {
|
||||
rotation,
|
||||
rotationVariant: 2,
|
||||
};
|
||||
}
|
||||
|
||||
// // When there is an acceptor to the left but no acceptor to the right,
|
||||
// // do a turn to the left
|
||||
// if (hasLeftAcceptor && !hasRightAcceptor) {
|
||||
// return {
|
||||
// rotation,
|
||||
// rotationVariant: 1,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// When there is an acceptor to the left but no acceptor to the right,
|
||||
// do a turn to the left
|
||||
if (hasLeftAcceptor && !hasRightAcceptor) {
|
||||
return {
|
||||
rotation,
|
||||
rotationVariant: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rotation,
|
||||
rotationVariant: 0,
|
||||
};
|
||||
}
|
||||
|
||||
getName() {
|
||||
return "Belt";
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
return "Transports items, hold and drag to place multiple, press 'R' to rotate.";
|
||||
}
|
||||
|
||||
getPreviewSprite(rotationVariant) {
|
||||
switch (arrayBeltVariantToRotation[rotationVariant]) {
|
||||
case enumDirection.top: {
|
||||
return Loader.getSprite("sprites/belt/forward_0.png");
|
||||
}
|
||||
case enumDirection.left: {
|
||||
return Loader.getSprite("sprites/belt/left_0.png");
|
||||
}
|
||||
case enumDirection.right: {
|
||||
return Loader.getSprite("sprites/belt/right_0.png");
|
||||
}
|
||||
default: {
|
||||
assertAlways(false, "Invalid belt rotation variant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getStayInPlacementMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden
|
||||
*/
|
||||
internalGetBeltDirection(rotationVariant) {
|
||||
return enumDirection.top;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,17 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
}
|
||||
}
|
||||
|
||||
getBlueprintSprite(rotationVariant) {
|
||||
switch (arrayUndergroundRotationVariantToMode[rotationVariant]) {
|
||||
case enumUndergroundBeltMode.sender:
|
||||
return Loader.getSprite("sprites/blueprints/underground_belt_entry.png");
|
||||
case enumUndergroundBeltMode.receiver:
|
||||
return Loader.getSprite("sprites/blueprints/underground_belt_exit.png");
|
||||
default:
|
||||
assertAlways(false, "Invalid rotation variant");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { pulseAnimation, makeDiv } from "../../../core/utils";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { Math_abs, Math_radians } from "../../../core/builtins";
|
||||
import { Math_abs, Math_radians, Math_degrees } from "../../../core/builtins";
|
||||
import { Loader } from "../../../core/loader";
|
||||
import { drawRotatedSprite } from "../../../core/draw_utils";
|
||||
import { Entity } from "../../entity";
|
||||
@@ -96,6 +96,9 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
||||
|
||||
if (!oldPos.equals(newPos)) {
|
||||
const delta = newPos.sub(oldPos);
|
||||
const angleDeg = Math_degrees(delta.angle());
|
||||
this.currentBaseRotation = (Math.round(angleDeg / 90) * 90 + 360) % 360;
|
||||
|
||||
// - Using bresenhams algorithmus
|
||||
|
||||
let x0 = oldPos.x;
|
||||
@@ -351,36 +354,49 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
||||
metaBuilding.updateRotationVariant(this.fakeEntity, rotationVariant);
|
||||
|
||||
// Check if we could place the buildnig
|
||||
const canBuild = this.root.logic.checkCanPlaceBuilding(tile, rotation, metaBuilding);
|
||||
const canBuild = this.root.logic.checkCanPlaceBuilding({
|
||||
origin: tile,
|
||||
rotation,
|
||||
rotationVariant,
|
||||
building: metaBuilding,
|
||||
});
|
||||
|
||||
// Fade in / out
|
||||
parameters.context.lineWidth = 1;
|
||||
// parameters.context.globalAlpha = 0.3 + pulseAnimation(this.root.time.realtimeNow(), 0.9) * 0.7;
|
||||
|
||||
// Determine the bounds and visualize them
|
||||
const entityBounds = staticComp.getTileSpaceBounds();
|
||||
const drawBorder = 2;
|
||||
parameters.context.globalAlpha = 0.5;
|
||||
const drawBorder = -3;
|
||||
if (canBuild) {
|
||||
parameters.context.fillStyle = "rgba(0, 255, 0, 0.2)";
|
||||
parameters.context.strokeStyle = "rgba(56, 235, 111, 0.5)";
|
||||
parameters.context.fillStyle = "rgba(56, 235, 111, 0.2)";
|
||||
} else {
|
||||
parameters.context.strokeStyle = "rgba(255, 0, 0, 0.2)";
|
||||
parameters.context.fillStyle = "rgba(255, 0, 0, 0.2)";
|
||||
}
|
||||
parameters.context.fillRect(
|
||||
|
||||
parameters.context.beginRoundedRect(
|
||||
entityBounds.x * globalConfig.tileSize - drawBorder,
|
||||
entityBounds.y * globalConfig.tileSize - drawBorder,
|
||||
entityBounds.w * globalConfig.tileSize + 2 * drawBorder,
|
||||
entityBounds.h * globalConfig.tileSize + 2 * drawBorder
|
||||
entityBounds.h * globalConfig.tileSize + 2 * drawBorder,
|
||||
4
|
||||
);
|
||||
parameters.context.stroke();
|
||||
// parameters.context.fill();
|
||||
parameters.context.globalAlpha = 1;
|
||||
|
||||
// HACK to draw the entity sprite
|
||||
const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant);
|
||||
staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, previewSprite);
|
||||
staticComp.origin = tile;
|
||||
|
||||
// Draw ejectors
|
||||
if (canBuild) {
|
||||
this.drawMatchingAcceptorsAndEjectors(parameters);
|
||||
}
|
||||
|
||||
// HACK to draw the entity sprite
|
||||
const previewSprite = metaBuilding.getPreviewSprite(rotationVariant);
|
||||
parameters.context.globalAlpha = 0.8 + pulseAnimation(this.root.time.realtimeNow(), 1) * 0.1;
|
||||
staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
|
||||
staticComp.drawSpriteOnFullEntityBounds(parameters, previewSprite);
|
||||
staticComp.origin = tile;
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,6 +413,8 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
||||
|
||||
// Just ignore this code ...
|
||||
|
||||
const offsetShift = 10;
|
||||
|
||||
if (acceptorComp) {
|
||||
const slots = acceptorComp.slots;
|
||||
for (let acceptorSlotIndex = 0; acceptorSlotIndex < slots.length; ++acceptorSlotIndex) {
|
||||
@@ -437,7 +455,7 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
||||
y: acceptorSlotWsPos.y,
|
||||
angle: Math_radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
|
||||
size: 13,
|
||||
offsetY: 15,
|
||||
offsetY: offsetShift + 13,
|
||||
});
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
@@ -483,7 +501,7 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
||||
y: ejectorSLotWsPos.y,
|
||||
angle: Math_radians(enumDirectionToAngle[ejectorSlotWsDirection]),
|
||||
size: 13,
|
||||
offsetY: 15,
|
||||
offsetY: offsetShift,
|
||||
});
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { StaticMapEntityComponent } from "./components/static_map_entity";
|
||||
import { Math_abs } from "../core/builtins";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { MetaBeltBaseBuilding, arrayBeltVariantToRotation } from "./buildings/belt_base";
|
||||
|
||||
const logger = createLogger("ingame/logic");
|
||||
|
||||
@@ -46,12 +47,14 @@ export class GameLogic {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Vector} origin
|
||||
* @param {number} rotation
|
||||
* @param {MetaBuilding} building
|
||||
* @param {object} param0
|
||||
* @param {Vector} param0.origin
|
||||
* @param {number} param0.rotation
|
||||
* @param {number} param0.rotationVariant
|
||||
* @param {MetaBuilding} param0.building
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAreaFreeToBuild(origin, rotation, building) {
|
||||
isAreaFreeToBuild({ origin, rotation, rotationVariant, building }) {
|
||||
const checker = new StaticMapEntityComponent({
|
||||
origin,
|
||||
tileSize: building.getDimensions(),
|
||||
@@ -63,8 +66,19 @@ export class GameLogic {
|
||||
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.getTileContentXY(x, y);
|
||||
if (contents && !contents.components.ReplaceableMapEntity) {
|
||||
return false;
|
||||
if (contents) {
|
||||
if (
|
||||
!this.checkCanReplaceBuilding({
|
||||
original: contents,
|
||||
origin,
|
||||
building,
|
||||
rotation,
|
||||
rotationVariant,
|
||||
})
|
||||
) {
|
||||
// Content already has same rotation
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,16 +86,54 @@ export class GameLogic {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Vector} origin
|
||||
* @param {number} rotation
|
||||
* @param {MetaBuilding} building
|
||||
* Checks if the given building can be replaced by another
|
||||
* @param {object} param0
|
||||
* @param {Entity} param0.original
|
||||
* @param {Vector} param0.origin
|
||||
* @param {number} param0.rotation
|
||||
* @param {number} param0.rotationVariant
|
||||
* @param {MetaBuilding} param0.building
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkCanPlaceBuilding(origin, rotation, building) {
|
||||
checkCanReplaceBuilding({ original, origin, building, rotation, rotationVariant }) {
|
||||
if (!original.components.ReplaceableMapEntity) {
|
||||
// Can not get replaced at all
|
||||
return false;
|
||||
}
|
||||
|
||||
const staticComp = original.components.StaticMapEntity;
|
||||
assert(staticComp, "Building is not static");
|
||||
const beltComp = original.components.Belt;
|
||||
if (beltComp) {
|
||||
// Its a belt, check if it differs in either rotation or rotation variant
|
||||
if (staticComp.rotationDegrees !== rotation) {
|
||||
return true;
|
||||
}
|
||||
if (beltComp.direction !== arrayBeltVariantToRotation[rotationVariant]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {Vector} param0.origin
|
||||
* @param {number} param0.rotation
|
||||
* @param {number} param0.rotationVariant
|
||||
* @param {MetaBuilding} param0.building
|
||||
*/
|
||||
checkCanPlaceBuilding({ origin, rotation, rotationVariant, building }) {
|
||||
if (!building.getIsUnlocked(this.root)) {
|
||||
return false;
|
||||
}
|
||||
return this.isAreaFreeToBuild(origin, rotation, building);
|
||||
return this.isAreaFreeToBuild({
|
||||
origin,
|
||||
rotation,
|
||||
rotationVariant,
|
||||
building,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +145,7 @@ export class GameLogic {
|
||||
* @param {MetaBuilding} param0.building
|
||||
*/
|
||||
tryPlaceBuilding({ origin, rotation, rotationVariant, building }) {
|
||||
if (this.checkCanPlaceBuilding(origin, rotation, building)) {
|
||||
if (this.checkCanPlaceBuilding({ origin, rotation, rotationVariant, building })) {
|
||||
// Remove any removeable entities below
|
||||
const checker = new StaticMapEntityComponent({
|
||||
origin,
|
||||
@@ -106,7 +158,7 @@ export class GameLogic {
|
||||
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.getTileContentXY(x, y);
|
||||
if (contents && contents.components.ReplaceableMapEntity) {
|
||||
if (contents) {
|
||||
if (!this.tryDeleteBuilding(contents)) {
|
||||
logger.error("Building has replaceable component but is also unremovable");
|
||||
return false;
|
||||
|
||||
@@ -65,6 +65,14 @@ export class MetaBuilding {
|
||||
return Loader.getSprite("sprites/buildings/" + this.id + ".png");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sprite for blueprints
|
||||
* @returns {AtlasSprite}
|
||||
*/
|
||||
getBlueprintSprite(rotationVariant = 0) {
|
||||
return Loader.getSprite("sprites/blueprints/" + this.id + ".png");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this building is rotateable
|
||||
* @returns {boolean}
|
||||
@@ -110,8 +118,8 @@ export class MetaBuilding {
|
||||
this.setupEntityComponents(entity, root);
|
||||
this.updateRotationVariant(entity, rotationVariant);
|
||||
|
||||
root.entityMgr.registerEntity(entity);
|
||||
root.map.placeStaticEntity(entity);
|
||||
root.entityMgr.registerEntity(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { gMetaBuildingRegistry } from "../../core/global_registries";
|
||||
import { MetaBeltBaseBuilding } from "../buildings/belt_base";
|
||||
|
||||
const BELT_ANIM_COUNT = 6;
|
||||
|
||||
@@ -51,6 +53,49 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
Loader.getSprite("sprites/belt/right_5.png"),
|
||||
],
|
||||
};
|
||||
|
||||
this.root.signals.entityAdded.add(this.updateSurroundingBeltPlacement, this);
|
||||
this.root.signals.entityDestroyed.add(this.updateSurroundingBeltPlacement, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the belt placement after an entity has been added / deleted
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
updateSurroundingBeltPlacement(entity) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
if (!staticComp) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metaBelt = gMetaBuildingRegistry.findByClass(MetaBeltBaseBuilding);
|
||||
|
||||
// Compute affected area
|
||||
const originalRect = staticComp.getTileSpaceBounds();
|
||||
const affectedArea = originalRect.expandedInAllDirections(1);
|
||||
for (let x = affectedArea.x; x < affectedArea.right(); ++x) {
|
||||
for (let y = affectedArea.y; y < affectedArea.bottom(); ++y) {
|
||||
if (!originalRect.containsPoint(x, y)) {
|
||||
const targetEntity = this.root.map.getTileContentXY(x, y);
|
||||
if (targetEntity) {
|
||||
const targetBeltComp = targetEntity.components.Belt;
|
||||
if (targetBeltComp) {
|
||||
const targetStaticComp = targetEntity.components.StaticMapEntity;
|
||||
const {
|
||||
rotation,
|
||||
rotationVariant,
|
||||
} = metaBelt.computeOptimalDirectionAndRotationVariantAtTile(
|
||||
this.root,
|
||||
new Vector(x, y),
|
||||
targetStaticComp.rotationDegrees
|
||||
);
|
||||
targetStaticComp.rotationDegrees = rotation;
|
||||
metaBelt.updateRotationVariant(targetEntity, rotationVariant);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw(parameters) {
|
||||
|
||||
Reference in New Issue
Block a user