Puzzle mode (#1135)
* Add mode button to main menu * [WIP] Add mode menu. Add factory-based gameMode creation * Add savefile migration, serialize, deserialize * Add hidden HUD elements, zone, and zoom, boundary constraints * Clean up lint issues * Add building, HUD exclusion, building exclusion, and refactor - [WIP] Add ConstantProducer building that combines ConstantSignal and ItemProducer functionality. Currently using temp assets. - Add pre-placement check to the zone - Use Rectangles for zone and boundary - Simplify zone drawing - Account for exclusion in savegame data - [WIP] Add puzzle play and edit buttons in puzzle mode menu * [WIP] Add building, component, and systems for producing and accepting user-specified items and checking goal criteria * Add ingame puzzle mode UI elements - Add minimal menus in puzzle mode for back, next navigation - Add lower menu for changing zone dimenensions Co-authored-by: Greg Considine <gconsidine@users.noreply.github.com>
BIN
res/ui/building_icons/constant_producer.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
res/ui/building_icons/goal_acceptor.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
res/ui/building_tutorials/constant_producer.png
Normal file
After Width: | Height: | Size: 271 KiB |
BIN
res/ui/building_tutorials/goal_acceptor.png
Normal file
After Width: | Height: | Size: 271 KiB |
BIN
res_raw/sprites/blueprints/constant_producer.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
res_raw/sprites/blueprints/goal_acceptor.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
res_raw/sprites/buildings/constant_producer.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
res_raw/sprites/buildings/goal_acceptor.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
54
src/css/ingame_hud/mode_menu.scss
Normal file
@ -0,0 +1,54 @@
|
||||
#ingame_HUD_ModeMenu {
|
||||
position: absolute;
|
||||
@include S(bottom, 10px);
|
||||
@include S(left, 10px);
|
||||
display: flex;
|
||||
|
||||
backdrop-filter: blur(D(1px));
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
backdrop-filter: blur(D(1px));
|
||||
padding: D(3px);
|
||||
|
||||
> button,
|
||||
> .button {
|
||||
@include PlainText;
|
||||
@include IncreasedClickArea(0px);
|
||||
background: green;
|
||||
@include S(width, 30px);
|
||||
@include S(height, 30px);
|
||||
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.12s ease-in-out;
|
||||
transition-property: opacity, transform;
|
||||
|
||||
display: inline-flex;
|
||||
background: center center / 70% no-repeat;
|
||||
grid-row: 1;
|
||||
|
||||
&.pressed {
|
||||
transform: scale(0.9) !important;
|
||||
}
|
||||
|
||||
opacity: 0.7;
|
||||
&:hover {
|
||||
opacity: 0.9 !important;
|
||||
}
|
||||
|
||||
@include DarkThemeInvert;
|
||||
|
||||
&.settings {
|
||||
& {
|
||||
/* @load-async */
|
||||
background-image: uiResource("icons/settings_menu_settings.png");
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
35
src/css/ingame_hud/mode_menu_back.scss
Normal file
@ -0,0 +1,35 @@
|
||||
#ingame_HUD_ModeMenuBack {
|
||||
position: absolute;
|
||||
@include S(top, 10px);
|
||||
@include S(left, 10px);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
backdrop-filter: blur(D(1px));
|
||||
padding: D(3px);
|
||||
|
||||
> .button {
|
||||
@include PlainText;
|
||||
@include IncreasedClickArea(0px);
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
color: #333438;
|
||||
transition: all 0.12s ease-in-out;
|
||||
transition-property: opacity, transform;
|
||||
|
||||
opacity: 0.8;
|
||||
&:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
&.pressed {
|
||||
transform: scale(0.9) !important;
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
42
src/css/ingame_hud/mode_menu_next.scss
Normal file
@ -0,0 +1,42 @@
|
||||
#ingame_HUD_ModeMenuNext {
|
||||
position: absolute;
|
||||
@include S(top, 10px);
|
||||
@include S(right, 10px);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
backdrop-filter: blur(D(1px));
|
||||
padding: D(3px);
|
||||
|
||||
> .button {
|
||||
@include ButtonText;
|
||||
@include IncreasedClickArea(0px);
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
color: #333438;
|
||||
transition: all 0.12s ease-in-out;
|
||||
transition-property: opacity, transform;
|
||||
|
||||
opacity: 0.8;
|
||||
&:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
&.pressed {
|
||||
transform: scale(0.9) !important;
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
@include SuperSmallText;
|
||||
@include S(font-size, 7px);
|
||||
@include S(width, 150px);
|
||||
text-align: right;
|
||||
}
|
||||
}
|
47
src/css/ingame_hud/mode_settings.scss
Normal file
@ -0,0 +1,47 @@
|
||||
#ingame_HUD_ModeSettings {
|
||||
position: absolute;
|
||||
background: $ingameHudBg;
|
||||
@include S(padding, 10px);
|
||||
@include S(bottom, 50px);
|
||||
@include S(left, 15px);
|
||||
|
||||
@include SuperSmallText;
|
||||
color: #eee;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .section {
|
||||
> label {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.plusMinus {
|
||||
@include S(margin-top, 5px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto auto;
|
||||
align-items: center;
|
||||
@include S(grid-gap, 5px);
|
||||
|
||||
label {
|
||||
@include S(margin-right, 10px);
|
||||
}
|
||||
|
||||
button {
|
||||
@include PlainText;
|
||||
@include S(padding, 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include S(width, 15px);
|
||||
@include S(height, 15px);
|
||||
@include IncreasedClickArea(0px);
|
||||
}
|
||||
|
||||
|
||||
.value {
|
||||
text-align: center;
|
||||
@include S(min-width, 15px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -55,6 +55,10 @@
|
||||
@import "ingame_hud/sandbox_controller";
|
||||
@import "ingame_hud/standalone_advantages";
|
||||
@import "ingame_hud/cat_memes";
|
||||
@import "ingame_hud/mode_menu_back";
|
||||
@import "ingame_hud/mode_menu_next";
|
||||
@import "ingame_hud/mode_menu";
|
||||
@import "ingame_hud/mode_settings";
|
||||
|
||||
// prettier-ignore
|
||||
$elements:
|
||||
@ -71,6 +75,10 @@ ingame_HUD_PlacerVariants,
|
||||
ingame_HUD_PinnedShapes,
|
||||
ingame_HUD_GameMenu,
|
||||
ingame_HUD_KeybindingOverlay,
|
||||
ingame_HUD_ModeMenuBack,
|
||||
ingame_HUD_ModeMenuNext,
|
||||
ingame_HUD_ModeMenu,
|
||||
ingame_HUD_ModeSettings,
|
||||
ingame_HUD_Notifications,
|
||||
ingame_HUD_DebugInfo,
|
||||
ingame_HUD_EntityDebugger,
|
||||
@ -113,6 +121,8 @@ body.uiHidden {
|
||||
#ingame_HUD_PlacementHints,
|
||||
#ingame_HUD_GameMenu,
|
||||
#ingame_HUD_PinnedShapes,
|
||||
#ingame_HUD_ModeMenuBack,
|
||||
#ingame_HUD_ModeMenuNext,
|
||||
#ingame_HUD_Notifications,
|
||||
#ingame_HUD_TutorialHints,
|
||||
#ingame_HUD_Waypoints,
|
||||
|
@ -1,6 +1,6 @@
|
||||
$buildings: belt, cutter, miner, mixer, painter, rotater, balancer, stacker, trash, underground_belt, wire,
|
||||
constant_signal, logic_gate, lever, filter, wire_tunnel, display, virtual_processor, reader, storage,
|
||||
transistor, analyzer, comparator, item_producer;
|
||||
transistor, analyzer, comparator, item_producer, constant_producer, goal_acceptor;
|
||||
|
||||
@each $building in $buildings {
|
||||
[data-icon="building_icons/#{$building}.png"] {
|
||||
@ -13,7 +13,8 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2,
|
||||
cutter, cutter-quad, rotater, rotater-ccw, stacker, mixer, painter-double, painter-quad, trash, storage,
|
||||
reader, rotater-rotate180, display, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not,
|
||||
logic_gate-xor, analyzer, virtual_processor-rotater, virtual_processor-unstacker, item_producer,
|
||||
virtual_processor-stacker, virtual_processor-painter, wire-second, painter, painter-mirrored, comparator;
|
||||
constant_producer, virtual_processor-stacker, virtual_processor-painter, wire-second, painter,
|
||||
painter-mirrored, comparator, goal_acceptor;
|
||||
@each $building in $buildingsAndVariants {
|
||||
[data-icon="building_tutorials/#{$building}.png"] {
|
||||
/* @load-async */
|
||||
|
@ -242,6 +242,16 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modeButtons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@include S(grid-column-gap, 10px);
|
||||
align-items: start;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.browserWarning {
|
||||
@include S(margin-bottom, 10px);
|
||||
background-color: $colorRedBright;
|
||||
@ -285,6 +295,18 @@
|
||||
@include S(margin-left, 15px);
|
||||
}
|
||||
|
||||
.playModeButton {
|
||||
@include IncreasedClickArea(0px);
|
||||
@include S(margin-top, 15px);
|
||||
@include S(margin-left, 15px);
|
||||
}
|
||||
|
||||
.editModeButton {
|
||||
@include IncreasedClickArea(0px);
|
||||
@include S(margin-top, 15px);
|
||||
@include S(margin-left, 15px);
|
||||
}
|
||||
|
||||
.savegames {
|
||||
@include S(max-height, 105px);
|
||||
overflow-y: auto;
|
||||
@ -439,6 +461,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bottomContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
@include S(padding-top, 10px);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@include S(grid-column-gap, 10px);
|
||||
align-items: start;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
|
@ -62,6 +62,9 @@ export default {
|
||||
// Allows unlocked achievements to be logged to console in the local build
|
||||
// testAchievements: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Enables use of (some) existing flags within the puzzle mode context
|
||||
// testPuzzleMode: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Disables the automatic switch to an overview when zooming out
|
||||
// disableMapOverview: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
@ -5,6 +5,7 @@ import { Factory } from "./factory";
|
||||
* @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed
|
||||
* @typedef {import("../game/component").Component} Component
|
||||
* @typedef {import("../game/base_item").BaseItem} BaseItem
|
||||
* @typedef {import("../game/game_mode").GameMode} GameMode
|
||||
* @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding
|
||||
|
||||
|
||||
@ -19,6 +20,9 @@ export let gBuildingsByCategory = null;
|
||||
/** @type {FactoryTemplate<Component>} */
|
||||
export let gComponentRegistry = new Factory("component");
|
||||
|
||||
/** @type {FactoryTemplate<GameMode>} */
|
||||
export let gGameModeRegistry = new Factory("gameMode");
|
||||
|
||||
/** @type {FactoryTemplate<BaseGameSpeed>} */
|
||||
export let gGameSpeedRegistry = new Factory("gamespeed");
|
||||
|
||||
|
41
src/js/game/buildings/constant_producer.js
Normal file
@ -0,0 +1,41 @@
|
||||
/* typehints:start */
|
||||
import { Entity } from "../entity";
|
||||
/* typehints:end */
|
||||
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumConstantSignalType, ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumItemProducerType, ItemProducerComponent } from "../components/item_producer";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
|
||||
export class MetaConstantProducerBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("constant_producer");
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#bfd630";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(
|
||||
new ItemEjectorComponent({
|
||||
slots: [{ pos: new Vector(0, 0), direction: enumDirection.top }],
|
||||
})
|
||||
);
|
||||
entity.addComponent(
|
||||
new ItemProducerComponent({
|
||||
type: enumItemProducerType.wireless,
|
||||
})
|
||||
);
|
||||
entity.addComponent(
|
||||
new ConstantSignalComponent({
|
||||
type: enumConstantSignalType.wireless,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
52
src/js/game/buildings/goal_acceptor.js
Normal file
@ -0,0 +1,52 @@
|
||||
/* typehints:start */
|
||||
import { Entity } from "../entity";
|
||||
/* typehints:end */
|
||||
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumBeltReaderType, BeltReaderComponent } from "../components/belt_reader";
|
||||
import { GoalAcceptorComponent } from "../components/goal_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
|
||||
export class MetaGoalAcceptorBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("goal_acceptor");
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#ce418a";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(
|
||||
new ItemAcceptorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.top],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new ItemProcessorComponent({
|
||||
processorType: enumItemProcessorTypes.goal,
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(
|
||||
new BeltReaderComponent({
|
||||
type: enumBeltReaderType.wireless,
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(new GoalAcceptorComponent({}));
|
||||
}
|
||||
}
|
@ -39,6 +39,6 @@ export class MetaItemProducerBuilding extends MetaBuilding {
|
||||
],
|
||||
})
|
||||
);
|
||||
entity.addComponent(new ItemProducerComponent());
|
||||
entity.addComponent(new ItemProducerComponent({}));
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,6 @@ export class MetaReaderBuilding extends MetaBuilding {
|
||||
})
|
||||
);
|
||||
|
||||
entity.addComponent(new BeltReaderComponent());
|
||||
entity.addComponent(new BeltReaderComponent({}));
|
||||
}
|
||||
}
|
||||
|
@ -392,13 +392,20 @@ export class Camera extends BasicSerializableObject {
|
||||
return rect.containsPoint(point.x, point.y);
|
||||
}
|
||||
|
||||
getMaximumZoom() {
|
||||
return this.root.gameMode.getMaximumZoom() * this.root.app.platformWrapper.getScreenScale();
|
||||
}
|
||||
|
||||
getMinimumZoom() {
|
||||
return this.root.gameMode.getMinimumZoom() * this.root.app.platformWrapper.getScreenScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if we can further zoom in
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canZoomIn() {
|
||||
const maxLevel = this.root.app.platformWrapper.getMaximumZoom();
|
||||
return this.zoomLevel <= maxLevel - 0.01;
|
||||
return this.zoomLevel <= this.getMaximumZoom() - 0.01;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -406,8 +413,7 @@ export class Camera extends BasicSerializableObject {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canZoomOut() {
|
||||
const minLevel = this.root.app.platformWrapper.getMinimumZoom();
|
||||
return this.zoomLevel >= minLevel + 0.01;
|
||||
return this.zoomLevel >= this.getMinimumZoom() + 0.01;
|
||||
}
|
||||
|
||||
// EVENTS
|
||||
@ -743,17 +749,30 @@ export class Camera extends BasicSerializableObject {
|
||||
if (G_IS_DEV && globalConfig.debug.disableZoomLimits) {
|
||||
return;
|
||||
}
|
||||
const wrapper = this.root.app.platformWrapper;
|
||||
|
||||
assert(Number.isFinite(this.zoomLevel), "Invalid zoom level *before* clamp: " + this.zoomLevel);
|
||||
this.zoomLevel = clamp(this.zoomLevel, wrapper.getMinimumZoom(), wrapper.getMaximumZoom());
|
||||
this.zoomLevel = clamp(this.zoomLevel, this.getMinimumZoom(), this.getMaximumZoom());
|
||||
assert(Number.isFinite(this.zoomLevel), "Invalid zoom level *after* clamp: " + this.zoomLevel);
|
||||
|
||||
if (this.desiredZoom) {
|
||||
this.desiredZoom = clamp(this.desiredZoom, wrapper.getMinimumZoom(), wrapper.getMaximumZoom());
|
||||
this.desiredZoom = clamp(this.desiredZoom, this.getMinimumZoom(), this.getMaximumZoom());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps x, y position within set boundaries
|
||||
* @param {Vector} vector
|
||||
*/
|
||||
clampToBounds(vector) {
|
||||
if (!this.root.gameMode.hasBounds()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = this.root.gameMode.getBounds().allScaled(globalConfig.tileSize);
|
||||
|
||||
vector.x = clamp(vector.x, bounds.x, bounds.x + bounds.w);
|
||||
vector.y = clamp(vector.y, bounds.y, bounds.y + bounds.h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the camera
|
||||
* @param {number} dt Delta time in milliseconds
|
||||
@ -857,6 +876,7 @@ export class Camera extends BasicSerializableObject {
|
||||
// Panning
|
||||
this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06);
|
||||
this.center = this.center.add(this.currentPan.multiplyScalar((0.5 * dt) / this.zoomLevel));
|
||||
this.clampToBounds(this.center);
|
||||
}
|
||||
}
|
||||
|
||||
@ -921,6 +941,8 @@ export class Camera extends BasicSerializableObject {
|
||||
((0.5 * dt) / this.zoomLevel) * this.root.app.settings.getMovementSpeed()
|
||||
)
|
||||
);
|
||||
|
||||
this.clampToBounds(this.center);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1006,6 +1028,8 @@ export class Camera extends BasicSerializableObject {
|
||||
|
||||
this.center.x += moveAmount * forceX * movementSpeed;
|
||||
this.center.y += moveAmount * forceY * movementSpeed;
|
||||
|
||||
this.clampToBounds(this.center);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import { DisplayComponent } from "./components/display";
|
||||
import { BeltReaderComponent } from "./components/belt_reader";
|
||||
import { FilterComponent } from "./components/filter";
|
||||
import { ItemProducerComponent } from "./components/item_producer";
|
||||
import { GoalAcceptorComponent } from "./components/goal_acceptor";
|
||||
|
||||
export function initComponentRegistry() {
|
||||
gComponentRegistry.register(StaticMapEntityComponent);
|
||||
@ -41,6 +42,7 @@ export function initComponentRegistry() {
|
||||
gComponentRegistry.register(BeltReaderComponent);
|
||||
gComponentRegistry.register(FilterComponent);
|
||||
gComponentRegistry.register(ItemProducerComponent);
|
||||
gComponentRegistry.register(GoalAcceptorComponent);
|
||||
|
||||
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
||||
|
||||
|
@ -3,6 +3,12 @@ import { BaseItem } from "../base_item";
|
||||
import { typeItemSingleton } from "../item_resolver";
|
||||
import { types } from "../../savegame/serialization";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumBeltReaderType = {
|
||||
wired: "wired",
|
||||
wireless: "wireless",
|
||||
};
|
||||
|
||||
export class BeltReaderComponent extends Component {
|
||||
static getId() {
|
||||
return "BeltReader";
|
||||
@ -10,13 +16,20 @@ export class BeltReaderComponent extends Component {
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
type: types.string,
|
||||
lastItem: types.nullable(typeItemSingleton),
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {string=} param0.type
|
||||
*/
|
||||
constructor({ type = enumBeltReaderType.wired }) {
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* Which items went through the reader, we only store the time
|
||||
* @type {Array<number>}
|
||||
@ -41,4 +54,8 @@ export class BeltReaderComponent extends Component {
|
||||
*/
|
||||
this.lastThroughputComputation = 0;
|
||||
}
|
||||
|
||||
isWireless() {
|
||||
return this.type === enumBeltReaderType.wireless;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,12 @@ import { Component } from "../component";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { typeItemSingleton } from "../item_resolver";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumConstantSignalType = {
|
||||
wired: "wired",
|
||||
wireless: "wireless",
|
||||
};
|
||||
|
||||
export class ConstantSignalComponent extends Component {
|
||||
static getId() {
|
||||
return "ConstantSignal";
|
||||
@ -11,6 +17,7 @@ export class ConstantSignalComponent extends Component {
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
type: types.string,
|
||||
signal: types.nullable(typeItemSingleton),
|
||||
};
|
||||
}
|
||||
@ -21,15 +28,22 @@ export class ConstantSignalComponent extends Component {
|
||||
*/
|
||||
copyAdditionalStateTo(otherComponent) {
|
||||
otherComponent.signal = this.signal;
|
||||
otherComponent.type = this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {string=} param0.type
|
||||
* @param {BaseItem=} param0.signal The signal to store
|
||||
*/
|
||||
constructor({ signal = null }) {
|
||||
constructor({ signal = null, type = enumConstantSignalType.wired }) {
|
||||
super();
|
||||
this.signal = signal;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
isWireless() {
|
||||
return this.type === enumConstantSignalType.wireless;
|
||||
}
|
||||
}
|
||||
|
22
src/js/game/components/goal_acceptor.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { BaseItem } from "../base_item";
|
||||
import { Component } from "../component";
|
||||
|
||||
export class GoalAcceptorComponent extends Component {
|
||||
static getId() {
|
||||
return "GoalAcceptor";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {BaseItem=} param0.item
|
||||
* @param {number=} param0.rate
|
||||
*/
|
||||
constructor({ item = null, rate = null }) {
|
||||
super();
|
||||
this.item = item;
|
||||
this.rate = rate;
|
||||
|
||||
this.achieved = false;
|
||||
this.achievedOnce = false;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ export const enumItemProcessorTypes = {
|
||||
hub: "hub",
|
||||
filter: "filter",
|
||||
reader: "reader",
|
||||
goal: "goal",
|
||||
};
|
||||
|
||||
/** @enum {string} */
|
||||
@ -104,7 +105,11 @@ export class ItemProcessorComponent extends Component {
|
||||
* @param {number} sourceSlot
|
||||
*/
|
||||
tryTakeItem(item, sourceSlot) {
|
||||
if (this.type === enumItemProcessorTypes.hub || this.type === enumItemProcessorTypes.trash) {
|
||||
if (
|
||||
this.type === enumItemProcessorTypes.hub ||
|
||||
this.type === enumItemProcessorTypes.trash ||
|
||||
this.type === enumItemProcessorTypes.goal
|
||||
) {
|
||||
// Hub has special logic .. not really nice but efficient.
|
||||
this.inputSlots.push({ item, sourceSlot });
|
||||
return true;
|
||||
|
@ -1,7 +1,33 @@
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { Component } from "../component";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumItemProducerType = {
|
||||
wired: "wired",
|
||||
wireless: "wireless",
|
||||
};
|
||||
|
||||
export class ItemProducerComponent extends Component {
|
||||
static getId() {
|
||||
return "ItemProducer";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
type: types.string,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {string=} param0.type
|
||||
*/
|
||||
constructor({ type = enumItemProducerType.wired }) {
|
||||
super();
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
isWireless() {
|
||||
return this.type === enumItemProducerType.wireless;
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import { KeyActionMapper } from "./key_action_mapper";
|
||||
import { GameLogic } from "./logic";
|
||||
import { MapView } from "./map_view";
|
||||
import { defaultBuildingVariant } from "./meta_building";
|
||||
import { RegularGameMode } from "./modes/regular";
|
||||
import { GameMode } from "./game_mode";
|
||||
import { ProductionAnalytics } from "./production_analytics";
|
||||
import { GameRoot } from "./root";
|
||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||
@ -82,7 +82,7 @@ export class GameCore {
|
||||
* @param {import("../states/ingame").InGameState} parentState
|
||||
* @param {Savegame} savegame
|
||||
*/
|
||||
initializeRoot(parentState, savegame) {
|
||||
initializeRoot(parentState, savegame, gameModeId) {
|
||||
// Construct the root element, this is the data representation of the game
|
||||
this.root = new GameRoot(this.app);
|
||||
this.root.gameState = parentState;
|
||||
@ -104,7 +104,7 @@ export class GameCore {
|
||||
root.dynamicTickrate = new DynamicTickrate(root);
|
||||
|
||||
// Init game mode
|
||||
root.gameMode = new RegularGameMode(root);
|
||||
root.gameMode = GameMode.create(root, gameModeId);
|
||||
|
||||
// Init classes
|
||||
root.camera = new Camera(root);
|
||||
@ -168,6 +168,10 @@ export class GameCore {
|
||||
this.root.gameIsFresh = true;
|
||||
this.root.map.seed = randomInt(0, 100000);
|
||||
|
||||
if (!this.root.gameMode.hasHub()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Place the hub
|
||||
const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
|
||||
root: this.root,
|
||||
|
@ -19,6 +19,7 @@ import { DisplayComponent } from "./components/display";
|
||||
import { BeltReaderComponent } from "./components/belt_reader";
|
||||
import { FilterComponent } from "./components/filter";
|
||||
import { ItemProducerComponent } from "./components/item_producer";
|
||||
import { GoalAcceptorComponent } from "./components/goal_acceptor";
|
||||
/* typehints:end */
|
||||
|
||||
/**
|
||||
@ -89,6 +90,9 @@ export class EntityComponentStorage {
|
||||
/** @type {ItemProducerComponent} */
|
||||
this.ItemProducer;
|
||||
|
||||
/** @type {GoalAcceptorComponent} */
|
||||
this.GoalAcceptor;
|
||||
|
||||
/* typehints:end */
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +1,184 @@
|
||||
/* typehints:start */
|
||||
import { enumHubGoalRewards } from "./tutorial_goals";
|
||||
import { GameRoot } from "./root";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
/* typehints:end */
|
||||
|
||||
import { GameRoot } from "./root";
|
||||
import { gGameModeRegistry } from "../core/global_registries";
|
||||
import { types, BasicSerializableObject } from "../savegame/serialization";
|
||||
|
||||
/** @typedef {{
|
||||
* shape: string,
|
||||
* amount: number
|
||||
* }} UpgradeRequirement */
|
||||
/** @enum {string} */
|
||||
export const enumGameModeIds = {
|
||||
puzzleEdit: "puzzleEditMode",
|
||||
puzzlePlay: "puzzlePlayMode",
|
||||
regular: "regularMode",
|
||||
};
|
||||
|
||||
/** @typedef {{
|
||||
* required: Array<UpgradeRequirement>
|
||||
* improvement?: number,
|
||||
* excludePrevious?: boolean
|
||||
* }} TierRequirement */
|
||||
/** @enum {string} */
|
||||
export const enumGameModeTypes = {
|
||||
default: "defaultModeType",
|
||||
puzzle: "puzzleModeType",
|
||||
};
|
||||
|
||||
/** @typedef {Array<TierRequirement>} UpgradeTiers */
|
||||
export class GameMode extends BasicSerializableObject {
|
||||
/** @returns {string} */
|
||||
static getId() {
|
||||
abstract;
|
||||
return "unknownMode";
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
static getType() {
|
||||
abstract;
|
||||
return "unknownType";
|
||||
}
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
* @param {string} [id=Regular]
|
||||
*/
|
||||
static create(root, id = enumGameModeIds.regular) {
|
||||
return new (gGameModeRegistry.findById(id))(root);
|
||||
}
|
||||
|
||||
/** @typedef {{
|
||||
* shape: string,
|
||||
* required: number,
|
||||
* reward: enumHubGoalRewards,
|
||||
* throughputOnly?: boolean
|
||||
* }} LevelDefinition */
|
||||
|
||||
export class GameMode {
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
super();
|
||||
this.root = root;
|
||||
this.hudParts = {};
|
||||
this.buildings = {};
|
||||
}
|
||||
|
||||
/** @returns {object} */
|
||||
serialize() {
|
||||
return {
|
||||
$: this.getId(),
|
||||
data: super.serialize(),
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {object} savedata */
|
||||
deserialize({ data }) {
|
||||
super.deserialize(data, this.root);
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
getId() {
|
||||
// @ts-ignore
|
||||
return this.constructor.getId();
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
getType() {
|
||||
// @ts-ignore
|
||||
return this.constructor.getType();
|
||||
}
|
||||
|
||||
setBuildings(buildings) {
|
||||
Object.assign(this.buildings, buildings);
|
||||
}
|
||||
|
||||
setHudParts(parts) {
|
||||
Object.assign(this.hudParts, parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {string} name - Class name of HUD Part
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsFreeplayAvailable() {
|
||||
isHudPartExcluded(name) {
|
||||
return this.hudParts[name] === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Class name of building
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isBuildingExcluded(name) {
|
||||
return this.buildings[name] === false;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
hasZone() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
hasHub() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
hasResources() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
hasBounds() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isZoneRestricted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isBoundaryRestricted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @returns {number} */
|
||||
getMinimumZoom() {
|
||||
return 0.1;
|
||||
}
|
||||
|
||||
/** @returns {number} */
|
||||
getMaximumZoom() {
|
||||
return 3.5;
|
||||
}
|
||||
|
||||
/** @returns {Object<string, Array>} */
|
||||
getUpgrades() {
|
||||
return {
|
||||
belt: [],
|
||||
miner: [],
|
||||
processors: [],
|
||||
painting: [],
|
||||
};
|
||||
}
|
||||
|
||||
/** @returns {?Rectangle} */
|
||||
getZone() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} w
|
||||
* @param {number} h
|
||||
*/
|
||||
expandZone(w = 0, h = 0) {
|
||||
abstract;
|
||||
return;
|
||||
}
|
||||
|
||||
/** @returns {?Rectangle} */
|
||||
getBounds() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @returns {array} */
|
||||
getLevelDefinitions() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
getIsFreeplayAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @returns {string} */
|
||||
getBlueprintShapeKey() {
|
||||
return "CbCbCbRb:CwCwCwCw";
|
||||
}
|
||||
}
|
||||
|
10
src/js/game/game_mode_registry.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { gGameModeRegistry } from "../core/global_registries";
|
||||
import { PuzzleEditGameMode } from "./modes/puzzle_edit";
|
||||
import { PuzzlePlayGameMode } from "./modes/puzzle_play";
|
||||
import { RegularGameMode } from "./modes/regular";
|
||||
|
||||
export function initGameModeRegistry() {
|
||||
gGameModeRegistry.register(PuzzleEditGameMode);
|
||||
gGameModeRegistry.register(PuzzlePlayGameMode);
|
||||
gGameModeRegistry.register(RegularGameMode);
|
||||
}
|
@ -24,6 +24,9 @@ import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays";
|
||||
import { BeltReaderSystem } from "./systems/belt_reader";
|
||||
import { FilterSystem } from "./systems/filter";
|
||||
import { ItemProducerSystem } from "./systems/item_producer";
|
||||
import { ConstantProducerSystem } from "./systems/constant_producer";
|
||||
import { GoalAcceptorSystem } from "./systems/goal_acceptor";
|
||||
import { ZoneSystem } from "./systems/zone";
|
||||
|
||||
const logger = createLogger("game_system_manager");
|
||||
|
||||
@ -100,6 +103,15 @@ export class GameSystemManager {
|
||||
/** @type {ItemProducerSystem} */
|
||||
itemProducer: null,
|
||||
|
||||
/** @type {ConstantProducerSystem} */
|
||||
ConstantProducer: null,
|
||||
|
||||
/** @type {GoalAcceptorSystem} */
|
||||
GoalAcceptor: null,
|
||||
|
||||
/** @type {ZoneSystem} */
|
||||
zone: null,
|
||||
|
||||
/* typehints:end */
|
||||
};
|
||||
this.systemUpdateOrder = [];
|
||||
@ -138,7 +150,9 @@ export class GameSystemManager {
|
||||
|
||||
add("itemEjector", ItemEjectorSystem);
|
||||
|
||||
add("mapResources", MapResourcesSystem);
|
||||
if (this.root.gameMode.hasResources()) {
|
||||
add("mapResources", MapResourcesSystem);
|
||||
}
|
||||
|
||||
add("hub", HubSystem);
|
||||
|
||||
@ -165,6 +179,14 @@ export class GameSystemManager {
|
||||
|
||||
add("itemProcessorOverlays", ItemProcessorOverlaysSystem);
|
||||
|
||||
add("constantProducer", ConstantProducerSystem);
|
||||
|
||||
add("goalAcceptor", GoalAcceptorSystem);
|
||||
|
||||
if (this.root.gameMode.hasZone()) {
|
||||
add("zone", ZoneSystem);
|
||||
}
|
||||
|
||||
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
|
||||
}
|
||||
|
||||
|
@ -500,6 +500,7 @@ export class HubGoals extends BasicSerializableObject {
|
||||
switch (processorType) {
|
||||
case enumItemProcessorTypes.trash:
|
||||
case enumItemProcessorTypes.hub:
|
||||
case enumItemProcessorTypes.goal:
|
||||
return 1e30;
|
||||
case enumItemProcessorTypes.balancer:
|
||||
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt * 2;
|
||||
|
@ -49,6 +49,10 @@ import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
|
||||
import { HUDCatMemes } from "./parts/cat_memes";
|
||||
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
||||
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
|
||||
import { HUDModeMenuBack } from "./parts/mode_menu_back";
|
||||
import { HUDModeMenuNext } from "./parts/mode_menu_next";
|
||||
import { HUDModeMenu } from "./parts/mode_menu";
|
||||
import { HUDModeSettings } from "./parts/mode_settings";
|
||||
|
||||
export class GameHUD {
|
||||
/**
|
||||
@ -74,45 +78,51 @@ export class GameHUD {
|
||||
unlockNotificationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||
};
|
||||
|
||||
this.parts = {
|
||||
buildingsToolbar: new HUDBuildingsToolbar(this.root),
|
||||
wiresToolbar: new HUDWiresToolbar(this.root),
|
||||
blueprintPlacer: new HUDBlueprintPlacer(this.root),
|
||||
buildingPlacer: new HUDBuildingPlacer(this.root),
|
||||
unlockNotification: new HUDUnlockNotification(this.root),
|
||||
gameMenu: new HUDGameMenu(this.root),
|
||||
massSelector: new HUDMassSelector(this.root),
|
||||
shop: new HUDShop(this.root),
|
||||
statistics: new HUDStatistics(this.root),
|
||||
waypoints: new HUDWaypoints(this.root),
|
||||
wireInfo: new HUDWireInfo(this.root),
|
||||
leverToggle: new HUDLeverToggle(this.root),
|
||||
constantSignalEdit: new HUDConstantSignalEdit(this.root),
|
||||
this.initParts({
|
||||
buildingsToolbar: HUDBuildingsToolbar,
|
||||
wiresToolbar: HUDWiresToolbar,
|
||||
blueprintPlacer: HUDBlueprintPlacer,
|
||||
buildingPlacer: HUDBuildingPlacer,
|
||||
unlockNotification: HUDUnlockNotification,
|
||||
gameMenu: HUDGameMenu,
|
||||
massSelector: HUDMassSelector,
|
||||
shop: HUDShop,
|
||||
statistics: HUDStatistics,
|
||||
waypoints: HUDWaypoints,
|
||||
wireInfo: HUDWireInfo,
|
||||
leverToggle: HUDLeverToggle,
|
||||
constantSignalEdit: HUDConstantSignalEdit,
|
||||
modeMenuBack: HUDModeMenuBack,
|
||||
modeMenuNext: HUDModeMenuNext,
|
||||
modeMenu: HUDModeMenu,
|
||||
modeSettings: HUDModeSettings,
|
||||
|
||||
// Must always exist
|
||||
pinnedShapes: new HUDPinnedShapes(this.root),
|
||||
notifications: new HUDNotifications(this.root),
|
||||
settingsMenu: new HUDSettingsMenu(this.root),
|
||||
debugInfo: new HUDDebugInfo(this.root),
|
||||
dialogs: new HUDModalDialogs(this.root),
|
||||
screenshotExporter: new HUDScreenshotExporter(this.root),
|
||||
shapeViewer: new HUDShapeViewer(this.root),
|
||||
pinnedShapes: HUDPinnedShapes,
|
||||
notifications: HUDNotifications,
|
||||
settingsMenu: HUDSettingsMenu,
|
||||
debugInfo: HUDDebugInfo,
|
||||
dialogs: HUDModalDialogs,
|
||||
screenshotExporter: HUDScreenshotExporter,
|
||||
shapeViewer: HUDShapeViewer,
|
||||
|
||||
wiresOverlay: new HUDWiresOverlay(this.root),
|
||||
layerPreview: new HUDLayerPreview(this.root),
|
||||
wiresOverlay: HUDWiresOverlay,
|
||||
layerPreview: HUDLayerPreview,
|
||||
|
||||
minerHighlight: new HUDMinerHighlight(this.root),
|
||||
tutorialVideoOffer: new HUDTutorialVideoOffer(this.root),
|
||||
minerHighlight: HUDMinerHighlight,
|
||||
tutorialVideoOffer: HUDTutorialVideoOffer,
|
||||
|
||||
// Typing hints
|
||||
/* typehints:start */
|
||||
/** @type {HUDChangesDebugger} */
|
||||
changesDebugger: null,
|
||||
/* typehints:end */
|
||||
};
|
||||
});
|
||||
|
||||
if (!IS_MOBILE) {
|
||||
this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root);
|
||||
if (!this.root.gameMode.isHudPartExcluded(HUDKeybindingOverlay.name)) {
|
||||
this.parts.keybindingOverlay = new HUDKeybindingOverlay(this.root);
|
||||
}
|
||||
}
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.enableEntityInspector) {
|
||||
@ -130,8 +140,13 @@ export class GameHUD {
|
||||
}
|
||||
|
||||
if (this.root.app.settings.getAllSettings().offerHints) {
|
||||
this.parts.tutorialHints = new HUDPartTutorialHints(this.root);
|
||||
this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root);
|
||||
if (!this.root.gameMode.isHudPartExcluded(HUDPartTutorialHints.name)) {
|
||||
this.parts.tutorialHints = new HUDPartTutorialHints(this.root);
|
||||
}
|
||||
|
||||
if (!this.root.gameMode.isHudPartExcluded(HUDInteractiveTutorial.name)) {
|
||||
this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.root.app.settings.getAllSettings().vignette) {
|
||||
@ -170,6 +185,21 @@ export class GameHUD {
|
||||
/* dev:end*/
|
||||
}
|
||||
|
||||
/** @param {object} parts */
|
||||
initParts(parts) {
|
||||
this.parts = {};
|
||||
|
||||
for (let key in parts) {
|
||||
const Part = parts[key];
|
||||
|
||||
if (!Part || this.root.gameMode.isHudPartExcluded(Part.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.parts[key] = new Part(this.root);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to close all overlays
|
||||
*/
|
||||
|
@ -23,8 +23,8 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
) {
|
||||
super(root);
|
||||
|
||||
this.primaryBuildings = primaryBuildings;
|
||||
this.secondaryBuildings = secondaryBuildings;
|
||||
this.primaryBuildings = this.filterBuildings(primaryBuildings);
|
||||
this.secondaryBuildings = this.filterBuildings(secondaryBuildings);
|
||||
this.visibilityCondition = visibilityCondition;
|
||||
this.htmlElementId = htmlElementId;
|
||||
this.layer = layer;
|
||||
@ -47,6 +47,24 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
this.element = makeDiv(parent, this.htmlElementId, ["ingame_buildingsToolbar"], "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<typeof MetaBuilding>} buildings
|
||||
* @returns {Array<typeof MetaBuilding>}
|
||||
*/
|
||||
filterBuildings(buildings) {
|
||||
const filtered = [];
|
||||
|
||||
for (let i = 0; i < buildings.length; i++) {
|
||||
if (this.root.gameMode.isBuildingExcluded(buildings[i].name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filtered.push(buildings[i]);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all buildings
|
||||
* @returns {Array<typeof MetaBuilding>}
|
||||
|
@ -15,12 +15,15 @@ import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
|
||||
import { HUDBaseToolbar } from "./base_toolbar";
|
||||
import { MetaStorageBuilding } from "../../buildings/storage";
|
||||
import { MetaItemProducerBuilding } from "../../buildings/item_producer";
|
||||
import { queryParamOptions } from "../../../core/query_parameters";
|
||||
import { MetaConstantProducerBuilding } from "../../buildings/constant_producer";
|
||||
import { MetaGoalAcceptorBuilding } from "../../buildings/goal_acceptor";
|
||||
|
||||
export class HUDBuildingsToolbar extends HUDBaseToolbar {
|
||||
constructor(root) {
|
||||
super(root, {
|
||||
primaryBuildings: [
|
||||
MetaConstantProducerBuilding,
|
||||
MetaGoalAcceptorBuilding,
|
||||
MetaBeltBuilding,
|
||||
MetaBalancerBuilding,
|
||||
MetaUndergroundBeltBuilding,
|
||||
@ -31,7 +34,7 @@ export class HUDBuildingsToolbar extends HUDBaseToolbar {
|
||||
MetaMixerBuilding,
|
||||
MetaPainterBuilding,
|
||||
MetaTrashBuilding,
|
||||
...(queryParamOptions.sandboxMode || G_IS_DEV ? [MetaItemProducerBuilding] : []),
|
||||
MetaItemProducerBuilding,
|
||||
],
|
||||
secondaryBuildings: [
|
||||
MetaStorageBuilding,
|
||||
|
17
src/js/game/hud/parts/mode_menu.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
|
||||
export class HUDModeMenu extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_ModeMenu");
|
||||
|
||||
this.settingsButton = makeDiv(this.element, null, ["button", "settings"]);
|
||||
this.trackClicks(this.settingsButton, this.openSettings);
|
||||
}
|
||||
|
||||
openSettings() {
|
||||
this.root.hud.parts.modeSettings.toggle();
|
||||
}
|
||||
|
||||
initialize() {}
|
||||
}
|
23
src/js/game/hud/parts/mode_menu_back.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
|
||||
export class HUDModeMenuBack extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
const key = this.root.gameMode.getId();
|
||||
|
||||
this.element = makeDiv(parent, "ingame_HUD_ModeMenuBack");
|
||||
this.button = document.createElement("button");
|
||||
this.button.classList.add("button");
|
||||
this.button.textContent = "⬅ " + T.ingame.modeMenu[key].back.title;
|
||||
this.element.appendChild(this.button);
|
||||
|
||||
this.trackClicks(this.button, this.back);
|
||||
}
|
||||
|
||||
initialize() {}
|
||||
|
||||
back() {
|
||||
this.root.gameState.goBackToMenu();
|
||||
}
|
||||
}
|
23
src/js/game/hud/parts/mode_menu_next.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
|
||||
export class HUDModeMenuNext extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
const key = this.root.gameMode.getId();
|
||||
|
||||
this.element = makeDiv(parent, "ingame_HUD_ModeMenuNext");
|
||||
this.button = document.createElement("button");
|
||||
this.button.classList.add("button");
|
||||
this.button.textContent = T.ingame.modeMenu[key].next.title + " ➡ ";
|
||||
this.element.appendChild(this.button);
|
||||
|
||||
this.content = makeDiv(this.element, null, ["content"], T.ingame.modeMenu[key].next.desc);
|
||||
|
||||
this.trackClicks(this.button, this.next);
|
||||
}
|
||||
|
||||
initialize() {}
|
||||
|
||||
next() {}
|
||||
}
|
67
src/js/game/hud/parts/mode_settings.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
|
||||
export class HUDModeSettings extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_ModeSettings");
|
||||
|
||||
const bind = (selector, handler) => this.trackClicks(this.element.querySelector(selector), handler);
|
||||
|
||||
if (this.root.gameMode.hasZone()) {
|
||||
this.zone = makeDiv(
|
||||
this.element,
|
||||
null,
|
||||
["section", "zone"],
|
||||
`
|
||||
<label>Zone</label>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="zoneWidth plusMinus">
|
||||
<label>Width</label>
|
||||
<button class="styledButton minus">-</button>
|
||||
<span class="value"></span>
|
||||
<button class="styledButton plus">+</button>
|
||||
</div>
|
||||
|
||||
<div class="zoneHeight plusMinus">
|
||||
<label>Height</label>
|
||||
<button class="styledButton minus">-</button>
|
||||
<span class="value"></span>
|
||||
<button class="styledButton plus">+</button>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
|
||||
bind(".zoneWidth .minus", () => this.modifyZone(-1, 0));
|
||||
bind(".zoneWidth .plus", () => this.modifyZone(1, 0));
|
||||
bind(".zoneHeight .minus", () => this.modifyZone(0, -1));
|
||||
bind(".zoneHeight .plus", () => this.modifyZone(0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.visible = false;
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.element);
|
||||
this.updateZoneValues();
|
||||
}
|
||||
|
||||
modifyZone(width, height) {
|
||||
this.root.gameMode.expandZone(width, height);
|
||||
this.updateZoneValues();
|
||||
}
|
||||
|
||||
updateZoneValues() {
|
||||
const zone = this.root.gameMode.getZone();
|
||||
this.element.querySelector(".zoneWidth > .value").textContent = String(zone.w);
|
||||
this.element.querySelector(".zoneHeight > .value").textContent = String(zone.h);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.visible = !this.visible;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.domAttach.update(this.visible);
|
||||
}
|
||||
}
|
@ -100,16 +100,14 @@ export class HUDWaypoints extends BaseHUDPart {
|
||||
|
||||
this.directionIndicatorSprite = Loader.getSprite("sprites/misc/hub_direction_indicator.png");
|
||||
|
||||
/** @type {Array<Waypoint>}
|
||||
*/
|
||||
this.waypoints = [
|
||||
{
|
||||
label: null,
|
||||
center: { x: 0, y: 0 },
|
||||
zoomLevel: 3,
|
||||
layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(),
|
||||
},
|
||||
];
|
||||
/** @type {Array<Waypoint>} */
|
||||
this.waypoints = [];
|
||||
this.waypoints.push({
|
||||
label: null,
|
||||
center: { x: 0, y: 0 },
|
||||
zoomLevel: 3,
|
||||
layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(),
|
||||
});
|
||||
|
||||
// Create a buffer we can use to measure text
|
||||
this.dummyBuffer = makeOffscreenBuffer(1, 1, {
|
||||
|
@ -49,6 +49,10 @@ export const KEYMAPPINGS = {
|
||||
},
|
||||
|
||||
buildings: {
|
||||
// Puzzle buildings
|
||||
constant_producer: { keyCode: key("H") },
|
||||
goal_acceptor: { keyCode: key("N") },
|
||||
|
||||
// Primary Toolbar
|
||||
belt: { keyCode: key("1") },
|
||||
balancer: { keyCode: key("2") },
|
||||
@ -262,6 +266,8 @@ export function getStringForKeyCode(code) {
|
||||
return ".";
|
||||
case 191:
|
||||
return "/";
|
||||
case 192:
|
||||
return "`";
|
||||
case 219:
|
||||
return "[";
|
||||
case 220:
|
||||
|
@ -41,7 +41,14 @@ export class MapChunkView extends MapChunk {
|
||||
*/
|
||||
drawBackgroundLayer(parameters) {
|
||||
const systems = this.root.systemMgr.systems;
|
||||
systems.mapResources.drawChunk(parameters, this);
|
||||
if (this.root.gameMode.hasZone()) {
|
||||
systems.zone.drawChunk(parameters, this);
|
||||
}
|
||||
|
||||
if (this.root.gameMode.hasResources()) {
|
||||
systems.mapResources.drawChunk(parameters, this);
|
||||
}
|
||||
|
||||
systems.beltUnderlays.drawChunk(parameters, this);
|
||||
systems.belt.drawChunk(parameters, this);
|
||||
}
|
||||
@ -69,6 +76,7 @@ export class MapChunkView extends MapChunk {
|
||||
systems.lever.drawChunk(parameters, this);
|
||||
systems.display.drawChunk(parameters, this);
|
||||
systems.storage.drawChunk(parameters, this);
|
||||
systems.constantProducer.drawChunk(parameters, this);
|
||||
systems.itemProcessorOverlays.drawChunk(parameters, this);
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,12 @@ import { MetaAnalyzerBuilding } from "./buildings/analyzer";
|
||||
import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer";
|
||||
import { MetaBeltBuilding } from "./buildings/belt";
|
||||
import { MetaComparatorBuilding } from "./buildings/comparator";
|
||||
import { MetaConstantProducerBuilding } from "./buildings/constant_producer";
|
||||
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
||||
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
|
||||
import { MetaDisplayBuilding } from "./buildings/display";
|
||||
import { MetaFilterBuilding } from "./buildings/filter";
|
||||
import { MetaGoalAcceptorBuilding } from "./buildings/goal_acceptor";
|
||||
import { MetaHubBuilding } from "./buildings/hub";
|
||||
import { MetaItemProducerBuilding } from "./buildings/item_producer";
|
||||
import { MetaLeverBuilding } from "./buildings/lever";
|
||||
@ -45,6 +47,7 @@ export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaStorageBuilding);
|
||||
gMetaBuildingRegistry.register(MetaBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaGoalAcceptorBuilding);
|
||||
gMetaBuildingRegistry.register(MetaHubBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireBuilding);
|
||||
gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
|
||||
@ -59,6 +62,7 @@ export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaAnalyzerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaComparatorBuilding);
|
||||
gMetaBuildingRegistry.register(MetaItemProducerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaConstantProducerBuilding);
|
||||
|
||||
// Belt
|
||||
registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0);
|
||||
@ -165,6 +169,12 @@ export function initMetaBuildingRegistry() {
|
||||
// Item producer
|
||||
registerBuildingVariant(61, MetaItemProducerBuilding);
|
||||
|
||||
// Constant producer
|
||||
registerBuildingVariant(62, MetaConstantProducerBuilding);
|
||||
|
||||
// Goal acceptor
|
||||
registerBuildingVariant(63, MetaGoalAcceptorBuilding);
|
||||
|
||||
// Propagate instances
|
||||
for (const key in gBuildingVariants) {
|
||||
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
|
||||
|
129
src/js/game/modes/puzzle.js
Normal file
@ -0,0 +1,129 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { types } from "../../savegame/serialization";
|
||||
import { enumGameModeTypes, GameMode } from "../game_mode";
|
||||
import { HUDGameMenu } from "../hud/parts/game_menu";
|
||||
import { HUDInteractiveTutorial } from "../hud/parts/interactive_tutorial";
|
||||
import { HUDKeybindingOverlay } from "../hud/parts/keybinding_overlay";
|
||||
import { HUDPartTutorialHints } from "../hud/parts/tutorial_hints";
|
||||
import { HUDPinnedShapes } from "../hud/parts/pinned_shapes";
|
||||
import { HUDWaypoints } from "../hud/parts/waypoints";
|
||||
|
||||
export class PuzzleGameMode extends GameMode {
|
||||
static getType() {
|
||||
return enumGameModeTypes.puzzle;
|
||||
}
|
||||
|
||||
/** @returns {object} */
|
||||
static getSchema() {
|
||||
return {
|
||||
zoneHeight: types.uint,
|
||||
zoneWidth: types.uint,
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
const data = this.getSaveData();
|
||||
|
||||
this.setHudParts({
|
||||
[HUDGameMenu.name]: false,
|
||||
[HUDInteractiveTutorial.name]: false,
|
||||
[HUDKeybindingOverlay.name]: false,
|
||||
[HUDPartTutorialHints.name]: false,
|
||||
[HUDPinnedShapes.name]: false,
|
||||
[HUDWaypoints.name]: false,
|
||||
});
|
||||
|
||||
this.setDimensions(data.zoneWidth, data.zoneHeight);
|
||||
}
|
||||
|
||||
setDimensions(w = 16, h = 9) {
|
||||
this.zoneWidth = w < 2 ? 2 : w;
|
||||
this.zoneHeight = h < 2 ? 2 : h;
|
||||
this.boundsHeight = this.zoneHeight < 8 ? 8 : this.zoneHeight;
|
||||
this.boundsWidth = this.zoneWidth < 8 ? 8 : this.zoneWidth;
|
||||
}
|
||||
|
||||
getSaveData() {
|
||||
const save = this.root.savegame.getCurrentDump();
|
||||
|
||||
if (!save) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return save.gameMode.data;
|
||||
}
|
||||
|
||||
createCenteredRectangle(width, height) {
|
||||
return new Rectangle(-Math.ceil(width / 2), -Math.ceil(height / 2), width, height);
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
if (this.bounds) {
|
||||
return this.bounds;
|
||||
}
|
||||
|
||||
this.bounds = this.createCenteredRectangle(this.boundsWidth, this.boundsHeight);
|
||||
|
||||
return this.bounds;
|
||||
}
|
||||
|
||||
getZone() {
|
||||
if (this.zone) {
|
||||
return this.zone;
|
||||
}
|
||||
|
||||
this.zone = this.createCenteredRectangle(this.zoneWidth, this.zoneHeight);
|
||||
|
||||
return this.zone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides GameMode's implementation to treat buildings like a whitelist
|
||||
* instead of a blacklist by default.
|
||||
* @param {string} name - Class name of building
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isBuildingExcluded(name) {
|
||||
return this.buildings[name] !== true;
|
||||
}
|
||||
|
||||
isInBounds(x, y) {
|
||||
return this.bounds.containsPoint(x, y);
|
||||
}
|
||||
|
||||
isInZone(x, y) {
|
||||
return this.zone.containsPoint(x, y);
|
||||
}
|
||||
|
||||
hasZone() {
|
||||
return true;
|
||||
}
|
||||
|
||||
hasHub() {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasResources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasBounds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getMinimumZoom() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
getIsFreeplayAvailable() {
|
||||
return true;
|
||||
}
|
||||
}
|
52
src/js/game/modes/puzzle_edit.js
Normal file
@ -0,0 +1,52 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
// import { MetaBeltBuilding } from "../buildings/belt";
|
||||
import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
|
||||
import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor";
|
||||
// import { MetaItemProducerBuilding } from "../buildings/item_producer";
|
||||
import { enumGameModeIds } from "../game_mode";
|
||||
import { PuzzleGameMode } from "./puzzle";
|
||||
|
||||
export class PuzzleEditGameMode extends PuzzleGameMode {
|
||||
static getId() {
|
||||
return enumGameModeIds.puzzleEdit;
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
this.playtest = false;
|
||||
|
||||
this.setBuildings({
|
||||
[MetaConstantProducerBuilding.name]: true,
|
||||
[MetaGoalAcceptorBuilding.name]: true,
|
||||
});
|
||||
}
|
||||
|
||||
isZoneRestricted() {
|
||||
return !this.playtest;
|
||||
}
|
||||
|
||||
isBoundaryRestricted() {
|
||||
return this.playtest;
|
||||
}
|
||||
|
||||
expandZone(w = 0, h = 0) {
|
||||
if (this.zoneWidth + w > 0) {
|
||||
this.zoneWidth += w;
|
||||
}
|
||||
|
||||
if (this.zoneHeight + h > 0) {
|
||||
this.zoneHeight += h;
|
||||
}
|
||||
|
||||
this.zone = this.createCenteredRectangle(this.zoneWidth, this.zoneHeight);
|
||||
}
|
||||
}
|
17
src/js/game/modes/puzzle_play.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { PuzzleGameMode } from "./puzzle";
|
||||
import { enumGameModeIds } from "../game_mode";
|
||||
|
||||
export class PuzzlePlayGameMode extends PuzzleGameMode {
|
||||
static getId() {
|
||||
return enumGameModeIds.puzzlePlay;
|
||||
}
|
||||
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root);
|
||||
}
|
||||
}
|
@ -1,19 +1,50 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { queryParamOptions } from "../../core/query_parameters";
|
||||
import { findNiceIntegerValue } from "../../core/utils";
|
||||
import { GameMode } from "../game_mode";
|
||||
import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
|
||||
import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor";
|
||||
import { MetaItemProducerBuilding } from "../buildings/item_producer";
|
||||
import { HUDModeMenuBack } from "../hud/parts/mode_menu_back";
|
||||
import { HUDModeMenuNext } from "../hud/parts/mode_menu_next";
|
||||
import { HUDModeMenu } from "../hud/parts/mode_menu";
|
||||
import { HUDModeSettings } from "../hud/parts/mode_settings";
|
||||
import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
/** @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 */
|
||||
|
||||
const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
||||
const finalGameShape = "RuCw--Cw:----Ru--";
|
||||
const preparementShape = "CpRpCp--:SwSwSwSw";
|
||||
const blueprintShape = "CbCbCbRb:CwCwCwCw";
|
||||
|
||||
// Tiers need % of the previous tier as requirement too
|
||||
const tierGrowth = 2.5;
|
||||
|
||||
/**
|
||||
* Generates all upgrades
|
||||
* @returns {Object<string, import("../game_mode").UpgradeTiers>} */
|
||||
* @returns {Object<string, UpgradeTiers>} */
|
||||
function generateUpgrades(limitedVersion = false) {
|
||||
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
|
||||
const numEndgameUpgrades = limitedVersion ? 0 : 1000 - fixedImprovements.length - 1;
|
||||
@ -454,27 +485,58 @@ const fullVersionLevels = generateLevelDefinitions(false);
|
||||
const demoVersionLevels = generateLevelDefinitions(true);
|
||||
|
||||
export class RegularGameMode extends GameMode {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
static getId() {
|
||||
return enumGameModeIds.regular;
|
||||
}
|
||||
|
||||
static getType() {
|
||||
return enumGameModeTypes.default;
|
||||
}
|
||||
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
this.setHudParts({
|
||||
[HUDModeMenuBack.name]: false,
|
||||
[HUDModeMenuNext.name]: false,
|
||||
[HUDModeMenu.name]: false,
|
||||
[HUDModeSettings.name]: false,
|
||||
});
|
||||
|
||||
this.setBuildings({
|
||||
[MetaConstantProducerBuilding.name]: false,
|
||||
[MetaGoalAcceptorBuilding.name]: false,
|
||||
[MetaItemProducerBuilding.name]: queryParamOptions.sandboxMode || G_IS_DEV,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return all available upgrades
|
||||
* @returns {Object<string, UpgradeTiers>}
|
||||
*/
|
||||
getUpgrades() {
|
||||
return this.root.app.restrictionMgr.getHasExtendedUpgrades()
|
||||
? fullVersionUpgrades
|
||||
: demoVersionUpgrades;
|
||||
}
|
||||
|
||||
getIsFreeplayAvailable() {
|
||||
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
|
||||
}
|
||||
|
||||
getBlueprintShapeKey() {
|
||||
return blueprintShape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the goals for all levels including their reward
|
||||
* @returns {Array<LevelDefinition>}
|
||||
*/
|
||||
getLevelDefinitions() {
|
||||
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay()
|
||||
? fullVersionLevels
|
||||
: demoVersionLevels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return whether free play is available or if the game stops
|
||||
* after the predefined levels
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsFreeplayAvailable() {
|
||||
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ export class BeltReaderSystem extends GameSystemWithFilter {
|
||||
const minimumTimeForThroughput = now - 1;
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
|
||||
const readerComp = entity.components.BeltReader;
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
|
||||
@ -23,12 +22,14 @@ export class BeltReaderSystem extends GameSystemWithFilter {
|
||||
readerComp.lastItemTimes.shift();
|
||||
}
|
||||
|
||||
pinsComp.slots[1].value = readerComp.lastItem;
|
||||
pinsComp.slots[0].value =
|
||||
(readerComp.lastItemTimes[readerComp.lastItemTimes.length - 1] || 0) >
|
||||
minimumTimeForThroughput
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
if (!entity.components.BeltReader.isWireless()) {
|
||||
pinsComp.slots[1].value = readerComp.lastItem;
|
||||
pinsComp.slots[0].value =
|
||||
(readerComp.lastItemTimes[readerComp.lastItemTimes.length - 1] || 0) >
|
||||
minimumTimeForThroughput
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
|
||||
if (now - readerComp.lastThroughputComputation > 0.5) {
|
||||
// Compute throughput
|
||||
|
54
src/js/game/systems/constant_producer.js
Normal file
@ -0,0 +1,54 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { ItemProducerComponent } from "../components/item_producer";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
|
||||
export class ConstantProducerSystem extends GameSystemWithFilter {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root, [ConstantSignalComponent, ItemProducerComponent]);
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const producerComp = entity.components.ItemProducer;
|
||||
const signalComp = entity.components.ConstantSignal;
|
||||
|
||||
if (!producerComp.isWireless() || !signalComp.isWireless()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
|
||||
ejectorComp.tryEject(0, signalComp.signal);
|
||||
}
|
||||
}
|
||||
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const producerComp = contents[i].components.ItemProducer;
|
||||
const signalComp = contents[i].components.ConstantSignal;
|
||||
|
||||
if (!producerComp || !producerComp.isWireless() || !signalComp || !signalComp.isWireless()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const staticComp = contents[i].components.StaticMapEntity;
|
||||
const item = signalComp.signal;
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Better looking overlay
|
||||
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||
item.drawItemCenteredClipped(center.x, center.y, parameters, globalConfig.tileSize);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,9 +6,10 @@ import { fillInLinkIntoTranslation } from "../../core/utils";
|
||||
import { T } from "../../translations";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { enumConstantSignalType, ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { HUDPinnedShapes } from "../hud/parts/pinned_shapes";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
|
||||
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
@ -26,8 +27,13 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
// Set signals
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
const signalComp = entity.components.ConstantSignal;
|
||||
|
||||
if (signalComp.isWireless()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
pinsComp.slots[0].value = signalComp.signal;
|
||||
}
|
||||
}
|
||||
@ -54,23 +60,33 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
validator: val => this.parseSignalCode(val),
|
||||
});
|
||||
|
||||
const items = [
|
||||
BOOL_FALSE_SINGLETON,
|
||||
BOOL_TRUE_SINGLETON,
|
||||
...Object.values(COLOR_ITEM_SINGLETONS),
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(this.root.gameMode.getBlueprintShapeKey()),
|
||||
];
|
||||
|
||||
if (this.root.gameMode.hasHub()) {
|
||||
items.push(
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
|
||||
this.root.hubGoals.currentGoal.definition
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)) {
|
||||
items.push(
|
||||
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const itemInput = new FormElementItemChooser({
|
||||
id: "signalItem",
|
||||
label: null,
|
||||
items: [
|
||||
BOOL_FALSE_SINGLETON,
|
||||
BOOL_TRUE_SINGLETON,
|
||||
...Object.values(COLOR_ITEM_SINGLETONS),
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
|
||||
this.root.hubGoals.currentGoal.definition
|
||||
),
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(
|
||||
this.root.gameMode.getBlueprintShapeKey()
|
||||
),
|
||||
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
|
||||
),
|
||||
],
|
||||
items,
|
||||
});
|
||||
|
||||
const dialog = new DialogWithForm({
|
||||
@ -103,7 +119,6 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
}
|
||||
|
||||
if (itemInput.chosenItem) {
|
||||
console.log(itemInput.chosenItem);
|
||||
constantComp.signal = itemInput.chosenItem;
|
||||
} else {
|
||||
constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
|
||||
|
120
src/js/game/systems/goal_acceptor.js
Normal file
@ -0,0 +1,120 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { THIRDPARTY_URLS, globalConfig } from "../../core/config";
|
||||
import { DialogWithForm } from "../../core/modal_dialog_elements";
|
||||
import { FormElementInput, FormElementItemChooser } from "../../core/modal_dialog_forms";
|
||||
import { fillInLinkIntoTranslation } from "../../core/utils";
|
||||
import { T } from "../../translations";
|
||||
import { GoalAcceptorComponent } from "../components/goal_acceptor";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
// import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
|
||||
// import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
|
||||
export class GoalAcceptorSystem extends GameSystemWithFilter {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root, [GoalAcceptorComponent]);
|
||||
|
||||
this.root.signals.entityManuallyPlaced.add(this.editGoal, this);
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const goalComp = entity.components.GoalAcceptor;
|
||||
const readerComp = entity.components.BeltReader;
|
||||
|
||||
// Check against goals (set on placement)
|
||||
}
|
||||
|
||||
// Check if goal criteria has been met for all goals
|
||||
}
|
||||
|
||||
drawChunk(parameters, chunk) {
|
||||
/*
|
||||
*const contents = chunk.containedEntitiesByLayer.regular;
|
||||
*for (let i = 0; i < contents.length; ++i) {}
|
||||
*/
|
||||
}
|
||||
|
||||
editGoal(entity) {
|
||||
if (!entity.components.GoalAcceptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uid = entity.uid;
|
||||
const goalComp = entity.components.GoalAcceptor;
|
||||
|
||||
const itemInput = new FormElementInput({
|
||||
id: "goalItemInput",
|
||||
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
|
||||
placeholder: "CuCuCuCu",
|
||||
defaultValue: "CuCuCuCu",
|
||||
validator: val => this.parseItem(val),
|
||||
});
|
||||
|
||||
const rateInput = new FormElementInput({
|
||||
id: "goalRateInput",
|
||||
label: "Rate:",
|
||||
placeholder: "0",
|
||||
defaultValue: "0",
|
||||
validator: val => !isNaN(Number(val)),
|
||||
});
|
||||
|
||||
const dialog = new DialogWithForm({
|
||||
app: this.root.app,
|
||||
title: "Set Goal",
|
||||
desc: "",
|
||||
formElements: [itemInput, rateInput],
|
||||
buttons: ["cancel:bad:escape", "ok:good:enter"],
|
||||
closeButton: false,
|
||||
});
|
||||
this.root.hud.parts.dialogs.internalShowDialog(dialog);
|
||||
|
||||
const closeHandler = () => {
|
||||
if (this.isEntityStale(uid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
goalComp.item = this.parseItem(itemInput.getValue());
|
||||
goalComp.rate = this.parseRate(rateInput.getValue());
|
||||
};
|
||||
|
||||
dialog.buttonSignals.ok.add(closeHandler);
|
||||
dialog.buttonSignals.cancel.add(() => {
|
||||
if (this.isEntityStale(uid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.logic.tryDeleteBuilding(entity);
|
||||
});
|
||||
}
|
||||
|
||||
parseRate(value) {
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
parseItem(value) {
|
||||
return this.root.systemMgr.systems.constantSignal.parseSignalCode(value);
|
||||
}
|
||||
|
||||
isEntityStale(uid) {
|
||||
if (!this.root || !this.root.entityMgr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const entity = this.root.entityMgr.findByUid(uid, false);
|
||||
if (!entity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const goalComp = entity.components.GoalAcceptor;
|
||||
if (!goalComp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -59,6 +59,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
[enumItemProcessorTypes.painterQuad]: this.process_PAINTER_QUAD,
|
||||
[enumItemProcessorTypes.hub]: this.process_HUB,
|
||||
[enumItemProcessorTypes.reader]: this.process_READER,
|
||||
[enumItemProcessorTypes.goal]: this.process_GOAL,
|
||||
};
|
||||
|
||||
// Bind all handlers
|
||||
@ -562,4 +563,13 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
this.root.hubGoals.handleDefinitionDelivered(item.definition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ProcessorImplementationPayload} payload
|
||||
*/
|
||||
process_GOAL(payload) {
|
||||
const readerComp = payload.entity.components.BeltReader;
|
||||
readerComp.lastItemTimes.push(this.root.time.now());
|
||||
readerComp.lastItem = payload.items[payload.items.length - 1].item;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,27 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { ItemProducerComponent } from "../components/item_producer";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
|
||||
export class ItemProducerSystem extends GameSystemWithFilter {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root, [ItemProducerComponent]);
|
||||
this.item = null;
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const producerComp = entity.components.ItemProducer;
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
|
||||
if (producerComp.isWireless()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
const pin = pinsComp.slots[0];
|
||||
const network = pin.linkedNetwork;
|
||||
@ -17,8 +30,8 @@ export class ItemProducerSystem extends GameSystemWithFilter {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
ejectorComp.tryEject(0, network.currentValue);
|
||||
this.item = network.currentValue;
|
||||
ejectorComp.tryEject(0, this.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
63
src/js/game/systems/zone.js
Normal file
@ -0,0 +1,63 @@
|
||||
/* typehints:start */
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { STOP_PROPAGATION } from "../../core/signal";
|
||||
import { GameSystem } from "../game_system";
|
||||
import { THEME } from "../theme";
|
||||
|
||||
export class ZoneSystem extends GameSystem {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root);
|
||||
|
||||
this.root.signals.prePlacementCheck.add(this.prePlacementCheck, this);
|
||||
}
|
||||
|
||||
prePlacementCheck(entity, tile = null) {
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
if (!staticComp) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mode = this.root.gameMode;
|
||||
const zone = mode.getZone().expandedInAllDirections(-1);
|
||||
const transformed = staticComp.getTileSpaceBounds();
|
||||
|
||||
if (zone.containsRect(transformed)) {
|
||||
if (mode.isZoneRestricted()) {
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
} else {
|
||||
if (mode.isBoundaryRestricted()) {
|
||||
return STOP_PROPAGATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the zone
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const mode = this.root.gameMode;
|
||||
const zone = mode.getZone().allScaled(globalConfig.tileSize);
|
||||
const context = parameters.context;
|
||||
|
||||
context.globalAlpha = 0.1;
|
||||
context.fillStyle = THEME.map.zone.background;
|
||||
context.fillRect(zone.x, zone.y, zone.w, zone.h);
|
||||
|
||||
context.globalAlpha = 1;
|
||||
context.strokeStyle = THEME.map.zone.border;
|
||||
context.lineWidth = 2;
|
||||
context.strokeRect(zone.x, zone.y, zone.w, zone.h);
|
||||
|
||||
context.globalAlpha = 1;
|
||||
}
|
||||
}
|
@ -47,6 +47,11 @@
|
||||
"textColor": "#fff",
|
||||
"textColorCapped": "#ef5072",
|
||||
"background": "rgba(40, 50, 60, 0.8)"
|
||||
},
|
||||
|
||||
"zone": {
|
||||
"background": "#3e3f47",
|
||||
"border": "#667964"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -48,6 +48,11 @@
|
||||
"textColor": "#fff",
|
||||
"textColorCapped": "#ef5072",
|
||||
"background": "rgba(40, 50, 60, 0.8)"
|
||||
},
|
||||
|
||||
"zone": {
|
||||
"background": "#fff",
|
||||
"border": "#cbffc4"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { initComponentRegistry } from "./game/component_registry";
|
||||
import { initDrawUtils } from "./core/draw_utils";
|
||||
import { initItemRegistry } from "./game/item_registry";
|
||||
import { initMetaBuildingRegistry } from "./game/meta_building_registry";
|
||||
import { initGameModeRegistry } from "./game/game_mode_registry";
|
||||
import { initGameSpeedRegistry } from "./game/game_speed_registry";
|
||||
|
||||
const logger = createLogger("main");
|
||||
@ -81,6 +82,7 @@ initDrawUtils();
|
||||
initComponentRegistry();
|
||||
initItemRegistry();
|
||||
initMetaBuildingRegistry();
|
||||
initGameModeRegistry();
|
||||
initGameSpeedRegistry();
|
||||
|
||||
let app = null;
|
||||
|
@ -13,6 +13,7 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||
|
||||
const logger = createLogger("savegame");
|
||||
|
||||
@ -53,7 +54,7 @@ export class Savegame extends ReadWriteProxy {
|
||||
* @returns {number}
|
||||
*/
|
||||
static getCurrentVersion() {
|
||||
return 1008;
|
||||
return 1009;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,6 +137,11 @@ export class Savegame extends ReadWriteProxy {
|
||||
data.version = 1008;
|
||||
}
|
||||
|
||||
if (data.version === 1008) {
|
||||
SavegameInterface_V1009.migrate1008to1009(data);
|
||||
data.version = 1009;
|
||||
}
|
||||
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
|
||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||
|
||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||
export const savegameInterfaces = {
|
||||
@ -21,6 +22,7 @@ export const savegameInterfaces = {
|
||||
1006: SavegameInterface_V1006,
|
||||
1007: SavegameInterface_V1007,
|
||||
1008: SavegameInterface_V1008,
|
||||
1009: SavegameInterface_V1009,
|
||||
};
|
||||
|
||||
const logger = createLogger("savegame_interface_registry");
|
||||
|
@ -2,6 +2,8 @@ import { ExplainedResult } from "../core/explained_result";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { gComponentRegistry } from "../core/global_registries";
|
||||
import { SerializerInternal } from "./serializer_internal";
|
||||
import { HUDPinnedShapes } from "../game/hud/parts/pinned_shapes";
|
||||
import { HUDWaypoints } from "../game/hud/parts/waypoints";
|
||||
|
||||
/**
|
||||
* @typedef {import("../game/component").Component} Component
|
||||
@ -33,12 +35,17 @@ export class SavegameSerializer {
|
||||
camera: root.camera.serialize(),
|
||||
time: root.time.serialize(),
|
||||
map: root.map.serialize(),
|
||||
gameMode: root.gameMode.serialize(),
|
||||
entityMgr: root.entityMgr.serialize(),
|
||||
hubGoals: root.hubGoals.serialize(),
|
||||
pinnedShapes: root.hud.parts.pinnedShapes.serialize(),
|
||||
waypoints: root.hud.parts.waypoints.serialize(),
|
||||
entities: this.internal.serializeEntityArray(root.entityMgr.entities),
|
||||
beltPaths: root.systemMgr.systems.belt.serializePaths(),
|
||||
pinnedShapes: root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)
|
||||
? null
|
||||
: root.hud.parts.pinnedShapes.serialize(),
|
||||
waypoints: root.gameMode.isHudPartExcluded(HUDWaypoints.name)
|
||||
? null
|
||||
: root.hud.parts.waypoints.serialize(),
|
||||
};
|
||||
|
||||
if (G_IS_DEV) {
|
||||
@ -130,12 +137,19 @@ export class SavegameSerializer {
|
||||
errorReason = errorReason || root.time.deserialize(savegame.time);
|
||||
errorReason = errorReason || root.camera.deserialize(savegame.camera);
|
||||
errorReason = errorReason || root.map.deserialize(savegame.map);
|
||||
errorReason = errorReason || root.gameMode.deserialize(savegame.gameMode);
|
||||
errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals, root);
|
||||
errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
|
||||
errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);
|
||||
errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities);
|
||||
errorReason = errorReason || root.systemMgr.systems.belt.deserializePaths(savegame.beltPaths);
|
||||
|
||||
if (!root.gameMode.isHudPartExcluded(HUDPinnedShapes.name)) {
|
||||
errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
|
||||
}
|
||||
|
||||
if (!root.gameMode.isHudPartExcluded(HUDWaypoints.name)) {
|
||||
errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
if (errorReason) {
|
||||
return ExplainedResult.bad(errorReason);
|
||||
|
@ -12,6 +12,7 @@
|
||||
* time: any,
|
||||
* entityMgr: any,
|
||||
* map: any,
|
||||
* gameMode: object,
|
||||
* hubGoals: any,
|
||||
* pinnedShapes: any,
|
||||
* waypoints: any,
|
||||
|
34
src/js/savegame/schemas/1009.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { createLogger } from "../../core/logging.js";
|
||||
import { RegularGameMode } from "../../game/modes/regular.js";
|
||||
import { SavegameInterface_V1008 } from "./1008.js";
|
||||
|
||||
const schema = require("./1009.json");
|
||||
const logger = createLogger("savegame_interface/1009");
|
||||
|
||||
export class SavegameInterface_V1009 extends SavegameInterface_V1008 {
|
||||
getVersion() {
|
||||
return 1009;
|
||||
}
|
||||
|
||||
getSchemaUncached() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../savegame_typedefs.js").SavegameData} data
|
||||
*/
|
||||
static migrate1008to1009(data) {
|
||||
logger.log("Migrating 1008 to 1009");
|
||||
const dump = data.dump;
|
||||
if (!dump) {
|
||||
return true;
|
||||
}
|
||||
|
||||
dump.gameMode = {
|
||||
mode: {
|
||||
id: RegularGameMode.getId(),
|
||||
data: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
5
src/js/savegame/schemas/1009.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"additionalProperties": true
|
||||
}
|
@ -39,6 +39,9 @@ export class GameCreationPayload {
|
||||
/** @type {boolean|undefined} */
|
||||
this.fastEnter;
|
||||
|
||||
/** @type {string} */
|
||||
this.gameModeId;
|
||||
|
||||
/** @type {Savegame} */
|
||||
this.savegame;
|
||||
}
|
||||
@ -220,7 +223,7 @@ export class InGameState extends GameState {
|
||||
logger.log("Creating new game core");
|
||||
this.core = new GameCore(this.app);
|
||||
|
||||
this.core.initializeRoot(this, this.savegame);
|
||||
this.core.initializeRoot(this, this.savegame, this.gameModeId);
|
||||
|
||||
if (this.savegame.hasGameDump()) {
|
||||
this.stage4bResumeGame();
|
||||
@ -354,6 +357,7 @@ export class InGameState extends GameState {
|
||||
|
||||
this.creationPayload = payload;
|
||||
this.savegame = payload.savegame;
|
||||
this.gameModeId = payload.gameModeId;
|
||||
|
||||
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
||||
this.loadingOverlay.showBasic();
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
startFileChoose,
|
||||
waitNextFrame,
|
||||
} from "../core/utils";
|
||||
import { enumGameModeIds } from "../game/game_mode";
|
||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||
import { getApplicationSettingById } from "../profile/application_settings";
|
||||
import { T } from "../translations";
|
||||
@ -82,6 +83,9 @@ export class MainMenuState extends GameState {
|
||||
}
|
||||
<div class="buttons"></div>
|
||||
</div>
|
||||
<div class="bottomContainer">
|
||||
<div class="buttons"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer ${G_CHINA_VERSION ? "china" : ""}">
|
||||
@ -204,6 +208,11 @@ export class MainMenuState extends GameState {
|
||||
const qs = this.htmlElement.querySelector.bind(this.htmlElement);
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
|
||||
if (globalConfig.debug.testPuzzleMode) {
|
||||
this.onPuzzleEditButtonClicked();
|
||||
return;
|
||||
}
|
||||
|
||||
const games = this.app.savegameMgr.getSavegamesMetaData();
|
||||
if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
|
||||
this.resumeGame(games[0]);
|
||||
@ -304,6 +313,68 @@ export class MainMenuState extends GameState {
|
||||
this.trackClicks(playBtn, this.onPlayButtonClicked);
|
||||
buttonContainer.appendChild(importButtonElement);
|
||||
}
|
||||
|
||||
const bottomButtonContainer = this.htmlElement.querySelector(".bottomContainer .buttons");
|
||||
removeAllChildren(bottomButtonContainer);
|
||||
|
||||
const puzzleModeButton = makeButton(bottomButtonContainer, ["styledButton"], T.mainMenu.puzzleMode);
|
||||
|
||||
bottomButtonContainer.appendChild(puzzleModeButton);
|
||||
this.trackClicks(puzzleModeButton, this.onPuzzleModeButtonClicked);
|
||||
}
|
||||
|
||||
renderPuzzleModeMenu() {
|
||||
const savegames = this.htmlElement.querySelector(".mainContainer .savegames");
|
||||
|
||||
if (savegames) {
|
||||
savegames.remove();
|
||||
}
|
||||
|
||||
const buttonContainer = this.htmlElement.querySelector(".mainContainer .buttons");
|
||||
removeAllChildren(buttonContainer);
|
||||
|
||||
const playButtonElement = makeButtonElement(["playModeButton", "styledButton"], T.puzzleMenu.play);
|
||||
const editButtonElement = makeButtonElement(["editModeButton", "styledButton"], T.puzzleMenu.edit);
|
||||
|
||||
buttonContainer.appendChild(playButtonElement);
|
||||
this.trackClicks(playButtonElement, this.onPuzzlePlayButtonClicked);
|
||||
buttonContainer.appendChild(editButtonElement);
|
||||
this.trackClicks(editButtonElement, this.onPuzzleEditButtonClicked);
|
||||
|
||||
const bottomButtonContainer = this.htmlElement.querySelector(".bottomContainer .buttons");
|
||||
removeAllChildren(bottomButtonContainer);
|
||||
|
||||
const backButton = makeButton(bottomButtonContainer, ["styledButton"], T.mainMenu.back);
|
||||
|
||||
bottomButtonContainer.appendChild(backButton);
|
||||
this.trackClicks(backButton, this.onBackButtonClicked);
|
||||
}
|
||||
|
||||
onPuzzlePlayButtonClicked() {
|
||||
const savegame = this.app.savegameMgr.createNewSavegame();
|
||||
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzlePlay,
|
||||
savegame,
|
||||
});
|
||||
}
|
||||
|
||||
onPuzzleEditButtonClicked() {
|
||||
const savegame = this.app.savegameMgr.createNewSavegame();
|
||||
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzleEdit,
|
||||
savegame,
|
||||
});
|
||||
}
|
||||
|
||||
onPuzzleModeButtonClicked() {
|
||||
this.renderPuzzleModeMenu();
|
||||
}
|
||||
|
||||
onBackButtonClicked() {
|
||||
this.renderMainMenu();
|
||||
this.renderSavegames();
|
||||
}
|
||||
|
||||
onSteamLinkClicked() {
|
||||
|
@ -116,6 +116,12 @@ mainMenu:
|
||||
savegameLevel: Level <x>
|
||||
savegameLevelUnknown: Unknown Level
|
||||
savegameUnnamed: Unnamed
|
||||
puzzleMode: Puzzle Mode
|
||||
back: Back
|
||||
|
||||
puzzleMenu:
|
||||
play: Play
|
||||
edit: Edit
|
||||
|
||||
dialogs:
|
||||
buttons:
|
||||
@ -477,6 +483,24 @@ ingame:
|
||||
title: Support me
|
||||
desc: I develop the game in my spare time!
|
||||
|
||||
modeMenu:
|
||||
puzzleEditMode:
|
||||
back:
|
||||
title: Main Menu
|
||||
next:
|
||||
title: Playtest
|
||||
desc: You will have to complete the puzzle before being able to publish it
|
||||
puzzleEditTestMode:
|
||||
back:
|
||||
title: Edit
|
||||
next:
|
||||
title: Publish
|
||||
puzzlePlayMode:
|
||||
back:
|
||||
title: Puzzle Menu
|
||||
next:
|
||||
title: Next
|
||||
|
||||
# All shop upgrades
|
||||
shopUpgrades:
|
||||
belt:
|
||||
@ -701,6 +725,16 @@ buildings:
|
||||
name: Item Producer
|
||||
description: Available in sandbox mode only, outputs the given signal from the wires layer on the regular layer.
|
||||
|
||||
constant_producer:
|
||||
default:
|
||||
name: &constant_producer Constant Producer
|
||||
description: Outputs a shape, color or boolean (1 or 0) as specified.
|
||||
|
||||
goal_acceptor:
|
||||
default:
|
||||
name: &goal_acceptor Goal Acceptor
|
||||
description: Accepts items and triggers a goal if the specified item and/or rate criteria are met.
|
||||
|
||||
storyRewards:
|
||||
# Those are the rewards gained from completing the store
|
||||
reward_cutter_and_trash:
|
||||
@ -1128,6 +1162,8 @@ keybindings:
|
||||
analyzer: *analyzer
|
||||
comparator: *comparator
|
||||
item_producer: Item Producer (Sandbox)
|
||||
constant_producer: *constant_producer
|
||||
goal_acceptor: *goal_acceptor
|
||||
# ---
|
||||
|
||||
pipette: Pipette
|
||||
|