mirror of
				https://github.com/tobspr/shapez.io.git
				synced 2025-06-13 13:04:03 +00:00 
			
		
		
		
	Add final shapes and upgrades until tier 20
This commit is contained in:
		
							parent
							
								
									d27e9226be
								
							
						
					
					
						commit
						4f6d9785c1
					
				@ -1,7 +1,8 @@
 | 
			
		||||
#ingame_HUD_BetaOverlay {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    @include S(top, 10px);
 | 
			
		||||
    @include S(right, 15px);
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    transform: translateX(-50%);
 | 
			
		||||
    color: $colorRedBright;
 | 
			
		||||
    @include Heading;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,10 @@
 | 
			
		||||
import { globalConfig } from "../core/config";
 | 
			
		||||
import { RandomNumberGenerator } from "../core/rng";
 | 
			
		||||
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
 | 
			
		||||
import { BasicSerializableObject, types } from "../savegame/serialization";
 | 
			
		||||
import { enumColors } from "./colors";
 | 
			
		||||
import { enumItemProcessorTypes } from "./components/item_processor";
 | 
			
		||||
import { enumAnalyticsDataSource } from "./production_analytics";
 | 
			
		||||
import { GameRoot } from "./root";
 | 
			
		||||
import { enumSubShape, ShapeDefinition } from "./shape_definition";
 | 
			
		||||
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
 | 
			
		||||
