js/game/buildings/belt.js
import { Loader } from "../../core/loader";
import { formatItemsPerSecond, generateMatrixRotations } from "../../core/utils";
import { enumAngleToDirection, enumDirection, Vector } from "../../core/vector";
import { SOUNDS } from "../../platform/sound";
import { T } from "../../translations";
import { BeltComponent } from "../components/belt";
import { Entity } from "../entity";
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
import { GameRoot } from "../root";
import { THEME } from "../theme";
export class MetaBeltBuilding extends MetaBuilding {
constructor() {
super("belt");
}
/**
* @param {string} variant
*/
getSilhouetteColor(variant) {
return MetaBeltBuilding.silhouetteColors[variant]();
}
/**
* @param {GameRoot} root
*/
getIsUnlocked(root) {
return this.getAvailableVariants(root).length > 0;
}
/**
* @param {string} variant
*/
getIsRemovable(variant) {
return MetaBeltBuilding.isRemovable[variant]();
}
/**
* @param {string} variant
*/
getIsRotateable(variant) {
return MetaBeltBuilding.isRotateable[variant]();
}
/**
* @param {GameRoot} root
*/
getAvailableVariants(root) {
const variants = MetaBeltBuilding.avaibleVariants;
let available = [];
for (const variant in variants) {
if (variants[variant](root)) available.push(variant);
}
return available;
}
/**
* Returns the edit layer of the building
* @param {GameRoot} root
* @param {string} variant
* @returns {Layer}
*/
getLayer(root, variant) {
// @ts-ignore
return MetaBeltBuilding.layerByVariant[variant](root);
}
/**
* @param {string} variant
*/
getDimensions(variant) {
return MetaBeltBuilding.dimensions[variant]();
}
/**
* @param {GameRoot} root
* @param {string} variant
* @returns {Array<[string, string]>}
*/
getAdditionalStatistics(root, variant) {
return MetaBeltBuilding.additionalStatistics[variant](root);
}
getIsReplaceable(variant) {
return MetaBeltBuilding.isReplaceable[variant]();
}
/**
* @param {string} variant
*/
getShowLayerPreview(variant) {
return MetaBeltBuilding.layerPreview[variant]();
}
/**
* @param {number} rotation
* @param {number} rotationVariant
* @param {string} variant
* @param {Entity} entity
* @returns {Array<number>|null}
*/
getSpecialOverlayRenderMatrix(rotation, rotationVariant, variant, entity) {
let matrices = MetaBeltBuilding.overlayMatrices[MetaBeltBuilding.variantToRotation[rotationVariant]](
entity,
rotationVariant
);
return matrices ? matrices[rotation] : null;
}
/**
* @param {string} variant
*/
getRenderPins(variant) {
return MetaBeltBuilding.renderPins[variant]();
}
getPlacementSound(variant) {
return MetaBeltBuilding.placementSounds[variant]();
}
getHasDirectionLockAvailable() {
return true;
}
getStayInPlacementMode() {
return true;
}
getRotateAutomaticallyWhilePlacing() {
return true;
}
getSprite() {
return null;
}
getPreviewSprite(rotationVariant) {
switch (MetaBeltBuilding.variantToRotation[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 (MetaBeltBuilding.variantToRotation[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");
}
}
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {
MetaBeltBuilding.setupEntityComponents.forEach(func => func(entity));
}
/**
* @param {Entity} entity
* @param {number} rotationVariant
* @param {string} variant
*/
updateVariants(entity, rotationVariant, variant) {
MetaBeltBuilding.componentVariations[variant](entity, rotationVariant);
}
/**
* Should compute the optimal rotation variant on the given tile
* @param {object} param0
* @param {GameRoot} param0.root
* @param {Vector} param0.tile
* @param {number} param0.rotation
* @param {string} param0.variant
* @param {Layer} param0.layer
* @return {{ rotation: number, rotationVariant: number, connectedEntities?: Array<Entity> }}
*/
computeOptimalDirectionAndRotationVariantAtTile({ root, tile, rotation, variant, layer }) {
const topDirection = enumAngleToDirection[rotation];
const rightDirection = enumAngleToDirection[(rotation + 90) % 360];
const bottomDirection = enumAngleToDirection[(rotation + 180) % 360];
const leftDirection = enumAngleToDirection[(rotation + 270) % 360];
const { ejectors, acceptors } = root.logic.getEjectorsAndAcceptorsAtTile(tile);
let hasBottomEjector = false;
let hasRightEjector = false;
let hasLeftEjector = false;
let hasTopAcceptor = false;
let hasLeftAcceptor = false;
let hasRightAcceptor = false;
// Check all ejectors
for (let i = 0; i < ejectors.length; ++i) {
const ejector = ejectors[i];
if (ejector.toDirection === topDirection) {
hasBottomEjector = true;
} else if (ejector.toDirection === leftDirection) {
hasRightEjector = true;
} else if (ejector.toDirection === rightDirection) {
hasLeftEjector = true;
}
}
// Check all acceptors
for (let i = 0; i < acceptors.length; ++i) {
const acceptor = acceptors[i];
if (acceptor.fromDirection === bottomDirection) {
hasTopAcceptor = true;
} else if (acceptor.fromDirection === rightDirection) {
hasLeftAcceptor = true;
} else if (acceptor.fromDirection === leftDirection) {
hasRightAcceptor = true;
}
}
// Soo .. if there is any ejector below us we always prioritize
// this ejector
if (!hasBottomEjector) {
// When something ejects to us from the left and nothing from the right,
// do a curve from the left to the top
if (hasRightEjector && !hasLeftEjector) {
return {
rotation: (rotation + 270) % 360,
rotationVariant: 2,
};
}
// When something ejects to us from the right and nothing from the left,
// do a curve from the right to the top
if (hasLeftEjector && !hasRightEjector) {
return {
rotation: (rotation + 90) % 360,
rotationVariant: 1,
};
}
}
// 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,
};
}
}
return {
rotation,
rotationVariant: 0,
};
}
}
MetaBeltBuilding.setupEntityComponents = [
entity =>
entity.addComponent(
new BeltComponent({
direction: enumDirection.top, // updated later
})
),
];
MetaBeltBuilding.silhouetteColors = {
[defaultBuildingVariant]: () => THEME.map.chunkOverview.beltColor,
};
MetaBeltBuilding.variantToRotation = [enumDirection.top, enumDirection.left, enumDirection.right];
MetaBeltBuilding.overlayMatrices = {
[enumDirection.top]: (entity, rotationVariant) => generateMatrixRotations([0, 1, 0, 0, 1, 0, 0, 1, 0]),
[enumDirection.left]: (entity, rotationVariant) => generateMatrixRotations([0, 0, 0, 1, 1, 0, 0, 1, 0]),
[enumDirection.right]: (entity, rotationVariant) => generateMatrixRotations([0, 0, 0, 0, 1, 1, 0, 1, 0]),
};
MetaBeltBuilding.placementSounds = {
[defaultBuildingVariant]: () => SOUNDS.placeBelt,
};
MetaBeltBuilding.rotationVariants = [0, 1, 2];
MetaBeltBuilding.avaibleVariants = {
[defaultBuildingVariant]: root => true,
};
MetaBeltBuilding.dimensions = {
[defaultBuildingVariant]: () => new Vector(1, 1),
};
MetaBeltBuilding.isRemovable = {
[defaultBuildingVariant]: () => true,
};
MetaBeltBuilding.isReplaceable = {
[defaultBuildingVariant]: () => true,
};
MetaBeltBuilding.isRotateable = {
[defaultBuildingVariant]: () => true,
};
MetaBeltBuilding.renderPins = {
[defaultBuildingVariant]: () => null,
};
MetaBeltBuilding.layerPreview = {
[defaultBuildingVariant]: () => null,
};
MetaBeltBuilding.layerByVariant = {
[defaultBuildingVariant]: root => "regular",
};
MetaBeltBuilding.componentVariations = {
[defaultBuildingVariant]: (entity, rotationVariant) => {
entity.components.Belt.direction = MetaBeltBuilding.variantToRotation[rotationVariant];
},
};
MetaBeltBuilding.additionalStatistics = {
/**
* @param {*} root
* @returns {Array<[string, string]>}
*/
[defaultBuildingVariant]: root => [
[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(root.hubGoals.getBeltBaseSpeed())],
],
};