1
0
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:
Tobias Springer
2020-05-10 17:00:02 +02:00
parent 61bda596b6
commit 72476486b7
22 changed files with 335 additions and 94 deletions

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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
*/

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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) {