@ -18,12 +20,6 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
            level: types.uint,
 | 
			
		||||
            storedShapes: types.keyValueMap(types.uint),
 | 
			
		||||
            upgradeLevels: types.keyValueMap(types.uint),
 | 
			
		||||
 | 
			
		||||
            currentGoal: types.structured({
 | 
			
		||||
                definition: types.knownType(ShapeDefinition),
 | 
			
		||||
                required: types.uint,
 | 
			
		||||
                reward: types.nullable(types.enum(enumHubGoalRewards)),
 | 
			
		||||
            }),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -53,15 +49,7 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Compute current goal
 | 
			
		||||
        const goal = tutorialGoals[this.level - 1];
 | 
			
		||||
        if (goal) {
 | 
			
		||||
            this.currentGoal = {
 | 
			
		||||
                /** @type {ShapeDefinition} */
 | 
			
		||||
                definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(goal.shape),
 | 
			
		||||
                required: goal.required,
 | 
			
		||||
                reward: goal.reward,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        this.computeNextGoal();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -106,7 +94,7 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
            this.upgradeImprovements[key] = 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.createNextGoal();
 | 
			
		||||
        this.computeNextGoal();
 | 
			
		||||
 | 
			
		||||
        // Allow quickly switching goals in dev mode
 | 
			
		||||
        if (G_IS_DEV) {
 | 
			
		||||
@ -155,6 +143,13 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
     * Returns how much of the current goal was already delivered
 | 
			
		||||
     */
 | 
			
		||||
    getCurrentGoalDelivered() {
 | 
			
		||||
        if (this.currentGoal.throughputOnly) {
 | 
			
		||||
            return this.root.productionAnalytics.getCurrentShapeRate(
 | 
			
		||||
                enumAnalyticsDataSource.delivered,
 | 
			
		||||
                this.currentGoal.definition
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.getShapesStored(this.currentGoal.definition);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -189,9 +184,8 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
        this.root.signals.shapeDelivered.dispatch(definition);
 | 
			
		||||
 | 
			
		||||
        // Check if we have enough for the next level
 | 
			
		||||
        const targetHash = this.currentGoal.definition.getHash();
 | 
			
		||||
        if (
 | 
			
		||||
            this.storedShapes[targetHash] >= this.currentGoal.required ||
 | 
			
		||||
            this.getCurrentGoalDelivered() >= this.currentGoal.required ||
 | 
			
		||||
            (G_IS_DEV && globalConfig.debug.rewardsInstant)
 | 
			
		||||
        ) {
 | 
			
		||||
            this.onGoalCompleted();
 | 
			
		||||
@ -201,24 +195,28 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the next goal
 | 
			
		||||
     */
 | 
			
		||||
    createNextGoal() {
 | 
			
		||||
    computeNextGoal() {
 | 
			
		||||
        const storyIndex = this.level - 1;
 | 
			
		||||
        if (storyIndex < tutorialGoals.length) {
 | 
			
		||||
            const { shape, required, reward } = tutorialGoals[storyIndex];
 | 
			
		||||
            const { shape, required, reward, throughputOnly } = tutorialGoals[storyIndex];
 | 
			
		||||
            this.currentGoal = {
 | 
			
		||||
                /** @type {ShapeDefinition} */
 | 
			
		||||
                definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape),
 | 
			
		||||
                required,
 | 
			
		||||
                reward,
 | 
			
		||||
                throughputOnly,
 | 
			
		||||
            };
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const required = 4 + (this.level - 27) * 0.25;
 | 
			
		||||
 | 
			
		||||
        this.currentGoal = {
 | 
			
		||||
            /** @type {ShapeDefinition} */
 | 
			
		||||
            definition: this.createRandomShape(),
 | 
			
		||||
            required: findNiceIntegerValue(1000 + Math.pow(this.level * 2000, 0.8)),
 | 
			
		||||
            definition: this.computeFreeplayShape(this.level),
 | 
			
		||||
            required,
 | 
			
		||||
            reward: enumHubGoalRewards.no_reward_freeplay,
 | 
			
		||||
            throughputOnly: true,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -231,7 +229,7 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
 | 
			
		||||
        this.root.app.gameAnalytics.handleLevelCompleted(this.level);
 | 
			
		||||
        ++this.level;
 | 
			
		||||
        this.createNextGoal();
 | 
			
		||||
        this.computeNextGoal();
 | 
			
		||||
 | 
			
		||||
        this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward);
 | 
			
		||||
    }
 | 
			
		||||
@ -325,15 +323,85 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Picks random colors which are close to each other
 | 
			
		||||
     * @param {RandomNumberGenerator} rng
 | 
			
		||||
     */
 | 
			
		||||
    generateRandomColorSet(rng, allowUncolored = false) {
 | 
			
		||||
        const colorWheel = [
 | 
			
		||||
            enumColors.red,
 | 
			
		||||
            enumColors.yellow,
 | 
			
		||||
            enumColors.green,
 | 
			
		||||
            enumColors.cyan,
 | 
			
		||||
            enumColors.blue,
 | 
			
		||||
            enumColors.purple,
 | 
			
		||||
            enumColors.red,
 | 
			
		||||
            enumColors.yellow,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const universalColors = [enumColors.white];
 | 
			
		||||
        if (allowUncolored) {
 | 
			
		||||
            universalColors.push(enumColors.uncolored);
 | 
			
		||||
        }
 | 
			
		||||
        const index = rng.nextIntRangeInclusive(0, colorWheel.length - 3);
 | 
			
		||||
        const pickedColors = colorWheel.slice(index, index + 3);
 | 
			
		||||
        pickedColors.push(rng.choice(universalColors));
 | 
			
		||||
        return pickedColors;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a (seeded) random shape
 | 
			
		||||
     * @param {number} level
 | 
			
		||||
     * @returns {ShapeDefinition}
 | 
			
		||||
     */
 | 
			
		||||
    createRandomShape() {
 | 
			
		||||
    computeFreeplayShape(level) {
 | 
			
		||||
        const layerCount = clamp(this.level / 25, 2, 4);
 | 
			
		||||
 | 
			
		||||
        /** @type {Array<import("./shape_definition").ShapeLayer>} */
 | 
			
		||||
        let layers = [];
 | 
			
		||||
 | 
			
		||||
        const randomColor = () => randomChoice(Object.values(enumColors));
 | 
			
		||||
        const randomShape = () => randomChoice(Object.values(enumSubShape));
 | 
			
		||||
        const rng = new RandomNumberGenerator(this.root.map.seed + "/" + level);
 | 
			
		||||
 | 
			
		||||
        const colors = this.generateRandomColorSet(rng, level > 35);
 | 
			
		||||
 | 
			
		||||
        let pickedSymmetry = null; // pairs of quadrants that must be the same
 | 
			
		||||
        let availableShapes = [enumSubShape.rect, enumSubShape.circle, enumSubShape.star];
 | 
			
		||||
        if (rng.next() < 0.5) {
 | 
			
		||||
            pickedSymmetry = [
 | 
			
		||||
                // radial symmetry
 | 
			
		||||
                [0, 2],
 | 
			
		||||
                [1, 3],
 | 
			
		||||
            ];
 | 
			
		||||
            availableShapes.push(enumSubShape.windmill); // windmill looks good only in radial symmetry
 | 
			
		||||
        } else {
 | 
			
		||||
            const symmetries = [
 | 
			
		||||
                [
 | 
			
		||||
                    // horizontal axis
 | 
			
		||||
                    [0, 3],
 | 
			
		||||
                    [1, 2],
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    // vertical axis
 | 
			
		||||
                    [0, 1],
 | 
			
		||||
                    [2, 3],
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    // diagonal axis
 | 
			
		||||
                    [0, 2],
 | 
			
		||||
                    [1],
 | 
			
		||||
                    [3],
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    // other diagonal axis
 | 
			
		||||
                    [1, 3],
 | 
			
		||||
                    [0],
 | 
			
		||||
                    [2],
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
            pickedSymmetry = rng.choice(symmetries);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const randomColor = () => rng.choice(colors);
 | 
			
		||||
        const randomShape = () => rng.choice(Object.values(enumSubShape));
 | 
			
		||||
 | 
			
		||||
        let anyIsMissingTwo = false;
 | 
			
		||||
 | 
			
		||||
@ -341,23 +409,24 @@ export class HubGoals extends BasicSerializableObject {
 | 
			
		||||
            /** @type {import("./shape_definition").ShapeLayer} */
 | 
			
		||||
            const layer = [null, null, null, null];
 | 
			
		||||
 | 
			
		||||
            for (let quad = 0; quad < 4; ++quad) {
 | 
			
		||||
                layer[quad] = {
 | 
			
		||||
                    subShape: randomShape(),
 | 
			
		||||
                    color: randomColor(),
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Sometimes shapes are missing
 | 
			
		||||
            if (Math.random() > 0.85) {
 | 
			
		||||
                layer[randomInt(0, 3)] = null;
 | 
			
		||||
            for (let j = 0; j < pickedSymmetry.length; ++j) {
 | 
			
		||||
                const group = pickedSymmetry[j];
 | 
			
		||||
                const shape = randomShape();
 | 
			
		||||
                const color = randomColor();
 | 
			
		||||
                for (let k = 0; k < group.length; ++k) {
 | 
			
		||||
                    const quad = group[k];
 | 
			
		||||
                    layer[quad] = {
 | 
			
		||||
                        subShape: shape,
 | 
			
		||||
                        color,
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Sometimes they actually are missing *two* ones!
 | 
			
		||||
            // Make sure at max only one layer is missing it though, otherwise we could
 | 
			
		||||
            // create an uncreateable shape
 | 
			
		||||
            if (Math.random() > 0.95 && !anyIsMissingTwo) {
 | 
			
		||||
                layer[randomInt(0, 3)] = null;
 | 
			
		||||
            if (level > 75 && rng.next() > 0.95 && !anyIsMissingTwo) {
 | 
			
		||||
                layer[rng.nextIntRange(0, 4)] = null;
 | 
			
		||||
                anyIsMissingTwo = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,7 @@ import { HUDWireInfo } from "./parts/wire_info";
 | 
			
		||||
import { HUDLeverToggle } from "./parts/lever_toggle";
 | 
			
		||||
import { HUDLayerPreview } from "./parts/layer_preview";
 | 
			
		||||
import { HUDMinerHighlight } from "./parts/miner_highlight";
 | 
			
		||||
import { HUDBetaOverlay } from "./parts/beta_overlay";
 | 
			
		||||
 | 
			
		||||
export class GameHUD {
 | 
			
		||||
    /**
 | 
			
		||||
@ -75,7 +76,6 @@ export class GameHUD {
 | 
			
		||||
            pinnedShapes: new HUDPinnedShapes(this.root),
 | 
			
		||||
            notifications: new HUDNotifications(this.root),
 | 
			
		||||
            settingsMenu: new HUDSettingsMenu(this.root),
 | 
			
		||||
            // betaOverlay: new HUDBetaOverlay(this.root),
 | 
			
		||||
            debugInfo: new HUDDebugInfo(this.root),
 | 
			
		||||
            dialogs: new HUDModalDialogs(this.root),
 | 
			
		||||
            screenshotExporter: new HUDScreenshotExporter(this.root),
 | 
			
		||||
@ -137,6 +137,10 @@ export class GameHUD {
 | 
			
		||||
            this.parts.sandboxController = new HUDSandboxController(this.root);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!G_IS_RELEASE) {
 | 
			
		||||
            this.parts.betaOverlay = new HUDBetaOverlay(this.root);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const frag = document.createDocumentFragment();
 | 
			
		||||
        for (const key in this.parts) {
 | 
			
		||||
            this.parts[key].createElements(frag);
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import { makeDiv } from "../../../core/utils";
 | 
			
		||||
 | 
			
		||||
export class HUDBetaOverlay extends BaseHUDPart {
 | 
			
		||||
    createElements(parent) {
 | 
			
		||||
        this.element = makeDiv(parent, "ingame_HUD_BetaOverlay", [], "CLOSED BETA");
 | 
			
		||||
        this.element = makeDiv(parent, "ingame_HUD_BetaOverlay", [], "BETA VERSION");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialize() {}
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@ import { ShapeDefinition } from "../../shape_definition";
 | 
			
		||||
import { BaseHUDPart } from "../base_hud_part";
 | 
			
		||||
import { blueprintShape, UPGRADES } from "../../upgrades";
 | 
			
		||||
import { enumHubGoalRewards } from "../../tutorial_goals";
 | 
			
		||||
import { enumAnalyticsDataSource } from "../../production_analytics";
 | 
			
		||||
import { T } from "../../../translations";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages the pinned shapes on the left side of the screen
 | 
			
		||||
@ -22,11 +24,13 @@ export class HUDPinnedShapes extends BaseHUDPart {
 | 
			
		||||
         * convenient. Also allows for cleaning up handles.
 | 
			
		||||
         * @type {Array<{
 | 
			
		||||
         *  key: string,
 | 
			
		||||
         *  definition: ShapeDefinition,
 | 
			
		||||
         *  amountLabel: HTMLElement,
 | 
			
		||||
         *  lastRenderedValue: string,
 | 
			
		||||
         *  element: HTMLElement,
 | 
			
		||||
         *  detector?: ClickDetector,
 | 
			
		||||
         *  infoDetector?: ClickDetector
 | 
			
		||||
         *  infoDetector?: ClickDetector,
 | 
			
		||||
         *  throughputOnly?: boolean
 | 
			
		||||
         * }>}
 | 
			
		||||
         */
 | 
			
		||||
        this.handles = [];
 | 
			
		||||
@ -163,29 +167,40 @@ export class HUDPinnedShapes extends BaseHUDPart {
 | 
			
		||||
        this.handles = [];
 | 
			
		||||
 | 
			
		||||
        // Pin story goal
 | 
			
		||||
        this.internalPinShape(currentKey, false, "goal");
 | 
			
		||||
        this.internalPinShape({
 | 
			
		||||
            key: currentKey,
 | 
			
		||||
            canUnpin: false,
 | 
			
		||||
            className: "goal",
 | 
			
		||||
            throughputOnly: currentGoal.throughputOnly,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Pin blueprint shape as well
 | 
			
		||||
        if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
 | 
			
		||||
            this.internalPinShape(blueprintShape, false, "blueprint");
 | 
			
		||||
            this.internalPinShape({
 | 
			
		||||
                key: blueprintShape,
 | 
			
		||||
                canUnpin: false,
 | 
			
		||||
                className: "blueprint",
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Pin manually pinned shapes
 | 
			
		||||
        for (let i = 0; i < this.pinnedShapes.length; ++i) {
 | 
			
		||||
            const key = this.pinnedShapes[i];
 | 
			
		||||
            if (key !== currentKey) {
 | 
			
		||||
                this.internalPinShape(key);
 | 
			
		||||
                this.internalPinShape({ key });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Pins a new shape
 | 
			
		||||
     * @param {string} key
 | 
			
		||||
     * @param {boolean} canUnpin
 | 
			
		||||
     * @param {string=} className
 | 
			
		||||
     * @param {object} param0
 | 
			
		||||
     * @param {string} param0.key
 | 
			
		||||
     * @param {boolean=} param0.canUnpin
 | 
			
		||||
     * @param {string=} param0.className
 | 
			
		||||
     * @param {boolean=} param0.throughputOnly
 | 
			
		||||
     */
 | 
			
		||||
    internalPinShape(key, canUnpin = true, className = null) {
 | 
			
		||||
    internalPinShape({ key, canUnpin = true, className = null, throughputOnly = false }) {
 | 
			
		||||
        const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
 | 
			
		||||
 | 
			
		||||
        const element = makeDiv(this.element, null, ["shape"]);
 | 
			
		||||
@ -229,11 +244,13 @@ export class HUDPinnedShapes extends BaseHUDPart {
 | 
			
		||||
 | 
			
		||||
        this.handles.push({
 | 
			
		||||
            key,
 | 
			
		||||
            definition,
 | 
			
		||||
            element,
 | 
			
		||||
            amountLabel,
 | 
			
		||||
            lastRenderedValue: "",
 | 
			
		||||
            detector,
 | 
			
		||||
            infoDetector,
 | 
			
		||||
            throughputOnly,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -244,8 +261,20 @@ export class HUDPinnedShapes extends BaseHUDPart {
 | 
			
		||||
        for (let i = 0; i < this.handles.length; ++i) {
 | 
			
		||||
            const handle = this.handles[i];
 | 
			
		||||
 | 
			
		||||
            const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
 | 
			
		||||
            const currentValueFormatted = formatBigNumber(currentValue);
 | 
			
		||||
            let currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
 | 
			
		||||
            let currentValueFormatted = formatBigNumber(currentValue);
 | 
			
		||||
 | 
			
		||||
            if (handle.throughputOnly) {
 | 
			
		||||
                currentValue = this.root.productionAnalytics.getCurrentShapeRate(
 | 
			
		||||
                    enumAnalyticsDataSource.delivered,
 | 
			
		||||
                    handle.definition
 | 
			
		||||
                );
 | 
			
		||||
                currentValueFormatted = T.ingame.statistics.shapesDisplayUnits.second.replace(
 | 
			
		||||
                    "<shapes>",
 | 
			
		||||
                    String(currentValue)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (currentValueFormatted !== handle.lastRenderedValue) {
 | 
			
		||||
                handle.lastRenderedValue = currentValueFormatted;
 | 
			
		||||
                handle.amountLabel.innerText = currentValueFormatted;
 | 
			
		||||
 | 
			
		||||
@ -113,7 +113,7 @@ export class HUDSandboxController extends BaseHUDPart {
 | 
			
		||||
    modifyLevel(amount) {
 | 
			
		||||
        const hubGoals = this.root.hubGoals;
 | 
			
		||||
        hubGoals.level = Math.max(1, hubGoals.level + amount);
 | 
			
		||||
        hubGoals.createNextGoal();
 | 
			
		||||
        hubGoals.computeNextGoal();
 | 
			
		||||
 | 
			
		||||
        // Clear all shapes of this level
 | 
			
		||||
        hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0;
 | 
			
		||||
 | 
			
		||||
@ -90,17 +90,15 @@ export class HUDShop extends BaseHUDPart {
 | 
			
		||||
                // Max level
 | 
			
		||||
                handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace(
 | 
			
		||||
                    "<currentMult>",
 | 
			
		||||
                    currentTierMultiplier.toString()
 | 
			
		||||
                    formatBigNumber(currentTierMultiplier)
 | 
			
		||||
                );
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Set description
 | 
			
		||||
            handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description
 | 
			
		||||
                .replace("<currentMult>", currentTierMultiplier.toString())
 | 
			
		||||
                .replace("<newMult>", (currentTierMultiplier + tierHandle.improvement).toString())
 | 
			
		||||
                // Backwards compatibility
 | 
			
		||||
                .replace("<gain>", (tierHandle.improvement * 100.0).toString());
 | 
			
		||||
                .replace("<currentMult>", formatBigNumber(currentTierMultiplier))
 | 
			
		||||
                .replace("<newMult>", formatBigNumber(currentTierMultiplier + tierHandle.improvement));
 | 
			
		||||
 | 
			
		||||
            tierHandle.required.forEach(({ shape, amount }) => {
 | 
			
		||||
                const container = makeDiv(handle.elemRequirements, null, ["requirement"]);
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,12 @@ import { makeDiv } from "../../../core/utils";
 | 
			
		||||
import { SOUNDS } from "../../../platform/sound";
 | 
			
		||||
import { T } from "../../../translations";
 | 
			
		||||
import { defaultBuildingVariant } from "../../meta_building";
 | 
			
		||||
import { enumHubGoalRewards } from "../../tutorial_goals";
 | 
			
		||||
import { enumHubGoalRewards, tutorialGoals } from "../../tutorial_goals";
 | 
			
		||||
import { BaseHUDPart } from "../base_hud_part";
 | 
			
		||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
 | 
			
		||||
import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings";
 | 
			
		||||
import { InputReceiver } from "../../../core/input_receiver";
 | 
			
		||||
import { enumNotificationType } from "./notifications";
 | 
			
		||||
 | 
			
		||||
export class HUDUnlockNotification extends BaseHUDPart {
 | 
			
		||||
    initialize() {
 | 
			
		||||
@ -50,6 +51,14 @@ export class HUDUnlockNotification extends BaseHUDPart {
 | 
			
		||||
     * @param {enumHubGoalRewards} reward
 | 
			
		||||
     */
 | 
			
		||||
    showForLevel(level, reward) {
 | 
			
		||||
        if (level > tutorialGoals.length) {
 | 
			
		||||
            this.root.hud.signals.notification.dispatch(
 | 
			
		||||
                T.ingame.notifications.freeplayLevelComplete.replace("<level>", String(level)),
 | 
			
		||||
                enumNotificationType.success
 | 
			
		||||
            );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
 | 
			
		||||
        this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace(
 | 
			
		||||
            "<level>",
 | 
			
		||||
 | 
			
		||||
@ -1,172 +1,185 @@
 | 
			
		||||
import { DrawParameters } from "../../core/draw_parameters";
 | 
			
		||||
import { Loader } from "../../core/loader";
 | 
			
		||||
import { formatBigNumber } from "../../core/utils";
 | 
			
		||||
import { T } from "../../translations";
 | 
			
		||||
import { HubComponent } from "../components/hub";
 | 
			
		||||
import { Entity } from "../entity";
 | 
			
		||||
import { GameSystemWithFilter } from "../game_system_with_filter";
 | 
			
		||||
import { globalConfig } from "../../core/config";
 | 
			
		||||
import { smoothenDpi } from "../../core/dpi_manager";
 | 
			
		||||
import { drawSpriteClipped } from "../../core/draw_utils";
 | 
			
		||||
import { Rectangle } from "../../core/rectangle";
 | 
			
		||||
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
 | 
			
		||||
 | 
			
		||||
const HUB_SIZE_TILES = 4;
 | 
			
		||||
const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize;
 | 
			
		||||
 | 
			
		||||
export class HubSystem extends GameSystemWithFilter {
 | 
			
		||||
    constructor(root) {
 | 
			
		||||
        super(root, [HubComponent]);
 | 
			
		||||
 | 
			
		||||
        this.hubSprite = Loader.getSprite("sprites/buildings/hub.png");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     */
 | 
			
		||||
    draw(parameters) {
 | 
			
		||||
        for (let i = 0; i < this.allEntities.length; ++i) {
 | 
			
		||||
            this.drawEntity(parameters, this.allEntities[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update() {
 | 
			
		||||
        for (let i = 0; i < this.allEntities.length; ++i) {
 | 
			
		||||
            // Set hub goal
 | 
			
		||||
            const entity = this.allEntities[i];
 | 
			
		||||
            const pinsComp = entity.components.WiredPins;
 | 
			
		||||
            pinsComp.slots[0].value = this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
 | 
			
		||||
                this.root.hubGoals.currentGoal.definition
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param {HTMLCanvasElement} canvas
 | 
			
		||||
     * @param {CanvasRenderingContext2D} context
 | 
			
		||||
     * @param {number} w
 | 
			
		||||
     * @param {number} h
 | 
			
		||||
     * @param {number} dpi
 | 
			
		||||
     */
 | 
			
		||||
    redrawHubBaseTexture(canvas, context, w, h, dpi) {
 | 
			
		||||
        // This method is quite ugly, please ignore it!
 | 
			
		||||
 | 
			
		||||
        context.scale(dpi, dpi);
 | 
			
		||||
 | 
			
		||||
        const parameters = new DrawParameters({
 | 
			
		||||
            context,
 | 
			
		||||
            visibleRect: new Rectangle(0, 0, w, h),
 | 
			
		||||
            desiredAtlasScale: ORIGINAL_SPRITE_SCALE,
 | 
			
		||||
            zoomLevel: dpi * 0.75,
 | 
			
		||||
            root: this.root,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        context.clearRect(0, 0, w, h);
 | 
			
		||||
 | 
			
		||||
        this.hubSprite.draw(context, 0, 0, w, h);
 | 
			
		||||
 | 
			
		||||
        const definition = this.root.hubGoals.currentGoal.definition;
 | 
			
		||||
        definition.drawCentered(45, 58, parameters, 36);
 | 
			
		||||
 | 
			
		||||
        const goals = this.root.hubGoals.currentGoal;
 | 
			
		||||
 | 
			
		||||
        const textOffsetX = 70;
 | 
			
		||||
        const textOffsetY = 61;
 | 
			
		||||
 | 
			
		||||
        // Deliver count
 | 
			
		||||
        const delivered = this.root.hubGoals.getCurrentGoalDelivered();
 | 
			
		||||
        const deliveredText = "" + formatBigNumber(delivered);
 | 
			
		||||
 | 
			
		||||
        if (delivered > 9999) {
 | 
			
		||||
            context.font = "bold 16px GameFont";
 | 
			
		||||
        } else if (delivered > 999) {
 | 
			
		||||
            context.font = "bold 20px GameFont";
 | 
			
		||||
        } else {
 | 
			
		||||
            context.font = "bold 25px GameFont";
 | 
			
		||||
        }
 | 
			
		||||
        context.fillStyle = "#64666e";
 | 
			
		||||
        context.textAlign = "left";
 | 
			
		||||
        context.fillText(deliveredText, textOffsetX, textOffsetY);
 | 
			
		||||
 | 
			
		||||
        // Required
 | 
			
		||||
        context.font = "13px GameFont";
 | 
			
		||||
        context.fillStyle = "#a4a6b0";
 | 
			
		||||
        context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
 | 
			
		||||
 | 
			
		||||
        // Reward
 | 
			
		||||
        const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
 | 
			
		||||
        if (rewardText.length > 12) {
 | 
			
		||||
            context.font = "bold 8px GameFont";
 | 
			
		||||
        } else {
 | 
			
		||||
            context.font = "bold 10px GameFont";
 | 
			
		||||
        }
 | 
			
		||||
        context.fillStyle = "#fd0752";
 | 
			
		||||
        context.textAlign = "center";
 | 
			
		||||
 | 
			
		||||
        context.fillText(rewardText, HUB_SIZE_PIXELS / 2, 105);
 | 
			
		||||
 | 
			
		||||
        // Level "8"
 | 
			
		||||
        context.font = "bold 10px GameFont";
 | 
			
		||||
        context.fillStyle = "#fff";
 | 
			
		||||
        context.fillText("" + this.root.hubGoals.level, 27, 32);
 | 
			
		||||
 | 
			
		||||
        // "LVL"
 | 
			
		||||
        context.textAlign = "center";
 | 
			
		||||
        context.fillStyle = "#fff";
 | 
			
		||||
        context.font = "bold 6px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.levelShortcut, 27, 22);
 | 
			
		||||
 | 
			
		||||
        // "Deliver"
 | 
			
		||||
        context.fillStyle = "#64666e";
 | 
			
		||||
        context.font = "bold 10px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.deliver.toUpperCase(), HUB_SIZE_PIXELS / 2, 30);
 | 
			
		||||
 | 
			
		||||
        // "To unlock"
 | 
			
		||||
        const unlockText = T.buildings.hub.toUnlock.toUpperCase();
 | 
			
		||||
        if (unlockText.length > 15) {
 | 
			
		||||
            context.font = "bold 8px GameFont";
 | 
			
		||||
        } else {
 | 
			
		||||
            context.font = "bold 10px GameFont";
 | 
			
		||||
        }
 | 
			
		||||
        context.fillText(T.buildings.hub.toUnlock.toUpperCase(), HUB_SIZE_PIXELS / 2, 92);
 | 
			
		||||
 | 
			
		||||
        context.textAlign = "left";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {Entity} entity
 | 
			
		||||
     */
 | 
			
		||||
    drawEntity(parameters, entity) {
 | 
			
		||||
        const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
        if (!staticComp.shouldBeDrawn(parameters)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Deliver count
 | 
			
		||||
        const delivered = this.root.hubGoals.getCurrentGoalDelivered();
 | 
			
		||||
        const deliveredText = "" + formatBigNumber(delivered);
 | 
			
		||||
 | 
			
		||||
        const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
 | 
			
		||||
        const canvas = parameters.root.buffers.getForKey({
 | 
			
		||||
            key: "hub",
 | 
			
		||||
            subKey: dpi + "/" + this.root.hubGoals.level + "/" + deliveredText,
 | 
			
		||||
            w: globalConfig.tileSize * 4,
 | 
			
		||||
            h: globalConfig.tileSize * 4,
 | 
			
		||||
            dpi,
 | 
			
		||||
            redrawMethod: this.redrawHubBaseTexture.bind(this),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const extrude = 8;
 | 
			
		||||
        drawSpriteClipped({
 | 
			
		||||
            parameters,
 | 
			
		||||
            sprite: canvas,
 | 
			
		||||
            x: staticComp.origin.x * globalConfig.tileSize - extrude,
 | 
			
		||||
            y: staticComp.origin.y * globalConfig.tileSize - extrude,
 | 
			
		||||
            w: HUB_SIZE_PIXELS + 2 * extrude,
 | 
			
		||||
            h: HUB_SIZE_PIXELS + 2 * extrude,
 | 
			
		||||
            originalW: HUB_SIZE_PIXELS * dpi,
 | 
			
		||||
            originalH: HUB_SIZE_PIXELS * dpi,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
import { globalConfig } from "../../core/config";
 | 
			
		||||
import { smoothenDpi } from "../../core/dpi_manager";
 | 
			
		||||
import { DrawParameters } from "../../core/draw_parameters";
 | 
			
		||||
import { drawSpriteClipped } from "../../core/draw_utils";
 | 
			
		||||
import { Loader } from "../../core/loader";
 | 
			
		||||
import { Rectangle } from "../../core/rectangle";
 | 
			
		||||
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
 | 
			
		||||
import { formatBigNumber } from "../../core/utils";
 | 
			
		||||
import { T } from "../../translations";
 | 
			
		||||
import { HubComponent } from "../components/hub";
 | 
			
		||||
import { Entity } from "../entity";
 | 
			
		||||
import { GameSystemWithFilter } from "../game_system_with_filter";
 | 
			
		||||
 | 
			
		||||
const HUB_SIZE_TILES = 4;
 | 
			
		||||
const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize;
 | 
			
		||||
 | 
			
		||||
export class HubSystem extends GameSystemWithFilter {
 | 
			
		||||
    constructor(root) {
 | 
			
		||||
        super(root, [HubComponent]);
 | 
			
		||||
 | 
			
		||||
        this.hubSprite = Loader.getSprite("sprites/buildings/hub.png");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     */
 | 
			
		||||
    draw(parameters) {
 | 
			
		||||
        for (let i = 0; i < this.allEntities.length; ++i) {
 | 
			
		||||
            this.drawEntity(parameters, this.allEntities[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update() {
 | 
			
		||||
        for (let i = 0; i < this.allEntities.length; ++i) {
 | 
			
		||||
            // Set hub goal
 | 
			
		||||
            const entity = this.allEntities[i];
 | 
			
		||||
            const pinsComp = entity.components.WiredPins;
 | 
			
		||||
            pinsComp.slots[0].value = this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
 | 
			
		||||
                this.root.hubGoals.currentGoal.definition
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param {HTMLCanvasElement} canvas
 | 
			
		||||
     * @param {CanvasRenderingContext2D} context
 | 
			
		||||
     * @param {number} w
 | 
			
		||||
     * @param {number} h
 | 
			
		||||
     * @param {number} dpi
 | 
			
		||||
     */
 | 
			
		||||
    redrawHubBaseTexture(canvas, context, w, h, dpi) {
 | 
			
		||||
        // This method is quite ugly, please ignore it!
 | 
			
		||||
 | 
			
		||||
        context.scale(dpi, dpi);
 | 
			
		||||
 | 
			
		||||
        const parameters = new DrawParameters({
 | 
			
		||||
            context,
 | 
			
		||||
            visibleRect: new Rectangle(0, 0, w, h),
 | 
			
		||||
            desiredAtlasScale: ORIGINAL_SPRITE_SCALE,
 | 
			
		||||
            zoomLevel: dpi * 0.75,
 | 
			
		||||
            root: this.root,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        context.clearRect(0, 0, w, h);
 | 
			
		||||
 | 
			
		||||
        this.hubSprite.draw(context, 0, 0, w, h);
 | 
			
		||||
 | 
			
		||||
        const definition = this.root.hubGoals.currentGoal.definition;
 | 
			
		||||
        definition.drawCentered(45, 58, parameters, 36);
 | 
			
		||||
 | 
			
		||||
        const goals = this.root.hubGoals.currentGoal;
 | 
			
		||||
 | 
			
		||||
        const textOffsetX = 70;
 | 
			
		||||
        const textOffsetY = 61;
 | 
			
		||||
 | 
			
		||||
        if (goals.throughputOnly) {
 | 
			
		||||
            // Throughput
 | 
			
		||||
            const deliveredText = T.ingame.statistics.shapesDisplayUnits.second.replace(
 | 
			
		||||
                "<shapes>",
 | 
			
		||||
                formatBigNumber(goals.required)
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            context.font = "bold 12px GameFont";
 | 
			
		||||
            context.fillStyle = "#64666e";
 | 
			
		||||
            context.textAlign = "left";
 | 
			
		||||
            context.fillText(deliveredText, textOffsetX, textOffsetY);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Deliver count
 | 
			
		||||
            const delivered = this.root.hubGoals.getCurrentGoalDelivered();
 | 
			
		||||
            const deliveredText = "" + formatBigNumber(delivered);
 | 
			
		||||
 | 
			
		||||
            if (delivered > 9999) {
 | 
			
		||||
                context.font = "bold 16px GameFont";
 | 
			
		||||
            } else if (delivered > 999) {
 | 
			
		||||
                context.font = "bold 20px GameFont";
 | 
			
		||||
            } else {
 | 
			
		||||
                context.font = "bold 25px GameFont";
 | 
			
		||||
            }
 | 
			
		||||
            context.fillStyle = "#64666e";
 | 
			
		||||
            context.textAlign = "left";
 | 
			
		||||
            context.fillText(deliveredText, textOffsetX, textOffsetY);
 | 
			
		||||
 | 
			
		||||
            // Required
 | 
			
		||||
            context.font = "13px GameFont";
 | 
			
		||||
            context.fillStyle = "#a4a6b0";
 | 
			
		||||
            context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Reward
 | 
			
		||||
        const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
 | 
			
		||||
        if (rewardText.length > 12) {
 | 
			
		||||
            context.font = "bold 8px GameFont";
 | 
			
		||||
        } else {
 | 
			
		||||
            context.font = "bold 10px GameFont";
 | 
			
		||||
        }
 | 
			
		||||
        context.fillStyle = "#fd0752";
 | 
			
		||||
        context.textAlign = "center";
 | 
			
		||||
 | 
			
		||||
        context.fillText(rewardText, HUB_SIZE_PIXELS / 2, 105);
 | 
			
		||||
 | 
			
		||||
        // Level "8"
 | 
			
		||||
        context.font = "bold 10px GameFont";
 | 
			
		||||
        context.fillStyle = "#fff";
 | 
			
		||||
        context.fillText("" + this.root.hubGoals.level, 27, 32);
 | 
			
		||||
 | 
			
		||||
        // "LVL"
 | 
			
		||||
        context.textAlign = "center";
 | 
			
		||||
        context.fillStyle = "#fff";
 | 
			
		||||
        context.font = "bold 6px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.levelShortcut, 27, 22);
 | 
			
		||||
 | 
			
		||||
        // "Deliver"
 | 
			
		||||
        context.fillStyle = "#64666e";
 | 
			
		||||
        context.font = "bold 10px GameFont";
 | 
			
		||||
        context.fillText(T.buildings.hub.deliver.toUpperCase(), HUB_SIZE_PIXELS / 2, 30);
 | 
			
		||||
 | 
			
		||||
        // "To unlock"
 | 
			
		||||
        const unlockText = T.buildings.hub.toUnlock.toUpperCase();
 | 
			
		||||
        if (unlockText.length > 15) {
 | 
			
		||||
            context.font = "bold 8px GameFont";
 | 
			
		||||
        } else {
 | 
			
		||||
            context.font = "bold 10px GameFont";
 | 
			
		||||
        }
 | 
			
		||||
        context.fillText(T.buildings.hub.toUnlock.toUpperCase(), HUB_SIZE_PIXELS / 2, 92);
 | 
			
		||||
 | 
			
		||||
        context.textAlign = "left";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {DrawParameters} parameters
 | 
			
		||||
     * @param {Entity} entity
 | 
			
		||||
     */
 | 
			
		||||
    drawEntity(parameters, entity) {
 | 
			
		||||
        const staticComp = entity.components.StaticMapEntity;
 | 
			
		||||
        if (!staticComp.shouldBeDrawn(parameters)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Deliver count
 | 
			
		||||
        const delivered = this.root.hubGoals.getCurrentGoalDelivered();
 | 
			
		||||
        const deliveredText = "" + formatBigNumber(delivered);
 | 
			
		||||
 | 
			
		||||
        const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
 | 
			
		||||
        const canvas = parameters.root.buffers.getForKey({
 | 
			
		||||
            key: "hub",
 | 
			
		||||
            subKey: dpi + "/" + this.root.hubGoals.level + "/" + deliveredText,
 | 
			
		||||
            w: globalConfig.tileSize * 4,
 | 
			
		||||
            h: globalConfig.tileSize * 4,
 | 
			
		||||
            dpi,
 | 
			
		||||
            redrawMethod: this.redrawHubBaseTexture.bind(this),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const extrude = 8;
 | 
			
		||||
        drawSpriteClipped({
 | 
			
		||||
            parameters,
 | 
			
		||||
            sprite: canvas,
 | 
			
		||||
            x: staticComp.origin.x * globalConfig.tileSize - extrude,
 | 
			
		||||
            y: staticComp.origin.y * globalConfig.tileSize - extrude,
 | 
			
		||||
            w: HUB_SIZE_PIXELS + 2 * extrude,
 | 
			
		||||
            h: HUB_SIZE_PIXELS + 2 * extrude,
 | 
			
		||||
            originalW: HUB_SIZE_PIXELS * dpi,
 | 
			
		||||
            originalH: HUB_SIZE_PIXELS * dpi,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -143,10 +143,10 @@ export const tutorialGoals = [
 | 
			
		||||
    // 14
 | 
			
		||||
    // Belt reader
 | 
			
		||||
    {
 | 
			
		||||
        // @todo
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        required: 0,
 | 
			
		||||
        shape: "--Cg----:--Cr----", // unused
 | 
			
		||||
        required: 16, // Per second!
 | 
			
		||||
        reward: enumHubGoalRewards.reward_belt_reader,
 | 
			
		||||
        throughputOnly: true,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 15
 | 
			
		||||
@ -176,8 +176,7 @@ export const tutorialGoals = [
 | 
			
		||||
    // 18
 | 
			
		||||
    // Rotater (180deg)
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused
 | 
			
		||||
        required: 20000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_rotater_180,
 | 
			
		||||
    },
 | 
			
		||||
@ -185,8 +184,7 @@ export const tutorialGoals = [
 | 
			
		||||
    // 19
 | 
			
		||||
    // Compact splitter
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        shape: "CpRpCp--:SwSwSwSw",
 | 
			
		||||
        required: 25000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_splitter,
 | 
			
		||||
    },
 | 
			
		||||
@ -202,8 +200,7 @@ export const tutorialGoals = [
 | 
			
		||||
    // 21
 | 
			
		||||
    // Display
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        shape: "CrCrCrCr:CwCwCwCw:CrCrCrCr:CwCwCwCw",
 | 
			
		||||
        required: 25000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_display,
 | 
			
		||||
    },
 | 
			
		||||
@ -211,43 +208,37 @@ export const tutorialGoals = [
 | 
			
		||||
    // 22
 | 
			
		||||
    // Constant signal
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        required: 30000,
 | 
			
		||||
        shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
 | 
			
		||||
        required: 25000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_constant_signal,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 23
 | 
			
		||||
    // Quad Painter
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        // shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
 | 
			
		||||
        required: 35000,
 | 
			
		||||
        shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
 | 
			
		||||
        required: 5000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_painter_quad,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 24 Logic gates
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        required: 40000,
 | 
			
		||||
        shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
 | 
			
		||||
        required: 10000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_logic_gates,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 25 Virtual Processing
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        required: 45000,
 | 
			
		||||
        shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg",
 | 
			
		||||
        required: 10000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_virtual_processing,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 26 Freeplay
 | 
			
		||||
    {
 | 
			
		||||
        // @TODO
 | 
			
		||||
        shape: "CuCuCuCu",
 | 
			
		||||
        required: 100000,
 | 
			
		||||
        shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw",
 | 
			
		||||
        required: 10000,
 | 
			
		||||
        reward: enumHubGoalRewards.reward_freeplay,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,28 @@ import { findNiceIntegerValue } from "../core/utils";
 | 
			
		||||
import { ShapeDefinition } from "./shape_definition";
 | 
			
		||||
 | 
			
		||||
export const finalGameShape = "RuCw--Cw:----Ru--";
 | 
			
		||||
export const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
 | 
			
		||||
export const blueprintShape = "CbCbCbRb:CwCwCwCw";
 | 
			
		||||
 | 
			
		||||
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 2];
 | 
			
		||||
 | 
			
		||||
const numEndgameUpgrades = G_IS_DEV || G_IS_STANDALONE ? 20 - fixedImprovements.length - 1 : 0;
 | 
			
		||||
 | 
			
		||||
function generateEndgameUpgrades() {
 | 
			
		||||
    return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({
 | 
			
		||||
        required: [
 | 
			
		||||
            { shape: blueprintShape, amount: 30000 + i * 10000 },
 | 
			
		||||
            { shape: finalGameShape, amount: 20000 + i * 5000 },
 | 
			
		||||
            { shape: rocketShape, amount: 20000 + i * 5000 },
 | 
			
		||||
        ],
 | 
			
		||||
        excludePrevious: true,
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
for (let i = 0; i < numEndgameUpgrades; ++i) {
 | 
			
		||||
    fixedImprovements.push(0.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** @typedef {{
 | 
			
		||||
 *   shape: string,
 | 
			
		||||
 *   amount: number
 | 
			
		||||
@ -41,6 +59,7 @@ export const UPGRADES = {
 | 
			
		||||
            required: [{ shape: finalGameShape, amount: 50000 }],
 | 
			
		||||
            excludePrevious: true,
 | 
			
		||||
        },
 | 
			
		||||
        ...generateEndgameUpgrades(),
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    miner: [
 | 
			
		||||
@ -63,6 +82,7 @@ export const UPGRADES = {
 | 
			
		||||
            required: [{ shape: finalGameShape, amount: 50000 }],
 | 
			
		||||
            excludePrevious: true,
 | 
			
		||||
        },
 | 
			
		||||
        ...generateEndgameUpgrades(),
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    processors: [
 | 
			
		||||
@ -85,6 +105,7 @@ export const UPGRADES = {
 | 
			
		||||
            required: [{ shape: finalGameShape, amount: 50000 }],
 | 
			
		||||
            excludePrevious: true,
 | 
			
		||||
        },
 | 
			
		||||
        ...generateEndgameUpgrades(),
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    painting: [
 | 
			
		||||
@ -107,6 +128,7 @@ export const UPGRADES = {
 | 
			
		||||
            required: [{ shape: finalGameShape, amount: 50000 }],
 | 
			
		||||
            excludePrevious: true,
 | 
			
		||||
        },
 | 
			
		||||
        ...generateEndgameUpgrades(),
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -350,6 +350,7 @@ ingame:
 | 
			
		||||
    notifications:
 | 
			
		||||
        newUpgrade: A new upgrade is available!
 | 
			
		||||
        gameSaved: Your game has been saved.
 | 
			
		||||
        freeplayLevelComplete: Level <level> has been completed!
 | 
			
		||||
 | 
			
		||||
    # The "Upgrades" window
 | 
			
		||||
    shop:
 | 
			
		||||
@ -360,7 +361,8 @@ ingame:
 | 
			
		||||
        tier: Tier <x>
 | 
			
		||||
 | 
			
		||||
        # The roman number for each tier
 | 
			
		||||
        tierLabels: [I, II, III, IV, V, VI, VII, VIII, IX, X]
 | 
			
		||||
        tierLabels:
 | 
			
		||||
            [I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX]
 | 
			
		||||
 | 
			
		||||
        maximumLevel: MAXIMUM LEVEL (Speed x<currentMult>)
 | 
			
		||||
 | 
			
		||||
@ -788,13 +790,13 @@ storyRewards:
 | 
			
		||||
    no_reward_freeplay:
 | 
			
		||||
        title: Next level
 | 
			
		||||
        desc: >-
 | 
			
		||||
            Congratulations! By the way, more content is planned for the standalone!
 | 
			
		||||
            Congratulations!
 | 
			
		||||
 | 
			
		||||
    reward_freeplay:
 | 
			
		||||
        title: Freeplay
 | 
			
		||||
        desc: >-
 | 
			
		||||
            You did it! You unlocked the <strong>free-play mode</strong>! This means that shapes are now <strong>randomly</strong> generated!<br><br>
 | 
			
		||||
            Since the hub will only require low quantities from now on, I highly recommend to build a machine which automatically delivers the requested shape!<br><br>
 | 
			
		||||
            Since the hub will require a <strong>throughput</strong> from now on, I highly recommend to build a machine which automatically delivers the requested shape!<br><br>
 | 
			
		||||
            The HUB outputs the requested shape on the wires layer, so all you have to do is to analyze it and automatically configure your factory based on that.
 | 
			
		||||
 | 
			
		||||
settings:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user