1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00
This commit is contained in:
Emerald Block 2021-07-30 16:03:23 -05:00 committed by GitHub
commit 9757db2e55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 608 additions and 43 deletions

View File

@ -164,7 +164,7 @@
.keybinding {
position: relative;
background: #eee;
@include PlainText;
@include SuperSmallText;
height: unset;
margin: 1px 0;
}
@ -214,6 +214,69 @@
}
}
}
.checkBoxFormElem,
.enumFormElem {
display: flex;
align-items: center;
@include S(margin, 10px, 0);
> label {
@include S(margin-right, 10px);
}
}
.checkBoxGridFormElem {
display: inline-grid;
grid-template-columns: 1fr;
@include S(margin, 10px, 0);
@include S(grid-row-gap, 10px);
> .checkBoxFormElem {
margin: 0;
justify-content: space-between;
}
}
.enum {
display: grid;
grid-template-columns: auto 1fr auto;
@include S(grid-gap, 4px);
@include S(min-width, 160px);
> div {
background: $mainBgColor;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
pointer-events: all;
cursor: pointer;
@include S(border-radius, $globalBorderRadius);
@include S(padding, 4px);
transition: background-color 0.12s ease-in-out;
&:hover {
background-color: darken($mainBgColor, 5);
}
@include DarkThemeOverride {
background-color: $darkModeControlsBackground;
color: #ddd;
&:hover {
background-color: darken($darkModeControlsBackground, 2);
}
}
&.toggle {
@include S(width, 16px);
}
&.value {
transform: none !important;
}
}
}
}
> .buttons {

View File

@ -1,6 +1,8 @@
import { BaseItem } from "../game/base_item";
import { ClickDetector } from "./click_detector";
import { createLogger } from "./logging";
import { Signal } from "./signal";
import { safeModulo } from "./utils";
/*
* ***************************************************
@ -13,6 +15,8 @@ import { Signal } from "./signal";
* ***************************************************
*/
const logger = createLogger("dialog_forms");
export class FormElement {
constructor(id, label) {
this.id = id;
@ -139,7 +143,7 @@ export class FormElementCheckbox extends FormElement {
getHtml() {
return `
<div class="formElement checkBoxFormElem">
${this.label ? `<label>${this.label}</label>` : ""}
${this.label ? `<label>${this.label}</label>` : ""}
<div class="checkbox ${this.defaultValue ? "checked" : ""}" data-formId='${this.id}'>
<span class="knob"></span >
</div >
@ -166,7 +170,32 @@ export class FormElementCheckbox extends FormElement {
this.element.classList.toggle("checked", this.value);
}
focus(parent) {}
focus() {}
}
export class FormElementCheckboxList extends FormElement {
constructor({ id, label = null, checkboxes = [] }) {
super(id, label);
this.checkboxes = checkboxes;
}
getHtml() {
return `
<div class="formElement checkBoxGridFormElem">
${this.checkboxes.map(checkbox => checkbox.getHtml()).join("\n")}
</div>
`;
}
bindEvents(parent, clickTrackers) {
this.checkboxes.forEach(checkbox => checkbox.bindEvents(parent, clickTrackers));
}
getValue() {
return this.checkboxes.map(checkbox => checkbox.getValue());
}
focus() {}
}
export class FormElementItemChooser extends FormElement {
@ -235,3 +264,64 @@ export class FormElementItemChooser extends FormElement {
focus() {}
}
export class FormElementEnum extends FormElement {
constructor({ id, label = null, options, defaultValue = null, valueGetter, textGetter }) {
super(id, label);
this.options = options;
this.valueGetter = valueGetter;
this.textGetter = textGetter;
this.index = 0;
if (defaultValue !== null) {
const index = this.options.findIndex(option => option.id === defaultValue);
if (index >= 0) {
this.index = index;
} else {
logger.warn("Option ID", defaultValue, "not found in", options, "!");
}
}
this.element = null;
}
getHtml() {
return `
<div class="formElement enumFormElem">
${this.label ? `<label>${this.label}</label>` : ""}
<div class="enum" data-formId="${this.id}">
<div class="toggle prev"></div>
<div class="value">${this.textGetter(this.options[this.index])}</div>
<div class="toggle next"></div>
</div>
</div>
`;
}
/**
* @param {HTMLElement} parent
* @param {Array<ClickDetector>} clickTrackers
*/
bindEvents(parent, clickTrackers) {
this.element = this.getFormElement(parent);
const children = this.element.children;
for (let i = 0; i < children.length; ++i) {
const child = children[i];
const detector = new ClickDetector(child, { preventDefault: false });
clickTrackers.push(detector);
const change = child.classList.contains("prev") ? -1 : 1;
detector.click.add(() => this.toggle(change), this);
}
}
getValue() {
return this.valueGetter(this.options[this.index]);
}
toggle(amount) {
this.index = safeModulo(this.index + amount, this.options.length);
this.element.querySelector(".value").innerText = this.textGetter(this.options[this.index]);
}
focus() {}
}

View File

@ -8,9 +8,47 @@ import { T } from "../../../translations";
import { StaticMapEntityComponent } from "../../components/static_map_entity";
import { KEYMAPPINGS } from "../../key_action_mapper";
import { BaseHUDPart } from "../base_hud_part";
import { DialogWithForm } from "../../../core/modal_dialog_elements";
import {
FormElementCheckbox,
FormElementCheckboxList,
FormElementEnum,
} from "../../../core/modal_dialog_forms";
import { ORIGINAL_SPRITE_SCALE } from "../../../core/sprites";
import { getDeviceDPI } from "../../../core/dpi_manager";
import { HUDMassSelector } from "./mass_selector";
import { clamp } from "../../../core/utils";
import { CHUNK_OVERLAY_RES, MapChunkView } from "../../map_chunk_view";
import { enumHubGoalRewards } from "../../tutorial_goals";
const logger = createLogger("screenshot_exporter");
const MAX_CANVAS_DIMS = 16384;
// should be odd so that the centers of tiles are rendered
// as pixels per tile must be a multiple of this
const TARGET_INVERSE_BORDER = 3;
const screenshotQualities = [
{
id: "high",
resolution: MAX_CANVAS_DIMS,
},
{
id: "medium",
resolution: MAX_CANVAS_DIMS / 4,
},
{
id: "low",
resolution: MAX_CANVAS_DIMS / 16,
},
{
id: "pixels",
resolution: 0,
},
];
// @TODO: translation (T.dialogs.exportScreenshotWarning.qualities)
const qualityNames = { high: "High", medium: "Medium", low: "Low", pixels: "Pixels" };
export class HUDScreenshotExporter extends BaseHUDPart {
createElements() {}
@ -24,52 +62,228 @@ export class HUDScreenshotExporter extends BaseHUDPart {
return;
}
const { ok } = this.root.hud.parts.dialogs.showInfo(
T.dialogs.exportScreenshotWarning.title,
T.dialogs.exportScreenshotWarning.desc,
["cancel:good", "ok:bad"]
/** @type {Rectangle} */
let bounds = undefined;
const massSelector = this.root.hud.parts.massSelector;
if (massSelector instanceof HUDMassSelector) {
if (massSelector.currentSelectionStartWorld) {
const worldStart = massSelector.currentSelectionStartWorld;
const worldEnd = this.root.camera.screenToWorld(massSelector.currentSelectionEnd);
const tileStart = worldStart.toTileSpace();
const tileEnd = worldEnd.toTileSpace();
bounds = Rectangle.fromTwoPoints(tileStart, tileEnd);
bounds.w += 1;
bounds.h += 1;
} else if (massSelector.selectedUids.size > 0) {
const minTile = new Vector(Infinity, Infinity);
const maxTile = new Vector(-Infinity, -Infinity);
const entityUids = Array.from(massSelector.selectedUids);
for (let i = 0; i < entityUids.length; ++i) {
const entityBounds = this.root.entityMgr
.findByUid(entityUids[i])
.components.StaticMapEntity.getTileSpaceBounds();
minTile.x = Math.min(minTile.x, entityBounds.x);
minTile.y = Math.min(minTile.y, entityBounds.y);
maxTile.x = Math.max(maxTile.x, entityBounds.x + entityBounds.w);
maxTile.y = Math.max(maxTile.y, entityBounds.y + entityBounds.h);
}
bounds = Rectangle.fromTwoPoints(minTile, maxTile);
}
}
const qualityInput = new FormElementEnum({
id: "screenshotQuality",
label: "Quality",
options: screenshotQualities,
defaultValue: "medium",
valueGetter: quality => quality.resolution,
// @TODO: translation (T.dialogs.exportScreenshotWarning.qualityLabel)
textGetter: quality => qualityNames[quality.id],
});
const overlayInput = new FormElementCheckbox({
id: "screenshotView",
// @TODO: translation (T.dialogs.exportScreenshotWarning.descOverlay)
label: "Map view",
defaultValue: this.root.camera.getIsMapOverlayActive() ? true : false,
});
const layerInput = new FormElementCheckbox({
id: "screenshotLayer",
// @TODO: translation (T.dialogs.exportScreenshotWarning.descLayer)
label: "Wires layer",
defaultValue: this.root.currentLayer === "wires" ? true : false,
});
const backgroundInput = new FormElementCheckbox({
id: "screenshotBackground",
// @TODO: translation (T.dialogs.exportScreenshotWarning.descBackground)
label: "Transparent background",
defaultValue: false,
});
const checkboxInputs = new FormElementCheckboxList({
id: "screenshotCheckboxes",
checkboxes: [
overlayInput,
...(this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_wires_painter_and_levers)
? [layerInput]
: []),
backgroundInput,
],
});
const dialog = new DialogWithForm({
app: this.root.app,
title: T.dialogs.exportScreenshotWarning.title,
desc: bounds
? // @TODO: translation (T.dialogs.exportScreenshotWarning.descSelection)
"You requested to export a region of your base as a screenshot. Please note that this will be quite slow for a bigger region and could potentially crash your game!"
: // @TODO: update translation (T.dialogs.exportScreenshotWarning.desc)
"You requested to export your base as a screenshot. Please note that this will be quite slow for a bigger base and could potentially crash your game!<br><br>Tip: You can select a region with <key> to only take a screenshot of that region.".replace(
"<key>",
"<code class='keybinding'>" +
this.root.keyMapper
.getBinding(KEYMAPPINGS.massSelect.massSelectStart)
.getKeyCodeString() +
"</code>"
),
formElements: [qualityInput, checkboxInputs],
buttons: ["cancel:good", "ok:bad"],
});
dialog.inputReciever.keydown.add(({ keyCode }) => {
if (keyCode === KEYMAPPINGS.ingame.exportScreenshot.keyCode) {
this.root.hud.parts.dialogs.closeDialog(dialog);
}
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
dialog.buttonSignals.ok.add(
() =>
this.doExport(
qualityInput.getValue(),
overlayInput.getValue(),
layerInput.getValue(),
backgroundInput.getValue(),
!!bounds,
bounds
),
this
);
ok.add(this.doExport, this);
}
doExport() {
/**
* Renders a screenshot of the entire base as closely as possible to the ingame camera
* @param {number} targetResolution
* @param {boolean} overlay
* @param {boolean} wiresLayer
* @param {boolean} hideBackground
* @param {boolean} allowBorder
* @param {Rectangle?} tileBounds
*/
doExport(targetResolution, overlay, wiresLayer, hideBackground, allowBorder, tileBounds) {
logger.log("Starting export ...");
// Find extends
const staticEntities = this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent);
const boundsSelected = !!tileBounds;
if (!tileBounds) {
// Find extends
const staticEntities = this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent);
const minTile = new Vector(0, 0);
const maxTile = new Vector(0, 0);
for (let i = 0; i < staticEntities.length; ++i) {
const bounds = staticEntities[i].components.StaticMapEntity.getTileSpaceBounds();
minTile.x = Math.min(minTile.x, bounds.x);
minTile.y = Math.min(minTile.y, bounds.y);
const minTile = new Vector(0, 0);
const maxTile = new Vector(0, 0);
for (let i = 0; i < staticEntities.length; ++i) {
const entityBounds = staticEntities[i].components.StaticMapEntity.getTileSpaceBounds();
minTile.x = Math.min(minTile.x, entityBounds.x);
minTile.y = Math.min(minTile.y, entityBounds.y);
maxTile.x = Math.max(maxTile.x, bounds.x + bounds.w);
maxTile.y = Math.max(maxTile.y, bounds.y + bounds.h);
maxTile.x = Math.max(maxTile.x, entityBounds.x + entityBounds.w);
maxTile.y = Math.max(maxTile.y, entityBounds.y + entityBounds.h);
}
minTile.x = Math.floor(minTile.x / globalConfig.mapChunkSize) * globalConfig.mapChunkSize;
minTile.y = Math.floor(minTile.y / globalConfig.mapChunkSize) * globalConfig.mapChunkSize;
maxTile.x = Math.ceil(maxTile.x / globalConfig.mapChunkSize) * globalConfig.mapChunkSize;
maxTile.y = Math.ceil(maxTile.y / globalConfig.mapChunkSize) * globalConfig.mapChunkSize;
tileBounds = Rectangle.fromTwoPoints(minTile, maxTile).expandedInAllDirections(
globalConfig.mapChunkSize
);
}
const minChunk = minTile.divideScalar(globalConfig.mapChunkSize).floor();
const maxChunk = maxTile.divideScalar(globalConfig.mapChunkSize).ceil();
// if the desired pixels per tile is too small, we do not create a border
// so that we have more valid values for pixels per tile
// we do not create a border for map view since there is no sprite overflow
const border =
allowBorder &&
!overlay &&
targetResolution / (Math.max(tileBounds.w, tileBounds.h) + 2 / TARGET_INVERSE_BORDER) >=
3 * TARGET_INVERSE_BORDER;
const dimensions = maxChunk.sub(minChunk);
logger.log("Dimensions:", dimensions);
const bounds = border ? tileBounds.expandedInAllDirections(1 / TARGET_INVERSE_BORDER) : tileBounds;
logger.log("Bounds:", bounds);
let chunkSizePixels = 128;
const maxDimensions = Math.max(dimensions.x, dimensions.y);
const maxDimensions = Math.max(bounds.w, bounds.h);
if (maxDimensions > 128) {
chunkSizePixels = Math.max(1, Math.floor(128 * (128 / maxDimensions)));
// at least 3 pixels per tile, for bearable quality
// at most the resolution of the assets, to not be excessive
const clamped = clamp(
targetResolution / (maxDimensions + (border ? 2 / 3 : 0)),
3,
globalConfig.assetsDpi * globalConfig.tileSize
);
// 1 is a fake value since it behaves the same as a border width of 0
const inverseBorder = border ? TARGET_INVERSE_BORDER : 1;
const tileSizePixels = overlay
? // we floor to the nearest multiple of the map view tile resolution
Math.floor(clamped / CHUNK_OVERLAY_RES) * CHUNK_OVERLAY_RES || CHUNK_OVERLAY_RES
: // we floor to the nearest odd multiple so that the center of each building is rendered
Math.floor((clamped + inverseBorder) / (2 * inverseBorder)) * (2 * inverseBorder) -
inverseBorder || inverseBorder;
logger.log("Pixels per tile:", tileSizePixels);
if (Math.round(tileSizePixels * maxDimensions) > MAX_CANVAS_DIMS) {
logger.error("Maximum canvas size exceeded, aborting");
this.root.hud.parts.dialogs.showInfo(
// @TODO: translation (T.dialogs.exportScreenshotFail.title)
"Too large",
boundsSelected
? // @TODO: translation (T.dialogs.exportScreenshotFail.descSelection)
"The region selected is too large to render, sorry! Try selecting a smaller region."
: // @TODO: translation (T.dialogs.exportScreenshotFail.desc)
"The base is too large to render, sorry! Try selecting just a region of your base with <key>.".replace(
"<key>",
"<code class='keybinding'>" +
this.root.keyMapper
.getBinding(KEYMAPPINGS.massSelect.massSelectStart)
.getKeyCodeString() +
"</code>"
)
);
return;
}
logger.log("ChunkSizePixels:", chunkSizePixels);
const chunkScale = chunkSizePixels / globalConfig.mapChunkWorldSize;
logger.log("Scale:", chunkScale);
const zoomLevel = tileSizePixels / globalConfig.tileSize;
logger.log("Scale:", zoomLevel);
// Compute atlas scale
const lowQuality = this.root.app.settings.getAllSettings().lowQualityTextures;
const effectiveZoomLevel = (zoomLevel / globalConfig.assetsDpi) * globalConfig.assetsSharpness;
let desiredAtlasScale = "0.25";
if (effectiveZoomLevel > 0.5 && !lowQuality) {
desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
} else if (effectiveZoomLevel > 0.35 && !lowQuality) {
desiredAtlasScale = "0.5";
}
logger.log("Allocating buffer, if the factory grew too big it will crash here");
const [canvas, context] = makeOffscreenBuffer(
dimensions.x * chunkSizePixels,
dimensions.y * chunkSizePixels,
Math.round(bounds.w * tileSizePixels),
Math.round(bounds.h * tileSizePixels),
{
smooth: true,
reusable: false,
@ -78,26 +292,61 @@ export class HUDScreenshotExporter extends BaseHUDPart {
);
logger.log("Got buffer, rendering now ...");
const visibleRect = new Rectangle(
minChunk.x * globalConfig.mapChunkWorldSize,
minChunk.y * globalConfig.mapChunkWorldSize,
dimensions.x * globalConfig.mapChunkWorldSize,
dimensions.y * globalConfig.mapChunkWorldSize
);
const visibleRect = bounds.allScaled(globalConfig.tileSize);
const parameters = new DrawParameters({
context,
visibleRect,
desiredAtlasScale: 0.25,
desiredAtlasScale,
root: this.root,
zoomLevel: chunkScale,
zoomLevel: zoomLevel,
});
context.scale(chunkScale, chunkScale);
context.scale(zoomLevel, zoomLevel);
context.translate(-visibleRect.x, -visibleRect.y);
// hack but works
const currentLayer = this.root.currentLayer;
const currentAlpha = this.root.hud.parts.wiresOverlay.currentAlpha;
if (wiresLayer) {
this.root.currentLayer = "wires";
this.root.hud.parts.wiresOverlay.currentAlpha = 1;
} else {
this.root.currentLayer = "regular";
this.root.hud.parts.wiresOverlay.currentAlpha = 0;
}
this.root.systemMgr.systems.itemAcceptor.updateForScreenshot();
// Render all relevant chunks
this.root.map.drawBackground(parameters);
this.root.map.drawForeground(parameters);
this.root.signals.gameFrameStarted.dispatch();
if (overlay) {
this.root;
if (hideBackground) {
this.root.map.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlayNoBackground);
} else {
this.root.map.drawOverlay(parameters);
}
} else {
if (hideBackground) {
this.root.map.drawVisibleChunks(
parameters,
MapChunkView.prototype.drawBackgroundLayerBeltsOnly
);
} else {
this.root.map.drawBackground(parameters);
}
this.root.systemMgr.systems.belt.drawBeltItems(parameters);
this.root.map.drawForeground(parameters);
this.root.systemMgr.systems.hub.draw(parameters);
if (this.root.hud.parts.wiresOverlay) {
this.root.hud.parts.wiresOverlay.draw(parameters);
}
if (this.root.currentLayer === "wires") {
this.root.map.drawWiresForegroundLayer(parameters);
}
}
this.root.currentLayer = currentLayer;
this.root.hud.parts.wiresOverlay.currentAlpha = currentAlpha;
// Offer export
logger.log("Rendered buffer, exporting ...");

View File

@ -53,6 +53,17 @@ export class MapChunkView extends MapChunk {
systems.belt.drawChunk(parameters, this);
}
/**
* Draws only the belts of the background layer
* @param {DrawParameters} parameters
*/
drawBackgroundLayerBeltsOnly(parameters) {
const systems = this.root.systemMgr.systems;
systems.beltUnderlays.drawChunk(parameters, this);
systems.belt.drawChunk(parameters, this);
}
/**
* Draws the dynamic foreground layer
* @param {DrawParameters} parameters
@ -130,6 +141,40 @@ export class MapChunkView extends MapChunk {
}
}
/**
* Overlay with transparent background
* @param {DrawParameters} parameters
*/
drawOverlayNoBackground(parameters) {
const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
const sprite = this.root.buffers.getForKey({
key: "chunknobg@" + this.root.currentLayer,
subKey: this.renderKey,
w: overlaySize,
h: overlaySize,
dpi: 1,
redrawMethod: this.generateOverlayBufferNoBackground.bind(this),
});
const dims = globalConfig.mapChunkWorldSize;
const extrude = 0.05;
// Draw chunk "pixel" art
parameters.context.imageSmoothingEnabled = false;
drawSpriteClipped({
parameters,
sprite,
x: this.x * dims - extrude,
y: this.y * dims - extrude,
w: dims + 2 * extrude,
h: dims + 2 * extrude,
originalW: overlaySize,
originalH: overlaySize,
});
parameters.context.imageSmoothingEnabled = true;
}
/**
*
* @param {HTMLCanvasElement} canvas
@ -254,6 +299,94 @@ export class MapChunkView extends MapChunk {
}
}
/**
*
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} context
* @param {number} w
* @param {number} h
* @param {number} dpi
*/
generateOverlayBufferNoBackground(canvas, context, w, h, dpi) {
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const upperArray = this.contents[x];
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
const upperContent = upperArray[y];
if (upperContent) {
const staticComp = upperContent.components.StaticMapEntity;
const data = getBuildingDataFromCode(staticComp.code);
const metaBuilding = data.metaInstance;
const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
staticComp.rotation,
data.rotationVariant,
data.variant,
upperContent
);
if (overlayMatrix) {
context.fillStyle = metaBuilding.getSilhouetteColor(
data.variant,
data.rotationVariant
);
for (let dx = 0; dx < 3; ++dx) {
for (let dy = 0; dy < 3; ++dy) {
const isFilled = overlayMatrix[dx + dy * 3];
if (isFilled) {
context.fillRect(
x * CHUNK_OVERLAY_RES + dx,
y * CHUNK_OVERLAY_RES + dy,
1,
1
);
}
}
}
continue;
} else {
context.fillStyle = metaBuilding.getSilhouetteColor(
data.variant,
data.rotationVariant
);
context.fillRect(
x * CHUNK_OVERLAY_RES,
y * CHUNK_OVERLAY_RES,
CHUNK_OVERLAY_RES,
CHUNK_OVERLAY_RES
);
continue;
}
}
}
}
if (this.root.currentLayer === "wires") {
// Draw wires overlay
context.fillStyle = THEME.map.wires.overlayColor;
context.fillRect(0, 0, w, h);
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
const wiresArray = this.wireContents[x];
for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
const content = wiresArray[y];
if (!content) {
continue;
}
MapChunkView.drawSingleWiresOverviewTile({
context,
x: x * CHUNK_OVERLAY_RES,
y: y * CHUNK_OVERLAY_RES,
entity: content,
tileSizePixels: CHUNK_OVERLAY_RES,
});
}
}
}
}
/**
* @param {object} param0
* @param {CanvasRenderingContext2D} param0.context

View File

@ -56,6 +56,36 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
}
}
updateForScreenshot() {
// Compute how much ticks we missed
const numTicks = this.accumulatedTicksWhileInMapOverview;
const progress =
this.root.dynamicTickrate.deltaSeconds *
2 *
this.root.hubGoals.getBeltBaseSpeed() *
globalConfig.itemSpacingOnBelts * // * 2 because its only a half tile
numTicks;
// Reset accumulated ticks
this.accumulatedTicksWhileInMapOverview = 0;
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const aceptorComp = entity.components.ItemAcceptor;
const animations = aceptorComp.itemConsumptionAnimations;
// Process item consumption animations to avoid items popping from the belts
for (let animIndex = 0; animIndex < animations.length; ++animIndex) {
const anim = animations[animIndex];
anim.animProgress += progress;
if (anim.animProgress > 1) {
fastArrayDelete(animations, animIndex);
animIndex -= 1;
}
}
}
}
/**
* @param {DrawParameters} parameters
* @param {MapChunkView} chunk