import { Math_radians, Math_min } from "../../core/builtins"; import { globalConfig } from "../../core/config"; import { DrawParameters } from "../../core/draw_parameters"; import { Loader } from "../../core/loader"; import { AtlasSprite } from "../../core/sprites"; import { BeltComponent } from "../components/belt"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector"; import { MapChunkView } from "../map_chunk_view"; const BELT_ANIM_COUNT = 6; export class BeltSystem extends GameSystemWithFilter { constructor(root) { super(root, [BeltComponent]); /** * @type {Object.>} */ this.beltSprites = { [enumDirection.top]: Loader.getSprite("sprites/belt/forward_0.png"), [enumDirection.left]: Loader.getSprite("sprites/belt/left_0.png"), [enumDirection.right]: Loader.getSprite("sprites/belt/right_0.png"), }; /** * @type {Object.>} */ this.beltAnimations = { [enumDirection.top]: [ Loader.getSprite("sprites/belt/forward_0.png"), Loader.getSprite("sprites/belt/forward_1.png"), Loader.getSprite("sprites/belt/forward_2.png"), Loader.getSprite("sprites/belt/forward_3.png"), Loader.getSprite("sprites/belt/forward_4.png"), Loader.getSprite("sprites/belt/forward_5.png"), ], [enumDirection.left]: [ Loader.getSprite("sprites/belt/left_0.png"), Loader.getSprite("sprites/belt/left_1.png"), Loader.getSprite("sprites/belt/left_2.png"), Loader.getSprite("sprites/belt/left_3.png"), Loader.getSprite("sprites/belt/left_4.png"), Loader.getSprite("sprites/belt/left_5.png"), ], [enumDirection.right]: [ Loader.getSprite("sprites/belt/right_0.png"), Loader.getSprite("sprites/belt/right_1.png"), Loader.getSprite("sprites/belt/right_2.png"), Loader.getSprite("sprites/belt/right_3.png"), Loader.getSprite("sprites/belt/right_4.png"), Loader.getSprite("sprites/belt/right_5.png"), ], }; } draw(parameters) { this.forEachMatchingEntityOnScreen(parameters, this.drawEntityItems.bind(this)); } update() { const beltSpeed = this.root.hubGoals.getBeltBaseSpeed() * globalConfig.physicsDeltaSeconds; for (let i = 0; i < this.allEntities.length; ++i) { const entity = this.allEntities[i]; const beltComp = entity.components.Belt; const staticComp = entity.components.StaticMapEntity; const items = beltComp.sortedItems; if (items.length === 0) { // Fast out for performance continue; } const ejectorComp = entity.components.ItemEjector; let maxProgress = 1; // When ejecting, we can not go further than the item spacing since it // will be on the corner if (ejectorComp.isAnySlotEjecting()) { maxProgress = 1 - globalConfig.itemSpacingOnBelts; } else { // Find follow up belt to make sure we don't clash items const followUpDirection = staticComp.localDirectionToWorld(beltComp.direction); const followUpVector = enumDirectionToVector[followUpDirection]; const followUpTile = staticComp.origin.add(followUpVector); const followUpEntity = this.root.map.getTileContent(followUpTile); if (followUpEntity) { const followUpBeltComp = followUpEntity.components.Belt; if (followUpBeltComp) { const spacingOnBelt = followUpBeltComp.getDistanceToFirstItemCenter(); maxProgress = Math_min(1, 1 - globalConfig.itemSpacingOnBelts + spacingOnBelt); } } } let speedMultiplier = 1; if (beltComp.direction !== enumDirection.top) { // Shaped belts are longer, thus being quicker speedMultiplier = 1.41; } for (let itemIndex = items.length - 1; itemIndex >= 0; --itemIndex) { const itemAndProgress = items[itemIndex]; const newProgress = itemAndProgress[0] + speedMultiplier * beltSpeed; if (newProgress >= 1.0) { // Try to give this item to a new belt const freeSlot = ejectorComp.getFirstFreeSlot(); if (freeSlot === null) { // So, we don't have a free slot - damned! itemAndProgress[0] = 1.0; maxProgress = 1 - globalConfig.itemSpacingOnBelts; } else { // We got a free slot, remove this item and keep it on the ejector slot if (!ejectorComp.tryEject(freeSlot, itemAndProgress[1])) { assert(false, "Ejection failed"); } items.splice(itemIndex, 1); maxProgress = 1; } } else { itemAndProgress[0] = Math_min(newProgress, maxProgress); maxProgress = itemAndProgress[0] - globalConfig.itemSpacingOnBelts; } } } } /** * * @param {DrawParameters} parameters * @param {MapChunkView} chunk */ drawChunk(parameters, chunk) { if (parameters.zoomLevel < globalConfig.mapChunkOverviewMinZoom) { return; 1; } const speedMultiplier = this.root.hubGoals.getBeltBaseSpeed(); // SYNC with systems/item_processor.js:drawEntityUnderlays! // 126 / 42 is the exact animation speed of the png animation const animationIndex = Math.floor( (this.root.time.now() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42 ); const contents = chunk.contents; for (let y = 0; y < globalConfig.mapChunkSize; ++y) { for (let x = 0; x < globalConfig.mapChunkSize; ++x) { const entity = contents[x][y]; if (entity && entity.components.Belt) { const direction = entity.components.Belt.direction; const sprite = this.beltAnimations[direction][animationIndex % BELT_ANIM_COUNT]; entity.components.StaticMapEntity.drawSpriteOnFullEntityBounds( parameters, sprite, 0, false ); } } } 1; } /** * @param {DrawParameters} parameters * @param {Entity} entity */ drawEntityItems(parameters, entity) { const beltComp = entity.components.Belt; const staticComp = entity.components.StaticMapEntity; const items = beltComp.sortedItems; if (items.length === 0) { // Fast out for performance return; } for (let i = 0; i < items.length; ++i) { const itemAndProgress = items[i]; // Nice would be const [pos, item] = itemAndPos; but that gets polyfilled and is super slow then const progress = itemAndProgress[0]; const item = itemAndProgress[1]; const position = staticComp.applyRotationToVector(beltComp.transformBeltToLocalSpace(progress)); item.draw( (staticComp.origin.x + position.x + 0.5) * globalConfig.tileSize, (staticComp.origin.y + position.y + 0.5) * globalConfig.tileSize, parameters ); } } }