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/sandbox_controller";
|
||||||
@import "ingame_hud/standalone_advantages";
|
@import "ingame_hud/standalone_advantages";
|
||||||
@import "ingame_hud/cat_memes";
|
@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
|
// prettier-ignore
|
||||||
$elements:
|
$elements:
|
||||||
@ -71,6 +75,10 @@ ingame_HUD_PlacerVariants,
|
|||||||
ingame_HUD_PinnedShapes,
|
ingame_HUD_PinnedShapes,
|
||||||
ingame_HUD_GameMenu,
|
ingame_HUD_GameMenu,
|
||||||
ingame_HUD_KeybindingOverlay,
|
ingame_HUD_KeybindingOverlay,
|
||||||
|
ingame_HUD_ModeMenuBack,
|
||||||
|
ingame_HUD_ModeMenuNext,
|
||||||
|
ingame_HUD_ModeMenu,
|
||||||
|
ingame_HUD_ModeSettings,
|
||||||
ingame_HUD_Notifications,
|
ingame_HUD_Notifications,
|
||||||
ingame_HUD_DebugInfo,
|
ingame_HUD_DebugInfo,
|
||||||
ingame_HUD_EntityDebugger,
|
ingame_HUD_EntityDebugger,
|
||||||
@ -113,6 +121,8 @@ body.uiHidden {
|
|||||||
#ingame_HUD_PlacementHints,
|
#ingame_HUD_PlacementHints,
|
||||||
#ingame_HUD_GameMenu,
|
#ingame_HUD_GameMenu,
|
||||||
#ingame_HUD_PinnedShapes,
|
#ingame_HUD_PinnedShapes,
|
||||||
|
#ingame_HUD_ModeMenuBack,
|
||||||
|
#ingame_HUD_ModeMenuNext,
|
||||||
#ingame_HUD_Notifications,
|
#ingame_HUD_Notifications,
|
||||||
#ingame_HUD_TutorialHints,
|
#ingame_HUD_TutorialHints,
|
||||||
#ingame_HUD_Waypoints,
|
#ingame_HUD_Waypoints,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
$buildings: belt, cutter, miner, mixer, painter, rotater, balancer, stacker, trash, underground_belt, wire,
|
$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,
|
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 {
|
@each $building in $buildings {
|
||||||
[data-icon="building_icons/#{$building}.png"] {
|
[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,
|
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,
|
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,
|
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 {
|
@each $building in $buildingsAndVariants {
|
||||||
[data-icon="building_tutorials/#{$building}.png"] {
|
[data-icon="building_tutorials/#{$building}.png"] {
|
||||||
/* @load-async */
|
/* @load-async */
|
||||||
|
@ -242,6 +242,16 @@
|
|||||||
align-items: center;
|
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 {
|
.browserWarning {
|
||||||
@include S(margin-bottom, 10px);
|
@include S(margin-bottom, 10px);
|
||||||
background-color: $colorRedBright;
|
background-color: $colorRedBright;
|
||||||
@ -285,6 +295,18 @@
|
|||||||
@include S(margin-left, 15px);
|
@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 {
|
.savegames {
|
||||||
@include S(max-height, 105px);
|
@include S(max-height, 105px);
|
||||||
overflow-y: auto;
|
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 {
|
.footer {
|
||||||
display: grid;
|
display: grid;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -62,6 +62,9 @@ export default {
|
|||||||
// Allows unlocked achievements to be logged to console in the local build
|
// Allows unlocked achievements to be logged to console in the local build
|
||||||
// testAchievements: true,
|
// 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
|
// Disables the automatic switch to an overview when zooming out
|
||||||
// disableMapOverview: true,
|
// disableMapOverview: true,
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
|
@ -5,6 +5,7 @@ import { Factory } from "./factory";
|
|||||||
* @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed
|
* @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed
|
||||||
* @typedef {import("../game/component").Component} Component
|
* @typedef {import("../game/component").Component} Component
|
||||||
* @typedef {import("../game/base_item").BaseItem} BaseItem
|
* @typedef {import("../game/base_item").BaseItem} BaseItem
|
||||||
|
* @typedef {import("../game/game_mode").GameMode} GameMode
|
||||||
* @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding
|
* @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding
|
||||||
|
|
||||||
|
|
||||||
@ -19,6 +20,9 @@ export let gBuildingsByCategory = null;
|
|||||||
/** @type {FactoryTemplate<Component>} */
|
/** @type {FactoryTemplate<Component>} */
|
||||||
export let gComponentRegistry = new Factory("component");
|
export let gComponentRegistry = new Factory("component");
|
||||||
|
|
||||||
|
/** @type {FactoryTemplate<GameMode>} */
|
||||||
|
export let gGameModeRegistry = new Factory("gameMode");
|
||||||
|
|
||||||
/** @type {FactoryTemplate<BaseGameSpeed>} */
|
/** @type {FactoryTemplate<BaseGameSpeed>} */
|
||||||
export let gGameSpeedRegistry = new Factory("gamespeed");
|
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);
|
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 if we can further zoom in
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
canZoomIn() {
|
canZoomIn() {
|
||||||
const maxLevel = this.root.app.platformWrapper.getMaximumZoom();
|
return this.zoomLevel <= this.getMaximumZoom() - 0.01;
|
||||||
return this.zoomLevel <= maxLevel - 0.01;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -406,8 +413,7 @@ export class Camera extends BasicSerializableObject {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
canZoomOut() {
|
canZoomOut() {
|
||||||
const minLevel = this.root.app.platformWrapper.getMinimumZoom();
|
return this.zoomLevel >= this.getMinimumZoom() + 0.01;
|
||||||
return this.zoomLevel >= minLevel + 0.01;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVENTS
|
// EVENTS
|
||||||
@ -743,17 +749,30 @@ export class Camera extends BasicSerializableObject {
|
|||||||
if (G_IS_DEV && globalConfig.debug.disableZoomLimits) {
|
if (G_IS_DEV && globalConfig.debug.disableZoomLimits) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const wrapper = this.root.app.platformWrapper;
|
|
||||||
|
|
||||||
assert(Number.isFinite(this.zoomLevel), "Invalid zoom level *before* clamp: " + this.zoomLevel);
|
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);
|
assert(Number.isFinite(this.zoomLevel), "Invalid zoom level *after* clamp: " + this.zoomLevel);
|
||||||
|
|
||||||
if (this.desiredZoom) {
|
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
|
* Updates the camera
|
||||||
* @param {number} dt Delta time in milliseconds
|
* @param {number} dt Delta time in milliseconds
|
||||||
@ -857,6 +876,7 @@ export class Camera extends BasicSerializableObject {
|
|||||||
// Panning
|
// Panning
|
||||||
this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06);
|
this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06);
|
||||||
this.center = this.center.add(this.currentPan.multiplyScalar((0.5 * dt) / this.zoomLevel));
|
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()
|
((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.x += moveAmount * forceX * movementSpeed;
|
||||||
this.center.y += moveAmount * forceY * 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 { BeltReaderComponent } from "./components/belt_reader";
|
||||||
import { FilterComponent } from "./components/filter";
|
import { FilterComponent } from "./components/filter";
|
||||||
import { ItemProducerComponent } from "./components/item_producer";
|
import { ItemProducerComponent } from "./components/item_producer";
|
||||||
|
import { GoalAcceptorComponent } from "./components/goal_acceptor";
|
||||||
|
|
||||||
export function initComponentRegistry() {
|
export function initComponentRegistry() {
|
||||||
gComponentRegistry.register(StaticMapEntityComponent);
|
gComponentRegistry.register(StaticMapEntityComponent);
|
||||||
@ -41,6 +42,7 @@ export function initComponentRegistry() {
|
|||||||
gComponentRegistry.register(BeltReaderComponent);
|
gComponentRegistry.register(BeltReaderComponent);
|
||||||
gComponentRegistry.register(FilterComponent);
|
gComponentRegistry.register(FilterComponent);
|
||||||
gComponentRegistry.register(ItemProducerComponent);
|
gComponentRegistry.register(ItemProducerComponent);
|
||||||
|
gComponentRegistry.register(GoalAcceptorComponent);
|
||||||
|
|
||||||
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
||||||
|
|
||||||
|
@ -3,6 +3,12 @@ import { BaseItem } from "../base_item";
|
|||||||
import { typeItemSingleton } from "../item_resolver";
|
import { typeItemSingleton } from "../item_resolver";
|
||||||
import { types } from "../../savegame/serialization";
|
import { types } from "../../savegame/serialization";
|
||||||
|
|
||||||
|
/** @enum {string} */
|
||||||
|
export const enumBeltReaderType = {
|
||||||
|
wired: "wired",
|
||||||
|
wireless: "wireless",
|
||||||
|
};
|
||||||
|
|
||||||
export class BeltReaderComponent extends Component {
|
export class BeltReaderComponent extends Component {
|
||||||
static getId() {
|
static getId() {
|
||||||
return "BeltReader";
|
return "BeltReader";
|
||||||
@ -10,13 +16,20 @@ export class BeltReaderComponent extends Component {
|
|||||||
|
|
||||||
static getSchema() {
|
static getSchema() {
|
||||||
return {
|
return {
|
||||||
|
type: types.string,
|
||||||
lastItem: types.nullable(typeItemSingleton),
|
lastItem: types.nullable(typeItemSingleton),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
/**
|
||||||
|
* @param {object} param0
|
||||||
|
* @param {string=} param0.type
|
||||||
|
*/
|
||||||
|
constructor({ type = enumBeltReaderType.wired }) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.type = type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which items went through the reader, we only store the time
|
* Which items went through the reader, we only store the time
|
||||||
* @type {Array<number>}
|
* @type {Array<number>}
|
||||||
@ -41,4 +54,8 @@ export class BeltReaderComponent extends Component {
|
|||||||
*/
|
*/
|
||||||
this.lastThroughputComputation = 0;
|
this.lastThroughputComputation = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isWireless() {
|
||||||
|
return this.type === enumBeltReaderType.wireless;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,12 @@ import { Component } from "../component";
|
|||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { typeItemSingleton } from "../item_resolver";
|
import { typeItemSingleton } from "../item_resolver";
|
||||||
|
|
||||||
|
/** @enum {string} */
|
||||||
|
export const enumConstantSignalType = {
|
||||||
|
wired: "wired",
|
||||||
|
wireless: "wireless",
|
||||||
|
};
|
||||||
|
|
||||||
export class ConstantSignalComponent extends Component {
|
export class ConstantSignalComponent extends Component {
|
||||||
static getId() {
|
static getId() {
|
||||||
return "ConstantSignal";
|
return "ConstantSignal";
|
||||||
@ -11,6 +17,7 @@ export class ConstantSignalComponent extends Component {
|
|||||||
|
|
||||||
static getSchema() {
|
static getSchema() {
|
||||||
return {
|
return {
|
||||||
|
type: types.string,
|
||||||
signal: types.nullable(typeItemSingleton),
|
signal: types.nullable(typeItemSingleton),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -21,15 +28,22 @@ export class ConstantSignalComponent extends Component {
|
|||||||
*/
|
*/
|
||||||
copyAdditionalStateTo(otherComponent) {
|
copyAdditionalStateTo(otherComponent) {
|
||||||
otherComponent.signal = this.signal;
|
otherComponent.signal = this.signal;
|
||||||
|
otherComponent.type = this.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} param0
|
* @param {object} param0
|
||||||
|
* @param {string=} param0.type
|
||||||
* @param {BaseItem=} param0.signal The signal to store
|
* @param {BaseItem=} param0.signal The signal to store
|
||||||
*/
|
*/
|
||||||
constructor({ signal = null }) {
|
constructor({ signal = null, type = enumConstantSignalType.wired }) {
|
||||||
super();
|
super();
|
||||||
this.signal = signal;
|
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",
|
hub: "hub",
|
||||||
filter: "filter",
|
filter: "filter",
|
||||||
reader: "reader",
|
reader: "reader",
|
||||||
|
goal: "goal",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
@ -104,7 +105,11 @@ export class ItemProcessorComponent extends Component {
|
|||||||
* @param {number} sourceSlot
|
* @param {number} sourceSlot
|
||||||
*/
|
*/
|
||||||
tryTakeItem(item, 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.
|
// Hub has special logic .. not really nice but efficient.
|
||||||
this.inputSlots.push({ item, sourceSlot });
|
this.inputSlots.push({ item, sourceSlot });
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,7 +1,33 @@
|
|||||||
|
import { types } from "../../savegame/serialization";
|
||||||
import { Component } from "../component";
|
import { Component } from "../component";
|
||||||
|
|
||||||
|
/** @enum {string} */
|
||||||
|
export const enumItemProducerType = {
|
||||||
|
wired: "wired",
|
||||||
|
wireless: "wireless",
|
||||||
|
};
|
||||||
|
|
||||||
export class ItemProducerComponent extends Component {
|
export class ItemProducerComponent extends Component {
|
||||||
static getId() {
|
static getId() {
|
||||||
return "ItemProducer";
|
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 { GameLogic } from "./logic";
|
||||||
import { MapView } from "./map_view";
|
import { MapView } from "./map_view";
|
||||||
import { defaultBuildingVariant } from "./meta_building";
|
import { defaultBuildingVariant } from "./meta_building";
|
||||||
import { RegularGameMode } from "./modes/regular";
|
import { GameMode } from "./game_mode";
|
||||||
import { ProductionAnalytics } from "./production_analytics";
|
import { ProductionAnalytics } from "./production_analytics";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||||
@ -82,7 +82,7 @@ export class GameCore {
|
|||||||
* @param {import("../states/ingame").InGameState} parentState
|
* @param {import("../states/ingame").InGameState} parentState
|
||||||
* @param {Savegame} savegame
|
* @param {Savegame} savegame
|
||||||
*/
|
*/
|
||||||
initializeRoot(parentState, savegame) {
|
initializeRoot(parentState, savegame, gameModeId) {
|
||||||
// Construct the root element, this is the data representation of the game
|
// Construct the root element, this is the data representation of the game
|
||||||
this.root = new GameRoot(this.app);
|
this.root = new GameRoot(this.app);
|
||||||
this.root.gameState = parentState;
|
this.root.gameState = parentState;
|
||||||
@ -104,7 +104,7 @@ export class GameCore {
|
|||||||
root.dynamicTickrate = new DynamicTickrate(root);
|
root.dynamicTickrate = new DynamicTickrate(root);
|
||||||
|
|
||||||
// Init game mode
|
// Init game mode
|
||||||
root.gameMode = new RegularGameMode(root);
|
root.gameMode = GameMode.create(root, gameModeId);
|
||||||
|
|
||||||
// Init classes
|
// Init classes
|
||||||
root.camera = new Camera(root);
|
root.camera = new Camera(root);
|
||||||
@ -168,6 +168,10 @@ export class GameCore {
|
|||||||
this.root.gameIsFresh = true;
|
this.root.gameIsFresh = true;
|
||||||
this.root.map.seed = randomInt(0, 100000);
|
this.root.map.seed = randomInt(0, 100000);
|
||||||
|
|
||||||
|
if (!this.root.gameMode.hasHub()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Place the hub
|
// Place the hub
|
||||||
const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
|
const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
|
||||||
root: this.root,
|
root: this.root,
|
||||||
|
@ -19,6 +19,7 @@ import { DisplayComponent } from "./components/display";
|
|||||||
import { BeltReaderComponent } from "./components/belt_reader";
|
import { BeltReaderComponent } from "./components/belt_reader";
|
||||||
import { FilterComponent } from "./components/filter";
|
import { FilterComponent } from "./components/filter";
|
||||||
import { ItemProducerComponent } from "./components/item_producer";
|
import { ItemProducerComponent } from "./components/item_producer";
|
||||||
|
import { GoalAcceptorComponent } from "./components/goal_acceptor";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,6 +90,9 @@ export class EntityComponentStorage {
|
|||||||
/** @type {ItemProducerComponent} */
|
/** @type {ItemProducerComponent} */
|
||||||
this.ItemProducer;
|
this.ItemProducer;
|
||||||
|
|
||||||
|
/** @type {GoalAcceptorComponent} */
|
||||||
|
this.GoalAcceptor;
|
||||||
|
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,184 @@
|
|||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
import { enumHubGoalRewards } from "./tutorial_goals";
|
import { GameRoot } from "./root";
|
||||||
|
import { Rectangle } from "../core/rectangle";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
import { GameRoot } from "./root";
|
import { gGameModeRegistry } from "../core/global_registries";
|
||||||
|
import { types, BasicSerializableObject } from "../savegame/serialization";
|
||||||
|
|
||||||
/** @typedef {{
|
/** @enum {string} */
|
||||||
* shape: string,
|
export const enumGameModeIds = {
|
||||||
* amount: number
|
puzzleEdit: "puzzleEditMode",
|
||||||
* }} UpgradeRequirement */
|
puzzlePlay: "puzzlePlayMode",
|
||||||
|
regular: "regularMode",
|
||||||
|
};
|
||||||
|
|
||||||
/** @typedef {{
|
/** @enum {string} */
|
||||||
* required: Array<UpgradeRequirement>
|
export const enumGameModeTypes = {
|
||||||
* improvement?: number,
|
default: "defaultModeType",
|
||||||
* excludePrevious?: boolean
|
puzzle: "puzzleModeType",
|
||||||
* }} TierRequirement */
|
};
|
||||||
|
|
||||||
/** @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
|
* @param {GameRoot} root
|
||||||
*/
|
*/
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
|
super();
|
||||||
this.root = root;
|
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
|
* @param {string} name - Class name of HUD Part
|
||||||
* @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}
|
* @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;
|
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 { BeltReaderSystem } from "./systems/belt_reader";
|
||||||
import { FilterSystem } from "./systems/filter";
|
import { FilterSystem } from "./systems/filter";
|
||||||
import { ItemProducerSystem } from "./systems/item_producer";
|
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");
|
const logger = createLogger("game_system_manager");
|
||||||
|
|
||||||
@ -100,6 +103,15 @@ export class GameSystemManager {
|
|||||||
/** @type {ItemProducerSystem} */
|
/** @type {ItemProducerSystem} */
|
||||||
itemProducer: null,
|
itemProducer: null,
|
||||||
|
|
||||||
|
/** @type {ConstantProducerSystem} */
|
||||||
|
ConstantProducer: null,
|
||||||
|
|
||||||
|
/** @type {GoalAcceptorSystem} */
|
||||||
|
GoalAcceptor: null,
|
||||||
|
|
||||||
|
/** @type {ZoneSystem} */
|
||||||
|
zone: null,
|
||||||
|
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
};
|
};
|
||||||
this.systemUpdateOrder = [];
|
this.systemUpdateOrder = [];
|
||||||
@ -138,7 +150,9 @@ export class GameSystemManager {
|
|||||||
|
|
||||||
add("itemEjector", ItemEjectorSystem);
|
add("itemEjector", ItemEjectorSystem);
|
||||||
|
|
||||||
add("mapResources", MapResourcesSystem);
|
if (this.root.gameMode.hasResources()) {
|
||||||
|
add("mapResources", MapResourcesSystem);
|
||||||
|
}
|
||||||
|
|
||||||
add("hub", HubSystem);
|
add("hub", HubSystem);
|
||||||
|
|
||||||
@ -165,6 +179,14 @@ export class GameSystemManager {
|
|||||||
|
|
||||||
add("itemProcessorOverlays", ItemProcessorOverlaysSystem);
|
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");
|
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,6 +500,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
switch (processorType) {
|
switch (processorType) {
|
||||||
case enumItemProcessorTypes.trash:
|
case enumItemProcessorTypes.trash:
|
||||||
case enumItemProcessorTypes.hub:
|
case enumItemProcessorTypes.hub:
|
||||||
|
case enumItemProcessorTypes.goal:
|
||||||
return 1e30;
|
return 1e30;
|
||||||
case enumItemProcessorTypes.balancer:
|
case enumItemProcessorTypes.balancer:
|
||||||
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt * 2;
|
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt * 2;
|
||||||
|
@ -49,6 +49,10 @@ import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
|
|||||||
import { HUDCatMemes } from "./parts/cat_memes";
|
import { HUDCatMemes } from "./parts/cat_memes";
|
||||||
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
|
||||||
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
|
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 {
|
export class GameHUD {
|
||||||
/**
|
/**
|
||||||
@ -74,45 +78,51 @@ export class GameHUD {
|
|||||||
unlockNotificationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
unlockNotificationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.parts = {
|
this.initParts({
|
||||||
buildingsToolbar: new HUDBuildingsToolbar(this.root),
|
buildingsToolbar: HUDBuildingsToolbar,
|
||||||
wiresToolbar: new HUDWiresToolbar(this.root),
|
wiresToolbar: HUDWiresToolbar,
|
||||||
blueprintPlacer: new HUDBlueprintPlacer(this.root),
|
blueprintPlacer: HUDBlueprintPlacer,
|
||||||
buildingPlacer: new HUDBuildingPlacer(this.root),
|
buildingPlacer: HUDBuildingPlacer,
|
||||||
unlockNotification: new HUDUnlockNotification(this.root),
|
unlockNotification: HUDUnlockNotification,
|
||||||
gameMenu: new HUDGameMenu(this.root),
|
gameMenu: HUDGameMenu,
|
||||||
massSelector: new HUDMassSelector(this.root),
|
massSelector: HUDMassSelector,
|
||||||
shop: new HUDShop(this.root),
|
shop: HUDShop,
|
||||||
statistics: new HUDStatistics(this.root),
|
statistics: HUDStatistics,
|
||||||
waypoints: new HUDWaypoints(this.root),
|
waypoints: HUDWaypoints,
|
||||||
wireInfo: new HUDWireInfo(this.root),
|
wireInfo: HUDWireInfo,
|
||||||
leverToggle: new HUDLeverToggle(this.root),
|
leverToggle: HUDLeverToggle,
|
||||||
constantSignalEdit: new HUDConstantSignalEdit(this.root),
|
constantSignalEdit: HUDConstantSignalEdit,
|
||||||
|
modeMenuBack: HUDModeMenuBack,
|
||||||
|
modeMenuNext: HUDModeMenuNext,
|
||||||
|
modeMenu: HUDModeMenu,
|
||||||
|
modeSettings: HUDModeSettings,
|
||||||
|
|
||||||
// Must always exist
|
// Must always exist
|
||||||
pinnedShapes: new HUDPinnedShapes(this.root),
|
pinnedShapes: HUDPinnedShapes,
|
||||||
notifications: new HUDNotifications(this.root),
|
notifications: HUDNotifications,
|
||||||
settingsMenu: new HUDSettingsMenu(this.root),
|
settingsMenu: HUDSettingsMenu,
|
||||||
debugInfo: new HUDDebugInfo(this.root),
|
debugInfo: HUDDebugInfo,
|
||||||
dialogs: new HUDModalDialogs(this.root),
|
dialogs: HUDModalDialogs,
|
||||||
screenshotExporter: new HUDScreenshotExporter(this.root),
|
screenshotExporter: HUDScreenshotExporter,
|
||||||
shapeViewer: new HUDShapeViewer(this.root),
|
shapeViewer: HUDShapeViewer,
|
||||||
|
|
||||||
wiresOverlay: new HUDWiresOverlay(this.root),
|
wiresOverlay: HUDWiresOverlay,
|
||||||
layerPreview: new HUDLayerPreview(this.root),
|
layerPreview: HUDLayerPreview,
|
||||||
|
|
||||||
minerHighlight: new HUDMinerHighlight(this.root),
|
minerHighlight: HUDMinerHighlight,
|
||||||
tutorialVideoOffer: new HUDTutorialVideoOffer(this.root),
|
tutorialVideoOffer: HUDTutorialVideoOffer,
|
||||||
|
|
||||||
// Typing hints
|
// Typing hints
|
||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
/** @type {HUDChangesDebugger} */
|
/** @type {HUDChangesDebugger} */
|
||||||
changesDebugger: null,
|
changesDebugger: null,
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!IS_MOBILE) {
|
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) {
|
if (G_IS_DEV && globalConfig.debug.enableEntityInspector) {
|
||||||
@ -130,8 +140,13 @@ export class GameHUD {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.root.app.settings.getAllSettings().offerHints) {
|
if (this.root.app.settings.getAllSettings().offerHints) {
|
||||||
this.parts.tutorialHints = new HUDPartTutorialHints(this.root);
|
if (!this.root.gameMode.isHudPartExcluded(HUDPartTutorialHints.name)) {
|
||||||
this.parts.interactiveTutorial = new HUDInteractiveTutorial(this.root);
|
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) {
|
if (this.root.app.settings.getAllSettings().vignette) {
|
||||||
@ -170,6 +185,21 @@ export class GameHUD {
|
|||||||
/* dev:end*/
|
/* 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
|
* Attempts to close all overlays
|
||||||
*/
|
*/
|
||||||
|
@ -23,8 +23,8 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
|||||||
) {
|
) {
|
||||||
super(root);
|
super(root);
|
||||||
|
|
||||||
this.primaryBuildings = primaryBuildings;
|
this.primaryBuildings = this.filterBuildings(primaryBuildings);
|
||||||
this.secondaryBuildings = secondaryBuildings;
|
this.secondaryBuildings = this.filterBuildings(secondaryBuildings);
|
||||||
this.visibilityCondition = visibilityCondition;
|
this.visibilityCondition = visibilityCondition;
|
||||||
this.htmlElementId = htmlElementId;
|
this.htmlElementId = htmlElementId;
|
||||||
this.layer = layer;
|
this.layer = layer;
|
||||||
@ -47,6 +47,24 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
|||||||
this.element = makeDiv(parent, this.htmlElementId, ["ingame_buildingsToolbar"], "");
|
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 all buildings
|
||||||
* @returns {Array<typeof MetaBuilding>}
|
* @returns {Array<typeof MetaBuilding>}
|
||||||
|
@ -15,12 +15,15 @@ import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
|
|||||||
import { HUDBaseToolbar } from "./base_toolbar";
|
import { HUDBaseToolbar } from "./base_toolbar";
|
||||||
import { MetaStorageBuilding } from "../../buildings/storage";
|
import { MetaStorageBuilding } from "../../buildings/storage";
|
||||||
import { MetaItemProducerBuilding } from "../../buildings/item_producer";
|
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 {
|
export class HUDBuildingsToolbar extends HUDBaseToolbar {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root, {
|
super(root, {
|
||||||
primaryBuildings: [
|
primaryBuildings: [
|
||||||
|
MetaConstantProducerBuilding,
|
||||||
|
MetaGoalAcceptorBuilding,
|
||||||
MetaBeltBuilding,
|
MetaBeltBuilding,
|
||||||
MetaBalancerBuilding,
|
MetaBalancerBuilding,
|
||||||
MetaUndergroundBeltBuilding,
|
MetaUndergroundBeltBuilding,
|
||||||
@ -31,7 +34,7 @@ export class HUDBuildingsToolbar extends HUDBaseToolbar {
|
|||||||
MetaMixerBuilding,
|
MetaMixerBuilding,
|
||||||
MetaPainterBuilding,
|
MetaPainterBuilding,
|
||||||
MetaTrashBuilding,
|
MetaTrashBuilding,
|
||||||
...(queryParamOptions.sandboxMode || G_IS_DEV ? [MetaItemProducerBuilding] : []),
|
MetaItemProducerBuilding,
|
||||||
],
|
],
|
||||||
secondaryBuildings: [
|
secondaryBuildings: [
|
||||||
MetaStorageBuilding,
|
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");
|
this.directionIndicatorSprite = Loader.getSprite("sprites/misc/hub_direction_indicator.png");
|
||||||
|
|
||||||
/** @type {Array<Waypoint>}
|
/** @type {Array<Waypoint>} */
|
||||||
*/
|
this.waypoints = [];
|
||||||
this.waypoints = [
|
this.waypoints.push({
|
||||||
{
|
label: null,
|
||||||
label: null,
|
center: { x: 0, y: 0 },
|
||||||
center: { x: 0, y: 0 },
|
zoomLevel: 3,
|
||||||
zoomLevel: 3,
|
layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(),
|
||||||
layer: gMetaBuildingRegistry.findByClass(MetaHubBuilding).getLayer(),
|
});
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create a buffer we can use to measure text
|
// Create a buffer we can use to measure text
|
||||||
this.dummyBuffer = makeOffscreenBuffer(1, 1, {
|
this.dummyBuffer = makeOffscreenBuffer(1, 1, {
|
||||||
|
@ -49,6 +49,10 @@ export const KEYMAPPINGS = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
buildings: {
|
buildings: {
|
||||||
|
// Puzzle buildings
|
||||||
|
constant_producer: { keyCode: key("H") },
|
||||||
|
goal_acceptor: { keyCode: key("N") },
|
||||||
|
|
||||||
// Primary Toolbar
|
// Primary Toolbar
|
||||||
belt: { keyCode: key("1") },
|
belt: { keyCode: key("1") },
|
||||||
balancer: { keyCode: key("2") },
|
balancer: { keyCode: key("2") },
|
||||||
@ -262,6 +266,8 @@ export function getStringForKeyCode(code) {
|
|||||||
return ".";
|
return ".";
|
||||||
case 191:
|
case 191:
|
||||||
return "/";
|
return "/";
|
||||||
|
case 192:
|
||||||
|
return "`";
|
||||||
case 219:
|
case 219:
|
||||||
return "[";
|
return "[";
|
||||||
case 220:
|
case 220:
|
||||||
|
@ -41,7 +41,14 @@ export class MapChunkView extends MapChunk {
|
|||||||
*/
|
*/
|
||||||
drawBackgroundLayer(parameters) {
|
drawBackgroundLayer(parameters) {
|
||||||
const systems = this.root.systemMgr.systems;
|
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.beltUnderlays.drawChunk(parameters, this);
|
||||||
systems.belt.drawChunk(parameters, this);
|
systems.belt.drawChunk(parameters, this);
|
||||||
}
|
}
|
||||||
@ -69,6 +76,7 @@ export class MapChunkView extends MapChunk {
|
|||||||
systems.lever.drawChunk(parameters, this);
|
systems.lever.drawChunk(parameters, this);
|
||||||
systems.display.drawChunk(parameters, this);
|
systems.display.drawChunk(parameters, this);
|
||||||
systems.storage.drawChunk(parameters, this);
|
systems.storage.drawChunk(parameters, this);
|
||||||
|
systems.constantProducer.drawChunk(parameters, this);
|
||||||
systems.itemProcessorOverlays.drawChunk(parameters, this);
|
systems.itemProcessorOverlays.drawChunk(parameters, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,12 @@ import { MetaAnalyzerBuilding } from "./buildings/analyzer";
|
|||||||
import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer";
|
import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer";
|
||||||
import { MetaBeltBuilding } from "./buildings/belt";
|
import { MetaBeltBuilding } from "./buildings/belt";
|
||||||
import { MetaComparatorBuilding } from "./buildings/comparator";
|
import { MetaComparatorBuilding } from "./buildings/comparator";
|
||||||
|
import { MetaConstantProducerBuilding } from "./buildings/constant_producer";
|
||||||
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
||||||
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
|
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
|
||||||
import { MetaDisplayBuilding } from "./buildings/display";
|
import { MetaDisplayBuilding } from "./buildings/display";
|
||||||
import { MetaFilterBuilding } from "./buildings/filter";
|
import { MetaFilterBuilding } from "./buildings/filter";
|
||||||
|
import { MetaGoalAcceptorBuilding } from "./buildings/goal_acceptor";
|
||||||
import { MetaHubBuilding } from "./buildings/hub";
|
import { MetaHubBuilding } from "./buildings/hub";
|
||||||
import { MetaItemProducerBuilding } from "./buildings/item_producer";
|
import { MetaItemProducerBuilding } from "./buildings/item_producer";
|
||||||
import { MetaLeverBuilding } from "./buildings/lever";
|
import { MetaLeverBuilding } from "./buildings/lever";
|
||||||
@ -45,6 +47,7 @@ export function initMetaBuildingRegistry() {
|
|||||||
gMetaBuildingRegistry.register(MetaStorageBuilding);
|
gMetaBuildingRegistry.register(MetaStorageBuilding);
|
||||||
gMetaBuildingRegistry.register(MetaBeltBuilding);
|
gMetaBuildingRegistry.register(MetaBeltBuilding);
|
||||||
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
|
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
|
||||||
|
gMetaBuildingRegistry.register(MetaGoalAcceptorBuilding);
|
||||||
gMetaBuildingRegistry.register(MetaHubBuilding);
|
gMetaBuildingRegistry.register(MetaHubBuilding);
|
||||||
gMetaBuildingRegistry.register(MetaWireBuilding);
|
gMetaBuildingRegistry.register(MetaWireBuilding);
|
||||||
gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
|
gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
|
||||||
@ -59,6 +62,7 @@ export function initMetaBuildingRegistry() {
|
|||||||
gMetaBuildingRegistry.register(MetaAnalyzerBuilding);
|
gMetaBuildingRegistry.register(MetaAnalyzerBuilding);
|
||||||
gMetaBuildingRegistry.register(MetaComparatorBuilding);
|
gMetaBuildingRegistry.register(MetaComparatorBuilding);
|
||||||
gMetaBuildingRegistry.register(MetaItemProducerBuilding);
|
gMetaBuildingRegistry.register(MetaItemProducerBuilding);
|
||||||
|
gMetaBuildingRegistry.register(MetaConstantProducerBuilding);
|
||||||
|
|
||||||
// Belt
|
// Belt
|
||||||
registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0);
|
registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0);
|
||||||
@ -165,6 +169,12 @@ export function initMetaBuildingRegistry() {
|
|||||||
// Item producer
|
// Item producer
|
||||||
registerBuildingVariant(61, MetaItemProducerBuilding);
|
registerBuildingVariant(61, MetaItemProducerBuilding);
|
||||||
|
|
||||||
|
// Constant producer
|
||||||
|
registerBuildingVariant(62, MetaConstantProducerBuilding);
|
||||||
|
|
||||||
|
// Goal acceptor
|
||||||
|
registerBuildingVariant(63, MetaGoalAcceptorBuilding);
|
||||||
|
|
||||||
// Propagate instances
|
// Propagate instances
|
||||||
for (const key in gBuildingVariants) {
|
for (const key in gBuildingVariants) {
|
||||||
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
|
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 { 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 { ShapeDefinition } from "../shape_definition";
|
||||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
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 rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
||||||
const finalGameShape = "RuCw--Cw:----Ru--";
|
const finalGameShape = "RuCw--Cw:----Ru--";
|
||||||
const preparementShape = "CpRpCp--:SwSwSwSw";
|
const preparementShape = "CpRpCp--:SwSwSwSw";
|
||||||
const blueprintShape = "CbCbCbRb:CwCwCwCw";
|
|
||||||
|
|
||||||
// Tiers need % of the previous tier as requirement too
|
// Tiers need % of the previous tier as requirement too
|
||||||
const tierGrowth = 2.5;
|
const tierGrowth = 2.5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates all upgrades
|
* Generates all upgrades
|
||||||
* @returns {Object<string, import("../game_mode").UpgradeTiers>} */
|
* @returns {Object<string, UpgradeTiers>} */
|
||||||
function generateUpgrades(limitedVersion = false) {
|
function generateUpgrades(limitedVersion = false) {
|
||||||
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
|
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
|
||||||
const numEndgameUpgrades = limitedVersion ? 0 : 1000 - fixedImprovements.length - 1;
|
const numEndgameUpgrades = limitedVersion ? 0 : 1000 - fixedImprovements.length - 1;
|
||||||
@ -454,27 +485,58 @@ const fullVersionLevels = generateLevelDefinitions(false);
|
|||||||
const demoVersionLevels = generateLevelDefinitions(true);
|
const demoVersionLevels = generateLevelDefinitions(true);
|
||||||
|
|
||||||
export class RegularGameMode extends GameMode {
|
export class RegularGameMode extends GameMode {
|
||||||
constructor(root) {
|
static getId() {
|
||||||
super(root);
|
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() {
|
getUpgrades() {
|
||||||
return this.root.app.restrictionMgr.getHasExtendedUpgrades()
|
return this.root.app.restrictionMgr.getHasExtendedUpgrades()
|
||||||
? fullVersionUpgrades
|
? fullVersionUpgrades
|
||||||
: demoVersionUpgrades;
|
: demoVersionUpgrades;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIsFreeplayAvailable() {
|
/**
|
||||||
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
|
* Returns the goals for all levels including their reward
|
||||||
}
|
* @returns {Array<LevelDefinition>}
|
||||||
|
*/
|
||||||
getBlueprintShapeKey() {
|
|
||||||
return blueprintShape;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLevelDefinitions() {
|
getLevelDefinitions() {
|
||||||
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay()
|
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay()
|
||||||
? fullVersionLevels
|
? fullVersionLevels
|
||||||
: demoVersionLevels;
|
: 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;
|
const minimumTimeForThroughput = now - 1;
|
||||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||||
const entity = this.allEntities[i];
|
const entity = this.allEntities[i];
|
||||||
|
|
||||||
const readerComp = entity.components.BeltReader;
|
const readerComp = entity.components.BeltReader;
|
||||||
const pinsComp = entity.components.WiredPins;
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
|
||||||
@ -23,12 +22,14 @@ export class BeltReaderSystem extends GameSystemWithFilter {
|
|||||||
readerComp.lastItemTimes.shift();
|
readerComp.lastItemTimes.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
pinsComp.slots[1].value = readerComp.lastItem;
|
if (!entity.components.BeltReader.isWireless()) {
|
||||||
pinsComp.slots[0].value =
|
pinsComp.slots[1].value = readerComp.lastItem;
|
||||||
(readerComp.lastItemTimes[readerComp.lastItemTimes.length - 1] || 0) >
|
pinsComp.slots[0].value =
|
||||||
minimumTimeForThroughput
|
(readerComp.lastItemTimes[readerComp.lastItemTimes.length - 1] || 0) >
|
||||||
? BOOL_TRUE_SINGLETON
|
minimumTimeForThroughput
|
||||||
: BOOL_FALSE_SINGLETON;
|
? BOOL_TRUE_SINGLETON
|
||||||
|
: BOOL_FALSE_SINGLETON;
|
||||||
|
}
|
||||||
|
|
||||||
if (now - readerComp.lastThroughputComputation > 0.5) {
|
if (now - readerComp.lastThroughputComputation > 0.5) {
|
||||||
// Compute throughput
|
// 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 { T } from "../../translations";
|
||||||
import { BaseItem } from "../base_item";
|
import { BaseItem } from "../base_item";
|
||||||
import { enumColors } from "../colors";
|
import { enumColors } from "../colors";
|
||||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
import { enumConstantSignalType, ConstantSignalComponent } from "../components/constant_signal";
|
||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
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 { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
|
||||||
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||||
import { ShapeDefinition } from "../shape_definition";
|
import { ShapeDefinition } from "../shape_definition";
|
||||||
@ -26,8 +27,13 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
|||||||
// Set signals
|
// Set signals
|
||||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||||
const entity = this.allEntities[i];
|
const entity = this.allEntities[i];
|
||||||
const pinsComp = entity.components.WiredPins;
|
|
||||||
const signalComp = entity.components.ConstantSignal;
|
const signalComp = entity.components.ConstantSignal;
|
||||||
|
|
||||||
|
if (signalComp.isWireless()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinsComp = entity.components.WiredPins;
|
||||||
pinsComp.slots[0].value = signalComp.signal;
|
pinsComp.slots[0].value = signalComp.signal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,23 +60,33 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
|||||||
validator: val => this.parseSignalCode(val),
|
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({
|
const itemInput = new FormElementItemChooser({
|
||||||
id: "signalItem",
|
id: "signalItem",
|
||||||
label: null,
|
label: null,
|
||||||
items: [
|
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)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialog = new DialogWithForm({
|
const dialog = new DialogWithForm({
|
||||||
@ -103,7 +119,6 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (itemInput.chosenItem) {
|
if (itemInput.chosenItem) {
|
||||||
console.log(itemInput.chosenItem);
|
|
||||||
constantComp.signal = itemInput.chosenItem;
|
constantComp.signal = itemInput.chosenItem;
|
||||||
} else {
|
} else {
|
||||||
constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
|
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.painterQuad]: this.process_PAINTER_QUAD,
|
||||||
[enumItemProcessorTypes.hub]: this.process_HUB,
|
[enumItemProcessorTypes.hub]: this.process_HUB,
|
||||||
[enumItemProcessorTypes.reader]: this.process_READER,
|
[enumItemProcessorTypes.reader]: this.process_READER,
|
||||||
|
[enumItemProcessorTypes.goal]: this.process_GOAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bind all handlers
|
// Bind all handlers
|
||||||
@ -562,4 +563,13 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
this.root.hubGoals.handleDefinitionDelivered(item.definition);
|
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 { ItemProducerComponent } from "../components/item_producer";
|
||||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||||
|
|
||||||
export class ItemProducerSystem extends GameSystemWithFilter {
|
export class ItemProducerSystem extends GameSystemWithFilter {
|
||||||
|
/** @param {GameRoot} root */
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root, [ItemProducerComponent]);
|
super(root, [ItemProducerComponent]);
|
||||||
|
this.item = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||||
const entity = this.allEntities[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 pinsComp = entity.components.WiredPins;
|
||||||
const pin = pinsComp.slots[0];
|
const pin = pinsComp.slots[0];
|
||||||
const network = pin.linkedNetwork;
|
const network = pin.linkedNetwork;
|
||||||
@ -17,8 +30,8 @@ export class ItemProducerSystem extends GameSystemWithFilter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ejectorComp = entity.components.ItemEjector;
|
this.item = network.currentValue;
|
||||||
ejectorComp.tryEject(0, 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",
|
"textColor": "#fff",
|
||||||
"textColorCapped": "#ef5072",
|
"textColorCapped": "#ef5072",
|
||||||
"background": "rgba(40, 50, 60, 0.8)"
|
"background": "rgba(40, 50, 60, 0.8)"
|
||||||
|
},
|
||||||
|
|
||||||
|
"zone": {
|
||||||
|
"background": "#3e3f47",
|
||||||
|
"border": "#667964"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -48,6 +48,11 @@
|
|||||||
"textColor": "#fff",
|
"textColor": "#fff",
|
||||||
"textColorCapped": "#ef5072",
|
"textColorCapped": "#ef5072",
|
||||||
"background": "rgba(40, 50, 60, 0.8)"
|
"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 { initDrawUtils } from "./core/draw_utils";
|
||||||
import { initItemRegistry } from "./game/item_registry";
|
import { initItemRegistry } from "./game/item_registry";
|
||||||
import { initMetaBuildingRegistry } from "./game/meta_building_registry";
|
import { initMetaBuildingRegistry } from "./game/meta_building_registry";
|
||||||
|
import { initGameModeRegistry } from "./game/game_mode_registry";
|
||||||
import { initGameSpeedRegistry } from "./game/game_speed_registry";
|
import { initGameSpeedRegistry } from "./game/game_speed_registry";
|
||||||
|
|
||||||
const logger = createLogger("main");
|
const logger = createLogger("main");
|
||||||
@ -81,6 +82,7 @@ initDrawUtils();
|
|||||||
initComponentRegistry();
|
initComponentRegistry();
|
||||||
initItemRegistry();
|
initItemRegistry();
|
||||||
initMetaBuildingRegistry();
|
initMetaBuildingRegistry();
|
||||||
|
initGameModeRegistry();
|
||||||
initGameSpeedRegistry();
|
initGameSpeedRegistry();
|
||||||
|
|
||||||
let app = null;
|
let app = null;
|
||||||
|
@ -13,6 +13,7 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
|
|||||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
import { SavegameInterface_V1008 } from "./schemas/1008";
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
|
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||||
|
|
||||||
const logger = createLogger("savegame");
|
const logger = createLogger("savegame");
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static getCurrentVersion() {
|
static getCurrentVersion() {
|
||||||
return 1008;
|
return 1009;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,6 +137,11 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
data.version = 1008;
|
data.version = 1008;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.version === 1008) {
|
||||||
|
SavegameInterface_V1009.migrate1008to1009(data);
|
||||||
|
data.version = 1009;
|
||||||
|
}
|
||||||
|
|
||||||
return ExplainedResult.good();
|
return ExplainedResult.good();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
|
|||||||
import { SavegameInterface_V1006 } from "./schemas/1006";
|
import { SavegameInterface_V1006 } from "./schemas/1006";
|
||||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
import { SavegameInterface_V1008 } from "./schemas/1008";
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
|
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||||
|
|
||||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||||
export const savegameInterfaces = {
|
export const savegameInterfaces = {
|
||||||
@ -21,6 +22,7 @@ export const savegameInterfaces = {
|
|||||||
1006: SavegameInterface_V1006,
|
1006: SavegameInterface_V1006,
|
||||||
1007: SavegameInterface_V1007,
|
1007: SavegameInterface_V1007,
|
||||||
1008: SavegameInterface_V1008,
|
1008: SavegameInterface_V1008,
|
||||||
|
1009: SavegameInterface_V1009,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger("savegame_interface_registry");
|
const logger = createLogger("savegame_interface_registry");
|
||||||
|
@ -2,6 +2,8 @@ import { ExplainedResult } from "../core/explained_result";
|
|||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
import { gComponentRegistry } from "../core/global_registries";
|
import { gComponentRegistry } from "../core/global_registries";
|
||||||
import { SerializerInternal } from "./serializer_internal";
|
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
|
* @typedef {import("../game/component").Component} Component
|
||||||
@ -33,12 +35,17 @@ export class SavegameSerializer {
|
|||||||
camera: root.camera.serialize(),
|
camera: root.camera.serialize(),
|
||||||
time: root.time.serialize(),
|
time: root.time.serialize(),
|
||||||
map: root.map.serialize(),
|
map: root.map.serialize(),
|
||||||
|
gameMode: root.gameMode.serialize(),
|
||||||
entityMgr: root.entityMgr.serialize(),
|
entityMgr: root.entityMgr.serialize(),
|
||||||
hubGoals: root.hubGoals.serialize(),
|
hubGoals: root.hubGoals.serialize(),
|
||||||
pinnedShapes: root.hud.parts.pinnedShapes.serialize(),
|
|
||||||
waypoints: root.hud.parts.waypoints.serialize(),
|
|
||||||
entities: this.internal.serializeEntityArray(root.entityMgr.entities),
|
entities: this.internal.serializeEntityArray(root.entityMgr.entities),
|
||||||
beltPaths: root.systemMgr.systems.belt.serializePaths(),
|
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) {
|
if (G_IS_DEV) {
|
||||||
@ -130,12 +137,19 @@ export class SavegameSerializer {
|
|||||||
errorReason = errorReason || root.time.deserialize(savegame.time);
|
errorReason = errorReason || root.time.deserialize(savegame.time);
|
||||||
errorReason = errorReason || root.camera.deserialize(savegame.camera);
|
errorReason = errorReason || root.camera.deserialize(savegame.camera);
|
||||||
errorReason = errorReason || root.map.deserialize(savegame.map);
|
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.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 || this.internal.deserializeEntityArray(root, savegame.entities);
|
||||||
errorReason = errorReason || root.systemMgr.systems.belt.deserializePaths(savegame.beltPaths);
|
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
|
// Check for errors
|
||||||
if (errorReason) {
|
if (errorReason) {
|
||||||
return ExplainedResult.bad(errorReason);
|
return ExplainedResult.bad(errorReason);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
* time: any,
|
* time: any,
|
||||||
* entityMgr: any,
|
* entityMgr: any,
|
||||||
* map: any,
|
* map: any,
|
||||||
|
* gameMode: object,
|
||||||
* hubGoals: any,
|
* hubGoals: any,
|
||||||
* pinnedShapes: any,
|
* pinnedShapes: any,
|
||||||
* waypoints: 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} */
|
/** @type {boolean|undefined} */
|
||||||
this.fastEnter;
|
this.fastEnter;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
this.gameModeId;
|
||||||
|
|
||||||
/** @type {Savegame} */
|
/** @type {Savegame} */
|
||||||
this.savegame;
|
this.savegame;
|
||||||
}
|
}
|
||||||
@ -220,7 +223,7 @@ export class InGameState extends GameState {
|
|||||||
logger.log("Creating new game core");
|
logger.log("Creating new game core");
|
||||||
this.core = new GameCore(this.app);
|
this.core = new GameCore(this.app);
|
||||||
|
|
||||||
this.core.initializeRoot(this, this.savegame);
|
this.core.initializeRoot(this, this.savegame, this.gameModeId);
|
||||||
|
|
||||||
if (this.savegame.hasGameDump()) {
|
if (this.savegame.hasGameDump()) {
|
||||||
this.stage4bResumeGame();
|
this.stage4bResumeGame();
|
||||||
@ -354,6 +357,7 @@ export class InGameState extends GameState {
|
|||||||
|
|
||||||
this.creationPayload = payload;
|
this.creationPayload = payload;
|
||||||
this.savegame = payload.savegame;
|
this.savegame = payload.savegame;
|
||||||
|
this.gameModeId = payload.gameModeId;
|
||||||
|
|
||||||
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
||||||
this.loadingOverlay.showBasic();
|
this.loadingOverlay.showBasic();
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
startFileChoose,
|
startFileChoose,
|
||||||
waitNextFrame,
|
waitNextFrame,
|
||||||
} from "../core/utils";
|
} from "../core/utils";
|
||||||
|
import { enumGameModeIds } from "../game/game_mode";
|
||||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||||
import { getApplicationSettingById } from "../profile/application_settings";
|
import { getApplicationSettingById } from "../profile/application_settings";
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
@ -82,6 +83,9 @@ export class MainMenuState extends GameState {
|
|||||||
}
|
}
|
||||||
<div class="buttons"></div>
|
<div class="buttons"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bottomContainer">
|
||||||
|
<div class="buttons"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer ${G_CHINA_VERSION ? "china" : ""}">
|
<div class="footer ${G_CHINA_VERSION ? "china" : ""}">
|
||||||
@ -204,6 +208,11 @@ export class MainMenuState extends GameState {
|
|||||||
const qs = this.htmlElement.querySelector.bind(this.htmlElement);
|
const qs = this.htmlElement.querySelector.bind(this.htmlElement);
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
|
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
|
||||||
|
if (globalConfig.debug.testPuzzleMode) {
|
||||||
|
this.onPuzzleEditButtonClicked();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const games = this.app.savegameMgr.getSavegamesMetaData();
|
const games = this.app.savegameMgr.getSavegamesMetaData();
|
||||||
if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
|
if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
|
||||||
this.resumeGame(games[0]);
|
this.resumeGame(games[0]);
|
||||||
@ -304,6 +313,68 @@ export class MainMenuState extends GameState {
|
|||||||
this.trackClicks(playBtn, this.onPlayButtonClicked);
|
this.trackClicks(playBtn, this.onPlayButtonClicked);
|
||||||
buttonContainer.appendChild(importButtonElement);
|
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() {
|
onSteamLinkClicked() {
|
||||||
|
@ -116,6 +116,12 @@ mainMenu:
|
|||||||
savegameLevel: Level <x>
|
savegameLevel: Level <x>
|
||||||
savegameLevelUnknown: Unknown Level
|
savegameLevelUnknown: Unknown Level
|
||||||
savegameUnnamed: Unnamed
|
savegameUnnamed: Unnamed
|
||||||
|
puzzleMode: Puzzle Mode
|
||||||
|
back: Back
|
||||||
|
|
||||||
|
puzzleMenu:
|
||||||
|
play: Play
|
||||||
|
edit: Edit
|
||||||
|
|
||||||
dialogs:
|
dialogs:
|
||||||
buttons:
|
buttons:
|
||||||
@ -477,6 +483,24 @@ ingame:
|
|||||||
title: Support me
|
title: Support me
|
||||||
desc: I develop the game in my spare time!
|
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
|
# All shop upgrades
|
||||||
shopUpgrades:
|
shopUpgrades:
|
||||||
belt:
|
belt:
|
||||||
@ -701,6 +725,16 @@ buildings:
|
|||||||
name: Item Producer
|
name: Item Producer
|
||||||
description: Available in sandbox mode only, outputs the given signal from the wires layer on the regular layer.
|
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:
|
storyRewards:
|
||||||
# Those are the rewards gained from completing the store
|
# Those are the rewards gained from completing the store
|
||||||
reward_cutter_and_trash:
|
reward_cutter_and_trash:
|
||||||
@ -1128,6 +1162,8 @@ keybindings:
|
|||||||
analyzer: *analyzer
|
analyzer: *analyzer
|
||||||
comparator: *comparator
|
comparator: *comparator
|
||||||
item_producer: Item Producer (Sandbox)
|
item_producer: Item Producer (Sandbox)
|
||||||
|
constant_producer: *constant_producer
|
||||||
|
goal_acceptor: *goal_acceptor
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
pipette: Pipette
|
pipette: Pipette
|
||||||
|