1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Rebalance the whole game, rename splitter -> balancer

This commit is contained in:
tobspr
2020-09-22 14:47:59 +02:00
parent b0058cb59b
commit c6eb1dad04
42 changed files with 1005 additions and 981 deletions

View File

@@ -11,29 +11,29 @@ import { formatItemsPerSecond } from "../../core/utils";
import { BeltUnderlaysComponent } from "../components/belt_underlays";
/** @enum {string} */
export const enumSplitterVariants = {
compact: "compact",
compactInverse: "compact-inverse",
compactMerge: "compact-merge",
compactMergeInverse: "compact-merge-inverse",
export const enumBalancerVariants = {
merger: "merger",
mergerInverse: "merger-inverse",
splitter: "splitter",
splitterInverse: "splitter-inverse",
};
export class MetaSplitterBuilding extends MetaBuilding {
export class MetaBalancerBuilding extends MetaBuilding {
constructor() {
super("splitter");
super("balancer");
}
getDimensions(variant) {
switch (variant) {
case defaultBuildingVariant:
return new Vector(2, 1);
case enumSplitterVariants.compact:
case enumSplitterVariants.compactInverse:
case enumSplitterVariants.compactMerge:
case enumSplitterVariants.compactMergeInverse:
case enumBalancerVariants.merger:
case enumBalancerVariants.mergerInverse:
case enumBalancerVariants.splitter:
case enumBalancerVariants.splitterInverse:
return new Vector(1, 1);
default:
assertAlways(false, "Unknown splitter variant: " + variant);
assertAlways(false, "Unknown balancer variant: " + variant);
}
}
@@ -43,7 +43,7 @@ export class MetaSplitterBuilding extends MetaBuilding {
* @returns {Array<[string, string]>}
*/
getAdditionalStatistics(root, variant) {
const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.splitter);
const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.balancer);
return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
}
@@ -57,12 +57,12 @@ export class MetaSplitterBuilding extends MetaBuilding {
getAvailableVariants(root) {
let available = [defaultBuildingVariant];
if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_splitter_compact)) {
available.push(enumSplitterVariants.compact, enumSplitterVariants.compactInverse);
if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_merger)) {
available.push(enumBalancerVariants.merger, enumBalancerVariants.mergerInverse);
}
if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_merger_compact)) {
available.push(enumSplitterVariants.compactMerge, enumSplitterVariants.compactMergeInverse);
if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_splitter)) {
available.push(enumBalancerVariants.splitter, enumBalancerVariants.splitterInverse);
}
return available;
@@ -72,7 +72,7 @@ export class MetaSplitterBuilding extends MetaBuilding {
* @param {GameRoot} root
*/
getIsUnlocked(root) {
return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_splitter);
return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_balancer);
}
/**
@@ -89,7 +89,7 @@ export class MetaSplitterBuilding extends MetaBuilding {
entity.addComponent(
new ItemProcessorComponent({
inputsPerCharge: 1,
processorType: enumItemProcessorTypes.splitter,
processorType: enumItemProcessorTypes.balancer,
})
);
@@ -135,8 +135,8 @@ export class MetaSplitterBuilding extends MetaBuilding {
break;
}
case enumSplitterVariants.compact:
case enumSplitterVariants.compactInverse: {
case enumBalancerVariants.merger:
case enumBalancerVariants.mergerInverse: {
entity.components.ItemAcceptor.setSlots([
{
pos: new Vector(0, 0),
@@ -145,7 +145,7 @@ export class MetaSplitterBuilding extends MetaBuilding {
{
pos: new Vector(0, 0),
directions: [
variant === enumSplitterVariants.compactInverse
variant === enumBalancerVariants.mergerInverse
? enumDirection.left
: enumDirection.right,
],
@@ -162,8 +162,8 @@ export class MetaSplitterBuilding extends MetaBuilding {
break;
}
case enumSplitterVariants.compactMerge:
case enumSplitterVariants.compactMergeInverse: {
case enumBalancerVariants.splitter:
case enumBalancerVariants.splitterInverse: {
entity.components.ItemAcceptor.setSlots([
{
pos: new Vector(0, 0),
@@ -179,7 +179,7 @@ export class MetaSplitterBuilding extends MetaBuilding {
{
pos: new Vector(0, 0),
direction:
variant === enumSplitterVariants.compactMergeInverse
variant === enumBalancerVariants.splitterInverse
? enumDirection.left
: enumDirection.right,
},
@@ -192,7 +192,7 @@ export class MetaSplitterBuilding extends MetaBuilding {
break;
}
default:
assertAlways(false, "Unknown splitter variant: " + variant);
assertAlways(false, "Unknown balancer variant: " + variant);
}
}
}

View File

@@ -895,7 +895,7 @@ export class Camera extends BasicSerializableObject {
return;
}
const panAreaPixels = Math.min(this.root.gameWidth, this.root.gameHeight) * 0.015;
const panAreaPixels = 2;
const panVelocity = new Vector();
if (mousePos.x < panAreaPixels) {

View File

@@ -4,7 +4,7 @@ import { Component } from "../component";
/** @enum {string} */
export const enumItemProcessorTypes = {
splitter: "splitter",
balancer: "balancer",
cutter: "cutter",
cutterQuad: "cutterQuad",
rotater: "rotater",
@@ -57,14 +57,14 @@ export class ItemProcessorComponent extends Component {
*
*/
constructor({
processorType = enumItemProcessorTypes.splitter,
processorType = enumItemProcessorTypes.balancer,
processingRequirement = null,
inputsPerCharge = 1,
}) {
super();
// Which slot to emit next, this is only a preference and if it can't emit
// it will take the other one. Some machines ignore this (e.g. the splitter) to make
// it will take the other one. Some machines ignore this (e.g. the balancer) to make
// sure the outputs always match
this.nextOutputSlot = 0;

View File

@@ -43,11 +43,11 @@ export class HubGoals extends BasicSerializableObject {
// Compute upgrade improvements
for (const upgradeId in UPGRADES) {
const upgradeHandle = UPGRADES[upgradeId];
const tiers = UPGRADES[upgradeId];
const level = this.upgradeLevels[upgradeId] || 0;
let totalImprovement = upgradeHandle.baseValue || 1;
let totalImprovement = 1;
for (let i = 0; i < level; ++i) {
totalImprovement += upgradeHandle.tiers[i].improvement;
totalImprovement += tiers[i].improvement;
}
this.upgradeImprovements[upgradeId] = totalImprovement;
}
@@ -98,7 +98,7 @@ export class HubGoals extends BasicSerializableObject {
*/
this.upgradeImprovements = {};
for (const key in UPGRADES) {
this.upgradeImprovements[key] = UPGRADES[key].baseValue || 1;
this.upgradeImprovements[key] = 1;
}
this.createNextGoal();
@@ -212,7 +212,7 @@ export class HubGoals extends BasicSerializableObject {
this.currentGoal = {
/** @type {ShapeDefinition} */
definition: this.createRandomShape(),
required: 10000 + findNiceIntegerValue(this.level * 2000),
required: findNiceIntegerValue(5000 + Math.pow(this.level * 2000, 0.75)),
reward: enumHubGoalRewards.no_reward_freeplay,
};
}
@@ -243,10 +243,10 @@ export class HubGoals extends BasicSerializableObject {
* @param {string} upgradeId
*/
canUnlockUpgrade(upgradeId) {
const handle = UPGRADES[upgradeId];
const tiers = UPGRADES[upgradeId];
const currentLevel = this.getUpgradeLevel(upgradeId);
if (currentLevel >= handle.tiers.length) {
if (currentLevel >= tiers.length) {
// Max level
return false;
}
@@ -255,7 +255,7 @@ export class HubGoals extends BasicSerializableObject {
return true;
}
const tierData = handle.tiers[currentLevel];
const tierData = tiers[currentLevel];
for (let i = 0; i < tierData.required.length; ++i) {
const requirement = tierData.required[i];
@@ -290,10 +290,10 @@ export class HubGoals extends BasicSerializableObject {
return false;
}
const handle = UPGRADES[upgradeId];
const upgradeTiers = UPGRADES[upgradeId];
const currentLevel = this.getUpgradeLevel(upgradeId);
const tierData = handle.tiers[currentLevel];
const tierData = upgradeTiers[currentLevel];
if (!tierData) {
return false;
}
@@ -399,7 +399,7 @@ export class HubGoals extends BasicSerializableObject {
case enumItemProcessorTypes.trash:
case enumItemProcessorTypes.hub:
return 1e30;
case enumItemProcessorTypes.splitter:
case enumItemProcessorTypes.balancer:
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt * 2;
case enumItemProcessorTypes.reader:
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt;

View File

@@ -8,7 +8,7 @@ import { MetaMixerBuilding } from "../../buildings/mixer";
import { MetaPainterBuilding } from "../../buildings/painter";
import { MetaReaderBuilding } from "../../buildings/reader";
import { MetaRotaterBuilding } from "../../buildings/rotater";
import { MetaSplitterBuilding } from "../../buildings/splitter";
import { MetaBalancerBuilding } from "../../buildings/balancer";
import { MetaStackerBuilding } from "../../buildings/stacker";
import { MetaTrashBuilding } from "../../buildings/trash";
import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
@@ -16,7 +16,7 @@ import { HUDBaseToolbar } from "./base_toolbar";
const supportedBuildings = [
MetaBeltBuilding,
MetaSplitterBuilding,
MetaBalancerBuilding,
MetaUndergroundBeltBuilding,
MetaMinerBuilding,
MetaCutterBuilding,

View File

@@ -1,291 +1,291 @@
import { ClickDetector } from "../../../core/click_detector";
import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils";
import { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part";
import { blueprintShape, UPGRADES } from "../../upgrades";
import { enumHubGoalRewards } from "../../tutorial_goals";
/**
* Manages the pinned shapes on the left side of the screen
*/
export class HUDPinnedShapes extends BaseHUDPart {
constructor(root) {
super(root);
/**
* Store a list of pinned shapes
* @type {Array<string>}
*/
this.pinnedShapes = [];
/**
* Store handles to the currently rendered elements, so we can update them more
* convenient. Also allows for cleaning up handles.
* @type {Array<{
* key: string,
* amountLabel: HTMLElement,
* lastRenderedValue: string,
* element: HTMLElement,
* detector?: ClickDetector,
* infoDetector?: ClickDetector
* }>}
*/
this.handles = [];
}
createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PinnedShapes", []);
}
/**
* Serializes the pinned shapes
*/
serialize() {
return {
shapes: this.pinnedShapes,
};
}
/**
* Deserializes the pinned shapes
* @param {{ shapes: Array<string>}} data
*/
deserialize(data) {
if (!data || !data.shapes || !Array.isArray(data.shapes)) {
return "Invalid pinned shapes data";
}
this.pinnedShapes = data.shapes;
}
/**
* Initializes the hud component
*/
initialize() {
// Connect to any relevant signals
this.root.signals.storyGoalCompleted.add(this.rerenderFull, this);
this.root.signals.upgradePurchased.add(this.updateShapesAfterUpgrade, this);
this.root.signals.postLoadHook.add(this.rerenderFull, this);
this.root.hud.signals.shapePinRequested.add(this.pinNewShape, this);
this.root.hud.signals.shapeUnpinRequested.add(this.unpinShape, this);
// Perform initial render
this.updateShapesAfterUpgrade();
}
/**
* Updates all shapes after an upgrade has been purchased and removes the unused ones
*/
updateShapesAfterUpgrade() {
for (let i = 0; i < this.pinnedShapes.length; ++i) {
const key = this.pinnedShapes[i];
if (key === blueprintShape) {
// Ignore blueprint shapes
continue;
}
let goal = this.findGoalValueForShape(key);
if (!goal) {
// Seems no longer relevant
this.pinnedShapes.splice(i, 1);
i -= 1;
}
}
this.rerenderFull();
}
/**
* Finds the current goal for the given key. If the key is the story goal, returns
* the story goal. If its the blueprint shape, no goal is returned. Otherwise
* it's searched for upgrades.
* @param {string} key
*/
findGoalValueForShape(key) {
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
return this.root.hubGoals.currentGoal.required;
}
if (key === blueprintShape) {
return null;
}
// Check if this shape is required for any upgrade
for (const upgradeId in UPGRADES) {
const { tiers } = UPGRADES[upgradeId];
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
const tierHandle = tiers[currentTier];
if (!tierHandle) {
// Max level
continue;
}
for (let i = 0; i < tierHandle.required.length; ++i) {
const { shape, amount } = tierHandle.required[i];
if (shape === key) {
return amount;
}
}
}
return null;
}
/**
* Returns whether a given shape is currently pinned
* @param {string} key
*/
isShapePinned(key) {
if (key === this.root.hubGoals.currentGoal.definition.getHash() || key === blueprintShape) {
// This is a "special" shape which is always pinned
return true;
}
return this.pinnedShapes.indexOf(key) >= 0;
}
/**
* Rerenders the whole component
*/
rerenderFull() {
const currentGoal = this.root.hubGoals.currentGoal;
const currentKey = currentGoal.definition.getHash();
// First, remove all old shapes
for (let i = 0; i < this.handles.length; ++i) {
this.handles[i].element.remove();
const detector = this.handles[i].detector;
if (detector) {
detector.cleanup();
}
const infoDetector = this.handles[i].infoDetector;
if (infoDetector) {
infoDetector.cleanup();
}
}
this.handles = [];
// Pin story goal
this.internalPinShape(currentKey, false, "goal");
// Pin blueprint shape as well
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
this.internalPinShape(blueprintShape, false, "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);
}
}
}
/**
* Pins a new shape
* @param {string} key
* @param {boolean} canUnpin
* @param {string=} className
*/
internalPinShape(key, canUnpin = true, className = null) {
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
const element = makeDiv(this.element, null, ["shape"]);
const canvas = definition.generateAsCanvas(120);
element.appendChild(canvas);
if (className) {
element.classList.add(className);
}
let detector = null;
if (canUnpin) {
element.classList.add("unpinable");
detector = new ClickDetector(element, {
consumeEvents: true,
preventDefault: true,
targetOnly: true,
});
detector.click.add(() => this.unpinShape(key));
} else {
element.classList.add("marked");
}
// Show small info icon
const infoButton = document.createElement("button");
infoButton.classList.add("infoButton");
element.appendChild(infoButton);
const infoDetector = new ClickDetector(infoButton, {
consumeEvents: true,
preventDefault: true,
targetOnly: true,
});
infoDetector.click.add(() => this.root.hud.signals.viewShapeDetailsRequested.dispatch(definition));
const amountLabel = makeDiv(element, null, ["amountLabel"], "");
const goal = this.findGoalValueForShape(key);
if (goal) {
makeDiv(element, null, ["goalLabel"], "/" + formatBigNumber(goal));
}
this.handles.push({
key,
element,
amountLabel,
lastRenderedValue: "",
detector,
infoDetector,
});
}
/**
* Updates all amount labels
*/
update() {
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);
if (currentValueFormatted !== handle.lastRenderedValue) {
handle.lastRenderedValue = currentValueFormatted;
handle.amountLabel.innerText = currentValueFormatted;
const goal = this.findGoalValueForShape(handle.key);
handle.element.classList.toggle("completed", goal && currentValue > goal);
}
}
}
/**
* Unpins a shape
* @param {string} key
*/
unpinShape(key) {
arrayDeleteValue(this.pinnedShapes, key);
this.rerenderFull();
}
/**
* Requests to pin a new shape
* @param {ShapeDefinition} definition
*/
pinNewShape(definition) {
const key = definition.getHash();
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
// Can not pin current goal
return;
}
if (key === blueprintShape) {
// Can not pin the blueprint shape
return;
}
// Check if its already pinned
if (this.pinnedShapes.indexOf(key) >= 0) {
return;
}
this.pinnedShapes.push(key);
this.rerenderFull();
}
}
import { ClickDetector } from "../../../core/click_detector";
import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils";
import { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part";
import { blueprintShape, UPGRADES } from "../../upgrades";
import { enumHubGoalRewards } from "../../tutorial_goals";
/**
* Manages the pinned shapes on the left side of the screen
*/
export class HUDPinnedShapes extends BaseHUDPart {
constructor(root) {
super(root);
/**
* Store a list of pinned shapes
* @type {Array<string>}
*/
this.pinnedShapes = [];
/**
* Store handles to the currently rendered elements, so we can update them more
* convenient. Also allows for cleaning up handles.
* @type {Array<{
* key: string,
* amountLabel: HTMLElement,
* lastRenderedValue: string,
* element: HTMLElement,
* detector?: ClickDetector,
* infoDetector?: ClickDetector
* }>}
*/
this.handles = [];
}
createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PinnedShapes", []);
}
/**
* Serializes the pinned shapes
*/
serialize() {
return {
shapes: this.pinnedShapes,
};
}
/**
* Deserializes the pinned shapes
* @param {{ shapes: Array<string>}} data
*/
deserialize(data) {
if (!data || !data.shapes || !Array.isArray(data.shapes)) {
return "Invalid pinned shapes data";
}
this.pinnedShapes = data.shapes;
}
/**
* Initializes the hud component
*/
initialize() {
// Connect to any relevant signals
this.root.signals.storyGoalCompleted.add(this.rerenderFull, this);
this.root.signals.upgradePurchased.add(this.updateShapesAfterUpgrade, this);
this.root.signals.postLoadHook.add(this.rerenderFull, this);
this.root.hud.signals.shapePinRequested.add(this.pinNewShape, this);
this.root.hud.signals.shapeUnpinRequested.add(this.unpinShape, this);
// Perform initial render
this.updateShapesAfterUpgrade();
}
/**
* Updates all shapes after an upgrade has been purchased and removes the unused ones
*/
updateShapesAfterUpgrade() {
for (let i = 0; i < this.pinnedShapes.length; ++i) {
const key = this.pinnedShapes[i];
if (key === blueprintShape) {
// Ignore blueprint shapes
continue;
}
let goal = this.findGoalValueForShape(key);
if (!goal) {
// Seems no longer relevant
this.pinnedShapes.splice(i, 1);
i -= 1;
}
}
this.rerenderFull();
}
/**
* Finds the current goal for the given key. If the key is the story goal, returns
* the story goal. If its the blueprint shape, no goal is returned. Otherwise
* it's searched for upgrades.
* @param {string} key
*/
findGoalValueForShape(key) {
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
return this.root.hubGoals.currentGoal.required;
}
if (key === blueprintShape) {
return null;
}
// Check if this shape is required for any upgrade
for (const upgradeId in UPGRADES) {
const upgradeTiers = UPGRADES[upgradeId];
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
const tierHandle = upgradeTiers[currentTier];
if (!tierHandle) {
// Max level
continue;
}
for (let i = 0; i < tierHandle.required.length; ++i) {
const { shape, amount } = tierHandle.required[i];
if (shape === key) {
return amount;
}
}
}
return null;
}
/**
* Returns whether a given shape is currently pinned
* @param {string} key
*/
isShapePinned(key) {
if (key === this.root.hubGoals.currentGoal.definition.getHash() || key === blueprintShape) {
// This is a "special" shape which is always pinned
return true;
}
return this.pinnedShapes.indexOf(key) >= 0;
}
/**
* Rerenders the whole component
*/
rerenderFull() {
const currentGoal = this.root.hubGoals.currentGoal;
const currentKey = currentGoal.definition.getHash();
// First, remove all old shapes
for (let i = 0; i < this.handles.length; ++i) {
this.handles[i].element.remove();
const detector = this.handles[i].detector;
if (detector) {
detector.cleanup();
}
const infoDetector = this.handles[i].infoDetector;
if (infoDetector) {
infoDetector.cleanup();
}
}
this.handles = [];
// Pin story goal
this.internalPinShape(currentKey, false, "goal");
// Pin blueprint shape as well
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
this.internalPinShape(blueprintShape, false, "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);
}
}
}
/**
* Pins a new shape
* @param {string} key
* @param {boolean} canUnpin
* @param {string=} className
*/
internalPinShape(key, canUnpin = true, className = null) {
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
const element = makeDiv(this.element, null, ["shape"]);
const canvas = definition.generateAsCanvas(120);
element.appendChild(canvas);
if (className) {
element.classList.add(className);
}
let detector = null;
if (canUnpin) {
element.classList.add("unpinable");
detector = new ClickDetector(element, {
consumeEvents: true,
preventDefault: true,
targetOnly: true,
});
detector.click.add(() => this.unpinShape(key));
} else {
element.classList.add("marked");
}
// Show small info icon
const infoButton = document.createElement("button");
infoButton.classList.add("infoButton");
element.appendChild(infoButton);
const infoDetector = new ClickDetector(infoButton, {
consumeEvents: true,
preventDefault: true,
targetOnly: true,
});
infoDetector.click.add(() => this.root.hud.signals.viewShapeDetailsRequested.dispatch(definition));
const amountLabel = makeDiv(element, null, ["amountLabel"], "");
const goal = this.findGoalValueForShape(key);
if (goal) {
makeDiv(element, null, ["goalLabel"], "/" + formatBigNumber(goal));
}
this.handles.push({
key,
element,
amountLabel,
lastRenderedValue: "",
detector,
infoDetector,
});
}
/**
* Updates all amount labels
*/
update() {
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);
if (currentValueFormatted !== handle.lastRenderedValue) {
handle.lastRenderedValue = currentValueFormatted;
handle.amountLabel.innerText = currentValueFormatted;
const goal = this.findGoalValueForShape(handle.key);
handle.element.classList.toggle("completed", goal && currentValue > goal);
}
}
}
/**
* Unpins a shape
* @param {string} key
*/
unpinShape(key) {
arrayDeleteValue(this.pinnedShapes, key);
this.rerenderFull();
}
/**
* Requests to pin a new shape
* @param {ShapeDefinition} definition
*/
pinNewShape(definition) {
const key = definition.getHash();
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
// Can not pin current goal
return;
}
if (key === blueprintShape) {
// Can not pin the blueprint shape
return;
}
// Check if its already pinned
if (this.pinnedShapes.indexOf(key) >= 0) {
return;
}
this.pinnedShapes.push(key);
this.rerenderFull();
}
}

View File

@@ -89,8 +89,8 @@ export class HUDSandboxController extends BaseHUDPart {
}
modifyUpgrade(id, amount) {
const handle = UPGRADES[id];
const maxLevel = handle.tiers.length;
const upgradeTiers = UPGRADES[id];
const maxLevel = upgradeTiers.length;
this.root.hubGoals.upgradeLevels[id] = Math.max(
0,
@@ -100,7 +100,7 @@ export class HUDSandboxController extends BaseHUDPart {
// Compute improvement
let improvement = 1;
for (let i = 0; i < this.root.hubGoals.upgradeLevels[id]; ++i) {
improvement += handle.tiers[i].improvement;
improvement += upgradeTiers[i].improvement;
}
this.root.hubGoals.upgradeImprovements[id] = improvement;
this.root.signals.upgradePurchased.dispatch(id);

View File

@@ -59,11 +59,11 @@ export class HUDShop extends BaseHUDPart {
rerenderFull() {
for (const upgradeId in this.upgradeToElements) {
const handle = this.upgradeToElements[upgradeId];
const { tiers } = UPGRADES[upgradeId];
const upgradeTiers = UPGRADES[upgradeId];
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
const currentTierMultiplier = this.root.hubGoals.upgradeImprovements[upgradeId];
const tierHandle = tiers[currentTier];
const tierHandle = upgradeTiers[currentTier];
// Set tier
handle.elemTierLabel.innerText = T.ingame.shop.tier.replace(

View File

@@ -45,7 +45,7 @@ export const KEYMAPPINGS = {
buildings: {
belt: { keyCode: key("1") },
splitter: { keyCode: key("2") },
balancer: { keyCode: key("2") },
underground_belt: { keyCode: key("3") },
miner: { keyCode: key("4") },
cutter: { keyCode: key("5") },

View File

@@ -7,7 +7,7 @@ import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
import { MetaMixerBuilding } from "./buildings/mixer";
import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater";
import { enumSplitterVariants, MetaSplitterBuilding } from "./buildings/splitter";
import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer";
import { MetaStackerBuilding } from "./buildings/stacker";
import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash";
import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
@@ -26,7 +26,7 @@ import { MetaReaderBuilding } from "./buildings/reader";
const logger = createLogger("building_registry");
export function initMetaBuildingRegistry() {
gMetaBuildingRegistry.register(MetaSplitterBuilding);
gMetaBuildingRegistry.register(MetaBalancerBuilding);
gMetaBuildingRegistry.register(MetaMinerBuilding);
gMetaBuildingRegistry.register(MetaCutterBuilding);
gMetaBuildingRegistry.register(MetaRotaterBuilding);
@@ -52,12 +52,12 @@ export function initMetaBuildingRegistry() {
registerBuildingVariant(2, MetaBeltBuilding, defaultBuildingVariant, 1);
registerBuildingVariant(3, MetaBeltBuilding, defaultBuildingVariant, 2);
// Splitter
registerBuildingVariant(4, MetaSplitterBuilding);
registerBuildingVariant(5, MetaSplitterBuilding, enumSplitterVariants.compact);
registerBuildingVariant(6, MetaSplitterBuilding, enumSplitterVariants.compactInverse);
registerBuildingVariant(47, MetaSplitterBuilding, enumSplitterVariants.compactMerge);
registerBuildingVariant(48, MetaSplitterBuilding, enumSplitterVariants.compactMergeInverse);
// Balancer
registerBuildingVariant(4, MetaBalancerBuilding);
registerBuildingVariant(5, MetaBalancerBuilding, enumBalancerVariants.merger);
registerBuildingVariant(6, MetaBalancerBuilding, enumBalancerVariants.mergerInverse);
registerBuildingVariant(47, MetaBalancerBuilding, enumBalancerVariants.splitter);
registerBuildingVariant(48, MetaBalancerBuilding, enumBalancerVariants.splitterInverse);
// Miner
registerBuildingVariant(7, MetaMinerBuilding);

View File

@@ -45,7 +45,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
* @type {Object<enumItemProcessorTypes, function(ProcessorImplementationPayload) : string>}
*/
this.handlers = {
[enumItemProcessorTypes.splitter]: this.process_SPLITTER,
[enumItemProcessorTypes.balancer]: this.process_BALANCER,
[enumItemProcessorTypes.cutter]: this.process_CUTTER,
[enumItemProcessorTypes.cutterQuad]: this.process_CUTTER_QUAD,
[enumItemProcessorTypes.rotater]: this.process_ROTATER,
@@ -305,8 +305,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
/**
* @param {ProcessorImplementationPayload} payload
*/
process_SPLITTER(payload) {
// trackProduction = false;
process_BALANCER(payload) {
const availableSlots = payload.entity.components.ItemEjector.slots.length;
const processorComp = payload.entity.components.ItemProcessor;

View File

@@ -11,21 +11,19 @@ export const enumHubGoalRewards = {
reward_painter: "reward_painter",
reward_mixer: "reward_mixer",
reward_stacker: "reward_stacker",
reward_splitter: "reward_splitter",
reward_balancer: "reward_balancer",
reward_tunnel: "reward_tunnel",
reward_rotater_ccw: "reward_rotater_ccw",
reward_rotater_180: "reward_rotater_fl",
reward_rotater_180: "reward_rotater_180",
reward_miner_chainable: "reward_miner_chainable",
reward_underground_belt_tier_2: "reward_underground_belt_tier_2",
reward_splitter_compact: "reward_splitter_compact",
reward_splitter: "reward_splitter",
reward_cutter_quad: "reward_cutter_quad",
reward_painter_double: "reward_painter_double",
reward_painter_quad: "reward_painter_quad",
reward_storage: "reward_storage",
// @todo: unlock
reward_merger_compact: "reward_compact_merger",
reward_merger: "reward_merger",
reward_blueprints: "reward_blueprints",
reward_freeplay: "reward_freeplay",
@@ -55,14 +53,14 @@ export const tutorialGoals = [
// Rectangle
{
shape: "RuRuRuRu", // miners t1
required: 100,
reward: enumHubGoalRewards.reward_splitter,
required: 85,
reward: enumHubGoalRewards.reward_balancer,
},
// 4
{
shape: "RuRu----", // processors t2
required: 120,
required: 100,
reward: enumHubGoalRewards.reward_rotater,
},
@@ -70,14 +68,14 @@ export const tutorialGoals = [
// Rotater
{
shape: "Cu----Cu", // belts t2
required: 200,
required: 175,
reward: enumHubGoalRewards.reward_tunnel,
},
// 6
{
shape: "Cu------", // miners t2
required: 400,
required: 250,
reward: enumHubGoalRewards.reward_painter,
},
@@ -85,14 +83,14 @@ export const tutorialGoals = [
// Painter
{
shape: "CrCrCrCr", // unused
required: 800,
required: 500,
reward: enumHubGoalRewards.reward_rotater_ccw,
},
// 8
{
shape: "RbRb----", // painter t2
required: 1000,
required: 700,
reward: enumHubGoalRewards.reward_mixer,
},
@@ -100,15 +98,15 @@ export const tutorialGoals = [
// Mixing (purple)
{
shape: "CpCpCpCp", // belts t3
required: 1400,
reward: enumHubGoalRewards.reward_splitter_compact,
required: 800,
reward: enumHubGoalRewards.reward_splitter,
},
// 10
// Star shape + cyan
{
shape: "ScScScSc", // miners t3
required: 1600,
required: 900,
reward: enumHubGoalRewards.reward_stacker,
},
@@ -116,7 +114,7 @@ export const tutorialGoals = [
// Stacker
{
shape: "CgScScCg", // processors t3
required: 1800,
required: 1000,
reward: enumHubGoalRewards.reward_miner_chainable,
},
@@ -124,49 +122,56 @@ export const tutorialGoals = [
// Blueprints
{
shape: "CbCbCbRb:CwCwCwCw",
required: 2000,
required: 1250,
reward: enumHubGoalRewards.reward_blueprints,
},
// 13
{
shape: "RpRpRpRp:CwCwCwCw", // painting t3
required: 12000,
required: 5000,
reward: enumHubGoalRewards.reward_underground_belt_tier_2,
},
// 14
{
shape: "SrSrSrSr:CyCyCyCy", // unused
required: 16000,
required: 7500,
reward: enumHubGoalRewards.reward_storage,
},
// 15
{
shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants)
required: 25000,
required: 15000,
reward: enumHubGoalRewards.reward_cutter_quad,
},
// 16
{
shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
required: 50000,
required: 20000,
reward: enumHubGoalRewards.reward_painter_double,
},
// 17
{
shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
required: 120000,
reward: enumHubGoalRewards.reward_painter_quad,
shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // rotater 180
required: 25000,
reward: enumHubGoalRewards.reward_rotater_180,
},
// 18
{
shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
required: 30000,
reward: enumHubGoalRewards.reward_painter_quad,
},
// 19
{
shape: finalGameShape,
required: 250000,
required: 50000,
reward: enumHubGoalRewards.reward_freeplay,
},
];

View File

@@ -4,7 +4,7 @@ import { MetaRotaterBuilding, enumRotaterVariants } from "./buildings/rotater";
import { MetaPainterBuilding, enumPainterVariants } from "./buildings/painter";
import { MetaMixerBuilding } from "./buildings/mixer";
import { MetaStackerBuilding } from "./buildings/stacker";
import { MetaSplitterBuilding, enumSplitterVariants } from "./buildings/splitter";
import { MetaBalancerBuilding, enumBalancerVariants } from "./buildings/balancer";
import { MetaUndergroundBeltBuilding, enumUndergroundBeltVariants } from "./buildings/underground_belt";
import { MetaMinerBuilding, enumMinerVariants } from "./buildings/miner";
import { MetaTrashBuilding, enumTrashVariants } from "./buildings/trash";
@@ -29,7 +29,7 @@ export const enumHubGoalRewardsToContentUnlocked = {
[enumHubGoalRewards.reward_painter]: typed([[MetaPainterBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_mixer]: typed([[MetaMixerBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_stacker]: typed([[MetaStackerBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_splitter]: typed([[MetaSplitterBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_balancer]: typed([[MetaBalancerBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_tunnel]: typed([[MetaUndergroundBeltBuilding, defaultBuildingVariant]]),
[enumHubGoalRewards.reward_rotater_ccw]: typed([[MetaRotaterBuilding, enumRotaterVariants.ccw]]),
@@ -38,9 +38,8 @@ export const enumHubGoalRewardsToContentUnlocked = {
[enumHubGoalRewards.reward_underground_belt_tier_2]: typed([
[MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2],
]),
[enumHubGoalRewards.reward_splitter_compact]: typed([
[MetaSplitterBuilding, enumSplitterVariants.compact],
]),
[enumHubGoalRewards.reward_splitter]: typed([[MetaBalancerBuilding, enumBalancerVariants.splitter]]),
[enumHubGoalRewards.reward_merger]: typed([[MetaBalancerBuilding, enumBalancerVariants.merger]]),
[enumHubGoalRewards.reward_cutter_quad]: typed([[MetaCutterBuilding, enumCutterVariants.quad]]),
[enumHubGoalRewards.reward_painter_double]: typed([[MetaPainterBuilding, enumPainterVariants.double]]),
[enumHubGoalRewards.reward_painter_quad]: typed([[MetaPainterBuilding, enumPainterVariants.quad]]),

View File

@@ -1,175 +1,160 @@
import { findNiceIntegerValue } from "../core/utils";
import { ShapeDefinition } from "./shape_definition";
export const finalGameShape = "RuCw--Cw:----Ru--";
export const blueprintShape = "CbCbCbRb:CwCwCwCw";
export const UPGRADES = {
belt: {
tiers: [
{
required: [{ shape: "CuCuCuCu", amount: 150 }],
improvement: 1,
},
{
required: [{ shape: "--CuCu--", amount: 1200 }],
improvement: 2,
},
{
required: [{ shape: "CpCpCpCp", amount: 15000 }],
improvement: 2,
},
{
required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: finalGameShape, amount: 150000 }],
improvement: 5,
excludePrevious: true,
},
],
},
miner: {
tiers: [
{
required: [{ shape: "RuRuRuRu", amount: 400 }],
improvement: 1,
},
{
required: [{ shape: "Cu------", amount: 4000 }],
improvement: 2,
},
{
required: [{ shape: "ScScScSc", amount: 20000 }],
improvement: 2,
},
{
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: finalGameShape, amount: 150000 }],
improvement: 5,
excludePrevious: true,
},
],
},
processors: {
tiers: [
{
required: [{ shape: "SuSuSuSu", amount: 1000 }],
improvement: 1,
},
{
required: [{ shape: "RuRu----", amount: 2000 }],
improvement: 2,
},
{
required: [{ shape: "CgScScCg", amount: 25000 }],
improvement: 2,
},
{
required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: finalGameShape, amount: 150000 }],
improvement: 5,
excludePrevious: true,
},
],
},
painting: {
tiers: [
{
required: [{ shape: "RbRb----", amount: 1500 }],
improvement: 2,
},
{
required: [{ shape: "WrWrWrWr", amount: 4000 }],
improvement: 1,
},
{
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 30000 }],
improvement: 2,
},
{
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 40000 }],
improvement: 2,
},
{
required: [{ shape: finalGameShape, amount: 150000 }],
improvement: 5,
excludePrevious: true,
},
],
},
};
// Tiers need % of the previous tier as requirement too
const tierGrowth = 2.5;
// Automatically generate tier levels
for (const upgradeId in UPGRADES) {
const upgrade = UPGRADES[upgradeId];
let currentTierRequirements = [];
for (let i = 0; i < upgrade.tiers.length; ++i) {
const tierHandle = upgrade.tiers[i];
const originalRequired = tierHandle.required.slice();
for (let k = currentTierRequirements.length - 1; k >= 0; --k) {
const oldTierRequirement = currentTierRequirements[k];
if (!tierHandle.excludePrevious) {
tierHandle.required.unshift({
shape: oldTierRequirement.shape,
amount: oldTierRequirement.amount,
});
}
}
currentTierRequirements.push(
...originalRequired.map(req => ({
amount: req.amount,
shape: req.shape,
}))
);
currentTierRequirements.forEach(tier => {
tier.amount = findNiceIntegerValue(tier.amount * tierGrowth);
});
}
}
if (G_IS_DEV) {
for (const upgradeId in UPGRADES) {
const upgrade = UPGRADES[upgradeId];
upgrade.tiers.forEach(tier => {
tier.required.forEach(({ shape }) => {
try {
ShapeDefinition.fromShortKey(shape);
} catch (ex) {
throw new Error("Invalid upgrade goal: '" + ex + "' for shape" + shape);
}
});
});
}
}
import { findNiceIntegerValue } from "../core/utils";
import { ShapeDefinition } from "./shape_definition";
export const finalGameShape = "RuCw--Cw:----Ru--";
export const blueprintShape = "CbCbCbRb:CwCwCwCw";
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 2];
/** @typedef {{
* shape: string,
* amount: number
* }} UpgradeRequirement */
/** @typedef {{
* required: Array<UpgradeRequirement>
* improvement?: number,
* excludePrevious?: boolean
* }} TierRequirement */
/** @typedef {Array<TierRequirement>} UpgradeTiers */
/** @type {Object<string, UpgradeTiers>} */
export const UPGRADES = {
belt: [
{
required: [{ shape: "CuCuCuCu", amount: 150 }],
},
{
required: [{ shape: "--CuCu--", amount: 1000 }],
},
{
required: [{ shape: "CpCpCpCp", amount: 5000 }],
},
{
required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 12000 }],
},
{
required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 20000 }],
},
{
required: [{ shape: finalGameShape, amount: 75000 }],
excludePrevious: true,
},
],
miner: [
{
required: [{ shape: "RuRuRuRu", amount: 400 }],
},
{
required: [{ shape: "Cu------", amount: 3000 }],
},
{
required: [{ shape: "ScScScSc", amount: 7000 }],
},
{
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 15000 }],
},
{
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 30000 }],
},
{
required: [{ shape: finalGameShape, amount: 85000 }],
excludePrevious: true,
},
],
processors: [
{
required: [{ shape: "SuSuSuSu", amount: 600 }],
},
{
required: [{ shape: "RuRu----", amount: 2000 }],
},
{
required: [{ shape: "CgScScCg", amount: 15000 }],
},
{
required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 20000 }],
},
{
required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 30000 }],
},
{
required: [{ shape: finalGameShape, amount: 100000 }],
excludePrevious: true,
},
],
painting: [
{
required: [{ shape: "RbRb----", amount: 1000 }],
},
{
required: [{ shape: "WrWrWrWr", amount: 3000 }],
},
{
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 15000 }],
},
{
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 20000 }],
},
{
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 30000 }],
},
{
required: [{ shape: finalGameShape, amount: 125000 }],
excludePrevious: true,
},
],
};
// Tiers need % of the previous tier as requirement too
const tierGrowth = 1.8;
// Automatically generate tier levels
for (const upgradeId in UPGRADES) {
const upgradeTiers = UPGRADES[upgradeId];
let currentTierRequirements = [];
for (let i = 0; i < upgradeTiers.length; ++i) {
const tierHandle = upgradeTiers[i];
tierHandle.improvement = fixedImprovements[i];
const originalRequired = tierHandle.required.slice();
for (let k = currentTierRequirements.length - 1; k >= 0; --k) {
const oldTierRequirement = currentTierRequirements[k];
if (!tierHandle.excludePrevious) {
tierHandle.required.unshift({
shape: oldTierRequirement.shape,
amount: oldTierRequirement.amount,
});
}
}
currentTierRequirements.push(
...originalRequired.map(req => ({
amount: req.amount,
shape: req.shape,
}))
);
currentTierRequirements.forEach(tier => {
tier.amount = findNiceIntegerValue(tier.amount * tierGrowth);
});
}
}
// VALIDATE
if (G_IS_DEV) {
for (const upgradeId in UPGRADES) {
UPGRADES[upgradeId].forEach(tier => {
tier.required.forEach(({ shape }) => {
try {
ShapeDefinition.fromShortKey(shape);
} catch (ex) {
throw new Error("Invalid upgrade goal: '" + ex + "' for shape" + shape);
}
});
});
}
}