Add final shapes and upgrades until tier 20

This commit is contained in:
tobspr 2020-09-28 10:54:21 +02:00
parent d27e9226be
commit 4f6d9785c1
12 changed files with 396 additions and 258 deletions

View File

@ -1,7 +1,8 @@
#ingame_HUD_BetaOverlay { #ingame_HUD_BetaOverlay {
position: fixed; position: fixed;
@include S(top, 10px); @include S(top, 10px);
@include S(right, 15px); left: 50%;
transform: translateX(-50%);
color: $colorRedBright; color: $colorRedBright;
@include Heading; @include Heading;
text-transform: uppercase; text-transform: uppercase;

View File

@ -1,8 +1,10 @@
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { RandomNumberGenerator } from "../core/rng";
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils"; import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
import { BasicSerializableObject, types } from "../savegame/serialization"; import { BasicSerializableObject, types } from "../savegame/serialization";
import { enumColors } from "./colors"; import { enumColors } from "./colors";
import { enumItemProcessorTypes } from "./components/item_processor"; import { enumItemProcessorTypes } from "./components/item_processor";
import { enumAnalyticsDataSource } from "./production_analytics";
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { enumSubShape, ShapeDefinition } from "./shape_definition"; import { enumSubShape, ShapeDefinition } from "./shape_definition";
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals"; import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
@ -18,12 +20,6 @@ export class HubGoals extends BasicSerializableObject {
level: types.uint, level: types.uint,
storedShapes: types.keyValueMap(types.uint), storedShapes: types.keyValueMap(types.uint),
upgradeLevels: 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 // Compute current goal
const goal = tutorialGoals[this.level - 1]; this.computeNextGoal();
if (goal) {
this.currentGoal = {
/** @type {ShapeDefinition} */
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(goal.shape),
required: goal.required,
reward: goal.reward,
};
}
} }
/** /**
@ -106,7 +94,7 @@ export class HubGoals extends BasicSerializableObject {
this.upgradeImprovements[key] = 1; this.upgradeImprovements[key] = 1;
} }
this.createNextGoal(); this.computeNextGoal();
// Allow quickly switching goals in dev mode // Allow quickly switching goals in dev mode
if (G_IS_DEV) { if (G_IS_DEV) {
@ -155,6 +143,13 @@ export class HubGoals extends BasicSerializableObject {
* Returns how much of the current goal was already delivered * Returns how much of the current goal was already delivered
*/ */
getCurrentGoalDelivered() { getCurrentGoalDelivered() {
if (this.currentGoal.throughputOnly) {
return this.root.productionAnalytics.getCurrentShapeRate(
enumAnalyticsDataSource.delivered,
this.currentGoal.definition
);
}
return this.getShapesStored(this.currentGoal.definition); return this.getShapesStored(this.currentGoal.definition);
} }
@ -189,9 +184,8 @@ export class HubGoals extends BasicSerializableObject {
this.root.signals.shapeDelivered.dispatch(definition); this.root.signals.shapeDelivered.dispatch(definition);
// Check if we have enough for the next level // Check if we have enough for the next level
const targetHash = this.currentGoal.definition.getHash();
if ( if (
this.storedShapes[targetHash] >= this.currentGoal.required || this.getCurrentGoalDelivered() >= this.currentGoal.required ||
(G_IS_DEV && globalConfig.debug.rewardsInstant) (G_IS_DEV && globalConfig.debug.rewardsInstant)
) { ) {
this.onGoalCompleted(); this.onGoalCompleted();
@ -201,24 +195,28 @@ export class HubGoals extends BasicSerializableObject {
/** /**
* Creates the next goal * Creates the next goal
*/ */
createNextGoal() { computeNextGoal() {
const storyIndex = this.level - 1; const storyIndex = this.level - 1;
if (storyIndex < tutorialGoals.length) { if (storyIndex < tutorialGoals.length) {
const { shape, required, reward } = tutorialGoals[storyIndex]; const { shape, required, reward, throughputOnly } = tutorialGoals[storyIndex];
this.currentGoal = { this.currentGoal = {
/** @type {ShapeDefinition} */ /** @type {ShapeDefinition} */
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape), definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape),
required, required,
reward, reward,
throughputOnly,
}; };
return; return;
} }
const required = 4 + (this.level - 27) * 0.25;
this.currentGoal = { this.currentGoal = {
/** @type {ShapeDefinition} */ /** @type {ShapeDefinition} */
definition: this.createRandomShape(), definition: this.computeFreeplayShape(this.level),
required: findNiceIntegerValue(1000 + Math.pow(this.level * 2000, 0.8)), required,
reward: enumHubGoalRewards.no_reward_freeplay, reward: enumHubGoalRewards.no_reward_freeplay,
throughputOnly: true,
}; };
} }
@ -231,7 +229,7 @@ export class HubGoals extends BasicSerializableObject {
this.root.app.gameAnalytics.handleLevelCompleted(this.level); this.root.app.gameAnalytics.handleLevelCompleted(this.level);
++this.level; ++this.level;
this.createNextGoal(); this.computeNextGoal();
this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward); 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} * @returns {ShapeDefinition}
*/ */
createRandomShape() { computeFreeplayShape(level) {
const layerCount = clamp(this.level / 25, 2, 4); const layerCount = clamp(this.level / 25, 2, 4);
/** @type {Array<import("./shape_definition").ShapeLayer>} */ /** @type {Array<import("./shape_definition").ShapeLayer>} */
let layers = []; let layers = [];
const randomColor = () => randomChoice(Object.values(enumColors)); const rng = new RandomNumberGenerator(this.root.map.seed + "/" + level);
const randomShape = () => randomChoice(Object.values(enumSubShape));
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; let anyIsMissingTwo = false;
@ -341,23 +409,24 @@ export class HubGoals extends BasicSerializableObject {
/** @type {import("./shape_definition").ShapeLayer} */ /** @type {import("./shape_definition").ShapeLayer} */
const layer = [null, null, null, null]; const layer = [null, null, null, null];
for (let quad = 0; quad < 4; ++quad) { for (let j = 0; j < pickedSymmetry.length; ++j) {
layer[quad] = { const group = pickedSymmetry[j];
subShape: randomShape(), const shape = randomShape();
color: randomColor(), const color = randomColor();
}; for (let k = 0; k < group.length; ++k) {
} const quad = group[k];
layer[quad] = {
// Sometimes shapes are missing subShape: shape,
if (Math.random() > 0.85) { color,
layer[randomInt(0, 3)] = null; };
}
} }
// Sometimes they actually are missing *two* ones! // Sometimes they actually are missing *two* ones!
// Make sure at max only one layer is missing it though, otherwise we could // Make sure at max only one layer is missing it though, otherwise we could
// create an uncreateable shape // create an uncreateable shape
if (Math.random() > 0.95 && !anyIsMissingTwo) { if (level > 75 && rng.next() > 0.95 && !anyIsMissingTwo) {
layer[randomInt(0, 3)] = null; layer[rng.nextIntRange(0, 4)] = null;
anyIsMissingTwo = true; anyIsMissingTwo = true;
} }

View File

@ -44,6 +44,7 @@ import { HUDWireInfo } from "./parts/wire_info";
import { HUDLeverToggle } from "./parts/lever_toggle"; import { HUDLeverToggle } from "./parts/lever_toggle";
import { HUDLayerPreview } from "./parts/layer_preview"; import { HUDLayerPreview } from "./parts/layer_preview";
import { HUDMinerHighlight } from "./parts/miner_highlight"; import { HUDMinerHighlight } from "./parts/miner_highlight";
import { HUDBetaOverlay } from "./parts/beta_overlay";
export class GameHUD { export class GameHUD {
/** /**
@ -75,7 +76,6 @@ export class GameHUD {
pinnedShapes: new HUDPinnedShapes(this.root), pinnedShapes: new HUDPinnedShapes(this.root),
notifications: new HUDNotifications(this.root), notifications: new HUDNotifications(this.root),
settingsMenu: new HUDSettingsMenu(this.root), settingsMenu: new HUDSettingsMenu(this.root),
// betaOverlay: new HUDBetaOverlay(this.root),
debugInfo: new HUDDebugInfo(this.root), debugInfo: new HUDDebugInfo(this.root),
dialogs: new HUDModalDialogs(this.root), dialogs: new HUDModalDialogs(this.root),
screenshotExporter: new HUDScreenshotExporter(this.root), screenshotExporter: new HUDScreenshotExporter(this.root),
@ -137,6 +137,10 @@ export class GameHUD {
this.parts.sandboxController = new HUDSandboxController(this.root); this.parts.sandboxController = new HUDSandboxController(this.root);
} }
if (!G_IS_RELEASE) {
this.parts.betaOverlay = new HUDBetaOverlay(this.root);
}
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
for (const key in this.parts) { for (const key in this.parts) {
this.parts[key].createElements(frag); this.parts[key].createElements(frag);

View File

@ -3,7 +3,7 @@ import { makeDiv } from "../../../core/utils";
export class HUDBetaOverlay extends BaseHUDPart { export class HUDBetaOverlay extends BaseHUDPart {
createElements(parent) { createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_BetaOverlay", [], "CLOSED BETA"); this.element = makeDiv(parent, "ingame_HUD_BetaOverlay", [], "BETA VERSION");
} }
initialize() {} initialize() {}

View File

@ -4,6 +4,8 @@ import { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { blueprintShape, UPGRADES } from "../../upgrades"; import { blueprintShape, UPGRADES } from "../../upgrades";
import { enumHubGoalRewards } from "../../tutorial_goals"; 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 * 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. * convenient. Also allows for cleaning up handles.
* @type {Array<{ * @type {Array<{
* key: string, * key: string,
* definition: ShapeDefinition,
* amountLabel: HTMLElement, * amountLabel: HTMLElement,
* lastRenderedValue: string, * lastRenderedValue: string,
* element: HTMLElement, * element: HTMLElement,
* detector?: ClickDetector, * detector?: ClickDetector,
* infoDetector?: ClickDetector * infoDetector?: ClickDetector,
* throughputOnly?: boolean
* }>} * }>}
*/ */
this.handles = []; this.handles = [];
@ -163,29 +167,40 @@ export class HUDPinnedShapes extends BaseHUDPart {
this.handles = []; this.handles = [];
// Pin story goal // Pin story goal
this.internalPinShape(currentKey, false, "goal"); this.internalPinShape({
key: currentKey,
canUnpin: false,
className: "goal",
throughputOnly: currentGoal.throughputOnly,
});
// Pin blueprint shape as well // Pin blueprint shape as well
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) { 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 // Pin manually pinned shapes
for (let i = 0; i < this.pinnedShapes.length; ++i) { for (let i = 0; i < this.pinnedShapes.length; ++i) {
const key = this.pinnedShapes[i]; const key = this.pinnedShapes[i];
if (key !== currentKey) { if (key !== currentKey) {
this.internalPinShape(key); this.internalPinShape({ key });
} }
} }
} }
/** /**
* Pins a new shape * Pins a new shape
* @param {string} key * @param {object} param0
* @param {boolean} canUnpin * @param {string} param0.key
* @param {string=} className * @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 definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
const element = makeDiv(this.element, null, ["shape"]); const element = makeDiv(this.element, null, ["shape"]);
@ -229,11 +244,13 @@ export class HUDPinnedShapes extends BaseHUDPart {
this.handles.push({ this.handles.push({
key, key,
definition,
element, element,
amountLabel, amountLabel,
lastRenderedValue: "", lastRenderedValue: "",
detector, detector,
infoDetector, infoDetector,
throughputOnly,
}); });
} }
@ -244,8 +261,20 @@ export class HUDPinnedShapes extends BaseHUDPart {
for (let i = 0; i < this.handles.length; ++i) { for (let i = 0; i < this.handles.length; ++i) {
const handle = this.handles[i]; const handle = this.handles[i];
const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key); let currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
const currentValueFormatted = formatBigNumber(currentValue); 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) { if (currentValueFormatted !== handle.lastRenderedValue) {
handle.lastRenderedValue = currentValueFormatted; handle.lastRenderedValue = currentValueFormatted;
handle.amountLabel.innerText = currentValueFormatted; handle.amountLabel.innerText = currentValueFormatted;

View File

@ -113,7 +113,7 @@ export class HUDSandboxController extends BaseHUDPart {
modifyLevel(amount) { modifyLevel(amount) {
const hubGoals = this.root.hubGoals; const hubGoals = this.root.hubGoals;
hubGoals.level = Math.max(1, hubGoals.level + amount); hubGoals.level = Math.max(1, hubGoals.level + amount);
hubGoals.createNextGoal(); hubGoals.computeNextGoal();
// Clear all shapes of this level // Clear all shapes of this level
hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0; hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0;

View File

@ -90,17 +90,15 @@ export class HUDShop extends BaseHUDPart {
// Max level // Max level
handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace( handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace(
"<currentMult>", "<currentMult>",
currentTierMultiplier.toString() formatBigNumber(currentTierMultiplier)
); );
continue; continue;
} }
// Set description // Set description
handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description
.replace("<currentMult>", currentTierMultiplier.toString()) .replace("<currentMult>", formatBigNumber(currentTierMultiplier))
.replace("<newMult>", (currentTierMultiplier + tierHandle.improvement).toString()) .replace("<newMult>", formatBigNumber(currentTierMultiplier + tierHandle.improvement));
// Backwards compatibility
.replace("<gain>", (tierHandle.improvement * 100.0).toString());
tierHandle.required.forEach(({ shape, amount }) => { tierHandle.required.forEach(({ shape, amount }) => {
const container = makeDiv(handle.elemRequirements, null, ["requirement"]); const container = makeDiv(handle.elemRequirements, null, ["requirement"]);

View File

@ -4,11 +4,12 @@ import { makeDiv } from "../../../core/utils";
import { SOUNDS } from "../../../platform/sound"; import { SOUNDS } from "../../../platform/sound";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { defaultBuildingVariant } from "../../meta_building"; import { defaultBuildingVariant } from "../../meta_building";
import { enumHubGoalRewards } from "../../tutorial_goals"; import { enumHubGoalRewards, tutorialGoals } from "../../tutorial_goals";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach"; import { DynamicDomAttach } from "../dynamic_dom_attach";
import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings"; import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings";
import { InputReceiver } from "../../../core/input_receiver"; import { InputReceiver } from "../../../core/input_receiver";
import { enumNotificationType } from "./notifications";
export class HUDUnlockNotification extends BaseHUDPart { export class HUDUnlockNotification extends BaseHUDPart {
initialize() { initialize() {
@ -50,6 +51,14 @@ export class HUDUnlockNotification extends BaseHUDPart {
* @param {enumHubGoalRewards} reward * @param {enumHubGoalRewards} reward
*/ */
showForLevel(level, 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.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace( this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace(
"<level>", "<level>",

View File

@ -1,172 +1,185 @@
import { DrawParameters } from "../../core/draw_parameters"; import { globalConfig } from "../../core/config";
import { Loader } from "../../core/loader"; import { smoothenDpi } from "../../core/dpi_manager";
import { formatBigNumber } from "../../core/utils"; import { DrawParameters } from "../../core/draw_parameters";
import { T } from "../../translations"; import { drawSpriteClipped } from "../../core/draw_utils";
import { HubComponent } from "../components/hub"; import { Loader } from "../../core/loader";
import { Entity } from "../entity"; import { Rectangle } from "../../core/rectangle";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
import { globalConfig } from "../../core/config"; import { formatBigNumber } from "../../core/utils";
import { smoothenDpi } from "../../core/dpi_manager"; import { T } from "../../translations";
import { drawSpriteClipped } from "../../core/draw_utils"; import { HubComponent } from "../components/hub";
import { Rectangle } from "../../core/rectangle"; import { Entity } from "../entity";
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites"; import { GameSystemWithFilter } from "../game_system_with_filter";
const HUB_SIZE_TILES = 4; const HUB_SIZE_TILES = 4;
const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize; const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize;
export class HubSystem extends GameSystemWithFilter { export class HubSystem extends GameSystemWithFilter {
constructor(root) { constructor(root) {
super(root, [HubComponent]); super(root, [HubComponent]);
this.hubSprite = Loader.getSprite("sprites/buildings/hub.png"); this.hubSprite = Loader.getSprite("sprites/buildings/hub.png");
} }
/** /**
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
*/ */
draw(parameters) { draw(parameters) {
for (let i = 0; i < this.allEntities.length; ++i) { for (let i = 0; i < this.allEntities.length; ++i) {
this.drawEntity(parameters, this.allEntities[i]); this.drawEntity(parameters, this.allEntities[i]);
} }
} }
update() { update() {
for (let i = 0; i < this.allEntities.length; ++i) { for (let i = 0; i < this.allEntities.length; ++i) {
// Set hub goal // Set hub goal
const entity = this.allEntities[i]; const entity = this.allEntities[i];
const pinsComp = entity.components.WiredPins; const pinsComp = entity.components.WiredPins;
pinsComp.slots[0].value = this.root.shapeDefinitionMgr.getShapeItemFromDefinition( pinsComp.slots[0].value = this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
this.root.hubGoals.currentGoal.definition this.root.hubGoals.currentGoal.definition
); );
} }
} }
/** /**
* *
* @param {HTMLCanvasElement} canvas * @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} context * @param {CanvasRenderingContext2D} context
* @param {number} w * @param {number} w
* @param {number} h * @param {number} h
* @param {number} dpi * @param {number} dpi
*/ */
redrawHubBaseTexture(canvas, context, w, h, dpi) { redrawHubBaseTexture(canvas, context, w, h, dpi) {
// This method is quite ugly, please ignore it! // This method is quite ugly, please ignore it!
context.scale(dpi, dpi); context.scale(dpi, dpi);
const parameters = new DrawParameters({ const parameters = new DrawParameters({
context, context,
visibleRect: new Rectangle(0, 0, w, h), visibleRect: new Rectangle(0, 0, w, h),
desiredAtlasScale: ORIGINAL_SPRITE_SCALE, desiredAtlasScale: ORIGINAL_SPRITE_SCALE,
zoomLevel: dpi * 0.75, zoomLevel: dpi * 0.75,
root: this.root, root: this.root,
}); });
context.clearRect(0, 0, w, h); context.clearRect(0, 0, w, h);
this.hubSprite.draw(context, 0, 0, w, h); this.hubSprite.draw(context, 0, 0, w, h);
const definition = this.root.hubGoals.currentGoal.definition; const definition = this.root.hubGoals.currentGoal.definition;
definition.drawCentered(45, 58, parameters, 36); definition.drawCentered(45, 58, parameters, 36);
const goals = this.root.hubGoals.currentGoal; const goals = this.root.hubGoals.currentGoal;
const textOffsetX = 70; const textOffsetX = 70;
const textOffsetY = 61; const textOffsetY = 61;
// Deliver count if (goals.throughputOnly) {
const delivered = this.root.hubGoals.getCurrentGoalDelivered(); // Throughput
const deliveredText = "" + formatBigNumber(delivered); const deliveredText = T.ingame.statistics.shapesDisplayUnits.second.replace(
"<shapes>",
if (delivered > 9999) { formatBigNumber(goals.required)
context.font = "bold 16px GameFont"; );
} else if (delivered > 999) {
context.font = "bold 20px GameFont"; context.font = "bold 12px GameFont";
} else { context.fillStyle = "#64666e";
context.font = "bold 25px GameFont"; context.textAlign = "left";
} context.fillText(deliveredText, textOffsetX, textOffsetY);
context.fillStyle = "#64666e"; } else {
context.textAlign = "left"; // Deliver count
context.fillText(deliveredText, textOffsetX, textOffsetY); const delivered = this.root.hubGoals.getCurrentGoalDelivered();
const deliveredText = "" + formatBigNumber(delivered);
// Required
context.font = "13px GameFont"; if (delivered > 9999) {
context.fillStyle = "#a4a6b0"; context.font = "bold 16px GameFont";
context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13); } else if (delivered > 999) {
context.font = "bold 20px GameFont";
// Reward } else {
const rewardText = T.storyRewards[goals.reward].title.toUpperCase(); context.font = "bold 25px GameFont";
if (rewardText.length > 12) { }
context.font = "bold 8px GameFont"; context.fillStyle = "#64666e";
} else { context.textAlign = "left";
context.font = "bold 10px GameFont"; context.fillText(deliveredText, textOffsetX, textOffsetY);
}
context.fillStyle = "#fd0752"; // Required
context.textAlign = "center"; context.font = "13px GameFont";
context.fillStyle = "#a4a6b0";
context.fillText(rewardText, HUB_SIZE_PIXELS / 2, 105); context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
}
// Level "8"
context.font = "bold 10px GameFont"; // Reward
context.fillStyle = "#fff"; const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
context.fillText("" + this.root.hubGoals.level, 27, 32); if (rewardText.length > 12) {
context.font = "bold 8px GameFont";
// "LVL" } else {
context.textAlign = "center"; context.font = "bold 10px GameFont";
context.fillStyle = "#fff"; }
context.font = "bold 6px GameFont"; context.fillStyle = "#fd0752";
context.fillText(T.buildings.hub.levelShortcut, 27, 22); context.textAlign = "center";
// "Deliver" context.fillText(rewardText, HUB_SIZE_PIXELS / 2, 105);
context.fillStyle = "#64666e";
context.font = "bold 10px GameFont"; // Level "8"
context.fillText(T.buildings.hub.deliver.toUpperCase(), HUB_SIZE_PIXELS / 2, 30); context.font = "bold 10px GameFont";
context.fillStyle = "#fff";
// "To unlock" context.fillText("" + this.root.hubGoals.level, 27, 32);
const unlockText = T.buildings.hub.toUnlock.toUpperCase();
if (unlockText.length > 15) { // "LVL"
context.font = "bold 8px GameFont"; context.textAlign = "center";
} else { context.fillStyle = "#fff";
context.font = "bold 10px GameFont"; context.font = "bold 6px GameFont";
} context.fillText(T.buildings.hub.levelShortcut, 27, 22);
context.fillText(T.buildings.hub.toUnlock.toUpperCase(), HUB_SIZE_PIXELS / 2, 92);
// "Deliver"
context.textAlign = "left"; context.fillStyle = "#64666e";
} context.font = "bold 10px GameFont";
context.fillText(T.buildings.hub.deliver.toUpperCase(), HUB_SIZE_PIXELS / 2, 30);
/**
* @param {DrawParameters} parameters // "To unlock"
* @param {Entity} entity const unlockText = T.buildings.hub.toUnlock.toUpperCase();
*/ if (unlockText.length > 15) {
drawEntity(parameters, entity) { context.font = "bold 8px GameFont";
const staticComp = entity.components.StaticMapEntity; } else {
if (!staticComp.shouldBeDrawn(parameters)) { context.font = "bold 10px GameFont";
return; }
} context.fillText(T.buildings.hub.toUnlock.toUpperCase(), HUB_SIZE_PIXELS / 2, 92);
// Deliver count context.textAlign = "left";
const delivered = this.root.hubGoals.getCurrentGoalDelivered(); }
const deliveredText = "" + formatBigNumber(delivered);
/**
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel); * @param {DrawParameters} parameters
const canvas = parameters.root.buffers.getForKey({ * @param {Entity} entity
key: "hub", */
subKey: dpi + "/" + this.root.hubGoals.level + "/" + deliveredText, drawEntity(parameters, entity) {
w: globalConfig.tileSize * 4, const staticComp = entity.components.StaticMapEntity;
h: globalConfig.tileSize * 4, if (!staticComp.shouldBeDrawn(parameters)) {
dpi, return;
redrawMethod: this.redrawHubBaseTexture.bind(this), }
});
// Deliver count
const extrude = 8; const delivered = this.root.hubGoals.getCurrentGoalDelivered();
drawSpriteClipped({ const deliveredText = "" + formatBigNumber(delivered);
parameters,
sprite: canvas, const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
x: staticComp.origin.x * globalConfig.tileSize - extrude, const canvas = parameters.root.buffers.getForKey({
y: staticComp.origin.y * globalConfig.tileSize - extrude, key: "hub",
w: HUB_SIZE_PIXELS + 2 * extrude, subKey: dpi + "/" + this.root.hubGoals.level + "/" + deliveredText,
h: HUB_SIZE_PIXELS + 2 * extrude, w: globalConfig.tileSize * 4,
originalW: HUB_SIZE_PIXELS * dpi, h: globalConfig.tileSize * 4,
originalH: HUB_SIZE_PIXELS * dpi, 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,
});
}
}

View File

@ -143,10 +143,10 @@ export const tutorialGoals = [
// 14 // 14
// Belt reader // Belt reader
{ {
// @todo shape: "--Cg----:--Cr----", // unused
shape: "CuCuCuCu", required: 16, // Per second!
required: 0,
reward: enumHubGoalRewards.reward_belt_reader, reward: enumHubGoalRewards.reward_belt_reader,
throughputOnly: true,
}, },
// 15 // 15
@ -176,8 +176,7 @@ export const tutorialGoals = [
// 18 // 18
// Rotater (180deg) // Rotater (180deg)
{ {
// @TODO shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused
shape: "CuCuCuCu",
required: 20000, required: 20000,
reward: enumHubGoalRewards.reward_rotater_180, reward: enumHubGoalRewards.reward_rotater_180,
}, },
@ -185,8 +184,7 @@ export const tutorialGoals = [
// 19 // 19
// Compact splitter // Compact splitter
{ {
// @TODO shape: "CpRpCp--:SwSwSwSw",
shape: "CuCuCuCu",
required: 25000, required: 25000,
reward: enumHubGoalRewards.reward_splitter, reward: enumHubGoalRewards.reward_splitter,
}, },
@ -202,8 +200,7 @@ export const tutorialGoals = [
// 21 // 21
// Display // Display
{ {
// @TODO shape: "CrCrCrCr:CwCwCwCw:CrCrCrCr:CwCwCwCw",
shape: "CuCuCuCu",
required: 25000, required: 25000,
reward: enumHubGoalRewards.reward_display, reward: enumHubGoalRewards.reward_display,
}, },
@ -211,43 +208,37 @@ export const tutorialGoals = [
// 22 // 22
// Constant signal // Constant signal
{ {
// @TODO shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
shape: "CuCuCuCu", required: 25000,
required: 30000,
reward: enumHubGoalRewards.reward_constant_signal, reward: enumHubGoalRewards.reward_constant_signal,
}, },
// 23 // 23
// Quad Painter // Quad Painter
{ {
// @TODO shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
shape: "CuCuCuCu", required: 5000,
// shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
required: 35000,
reward: enumHubGoalRewards.reward_painter_quad, reward: enumHubGoalRewards.reward_painter_quad,
}, },
// 24 Logic gates // 24 Logic gates
{ {
// @TODO shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
shape: "CuCuCuCu", required: 10000,
required: 40000,
reward: enumHubGoalRewards.reward_logic_gates, reward: enumHubGoalRewards.reward_logic_gates,
}, },
// 25 Virtual Processing // 25 Virtual Processing
{ {
// @TODO shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg",
shape: "CuCuCuCu", required: 10000,
required: 45000,
reward: enumHubGoalRewards.reward_virtual_processing, reward: enumHubGoalRewards.reward_virtual_processing,
}, },
// 26 Freeplay // 26 Freeplay
{ {
// @TODO shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw",
shape: "CuCuCuCu", required: 10000,
required: 100000,
reward: enumHubGoalRewards.reward_freeplay, reward: enumHubGoalRewards.reward_freeplay,
}, },
]; ];

View File

@ -2,10 +2,28 @@ import { findNiceIntegerValue } from "../core/utils";
import { ShapeDefinition } from "./shape_definition"; import { ShapeDefinition } from "./shape_definition";
export const finalGameShape = "RuCw--Cw:----Ru--"; export const finalGameShape = "RuCw--Cw:----Ru--";
export const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
export const blueprintShape = "CbCbCbRb:CwCwCwCw"; export const blueprintShape = "CbCbCbRb:CwCwCwCw";
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 2]; 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 {{ /** @typedef {{
* shape: string, * shape: string,
* amount: number * amount: number
@ -41,6 +59,7 @@ export const UPGRADES = {
required: [{ shape: finalGameShape, amount: 50000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
miner: [ miner: [
@ -63,6 +82,7 @@ export const UPGRADES = {
required: [{ shape: finalGameShape, amount: 50000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
processors: [ processors: [
@ -85,6 +105,7 @@ export const UPGRADES = {
required: [{ shape: finalGameShape, amount: 50000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
painting: [ painting: [
@ -107,6 +128,7 @@ export const UPGRADES = {
required: [{ shape: finalGameShape, amount: 50000 }], required: [{ shape: finalGameShape, amount: 50000 }],
excludePrevious: true, excludePrevious: true,
}, },
...generateEndgameUpgrades(),
], ],
}; };

View File

@ -350,6 +350,7 @@ ingame:
notifications: notifications:
newUpgrade: A new upgrade is available! newUpgrade: A new upgrade is available!
gameSaved: Your game has been saved. gameSaved: Your game has been saved.
freeplayLevelComplete: Level <level> has been completed!
# The "Upgrades" window # The "Upgrades" window
shop: shop:
@ -360,7 +361,8 @@ ingame:
tier: Tier <x> tier: Tier <x>
# The roman number for each tier # 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>) maximumLevel: MAXIMUM LEVEL (Speed x<currentMult>)
@ -788,13 +790,13 @@ storyRewards:
no_reward_freeplay: no_reward_freeplay:
title: Next level title: Next level
desc: >- desc: >-
Congratulations! By the way, more content is planned for the standalone! Congratulations!
reward_freeplay: reward_freeplay:
title: Freeplay title: Freeplay
desc: >- desc: >-
You did it! You unlocked the <strong>free-play mode</strong>! This means that shapes are now <strong>randomly</strong> generated!<br><br> 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. 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: settings: