parent
816fd37b55
commit
94266173d8
@ -0,0 +1,71 @@
|
||||
/* typehints:start */
|
||||
import { enumHubGoalRewards } from "./tutorial_goals";
|
||||
/* typehints:end */
|
||||
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
/** @typedef {{
|
||||
* shape: string,
|
||||
* amount: number
|
||||
* }} UpgradeRequirement */
|
||||
|
||||
/** @typedef {{
|
||||
* required: Array<UpgradeRequirement>
|
||||
* improvement?: number,
|
||||
* excludePrevious?: boolean
|
||||
* }} TierRequirement */
|
||||
|
||||
/** @typedef {Array<TierRequirement>} UpgradeTiers */
|
||||
|
||||
/** @typedef {{
|
||||
* shape: string,
|
||||
* required: number,
|
||||
* reward: enumHubGoalRewards,
|
||||
* throughputOnly?: boolean
|
||||
* }} LevelDefinition */
|
||||
|
||||
export class GameMode {
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return all available upgrades
|
||||
* @returns {Object<string, UpgradeTiers>}
|
||||
*/
|
||||
getUpgrades() {
|
||||
abstract;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blueprint shape key
|
||||
* @returns {string}
|
||||
*/
|
||||
getBlueprintShapeKey() {
|
||||
abstract;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the goals for all levels including their reward
|
||||
* @returns {Array<LevelDefinition>}
|
||||
*/
|
||||
getLevelDefinitions() {
|
||||
abstract;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return whether free play is available or if the game stops
|
||||
* after the predefined levels
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsFreeplayAvailable() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,202 +1,203 @@
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { Vector } from "../../../core/vector";
|
||||
import { T } from "../../../translations";
|
||||
import { enumMouseButton } from "../../camera";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { blueprintShape } from "../../upgrades";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { Blueprint } from "../../blueprint";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
|
||||
export class HUDBlueprintPlacer extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
const blueprintCostShape = this.root.shapeDefinitionMgr.getShapeFromShortKey(blueprintShape);
|
||||
const blueprintCostShapeCanvas = blueprintCostShape.generateAsCanvas(80);
|
||||
|
||||
this.costDisplayParent = makeDiv(parent, "ingame_HUD_BlueprintPlacer", [], ``);
|
||||
|
||||
makeDiv(this.costDisplayParent, null, ["label"], T.ingame.blueprintPlacer.cost);
|
||||
const costContainer = makeDiv(this.costDisplayParent, null, ["costContainer"], "");
|
||||
this.costDisplayText = makeDiv(costContainer, null, ["costText"], "");
|
||||
costContainer.appendChild(blueprintCostShapeCanvas);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.root.hud.signals.buildingsSelectedForCopy.add(this.createBlueprintFromBuildings, this);
|
||||
|
||||
/** @type {TypedTrackedState<Blueprint?>} */
|
||||
this.currentBlueprint = new TrackedState(this.onBlueprintChanged, this);
|
||||
/** @type {Blueprint?} */
|
||||
this.lastBlueprintUsed = null;
|
||||
|
||||
const keyActionMapper = this.root.keyMapper;
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.abortPlacement, this);
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this);
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.massSelect.pasteLastBlueprint).add(this.pasteBlueprint, this);
|
||||
|
||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||
this.root.camera.movePreHandler.add(this.onMouseMove, this);
|
||||
|
||||
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.abortPlacement, this);
|
||||
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
||||
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent);
|
||||
this.trackedCanAfford = new TrackedState(this.onCanAffordChanged, this);
|
||||
}
|
||||
|
||||
abortPlacement() {
|
||||
if (this.currentBlueprint.get()) {
|
||||
this.currentBlueprint.set(null);
|
||||
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the layer was changed
|
||||
* @param {Layer} layer
|
||||
*/
|
||||
onEditModeChanged(layer) {
|
||||
// Check if the layer of the blueprint differs and thus we have to deselect it
|
||||
const blueprint = this.currentBlueprint.get();
|
||||
if (blueprint) {
|
||||
if (blueprint.layer !== layer) {
|
||||
this.currentBlueprint.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the blueprint is now affordable or not
|
||||
* @param {boolean} canAfford
|
||||
*/
|
||||
onCanAffordChanged(canAfford) {
|
||||
this.costDisplayParent.classList.toggle("canAfford", canAfford);
|
||||
}
|
||||
|
||||
update() {
|
||||
const currentBlueprint = this.currentBlueprint.get();
|
||||
this.domAttach.update(currentBlueprint && currentBlueprint.getCost() > 0);
|
||||
this.trackedCanAfford.set(currentBlueprint && currentBlueprint.canAfford(this.root));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the blueprint was changed
|
||||
* @param {Blueprint} blueprint
|
||||
*/
|
||||
onBlueprintChanged(blueprint) {
|
||||
if (blueprint) {
|
||||
this.lastBlueprintUsed = blueprint;
|
||||
this.costDisplayText.innerText = "" + blueprint.getCost();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mouse down pre handler
|
||||
* @param {Vector} pos
|
||||
* @param {enumMouseButton} button
|
||||
*/
|
||||
onMouseDown(pos, button) {
|
||||
if (button === enumMouseButton.right) {
|
||||
if (this.currentBlueprint.get()) {
|
||||
this.abortPlacement();
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
const blueprint = this.currentBlueprint.get();
|
||||
if (!blueprint) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!blueprint.canAfford(this.root)) {
|
||||
this.root.soundProxy.playUiError();
|
||||
return;
|
||||
}
|
||||
|
||||
const worldPos = this.root.camera.screenToWorld(pos);
|
||||
const tile = worldPos.toTileSpace();
|
||||
if (blueprint.tryPlace(this.root, tile)) {
|
||||
const cost = blueprint.getCost();
|
||||
this.root.hubGoals.takeShapeByKey(blueprintShape, cost);
|
||||
this.root.soundProxy.playUi(SOUNDS.placeBuilding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mose move handler
|
||||
*/
|
||||
onMouseMove() {
|
||||
// Prevent movement while blueprint is selected
|
||||
if (this.currentBlueprint.get()) {
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an array of bulidings was selected
|
||||
* @param {Array<number>} uids
|
||||
*/
|
||||
createBlueprintFromBuildings(uids) {
|
||||
if (uids.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.currentBlueprint.set(Blueprint.fromUids(this.root, uids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to rotate the current blueprint
|
||||
*/
|
||||
rotateBlueprint() {
|
||||
if (this.currentBlueprint.get()) {
|
||||
if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
|
||||
this.currentBlueprint.get().rotateCcw();
|
||||
} else {
|
||||
this.currentBlueprint.get().rotateCw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to paste the last blueprint
|
||||
*/
|
||||
pasteBlueprint() {
|
||||
if (this.lastBlueprintUsed !== null) {
|
||||
if (this.lastBlueprintUsed.layer !== this.root.currentLayer) {
|
||||
// Not compatible
|
||||
this.root.soundProxy.playUiError();
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.hud.signals.pasteBlueprintRequested.dispatch();
|
||||
this.currentBlueprint.set(this.lastBlueprintUsed);
|
||||
} else {
|
||||
this.root.soundProxy.playUiError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
draw(parameters) {
|
||||
const blueprint = this.currentBlueprint.get();
|
||||
if (!blueprint) {
|
||||
return;
|
||||
}
|
||||
const mousePosition = this.root.app.mousePosition;
|
||||
if (!mousePosition) {
|
||||
// Not on screen
|
||||
return;
|
||||
}
|
||||
|
||||
const worldPos = this.root.camera.screenToWorld(mousePosition);
|
||||
const tile = worldPos.toTileSpace();
|
||||
blueprint.draw(parameters, tile);
|
||||
}
|
||||
}
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { Vector } from "../../../core/vector";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
import { T } from "../../../translations";
|
||||
import { Blueprint } from "../../blueprint";
|
||||
import { enumMouseButton } from "../../camera";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
|
||||
export class HUDBlueprintPlacer extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
const blueprintCostShape = this.root.shapeDefinitionMgr.getShapeFromShortKey(
|
||||
this.root.gameMode.getBlueprintShapeKey()
|
||||
);
|
||||
const blueprintCostShapeCanvas = blueprintCostShape.generateAsCanvas(80);
|
||||
|
||||
this.costDisplayParent = makeDiv(parent, "ingame_HUD_BlueprintPlacer", [], ``);
|
||||
|
||||
makeDiv(this.costDisplayParent, null, ["label"], T.ingame.blueprintPlacer.cost);
|
||||
const costContainer = makeDiv(this.costDisplayParent, null, ["costContainer"], "");
|
||||
this.costDisplayText = makeDiv(costContainer, null, ["costText"], "");
|
||||
costContainer.appendChild(blueprintCostShapeCanvas);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.root.hud.signals.buildingsSelectedForCopy.add(this.createBlueprintFromBuildings, this);
|
||||
|
||||
/** @type {TypedTrackedState<Blueprint?>} */
|
||||
this.currentBlueprint = new TrackedState(this.onBlueprintChanged, this);
|
||||
/** @type {Blueprint?} */
|
||||
this.lastBlueprintUsed = null;
|
||||
|
||||
const keyActionMapper = this.root.keyMapper;
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.abortPlacement, this);
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this);
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.massSelect.pasteLastBlueprint).add(this.pasteBlueprint, this);
|
||||
|
||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||
this.root.camera.movePreHandler.add(this.onMouseMove, this);
|
||||
|
||||
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.abortPlacement, this);
|
||||
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
||||
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent);
|
||||
this.trackedCanAfford = new TrackedState(this.onCanAffordChanged, this);
|
||||
}
|
||||
|
||||
abortPlacement() {
|
||||
if (this.currentBlueprint.get()) {
|
||||
this.currentBlueprint.set(null);
|
||||
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the layer was changed
|
||||
* @param {Layer} layer
|
||||
*/
|
||||
onEditModeChanged(layer) {
|
||||
// Check if the layer of the blueprint differs and thus we have to deselect it
|
||||
const blueprint = this.currentBlueprint.get();
|
||||
if (blueprint) {
|
||||
if (blueprint.layer !== layer) {
|
||||
this.currentBlueprint.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the blueprint is now affordable or not
|
||||
* @param {boolean} canAfford
|
||||
*/
|
||||
onCanAffordChanged(canAfford) {
|
||||
this.costDisplayParent.classList.toggle("canAfford", canAfford);
|
||||
}
|
||||
|
||||
update() {
|
||||
const currentBlueprint = this.currentBlueprint.get();
|
||||
this.domAttach.update(currentBlueprint && currentBlueprint.getCost() > 0);
|
||||
this.trackedCanAfford.set(currentBlueprint && currentBlueprint.canAfford(this.root));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the blueprint was changed
|
||||
* @param {Blueprint} blueprint
|
||||
*/
|
||||
onBlueprintChanged(blueprint) {
|
||||
if (blueprint) {
|
||||
this.lastBlueprintUsed = blueprint;
|
||||
this.costDisplayText.innerText = "" + blueprint.getCost();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mouse down pre handler
|
||||
* @param {Vector} pos
|
||||
* @param {enumMouseButton} button
|
||||
*/
|
||||
onMouseDown(pos, button) {
|
||||
if (button === enumMouseButton.right) {
|
||||
if (this.currentBlueprint.get()) {
|
||||
this.abortPlacement();
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
const blueprint = this.currentBlueprint.get();
|
||||
if (!blueprint) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!blueprint.canAfford(this.root)) {
|
||||
this.root.soundProxy.playUiError();
|
||||
return;
|
||||
}
|
||||
|
||||
const worldPos = this.root.camera.screenToWorld(pos);
|
||||
const tile = worldPos.toTileSpace();
|
||||
if (blueprint.tryPlace(this.root, tile)) {
|
||||
const cost = blueprint.getCost();
|
||||
this.root.hubGoals.takeShapeByKey(this.root.gameMode.getBlueprintShapeKey(), cost);
|
||||
this.root.soundProxy.playUi(SOUNDS.placeBuilding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mose move handler
|
||||
*/
|
||||
onMouseMove() {
|
||||
// Prevent movement while blueprint is selected
|
||||
if (this.currentBlueprint.get()) {
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an array of bulidings was selected
|
||||
* @param {Array<number>} uids
|
||||
*/
|
||||
createBlueprintFromBuildings(uids) {
|
||||
if (uids.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.currentBlueprint.set(Blueprint.fromUids(this.root, uids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to rotate the current blueprint
|
||||
*/
|
||||
rotateBlueprint() {
|
||||
if (this.currentBlueprint.get()) {
|
||||
if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
|
||||
this.currentBlueprint.get().rotateCcw();
|
||||
} else {
|
||||
this.currentBlueprint.get().rotateCw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to paste the last blueprint
|
||||
*/
|
||||
pasteBlueprint() {
|
||||
if (this.lastBlueprintUsed !== null) {
|
||||
if (this.lastBlueprintUsed.layer !== this.root.currentLayer) {
|
||||
// Not compatible
|
||||
this.root.soundProxy.playUiError();
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.hud.signals.pasteBlueprintRequested.dispatch();
|
||||
this.currentBlueprint.set(this.lastBlueprintUsed);
|
||||
} else {
|
||||
this.root.soundProxy.playUiError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
draw(parameters) {
|
||||
const blueprint = this.currentBlueprint.get();
|
||||
if (!blueprint) {
|
||||
return;
|
||||
}
|
||||
const mousePosition = this.root.app.mousePosition;
|
||||
if (!mousePosition) {
|
||||
// Not on screen
|
||||
return;
|
||||
}
|
||||
|
||||
const worldPos = this.root.camera.screenToWorld(mousePosition);
|
||||
const tile = worldPos.toTileSpace();
|
||||
blueprint.draw(parameters, tile);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,445 @@
|
||||
import { IS_DEMO } from "../../core/config";
|
||||
import { findNiceIntegerValue } from "../../core/utils";
|
||||
import { GameMode } from "../game_mode";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
||||
const finalGameShape = "RuCw--Cw:----Ru--";
|
||||
const preparementShape = "CpRpCp--:SwSwSwSw";
|
||||
const blueprintShape = "CbCbCbRb:CwCwCwCw";
|
||||
|
||||
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
|
||||
|
||||
const numEndgameUpgrades = !IS_DEMO ? 20 - fixedImprovements.length - 1 : 0;
|
||||
|
||||
function generateEndgameUpgrades() {
|
||||
return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({
|
||||
required: [
|
||||
{ shape: preparementShape, 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);
|
||||
}
|
||||
|
||||
/** @type {Object<string, import("../game_mode").UpgradeTiers>} */
|
||||
const cachedUpgrades = {
|
||||
belt: [
|
||||
{
|
||||
required: [{ shape: "CuCuCuCu", amount: 60 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "--CuCu--", amount: 500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CpCpCpCp", amount: 1000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 6000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 25000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
miner: [
|
||||
{
|
||||
required: [{ shape: "RuRuRuRu", amount: 300 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "Cu------", amount: 800 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "ScScScSc", amount: 3500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
processors: [
|
||||
{
|
||||
required: [{ shape: "SuSuSuSu", amount: 500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "RuRu----", amount: 600 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CgScScCg", amount: 3500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 25000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 50000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
painting: [
|
||||
{
|
||||
required: [{ shape: "RbRb----", amount: 600 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WrWrWrWr", amount: 3800 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 50000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
};
|
||||
|
||||
// Tiers need % of the previous tier as requirement too
|
||||
const tierGrowth = 2.5;
|
||||
|
||||
// Automatically generate tier levels
|
||||
for (const upgradeId in cachedUpgrades) {
|
||||
const upgradeTiers = cachedUpgrades[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 cachedUpgrades) {
|
||||
cachedUpgrades[upgradeId].forEach(tier => {
|
||||
tier.required.forEach(({ shape }) => {
|
||||
try {
|
||||
ShapeDefinition.fromShortKey(shape);
|
||||
} catch (ex) {
|
||||
throw new Error("Invalid upgrade goal: '" + ex + "' for shape" + shape);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const levelDefinitions = [
|
||||
// 1
|
||||
// Circle
|
||||
{
|
||||
shape: "CuCuCuCu", // belts t1
|
||||
required: 30,
|
||||
reward: enumHubGoalRewards.reward_cutter_and_trash,
|
||||
},
|
||||
|
||||
// 2
|
||||
// Cutter
|
||||
{
|
||||
shape: "----CuCu", //
|
||||
required: 40,
|
||||
reward: enumHubGoalRewards.no_reward,
|
||||
},
|
||||
|
||||
// 3
|
||||
// Rectangle
|
||||
{
|
||||
shape: "RuRuRuRu", // miners t1
|
||||
required: 70,
|
||||
reward: enumHubGoalRewards.reward_balancer,
|
||||
},
|
||||
|
||||
// 4
|
||||
{
|
||||
shape: "RuRu----", // processors t2
|
||||
required: 70,
|
||||
reward: enumHubGoalRewards.reward_rotater,
|
||||
},
|
||||
|
||||
// 5
|
||||
// Rotater
|
||||
{
|
||||
shape: "Cu----Cu", // belts t2
|
||||
required: 170,
|
||||
reward: enumHubGoalRewards.reward_tunnel,
|
||||
},
|
||||
|
||||
// 6
|
||||
{
|
||||
shape: "Cu------", // miners t2
|
||||
required: 270,
|
||||
reward: enumHubGoalRewards.reward_painter,
|
||||
},
|
||||
|
||||
// 7
|
||||
// Painter
|
||||
{
|
||||
shape: "CrCrCrCr", // unused
|
||||
required: 300,
|
||||
reward: enumHubGoalRewards.reward_rotater_ccw,
|
||||
},
|
||||
|
||||
// 8
|
||||
{
|
||||
shape: "RbRb----", // painter t2
|
||||
required: 480,
|
||||
reward: enumHubGoalRewards.reward_mixer,
|
||||
},
|
||||
|
||||
// 9
|
||||
// Mixing (purple)
|
||||
{
|
||||
shape: "CpCpCpCp", // belts t3
|
||||
required: 600,
|
||||
reward: enumHubGoalRewards.reward_merger,
|
||||
},
|
||||
|
||||
// 10
|
||||
// STACKER: Star shape + cyan
|
||||
{
|
||||
shape: "ScScScSc", // miners t3
|
||||
required: 800,
|
||||
reward: enumHubGoalRewards.reward_stacker,
|
||||
},
|
||||
|
||||
// 11
|
||||
// Chainable miner
|
||||
{
|
||||
shape: "CgScScCg", // processors t3
|
||||
required: 1000,
|
||||
reward: enumHubGoalRewards.reward_miner_chainable,
|
||||
},
|
||||
|
||||
// 12
|
||||
// Blueprints
|
||||
{
|
||||
shape: "CbCbCbRb:CwCwCwCw",
|
||||
required: 1000,
|
||||
reward: enumHubGoalRewards.reward_blueprints,
|
||||
},
|
||||
|
||||
// 13
|
||||
// Tunnel Tier 2
|
||||
{
|
||||
shape: "RpRpRpRp:CwCwCwCw", // painting t3
|
||||
required: 3800,
|
||||
reward: enumHubGoalRewards.reward_underground_belt_tier_2,
|
||||
},
|
||||
|
||||
// DEMO STOPS HERE
|
||||
...(IS_DEMO
|
||||
? [
|
||||
{
|
||||
shape: "RpRpRpRp:CwCwCwCw",
|
||||
required: 0,
|
||||
reward: enumHubGoalRewards.reward_demo_end,
|
||||
},
|
||||
]
|
||||
: [
|
||||
// 14
|
||||
// Belt reader
|
||||
{
|
||||
shape: "--Cg----:--Cr----", // unused
|
||||
required: 16, // Per second!
|
||||
reward: enumHubGoalRewards.reward_belt_reader,
|
||||
throughputOnly: true,
|
||||
},
|
||||
|
||||
// 15
|
||||
// Storage
|
||||
{
|
||||
shape: "SrSrSrSr:CyCyCyCy", // unused
|
||||
required: 10000,
|
||||
reward: enumHubGoalRewards.reward_storage,
|
||||
},
|
||||
|
||||
// 16
|
||||
// Quad Cutter
|
||||
{
|
||||
shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants)
|
||||
required: 6000,
|
||||
reward: enumHubGoalRewards.reward_cutter_quad,
|
||||
},
|
||||
|
||||
// 17
|
||||
// Double painter
|
||||
{
|
||||
shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
|
||||
required: 20000,
|
||||
reward: enumHubGoalRewards.reward_painter_double,
|
||||
},
|
||||
|
||||
// 18
|
||||
// Rotater (180deg)
|
||||
{
|
||||
shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused
|
||||
required: 20000,
|
||||
reward: enumHubGoalRewards.reward_rotater_180,
|
||||
},
|
||||
|
||||
// 19
|
||||
// Compact splitter
|
||||
{
|
||||
shape: "CpRpCp--:SwSwSwSw",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_splitter,
|
||||
},
|
||||
|
||||
// 20
|
||||
// WIRES
|
||||
{
|
||||
shape: finalGameShape,
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_wires_painter_and_levers,
|
||||
},
|
||||
|
||||
// 21
|
||||
// Filter
|
||||
{
|
||||
shape: "CrCwCrCw:CwCrCwCr:CrCwCrCw:CwCrCwCr",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_filter,
|
||||
},
|
||||
|
||||
// 22
|
||||
// Constant signal
|
||||
{
|
||||
shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_constant_signal,
|
||||
},
|
||||
|
||||
// 23
|
||||
// Display
|
||||
{
|
||||
shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_display,
|
||||
},
|
||||
|
||||
// 24 Logic gates
|
||||
{
|
||||
shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_logic_gates,
|
||||
},
|
||||
|
||||
// 25 Virtual Processing
|
||||
{
|
||||
shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_virtual_processing,
|
||||
},
|
||||
|
||||
// 26 Freeplay
|
||||
{
|
||||
shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw",
|
||||
required: 50000,
|
||||
reward: enumHubGoalRewards.reward_freeplay,
|
||||
},
|
||||
]),
|
||||
];
|
||||
|
||||
if (G_IS_DEV) {
|
||||
levelDefinitions.forEach(({ shape }) => {
|
||||
try {
|
||||
ShapeDefinition.fromShortKey(shape);
|
||||
} catch (ex) {
|
||||
throw new Error("Invalid tutorial goal: '" + ex + "' for shape" + shape);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class RegularGameMode extends GameMode {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
}
|
||||
|
||||
getUpgrades() {
|
||||
return cachedUpgrades;
|
||||
}
|
||||
|
||||
getBlueprintShapeKey() {
|
||||
return blueprintShape;
|
||||
}
|
||||
|
||||
getLevelDefinitions() {
|
||||
return levelDefinitions;
|
||||
}
|
||||
}
|
@ -1,221 +1,225 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { Signal } from "../core/signal";
|
||||
import { RandomNumberGenerator } from "../core/rng";
|
||||
import { createLogger } from "../core/logging";
|
||||
|
||||
// Type hints
|
||||
/* typehints:start */
|
||||
import { GameTime } from "./time/game_time";
|
||||
import { EntityManager } from "./entity_manager";
|
||||
import { GameSystemManager } from "./game_system_manager";
|
||||
import { GameHUD } from "./hud/hud";
|
||||
import { MapView } from "./map_view";
|
||||
import { Camera } from "./camera";
|
||||
import { InGameState } from "../states/ingame";
|
||||
import { AutomaticSave } from "./automatic_save";
|
||||
import { Application } from "../application";
|
||||
import { SoundProxy } from "./sound_proxy";
|
||||
import { Savegame } from "../savegame/savegame";
|
||||
import { GameLogic } from "./logic";
|
||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||
import { HubGoals } from "./hub_goals";
|
||||
import { BufferMaintainer } from "../core/buffer_maintainer";
|
||||
import { ProductionAnalytics } from "./production_analytics";
|
||||
import { Entity } from "./entity";
|
||||
import { ShapeDefinition } from "./shape_definition";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { DynamicTickrate } from "./dynamic_tickrate";
|
||||
import { KeyActionMapper } from "./key_action_mapper";
|
||||
import { Vector } from "../core/vector";
|
||||
/* typehints:end */
|
||||
|
||||
const logger = createLogger("game/root");
|
||||
|
||||
/** @type {Array<Layer>} */
|
||||
export const layers = ["regular", "wires"];
|
||||
|
||||
/**
|
||||
* The game root is basically the whole game state at a given point,
|
||||
* combining all important classes. We don't have globals, but this
|
||||
* class is passed to almost all game classes.
|
||||
*/
|
||||
export class GameRoot {
|
||||
/**
|
||||
* Constructs a new game root
|
||||
* @param {Application} app
|
||||
*/
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
/** @type {Savegame} */
|
||||
this.savegame = null;
|
||||
|
||||
/** @type {InGameState} */
|
||||
this.gameState = null;
|
||||
|
||||
/** @type {KeyActionMapper} */
|
||||
this.keyMapper = null;
|
||||
|
||||
// Store game dimensions
|
||||
this.gameWidth = 500;
|
||||
this.gameHeight = 500;
|
||||
|
||||
// Stores whether the current session is a fresh game (true), or was continued (false)
|
||||
/** @type {boolean} */
|
||||
this.gameIsFresh = true;
|
||||
|
||||
// Stores whether the logic is already initialized
|
||||
/** @type {boolean} */
|
||||
this.logicInitialized = false;
|
||||
|
||||
// Stores whether the game is already initialized, that is, all systems etc have been created
|
||||
/** @type {boolean} */
|
||||
this.gameInitialized = false;
|
||||
|
||||
/**
|
||||
* Whether a bulk operation is running
|
||||
*/
|
||||
this.bulkOperationRunning = false;
|
||||
|
||||
//////// Other properties ///////
|
||||
|
||||
/** @type {Camera} */
|
||||
this.camera = null;
|
||||
|
||||
/** @type {HTMLCanvasElement} */
|
||||
this.canvas = null;
|
||||
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
this.context = null;
|
||||
|
||||
/** @type {MapView} */
|
||||
this.map = null;
|
||||
|
||||
/** @type {GameLogic} */
|
||||
this.logic = null;
|
||||
|
||||
/** @type {EntityManager} */
|
||||
this.entityMgr = null;
|
||||
|
||||
/** @type {GameHUD} */
|
||||
this.hud = null;
|
||||
|
||||
/** @type {GameSystemManager} */
|
||||
this.systemMgr = null;
|
||||
|
||||
/** @type {GameTime} */
|
||||
this.time = null;
|
||||
|
||||
/** @type {HubGoals} */
|
||||
this.hubGoals = null;
|
||||
|
||||
/** @type {BufferMaintainer} */
|
||||
this.buffers = null;
|
||||
|
||||
/** @type {AutomaticSave} */
|
||||
this.automaticSave = null;
|
||||
|
||||
/** @type {SoundProxy} */
|
||||
this.soundProxy = null;
|
||||
|
||||
/** @type {ShapeDefinitionManager} */
|
||||
this.shapeDefinitionMgr = null;
|
||||
|
||||
/** @type {ProductionAnalytics} */
|
||||
this.productionAnalytics = null;
|
||||
|
||||
/** @type {DynamicTickrate} */
|
||||
this.dynamicTickrate = null;
|
||||
|
||||
/** @type {Layer} */
|
||||
this.currentLayer = "regular";
|
||||
|
||||
this.signals = {
|
||||
// Entities
|
||||
entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityAdded: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityChanged: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityGotNewComponent: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityComponentRemoved: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityQueuedForDestroy: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityDestroyed: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
|
||||
// Global
|
||||
resized: /** @type {TypedSignal<[number, number]>} */ (new Signal()),
|
||||
readyToRender: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
aboutToDestruct: /** @type {TypedSignal<[]>} */ new Signal(),
|
||||
|
||||
// Game Hooks
|
||||
gameSaved: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got saved
|
||||
gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored
|
||||
|
||||
gameFrameStarted: /** @type {TypedSignal<[]>} */ (new Signal()), // New frame
|
||||
|
||||
storyGoalCompleted: /** @type {TypedSignal<[number, string]>} */ (new Signal()),
|
||||
upgradePurchased: /** @type {TypedSignal<[string]>} */ (new Signal()),
|
||||
|
||||
// Called right after game is initialized
|
||||
postLoadHook: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
|
||||
shapeDelivered: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
|
||||
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
|
||||
|
||||
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
|
||||
editModeChanged: /** @type {TypedSignal<[Layer]>} */ (new Signal()),
|
||||
|
||||
// Called to check if an entity can be placed, second parameter is an additional offset.
|
||||
// Use to introduce additional placement checks
|
||||
prePlacementCheck: /** @type {TypedSignal<[Entity, Vector]>} */ (new Signal()),
|
||||
|
||||
// Called before actually placing an entity, use to perform additional logic
|
||||
// for freeing space before actually placing.
|
||||
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
};
|
||||
|
||||
// RNG's
|
||||
/** @type {Object.<string, Object.<string, RandomNumberGenerator>>} */
|
||||
this.rngs = {};
|
||||
|
||||
// Work queue
|
||||
this.queue = {
|
||||
requireRedraw: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructs the game root
|
||||
*/
|
||||
destruct() {
|
||||
logger.log("destructing root");
|
||||
this.signals.aboutToDestruct.dispatch();
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the whole root and removes all properties
|
||||
*/
|
||||
reset() {
|
||||
if (this.signals) {
|
||||
// Destruct all signals
|
||||
for (let i = 0; i < this.signals.length; ++i) {
|
||||
this.signals[i].removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hud) {
|
||||
this.hud.cleanup();
|
||||
}
|
||||
if (this.camera) {
|
||||
this.camera.cleanup();
|
||||
}
|
||||
|
||||
// Finally free all properties
|
||||
for (let prop in this) {
|
||||
if (this.hasOwnProperty(prop)) {
|
||||
delete this[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { Signal } from "../core/signal";
|
||||
import { RandomNumberGenerator } from "../core/rng";
|
||||
import { createLogger } from "../core/logging";
|
||||
|
||||
// Type hints
|
||||
/* typehints:start */
|
||||
import { GameTime } from "./time/game_time";
|
||||
import { EntityManager } from "./entity_manager";
|
||||
import { GameSystemManager } from "./game_system_manager";
|
||||
import { GameHUD } from "./hud/hud";
|
||||
import { MapView } from "./map_view";
|
||||
import { Camera } from "./camera";
|
||||
import { InGameState } from "../states/ingame";
|
||||
import { AutomaticSave } from "./automatic_save";
|
||||
import { Application } from "../application";
|
||||
import { SoundProxy } from "./sound_proxy";
|
||||
import { Savegame } from "../savegame/savegame";
|
||||
import { GameLogic } from "./logic";
|
||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||
import { HubGoals } from "./hub_goals";
|
||||
import { BufferMaintainer } from "../core/buffer_maintainer";
|
||||
import { ProductionAnalytics } from "./production_analytics";
|
||||
import { Entity } from "./entity";
|
||||
import { ShapeDefinition } from "./shape_definition";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { DynamicTickrate } from "./dynamic_tickrate";
|
||||
import { KeyActionMapper } from "./key_action_mapper";
|
||||
import { Vector } from "../core/vector";
|
||||
import { GameMode } from "./game_mode";
|
||||
/* typehints:end */
|
||||
|
||||
const logger = createLogger("game/root");
|
||||
|
||||
/** @type {Array<Layer>} */
|
||||
export const layers = ["regular", "wires"];
|
||||
|
||||
/**
|
||||
* The game root is basically the whole game state at a given point,
|
||||
* combining all important classes. We don't have globals, but this
|
||||
* class is passed to almost all game classes.
|
||||
*/
|
||||
export class GameRoot {
|
||||
/**
|
||||
* Constructs a new game root
|
||||
* @param {Application} app
|
||||
*/
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
/** @type {Savegame} */
|
||||
this.savegame = null;
|
||||
|
||||
/** @type {InGameState} */
|
||||
this.gameState = null;
|
||||
|
||||
/** @type {KeyActionMapper} */
|
||||
this.keyMapper = null;
|
||||
|
||||
// Store game dimensions
|
||||
this.gameWidth = 500;
|
||||
this.gameHeight = 500;
|
||||
|
||||
// Stores whether the current session is a fresh game (true), or was continued (false)
|
||||
/** @type {boolean} */
|
||||
this.gameIsFresh = true;
|
||||
|
||||
// Stores whether the logic is already initialized
|
||||
/** @type {boolean} */
|
||||
this.logicInitialized = false;
|
||||
|
||||
// Stores whether the game is already initialized, that is, all systems etc have been created
|
||||
/** @type {boolean} */
|
||||
this.gameInitialized = false;
|
||||
|
||||
/**
|
||||
* Whether a bulk operation is running
|
||||
*/
|
||||
this.bulkOperationRunning = false;
|
||||
|
||||
//////// Other properties ///////
|
||||
|
||||
/** @type {Camera} */
|
||||
this.camera = null;
|
||||
|
||||
/** @type {HTMLCanvasElement} */
|
||||
this.canvas = null;
|
||||
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
this.context = null;
|
||||
|
||||
/** @type {MapView} */
|
||||
this.map = null;
|
||||
|
||||
/** @type {GameLogic} */
|
||||
this.logic = null;
|
||||
|
||||
/** @type {EntityManager} */
|
||||
this.entityMgr = null;
|
||||
|
||||
/** @type {GameHUD} */
|
||||
this.hud = null;
|
||||
|
||||
/** @type {GameSystemManager} */
|
||||
this.systemMgr = null;
|
||||
|
||||
/** @type {GameTime} */
|
||||
this.time = null;
|
||||
|
||||
/** @type {HubGoals} */
|
||||
this.hubGoals = null;
|
||||
|
||||
/** @type {BufferMaintainer} */
|
||||
this.buffers = null;
|
||||
|
||||
/** @type {AutomaticSave} */
|
||||
this.automaticSave = null;
|
||||
|
||||
/** @type {SoundProxy} */
|
||||
this.soundProxy = null;
|
||||
|
||||
/** @type {ShapeDefinitionManager} */
|
||||
this.shapeDefinitionMgr = null;
|
||||
|
||||
/** @type {ProductionAnalytics} */
|
||||
this.productionAnalytics = null;
|
||||
|
||||
/** @type {DynamicTickrate} */
|
||||
this.dynamicTickrate = null;
|
||||
|
||||
/** @type {Layer} */
|
||||
this.currentLayer = "regular";
|
||||
|
||||
/** @type {GameMode} */
|
||||
this.gameMode = null;
|
||||
|
||||
this.signals = {
|
||||
// Entities
|
||||
entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityAdded: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityChanged: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityGotNewComponent: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityComponentRemoved: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityQueuedForDestroy: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
entityDestroyed: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
|
||||
// Global
|
||||
resized: /** @type {TypedSignal<[number, number]>} */ (new Signal()),
|
||||
readyToRender: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
aboutToDestruct: /** @type {TypedSignal<[]>} */ new Signal(),
|
||||
|
||||
// Game Hooks
|
||||
gameSaved: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got saved
|
||||
gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored
|
||||
|
||||
gameFrameStarted: /** @type {TypedSignal<[]>} */ (new Signal()), // New frame
|
||||
|
||||
storyGoalCompleted: /** @type {TypedSignal<[number, string]>} */ (new Signal()),
|
||||
upgradePurchased: /** @type {TypedSignal<[string]>} */ (new Signal()),
|
||||
|
||||
// Called right after game is initialized
|
||||
postLoadHook: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
|
||||
shapeDelivered: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
|
||||
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
|
||||
|
||||
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
|
||||
editModeChanged: /** @type {TypedSignal<[Layer]>} */ (new Signal()),
|
||||
|
||||
// Called to check if an entity can be placed, second parameter is an additional offset.
|
||||
// Use to introduce additional placement checks
|
||||
prePlacementCheck: /** @type {TypedSignal<[Entity, Vector]>} */ (new Signal()),
|
||||
|
||||
// Called before actually placing an entity, use to perform additional logic
|
||||
// for freeing space before actually placing.
|
||||
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
};
|
||||
|
||||
// RNG's
|
||||
/** @type {Object.<string, Object.<string, RandomNumberGenerator>>} */
|
||||
this.rngs = {};
|
||||
|
||||
// Work queue
|
||||
this.queue = {
|
||||
requireRedraw: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructs the game root
|
||||
*/
|
||||
destruct() {
|
||||
logger.log("destructing root");
|
||||
this.signals.aboutToDestruct.dispatch();
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the whole root and removes all properties
|
||||
*/
|
||||
reset() {
|
||||
if (this.signals) {
|
||||
// Destruct all signals
|
||||
for (let i = 0; i < this.signals.length; ++i) {
|
||||
this.signals[i].removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hud) {
|
||||
this.hud.cleanup();
|
||||
}
|
||||
if (this.camera) {
|
||||
this.camera.cleanup();
|
||||
}
|
||||
|
||||
// Finally free all properties
|
||||
for (let prop in this) {
|
||||
if (this.hasOwnProperty(prop)) {
|
||||
delete this[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,212 +0,0 @@
|
||||
import { IS_DEMO } from "../core/config";
|
||||
import { findNiceIntegerValue } from "../core/utils";
|
||||
import { ShapeDefinition } from "./shape_definition";
|
||||
|
||||
export const preparementShape = "CpRpCp--:SwSwSwSw";
|
||||
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, 1, 1];
|
||||
|
||||
const numEndgameUpgrades = !IS_DEMO ? 20 - fixedImprovements.length - 1 : 0;
|
||||
|
||||
function generateEndgameUpgrades() {
|
||||
return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({
|
||||
required: [
|
||||
{ shape: preparementShape, 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
|
||||
* }} 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: 60 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "--CuCu--", amount: 500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CpCpCpCp", amount: 1000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 6000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 25000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
miner: [
|
||||
{
|
||||
required: [{ shape: "RuRuRuRu", amount: 300 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "Cu------", amount: 800 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "ScScScSc", amount: 3500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
processors: [
|
||||
{
|
||||
required: [{ shape: "SuSuSuSu", amount: 500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "RuRu----", amount: 600 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CgScScCg", amount: 3500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 25000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 50000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
painting: [
|
||||
{
|
||||
required: [{ shape: "RbRb----", amount: 600 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WrWrWrWr", amount: 3800 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 50000 }],
|
||||
},
|
||||
{
|
||||
required: [{ shape: preparementShape, amount: 25000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
{
|
||||
required: [
|
||||
{ shape: preparementShape, amount: 25000 },
|
||||
{ shape: finalGameShape, amount: 50000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
};
|
||||
|
||||
// Tiers need % of the previous tier as requirement too
|
||||
const tierGrowth = 2.5;
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in new issue