Improve placement of belts
BIN
res_raw/sprites/blueprints/belt_left.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
res_raw/sprites/blueprints/belt_right.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
res_raw/sprites/blueprints/belt_top.png
Normal file
After Width: | Height: | Size: 1022 B |
BIN
res_raw/sprites/blueprints/cutter.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
res_raw/sprites/blueprints/miner.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
res_raw/sprites/blueprints/mixer.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
res_raw/sprites/blueprints/painter.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
res_raw/sprites/blueprints/rotater.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
res_raw/sprites/blueprints/splitter.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
res_raw/sprites/blueprints/stacker.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
res_raw/sprites/blueprints/trash.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
res_raw/sprites/blueprints/underground_belt_entry.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
res_raw/sprites/blueprints/underground_belt_exit.png
Normal file
After Width: | Height: | Size: 11 KiB |
96
res_raw/sprites/create_blueprint_previews.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Requirements: numpy, scipy, Pillow,
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import numpy as np
|
||||||
|
from scipy import ndimage
|
||||||
|
from PIL import Image, ImageFilter, ImageChops
|
||||||
|
import math
|
||||||
|
from os import listdir
|
||||||
|
from os.path import isdir, isfile
|
||||||
|
|
||||||
|
roberts_cross_v = np.array([[0, 0, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 0, -1]])
|
||||||
|
|
||||||
|
roberts_cross_h = np.array([[0, 0, 0],
|
||||||
|
[0, 0, 1],
|
||||||
|
[0, -1, 0]])
|
||||||
|
|
||||||
|
|
||||||
|
def rgb2gray(rgb):
|
||||||
|
return np.dot(rgb[..., :3], [0.2989, 0.5870, 0.1140])
|
||||||
|
|
||||||
|
|
||||||
|
def save_image(data, outfilename, src_image):
|
||||||
|
img = Image.fromarray(np.asarray(
|
||||||
|
np.clip(data, 0, 255), dtype="uint8"), "L")
|
||||||
|
dest = Image.new("RGBA", (img.width, img.height))
|
||||||
|
src = img.load()
|
||||||
|
dst = dest.load()
|
||||||
|
|
||||||
|
realSrc = src_image.load()
|
||||||
|
mask = src_image.filter(ImageFilter.GaussianBlur(10)).load()
|
||||||
|
orig = src_image.load()
|
||||||
|
|
||||||
|
for x in range(img.width):
|
||||||
|
for y in range(img.height):
|
||||||
|
realpixl = realSrc[x, y]
|
||||||
|
greyval = float(src[x, y])
|
||||||
|
greyval = min(255.0, greyval)
|
||||||
|
greyval = math.pow(
|
||||||
|
min(1, float(greyval / 255.0 * 1)), 1.5) * 255.0 * 1
|
||||||
|
greyval = max(0, greyval)
|
||||||
|
alpha = mask[x, y][3] / 255.0 * 1
|
||||||
|
|
||||||
|
edgeFactor = src[x, y] / 255.0
|
||||||
|
noEdge = 1 - edgeFactor
|
||||||
|
|
||||||
|
shadow = min(1, 1 - realpixl[3] / 255.0 - edgeFactor)
|
||||||
|
noShadow = 1 - shadow
|
||||||
|
|
||||||
|
dst[x, y] = (
|
||||||
|
min(255, int((realpixl[0] / 255.0 * 0.4 + 0.6) * 104 * 1.1)),
|
||||||
|
min(255, int((realpixl[1] / 255.0 * 0.4 + 0.6) * 200 * 1.1)),
|
||||||
|
min(255, int((realpixl[2] / 255.0 * 0.4 + 0.6) * 255 * 1.1)),
|
||||||
|
min(255, int(float(realpixl[3]) * (0.6 + 5 * edgeFactor))))
|
||||||
|
|
||||||
|
|
||||||
|
dest.save(outfilename)
|
||||||
|
|
||||||
|
|
||||||
|
def roberts_cross(infilename, outfilename):
|
||||||
|
print "Processing", infilename
|
||||||
|
img = Image.open(infilename)
|
||||||
|
img.load()
|
||||||
|
img = img.filter(ImageFilter.GaussianBlur(0.5))
|
||||||
|
|
||||||
|
image = rgb2gray(np.asarray(img, dtype="int32"))
|
||||||
|
vertical = ndimage.convolve(image, roberts_cross_v)
|
||||||
|
horizontal = ndimage.convolve(image, roberts_cross_h)
|
||||||
|
output_image = np.sqrt(np.square(horizontal) + np.square(vertical))
|
||||||
|
save_image(output_image, outfilename, img)
|
||||||
|
|
||||||
|
|
||||||
|
def generateUiPreview(srcPath, buildingId):
|
||||||
|
print srcPath, buildingId
|
||||||
|
img = Image.open(srcPath)
|
||||||
|
img.load()
|
||||||
|
img.thumbnail((110, 110), Image.ANTIALIAS)
|
||||||
|
img.save("../res/ui/hud/building_previews/" + buildingId + ".png")
|
||||||
|
|
||||||
|
img = img.convert("LA")
|
||||||
|
|
||||||
|
data = img.load()
|
||||||
|
for x in range(img.width):
|
||||||
|
for y in range(img.height):
|
||||||
|
data[x, y] = (data[x, y][0], int(data[x, y][1] * 0.5))
|
||||||
|
|
||||||
|
img.save("../res/ui/hud/building_previews/" + buildingId + "_disabled.png")
|
||||||
|
|
||||||
|
|
||||||
|
buildings = listdir("buildings")
|
||||||
|
|
||||||
|
for buildingId in buildings:
|
||||||
|
if "hub" in buildingId:
|
||||||
|
continue
|
||||||
|
roberts_cross("buildings/" + buildingId + "", "blueprints/" + buildingId + "")
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.1 KiB |
@ -175,13 +175,14 @@ export class Rectangle {
|
|||||||
return new Rectangle(this.x * factor, this.y * factor, this.w * factor, this.h * factor);
|
return new Rectangle(this.x * factor, this.y * factor, this.w * factor, this.h * factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increases the rectangle in all directions
|
/**
|
||||||
expandInAllDirections(amount) {
|
* Expands the rectangle in all directions
|
||||||
this.x -= amount;
|
* @param {number} amount
|
||||||
this.y -= amount;
|
* @returns {Rectangle} new rectangle
|
||||||
this.w += 2 * amount;
|
*/
|
||||||
this.h += 2 * amount;
|
|
||||||
return this;
|
expandedInAllDirections(amount) {
|
||||||
|
return new Rectangle(this.x - amount, this.y - amount, this.w + 2 * amount, this.h + 2 * amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Culling helpers
|
// Culling helpers
|
||||||
|
@ -19,6 +19,52 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
|||||||
return "#777";
|
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
|
* Creates the entity at the given location
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
@ -140,65 +186,29 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
|
|||||||
|
|
||||||
// When there is a top acceptor, ignore sides
|
// When there is a top acceptor, ignore sides
|
||||||
// NOTICE: This makes the belt prefer side turns *way* too much!
|
// NOTICE: This makes the belt prefer side turns *way* too much!
|
||||||
// if (!hasTopAcceptor) {
|
if (!hasTopAcceptor) {
|
||||||
// // When there is an acceptor to the right but no acceptor to the left,
|
// When there is an acceptor to the right but no acceptor to the left,
|
||||||
// // do a turn to the right
|
// do a turn to the right
|
||||||
// if (hasRightAcceptor && !hasLeftAcceptor) {
|
if (hasRightAcceptor && !hasLeftAcceptor) {
|
||||||
// return {
|
return {
|
||||||
// rotation,
|
rotation,
|
||||||
// rotationVariant: 2,
|
rotationVariant: 2,
|
||||||
// };
|
};
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // When there is an acceptor to the left but no acceptor to the right,
|
// When there is an acceptor to the left but no acceptor to the right,
|
||||||
// // do a turn to the left
|
// do a turn to the left
|
||||||
// if (hasLeftAcceptor && !hasRightAcceptor) {
|
if (hasLeftAcceptor && !hasRightAcceptor) {
|
||||||
// return {
|
return {
|
||||||
// rotation,
|
rotation,
|
||||||
// rotationVariant: 1,
|
rotationVariant: 1,
|
||||||
// };
|
};
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rotation,
|
rotation,
|
||||||
rotationVariant: 0,
|
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
|
* @param {GameRoot} root
|
||||||
*/
|
*/
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import { pulseAnimation, makeDiv } from "../../../core/utils";
|
import { pulseAnimation, makeDiv } from "../../../core/utils";
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
import { TrackedState } from "../../../core/tracked_state";
|
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 { Loader } from "../../../core/loader";
|
||||||
import { drawRotatedSprite } from "../../../core/draw_utils";
|
import { drawRotatedSprite } from "../../../core/draw_utils";
|
||||||
import { Entity } from "../../entity";
|
import { Entity } from "../../entity";
|
||||||
@ -96,6 +96,9 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
|||||||
|
|
||||||
if (!oldPos.equals(newPos)) {
|
if (!oldPos.equals(newPos)) {
|
||||||
const delta = newPos.sub(oldPos);
|
const delta = newPos.sub(oldPos);
|
||||||
|
const angleDeg = Math_degrees(delta.angle());
|
||||||
|
this.currentBaseRotation = (Math.round(angleDeg / 90) * 90 + 360) % 360;
|
||||||
|
|
||||||
// - Using bresenhams algorithmus
|
// - Using bresenhams algorithmus
|
||||||
|
|
||||||
let x0 = oldPos.x;
|
let x0 = oldPos.x;
|
||||||
@ -351,36 +354,49 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
|||||||
metaBuilding.updateRotationVariant(this.fakeEntity, rotationVariant);
|
metaBuilding.updateRotationVariant(this.fakeEntity, rotationVariant);
|
||||||
|
|
||||||
// Check if we could place the buildnig
|
// 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
|
// Determine the bounds and visualize them
|
||||||
const entityBounds = staticComp.getTileSpaceBounds();
|
const entityBounds = staticComp.getTileSpaceBounds();
|
||||||
const drawBorder = 2;
|
const drawBorder = -3;
|
||||||
parameters.context.globalAlpha = 0.5;
|
|
||||||
if (canBuild) {
|
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 {
|
} else {
|
||||||
|
parameters.context.strokeStyle = "rgba(255, 0, 0, 0.2)";
|
||||||
parameters.context.fillStyle = "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.x * globalConfig.tileSize - drawBorder,
|
||||||
entityBounds.y * globalConfig.tileSize - drawBorder,
|
entityBounds.y * globalConfig.tileSize - drawBorder,
|
||||||
entityBounds.w * globalConfig.tileSize + 2 * 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
|
// Draw ejectors
|
||||||
if (canBuild) {
|
if (canBuild) {
|
||||||
this.drawMatchingAcceptorsAndEjectors(parameters);
|
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 ...
|
// Just ignore this code ...
|
||||||
|
|
||||||
|
const offsetShift = 10;
|
||||||
|
|
||||||
if (acceptorComp) {
|
if (acceptorComp) {
|
||||||
const slots = acceptorComp.slots;
|
const slots = acceptorComp.slots;
|
||||||
for (let acceptorSlotIndex = 0; acceptorSlotIndex < slots.length; ++acceptorSlotIndex) {
|
for (let acceptorSlotIndex = 0; acceptorSlotIndex < slots.length; ++acceptorSlotIndex) {
|
||||||
@ -437,7 +455,7 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
|||||||
y: acceptorSlotWsPos.y,
|
y: acceptorSlotWsPos.y,
|
||||||
angle: Math_radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
|
angle: Math_radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
|
||||||
size: 13,
|
size: 13,
|
||||||
offsetY: 15,
|
offsetY: offsetShift + 13,
|
||||||
});
|
});
|
||||||
parameters.context.globalAlpha = 1;
|
parameters.context.globalAlpha = 1;
|
||||||
}
|
}
|
||||||
@ -483,7 +501,7 @@ export class HUDBuildingPlacer extends BaseHUDPart {
|
|||||||
y: ejectorSLotWsPos.y,
|
y: ejectorSLotWsPos.y,
|
||||||
angle: Math_radians(enumDirectionToAngle[ejectorSlotWsDirection]),
|
angle: Math_radians(enumDirectionToAngle[ejectorSlotWsDirection]),
|
||||||
size: 13,
|
size: 13,
|
||||||
offsetY: 15,
|
offsetY: offsetShift,
|
||||||
});
|
});
|
||||||
parameters.context.globalAlpha = 1;
|
parameters.context.globalAlpha = 1;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { StaticMapEntityComponent } from "./components/static_map_entity";
|
|||||||
import { Math_abs } from "../core/builtins";
|
import { Math_abs } from "../core/builtins";
|
||||||
import { Rectangle } from "../core/rectangle";
|
import { Rectangle } from "../core/rectangle";
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
|
import { MetaBeltBaseBuilding, arrayBeltVariantToRotation } from "./buildings/belt_base";
|
||||||
|
|
||||||
const logger = createLogger("ingame/logic");
|
const logger = createLogger("ingame/logic");
|
||||||
|
|
||||||
@ -46,12 +47,14 @@ export class GameLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param {object} param0
|
||||||
* @param {Vector} origin
|
* @param {Vector} param0.origin
|
||||||
* @param {number} rotation
|
* @param {number} param0.rotation
|
||||||
* @param {MetaBuilding} building
|
* @param {number} param0.rotationVariant
|
||||||
|
* @param {MetaBuilding} param0.building
|
||||||
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isAreaFreeToBuild(origin, rotation, building) {
|
isAreaFreeToBuild({ origin, rotation, rotationVariant, building }) {
|
||||||
const checker = new StaticMapEntityComponent({
|
const checker = new StaticMapEntityComponent({
|
||||||
origin,
|
origin,
|
||||||
tileSize: building.getDimensions(),
|
tileSize: building.getDimensions(),
|
||||||
@ -63,25 +66,74 @@ export class GameLogic {
|
|||||||
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.getTileContentXY(x, y);
|
const contents = this.root.map.getTileContentXY(x, y);
|
||||||
if (contents && !contents.components.ReplaceableMapEntity) {
|
if (contents) {
|
||||||
|
if (
|
||||||
|
!this.checkCanReplaceBuilding({
|
||||||
|
original: contents,
|
||||||
|
origin,
|
||||||
|
building,
|
||||||
|
rotation,
|
||||||
|
rotationVariant,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
// Content already has same rotation
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Checks if the given building can be replaced by another
|
||||||
* @param {Vector} origin
|
* @param {object} param0
|
||||||
* @param {number} rotation
|
* @param {Entity} param0.original
|
||||||
* @param {MetaBuilding} building
|
* @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)) {
|
if (!building.getIsUnlocked(this.root)) {
|
||||||
return false;
|
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
|
* @param {MetaBuilding} param0.building
|
||||||
*/
|
*/
|
||||||
tryPlaceBuilding({ origin, rotation, rotationVariant, building }) {
|
tryPlaceBuilding({ origin, rotation, rotationVariant, building }) {
|
||||||
if (this.checkCanPlaceBuilding(origin, rotation, building)) {
|
if (this.checkCanPlaceBuilding({ origin, rotation, rotationVariant, building })) {
|
||||||
// Remove any removeable entities below
|
// Remove any removeable entities below
|
||||||
const checker = new StaticMapEntityComponent({
|
const checker = new StaticMapEntityComponent({
|
||||||
origin,
|
origin,
|
||||||
@ -106,7 +158,7 @@ export class GameLogic {
|
|||||||
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.getTileContentXY(x, y);
|
const contents = this.root.map.getTileContentXY(x, y);
|
||||||
if (contents && contents.components.ReplaceableMapEntity) {
|
if (contents) {
|
||||||
if (!this.tryDeleteBuilding(contents)) {
|
if (!this.tryDeleteBuilding(contents)) {
|
||||||
logger.error("Building has replaceable component but is also unremovable");
|
logger.error("Building has replaceable component but is also unremovable");
|
||||||
return false;
|
return false;
|
||||||
|
@ -65,6 +65,14 @@ export class MetaBuilding {
|
|||||||
return Loader.getSprite("sprites/buildings/" + this.id + ".png");
|
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 whether this building is rotateable
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@ -110,8 +118,8 @@ export class MetaBuilding {
|
|||||||
this.setupEntityComponents(entity, root);
|
this.setupEntityComponents(entity, root);
|
||||||
this.updateRotationVariant(entity, rotationVariant);
|
this.updateRotationVariant(entity, rotationVariant);
|
||||||
|
|
||||||
root.entityMgr.registerEntity(entity);
|
|
||||||
root.map.placeStaticEntity(entity);
|
root.map.placeStaticEntity(entity);
|
||||||
|
root.entityMgr.registerEntity(entity);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import { Entity } from "../entity";
|
|||||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||||
import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector";
|
import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector";
|
||||||
import { MapChunkView } from "../map_chunk_view";
|
import { MapChunkView } from "../map_chunk_view";
|
||||||
|
import { gMetaBuildingRegistry } from "../../core/global_registries";
|
||||||
|
import { MetaBeltBaseBuilding } from "../buildings/belt_base";
|
||||||
|
|
||||||
const BELT_ANIM_COUNT = 6;
|
const BELT_ANIM_COUNT = 6;
|
||||||
|
|
||||||
@ -51,6 +53,49 @@ export class BeltSystem extends GameSystemWithFilter {
|
|||||||
Loader.getSprite("sprites/belt/right_5.png"),
|
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) {
|
draw(parameters) {
|
||||||
|