Improve placement of belts

pull/33/head
Tobias Springer 4 years ago
parent 61bda596b6
commit 72476486b7

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -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 + "")

Binary file not shown.

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);
}
// 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,
// };
// }
// // 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,
// };
// }
// }
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,
};
}
}
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
);
// Draw ejectors
if (canBuild) {
this.drawMatchingAcceptorsAndEjectors(parameters);
}
parameters.context.stroke();
// parameters.context.fill();
parameters.context.globalAlpha = 1;
// HACK to draw the entity sprite
const previewSprite = metaBuilding.getPreviewSprite(rotationVariant);
parameters.context.globalAlpha = 0.8 + pulseAnimation(this.root.time.realtimeNow(), 1) * 0.1;
const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant);
staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
staticComp.drawSpriteOnFullEntityBounds(parameters, previewSprite);
staticComp.origin = tile;
parameters.context.globalAlpha = 1;
// Draw ejectors
if (canBuild) {
this.drawMatchingAcceptorsAndEjectors(parameters);
}
}
/**
@ -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}
*/
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, 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) {

Loading…
Cancel
Save