mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Mod Support - 1.5.0 Update (#1361)
* initial modloader draft * modloader features * Refactor mods to use signals * Add support for modifying and registering new transltions * Minor adjustments * Support for string building ids for mods * Initial support for adding new buildings * Refactor how mods are loaded to resolve circular dependencies and prepare for future mod loading * Lazy Load mods to make sure all dependencies are loaded * Expose all exported members automatically to mods * Fix duplicate exports * Allow loading mods from standalone * update changelog * Fix mods folder incorrect path * Fix modloading in standalone * Fix sprites not getting replaced, update demo mod * Load dev mod via raw loader * Improve mod developing so mods are directly ready to be deployed, load mods from local file server * Proper mods ui * Allow mods to register game systems and draw stuff * Change mods path * Fix sprites not loading * Minor adjustments, closes #1333 * Add support for loading atlases via mods * Add support for loading mods from external sources in DEV * Add confirmation when loading mods * Fix circular dependency * Minor Keybindings refactor, add support for keybindings to mods, add support for dialogs to mods * Add some mod signals * refactor game loading states * Make shapez exports global * Start to make mods safer * Refactor file system electron event handling * Properly isolate electron renderer process * Update to latest electron * Show errors when loading mods * Update confirm dialgo * Minor restructure, start to add mod examples * Allow adding custom themesw * Add more examples and allow defining custom item processor operations * Add interface to register new buildings * Fixed typescript type errors (#1335) * Refactor building registry, make it easier for mods to add new buildings * Allow overriding existing methods * Add more examples and more features * More mod examples * Make mod loading simpler * Add example how to add custom drawings * Remove unused code * Minor modloader adjustments * Support for rotation variants in mods (was broken previously) * Allow mods to replace builtin sub shapes * Add helper methods to extend classes * Fix menu bar on mac os * Remember window state * Add support for paste signals * Add example how to add custom components and systems * Support for mod settings * Add example for adding a new item type * Update class extensions * Minor adjustments * Fix typo * Add notification blocks mod example * Add small tutorial * Update readme * Add better instructions * Update JSDoc for Replacing Methods (#1336) * upgraded types for overriding methods * updated comments Co-authored-by: Edward Badel <you@example.com> * Direction lock now indicates when there is a building inbetween * Fix mod examples * Fix linter error * Game state register (#1341) * Added a gamestate register helper Added a gamestate register helper * Update mod_interface.js * export build options * Fix runBeforeMethod and runAfterMethod * Minor game system code cleanup * Belt path drawing optimization * Fix belt path optimization * Belt drawing improvements, again * Do not render belts in statics disabled view * Allow external URL to load more than one mod (#1337) * Allow external URL to load more than one mod Instead of loading the text returned from the remote server, load a JSON object with a `mods` field, containing strings of all the mods. This lets us work on more than one mod at a time or without separate repos. This will break tooling such as `create-shapezio-mod` though. * Update modloader.js * Prettier fixes * Added link to create-shapezio-mod npm page (#1339) Added link to create-shapezio-mod npm page: https://www.npmjs.com/package/create-shapezio-mod * allow command line switch to load more than one mod (#1342) * Fixed class handle type (#1345) * Fixed class handle type * Fixed import game state * Minor adjustments * Refactor item acceptor to allow only single direction slots * Allow specifying minimumGameVersion * Add sandbox example * Replaced concatenated strings with template literals (#1347) * Mod improvements * Make wired pins component optional on the storage * Fix mod examples * Bind `this` for method overriding JSDoc (#1352) * fix entity debugger reaching HTML elements (#1353) * Store mods in savegame and show warning when it differs * Closes #1357 * Fix All Shapez Exports Being Const (#1358) * Allowed setting of variables inside webpack modules * remove console log * Fix stringification of things inside of eval Co-authored-by: Edward Badel <you@example.com> * Fix building placer intersection warning * Add example for storing data in the savegame * Fix double painter bug (#1349) * Add example on how to extend builtin buildings * update readme * Disable steam achievements when playing with mods * Update translations Co-authored-by: Thomas (DJ1TJOO) <44841260+DJ1TJOO@users.noreply.github.com> Co-authored-by: Bagel03 <70449196+Bagel03@users.noreply.github.com> Co-authored-by: Edward Badel <you@example.com> Co-authored-by: Emerald Block <69981203+EmeraldBlock@users.noreply.github.com> Co-authored-by: saile515 <63782477+saile515@users.noreply.github.com> Co-authored-by: Sense101 <67970865+Sense101@users.noreply.github.com>
This commit is contained in:
@@ -169,6 +169,10 @@
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@include S(margin-top, 10px);
|
||||
}
|
||||
|
||||
input {
|
||||
background: #eee;
|
||||
color: #333438;
|
||||
@@ -214,6 +218,33 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialogModsMod {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
@include S(padding, 5px);
|
||||
@include S(margin, 10px, 0);
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr D(100px);
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1 / 3;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.version {
|
||||
@include SuperSmallText;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.name {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
@import "states/mobile_warning";
|
||||
@import "states/changelog";
|
||||
@import "states/puzzle_menu";
|
||||
@import "states/mods";
|
||||
|
||||
@import "ingame_hud/buildings_toolbar";
|
||||
@import "ingame_hud/building_placer";
|
||||
|
||||
@@ -61,7 +61,8 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2,
|
||||
background-image: uiResource("res/ui/building_tutorials/virtual_processor-cutter.png") !important;
|
||||
}
|
||||
|
||||
$icons: notification_saved, notification_success, notification_upgrade;
|
||||
$icons: notification_saved, notification_success, notification_upgrade, notification_info,
|
||||
notification_warning, notification_error;
|
||||
@each $icon in $icons {
|
||||
[data-icon="icons/#{$icon}.png"] {
|
||||
/* @load-async */
|
||||
|
||||
@@ -185,21 +185,20 @@
|
||||
.updateLabel {
|
||||
position: absolute;
|
||||
transform: translateX(50%) rotate(-5deg);
|
||||
color: #ff590b;
|
||||
@include Heading;
|
||||
color: #fff;
|
||||
@include PlainText;
|
||||
font-weight: bold;
|
||||
@include S(right, 40px);
|
||||
@include S(bottom, 20px);
|
||||
background: $modsColor;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(padding, 0, 5px, 1px, 5px);
|
||||
|
||||
@include InlineAnimation(1.3s ease-in-out infinite) {
|
||||
50% {
|
||||
transform: translateX(50%) rotate(-7deg) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
color: $colorBlueBright;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +289,99 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modsOverview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
grid-row: 1 / 2;
|
||||
grid-column: 2 / 3;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
align-items: flex-start;
|
||||
@include S(width, 250px);
|
||||
@include S(padding, 15px);
|
||||
@include S(padding-bottom, 10px);
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
@include S(margin-bottom, 10px);
|
||||
|
||||
.editMods {
|
||||
margin-left: auto;
|
||||
@include S(width, 20px);
|
||||
@include S(height, 20px);
|
||||
padding: 0;
|
||||
opacity: 0.5;
|
||||
background: transparent center center/ 80% no-repeat;
|
||||
& {
|
||||
/* @load-async */
|
||||
background-image: uiResource("icons/edit_key.png") !important;
|
||||
}
|
||||
@include DarkThemeInvert;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
@include Heading;
|
||||
color: $modsColor;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dlcHint {
|
||||
@include SuperSmallText;
|
||||
@include S(margin-top, 10px);
|
||||
width: 100%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mod {
|
||||
background: #eee;
|
||||
width: 100%;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(padding, 5px);
|
||||
box-sizing: border-box;
|
||||
@include PlainText;
|
||||
@include S(margin-bottom, 5px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.author,
|
||||
.version {
|
||||
@include SuperSmallText;
|
||||
align-self: end;
|
||||
opacity: 0.4;
|
||||
}
|
||||
.name {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.modsList {
|
||||
box-sizing: border-box;
|
||||
@include S(height, 100px);
|
||||
@include S(padding, 5px);
|
||||
border: D(1px) solid #eee;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: all;
|
||||
|
||||
:last-child {
|
||||
margin-bottom: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -308,6 +400,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modeButtons {
|
||||
@@ -352,27 +445,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
.importButton {
|
||||
.outer {
|
||||
@include S(margin-top, 15px);
|
||||
}
|
||||
|
||||
.importButton {
|
||||
@include IncreasedClickArea(0px);
|
||||
}
|
||||
|
||||
.newGameButton {
|
||||
@include IncreasedClickArea(0px);
|
||||
@include S(margin-top, 15px);
|
||||
@include S(margin-left, 15px);
|
||||
@include S(margin-left, 10px);
|
||||
}
|
||||
|
||||
.playModeButton {
|
||||
.modsButton {
|
||||
@include IncreasedClickArea(0px);
|
||||
@include S(margin-top, 15px);
|
||||
@include S(margin-left, 15px);
|
||||
}
|
||||
@include S(margin-left, 10px);
|
||||
|
||||
.editModeButton {
|
||||
@include IncreasedClickArea(0px);
|
||||
@include S(margin-top, 15px);
|
||||
@include S(margin-left, 15px);
|
||||
// @include S(width, 20px);
|
||||
|
||||
// & {
|
||||
// /* @load-async */
|
||||
// background-image: uiResource("res/ui/icons/mods_white.png") !important;
|
||||
// }
|
||||
background-position: center center;
|
||||
background-size: D(15px);
|
||||
background-color: $modsColor !important;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.savegames {
|
||||
@@ -736,6 +835,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modsOverview {
|
||||
background: $darkModeControlsBackground;
|
||||
|
||||
.modsList {
|
||||
border-color: darken($darkModeControlsBackground, 5);
|
||||
|
||||
.mod {
|
||||
background: darken($darkModeControlsBackground, 5);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.dlcHint {
|
||||
color: $accentColorBright;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
> a,
|
||||
.sidelinks > a {
|
||||
|
||||
141
src/css/states/mods.scss
Normal file
141
src/css/states/mods.scss
Normal file
@@ -0,0 +1,141 @@
|
||||
#state_ModsState {
|
||||
.mainContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
> .headerBar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
|
||||
> h1 {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.openModsFolder {
|
||||
background-color: $modsColor;
|
||||
}
|
||||
}
|
||||
|
||||
.noModSupport {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.steamLink {
|
||||
@include S(height, 50px);
|
||||
@include S(width, 220px);
|
||||
background: #171a23 center center / contain no-repeat;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
text-indent: -999em;
|
||||
cursor: pointer;
|
||||
@include S(margin-top, 30px);
|
||||
pointer-events: all;
|
||||
transition: all 0.12s ease-in;
|
||||
transition-property: opacity, transform;
|
||||
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modsStats {
|
||||
@include PlainText;
|
||||
color: $accentColorDark;
|
||||
|
||||
&.noMods {
|
||||
@include S(width, 400px);
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@include Text;
|
||||
@include S(margin-top, 100px);
|
||||
color: lighten($accentColorDark, 15);
|
||||
|
||||
button {
|
||||
@include S(margin-top, 10px);
|
||||
@include S(padding, 10px, 20px);
|
||||
}
|
||||
|
||||
&::before {
|
||||
@include S(margin-bottom, 15px);
|
||||
content: "";
|
||||
@include S(width, 50px);
|
||||
@include S(height, 50px);
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
opacity: 0.2;
|
||||
}
|
||||
&::before {
|
||||
/* @load-async */
|
||||
background-image: uiResource("res/ui/icons/mods.png") !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modsList {
|
||||
@include S(margin-top, 10px);
|
||||
overflow-y: scroll;
|
||||
pointer-events: all;
|
||||
@include S(padding-right, 5px);
|
||||
flex-grow: 1;
|
||||
|
||||
.mod {
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
background: $accentColorBright;
|
||||
@include S(margin-bottom, 4px);
|
||||
@include S(padding, 7px, 10px);
|
||||
@include S(grid-gap, 15px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr D(100px) D(80px) D(50px);
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: darken($darkModeControlsBackground, 5);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.mainInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
@include SuperSmallText;
|
||||
@include S(margin-top, 5px);
|
||||
color: $accentColorDark;
|
||||
}
|
||||
.website {
|
||||
text-transform: uppercase;
|
||||
align-self: start;
|
||||
@include SuperSmallText;
|
||||
@include S(margin-top, 5px);
|
||||
}
|
||||
}
|
||||
|
||||
.version,
|
||||
.author {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
strong {
|
||||
text-transform: uppercase;
|
||||
color: $accentColorDark;
|
||||
@include SuperSmallText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,20 +15,21 @@
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: grid;
|
||||
display: flex;
|
||||
@include S(min-width, 210px);
|
||||
@include S(max-width, 320px);
|
||||
@include S(grid-gap, 3px);
|
||||
grid-template-rows: auto auto auto auto auto 1fr;
|
||||
flex-direction: column;
|
||||
|
||||
@include StyleBelowWidth($layoutBreak) {
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-template-columns: auto auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
@include S(grid-gap, 5px);
|
||||
max-width: unset !important;
|
||||
}
|
||||
|
||||
button {
|
||||
text-align: left;
|
||||
@include S(margin-bottom, 3px);
|
||||
&::after {
|
||||
content: unset;
|
||||
}
|
||||
@@ -37,15 +38,26 @@
|
||||
|
||||
@include StyleBelowWidth($layoutBreak) {
|
||||
text-align: center;
|
||||
height: D(30px) !important;
|
||||
padding: D(5px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.other {
|
||||
@include S(margin-top, 10px);
|
||||
align-self: end;
|
||||
margin-top: auto;
|
||||
|
||||
@include StyleBelowWidth($layoutBreak) {
|
||||
margin-top: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@include S(grid-gap, 5px);
|
||||
max-width: unset !important;
|
||||
grid-column: 1 / 3;
|
||||
|
||||
button {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +81,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
button.privacy {
|
||||
@include S(margin-top, 4px);
|
||||
button.manageMods {
|
||||
background-color: lighten($modsColor, 38);
|
||||
color: $modsColor;
|
||||
display: flex;
|
||||
@include S(padding-right, 5px);
|
||||
.newBadge {
|
||||
color: #fff;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
background: $modsColor;
|
||||
margin-left: auto;
|
||||
@include S(padding, 0, 3px, 0, 3px);
|
||||
|
||||
@include InlineAnimation(1.3s ease-in-out infinite) {
|
||||
50% {
|
||||
transform: rotate(0deg) scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $colorGreenBright;
|
||||
}
|
||||
}
|
||||
|
||||
.versionbar {
|
||||
|
||||
@@ -38,6 +38,7 @@ $colorRedBright: #ef5072;
|
||||
$colorOrangeBright: #ef9d50;
|
||||
$themeColor: #393747;
|
||||
$ingameHudBg: rgba(#333438, 0.9);
|
||||
$modsColor: rgb(214, 60, 228);
|
||||
|
||||
$text3dColor: #f4ffff;
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<meta name="theme-color" content="#393747" />
|
||||
|
||||
<!-- seo -->
|
||||
<meta name="copyright" content="2020 Tobias Springer IT Solutions and .io Games" />
|
||||
<meta name="author" content="Tobias Springer, tobias.springer1@gmail.com" />
|
||||
<meta name="copyright" content="2022 tobspr Games" />
|
||||
<meta name="author" content="tobspr Games - tobspr.io" />
|
||||
<meta
|
||||
name="description"
|
||||
content="shapez.io is an open-source factory building game about combining and producing different types of shapes."
|
||||
|
||||
@@ -35,6 +35,9 @@ import { PuzzleMenuState } from "./states/puzzle_menu";
|
||||
import { ClientAPI } from "./platform/api";
|
||||
import { LoginState } from "./states/login";
|
||||
import { WegameSplashState } from "./states/wegame_splash";
|
||||
import { MODS } from "./mods/modloader";
|
||||
import { MOD_SIGNALS } from "./mods/mod_signals";
|
||||
import { ModsState } from "./states/mods";
|
||||
|
||||
/**
|
||||
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
|
||||
@@ -62,10 +65,24 @@ if (typeof document.hidden !== "undefined") {
|
||||
}
|
||||
|
||||
export class Application {
|
||||
constructor() {
|
||||
/**
|
||||
* Boots the application
|
||||
*/
|
||||
async boot() {
|
||||
console.log("Booting ...");
|
||||
|
||||
assert(!GLOBAL_APP, "Tried to construct application twice");
|
||||
logger.log("Creating application, platform =", getPlatformName());
|
||||
setGlobalApp(this);
|
||||
MODS.app = this;
|
||||
|
||||
// MODS
|
||||
|
||||
try {
|
||||
await MODS.initMods();
|
||||
} catch (ex) {
|
||||
alert("Failed to load mods (launch with --dev for more info): \n\n" + ex);
|
||||
}
|
||||
|
||||
this.unloaded = false;
|
||||
|
||||
@@ -128,6 +145,31 @@ export class Application {
|
||||
// Store the mouse position, or null if not available
|
||||
/** @type {Vector|null} */
|
||||
this.mousePosition = null;
|
||||
|
||||
this.registerStates();
|
||||
this.registerEventListeners();
|
||||
|
||||
Loader.linkAppAfterBoot(this);
|
||||
|
||||
if (G_WEGAME_VERSION) {
|
||||
this.stateMgr.moveToState("WegameSplashState");
|
||||
}
|
||||
|
||||
// Check for mobile
|
||||
else if (IS_MOBILE) {
|
||||
this.stateMgr.moveToState("MobileWarningState");
|
||||
} else {
|
||||
this.stateMgr.moveToState("PreloadState");
|
||||
}
|
||||
|
||||
// Starting rendering
|
||||
this.ticker.frameEmitted.add(this.onFrameEmitted, this);
|
||||
this.ticker.bgFrameEmitted.add(this.onBackgroundFrame, this);
|
||||
this.ticker.start();
|
||||
|
||||
window.focus();
|
||||
|
||||
MOD_SIGNALS.appBooted.dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,6 +209,7 @@ export class Application {
|
||||
ChangelogState,
|
||||
PuzzleMenuState,
|
||||
LoginState,
|
||||
ModsState,
|
||||
];
|
||||
|
||||
for (let i = 0; i < states.length; ++i) {
|
||||
@@ -322,35 +365,6 @@ export class Application {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boots the application
|
||||
*/
|
||||
boot() {
|
||||
console.log("Booting ...");
|
||||
this.registerStates();
|
||||
this.registerEventListeners();
|
||||
|
||||
Loader.linkAppAfterBoot(this);
|
||||
|
||||
if (G_WEGAME_VERSION) {
|
||||
this.stateMgr.moveToState("WegameSplashState");
|
||||
}
|
||||
|
||||
// Check for mobile
|
||||
else if (IS_MOBILE) {
|
||||
this.stateMgr.moveToState("MobileWarningState");
|
||||
} else {
|
||||
this.stateMgr.moveToState("PreloadState");
|
||||
}
|
||||
|
||||
// Starting rendering
|
||||
this.ticker.frameEmitted.add(this.onFrameEmitted, this);
|
||||
this.ticker.bgFrameEmitted.add(this.onBackgroundFrame, this);
|
||||
this.ticker.start();
|
||||
|
||||
window.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deinitializes the application
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export const CHANGELOG = [
|
||||
{
|
||||
version: "1.5.0",
|
||||
date: "unreleased",
|
||||
entries: [
|
||||
"This version adds an official modloader! You can now load mods by placing it in the mods/ folder of the game.",
|
||||
"When holding shift while placing a belt, the indicator now becomes red when crossing buildings",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "1.4.4",
|
||||
date: "29.08.2021",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SOUNDS, MUSIC } from "../platform/sound";
|
||||
import { AtlasDefinition, atlasFiles } from "./atlas_definitions";
|
||||
import { initBuildingCodesAfterResourcesLoaded } from "../game/meta_building_registry";
|
||||
import { cachebust } from "./cachebust";
|
||||
import { MODS } from "../mods/modloader";
|
||||
|
||||
const logger = createLogger("background_loader");
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ export const THIRDPARTY_URLS = {
|
||||
25: "https://www.youtube.com/watch?v=7OCV1g40Iew&",
|
||||
26: "https://www.youtube.com/watch?v=gfm6dS1dCoY",
|
||||
},
|
||||
|
||||
modBrowser: "https://shapez.mod.io/?preview=f55f6304ca4873d9a25f3b575571b948",
|
||||
};
|
||||
|
||||
// export const A_B_TESTING_LINK_TYPE = Math.random() > 0.95 ? "steam_1_pr" : "steam_2_npr";
|
||||
@@ -42,7 +44,7 @@ export const globalConfig = {
|
||||
// Which dpi the assets have
|
||||
assetsDpi: 192 / 32,
|
||||
assetsSharpness: 1.5,
|
||||
shapesSharpness: 1.4,
|
||||
shapesSharpness: 1.3,
|
||||
|
||||
// Achievements
|
||||
achievementSliceDuration: 10, // Seconds
|
||||
@@ -61,6 +63,8 @@ export const globalConfig = {
|
||||
mapChunkOverviewMinZoom: 0.9,
|
||||
mapChunkWorldSize: null, // COMPUTED
|
||||
|
||||
maxBeltShapeBundleSize: 20,
|
||||
|
||||
// Belt speeds
|
||||
// NOTICE: Update webpack.production.config too!
|
||||
beltSpeedItemsPerSecond: 2,
|
||||
|
||||
@@ -116,5 +116,11 @@ export default {
|
||||
// Disables slow asserts, useful for debugging performance
|
||||
// disableSlowAsserts: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Allows to load a mod from an external source for developing it
|
||||
// externalModUrl: "http://localhost:3005/combined.js",
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Visualizes the shape grouping on belts
|
||||
// showShapeGrouping: true
|
||||
// -----------------------------------------------------------------------------------
|
||||
/* dev:end */
|
||||
};
|
||||
|
||||
@@ -15,3 +15,20 @@ export function setGlobalApp(app) {
|
||||
assert(!GLOBAL_APP, "Create application twice!");
|
||||
GLOBAL_APP = app;
|
||||
}
|
||||
|
||||
export const BUILD_OPTIONS = {
|
||||
HAVE_ASSERT: G_HAVE_ASSERT,
|
||||
APP_ENVIRONMENT: G_APP_ENVIRONMENT,
|
||||
TRACKING_ENDPOINT: G_TRACKING_ENDPOINT,
|
||||
CHINA_VERSION: G_CHINA_VERSION,
|
||||
WEGAME_VERSION: G_WEGAME_VERSION,
|
||||
IS_DEV: G_IS_DEV,
|
||||
IS_RELEASE: G_IS_RELEASE,
|
||||
IS_MOBILE_APP: G_IS_MOBILE_APP,
|
||||
IS_BROWSER: G_IS_BROWSER,
|
||||
IS_STANDALONE: G_IS_STANDALONE,
|
||||
BUILD_TIME: G_BUILD_TIME,
|
||||
BUILD_COMMIT_HASH: G_BUILD_COMMIT_HASH,
|
||||
BUILD_VERSION: G_BUILD_VERSION,
|
||||
ALL_UI_IMAGES: G_ALL_UI_IMAGES,
|
||||
};
|
||||
|
||||
@@ -149,6 +149,8 @@ export class InputDistributor {
|
||||
window.addEventListener("mouseup", this.handleKeyMouseUp.bind(this));
|
||||
|
||||
window.addEventListener("blur", this.handleBlur.bind(this));
|
||||
|
||||
document.addEventListener("paste", this.handlePaste.bind(this));
|
||||
}
|
||||
|
||||
forwardToReceiver(eventId, payload = null) {
|
||||
@@ -186,6 +188,13 @@ export class InputDistributor {
|
||||
this.keysDown.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlePaste(ev) {
|
||||
this.forwardToReceiver("paste", ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent | MouseEvent} event
|
||||
*/
|
||||
@@ -211,6 +220,7 @@ export class InputDistributor {
|
||||
keyCode: keyCode,
|
||||
shift: event.shiftKey,
|
||||
alt: event.altKey,
|
||||
ctrl: event.ctrlKey,
|
||||
initial: isInitial,
|
||||
event,
|
||||
}) === STOP_PROPAGATION
|
||||
|
||||
@@ -12,12 +12,15 @@ export class InputReceiver {
|
||||
|
||||
// Dispatched on destroy
|
||||
this.destroyed = new Signal();
|
||||
|
||||
this.paste = new Signal();
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.backButton.removeAll();
|
||||
this.keydown.removeAll();
|
||||
this.keyup.removeAll();
|
||||
this.paste.removeAll();
|
||||
|
||||
this.destroyed.dispatch();
|
||||
}
|
||||
|
||||
@@ -169,6 +169,9 @@ class LoaderImpl {
|
||||
sprite = new AtlasSprite(spriteName);
|
||||
this.sprites.set(spriteName, sprite);
|
||||
}
|
||||
if (sprite.frozen) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const link = new SpriteAtlasLink({
|
||||
packedX: frame.x,
|
||||
@@ -181,6 +184,7 @@ class LoaderImpl {
|
||||
w: sourceSize.w,
|
||||
h: sourceSize.h,
|
||||
});
|
||||
|
||||
sprite.linksByResolution[scale] = link;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,9 @@ export class Dialog {
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean} param0.shift
|
||||
* @param {boolean} param0.alt
|
||||
* @param {boolean} param0.ctrl
|
||||
*/
|
||||
handleKeydown({ keyCode, shift, alt }) {
|
||||
handleKeydown({ keyCode, shift, alt, ctrl }) {
|
||||
if (keyCode === kbEnter && this.enterHandler) {
|
||||
this.internalButtonHandler(this.enterHandler);
|
||||
return STOP_PROPAGATION;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BaseItem } from "../game/base_item";
|
||||
import { ClickDetector } from "./click_detector";
|
||||
import { Signal } from "./signal";
|
||||
import { getIPCRenderer } from "./utils";
|
||||
|
||||
/*
|
||||
* ***************************************************
|
||||
@@ -113,13 +112,11 @@ export class FormElementInput extends FormElement {
|
||||
if (G_WEGAME_VERSION) {
|
||||
const value = String(this.element.value);
|
||||
|
||||
getIPCRenderer()
|
||||
.invoke("profanity-check", value)
|
||||
.then(newValue => {
|
||||
if (value !== newValue && this.element) {
|
||||
this.element.value = newValue;
|
||||
}
|
||||
});
|
||||
ipcRenderer.invoke("profanity-check", value).then(newValue => {
|
||||
if (value !== newValue && this.element) {
|
||||
this.element.value = newValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ export class AtlasSprite extends BaseSprite {
|
||||
/** @type {Object.<string, SpriteAtlasLink>} */
|
||||
this.linksByResolution = {};
|
||||
this.spriteName = spriteName;
|
||||
|
||||
this.frozen = false;
|
||||
}
|
||||
|
||||
getRawTexture() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { GameState } from "./game_state";
|
||||
import { createLogger } from "./logging";
|
||||
import { APPLICATION_ERROR_OCCURED } from "./error_handler";
|
||||
import { waitNextFrame, removeAllChildren } from "./utils";
|
||||
import { MOD_SIGNALS } from "../mods/mod_signals";
|
||||
|
||||
const logger = createLogger("state_manager");
|
||||
|
||||
@@ -109,6 +110,8 @@ export class StateManager {
|
||||
key
|
||||
);
|
||||
|
||||
MOD_SIGNALS.stateEntered.dispatch(this.currentState);
|
||||
|
||||
waitNextFrame().then(() => {
|
||||
document.body.classList.add("arrived");
|
||||
});
|
||||
|
||||
@@ -42,21 +42,6 @@ export function getPlatformName() {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IPC renderer, or null if not within the standalone
|
||||
* @returns {object|null}
|
||||
*/
|
||||
let ipcRenderer = null;
|
||||
export function getIPCRenderer() {
|
||||
if (!G_IS_STANDALONE) {
|
||||
return null;
|
||||
}
|
||||
if (!ipcRenderer) {
|
||||
ipcRenderer = eval("require")("electron").ipcRenderer;
|
||||
}
|
||||
return ipcRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a new 2D array with undefined contents
|
||||
* @param {number} w
|
||||
@@ -395,7 +380,7 @@ export function clamp(v, minimum = 0, maximum = 1) {
|
||||
* @param {Array<string>=} classes
|
||||
* @param {string=} innerHTML
|
||||
*/
|
||||
function makeDivElement(id = null, classes = [], innerHTML = "") {
|
||||
export function makeDivElement(id = null, classes = [], innerHTML = "") {
|
||||
const div = document.createElement("div");
|
||||
if (id) {
|
||||
div.id = id;
|
||||
|
||||
@@ -2,9 +2,6 @@ import { globalConfig } from "../core/config";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { BasicSerializableObject } from "../savegame/serialization";
|
||||
|
||||
/** @type {ItemType[]} **/
|
||||
export const itemTypes = ["shape", "color", "boolean"];
|
||||
|
||||
/**
|
||||
* Class for items on belts etc. Not an entity for performance reasons
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { smoothenDpi } from "../core/dpi_manager";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { Rectangle } from "../core/rectangle";
|
||||
import { ORIGINAL_SPRITE_SCALE } from "../core/sprites";
|
||||
import { clamp, epsilonCompare, round4Digits } from "../core/utils";
|
||||
import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
@@ -1430,6 +1432,12 @@ export class BeltPath extends BasicSerializableObject {
|
||||
|
||||
let trackPos = 0.0;
|
||||
|
||||
/**
|
||||
* @type {Array<[Vector, BaseItem]>}
|
||||
*/
|
||||
let drawStack = [];
|
||||
let drawStackProp = "";
|
||||
|
||||
// Iterate whole track and check items
|
||||
for (let i = 0; i < this.entityPath.length; ++i) {
|
||||
const entity = this.entityPath[i];
|
||||
@@ -1449,25 +1457,185 @@ export class BeltPath extends BasicSerializableObject {
|
||||
const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
|
||||
|
||||
const distanceAndItem = this.items[currentItemIndex];
|
||||
const item = distanceAndItem[1 /* item */];
|
||||
const nextItemDistance = distanceAndItem[0 /* nextDistance */];
|
||||
|
||||
distanceAndItem[1 /* item */].drawItemCenteredClipped(
|
||||
worldPos.x,
|
||||
worldPos.y,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
if (
|
||||
!parameters.visibleRect.containsCircle(
|
||||
worldPos.x,
|
||||
worldPos.y,
|
||||
globalConfig.defaultItemDiameter
|
||||
)
|
||||
) {
|
||||
// this one isn't visible, do not append it
|
||||
// Start a new stack
|
||||
this.drawDrawStack(drawStack, parameters, drawStackProp);
|
||||
drawStack = [];
|
||||
drawStackProp = "";
|
||||
} else {
|
||||
if (drawStack.length > 1) {
|
||||
// Check if we can append to the stack, since its already a stack of two same items
|
||||
const referenceItem = drawStack[0];
|
||||
if (Math.abs(referenceItem[0][drawStackProp] - worldPos[drawStackProp]) < 0.001) {
|
||||
// Will continue stack
|
||||
} else {
|
||||
// Start a new stack, since item doesn't follow in row
|
||||
this.drawDrawStack(drawStack, parameters, drawStackProp);
|
||||
drawStack = [];
|
||||
drawStackProp = "";
|
||||
}
|
||||
} else if (drawStack.length === 1) {
|
||||
const firstItem = drawStack[0];
|
||||
|
||||
// Check if we can make it a stack
|
||||
if (firstItem[1 /* item */].equals(item)) {
|
||||
// Same item, check if it is either horizontal or vertical
|
||||
const startPos = firstItem[0 /* pos */];
|
||||
|
||||
if (Math.abs(startPos.x - worldPos.x) < 0.001) {
|
||||
drawStackProp = "x";
|
||||
} else if (Math.abs(startPos.y - worldPos.y) < 0.001) {
|
||||
drawStackProp = "y";
|
||||
} else {
|
||||
// Start a new stack
|
||||
this.drawDrawStack(drawStack, parameters, drawStackProp);
|
||||
drawStack = [];
|
||||
drawStackProp = "";
|
||||
}
|
||||
} else {
|
||||
// Start a new stack, since item doesn't equal
|
||||
this.drawDrawStack(drawStack, parameters, drawStackProp);
|
||||
drawStack = [];
|
||||
drawStackProp = "";
|
||||
}
|
||||
} else {
|
||||
// First item of stack, do nothing
|
||||
}
|
||||
|
||||
drawStack.push([worldPos, item]);
|
||||
}
|
||||
|
||||
// Check for the next item
|
||||
currentItemPos += distanceAndItem[0 /* nextDistance */];
|
||||
currentItemPos += nextItemDistance;
|
||||
++currentItemIndex;
|
||||
|
||||
if (
|
||||
nextItemDistance > globalConfig.itemSpacingOnBelts + 0.001 ||
|
||||
drawStack.length > globalConfig.maxBeltShapeBundleSize
|
||||
) {
|
||||
// If next item is not directly following, abort drawing
|
||||
this.drawDrawStack(drawStack, parameters, drawStackProp);
|
||||
drawStack = [];
|
||||
drawStackProp = "";
|
||||
}
|
||||
|
||||
if (currentItemIndex >= this.items.length) {
|
||||
// We rendered all items
|
||||
|
||||
this.drawDrawStack(drawStack, parameters, drawStackProp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
trackPos += beltLength;
|
||||
}
|
||||
|
||||
this.drawDrawStack(drawStack, parameters, drawStackProp);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} w
|
||||
* @param {number} h
|
||||
* @param {number} dpi
|
||||
* @param {object} param0
|
||||
* @param {string} param0.direction
|
||||
* @param {Array<[Vector, BaseItem]>} param0.stack
|
||||
* @param {GameRoot} param0.root
|
||||
* @param {number} param0.zoomLevel
|
||||
*/
|
||||
drawShapesInARow(canvas, context, w, h, dpi, { direction, stack, root, zoomLevel }) {
|
||||
context.scale(dpi, dpi);
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.showShapeGrouping) {
|
||||
context.fillStyle = "rgba(0, 0, 255, 0.5)";
|
||||
context.fillRect(0, 0, w, h);
|
||||
}
|
||||
|
||||
const parameters = new DrawParameters({
|
||||
context,
|
||||
desiredAtlasScale: ORIGINAL_SPRITE_SCALE,
|
||||
root,
|
||||
visibleRect: new Rectangle(-1000, -1000, 2000, 2000),
|
||||
zoomLevel,
|
||||
});
|
||||
|
||||
const itemSize = globalConfig.itemSpacingOnBelts * globalConfig.tileSize;
|
||||
const item = stack[0];
|
||||
const pos = new Vector(itemSize / 2, itemSize / 2);
|
||||
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
item[1].drawItemCenteredClipped(pos.x, pos.y, parameters, globalConfig.defaultItemDiameter);
|
||||
pos[direction] += globalConfig.itemSpacingOnBelts * globalConfig.tileSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<[Vector, BaseItem]>} stack
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawDrawStack(stack, parameters, directionProp) {
|
||||
if (stack.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstItem = stack[0];
|
||||
const firstItemPos = firstItem[0];
|
||||
if (stack.length === 1) {
|
||||
firstItem[1].drawItemCenteredClipped(
|
||||
firstItemPos.x,
|
||||
firstItemPos.y,
|
||||
parameters,
|
||||
globalConfig.defaultItemDiameter
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const itemSize = globalConfig.itemSpacingOnBelts * globalConfig.tileSize;
|
||||
const inverseDirection = directionProp === "x" ? "y" : "x";
|
||||
|
||||
const dimensions = new Vector(itemSize, itemSize);
|
||||
dimensions[inverseDirection] *= stack.length;
|
||||
|
||||
const directionVector = firstItemPos.copy().sub(stack[1][0]);
|
||||
|
||||
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
|
||||
|
||||
const sprite = this.root.buffers.getForKey({
|
||||
key: "beltpaths",
|
||||
subKey: "stack-" + directionProp + "-" + dpi + "-" + stack.length + firstItem[1].serialize(),
|
||||
dpi,
|
||||
w: dimensions.x,
|
||||
h: dimensions.y,
|
||||
redrawMethod: this.drawShapesInARow.bind(this),
|
||||
additionalParams: {
|
||||
direction: inverseDirection,
|
||||
stack,
|
||||
root: this.root,
|
||||
zoomLevel: parameters.zoomLevel,
|
||||
},
|
||||
});
|
||||
|
||||
const anchor = directionVector[inverseDirection] < 0 ? firstItem : stack[stack.length - 1];
|
||||
|
||||
parameters.context.drawImage(
|
||||
sprite,
|
||||
anchor[0].x - itemSize / 2,
|
||||
anchor[0].y - itemSize / 2,
|
||||
dimensions.x,
|
||||
dimensions.y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export class Blueprint {
|
||||
const rect = staticComp.getTileSpaceBounds();
|
||||
rect.moveBy(tile.x, tile.y);
|
||||
|
||||
if (!parameters.root.logic.checkCanPlaceEntity(entity, tile)) {
|
||||
if (!parameters.root.logic.checkCanPlaceEntity(entity, { offset: tile })) {
|
||||
parameters.context.globalAlpha = 0.3;
|
||||
} else {
|
||||
parameters.context.globalAlpha = 1;
|
||||
@@ -131,7 +131,7 @@ export class Blueprint {
|
||||
|
||||
for (let i = 0; i < this.entities.length; ++i) {
|
||||
const entity = this.entities[i];
|
||||
if (root.logic.checkCanPlaceEntity(entity, tile)) {
|
||||
if (root.logic.checkCanPlaceEntity(entity, { offset: tile })) {
|
||||
anyPlaceable = true;
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export class Blueprint {
|
||||
let count = 0;
|
||||
for (let i = 0; i < this.entities.length; ++i) {
|
||||
const entity = this.entities[i];
|
||||
if (!root.logic.checkCanPlaceEntity(entity, tile)) {
|
||||
if (!root.logic.checkCanPlaceEntity(entity, { offset: tile })) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { AtlasSprite } from "../core/sprites";
|
||||
import { Vector } from "../core/vector";
|
||||
/* typehints:end */
|
||||
|
||||
import { gMetaBuildingRegistry } from "../core/global_registries";
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* metaClass: typeof MetaBuilding,
|
||||
@@ -19,7 +21,7 @@ import { Vector } from "../core/vector";
|
||||
|
||||
/**
|
||||
* Stores a lookup table for all building variants (for better performance)
|
||||
* @type {Object<number, BuildingVariantIdentifier>}
|
||||
* @type {Object<number|string, BuildingVariantIdentifier>}
|
||||
*/
|
||||
export const gBuildingVariants = {
|
||||
// Set later
|
||||
@@ -27,13 +29,13 @@ export const gBuildingVariants = {
|
||||
|
||||
/**
|
||||
* Mapping from 'metaBuildingId/variant/rotationVariant' to building code
|
||||
* @type {Map<string, number>}
|
||||
* @type {Map<string, number|string>}
|
||||
*/
|
||||
const variantsCache = new Map();
|
||||
|
||||
/**
|
||||
* Registers a new variant
|
||||
* @param {number} code
|
||||
* @param {number|string} code
|
||||
* @param {typeof MetaBuilding} meta
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
@@ -47,6 +49,7 @@ export function registerBuildingVariant(
|
||||
assert(!gBuildingVariants[code], "Duplicate id: " + code);
|
||||
gBuildingVariants[code] = {
|
||||
metaClass: meta,
|
||||
metaInstance: gMetaBuildingRegistry.findByClass(meta),
|
||||
variant,
|
||||
rotationVariant,
|
||||
// @ts-ignore
|
||||
@@ -54,9 +57,20 @@ export function registerBuildingVariant(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes the combination of buildng, variant and rotation variant
|
||||
* @param {string} buildingId
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
* @returns
|
||||
*/
|
||||
function generateBuildingHash(buildingId, variant, rotationVariant) {
|
||||
return buildingId + "/" + variant + "/" + rotationVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} code
|
||||
* @param {string|number} code
|
||||
* @returns {BuildingVariantIdentifier}
|
||||
*/
|
||||
export function getBuildingDataFromCode(code) {
|
||||
@@ -70,8 +84,8 @@ export function getBuildingDataFromCode(code) {
|
||||
export function buildBuildingCodeCache() {
|
||||
for (const code in gBuildingVariants) {
|
||||
const data = gBuildingVariants[code];
|
||||
const hash = data.metaInstance.getId() + "/" + data.variant + "/" + data.rotationVariant;
|
||||
variantsCache.set(hash, +code);
|
||||
const hash = generateBuildingHash(data.metaInstance.getId(), data.variant, data.rotationVariant);
|
||||
variantsCache.set(hash, isNaN(+code) ? code : +code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +94,10 @@ export function buildBuildingCodeCache() {
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
* @returns {number}
|
||||
* @returns {number|string}
|
||||
*/
|
||||
export function getCodeFromBuildingData(metaBuilding, variant, rotationVariant) {
|
||||
const hash = metaBuilding.getId() + "/" + variant + "/" + rotationVariant;
|
||||
const hash = generateBuildingHash(metaBuilding.getId(), variant, rotationVariant);
|
||||
const result = variantsCache.get(hash);
|
||||
if (G_IS_DEV) {
|
||||
if (!result) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -14,6 +14,15 @@ export class MetaAnalyzerBuilding extends MetaBuilding {
|
||||
super("analyzer");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 43,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#3a52bc";
|
||||
}
|
||||
|
||||
@@ -31,6 +31,31 @@ export class MetaBalancerBuilding extends MetaBuilding {
|
||||
super("balancer");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 4,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 5,
|
||||
variant: enumBalancerVariants.merger,
|
||||
},
|
||||
{
|
||||
internalId: 6,
|
||||
variant: enumBalancerVariants.mergerInverse,
|
||||
},
|
||||
{
|
||||
internalId: 47,
|
||||
variant: enumBalancerVariants.splitter,
|
||||
},
|
||||
{
|
||||
internalId: 48,
|
||||
variant: enumBalancerVariants.splitterInverse,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getDimensions(variant) {
|
||||
switch (variant) {
|
||||
case defaultBuildingVariant:
|
||||
@@ -154,11 +179,11 @@ export class MetaBalancerBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -179,15 +204,14 @@ export class MetaBalancerBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [
|
||||
direction:
|
||||
variant === enumBalancerVariants.mergerInverse
|
||||
? enumDirection.left
|
||||
: enumDirection.right,
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -206,7 +230,7 @@ export class MetaBalancerBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SOUNDS } from "../../platform/sound";
|
||||
import { T } from "../../translations";
|
||||
import { BeltComponent } from "../components/belt";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { THEME } from "../theme";
|
||||
|
||||
@@ -22,6 +22,26 @@ export class MetaBeltBuilding extends MetaBuilding {
|
||||
super("belt");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 1,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 0,
|
||||
},
|
||||
{
|
||||
internalId: 2,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 1,
|
||||
},
|
||||
{
|
||||
internalId: 3,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 2,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return THEME.map.chunkOverview.beltColor;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,22 @@
|
||||
import { Entity } from "../entity";
|
||||
/* typehints:end */
|
||||
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
|
||||
export class MetaBlockBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("block");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 64,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#333";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -11,6 +11,15 @@ export class MetaComparatorBuilding extends MetaBuilding {
|
||||
super("comparator");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 46,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#823cab";
|
||||
}
|
||||
|
||||
@@ -6,13 +6,22 @@ import { enumDirection, Vector } from "../../core/vector";
|
||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { ItemProducerComponent } from "../components/item_producer";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
|
||||
export class MetaConstantProducerBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("constant_producer");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 62,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#bfd630";
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { generateMatrixRotations } from "../../core/utils";
|
||||
@@ -14,6 +14,15 @@ export class MetaConstantSignalBuilding extends MetaBuilding {
|
||||
super("constant_signal");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 31,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#2b84fd";
|
||||
}
|
||||
|
||||
@@ -17,6 +17,19 @@ export class MetaCutterBuilding extends MetaBuilding {
|
||||
super("cutter");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 9,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 10,
|
||||
variant: enumCutterVariants.quad,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#7dcda2";
|
||||
}
|
||||
@@ -83,7 +96,7 @@ export class MetaCutterBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { DisplayComponent } from "../components/display";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
@@ -11,6 +11,15 @@ export class MetaDisplayBuilding extends MetaBuilding {
|
||||
super("display");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 40,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#aaaaaa";
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -15,6 +15,15 @@ export class MetaFilterBuilding extends MetaBuilding {
|
||||
super("filter");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 37,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#c45c2e";
|
||||
}
|
||||
@@ -69,7 +78,7 @@ export class MetaFilterBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -6,13 +6,22 @@ import { enumDirection, Vector } from "../../core/vector";
|
||||
import { GoalAcceptorComponent } from "../components/goal_acceptor";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
|
||||
export class MetaGoalAcceptorBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("goal_acceptor");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 63,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#ce418a";
|
||||
}
|
||||
@@ -36,7 +45,7 @@ export class MetaGoalAcceptorBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@ import { HubComponent } from "../components/hub";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
|
||||
|
||||
export class MetaHubBuilding extends MetaBuilding {
|
||||
@@ -11,6 +11,15 @@ export class MetaHubBuilding extends MetaBuilding {
|
||||
super("hub");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 26,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
return new Vector(4, 4);
|
||||
}
|
||||
@@ -61,80 +70,22 @@ export class MetaHubBuilding extends MetaBuilding {
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {Array<import("../components/item_acceptor").ItemAcceptorSlotConfig>}
|
||||
*/
|
||||
const slots = [];
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
slots.push(
|
||||
{ pos: new Vector(i, 0), direction: enumDirection.top, filter: "shape" },
|
||||
{ pos: new Vector(i, 3), direction: enumDirection.bottom, filter: "shape" },
|
||||
{ pos: new Vector(0, i), direction: enumDirection.left, filter: "shape" },
|
||||
{ pos: new Vector(3, i), direction: enumDirection.right, filter: "shape" }
|
||||
);
|
||||
}
|
||||
|
||||
entity.addComponent(
|
||||
new ItemAcceptorComponent({
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.top, enumDirection.left],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 0),
|
||||
directions: [enumDirection.top],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 0),
|
||||
directions: [enumDirection.top, enumDirection.right],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 3),
|
||||
directions: [enumDirection.bottom, enumDirection.left],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 3),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 3),
|
||||
directions: [enumDirection.bottom],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 3),
|
||||
directions: [enumDirection.bottom, enumDirection.right],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 1),
|
||||
directions: [enumDirection.left],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 2),
|
||||
directions: [enumDirection.left],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 3),
|
||||
directions: [enumDirection.left],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 1),
|
||||
directions: [enumDirection.right],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 2),
|
||||
directions: [enumDirection.right],
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 3),
|
||||
directions: [enumDirection.right],
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
slots,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,22 @@ import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { ItemProducerComponent } from "../components/item_producer";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
|
||||
export class MetaItemProducerBuilding extends MetaBuilding {
|
||||
constructor() {
|
||||
super("item_producer");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 61,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#b37dcd";
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { enumDirection, Vector } from "../../core/vector";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { LeverComponent } from "../components/lever";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
@@ -11,6 +11,15 @@ export class MetaLeverBuilding extends MetaBuilding {
|
||||
super("lever");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 33,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
// @todo: Render differently based on if its activated or not
|
||||
return "#1a678b";
|
||||
|
||||
@@ -15,7 +15,7 @@ export const enumLogicGateVariants = {
|
||||
};
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumVariantToGate = {
|
||||
const enumVariantToGate = {
|
||||
[defaultBuildingVariant]: enumLogicGateType.and,
|
||||
[enumLogicGateVariants.not]: enumLogicGateType.not,
|
||||
[enumLogicGateVariants.xor]: enumLogicGateType.xor,
|
||||
@@ -41,6 +41,27 @@ export class MetaLogicGateBuilding extends MetaBuilding {
|
||||
super("logic_gate");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 32,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 34,
|
||||
variant: enumLogicGateVariants.not,
|
||||
},
|
||||
{
|
||||
internalId: 35,
|
||||
variant: enumLogicGateVariants.xor,
|
||||
},
|
||||
{
|
||||
internalId: 36,
|
||||
variant: enumLogicGateVariants.or,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor(variant) {
|
||||
return colors[variant];
|
||||
}
|
||||
|
||||
@@ -21,6 +21,19 @@ export class MetaMinerBuilding extends MetaBuilding {
|
||||
super("miner");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 7,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 8,
|
||||
variant: enumMinerVariants.chainable,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#b37dcd";
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -14,6 +14,15 @@ export class MetaMixerBuilding extends MetaBuilding {
|
||||
super("mixer");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 15,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getDimensions() {
|
||||
return new Vector(2, 1);
|
||||
}
|
||||
@@ -64,12 +73,12 @@ export class MetaMixerBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "color",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -22,6 +22,27 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
super("painter");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 16,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 17,
|
||||
variant: enumPainterVariants.mirrored,
|
||||
},
|
||||
{
|
||||
internalId: 18,
|
||||
variant: enumPainterVariants.double,
|
||||
},
|
||||
{
|
||||
internalId: 19,
|
||||
variant: enumPainterVariants.quad,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getDimensions(variant) {
|
||||
switch (variant) {
|
||||
case defaultBuildingVariant:
|
||||
@@ -107,12 +128,12 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
direction: enumDirection.left,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.top],
|
||||
direction: enumDirection.top,
|
||||
filter: "color",
|
||||
},
|
||||
],
|
||||
@@ -139,14 +160,13 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
direction: enumDirection.left,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [
|
||||
direction:
|
||||
variant === defaultBuildingVariant ? enumDirection.top : enumDirection.bottom,
|
||||
],
|
||||
filter: "color",
|
||||
},
|
||||
]);
|
||||
@@ -172,17 +192,17 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
direction: enumDirection.left,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 1),
|
||||
directions: [enumDirection.left],
|
||||
direction: enumDirection.left,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.top],
|
||||
direction: enumDirection.top,
|
||||
filter: "color",
|
||||
},
|
||||
]);
|
||||
@@ -230,27 +250,27 @@ export class MetaPainterBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.left],
|
||||
direction: enumDirection.left,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(2, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "color",
|
||||
},
|
||||
{
|
||||
pos: new Vector(3, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "color",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { BeltUnderlaysComponent } from "../components/belt_underlays";
|
||||
import { BeltReaderComponent } from "../components/belt_reader";
|
||||
@@ -18,6 +18,15 @@ export class MetaReaderBuilding extends MetaBuilding {
|
||||
super("reader");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 49,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#25fff2";
|
||||
}
|
||||
@@ -75,7 +84,7 @@ export class MetaReaderBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -23,6 +23,23 @@ export class MetaRotaterBuilding extends MetaBuilding {
|
||||
super("rotater");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 11,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 12,
|
||||
variant: enumRotaterVariants.ccw,
|
||||
},
|
||||
{
|
||||
internalId: 13,
|
||||
variant: enumRotaterVariants.rotate180,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#7dc6cd";
|
||||
}
|
||||
@@ -111,7 +128,7 @@ export class MetaRotaterBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -14,6 +14,15 @@ export class MetaStackerBuilding extends MetaBuilding {
|
||||
super("stacker");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 14,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#9fcd7d";
|
||||
}
|
||||
@@ -64,12 +73,12 @@ export class MetaStackerBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "shape",
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
filter: "shape",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { StorageComponent } from "../components/storage";
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -17,6 +17,15 @@ export class MetaStorageBuilding extends MetaBuilding {
|
||||
super("storage");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 21,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#bbdf6d";
|
||||
}
|
||||
@@ -65,11 +74,11 @@ export class MetaStorageBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 1),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
{
|
||||
pos: new Vector(1, 1),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -22,6 +22,19 @@ export class MetaTransistorBuilding extends MetaBuilding {
|
||||
super("transistor");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 38,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 60,
|
||||
variant: enumTransistorVariants.mirrored,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#bc3a61";
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -15,6 +15,15 @@ export class MetaTrashBuilding extends MetaBuilding {
|
||||
super("trash");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 20,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getIsRotateable() {
|
||||
return false;
|
||||
}
|
||||
@@ -67,12 +76,19 @@ export class MetaTrashBuilding extends MetaBuilding {
|
||||
slots: [
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [
|
||||
enumDirection.top,
|
||||
enumDirection.right,
|
||||
enumDirection.bottom,
|
||||
enumDirection.left,
|
||||
],
|
||||
direction: enumDirection.top,
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.right,
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
direction: enumDirection.left,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -40,6 +40,31 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
super("underground_belt");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 22,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 0,
|
||||
},
|
||||
{
|
||||
internalId: 23,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 1,
|
||||
},
|
||||
{
|
||||
internalId: 24,
|
||||
variant: enumUndergroundBeltVariants.tier2,
|
||||
rotationVariant: 0,
|
||||
},
|
||||
{
|
||||
internalId: 25,
|
||||
variant: enumUndergroundBeltVariants.tier2,
|
||||
rotationVariant: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor(variant, rotationVariant) {
|
||||
return colorsByRotationVariant[rotationVariant];
|
||||
}
|
||||
@@ -252,7 +277,7 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding {
|
||||
entity.components.ItemAcceptor.setSlots([
|
||||
{
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
},
|
||||
]);
|
||||
return;
|
||||
|
||||
@@ -19,7 +19,7 @@ export const enumVirtualProcessorVariants = {
|
||||
};
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumVariantToGate = {
|
||||
const enumVariantToGate = {
|
||||
[defaultBuildingVariant]: enumLogicGateType.cutter,
|
||||
[enumVirtualProcessorVariants.rotater]: enumLogicGateType.rotater,
|
||||
[enumVirtualProcessorVariants.unstacker]: enumLogicGateType.unstacker,
|
||||
@@ -40,6 +40,31 @@ export class MetaVirtualProcessorBuilding extends MetaBuilding {
|
||||
super("virtual_processor");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 42,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
{
|
||||
internalId: 44,
|
||||
variant: enumVirtualProcessorVariants.rotater,
|
||||
},
|
||||
{
|
||||
internalId: 45,
|
||||
variant: enumVirtualProcessorVariants.unstacker,
|
||||
},
|
||||
{
|
||||
internalId: 50,
|
||||
variant: enumVirtualProcessorVariants.stacker,
|
||||
},
|
||||
{
|
||||
internalId: 51,
|
||||
variant: enumVirtualProcessorVariants.painter,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor(variant) {
|
||||
return colors[variant];
|
||||
}
|
||||
|
||||
@@ -37,6 +37,51 @@ export class MetaWireBuilding extends MetaBuilding {
|
||||
super("wire");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 27,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 0,
|
||||
},
|
||||
{
|
||||
internalId: 28,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 1,
|
||||
},
|
||||
{
|
||||
internalId: 29,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 2,
|
||||
},
|
||||
{
|
||||
internalId: 30,
|
||||
variant: defaultBuildingVariant,
|
||||
rotationVariant: 3,
|
||||
},
|
||||
{
|
||||
internalId: 52,
|
||||
variant: enumWireVariant.second,
|
||||
rotationVariant: 0,
|
||||
},
|
||||
{
|
||||
internalId: 53,
|
||||
variant: enumWireVariant.second,
|
||||
rotationVariant: 1,
|
||||
},
|
||||
{
|
||||
internalId: 54,
|
||||
variant: enumWireVariant.second,
|
||||
rotationVariant: 2,
|
||||
},
|
||||
{
|
||||
internalId: 55,
|
||||
variant: enumWireVariant.second,
|
||||
rotationVariant: 3,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getHasDirectionLockAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { generateMatrixRotations } from "../../core/utils";
|
||||
import { Vector } from "../../core/vector";
|
||||
import { WireTunnelComponent } from "../components/wire_tunnel";
|
||||
import { Entity } from "../entity";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
@@ -13,6 +13,15 @@ export class MetaWireTunnelBuilding extends MetaBuilding {
|
||||
super("wire_tunnel");
|
||||
}
|
||||
|
||||
static getAllVariantCombinations() {
|
||||
return [
|
||||
{
|
||||
internalId: 39,
|
||||
variant: defaultBuildingVariant,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getSilhouetteColor() {
|
||||
return "#777a86";
|
||||
}
|
||||
|
||||
@@ -22,31 +22,34 @@ import { ItemProducerComponent } from "./components/item_producer";
|
||||
import { GoalAcceptorComponent } from "./components/goal_acceptor";
|
||||
|
||||
export function initComponentRegistry() {
|
||||
gComponentRegistry.register(StaticMapEntityComponent);
|
||||
gComponentRegistry.register(BeltComponent);
|
||||
gComponentRegistry.register(ItemEjectorComponent);
|
||||
gComponentRegistry.register(ItemAcceptorComponent);
|
||||
gComponentRegistry.register(MinerComponent);
|
||||
gComponentRegistry.register(ItemProcessorComponent);
|
||||
gComponentRegistry.register(UndergroundBeltComponent);
|
||||
gComponentRegistry.register(HubComponent);
|
||||
gComponentRegistry.register(StorageComponent);
|
||||
gComponentRegistry.register(WiredPinsComponent);
|
||||
gComponentRegistry.register(BeltUnderlaysComponent);
|
||||
gComponentRegistry.register(WireComponent);
|
||||
gComponentRegistry.register(ConstantSignalComponent);
|
||||
gComponentRegistry.register(LogicGateComponent);
|
||||
gComponentRegistry.register(LeverComponent);
|
||||
gComponentRegistry.register(WireTunnelComponent);
|
||||
gComponentRegistry.register(DisplayComponent);
|
||||
gComponentRegistry.register(BeltReaderComponent);
|
||||
gComponentRegistry.register(FilterComponent);
|
||||
gComponentRegistry.register(ItemProducerComponent);
|
||||
gComponentRegistry.register(GoalAcceptorComponent);
|
||||
const components = [
|
||||
StaticMapEntityComponent,
|
||||
BeltComponent,
|
||||
ItemEjectorComponent,
|
||||
ItemAcceptorComponent,
|
||||
MinerComponent,
|
||||
ItemProcessorComponent,
|
||||
UndergroundBeltComponent,
|
||||
HubComponent,
|
||||
StorageComponent,
|
||||
WiredPinsComponent,
|
||||
BeltUnderlaysComponent,
|
||||
WireComponent,
|
||||
ConstantSignalComponent,
|
||||
LogicGateComponent,
|
||||
LeverComponent,
|
||||
WireTunnelComponent,
|
||||
DisplayComponent,
|
||||
BeltReaderComponent,
|
||||
FilterComponent,
|
||||
ItemProducerComponent,
|
||||
GoalAcceptorComponent,
|
||||
];
|
||||
components.forEach(component => gComponentRegistry.register(component));
|
||||
|
||||
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
||||
|
||||
// Sanity check - If this is thrown, you (=me, lol) forgot to add a new component here
|
||||
// Sanity check - If this is thrown, you forgot to add a new component here
|
||||
|
||||
assert(
|
||||
// @ts-ignore
|
||||
|
||||
@@ -8,7 +8,7 @@ export const curvedBeltLength = /* Math.PI / 4 */ 0.78;
|
||||
/** @type {import("./item_acceptor").ItemAcceptorSlot} */
|
||||
export const FAKE_BELT_ACCEPTOR_SLOT = {
|
||||
pos: new Vector(0, 0),
|
||||
directions: [enumDirection.bottom],
|
||||
direction: enumDirection.bottom,
|
||||
};
|
||||
|
||||
/** @type {Object<enumDirection, import("./item_ejector").ItemEjectorSlot>} */
|
||||
|
||||
@@ -3,9 +3,10 @@ import { types } from "../../savegame/serialization";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { Component } from "../component";
|
||||
|
||||
/** @typedef {{
|
||||
/**
|
||||
* @typedef {{
|
||||
* pos: Vector,
|
||||
* directions: enumDirection[],
|
||||
* direction: enumDirection,
|
||||
* filter?: ItemType
|
||||
* }} ItemAcceptorSlot */
|
||||
|
||||
@@ -14,12 +15,12 @@ import { Component } from "../component";
|
||||
* @typedef {{
|
||||
* slot: ItemAcceptorSlot,
|
||||
* index: number,
|
||||
* acceptedDirection: enumDirection
|
||||
* }} ItemAcceptorLocatedSlot */
|
||||
|
||||
/** @typedef {{
|
||||
/**
|
||||
* @typedef {{
|
||||
* pos: Vector,
|
||||
* directions: enumDirection[],
|
||||
* direction: enumDirection,
|
||||
* filter?: ItemType
|
||||
* }} ItemAcceptorSlotConfig */
|
||||
|
||||
@@ -64,7 +65,7 @@ export class ItemAcceptorComponent extends Component {
|
||||
const slot = slots[i];
|
||||
this.slots.push({
|
||||
pos: slot.pos,
|
||||
directions: slot.directions,
|
||||
direction: slot.direction,
|
||||
|
||||
// Which type of item to accept (shape | color | all) @see ItemType
|
||||
filter: slot.filter,
|
||||
@@ -122,15 +123,11 @@ export class ItemAcceptorComponent extends Component {
|
||||
}
|
||||
|
||||
// Check if the acceptor slot accepts items from our direction
|
||||
for (let i = 0; i < slot.directions.length; ++i) {
|
||||
// const localDirection = targetStaticComp.localDirectionToWorld(slot.directions[l]);
|
||||
if (desiredDirection === slot.directions[i]) {
|
||||
return {
|
||||
slot,
|
||||
index: slotIndex,
|
||||
acceptedDirection: desiredDirection,
|
||||
};
|
||||
}
|
||||
if (desiredDirection === slot.direction) {
|
||||
return {
|
||||
slot,
|
||||
index: slotIndex,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,11 @@ export class ItemProcessorComponent extends Component {
|
||||
* @type {number}
|
||||
*/
|
||||
this.bonusTime = 0;
|
||||
|
||||
/**
|
||||
* @type {Array<EjectorItemToEject>}
|
||||
*/
|
||||
this.queuedEjects = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ export class StaticMapEntityComponent extends Component {
|
||||
originalRotation: types.float,
|
||||
|
||||
// See building_codes.js
|
||||
code: types.uint,
|
||||
code: types.uintOrString,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ export class StaticMapEntityComponent extends Component {
|
||||
* @param {Vector=} param0.tileSize Size of the entity in tiles
|
||||
* @param {number=} param0.rotation Rotation in degrees. Must be multiple of 90
|
||||
* @param {number=} param0.originalRotation Original Rotation in degrees. Must be multiple of 90
|
||||
* @param {number=} param0.code Building code
|
||||
* @param {number|string=} param0.code Building code
|
||||
*/
|
||||
constructor({
|
||||
origin = new Vector(),
|
||||
|
||||
@@ -38,6 +38,7 @@ import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||
import { AchievementProxy } from "./achievement_proxy";
|
||||
import { SoundProxy } from "./sound_proxy";
|
||||
import { GameTime } from "./time/game_time";
|
||||
import { MOD_SIGNALS } from "../mods/mod_signals";
|
||||
|
||||
const logger = createLogger("ingame/core");
|
||||
|
||||
@@ -161,6 +162,7 @@ export class GameCore {
|
||||
}
|
||||
|
||||
logger.log("root initialized");
|
||||
MOD_SIGNALS.gameInitialized.dispatch(root);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -197,20 +197,18 @@ export class Entity extends BasicSerializableObject {
|
||||
for (let i = 0; i < acceptorComp.slots.length; ++i) {
|
||||
const slot = acceptorComp.slots[i];
|
||||
const slotTile = staticComp.localTileToWorld(slot.pos);
|
||||
for (let k = 0; k < slot.directions.length; ++k) {
|
||||
const direction = staticComp.localDirectionToWorld(slot.directions[k]);
|
||||
const directionVector = enumDirectionToVector[direction];
|
||||
const angle = Math.radians(enumDirectionToAngle[direction] + 180);
|
||||
context.globalAlpha = 0.4;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: acceptorSprite,
|
||||
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
||||
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
||||
angle,
|
||||
size: globalConfig.tileSize * 0.25,
|
||||
});
|
||||
}
|
||||
const direction = staticComp.localDirectionToWorld(slot.direction);
|
||||
const directionVector = enumDirectionToVector[direction];
|
||||
const angle = Math.radians(enumDirectionToAngle[direction] + 180);
|
||||
context.globalAlpha = 0.4;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite: acceptorSprite,
|
||||
x: (slotTile.x + 0.5 + directionVector.x * 0.37) * globalConfig.tileSize,
|
||||
y: (slotTile.y + 0.5 + directionVector.y * 0.37) * globalConfig.tileSize,
|
||||
angle,
|
||||
size: globalConfig.tileSize * 0.25,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* typehints:start */
|
||||
import { GameSystem } from "./game_system";
|
||||
import { GameRoot } from "./root";
|
||||
/* typehints:end */
|
||||
|
||||
@@ -30,6 +31,11 @@ import { ZoneSystem } from "./systems/zone";
|
||||
|
||||
const logger = createLogger("game_system_manager");
|
||||
|
||||
/**
|
||||
* @type {Object<string, Array<{ id: string; systemClass: new (any) => GameSystem}>>}
|
||||
*/
|
||||
export const MODS_ADDITIONAL_SYSTEMS = {};
|
||||
|
||||
export class GameSystemManager {
|
||||
/**
|
||||
*
|
||||
@@ -123,7 +129,15 @@ export class GameSystemManager {
|
||||
* Initializes all systems
|
||||
*/
|
||||
internalInitSystems() {
|
||||
const addBefore = id => {
|
||||
const systems = MODS_ADDITIONAL_SYSTEMS[id];
|
||||
if (systems) {
|
||||
systems.forEach(({ id, systemClass }) => add(id, systemClass));
|
||||
}
|
||||
};
|
||||
|
||||
const add = (id, systemClass) => {
|
||||
addBefore(id);
|
||||
this.systems[id] = new systemClass(this.root);
|
||||
this.systemUpdateOrder.push(id);
|
||||
};
|
||||
@@ -173,6 +187,7 @@ export class GameSystemManager {
|
||||
// IMPORTANT: We have 2 phases: In phase 1 we compute the output values of all gates,
|
||||
// processors etc. In phase 2 we propagate it through the wires network
|
||||
add("logicGate", LogicGateSystem);
|
||||
|
||||
add("beltReader", BeltReaderSystem);
|
||||
|
||||
add("display", DisplaySystem);
|
||||
@@ -187,6 +202,14 @@ export class GameSystemManager {
|
||||
add("zone", ZoneSystem);
|
||||
}
|
||||
|
||||
addBefore("end");
|
||||
|
||||
for (const key in MODS_ADDITIONAL_SYSTEMS) {
|
||||
if (!this.systems[key] && key !== "end") {
|
||||
logger.error("Mod system not attached due to invalid 'before': ", key);
|
||||
}
|
||||
}
|
||||
|
||||
logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import { GameRoot } from "./root";
|
||||
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
||||
import { enumHubGoalRewards } from "./tutorial_goals";
|
||||
|
||||
export const MOD_ITEM_PROCESSOR_SPEEDS = {};
|
||||
|
||||
export class HubGoals extends BasicSerializableObject {
|
||||
static getId() {
|
||||
return "HubGoals";
|
||||
@@ -433,7 +435,7 @@ export class HubGoals extends BasicSerializableObject {
|
||||
}
|
||||
|
||||
const randomColor = () => rng.choice(colors);
|
||||
const randomShape = () => rng.choice(Object.values(enumSubShape));
|
||||
const randomShape = () => rng.choice(availableShapes);
|
||||
|
||||
let anyIsMissingTwo = false;
|
||||
|
||||
@@ -556,6 +558,9 @@ export class HubGoals extends BasicSerializableObject {
|
||||
);
|
||||
}
|
||||
default:
|
||||
if (MOD_ITEM_PROCESSOR_SPEEDS[processorType]) {
|
||||
return MOD_ITEM_PROCESSOR_SPEEDS[processorType](this.root);
|
||||
}
|
||||
assertAlways(false, "invalid processor type: " + processorType);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { Signal } from "../../core/signal";
|
||||
import { MOD_SIGNALS } from "../../mods/mod_signals";
|
||||
import { KEYMAPPINGS } from "../key_action_mapper";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { GameRoot } from "../root";
|
||||
@@ -64,7 +65,7 @@ export class GameHUD {
|
||||
/* typehints:end */
|
||||
};
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.enableEntityInspector) {
|
||||
if (G_IS_DEV) {
|
||||
this.parts.entityDebugger = new HUDEntityDebugger(this.root);
|
||||
}
|
||||
|
||||
@@ -89,8 +90,11 @@ export class GameHUD {
|
||||
this.parts[partId] = new part(this.root);
|
||||
}
|
||||
|
||||
MOD_SIGNALS.hudInitializer.dispatch(this.root);
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
for (const key in this.parts) {
|
||||
MOD_SIGNALS.hudElementInitialized.dispatch(this.parts[key]);
|
||||
this.parts[key].createElements(frag);
|
||||
}
|
||||
|
||||
@@ -98,6 +102,7 @@ export class GameHUD {
|
||||
|
||||
for (const key in this.parts) {
|
||||
this.parts[key].initialize();
|
||||
MOD_SIGNALS.hudElementFinalized.dispatch(this.parts[key]);
|
||||
}
|
||||
|
||||
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.toggleHud).add(this.toggleUi, this);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
||||
import { globalWarn } from "../../../core/logging";
|
||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||
import { makeDiv, safeModulo } from "../../../core/utils";
|
||||
import { MetaBlockBuilding } from "../../buildings/block";
|
||||
@@ -101,7 +102,12 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
rawBinding = KEYMAPPINGS.buildings[metaBuilding.getId()];
|
||||
}
|
||||
|
||||
const binding = actionMapper.getBinding(rawBinding);
|
||||
if (rawBinding) {
|
||||
const binding = actionMapper.getBinding(rawBinding);
|
||||
binding.add(() => this.selectBuildingForPlacement(metaBuilding));
|
||||
} else {
|
||||
globalWarn("Building has no keybinding:", metaBuilding.getId());
|
||||
}
|
||||
|
||||
const itemContainer = makeDiv(
|
||||
this.primaryBuildings.includes(allBuildings[i]) ? rowPrimary : rowSecondary,
|
||||
@@ -110,7 +116,6 @@ export class HUDBaseToolbar extends BaseHUDPart {
|
||||
);
|
||||
itemContainer.setAttribute("data-icon", "building_icons/" + metaBuilding.getId() + ".png");
|
||||
itemContainer.setAttribute("data-id", metaBuilding.getId());
|
||||
binding.add(() => this.selectBuildingForPlacement(metaBuilding));
|
||||
|
||||
const icon = makeDiv(itemContainer, null, ["icon"]);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
this.currentInterpolatedCornerTile = new Vector();
|
||||
|
||||
this.lockIndicatorSprites = {};
|
||||
layers.forEach(layer => {
|
||||
[...layers, "error"].forEach(layer => {
|
||||
this.lockIndicatorSprites[layer] = this.makeLockIndicatorSprite(layer);
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
|
||||
/**
|
||||
* Makes the lock indicator sprite for the given layer
|
||||
* @param {Layer} layer
|
||||
* @param {string} layer
|
||||
*/
|
||||
makeLockIndicatorSprite(layer) {
|
||||
const dims = 48;
|
||||
@@ -126,12 +126,15 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
rawBinding = KEYMAPPINGS.buildings[metaBuilding.getId()];
|
||||
}
|
||||
|
||||
const binding = this.root.keyMapper.getBinding(rawBinding);
|
||||
|
||||
this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace(
|
||||
"<key>",
|
||||
"<code class='keybinding'>" + binding.getKeyCodeString() + "</code>"
|
||||
);
|
||||
if (rawBinding) {
|
||||
const binding = this.root.keyMapper.getBinding(rawBinding);
|
||||
this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace(
|
||||
"<key>",
|
||||
"<code class='keybinding'>" + binding.getKeyCodeString() + "</code>"
|
||||
);
|
||||
} else {
|
||||
this.buildingInfoElements.hotkey.innerHTML = "";
|
||||
}
|
||||
|
||||
this.buildingInfoElements.tutorialImage.setAttribute(
|
||||
"data-icon",
|
||||
@@ -355,7 +358,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
rotationVariant
|
||||
);
|
||||
|
||||
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
|
||||
const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity, {});
|
||||
|
||||
// Fade in / out
|
||||
parameters.context.lineWidth = 1;
|
||||
@@ -394,6 +397,46 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any entities in the way, returns true if there are
|
||||
* @param {Vector} from
|
||||
* @param {Vector} to
|
||||
* @param {Vector[]=} ignorePositions
|
||||
* @returns
|
||||
*/
|
||||
checkForObstales(from, to, ignorePositions = []) {
|
||||
assert(from.x === to.x || from.y === to.y, "Must be a straight line");
|
||||
|
||||
const prop = from.x === to.x ? "y" : "x";
|
||||
const current = from.copy();
|
||||
|
||||
const metaBuilding = this.currentMetaBuilding.get();
|
||||
this.fakeEntity.layer = metaBuilding.getLayer();
|
||||
const staticComp = this.fakeEntity.components.StaticMapEntity;
|
||||
staticComp.origin = current;
|
||||
staticComp.rotation = 0;
|
||||
metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get());
|
||||
staticComp.code = getCodeFromBuildingData(
|
||||
this.currentMetaBuilding.get(),
|
||||
this.currentVariant.get(),
|
||||
0
|
||||
);
|
||||
|
||||
const start = Math.min(from[prop], to[prop]);
|
||||
const end = Math.max(from[prop], to[prop]);
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
current[prop] = i;
|
||||
if (ignorePositions.some(p => p.distanceSquare(current) < 0.1)) {
|
||||
continue;
|
||||
}
|
||||
if (!this.root.logic.checkCanPlaceEntity(this.fakeEntity, { allowReplaceBuildings: false })) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
@@ -404,55 +447,76 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
return;
|
||||
}
|
||||
|
||||
const applyStyles = look => {
|
||||
parameters.context.fillStyle = THEME.map.directionLock[look].color;
|
||||
parameters.context.strokeStyle = THEME.map.directionLock[look].background;
|
||||
parameters.context.lineWidth = 10;
|
||||
};
|
||||
|
||||
if (!this.lastDragTile) {
|
||||
// Not dragging yet
|
||||
applyStyles(this.root.currentLayer);
|
||||
const mouseWorld = this.root.camera.screenToWorld(mousePosition);
|
||||
parameters.context.beginCircle(mouseWorld.x, mouseWorld.y, 4);
|
||||
parameters.context.fill();
|
||||
return;
|
||||
}
|
||||
|
||||
const mouseWorld = this.root.camera.screenToWorld(mousePosition);
|
||||
const mouseTile = mouseWorld.toTileSpace();
|
||||
parameters.context.fillStyle = THEME.map.directionLock[this.root.currentLayer].color;
|
||||
parameters.context.strokeStyle = THEME.map.directionLock[this.root.currentLayer].background;
|
||||
parameters.context.lineWidth = 10;
|
||||
const startLine = this.lastDragTile.toWorldSpaceCenterOfTile();
|
||||
const endLine = mouseTile.toWorldSpaceCenterOfTile();
|
||||
const midLine = this.currentDirectionLockCorner.toWorldSpaceCenterOfTile();
|
||||
const anyObstacle =
|
||||
this.checkForObstales(this.lastDragTile, this.currentDirectionLockCorner, [
|
||||
this.lastDragTile,
|
||||
mouseTile,
|
||||
]) ||
|
||||
this.checkForObstales(this.currentDirectionLockCorner, mouseTile, [this.lastDragTile, mouseTile]);
|
||||
|
||||
if (anyObstacle) {
|
||||
applyStyles("error");
|
||||
} else {
|
||||
applyStyles(this.root.currentLayer);
|
||||
}
|
||||
|
||||
parameters.context.beginCircle(mouseWorld.x, mouseWorld.y, 4);
|
||||
parameters.context.fill();
|
||||
|
||||
if (this.lastDragTile) {
|
||||
const startLine = this.lastDragTile.toWorldSpaceCenterOfTile();
|
||||
const endLine = mouseTile.toWorldSpaceCenterOfTile();
|
||||
const midLine = this.currentDirectionLockCorner.toWorldSpaceCenterOfTile();
|
||||
parameters.context.beginCircle(startLine.x, startLine.y, 8);
|
||||
parameters.context.fill();
|
||||
|
||||
parameters.context.beginCircle(startLine.x, startLine.y, 8);
|
||||
parameters.context.fill();
|
||||
parameters.context.beginPath();
|
||||
parameters.context.moveTo(startLine.x, startLine.y);
|
||||
parameters.context.lineTo(midLine.x, midLine.y);
|
||||
parameters.context.lineTo(endLine.x, endLine.y);
|
||||
parameters.context.stroke();
|
||||
|
||||
parameters.context.beginPath();
|
||||
parameters.context.moveTo(startLine.x, startLine.y);
|
||||
parameters.context.lineTo(midLine.x, midLine.y);
|
||||
parameters.context.lineTo(endLine.x, endLine.y);
|
||||
parameters.context.stroke();
|
||||
parameters.context.beginCircle(endLine.x, endLine.y, 5);
|
||||
parameters.context.fill();
|
||||
|
||||
parameters.context.beginCircle(endLine.x, endLine.y, 5);
|
||||
parameters.context.fill();
|
||||
// Draw arrow
|
||||
const arrowSprite = this.lockIndicatorSprites[anyObstacle ? "error" : this.root.currentLayer];
|
||||
const path = this.computeDirectionLockPath();
|
||||
for (let i = 0; i < path.length - 1; i += 1) {
|
||||
const { rotation, tile } = path[i];
|
||||
const worldPos = tile.toWorldSpaceCenterOfTile();
|
||||
const angle = Math.radians(rotation);
|
||||
|
||||
// Draw arrow
|
||||
const arrowSprite = this.lockIndicatorSprites[this.root.currentLayer];
|
||||
const path = this.computeDirectionLockPath();
|
||||
for (let i = 0; i < path.length - 1; i += 1) {
|
||||
const { rotation, tile } = path[i];
|
||||
const worldPos = tile.toWorldSpaceCenterOfTile();
|
||||
const angle = Math.radians(rotation);
|
||||
|
||||
parameters.context.translate(worldPos.x, worldPos.y);
|
||||
parameters.context.rotate(angle);
|
||||
parameters.context.drawImage(
|
||||
arrowSprite,
|
||||
-6,
|
||||
-globalConfig.halfTileSize -
|
||||
clamp((this.root.time.realtimeNow() * 1.5) % 1.0, 0, 1) * 1 * globalConfig.tileSize +
|
||||
globalConfig.halfTileSize -
|
||||
6,
|
||||
12,
|
||||
12
|
||||
);
|
||||
parameters.context.rotate(-angle);
|
||||
parameters.context.translate(-worldPos.x, -worldPos.y);
|
||||
}
|
||||
parameters.context.translate(worldPos.x, worldPos.y);
|
||||
parameters.context.rotate(angle);
|
||||
parameters.context.drawImage(
|
||||
arrowSprite,
|
||||
-6,
|
||||
-globalConfig.halfTileSize -
|
||||
clamp((this.root.time.realtimeNow() * 1.5) % 1.0, 0, 1) * 1 * globalConfig.tileSize +
|
||||
globalConfig.halfTileSize -
|
||||
6,
|
||||
12,
|
||||
12
|
||||
);
|
||||
parameters.context.rotate(-angle);
|
||||
parameters.context.translate(-worldPos.x, -worldPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +537,13 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
|
||||
const offsetShift = 10;
|
||||
|
||||
/**
|
||||
* @type {Array<import("../../components/item_acceptor").ItemAcceptorSlot>}
|
||||
*/
|
||||
let acceptorSlots = [];
|
||||
/**
|
||||
* @type {Array<import("../../components/item_ejector").ItemEjectorSlot>}
|
||||
*/
|
||||
let ejectorSlots = [];
|
||||
|
||||
if (ejectorComp) {
|
||||
@@ -491,71 +561,65 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
||||
acceptorSlots.push(fakeAcceptorSlot);
|
||||
}
|
||||
|
||||
for (let acceptorSlotIndex = 0; acceptorSlotIndex < acceptorSlots.length; ++acceptorSlotIndex) {
|
||||
const slot = acceptorSlots[acceptorSlotIndex];
|
||||
// Go over all slots
|
||||
for (let i = 0; i < acceptorSlots.length; ++i) {
|
||||
const slot = acceptorSlots[i];
|
||||
|
||||
const acceptorSlotWsTile = staticComp.localTileToWorld(slot.pos);
|
||||
const acceptorSlotWsPos = acceptorSlotWsTile.toWorldSpaceCenterOfTile();
|
||||
|
||||
// Go over all slots
|
||||
for (
|
||||
let acceptorDirectionIndex = 0;
|
||||
acceptorDirectionIndex < slot.directions.length;
|
||||
++acceptorDirectionIndex
|
||||
) {
|
||||
const direction = slot.directions[acceptorDirectionIndex];
|
||||
const worldDirection = staticComp.localDirectionToWorld(direction);
|
||||
const direction = slot.direction;
|
||||
const worldDirection = staticComp.localDirectionToWorld(direction);
|
||||
|
||||
// Figure out which tile ejects to this slot
|
||||
const sourceTile = acceptorSlotWsTile.add(enumDirectionToVector[worldDirection]);
|
||||
// Figure out which tile ejects to this slot
|
||||
const sourceTile = acceptorSlotWsTile.add(enumDirectionToVector[worldDirection]);
|
||||
|
||||
let isBlocked = false;
|
||||
let isConnected = false;
|
||||
let isBlocked = false;
|
||||
let isConnected = false;
|
||||
|
||||
// Find all entities which are on that tile
|
||||
const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
|
||||
// Find all entities which are on that tile
|
||||
const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
|
||||
|
||||
// Check for every entity:
|
||||
for (let i = 0; i < sourceEntities.length; ++i) {
|
||||
const sourceEntity = sourceEntities[i];
|
||||
const sourceEjector = sourceEntity.components.ItemEjector;
|
||||
const sourceBeltComp = sourceEntity.components.Belt;
|
||||
const sourceStaticComp = sourceEntity.components.StaticMapEntity;
|
||||
const ejectorAcceptLocalTile = sourceStaticComp.worldToLocalTile(acceptorSlotWsTile);
|
||||
// Check for every entity:
|
||||
for (let j = 0; j < sourceEntities.length; ++j) {
|
||||
const sourceEntity = sourceEntities[j];
|
||||
const sourceEjector = sourceEntity.components.ItemEjector;
|
||||
const sourceBeltComp = sourceEntity.components.Belt;
|
||||
const sourceStaticComp = sourceEntity.components.StaticMapEntity;
|
||||
const ejectorAcceptLocalTile = sourceStaticComp.worldToLocalTile(acceptorSlotWsTile);
|
||||
|
||||
// If this entity is on the same layer as the slot - if so, it can either be
|
||||
// connected, or it can not be connected and thus block the input
|
||||
if (sourceEjector && sourceEjector.anySlotEjectsToLocalTile(ejectorAcceptLocalTile)) {
|
||||
// This one is connected, all good
|
||||
isConnected = true;
|
||||
} else if (
|
||||
sourceBeltComp &&
|
||||
sourceStaticComp.localDirectionToWorld(sourceBeltComp.direction) ===
|
||||
enumInvertedDirections[worldDirection]
|
||||
) {
|
||||
// Belt connected
|
||||
isConnected = true;
|
||||
} else {
|
||||
// This one is blocked
|
||||
isBlocked = true;
|
||||
}
|
||||
// If this entity is on the same layer as the slot - if so, it can either be
|
||||
// connected, or it can not be connected and thus block the input
|
||||
if (sourceEjector && sourceEjector.anySlotEjectsToLocalTile(ejectorAcceptLocalTile)) {
|
||||
// This one is connected, all good
|
||||
isConnected = true;
|
||||
} else if (
|
||||
sourceBeltComp &&
|
||||
sourceStaticComp.localDirectionToWorld(sourceBeltComp.direction) ===
|
||||
enumInvertedDirections[worldDirection]
|
||||
) {
|
||||
// Belt connected
|
||||
isConnected = true;
|
||||
} else {
|
||||
// This one is blocked
|
||||
isBlocked = true;
|
||||
}
|
||||
|
||||
const alpha = isConnected || isBlocked ? 1.0 : 0.3;
|
||||
const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
|
||||
|
||||
parameters.context.globalAlpha = alpha;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite,
|
||||
x: acceptorSlotWsPos.x,
|
||||
y: acceptorSlotWsPos.y,
|
||||
angle: Math.radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
|
||||
size: 13,
|
||||
offsetY: offsetShift + 13,
|
||||
});
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
|
||||
const alpha = isConnected || isBlocked ? 1.0 : 0.3;
|
||||
const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
|
||||
|
||||
parameters.context.globalAlpha = alpha;
|
||||
drawRotatedSprite({
|
||||
parameters,
|
||||
sprite,
|
||||
x: acceptorSlotWsPos.x,
|
||||
y: acceptorSlotWsPos.y,
|
||||
angle: Math.radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
|
||||
size: 13,
|
||||
offsetY: offsetShift + 13,
|
||||
});
|
||||
parameters.context.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// Go over all slots
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import { THIRDPARTY_URLS } from "../../../core/config";
|
||||
import { DialogWithForm } from "../../../core/modal_dialog_elements";
|
||||
import { FormElementInput, FormElementItemChooser } from "../../../core/modal_dialog_forms";
|
||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||
import { fillInLinkIntoTranslation } from "../../../core/utils";
|
||||
import { Vector } from "../../../core/vector";
|
||||
import { T } from "../../../translations";
|
||||
import { BaseItem } from "../../base_item";
|
||||
import { enumMouseButton } from "../../camera";
|
||||
import { Entity } from "../../entity";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../../items/boolean_item";
|
||||
import { COLOR_ITEM_SINGLETONS } from "../../items/color_item";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import trim from "trim";
|
||||
import { enumColors } from "../../colors";
|
||||
import { ShapeDefinition } from "../../shape_definition";
|
||||
|
||||
export class HUDConstantSignalEdit extends BaseHUDPart {
|
||||
initialize() {
|
||||
@@ -23,7 +35,7 @@ export class HUDConstantSignalEdit extends BaseHUDPart {
|
||||
const constantComp = contents.components.ConstantSignal;
|
||||
if (constantComp) {
|
||||
if (button === enumMouseButton.left) {
|
||||
this.root.systemMgr.systems.constantSignal.editConstantSignal(contents, {
|
||||
this.editConstantSignal(contents, {
|
||||
deleteOnCancel: false,
|
||||
});
|
||||
return STOP_PROPAGATION;
|
||||
@@ -31,4 +43,171 @@ export class HUDConstantSignalEdit extends BaseHUDPart {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the entity to enter a valid signal code
|
||||
* @param {Entity} entity
|
||||
* @param {object} param0
|
||||
* @param {boolean=} param0.deleteOnCancel
|
||||
*/
|
||||
editConstantSignal(entity, { deleteOnCancel = true }) {
|
||||
if (!entity.components.ConstantSignal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, query, but also save the uid because it could get stale
|
||||
const uid = entity.uid;
|
||||
|
||||
const signal = entity.components.ConstantSignal.signal;
|
||||
const signalValueInput = new FormElementInput({
|
||||
id: "signalValue",
|
||||
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
|
||||
placeholder: "",
|
||||
defaultValue: signal ? signal.getAsCopyableKey() : "",
|
||||
validator: val => this.parseSignalCode(entity, val),
|
||||
});
|
||||
|
||||
const items = [...Object.values(COLOR_ITEM_SINGLETONS)];
|
||||
|
||||
if (entity.components.WiredPins) {
|
||||
items.unshift(BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON);
|
||||
items.push(
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(
|
||||
this.root.gameMode.getBlueprintShapeKey()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// producer which can produce virtually anything
|
||||
const shapes = ["CuCuCuCu", "RuRuRuRu", "WuWuWuWu", "SuSuSuSu"];
|
||||
items.unshift(
|
||||
...shapes.reverse().map(key => this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key))
|
||||
);
|
||||
}
|
||||
|
||||
if (this.root.gameMode.hasHub()) {
|
||||
items.push(
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
|
||||
this.root.hubGoals.currentGoal.definition
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.root.hud.parts.pinnedShapes) {
|
||||
items.push(
|
||||
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const itemInput = new FormElementItemChooser({
|
||||
id: "signalItem",
|
||||
label: null,
|
||||
items,
|
||||
});
|
||||
|
||||
const dialog = new DialogWithForm({
|
||||
app: this.root.app,
|
||||
title: T.dialogs.editConstantProducer.title,
|
||||
desc: T.dialogs.editSignal.descItems,
|
||||
formElements: [itemInput, signalValueInput],
|
||||
buttons: ["cancel:bad:escape", "ok:good:enter"],
|
||||
closeButton: false,
|
||||
});
|
||||
this.root.hud.parts.dialogs.internalShowDialog(dialog);
|
||||
|
||||
// When confirmed, set the signal
|
||||
const closeHandler = () => {
|
||||
if (!this.root || !this.root.entityMgr) {
|
||||
// Game got stopped
|
||||
return;
|
||||
}
|
||||
|
||||
const entityRef = this.root.entityMgr.findByUid(uid, false);
|
||||
if (!entityRef) {
|
||||
// outdated
|
||||
return;
|
||||
}
|
||||
|
||||
const constantComp = entityRef.components.ConstantSignal;
|
||||
if (!constantComp) {
|
||||
// no longer interesting
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemInput.chosenItem) {
|
||||
constantComp.signal = itemInput.chosenItem;
|
||||
} else {
|
||||
constantComp.signal = this.parseSignalCode(entity, signalValueInput.getValue());
|
||||
}
|
||||
};
|
||||
|
||||
dialog.buttonSignals.ok.add(() => {
|
||||
closeHandler();
|
||||
});
|
||||
dialog.valueChosen.add(() => {
|
||||
dialog.closeRequested.dispatch();
|
||||
closeHandler();
|
||||
});
|
||||
|
||||
// When cancelled, destroy the entity again
|
||||
if (deleteOnCancel) {
|
||||
dialog.buttonSignals.cancel.add(() => {
|
||||
if (!this.root || !this.root.entityMgr) {
|
||||
// Game got stopped
|
||||
return;
|
||||
}
|
||||
|
||||
const entityRef = this.root.entityMgr.findByUid(uid, false);
|
||||
if (!entityRef) {
|
||||
// outdated
|
||||
return;
|
||||
}
|
||||
|
||||
const constantComp = entityRef.components.ConstantSignal;
|
||||
if (!constantComp) {
|
||||
// no longer interesting
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.logic.tryDeleteBuilding(entityRef);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse a signal code
|
||||
* @param {Entity} entity
|
||||
* @param {string} code
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
parseSignalCode(entity, code) {
|
||||
if (!this.root || !this.root.shapeDefinitionMgr) {
|
||||
// Stale reference
|
||||
return null;
|
||||
}
|
||||
|
||||
code = trim(code);
|
||||
const codeLower = code.toLowerCase();
|
||||
|
||||
if (enumColors[codeLower]) {
|
||||
return COLOR_ITEM_SINGLETONS[codeLower];
|
||||
}
|
||||
|
||||
if (entity.components.WiredPins) {
|
||||
if (code === "1" || codeLower === "true") {
|
||||
return BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
if (code === "0" || codeLower === "false") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
}
|
||||
|
||||
if (ShapeDefinition.isValidShortKey(code)) {
|
||||
return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +94,12 @@ export class HUDEntityDebugger extends BaseHUDPart {
|
||||
<div>`;
|
||||
|
||||
for (const property in val) {
|
||||
const isRoot = val[property] == this.root;
|
||||
const isRecursive = recursion.includes(val[property]);
|
||||
|
||||
let hiddenValue = isRoot ? "<root>" : null;
|
||||
if (isRecursive) {
|
||||
let hiddenValue = null;
|
||||
if (val[property] == this.root) {
|
||||
hiddenValue = "<root>";
|
||||
} else if (val[property] instanceof Node) {
|
||||
hiddenValue = `<${val[property].constructor.name}>`;
|
||||
} else if (recursion.includes(val[property])) {
|
||||
// Avoid recursion by not "expanding" object more than once
|
||||
hiddenValue = "<recursion>";
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ export class HUDModalDialogs extends BaseHUDPart {
|
||||
|
||||
internalShowDialog(dialog) {
|
||||
const elem = dialog.createElement();
|
||||
dialog.setIndex(this.dialogStack.length);
|
||||
dialog.setIndex(1000 + this.dialogStack.length);
|
||||
|
||||
// Hide last dialog in queue
|
||||
if (this.dialogStack.length > 0) {
|
||||
|
||||
@@ -7,6 +7,9 @@ export const enumNotificationType = {
|
||||
saved: "saved",
|
||||
upgrade: "upgrade",
|
||||
success: "success",
|
||||
info: "info",
|
||||
warning: "warning",
|
||||
error: "error",
|
||||
};
|
||||
|
||||
const notificationDuration = 3;
|
||||
@@ -17,14 +20,14 @@ export class HUDNotifications extends BaseHUDPart {
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.root.hud.signals.notification.add(this.onNotification, this);
|
||||
this.root.hud.signals.notification.add(this.internalShowNotification, this);
|
||||
|
||||
/** @type {Array<{ element: HTMLElement, expireAt: number}>} */
|
||||
this.notificationElements = [];
|
||||
|
||||
// Automatic notifications
|
||||
this.root.signals.gameSaved.add(() =>
|
||||
this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
|
||||
this.internalShowNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,7 +35,7 @@ export class HUDNotifications extends BaseHUDPart {
|
||||
* @param {string} message
|
||||
* @param {enumNotificationType} type
|
||||
*/
|
||||
onNotification(message, type) {
|
||||
internalShowNotification(message, type) {
|
||||
const element = makeDiv(this.element, null, ["notification", "type-" + type], message);
|
||||
element.setAttribute("data-icon", "icons/notification_" + type + ".png");
|
||||
|
||||
|
||||
@@ -7,118 +7,152 @@ import { Application } from "../application";
|
||||
import { Signal, STOP_PROPAGATION } from "../core/signal";
|
||||
import { IS_MOBILE } from "../core/config";
|
||||
import { T } from "../translations";
|
||||
function key(str) {
|
||||
|
||||
export function keyToKeyCode(str) {
|
||||
return str.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
|
||||
const KEYCODE_UP_ARROW = 38;
|
||||
const KEYCODE_DOWN_ARROW = 40;
|
||||
const KEYCODE_LEFT_ARROW = 37;
|
||||
const KEYCODE_RIGHT_ARROW = 39;
|
||||
export const KEYCODES = {
|
||||
Tab: 9,
|
||||
Enter: 13,
|
||||
|
||||
Shift: 16,
|
||||
Ctrl: 17,
|
||||
Alt: 18,
|
||||
|
||||
Escape: 27,
|
||||
|
||||
Space: 32,
|
||||
|
||||
ArrowLeft: 37,
|
||||
ArrowUp: 38,
|
||||
ArrowRight: 39,
|
||||
ArrowDown: 40,
|
||||
|
||||
Delete: 46,
|
||||
|
||||
F1: 112,
|
||||
F2: 113,
|
||||
F3: 114,
|
||||
F4: 115,
|
||||
F5: 116,
|
||||
F6: 117,
|
||||
F7: 118,
|
||||
F8: 119,
|
||||
F9: 120,
|
||||
F10: 121,
|
||||
F11: 122,
|
||||
F12: 123,
|
||||
|
||||
Plus: 187,
|
||||
Minus: 189,
|
||||
};
|
||||
|
||||
export const KEYMAPPINGS = {
|
||||
// Make sure mods come first so they can override everything
|
||||
mods: {},
|
||||
|
||||
general: {
|
||||
confirm: { keyCode: 13 }, // enter
|
||||
back: { keyCode: 27, builtin: true }, // escape
|
||||
confirm: { keyCode: KEYCODES.Enter },
|
||||
back: { keyCode: KEYCODES.Escape, builtin: true },
|
||||
},
|
||||
|
||||
ingame: {
|
||||
menuOpenShop: { keyCode: key("F") },
|
||||
menuOpenStats: { keyCode: key("G") },
|
||||
menuClose: { keyCode: key("Q") },
|
||||
menuOpenShop: { keyCode: keyToKeyCode("F") },
|
||||
menuOpenStats: { keyCode: keyToKeyCode("G") },
|
||||
menuClose: { keyCode: keyToKeyCode("Q") },
|
||||
|
||||
toggleHud: { keyCode: 113 }, // F2
|
||||
exportScreenshot: { keyCode: 114 }, // F3PS
|
||||
toggleFPSInfo: { keyCode: 115 }, // F4
|
||||
toggleHud: { keyCode: KEYCODES.F2 },
|
||||
exportScreenshot: { keyCode: KEYCODES.F3 },
|
||||
toggleFPSInfo: { keyCode: KEYCODES.F4 },
|
||||
|
||||
switchLayers: { keyCode: key("E") },
|
||||
switchLayers: { keyCode: keyToKeyCode("E") },
|
||||
|
||||
showShapeTooltip: { keyCode: 18 }, // ALT
|
||||
showShapeTooltip: { keyCode: KEYCODES.Alt },
|
||||
},
|
||||
|
||||
navigation: {
|
||||
mapMoveUp: { keyCode: key("W") },
|
||||
mapMoveRight: { keyCode: key("D") },
|
||||
mapMoveDown: { keyCode: key("S") },
|
||||
mapMoveLeft: { keyCode: key("A") },
|
||||
mapMoveFaster: { keyCode: 16 }, //shift
|
||||
mapMoveUp: { keyCode: keyToKeyCode("W") },
|
||||
mapMoveRight: { keyCode: keyToKeyCode("D") },
|
||||
mapMoveDown: { keyCode: keyToKeyCode("S") },
|
||||
mapMoveLeft: { keyCode: keyToKeyCode("A") },
|
||||
mapMoveFaster: { keyCode: KEYCODES.Shift },
|
||||
|
||||
centerMap: { keyCode: 32 }, // SPACE
|
||||
mapZoomIn: { keyCode: 187, repeated: true }, // "+"
|
||||
mapZoomOut: { keyCode: 189, repeated: true }, // "-"
|
||||
|
||||
createMarker: { keyCode: key("M") },
|
||||
centerMap: { keyCode: KEYCODES.Space },
|
||||
mapZoomIn: { keyCode: KEYCODES.Plus, repeated: true },
|
||||
mapZoomOut: { keyCode: KEYCODES.Minus, repeated: true },
|
||||
createMarker: { keyCode: keyToKeyCode("M") },
|
||||
},
|
||||
|
||||
buildings: {
|
||||
// Puzzle buildings
|
||||
constant_producer: { keyCode: key("H") },
|
||||
goal_acceptor: { keyCode: key("N") },
|
||||
block: { keyCode: key("4") },
|
||||
constant_producer: { keyCode: keyToKeyCode("H") },
|
||||
goal_acceptor: { keyCode: keyToKeyCode("N") },
|
||||
block: { keyCode: keyToKeyCode("4") },
|
||||
|
||||
// Primary Toolbar
|
||||
belt: { keyCode: key("1") },
|
||||
balancer: { keyCode: key("2") },
|
||||
underground_belt: { keyCode: key("3") },
|
||||
miner: { keyCode: key("4") },
|
||||
cutter: { keyCode: key("5") },
|
||||
rotater: { keyCode: key("6") },
|
||||
stacker: { keyCode: key("7") },
|
||||
mixer: { keyCode: key("8") },
|
||||
painter: { keyCode: key("9") },
|
||||
trash: { keyCode: key("0") },
|
||||
belt: { keyCode: keyToKeyCode("1") },
|
||||
balancer: { keyCode: keyToKeyCode("2") },
|
||||
underground_belt: { keyCode: keyToKeyCode("3") },
|
||||
miner: { keyCode: keyToKeyCode("4") },
|
||||
cutter: { keyCode: keyToKeyCode("5") },
|
||||
rotater: { keyCode: keyToKeyCode("6") },
|
||||
stacker: { keyCode: keyToKeyCode("7") },
|
||||
mixer: { keyCode: keyToKeyCode("8") },
|
||||
painter: { keyCode: keyToKeyCode("9") },
|
||||
trash: { keyCode: keyToKeyCode("0") },
|
||||
|
||||
// Sandbox
|
||||
item_producer: { keyCode: key("L") },
|
||||
item_producer: { keyCode: keyToKeyCode("L") },
|
||||
|
||||
// Secondary toolbar
|
||||
storage: { keyCode: key("Y") },
|
||||
reader: { keyCode: key("U") },
|
||||
lever: { keyCode: key("I") },
|
||||
filter: { keyCode: key("O") },
|
||||
display: { keyCode: key("P") },
|
||||
storage: { keyCode: keyToKeyCode("Y") },
|
||||
reader: { keyCode: keyToKeyCode("U") },
|
||||
lever: { keyCode: keyToKeyCode("I") },
|
||||
filter: { keyCode: keyToKeyCode("O") },
|
||||
display: { keyCode: keyToKeyCode("P") },
|
||||
|
||||
// Wires toolbar
|
||||
wire: { keyCode: key("1") },
|
||||
wire_tunnel: { keyCode: key("2") },
|
||||
constant_signal: { keyCode: key("3") },
|
||||
logic_gate: { keyCode: key("4") },
|
||||
virtual_processor: { keyCode: key("5") },
|
||||
analyzer: { keyCode: key("6") },
|
||||
comparator: { keyCode: key("7") },
|
||||
transistor: { keyCode: key("8") },
|
||||
wire: { keyCode: keyToKeyCode("1") },
|
||||
wire_tunnel: { keyCode: keyToKeyCode("2") },
|
||||
constant_signal: { keyCode: keyToKeyCode("3") },
|
||||
logic_gate: { keyCode: keyToKeyCode("4") },
|
||||
virtual_processor: { keyCode: keyToKeyCode("5") },
|
||||
analyzer: { keyCode: keyToKeyCode("6") },
|
||||
comparator: { keyCode: keyToKeyCode("7") },
|
||||
transistor: { keyCode: keyToKeyCode("8") },
|
||||
},
|
||||
|
||||
placement: {
|
||||
pipette: { keyCode: key("Q") },
|
||||
rotateWhilePlacing: { keyCode: key("R") },
|
||||
rotateInverseModifier: { keyCode: 16 }, // SHIFT
|
||||
rotateToUp: { keyCode: KEYCODE_UP_ARROW },
|
||||
rotateToDown: { keyCode: KEYCODE_DOWN_ARROW },
|
||||
rotateToRight: { keyCode: KEYCODE_RIGHT_ARROW },
|
||||
rotateToLeft: { keyCode: KEYCODE_LEFT_ARROW },
|
||||
cycleBuildingVariants: { keyCode: key("T") },
|
||||
cycleBuildings: { keyCode: 9 }, // TAB
|
||||
switchDirectionLockSide: { keyCode: key("R") },
|
||||
pipette: { keyCode: keyToKeyCode("Q") },
|
||||
rotateWhilePlacing: { keyCode: keyToKeyCode("R") },
|
||||
rotateInverseModifier: { keyCode: KEYCODES.Shift },
|
||||
rotateToUp: { keyCode: KEYCODES.ArrowUp },
|
||||
rotateToDown: { keyCode: KEYCODES.ArrowDown },
|
||||
rotateToRight: { keyCode: KEYCODES.ArrowRight },
|
||||
rotateToLeft: { keyCode: KEYCODES.ArrowLeft },
|
||||
cycleBuildingVariants: { keyCode: keyToKeyCode("T") },
|
||||
cycleBuildings: { keyCode: KEYCODES.Tab },
|
||||
switchDirectionLockSide: { keyCode: keyToKeyCode("R") },
|
||||
|
||||
copyWireValue: { keyCode: key("Z") },
|
||||
copyWireValue: { keyCode: keyToKeyCode("Z") },
|
||||
},
|
||||
|
||||
massSelect: {
|
||||
massSelectStart: { keyCode: 17 }, // CTRL
|
||||
massSelectSelectMultiple: { keyCode: 16 }, // SHIFT
|
||||
massSelectCopy: { keyCode: key("C") },
|
||||
massSelectCut: { keyCode: key("X") },
|
||||
massSelectClear: { keyCode: key("B") },
|
||||
confirmMassDelete: { keyCode: 46 }, // DEL
|
||||
pasteLastBlueprint: { keyCode: key("V") },
|
||||
massSelectStart: { keyCode: KEYCODES.Ctrl },
|
||||
massSelectSelectMultiple: { keyCode: KEYCODES.Shift },
|
||||
massSelectCopy: { keyCode: keyToKeyCode("C") },
|
||||
massSelectCut: { keyCode: keyToKeyCode("X") },
|
||||
massSelectClear: { keyCode: keyToKeyCode("B") },
|
||||
confirmMassDelete: { keyCode: KEYCODES.Delete },
|
||||
pasteLastBlueprint: { keyCode: keyToKeyCode("V") },
|
||||
},
|
||||
|
||||
placementModifiers: {
|
||||
lockBeltDirection: { keyCode: 16 }, // SHIFT
|
||||
placementDisableAutoOrientation: { keyCode: 17 }, // CTRL
|
||||
placeMultiple: { keyCode: 16 }, // SHIFT
|
||||
placeInverse: { keyCode: 18 }, // ALT
|
||||
lockBeltDirection: { keyCode: KEYCODES.Shift },
|
||||
placementDisableAutoOrientation: { keyCode: KEYCODES.Ctrl },
|
||||
placeMultiple: { keyCode: KEYCODES.Shift },
|
||||
placeInverse: { keyCode: KEYCODES.Alt },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -153,23 +187,23 @@ export function getStringForKeyCode(code) {
|
||||
return "MB5";
|
||||
case 8:
|
||||
return "⌫";
|
||||
case 9:
|
||||
case KEYCODES.Tab:
|
||||
return T.global.keys.tab;
|
||||
case 13:
|
||||
case KEYCODES.Enter:
|
||||
return "⏎";
|
||||
case 16:
|
||||
case KEYCODES.Shift:
|
||||
return "⇪";
|
||||
case 17:
|
||||
case KEYCODES.Ctrl:
|
||||
return T.global.keys.control;
|
||||
case 18:
|
||||
case KEYCODES.Alt:
|
||||
return T.global.keys.alt;
|
||||
case 19:
|
||||
return "PAUSE";
|
||||
case 20:
|
||||
return "CAPS";
|
||||
case 27:
|
||||
case KEYCODES.Escape:
|
||||
return T.global.keys.escape;
|
||||
case 32:
|
||||
case KEYCODES.Space:
|
||||
return T.global.keys.space;
|
||||
case 33:
|
||||
return "PGUP";
|
||||
@@ -179,13 +213,13 @@ export function getStringForKeyCode(code) {
|
||||
return "END";
|
||||
case 36:
|
||||
return "HOME";
|
||||
case KEYCODE_LEFT_ARROW:
|
||||
case KEYCODES.ArrowLeft:
|
||||
return "⬅";
|
||||
case KEYCODE_UP_ARROW:
|
||||
case KEYCODES.ArrowUp:
|
||||
return "⬆";
|
||||
case KEYCODE_RIGHT_ARROW:
|
||||
case KEYCODES.ArrowRight:
|
||||
return "➡";
|
||||
case KEYCODE_DOWN_ARROW:
|
||||
case KEYCODES.ArrowDown:
|
||||
return "⬇";
|
||||
case 44:
|
||||
return "PRNT";
|
||||
@@ -225,29 +259,29 @@ export function getStringForKeyCode(code) {
|
||||
return ".";
|
||||
case 111:
|
||||
return "/";
|
||||
case 112:
|
||||
case KEYCODES.F1:
|
||||
return "F1";
|
||||
case 113:
|
||||
case KEYCODES.F2:
|
||||
return "F2";
|
||||
case 114:
|
||||
case KEYCODES.F3:
|
||||
return "F3";
|
||||
case 115:
|
||||
case KEYCODES.F4:
|
||||
return "F4";
|
||||
case 116:
|
||||
case KEYCODES.F5:
|
||||
return "F5";
|
||||
case 117:
|
||||
case KEYCODES.F6:
|
||||
return "F6";
|
||||
case 118:
|
||||
case KEYCODES.F7:
|
||||
return "F7";
|
||||
case 119:
|
||||
case KEYCODES.F8:
|
||||
return "F8";
|
||||
case 120:
|
||||
case KEYCODES.F9:
|
||||
return "F9";
|
||||
case 121:
|
||||
case KEYCODES.F10:
|
||||
return "F10";
|
||||
case 122:
|
||||
case KEYCODES.F11:
|
||||
return "F11";
|
||||
case 123:
|
||||
case KEYCODES.F12:
|
||||
return "F12";
|
||||
|
||||
case 144:
|
||||
@@ -296,8 +330,9 @@ export class Keybinding {
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean=} param0.builtin
|
||||
* @param {boolean=} param0.repeated
|
||||
* @param {{ shift?: boolean; alt?: boolean; ctrl?: boolean; }=} param0.modifiers
|
||||
*/
|
||||
constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) {
|
||||
constructor(keyMapper, app, { keyCode, builtin = false, repeated = false, modifiers = {} }) {
|
||||
assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
|
||||
this.keyMapper = keyMapper;
|
||||
this.app = app;
|
||||
@@ -305,6 +340,8 @@ export class Keybinding {
|
||||
this.builtin = builtin;
|
||||
this.repeated = repeated;
|
||||
|
||||
this.modifiers = modifiers;
|
||||
|
||||
this.signal = new Signal();
|
||||
this.toggled = new Signal();
|
||||
}
|
||||
@@ -395,7 +432,6 @@ export class KeyActionMapper {
|
||||
if (overrides[key]) {
|
||||
payload.keyCode = overrides[key];
|
||||
}
|
||||
|
||||
this.keybindings[key] = new Keybinding(this, this.root.app, payload);
|
||||
|
||||
if (G_IS_DEV) {
|
||||
@@ -459,9 +495,10 @@ export class KeyActionMapper {
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean} param0.shift
|
||||
* @param {boolean} param0.alt
|
||||
* @param {boolean} param0.ctrl
|
||||
* @param {boolean=} param0.initial
|
||||
*/
|
||||
handleKeydown({ keyCode, shift, alt, initial }) {
|
||||
handleKeydown({ keyCode, shift, alt, ctrl, initial }) {
|
||||
let stop = false;
|
||||
|
||||
// Find mapping
|
||||
@@ -469,6 +506,18 @@ export class KeyActionMapper {
|
||||
/** @type {Keybinding} */
|
||||
const binding = this.keybindings[key];
|
||||
if (binding.keyCode === keyCode && (initial || binding.repeated)) {
|
||||
if (binding.modifiers.shift && !shift) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (binding.modifiers.ctrl && !ctrl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (binding.modifiers.alt && !alt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @type {Signal} */
|
||||
const signal = this.keybindings[key].signal;
|
||||
if (signal.dispatch() === STOP_PROPAGATION) {
|
||||
@@ -505,4 +554,14 @@ export class KeyActionMapper {
|
||||
assert(this.keybindings[id], "Keybinding " + id + " not known!");
|
||||
return this.keybindings[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a given keybinding
|
||||
* @param {string} id
|
||||
* @returns {Keybinding}
|
||||
*/
|
||||
getBindingById(id) {
|
||||
assert(this.keybindings[id], "Keybinding " + id + " not known!");
|
||||
return this.keybindings[id];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,12 @@ export class GameLogic {
|
||||
/**
|
||||
* Checks if the given entity can be placed
|
||||
* @param {Entity} entity
|
||||
* @param {Vector=} offset Optional, move the entity by the given offset first
|
||||
* @param {Object} param0
|
||||
* @param {boolean=} param0.allowReplaceBuildings
|
||||
* @param {Vector=} param0.offset Optional, move the entity by the given offset first
|
||||
* @returns {boolean} true if the entity could be placed there
|
||||
*/
|
||||
checkCanPlaceEntity(entity, offset = null) {
|
||||
checkCanPlaceEntity(entity, { allowReplaceBuildings = true, offset = null }) {
|
||||
// Compute area of the building
|
||||
const rect = entity.components.StaticMapEntity.getTileSpaceBounds();
|
||||
if (offset) {
|
||||
@@ -71,7 +73,7 @@ export class GameLogic {
|
||||
const otherEntity = this.root.map.getLayerContentXY(x, y, entity.layer);
|
||||
if (otherEntity) {
|
||||
const metaClass = otherEntity.components.StaticMapEntity.getMetaBuilding();
|
||||
if (!metaClass.getIsReplaceable()) {
|
||||
if (!allowReplaceBuildings || !metaClass.getIsReplaceable()) {
|
||||
// This one is a direct blocker
|
||||
return false;
|
||||
}
|
||||
@@ -116,7 +118,7 @@ export class GameLogic {
|
||||
rotationVariant,
|
||||
variant,
|
||||
});
|
||||
if (this.checkCanPlaceEntity(entity)) {
|
||||
if (this.checkCanPlaceEntity(entity, {})) {
|
||||
this.freeEntityAreaBeforeBuild(entity);
|
||||
this.root.map.placeStaticEntity(entity);
|
||||
this.root.entityMgr.registerEntity(entity);
|
||||
@@ -393,7 +395,14 @@ export class GameLogic {
|
||||
|
||||
const entity = this.root.map.getLayerContentXY(tile.x + dx, tile.y + dy, "regular");
|
||||
if (entity) {
|
||||
/**
|
||||
* @type {Array<import("./components/item_ejector").ItemEjectorSlot>}
|
||||
*/
|
||||
let ejectorSlots = [];
|
||||
|
||||
/**
|
||||
* @type {Array<import("./components/item_acceptor").ItemAcceptorSlot>}
|
||||
*/
|
||||
let acceptorSlots = [];
|
||||
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
@@ -434,19 +443,16 @@ export class GameLogic {
|
||||
for (let acceptorSlot = 0; acceptorSlot < acceptorSlots.length; ++acceptorSlot) {
|
||||
const slot = acceptorSlots[acceptorSlot];
|
||||
const wsTile = staticComp.localTileToWorld(slot.pos);
|
||||
for (let k = 0; k < slot.directions.length; ++k) {
|
||||
const direction = slot.directions[k];
|
||||
const wsDirection = staticComp.localDirectionToWorld(direction);
|
||||
|
||||
const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]);
|
||||
if (sourceTile.equals(tile)) {
|
||||
acceptors.push({
|
||||
entity,
|
||||
slot,
|
||||
toTile: wsTile,
|
||||
fromDirection: wsDirection,
|
||||
});
|
||||
}
|
||||
const direction = slot.direction;
|
||||
const wsDirection = staticComp.localDirectionToWorld(direction);
|
||||
const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]);
|
||||
if (sourceTile.equals(tile)) {
|
||||
acceptors.push({
|
||||
entity,
|
||||
slot,
|
||||
toTile: wsTile,
|
||||
fromDirection: wsDirection,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ import { Rectangle } from "../core/rectangle";
|
||||
|
||||
const logger = createLogger("map_chunk");
|
||||
|
||||
/**
|
||||
* @type {Object<string, (distanceToOriginInChunks: number) => number>}
|
||||
*/
|
||||
export const MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS = {};
|
||||
|
||||
export class MapChunk {
|
||||
/**
|
||||
*
|
||||
@@ -192,6 +197,10 @@ export class MapChunk {
|
||||
[enumSubShape.windmill]: Math.round(6 + clamp(distanceToOriginInChunks / 2, 0, 20)),
|
||||
};
|
||||
|
||||
for (const key in MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS) {
|
||||
weights[key] = MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS[key](distanceToOriginInChunks);
|
||||
}
|
||||
|
||||
if (distanceToOriginInChunks < 7) {
|
||||
// Initial chunks can not spawn the good stuff
|
||||
weights[enumSubShape.star] = 0;
|
||||
@@ -274,6 +283,17 @@ export class MapChunk {
|
||||
const chunkCenter = new Vector(this.x, this.y).addScalar(0.5);
|
||||
const distanceToOriginInChunks = Math.round(chunkCenter.length());
|
||||
|
||||
this.generatePatches({ rng, chunkCenter, distanceToOriginInChunks });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {RandomNumberGenerator} param0.rng
|
||||
* @param {Vector} param0.chunkCenter
|
||||
* @param {number} param0.distanceToOriginInChunks
|
||||
*/
|
||||
generatePatches({ rng, chunkCenter, distanceToOriginInChunks }) {
|
||||
// Determine how likely it is that there is a color patch
|
||||
const colorPatchChance = 0.9 - clamp(distanceToOriginInChunks / 25, 0, 1) * 0.5;
|
||||
|
||||
@@ -424,7 +444,7 @@ export class MapChunk {
|
||||
* Sets the chunks contents
|
||||
* @param {number} tileX
|
||||
* @param {number} tileY
|
||||
* @param {Entity=} contents
|
||||
* @param {Entity} contents
|
||||
* @param {Layer} layer
|
||||
*/
|
||||
setLayerContentFromWorldCords(tileX, tileY, contents, layer) {
|
||||
|
||||
@@ -2,10 +2,9 @@ import { globalConfig } from "../core/config";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { drawSpriteClipped } from "../core/draw_utils";
|
||||
import { safeModulo } from "../core/utils";
|
||||
import { CHUNK_OVERLAY_RES } from "./map_chunk_view";
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
export const CHUNK_OVERLAY_RES = 3;
|
||||
|
||||
export class MapChunkAggregate {
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -5,10 +5,20 @@ import { Entity } from "./entity";
|
||||
import { MapChunk } from "./map_chunk";
|
||||
import { GameRoot } from "./root";
|
||||
import { THEME } from "./theme";
|
||||
import { drawSpriteClipped } from "../core/draw_utils";
|
||||
|
||||
export const CHUNK_OVERLAY_RES = 3;
|
||||
|
||||
export const MOD_CHUNK_DRAW_HOOKS = {
|
||||
backgroundLayerBefore: [],
|
||||
backgroundLayerAfter: [],
|
||||
|
||||
foregroundDynamicBefore: [],
|
||||
foregroundDynamicAfter: [],
|
||||
|
||||
staticBefore: [],
|
||||
staticAfter: [],
|
||||
};
|
||||
|
||||
export class MapChunkView extends MapChunk {
|
||||
/**
|
||||
*
|
||||
@@ -42,6 +52,11 @@ export class MapChunkView extends MapChunk {
|
||||
*/
|
||||
drawBackgroundLayer(parameters) {
|
||||
const systems = this.root.systemMgr.systems;
|
||||
|
||||
MOD_CHUNK_DRAW_HOOKS.backgroundLayerBefore.forEach(systemId =>
|
||||
systems[systemId].drawChunk(parameters, this)
|
||||
);
|
||||
|
||||
if (systems.zone) {
|
||||
systems.zone.drawChunk(parameters, this);
|
||||
}
|
||||
@@ -52,6 +67,10 @@ export class MapChunkView extends MapChunk {
|
||||
|
||||
systems.beltUnderlays.drawChunk(parameters, this);
|
||||
systems.belt.drawChunk(parameters, this);
|
||||
|
||||
MOD_CHUNK_DRAW_HOOKS.backgroundLayerAfter.forEach(systemId =>
|
||||
systems[systemId].drawChunk(parameters, this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,9 +80,17 @@ export class MapChunkView extends MapChunk {
|
||||
drawForegroundDynamicLayer(parameters) {
|
||||
const systems = this.root.systemMgr.systems;
|
||||
|
||||
MOD_CHUNK_DRAW_HOOKS.foregroundDynamicBefore.forEach(systemId =>
|
||||
systems[systemId].drawChunk(parameters, this)
|
||||
);
|
||||
|
||||
systems.itemEjector.drawChunk(parameters, this);
|
||||
systems.itemAcceptor.drawChunk(parameters, this);
|
||||
systems.miner.drawChunk(parameters, this);
|
||||
|
||||
MOD_CHUNK_DRAW_HOOKS.foregroundDynamicAfter.forEach(systemId =>
|
||||
systems[systemId].drawChunk(parameters, this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,6 +100,8 @@ export class MapChunkView extends MapChunk {
|
||||
drawForegroundStaticLayer(parameters) {
|
||||
const systems = this.root.systemMgr.systems;
|
||||
|
||||
MOD_CHUNK_DRAW_HOOKS.staticBefore.forEach(systemId => systems[systemId].drawChunk(parameters, this));
|
||||
|
||||
systems.staticMapEntities.drawChunk(parameters, this);
|
||||
systems.lever.drawChunk(parameters, this);
|
||||
systems.display.drawChunk(parameters, this);
|
||||
@@ -80,6 +109,8 @@ export class MapChunkView extends MapChunk {
|
||||
systems.constantProducer.drawChunk(parameters, this);
|
||||
systems.goalAcceptor.drawChunk(parameters, this);
|
||||
systems.itemProcessorOverlays.drawChunk(parameters, this);
|
||||
|
||||
MOD_CHUNK_DRAW_HOOKS.staticAfter.forEach(systemId => systems[systemId].drawChunk(parameters, this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,16 @@ export class MetaBuilding {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return all possible variants of this building, no matter
|
||||
* if they are already available or will be unlocked later on
|
||||
*
|
||||
* @returns {Array<{ variant: string, rotationVariant?: number, internalId?: number|string }>}
|
||||
*/
|
||||
static getAllVariantCombinations() {
|
||||
throw new Error("implement getAllVariantCombinations");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of this building
|
||||
*/
|
||||
|
||||
@@ -2,202 +2,89 @@ import { gMetaBuildingRegistry } from "../core/global_registries";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { T } from "../translations";
|
||||
import { MetaAnalyzerBuilding } from "./buildings/analyzer";
|
||||
import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer";
|
||||
import { MetaBalancerBuilding } from "./buildings/balancer";
|
||||
import { MetaBeltBuilding } from "./buildings/belt";
|
||||
import { MetaBlockBuilding } from "./buildings/block";
|
||||
import { MetaComparatorBuilding } from "./buildings/comparator";
|
||||
import { MetaConstantProducerBuilding } from "./buildings/constant_producer";
|
||||
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
||||
import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
|
||||
import { MetaCutterBuilding } from "./buildings/cutter";
|
||||
import { MetaDisplayBuilding } from "./buildings/display";
|
||||
import { MetaFilterBuilding } from "./buildings/filter";
|
||||
import { MetaGoalAcceptorBuilding } from "./buildings/goal_acceptor";
|
||||
import { MetaHubBuilding } from "./buildings/hub";
|
||||
import { MetaItemProducerBuilding } from "./buildings/item_producer";
|
||||
import { MetaLeverBuilding } from "./buildings/lever";
|
||||
import { enumLogicGateVariants, MetaLogicGateBuilding } from "./buildings/logic_gate";
|
||||
import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
|
||||
import { MetaLogicGateBuilding } from "./buildings/logic_gate";
|
||||
import { MetaMinerBuilding } from "./buildings/miner";
|
||||
import { MetaMixerBuilding } from "./buildings/mixer";
|
||||
import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
|
||||
import { MetaPainterBuilding } from "./buildings/painter";
|
||||
import { MetaReaderBuilding } from "./buildings/reader";
|
||||
import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater";
|
||||
import { MetaRotaterBuilding } from "./buildings/rotater";
|
||||
import { MetaStackerBuilding } from "./buildings/stacker";
|
||||
import { MetaStorageBuilding } from "./buildings/storage";
|
||||
import { enumTransistorVariants, MetaTransistorBuilding } from "./buildings/transistor";
|
||||
import { MetaTransistorBuilding } from "./buildings/transistor";
|
||||
import { MetaTrashBuilding } from "./buildings/trash";
|
||||
import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
|
||||
import { enumVirtualProcessorVariants, MetaVirtualProcessorBuilding } from "./buildings/virtual_processor";
|
||||
import { MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
|
||||
import { MetaVirtualProcessorBuilding } from "./buildings/virtual_processor";
|
||||
import { MetaWireBuilding } from "./buildings/wire";
|
||||
import { MetaWireTunnelBuilding } from "./buildings/wire_tunnel";
|
||||
import { buildBuildingCodeCache, gBuildingVariants, registerBuildingVariant } from "./building_codes";
|
||||
import { enumWireVariant } from "./components/wire";
|
||||
import { KEYMAPPINGS } from "./key_action_mapper";
|
||||
import { defaultBuildingVariant } from "./meta_building";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "./meta_building";
|
||||
|
||||
const logger = createLogger("building_registry");
|
||||
|
||||
export function initMetaBuildingRegistry() {
|
||||
gMetaBuildingRegistry.register(MetaBalancerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaMinerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaCutterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaRotaterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaStackerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaMixerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaPainterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaTrashBuilding);
|
||||
gMetaBuildingRegistry.register(MetaStorageBuilding);
|
||||
gMetaBuildingRegistry.register(MetaBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
|
||||
gMetaBuildingRegistry.register(MetaGoalAcceptorBuilding);
|
||||
gMetaBuildingRegistry.register(MetaHubBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireBuilding);
|
||||
gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
|
||||
gMetaBuildingRegistry.register(MetaLogicGateBuilding);
|
||||
gMetaBuildingRegistry.register(MetaLeverBuilding);
|
||||
gMetaBuildingRegistry.register(MetaFilterBuilding);
|
||||
gMetaBuildingRegistry.register(MetaWireTunnelBuilding);
|
||||
gMetaBuildingRegistry.register(MetaDisplayBuilding);
|
||||
gMetaBuildingRegistry.register(MetaVirtualProcessorBuilding);
|
||||
gMetaBuildingRegistry.register(MetaReaderBuilding);
|
||||
gMetaBuildingRegistry.register(MetaTransistorBuilding);
|
||||
gMetaBuildingRegistry.register(MetaAnalyzerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaComparatorBuilding);
|
||||
gMetaBuildingRegistry.register(MetaItemProducerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaConstantProducerBuilding);
|
||||
gMetaBuildingRegistry.register(MetaBlockBuilding);
|
||||
|
||||
// Belt
|
||||
registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(2, MetaBeltBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(3, MetaBeltBuilding, defaultBuildingVariant, 2);
|
||||
|
||||
// Balancer
|
||||
registerBuildingVariant(4, MetaBalancerBuilding);
|
||||
registerBuildingVariant(5, MetaBalancerBuilding, enumBalancerVariants.merger);
|
||||
registerBuildingVariant(6, MetaBalancerBuilding, enumBalancerVariants.mergerInverse);
|
||||
registerBuildingVariant(47, MetaBalancerBuilding, enumBalancerVariants.splitter);
|
||||
registerBuildingVariant(48, MetaBalancerBuilding, enumBalancerVariants.splitterInverse);
|
||||
|
||||
// Miner
|
||||
registerBuildingVariant(7, MetaMinerBuilding);
|
||||
registerBuildingVariant(8, MetaMinerBuilding, enumMinerVariants.chainable);
|
||||
|
||||
// Cutter
|
||||
registerBuildingVariant(9, MetaCutterBuilding);
|
||||
registerBuildingVariant(10, MetaCutterBuilding, enumCutterVariants.quad);
|
||||
|
||||
// Rotater
|
||||
registerBuildingVariant(11, MetaRotaterBuilding);
|
||||
registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw);
|
||||
registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.rotate180);
|
||||
|
||||
// Stacker
|
||||
registerBuildingVariant(14, MetaStackerBuilding);
|
||||
|
||||
// Mixer
|
||||
registerBuildingVariant(15, MetaMixerBuilding);
|
||||
|
||||
// Painter
|
||||
registerBuildingVariant(16, MetaPainterBuilding);
|
||||
registerBuildingVariant(17, MetaPainterBuilding, enumPainterVariants.mirrored);
|
||||
registerBuildingVariant(18, MetaPainterBuilding, enumPainterVariants.double);
|
||||
registerBuildingVariant(19, MetaPainterBuilding, enumPainterVariants.quad);
|
||||
|
||||
// Trash
|
||||
registerBuildingVariant(20, MetaTrashBuilding);
|
||||
|
||||
// Storage
|
||||
registerBuildingVariant(21, MetaStorageBuilding);
|
||||
|
||||
// Underground belt
|
||||
registerBuildingVariant(22, MetaUndergroundBeltBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(23, MetaUndergroundBeltBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(24, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 0);
|
||||
registerBuildingVariant(25, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 1);
|
||||
|
||||
// Hub
|
||||
registerBuildingVariant(26, MetaHubBuilding);
|
||||
|
||||
// Wire
|
||||
registerBuildingVariant(27, MetaWireBuilding, defaultBuildingVariant, 0);
|
||||
registerBuildingVariant(28, MetaWireBuilding, defaultBuildingVariant, 1);
|
||||
registerBuildingVariant(29, MetaWireBuilding, defaultBuildingVariant, 2);
|
||||
registerBuildingVariant(30, MetaWireBuilding, defaultBuildingVariant, 3);
|
||||
|
||||
registerBuildingVariant(52, MetaWireBuilding, enumWireVariant.second, 0);
|
||||
registerBuildingVariant(53, MetaWireBuilding, enumWireVariant.second, 1);
|
||||
registerBuildingVariant(54, MetaWireBuilding, enumWireVariant.second, 2);
|
||||
registerBuildingVariant(55, MetaWireBuilding, enumWireVariant.second, 3);
|
||||
|
||||
// Constant signal
|
||||
registerBuildingVariant(31, MetaConstantSignalBuilding);
|
||||
|
||||
// Logic gate
|
||||
registerBuildingVariant(32, MetaLogicGateBuilding);
|
||||
registerBuildingVariant(34, MetaLogicGateBuilding, enumLogicGateVariants.not);
|
||||
registerBuildingVariant(35, MetaLogicGateBuilding, enumLogicGateVariants.xor);
|
||||
registerBuildingVariant(36, MetaLogicGateBuilding, enumLogicGateVariants.or);
|
||||
|
||||
// Transistor
|
||||
registerBuildingVariant(38, MetaTransistorBuilding, defaultBuildingVariant);
|
||||
registerBuildingVariant(60, MetaTransistorBuilding, enumTransistorVariants.mirrored);
|
||||
|
||||
// Lever
|
||||
registerBuildingVariant(33, MetaLeverBuilding);
|
||||
|
||||
// Filter
|
||||
registerBuildingVariant(37, MetaFilterBuilding);
|
||||
|
||||
// Wire tunnel
|
||||
registerBuildingVariant(39, MetaWireTunnelBuilding);
|
||||
|
||||
// Display
|
||||
registerBuildingVariant(40, MetaDisplayBuilding);
|
||||
|
||||
// Virtual Processor
|
||||
registerBuildingVariant(42, MetaVirtualProcessorBuilding);
|
||||
registerBuildingVariant(44, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.rotater);
|
||||
registerBuildingVariant(45, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.unstacker);
|
||||
registerBuildingVariant(50, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.stacker);
|
||||
registerBuildingVariant(51, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.painter);
|
||||
|
||||
// Analyzer
|
||||
registerBuildingVariant(46, MetaComparatorBuilding);
|
||||
registerBuildingVariant(43, MetaAnalyzerBuilding);
|
||||
|
||||
// Reader
|
||||
registerBuildingVariant(49, MetaReaderBuilding);
|
||||
|
||||
// Item producer
|
||||
registerBuildingVariant(61, MetaItemProducerBuilding);
|
||||
|
||||
// Constant producer
|
||||
registerBuildingVariant(62, MetaConstantProducerBuilding);
|
||||
|
||||
// Goal acceptor
|
||||
registerBuildingVariant(63, MetaGoalAcceptorBuilding);
|
||||
|
||||
// Block
|
||||
registerBuildingVariant(64, MetaBlockBuilding);
|
||||
|
||||
// Propagate instances
|
||||
for (const key in gBuildingVariants) {
|
||||
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
|
||||
gBuildingVariants[key].metaClass
|
||||
/**
|
||||
*
|
||||
* @param {typeof MetaBuilding} metaBuilding
|
||||
*/
|
||||
export function registerBuildingVariants(metaBuilding) {
|
||||
gMetaBuildingRegistry.register(metaBuilding);
|
||||
const combinations = metaBuilding.getAllVariantCombinations();
|
||||
combinations.forEach(combination => {
|
||||
registerBuildingVariant(
|
||||
combination.internalId,
|
||||
metaBuilding,
|
||||
combination.variant || defaultBuildingVariant,
|
||||
combination.rotationVariant || 0
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const key in gBuildingVariants) {
|
||||
const variant = gBuildingVariants[key];
|
||||
assert(variant.metaClass, "Variant has no meta: " + key);
|
||||
export function initMetaBuildingRegistry() {
|
||||
const buildings = [
|
||||
MetaBalancerBuilding,
|
||||
MetaMinerBuilding,
|
||||
MetaCutterBuilding,
|
||||
MetaRotaterBuilding,
|
||||
MetaStackerBuilding,
|
||||
MetaMixerBuilding,
|
||||
MetaPainterBuilding,
|
||||
MetaTrashBuilding,
|
||||
MetaStorageBuilding,
|
||||
MetaBeltBuilding,
|
||||
MetaUndergroundBeltBuilding,
|
||||
MetaGoalAcceptorBuilding,
|
||||
MetaHubBuilding,
|
||||
MetaWireBuilding,
|
||||
MetaConstantSignalBuilding,
|
||||
MetaLogicGateBuilding,
|
||||
MetaLeverBuilding,
|
||||
MetaFilterBuilding,
|
||||
MetaWireTunnelBuilding,
|
||||
MetaDisplayBuilding,
|
||||
MetaVirtualProcessorBuilding,
|
||||
MetaReaderBuilding,
|
||||
MetaTransistorBuilding,
|
||||
MetaAnalyzerBuilding,
|
||||
MetaComparatorBuilding,
|
||||
MetaItemProducerBuilding,
|
||||
MetaConstantProducerBuilding,
|
||||
MetaBlockBuilding,
|
||||
];
|
||||
|
||||
if (typeof variant.rotationVariant === "undefined") {
|
||||
variant.rotationVariant = 0;
|
||||
}
|
||||
if (typeof variant.variant === "undefined") {
|
||||
variant.variant = defaultBuildingVariant;
|
||||
}
|
||||
}
|
||||
buildings.forEach(registerBuildingVariants);
|
||||
|
||||
// Check for valid keycodes
|
||||
if (G_IS_DEV) {
|
||||
@@ -205,18 +92,15 @@ export function initMetaBuildingRegistry() {
|
||||
const id = metaBuilding.getId();
|
||||
if (!["hub"].includes(id)) {
|
||||
if (!KEYMAPPINGS.buildings[id]) {
|
||||
assertAlways(
|
||||
false,
|
||||
console.error(
|
||||
"Building " + id + " has no keybinding assigned! Add it to key_action_mapper.js"
|
||||
);
|
||||
}
|
||||
|
||||
if (!T.buildings[id]) {
|
||||
assertAlways(false, "Translation for building " + id + " missing!");
|
||||
}
|
||||
|
||||
if (!T.buildings[id].default) {
|
||||
assertAlways(false, "Translation for building " + id + " missing (default variant)!");
|
||||
console.error("Translation for building " + id + " missing!");
|
||||
} else if (!T.buildings[id].default) {
|
||||
console.error("Translation for building " + id + " missing (default variant)!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ import { HUDPuzzlePlaySettings } from "../hud/parts/puzzle_play_settings";
|
||||
import { MetaBlockBuilding } from "../buildings/block";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { gMetaBuildingRegistry } from "../../core/global_registries";
|
||||
import { HUDPuzzleNextPuzzle } from "../hud/parts/HUDPuzzleNextPuzzle";
|
||||
import { HUDPuzzleNextPuzzle } from "../hud/parts/next_puzzle";
|
||||
|
||||
const logger = createLogger("puzzle-play");
|
||||
const copy = require("clipboard-copy");
|
||||
|
||||
@@ -38,6 +38,7 @@ import { HUDSandboxController } from "../hud/parts/sandbox_controller";
|
||||
import { queryParamOptions } from "../../core/query_parameters";
|
||||
import { MetaBlockBuilding } from "../buildings/block";
|
||||
import { MetaItemProducerBuilding } from "../buildings/item_producer";
|
||||
import { MOD_SIGNALS } from "../../mods/mod_signals";
|
||||
|
||||
/** @typedef {{
|
||||
* shape: string,
|
||||
@@ -68,10 +69,16 @@ const tierGrowth = 2.5;
|
||||
|
||||
const chinaShapes = G_WEGAME_VERSION || G_CHINA_VERSION;
|
||||
|
||||
const upgradesCache = {};
|
||||
|
||||
/**
|
||||
* Generates all upgrades
|
||||
* @returns {Object<string, UpgradeTiers>} */
|
||||
function generateUpgrades(limitedVersion = false) {
|
||||
if (upgradesCache[limitedVersion]) {
|
||||
return upgradesCache[limitedVersion];
|
||||
}
|
||||
|
||||
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
|
||||
const numEndgameUpgrades = limitedVersion ? 0 : 1000 - fixedImprovements.length - 1;
|
||||
|
||||
@@ -264,6 +271,8 @@ function generateUpgrades(limitedVersion = false) {
|
||||
}
|
||||
}
|
||||
|
||||
MOD_SIGNALS.modifyUpgrades.dispatch(upgrades);
|
||||
|
||||
// VALIDATE
|
||||
if (G_IS_DEV) {
|
||||
for (const upgradeId in upgrades) {
|
||||
@@ -279,14 +288,20 @@ function generateUpgrades(limitedVersion = false) {
|
||||
}
|
||||
}
|
||||
|
||||
upgradesCache[limitedVersion] = upgrades;
|
||||
return upgrades;
|
||||
}
|
||||
|
||||
const levelDefinitionsCache = {};
|
||||
|
||||
/**
|
||||
* Generates the level definitions
|
||||
* @param {boolean} limitedVersion
|
||||
*/
|
||||
export function generateLevelDefinitions(limitedVersion = false) {
|
||||
if (levelDefinitionsCache[limitedVersion]) {
|
||||
return levelDefinitionsCache[limitedVersion];
|
||||
}
|
||||
const levelDefinitions = [
|
||||
// 1
|
||||
// Circle
|
||||
@@ -511,6 +526,8 @@ export function generateLevelDefinitions(limitedVersion = false) {
|
||||
]),
|
||||
];
|
||||
|
||||
MOD_SIGNALS.modifyLevelDefinitions.dispatch(levelDefinitions);
|
||||
|
||||
if (G_IS_DEV) {
|
||||
levelDefinitions.forEach(({ shape }) => {
|
||||
try {
|
||||
@@ -521,15 +538,11 @@ export function generateLevelDefinitions(limitedVersion = false) {
|
||||
});
|
||||
}
|
||||
|
||||
levelDefinitionsCache[limitedVersion] = levelDefinitions;
|
||||
|
||||
return levelDefinitions;
|
||||
}
|
||||
|
||||
const fullVersionUpgrades = generateUpgrades(false);
|
||||
const demoVersionUpgrades = generateUpgrades(true);
|
||||
|
||||
const fullVersionLevels = generateLevelDefinitions(false);
|
||||
const demoVersionLevels = generateLevelDefinitions(true);
|
||||
|
||||
export class RegularGameMode extends GameMode {
|
||||
static getId() {
|
||||
return enumGameModeIds.regular;
|
||||
@@ -589,7 +602,7 @@ export class RegularGameMode extends GameMode {
|
||||
/** @type {(typeof MetaBuilding)[]} */
|
||||
this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding, MetaBlockBuilding];
|
||||
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
if (!(G_IS_DEV || window.sandboxMode || queryParamOptions.sandboxMode)) {
|
||||
this.hiddenBuildings.push(MetaItemProducerBuilding);
|
||||
}
|
||||
@@ -600,9 +613,7 @@ export class RegularGameMode extends GameMode {
|
||||
* @returns {Object<string, UpgradeTiers>}
|
||||
*/
|
||||
getUpgrades() {
|
||||
return this.root.app.restrictionMgr.getHasExtendedUpgrades()
|
||||
? fullVersionUpgrades
|
||||
: demoVersionUpgrades;
|
||||
return generateUpgrades(!this.root.app.restrictionMgr.getHasExtendedUpgrades());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,9 +621,7 @@ export class RegularGameMode extends GameMode {
|
||||
* @returns {Array<LevelDefinition>}
|
||||
*/
|
||||
getLevelDefinitions() {
|
||||
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay()
|
||||
? fullVersionLevels
|
||||
: demoVersionLevels;
|
||||
return generateLevelDefinitions(!this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,19 @@ import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors";
|
||||
import { THEME } from "./theme";
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* context: CanvasRenderingContext2D,
|
||||
* quadrantSize: number,
|
||||
* layerScale: number,
|
||||
* }} SubShapeDrawOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Object<string, (options: SubShapeDrawOptions) => void>}
|
||||
*/
|
||||
export const MODS_ADDITIONAL_SUB_SHAPE_DRAWERS = {};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* subShape: enumSubShape,
|
||||
@@ -14,6 +27,11 @@ import { THEME } from "./theme";
|
||||
* }} ShapeLayerItem
|
||||
*/
|
||||
|
||||
export const TOP_RIGHT = 0;
|
||||
export const BOTTOM_RIGHT = 1;
|
||||
export const BOTTOM_LEFT = 2;
|
||||
export const TOP_LEFT = 3;
|
||||
|
||||
/**
|
||||
* Order is Q1 (tr), Q2(br), Q3(bl), Q4(tl)
|
||||
* @typedef {[ShapeLayerItem?, ShapeLayerItem?, ShapeLayerItem?, ShapeLayerItem?]} ShapeLayer
|
||||
@@ -51,7 +69,7 @@ for (const key in enumSubShapeToShortcode) {
|
||||
/**
|
||||
* Converts the given parameters to a valid shape definition
|
||||
* @param {*} layers
|
||||
* @returns {Array<import("./shape_definition").ShapeLayer>}
|
||||
* @returns {Array<ShapeLayer>}
|
||||
*/
|
||||
export function createSimpleShape(layers) {
|
||||
layers.forEach(layer => {
|
||||
@@ -229,7 +247,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* Internal method to clone the shape definition
|
||||
* @returns {Array<ShapeLayer>}
|
||||
*/
|
||||
internalCloneLayers() {
|
||||
getClonedLayers() {
|
||||
return JSON.parse(JSON.stringify(this.layers));
|
||||
}
|
||||
|
||||
@@ -366,74 +384,79 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
context.strokeStyle = THEME.items.outline;
|
||||
context.lineWidth = THEME.items.outlineWidth;
|
||||
|
||||
const insetPadding = 0.0;
|
||||
if (MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]) {
|
||||
MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]({
|
||||
context,
|
||||
layerScale,
|
||||
quadrantSize,
|
||||
});
|
||||
} else {
|
||||
switch (subShape) {
|
||||
case enumSubShape.rect: {
|
||||
context.beginPath();
|
||||
const dims = quadrantSize * layerScale;
|
||||
context.rect(-quadrantHalfSize, quadrantHalfSize - dims, dims, dims);
|
||||
context.fill();
|
||||
context.stroke();
|
||||
break;
|
||||
}
|
||||
case enumSubShape.star: {
|
||||
context.beginPath();
|
||||
const dims = quadrantSize * layerScale;
|
||||
|
||||
switch (subShape) {
|
||||
case enumSubShape.rect: {
|
||||
context.beginPath();
|
||||
const dims = quadrantSize * layerScale;
|
||||
context.rect(
|
||||
insetPadding + -quadrantHalfSize,
|
||||
-insetPadding + quadrantHalfSize - dims,
|
||||
dims,
|
||||
dims
|
||||
);
|
||||
let originX = -quadrantHalfSize;
|
||||
let originY = quadrantHalfSize - dims;
|
||||
|
||||
break;
|
||||
}
|
||||
case enumSubShape.star: {
|
||||
context.beginPath();
|
||||
const dims = quadrantSize * layerScale;
|
||||
const moveInwards = dims * 0.4;
|
||||
context.moveTo(originX, originY + moveInwards);
|
||||
context.lineTo(originX + dims, originY);
|
||||
context.lineTo(originX + dims - moveInwards, originY + dims);
|
||||
context.lineTo(originX, originY + dims);
|
||||
context.closePath();
|
||||
context.fill();
|
||||
context.stroke();
|
||||
break;
|
||||
}
|
||||
|
||||
let originX = insetPadding - quadrantHalfSize;
|
||||
let originY = -insetPadding + quadrantHalfSize - dims;
|
||||
case enumSubShape.windmill: {
|
||||
context.beginPath();
|
||||
const dims = quadrantSize * layerScale;
|
||||
|
||||
const moveInwards = dims * 0.4;
|
||||
context.moveTo(originX, originY + moveInwards);
|
||||
context.lineTo(originX + dims, originY);
|
||||
context.lineTo(originX + dims - moveInwards, originY + dims);
|
||||
context.lineTo(originX, originY + dims);
|
||||
context.closePath();
|
||||
break;
|
||||
}
|
||||
let originX = -quadrantHalfSize;
|
||||
let originY = quadrantHalfSize - dims;
|
||||
const moveInwards = dims * 0.4;
|
||||
context.moveTo(originX, originY + moveInwards);
|
||||
context.lineTo(originX + dims, originY);
|
||||
context.lineTo(originX + dims, originY + dims);
|
||||
context.lineTo(originX, originY + dims);
|
||||
context.closePath();
|
||||
context.fill();
|
||||
context.stroke();
|
||||
break;
|
||||
}
|
||||
|
||||
case enumSubShape.windmill: {
|
||||
context.beginPath();
|
||||
const dims = quadrantSize * layerScale;
|
||||
case enumSubShape.circle: {
|
||||
context.beginPath();
|
||||
context.moveTo(-quadrantHalfSize, quadrantHalfSize);
|
||||
context.arc(
|
||||
-quadrantHalfSize,
|
||||
quadrantHalfSize,
|
||||
quadrantSize * layerScale,
|
||||
-Math.PI * 0.5,
|
||||
0
|
||||
);
|
||||
context.closePath();
|
||||
context.fill();
|
||||
context.stroke();
|
||||
break;
|
||||
}
|
||||
|
||||
let originX = insetPadding - quadrantHalfSize;
|
||||
let originY = -insetPadding + quadrantHalfSize - dims;
|
||||
const moveInwards = dims * 0.4;
|
||||
context.moveTo(originX, originY + moveInwards);
|
||||
context.lineTo(originX + dims, originY);
|
||||
context.lineTo(originX + dims, originY + dims);
|
||||
context.lineTo(originX, originY + dims);
|
||||
context.closePath();
|
||||
break;
|
||||
}
|
||||
|
||||
case enumSubShape.circle: {
|
||||
context.beginPath();
|
||||
context.moveTo(insetPadding + -quadrantHalfSize, -insetPadding + quadrantHalfSize);
|
||||
context.arc(
|
||||
insetPadding + -quadrantHalfSize,
|
||||
-insetPadding + quadrantHalfSize,
|
||||
quadrantSize * layerScale,
|
||||
-Math.PI * 0.5,
|
||||
0
|
||||
);
|
||||
context.closePath();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
assertAlways(false, "Unkown sub shape: " + subShape);
|
||||
default: {
|
||||
throw new Error("Unkown sub shape: " + subShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.fill();
|
||||
context.stroke();
|
||||
|
||||
context.rotate(-rotation);
|
||||
context.translate(-centerQuadrantX, -centerQuadrantY);
|
||||
}
|
||||
@@ -446,7 +469,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* @returns {ShapeDefinition}
|
||||
*/
|
||||
cloneFilteredByQuadrants(includeQuadrants) {
|
||||
const newLayers = this.internalCloneLayers();
|
||||
const newLayers = this.getClonedLayers();
|
||||
for (let layerIndex = 0; layerIndex < newLayers.length; ++layerIndex) {
|
||||
const quadrants = newLayers[layerIndex];
|
||||
let anyContents = false;
|
||||
@@ -472,7 +495,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* @returns {ShapeDefinition}
|
||||
*/
|
||||
cloneRotateCW() {
|
||||
const newLayers = this.internalCloneLayers();
|
||||
const newLayers = this.getClonedLayers();
|
||||
for (let layerIndex = 0; layerIndex < newLayers.length; ++layerIndex) {
|
||||
const quadrants = newLayers[layerIndex];
|
||||
quadrants.unshift(quadrants[3]);
|
||||
@@ -486,7 +509,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* @returns {ShapeDefinition}
|
||||
*/
|
||||
cloneRotateCCW() {
|
||||
const newLayers = this.internalCloneLayers();
|
||||
const newLayers = this.getClonedLayers();
|
||||
for (let layerIndex = 0; layerIndex < newLayers.length; ++layerIndex) {
|
||||
const quadrants = newLayers[layerIndex];
|
||||
quadrants.push(quadrants[0]);
|
||||
@@ -500,7 +523,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* @returns {ShapeDefinition}
|
||||
*/
|
||||
cloneRotate180() {
|
||||
const newLayers = this.internalCloneLayers();
|
||||
const newLayers = this.getClonedLayers();
|
||||
for (let layerIndex = 0; layerIndex < newLayers.length; ++layerIndex) {
|
||||
const quadrants = newLayers[layerIndex];
|
||||
quadrants.push(quadrants.shift(), quadrants.shift());
|
||||
@@ -558,7 +581,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
// Can't merge at a layer lower than 0
|
||||
const layerToMergeAt = Math.max(1 - smallestGapBetweenShapes, 0);
|
||||
|
||||
const mergedLayers = this.internalCloneLayers();
|
||||
const mergedLayers = this.getClonedLayers();
|
||||
for (let layer = mergedLayers.length; layer < layerToMergeAt + topShapeLayers.length; ++layer) {
|
||||
mergedLayers.push([null, null, null, null]);
|
||||
}
|
||||
@@ -584,7 +607,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* @param {enumColors} color
|
||||
*/
|
||||
cloneAndPaintWith(color) {
|
||||
const newLayers = this.internalCloneLayers();
|
||||
const newLayers = this.getClonedLayers();
|
||||
|
||||
for (let layerIndex = 0; layerIndex < newLayers.length; ++layerIndex) {
|
||||
const quadrants = newLayers[layerIndex];
|
||||
@@ -603,7 +626,7 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
* @param {[enumColors, enumColors, enumColors, enumColors]} colors
|
||||
*/
|
||||
cloneAndPaintWith4Colors(colors) {
|
||||
const newLayers = this.internalCloneLayers();
|
||||
const newLayers = this.getClonedLayers();
|
||||
|
||||
for (let layerIndex = 0; layerIndex < newLayers.length; ++layerIndex) {
|
||||
const quadrants = newLayers[layerIndex];
|
||||
|
||||
@@ -11,6 +11,7 @@ import { arrayBeltVariantToRotation, MetaBeltBuilding } from "../buildings/belt"
|
||||
import { getCodeFromBuildingData } from "../building_codes";
|
||||
import { BeltComponent } from "../components/belt";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystem } from "../game_system";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { defaultBuildingVariant } from "../meta_building";
|
||||
@@ -22,9 +23,9 @@ const logger = createLogger("belt");
|
||||
/**
|
||||
* Manages all belts
|
||||
*/
|
||||
export class BeltSystem extends GameSystemWithFilter {
|
||||
export class BeltSystem extends GameSystem {
|
||||
constructor(root) {
|
||||
super(root, [BeltComponent]);
|
||||
super(root);
|
||||
/**
|
||||
* @type {Object.<enumDirection, Array<AtlasSprite>>}
|
||||
*/
|
||||
@@ -425,8 +426,10 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const beltEntities = this.root.entityMgr.getAllWithComponent(BeltComponent);
|
||||
|
||||
for (let i = 0; i < beltEntities.length; ++i) {
|
||||
const entity = beltEntities[i];
|
||||
if (visitedUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
@@ -494,6 +497,10 @@ export class BeltSystem extends GameSystemWithFilter {
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
if (G_IS_DEV && globalConfig.debug.doNotRenderStatics) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit speed to avoid belts going backwards
|
||||
const speedMultiplier = Math.min(this.root.hubGoals.getBeltBaseSpeed(), 10);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { BeltUnderlaysComponent, enumClippedBeltUnderlayType } from "../componen
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { GameSystem } from "../game_system";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { BELT_ANIM_COUNT } from "./belt";
|
||||
|
||||
@@ -31,9 +31,9 @@ const enumUnderlayTypeToClipRect = {
|
||||
[enumClippedBeltUnderlayType.bottomOnly]: new Rectangle(0, 0.5, 1, 0.5),
|
||||
};
|
||||
|
||||
export class BeltUnderlaysSystem extends GameSystemWithFilter {
|
||||
export class BeltUnderlaysSystem extends GameSystem {
|
||||
constructor(root) {
|
||||
super(root, [BeltUnderlaysComponent]);
|
||||
super(root);
|
||||
|
||||
this.underlayBeltSprites = [];
|
||||
|
||||
@@ -113,12 +113,10 @@ export class BeltUnderlaysSystem extends GameSystemWithFilter {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 2: Check if any of the directions matches
|
||||
for (let j = 0; j < slot.directions.length; ++j) {
|
||||
const slotDirection = staticComp.localDirectionToWorld(slot.directions[j]);
|
||||
if (slotDirection === fromDirection) {
|
||||
return true;
|
||||
}
|
||||
// Step 2: Check if the direction matches
|
||||
const slotDirection = staticComp.localDirectionToWorld(slot.direction);
|
||||
if (slotDirection === fromDirection) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { ItemProducerComponent } from "../components/item_producer";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { MapChunk } from "../map_chunk";
|
||||
import { GameRoot } from "../root";
|
||||
|
||||
export class ConstantProducerSystem extends GameSystemWithFilter {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root, [ConstantSignalComponent, ItemProducerComponent]);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import trim from "trim";
|
||||
import { THIRDPARTY_URLS } 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 { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
import { Entity } from "../entity";
|
||||
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";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
|
||||
export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [ConstantSignalComponent]);
|
||||
|
||||
this.root.signals.entityManuallyPlaced.add(entity =>
|
||||
this.editConstantSignal(entity, { deleteOnCancel: true })
|
||||
);
|
||||
this.root.signals.entityManuallyPlaced.add(entity => {
|
||||
const editorHud = this.root.hud.parts.constantSignalEdit;
|
||||
if (editorHud) {
|
||||
editorHud.editConstantSignal(entity, { deleteOnCancel: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
@@ -34,171 +25,4 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the entity to enter a valid signal code
|
||||
* @param {Entity} entity
|
||||
* @param {object} param0
|
||||
* @param {boolean=} param0.deleteOnCancel
|
||||
*/
|
||||
editConstantSignal(entity, { deleteOnCancel = true }) {
|
||||
if (!entity.components.ConstantSignal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, query, but also save the uid because it could get stale
|
||||
const uid = entity.uid;
|
||||
|
||||
const signal = entity.components.ConstantSignal.signal;
|
||||
const signalValueInput = new FormElementInput({
|
||||
id: "signalValue",
|
||||
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
|
||||
placeholder: "",
|
||||
defaultValue: signal ? signal.getAsCopyableKey() : "",
|
||||
validator: val => this.parseSignalCode(entity, val),
|
||||
});
|
||||
|
||||
const items = [...Object.values(COLOR_ITEM_SINGLETONS)];
|
||||
|
||||
if (entity.components.WiredPins) {
|
||||
items.unshift(BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON);
|
||||
items.push(
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(
|
||||
this.root.gameMode.getBlueprintShapeKey()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// producer which can produce virtually anything
|
||||
const shapes = ["CuCuCuCu", "RuRuRuRu", "WuWuWuWu", "SuSuSuSu"];
|
||||
items.unshift(
|
||||
...shapes.reverse().map(key => this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key))
|
||||
);
|
||||
}
|
||||
|
||||
if (this.root.gameMode.hasHub()) {
|
||||
items.push(
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
|
||||
this.root.hubGoals.currentGoal.definition
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.root.hud.parts.pinnedShapes) {
|
||||
items.push(
|
||||
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const itemInput = new FormElementItemChooser({
|
||||
id: "signalItem",
|
||||
label: null,
|
||||
items,
|
||||
});
|
||||
|
||||
const dialog = new DialogWithForm({
|
||||
app: this.root.app,
|
||||
title: T.dialogs.editConstantProducer.title,
|
||||
desc: T.dialogs.editSignal.descItems,
|
||||
formElements: [itemInput, signalValueInput],
|
||||
buttons: ["cancel:bad:escape", "ok:good:enter"],
|
||||
closeButton: false,
|
||||
});
|
||||
this.root.hud.parts.dialogs.internalShowDialog(dialog);
|
||||
|
||||
// When confirmed, set the signal
|
||||
const closeHandler = () => {
|
||||
if (!this.root || !this.root.entityMgr) {
|
||||
// Game got stopped
|
||||
return;
|
||||
}
|
||||
|
||||
const entityRef = this.root.entityMgr.findByUid(uid, false);
|
||||
if (!entityRef) {
|
||||
// outdated
|
||||
return;
|
||||
}
|
||||
|
||||
const constantComp = entityRef.components.ConstantSignal;
|
||||
if (!constantComp) {
|
||||
// no longer interesting
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemInput.chosenItem) {
|
||||
constantComp.signal = itemInput.chosenItem;
|
||||
} else {
|
||||
constantComp.signal = this.parseSignalCode(entity, signalValueInput.getValue());
|
||||
}
|
||||
};
|
||||
|
||||
dialog.buttonSignals.ok.add(() => {
|
||||
closeHandler();
|
||||
});
|
||||
dialog.valueChosen.add(() => {
|
||||
dialog.closeRequested.dispatch();
|
||||
closeHandler();
|
||||
});
|
||||
|
||||
// When cancelled, destroy the entity again
|
||||
if (deleteOnCancel) {
|
||||
dialog.buttonSignals.cancel.add(() => {
|
||||
if (!this.root || !this.root.entityMgr) {
|
||||
// Game got stopped
|
||||
return;
|
||||
}
|
||||
|
||||
const entityRef = this.root.entityMgr.findByUid(uid, false);
|
||||
if (!entityRef) {
|
||||
// outdated
|
||||
return;
|
||||
}
|
||||
|
||||
const constantComp = entityRef.components.ConstantSignal;
|
||||
if (!constantComp) {
|
||||
// no longer interesting
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.logic.tryDeleteBuilding(entityRef);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse a signal code
|
||||
* @param {Entity} entity
|
||||
* @param {string} code
|
||||
* @returns {BaseItem}
|
||||
*/
|
||||
parseSignalCode(entity, code) {
|
||||
if (!this.root || !this.root.shapeDefinitionMgr) {
|
||||
// Stale reference
|
||||
return null;
|
||||
}
|
||||
|
||||
code = trim(code);
|
||||
const codeLower = code.toLowerCase();
|
||||
|
||||
if (enumColors[codeLower]) {
|
||||
return COLOR_ITEM_SINGLETONS[codeLower];
|
||||
}
|
||||
|
||||
if (entity.components.WiredPins) {
|
||||
if (code === "1" || codeLower === "true") {
|
||||
return BOOL_TRUE_SINGLETON;
|
||||
}
|
||||
|
||||
if (code === "0" || codeLower === "false") {
|
||||
return BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
}
|
||||
|
||||
if (ShapeDefinition.isValidShortKey(code)) {
|
||||
return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,14 @@ import { globalConfig } from "../../core/config";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { DisplayComponent } from "../components/display";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { GameSystem } from "../game_system";
|
||||
import { isTrueItem } from "../items/boolean_item";
|
||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class DisplaySystem extends GameSystemWithFilter {
|
||||
export class DisplaySystem extends GameSystem {
|
||||
constructor(root) {
|
||||
super(root, [DisplayComponent]);
|
||||
super(root);
|
||||
|
||||
/** @type {Object<string, import("../../core/draw_utils").AtlasSprite>} */
|
||||
this.displaySprites = {};
|
||||
|
||||
@@ -5,10 +5,8 @@ import { Vector } from "../../core/vector";
|
||||
import { GoalAcceptorComponent } from "../components/goal_acceptor";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { MapChunk } from "../map_chunk";
|
||||
import { GameRoot } from "../root";
|
||||
|
||||
export class GoalAcceptorSystem extends GameSystemWithFilter {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root, [GoalAcceptorComponent]);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createLogger } from "../../core/logging";
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { StaleAreaDetector } from "../../core/stale_area_detector";
|
||||
import { enumDirection, enumDirectionToVector } from "../../core/vector";
|
||||
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { BeltComponent } from "../components/belt";
|
||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||
@@ -204,11 +203,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
|
||||
if (this.tryPassOverItem(item, destEntity, destSlot.index)) {
|
||||
// Handover successful, clear slot
|
||||
if (!this.root.app.settings.getAllSettings().simplifiedBelts) {
|
||||
targetAcceptorComp.onItemAccepted(
|
||||
destSlot.index,
|
||||
destSlot.acceptedDirection,
|
||||
item
|
||||
);
|
||||
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.slot.direction, item);
|
||||
}
|
||||
sourceSlot.item = null;
|
||||
continue;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "../components/item_processor";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BOOL_TRUE_SINGLETON, isTruthyItem } from "../items/boolean_item";
|
||||
import { isTruthyItem } from "../items/boolean_item";
|
||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { ShapeItem } from "../items/shape_item";
|
||||
|
||||
@@ -38,6 +38,11 @@ const MAX_QUEUED_CHARGES = 2;
|
||||
* }} ProcessorImplementationPayload
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Object<string, (ProcessorImplementationPayload) => void>}
|
||||
*/
|
||||
export const MOD_ITEM_PROCESSOR_HANDLERS = {};
|
||||
|
||||
export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [ItemProcessorComponent]);
|
||||
@@ -61,6 +66,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
[enumItemProcessorTypes.hub]: this.process_HUB,
|
||||
[enumItemProcessorTypes.reader]: this.process_READER,
|
||||
[enumItemProcessorTypes.goal]: this.process_GOAL,
|
||||
...MOD_ITEM_PROCESSOR_HANDLERS,
|
||||
};
|
||||
|
||||
// Bind all handlers
|
||||
@@ -88,49 +94,16 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it finished
|
||||
if (currentCharge.remainingTime <= 0.0) {
|
||||
// Check if it finished and we don't already have queued ejects
|
||||
if (currentCharge.remainingTime <= 0.0 && !processorComp.queuedEjects.length) {
|
||||
const itemsToEject = currentCharge.items;
|
||||
|
||||
// Go over all items and try to eject them
|
||||
// Go over all items and add them to the queue
|
||||
for (let j = 0; j < itemsToEject.length; ++j) {
|
||||
const { item, requiredSlot, preferredSlot } = itemsToEject[j];
|
||||
|
||||
assert(ejectorComp, "To eject items, the building needs to have an ejector");
|
||||
|
||||
let slot = null;
|
||||
if (requiredSlot !== null && requiredSlot !== undefined) {
|
||||
// We have a slot override, check if that is free
|
||||
if (ejectorComp.canEjectOnSlot(requiredSlot)) {
|
||||
slot = requiredSlot;
|
||||
}
|
||||
} else if (preferredSlot !== null && preferredSlot !== undefined) {
|
||||
// We have a slot preference, try using it but otherwise use a free slot
|
||||
if (ejectorComp.canEjectOnSlot(preferredSlot)) {
|
||||
slot = preferredSlot;
|
||||
} else {
|
||||
slot = ejectorComp.getFirstFreeSlot();
|
||||
}
|
||||
} else {
|
||||
// We can eject on any slot
|
||||
slot = ejectorComp.getFirstFreeSlot();
|
||||
}
|
||||
|
||||
if (slot !== null) {
|
||||
// Alright, we can actually eject
|
||||
if (!ejectorComp.tryEject(slot, item)) {
|
||||
assert(false, "Failed to eject");
|
||||
} else {
|
||||
itemsToEject.splice(j, 1);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
processorComp.queuedEjects.push(itemsToEject[j]);
|
||||
}
|
||||
|
||||
// If the charge was entirely emptied to the outputs, start the next charge
|
||||
if (itemsToEject.length === 0) {
|
||||
processorComp.ongoingCharges.shift();
|
||||
}
|
||||
processorComp.ongoingCharges.shift();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +113,40 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
||||
this.startNewCharge(entity);
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < processorComp.queuedEjects.length; ++j) {
|
||||
const { item, requiredSlot, preferredSlot } = processorComp.queuedEjects[j];
|
||||
|
||||
assert(ejectorComp, "To eject items, the building needs to have an ejector");
|
||||
|
||||
let slot = null;
|
||||
if (requiredSlot !== null && requiredSlot !== undefined) {
|
||||
// We have a slot override, check if that is free
|
||||
if (ejectorComp.canEjectOnSlot(requiredSlot)) {
|
||||
slot = requiredSlot;
|
||||
}
|
||||
} else if (preferredSlot !== null && preferredSlot !== undefined) {
|
||||
// We have a slot preference, try using it but otherwise use a free slot
|
||||
if (ejectorComp.canEjectOnSlot(preferredSlot)) {
|
||||
slot = preferredSlot;
|
||||
} else {
|
||||
slot = ejectorComp.getFirstFreeSlot();
|
||||
}
|
||||
} else {
|
||||
// We can eject on any slot
|
||||
slot = ejectorComp.getFirstFreeSlot();
|
||||
}
|
||||
|
||||
if (slot !== null) {
|
||||
// Alright, we can actually eject
|
||||
if (!ejectorComp.tryEject(slot, item)) {
|
||||
assert(false, "Failed to eject");
|
||||
} else {
|
||||
processorComp.queuedEjects.splice(j, 1);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/* typehints:start */
|
||||
import { GameRoot } from "../root";
|
||||
/* typehints:end */
|
||||
|
||||
import { ItemProducerComponent } from "../components/item_producer";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
|
||||
export class ItemProducerSystem extends GameSystemWithFilter {
|
||||
/** @param {GameRoot} root */
|
||||
constructor(root) {
|
||||
super(root, [ItemProducerComponent]);
|
||||
this.item = null;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { LeverComponent } from "../components/lever";
|
||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { LeverComponent } from "../components/lever";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class LeverSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { StorageComponent } from "../components/storage";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { formatBigNumber, lerp } from "../../core/utils";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
||||
import { formatBigNumber, lerp } from "../../core/utils";
|
||||
import { StorageComponent } from "../components/storage";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class StorageSystem extends GameSystemWithFilter {
|
||||
@@ -50,8 +50,13 @@ export class StorageSystem extends GameSystemWithFilter {
|
||||
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
|
||||
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
|
||||
|
||||
pinsComp.slots[0].value = storageComp.storedItem;
|
||||
pinsComp.slots[1].value = storageComp.getIsFull() ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||
// a wired pins component is not guaranteed, but if its there, set the value
|
||||
if (pinsComp) {
|
||||
pinsComp.slots[0].value = storageComp.storedItem;
|
||||
pinsComp.slots[1].value = storageComp.getIsFull()
|
||||
? BOOL_TRUE_SINGLETON
|
||||
: BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { enumWireType, enumWireVariant, WireComponent } from "../components/wire
|
||||
import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
|
||||
import { WireTunnelComponent } from "../components/wire_tunnel";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystem } from "../game_system";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { isTruthyItem } from "../items/boolean_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
@@ -90,9 +91,9 @@ export class WireNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
export class WireSystem extends GameSystemWithFilter {
|
||||
export class WireSystem extends GameSystem {
|
||||
constructor(root) {
|
||||
super(root, [WireComponent]);
|
||||
super(root);
|
||||
|
||||
/**
|
||||
* @type {Object<enumWireVariant, Object<enumWireType, AtlasSprite>>}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"uiStyle": "dark",
|
||||
"map": {
|
||||
"background": "#3e3f47",
|
||||
"grid": "rgba(255, 255, 255, 0.02)",
|
||||
@@ -19,6 +18,10 @@
|
||||
"wires": {
|
||||
"color": "rgb(74, 237, 134)",
|
||||
"background": "rgba(74, 237, 134, 0.2)"
|
||||
},
|
||||
"error": {
|
||||
"color": "rgb(255, 137, 137)",
|
||||
"background": "rgba(255, 137, 137, 0.2)"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"uiStyle": "light",
|
||||
"map": {
|
||||
"background": "#fff",
|
||||
"grid": "#fafafa",
|
||||
@@ -19,6 +18,10 @@
|
||||
"wires": {
|
||||
"color": "rgb(74, 237, 134)",
|
||||
"background": "rgba(74, 237, 134, 0.2)"
|
||||
},
|
||||
"error": {
|
||||
"color": "rgb(255, 137, 137)",
|
||||
"background": "rgba(255, 137, 137, 0.2)"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
8
src/js/globals.d.ts
vendored
8
src/js/globals.d.ts
vendored
@@ -22,6 +22,10 @@ declare const G_IS_RELEASE: boolean;
|
||||
declare const G_CHINA_VERSION: boolean;
|
||||
declare const G_WEGAME_VERSION: boolean;
|
||||
|
||||
declare const shapez: any;
|
||||
|
||||
declare const ipcRenderer: any;
|
||||
|
||||
// Polyfills
|
||||
declare interface String {
|
||||
replaceAll(search: string, replacement: string): string;
|
||||
@@ -92,9 +96,11 @@ declare interface Window {
|
||||
cpmstarAPI: any;
|
||||
|
||||
// Mods
|
||||
registerMod: any;
|
||||
$shapez_registerMod: any;
|
||||
anyModLoaded: any;
|
||||
|
||||
shapez: any;
|
||||
|
||||
webkitRequestAnimationFrame();
|
||||
|
||||
assert(condition: boolean, failureMessage: string);
|
||||
|
||||
@@ -2,6 +2,8 @@ import "./core/polyfills";
|
||||
import "./core/assert";
|
||||
import "./core/error_handler";
|
||||
|
||||
import "./mods/modloader";
|
||||
|
||||
import { createLogger, logSection } from "./core/logging";
|
||||
import { Application } from "./application";
|
||||
import { IS_DEBUG } from "./core/config";
|
||||
@@ -19,39 +21,8 @@ if (window.coreThreadLoadedCb) {
|
||||
window.coreThreadLoadedCb();
|
||||
}
|
||||
|
||||
// Logrocket
|
||||
// if (!G_IS_DEV && !G_IS_STANDALONE) {
|
||||
// const monthlyUsers = 300; // thousand
|
||||
// const logrocketLimit = 10; // thousand
|
||||
// const percentageOfUsers = logrocketLimit / monthlyUsers;
|
||||
|
||||
// if (Math.random() <= percentageOfUsers) {
|
||||
// logger.log("Analyzing this session with logrocket");
|
||||
// const logrocket = require("logrocket");
|
||||
// logrocket.init("p1x9zh/shapezio");
|
||||
|
||||
// try {
|
||||
// logrocket.getSessionURL(function (sessionURL) {
|
||||
// logger.log("Connected lockrocket to GA");
|
||||
// // @ts-ignore
|
||||
// try {
|
||||
// window.ga("send", {
|
||||
// hitType: "event",
|
||||
// eventCategory: "LogRocket",
|
||||
// eventAction: sessionURL,
|
||||
// });
|
||||
// } catch (ex) {
|
||||
// logger.warn("Logrocket connection to analytics failed:", ex);
|
||||
// }
|
||||
// });
|
||||
// } catch (ex) {
|
||||
// logger.warn("Logrocket connection to analytics failed:", ex);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log(
|
||||
`%cshapez.io ️%c\n© 2020 Tobias Springer IT Solutions\nCommit %c${G_BUILD_COMMIT_HASH}%c on %c${new Date(
|
||||
`%cshapez.io ️%c\n© 2022 tobspr Games\nCommit %c${G_BUILD_COMMIT_HASH}%c on %c${new Date(
|
||||
G_BUILD_TIME
|
||||
).toLocaleString()}\n`,
|
||||
"font-size: 35px; font-family: Arial;font-weight: bold; padding: 10px 0;",
|
||||
|
||||
36
src/js/mods/mod.js
Normal file
36
src/js/mods/mod.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
import { ModLoader } from "./modloader";
|
||||
/* typehints:end */
|
||||
|
||||
import { MOD_SIGNALS } from "./mod_signals";
|
||||
|
||||
export class Mod {
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {Application} param0.app
|
||||
* @param {ModLoader} param0.modLoader
|
||||
* @param {import("./modloader").ModMetadata} param0.meta
|
||||
* @param {Object} param0.settings
|
||||
* @param {() => Promise<void>} param0.saveSettings
|
||||
*/
|
||||
constructor({ app, modLoader, meta, settings, saveSettings }) {
|
||||
this.app = app;
|
||||
this.modLoader = modLoader;
|
||||
this.metadata = meta;
|
||||
|
||||
this.signals = MOD_SIGNALS;
|
||||
this.modInterface = modLoader.modInterface;
|
||||
|
||||
this.settings = settings;
|
||||
this.saveSettings = saveSettings;
|
||||
}
|
||||
|
||||
init() {
|
||||
// to be overridden
|
||||
}
|
||||
|
||||
get dialogs() {
|
||||
return this.modInterface.dialogs;
|
||||
}
|
||||
}
|
||||
658
src/js/mods/mod_interface.js
Normal file
658
src/js/mods/mod_interface.js
Normal file
@@ -0,0 +1,658 @@
|
||||
/* typehints:start */
|
||||
import { ModLoader } from "./modloader";
|
||||
import { GameSystem } from "../game/game_system";
|
||||
import { Component } from "../game/component";
|
||||
import { MetaBuilding } from "../game/meta_building";
|
||||
/* typehints:end */
|
||||
|
||||
import { defaultBuildingVariant } from "../game/meta_building";
|
||||
import { AtlasSprite, SpriteAtlasLink } from "../core/sprites";
|
||||
import {
|
||||
enumShortcodeToSubShape,
|
||||
enumSubShape,
|
||||
enumSubShapeToShortcode,
|
||||
MODS_ADDITIONAL_SUB_SHAPE_DRAWERS,
|
||||
} from "../game/shape_definition";
|
||||
import { Loader } from "../core/loader";
|
||||
import { LANGUAGES } from "../languages";
|
||||
import { matchDataRecursive, T } from "../translations";
|
||||
import { gBuildingVariants, registerBuildingVariant } from "../game/building_codes";
|
||||
import { gComponentRegistry, gMetaBuildingRegistry } from "../core/global_registries";
|
||||
import { MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS } from "../game/map_chunk";
|
||||
import { MODS_ADDITIONAL_SYSTEMS } from "../game/game_system_manager";
|
||||
import { MOD_CHUNK_DRAW_HOOKS } from "../game/map_chunk_view";
|
||||
import { KEYMAPPINGS } from "../game/key_action_mapper";
|
||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||
import { THEMES } from "../game/theme";
|
||||
import { ModMetaBuilding } from "./mod_meta_building";
|
||||
import { BaseHUDPart } from "../game/hud/base_hud_part";
|
||||
import { Vector } from "../core/vector";
|
||||
import { GameRoot } from "../game/root";
|
||||
|
||||
/**
|
||||
* @typedef {{new(...args: any[]): any, prototype: any}} constructable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {(...args: any) => any} F The function
|
||||
* @template {object} T The value of this
|
||||
* @typedef {(this: T, ...args: Parameters<F>) => ReturnType<F>} bindThis
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {(...args: any[]) => any} F
|
||||
* @template P
|
||||
* @typedef {(...args: [P, Parameters<F>]) => ReturnType<F>} beforePrams IMPORTANT: this puts the original parameters into an array
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {(...args: any[]) => any} F
|
||||
* @template P
|
||||
* @typedef {(...args: [...Parameters<F>, P]) => ReturnType<F>} afterPrams
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {(...args: any[]) => any} F
|
||||
* @typedef {(...args: [...Parameters<F>, ...any]) => ReturnType<F>} extendsPrams
|
||||
*/
|
||||
|
||||
export class ModInterface {
|
||||
/**
|
||||
*
|
||||
* @param {ModLoader} modLoader
|
||||
*/
|
||||
constructor(modLoader) {
|
||||
this.modLoader = modLoader;
|
||||
}
|
||||
|
||||
registerCss(cssString) {
|
||||
// Preprocess css
|
||||
cssString = cssString.replace(/\$scaled\(([^)]*)\)/gim, (substr, expression) => {
|
||||
return "calc((" + expression + ") * var(--ui-scale))";
|
||||
});
|
||||
const element = document.createElement("style");
|
||||
element.textContent = cssString;
|
||||
document.head.appendChild(element);
|
||||
}
|
||||
|
||||
registerSprite(spriteId, base64string) {
|
||||
assert(base64string.startsWith("data:image"));
|
||||
const img = new Image();
|
||||
|
||||
const sprite = new AtlasSprite(spriteId);
|
||||
sprite.frozen = true;
|
||||
|
||||
img.addEventListener("load", () => {
|
||||
for (const resolution in sprite.linksByResolution) {
|
||||
const link = sprite.linksByResolution[resolution];
|
||||
link.w = img.width;
|
||||
link.h = img.height;
|
||||
link.packedW = img.width;
|
||||
link.packedH = img.height;
|
||||
}
|
||||
});
|
||||
|
||||
img.src = base64string;
|
||||
|
||||
const link = new SpriteAtlasLink({
|
||||
w: 1,
|
||||
h: 1,
|
||||
atlas: img,
|
||||
packOffsetX: 0,
|
||||
packOffsetY: 0,
|
||||
packedW: 1,
|
||||
packedH: 1,
|
||||
packedX: 0,
|
||||
packedY: 0,
|
||||
});
|
||||
|
||||
sprite.linksByResolution["0.25"] = link;
|
||||
sprite.linksByResolution["0.5"] = link;
|
||||
sprite.linksByResolution["0.75"] = link;
|
||||
|
||||
Loader.sprites.set(spriteId, sprite);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} imageBase64
|
||||
* @param {string} jsonTextData
|
||||
*/
|
||||
registerAtlas(imageBase64, jsonTextData) {
|
||||
const atlasData = JSON.parse(jsonTextData);
|
||||
const img = new Image();
|
||||
img.src = imageBase64;
|
||||
|
||||
const sourceData = atlasData.frames;
|
||||
for (const spriteName in sourceData) {
|
||||
const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName];
|
||||
|
||||
const sprite = new AtlasSprite(spriteName);
|
||||
Loader.sprites.set(spriteName, sprite);
|
||||
sprite.frozen = true;
|
||||
|
||||
const link = new SpriteAtlasLink({
|
||||
packedX: frame.x,
|
||||
packedY: frame.y,
|
||||
packedW: frame.w,
|
||||
packedH: frame.h,
|
||||
packOffsetX: spriteSourceSize.x,
|
||||
packOffsetY: spriteSourceSize.y,
|
||||
atlas: img,
|
||||
w: sourceSize.w,
|
||||
h: sourceSize.h,
|
||||
});
|
||||
sprite.linksByResolution["0.25"] = link;
|
||||
sprite.linksByResolution["0.5"] = link;
|
||||
sprite.linksByResolution["0.75"] = link;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {string} param0.id
|
||||
* @param {string} param0.shortCode
|
||||
* @param {(distanceToOriginInChunks: number) => number} param0.weightComputation
|
||||
* @param {(options: import("../game/shape_definition").SubShapeDrawOptions) => void} param0.draw
|
||||
*/
|
||||
registerSubShapeType({ id, shortCode, weightComputation, draw }) {
|
||||
if (shortCode.length !== 1) {
|
||||
throw new Error("Bad short code: " + shortCode);
|
||||
}
|
||||
enumSubShape[id] = id;
|
||||
enumSubShapeToShortcode[id] = shortCode;
|
||||
enumShortcodeToSubShape[shortCode] = id;
|
||||
|
||||
MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS[id] = weightComputation;
|
||||
MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[id] = draw;
|
||||
}
|
||||
|
||||
registerTranslations(language, translations) {
|
||||
const data = LANGUAGES[language];
|
||||
if (!data) {
|
||||
throw new Error("Unknown language: " + language);
|
||||
}
|
||||
|
||||
matchDataRecursive(data.data, translations, true);
|
||||
if (language === "en") {
|
||||
matchDataRecursive(T, translations, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {typeof Component} component
|
||||
*/
|
||||
registerComponent(component) {
|
||||
gComponentRegistry.register(component);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.id
|
||||
* @param {new (any) => GameSystem} param0.systemClass
|
||||
* @param {string=} param0.before
|
||||
* @param {string[]=} param0.drawHooks
|
||||
*/
|
||||
registerGameSystem({ id, systemClass, before, drawHooks }) {
|
||||
const key = before || "key";
|
||||
const payload = { id, systemClass };
|
||||
|
||||
if (MODS_ADDITIONAL_SYSTEMS[key]) {
|
||||
MODS_ADDITIONAL_SYSTEMS[key].push(payload);
|
||||
} else {
|
||||
MODS_ADDITIONAL_SYSTEMS[key] = [payload];
|
||||
}
|
||||
if (drawHooks) {
|
||||
drawHooks.forEach(hookId => this.registerGameSystemDrawHook(hookId, id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} hookId
|
||||
* @param {string} systemId
|
||||
*/
|
||||
registerGameSystemDrawHook(hookId, systemId) {
|
||||
if (!MOD_CHUNK_DRAW_HOOKS[hookId]) {
|
||||
throw new Error("bad game system draw hook: " + hookId);
|
||||
}
|
||||
MOD_CHUNK_DRAW_HOOKS[hookId].push(systemId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {typeof ModMetaBuilding} param0.metaClass
|
||||
* @param {string=} param0.buildingIconBase64
|
||||
*/
|
||||
registerNewBuilding({ metaClass, buildingIconBase64 }) {
|
||||
const id = new /** @type {new (...args) => ModMetaBuilding} */ (metaClass)().getId();
|
||||
if (gMetaBuildingRegistry.hasId(id)) {
|
||||
throw new Error("Tried to register building twice: " + id);
|
||||
}
|
||||
gMetaBuildingRegistry.register(metaClass);
|
||||
const metaInstance = gMetaBuildingRegistry.findByClass(metaClass);
|
||||
T.buildings[id] = {};
|
||||
|
||||
metaClass.getAllVariantCombinations().forEach(combination => {
|
||||
const variant = combination.variant || defaultBuildingVariant;
|
||||
const rotationVariant = combination.rotationVariant || 0;
|
||||
|
||||
const buildingIdentifier = id + (variant === defaultBuildingVariant ? "" : "-" + variant);
|
||||
|
||||
const uniqueTypeId = buildingIdentifier + (rotationVariant === 0 ? "" : "-" + rotationVariant);
|
||||
registerBuildingVariant(uniqueTypeId, metaClass, variant, rotationVariant);
|
||||
|
||||
gBuildingVariants[id].metaInstance = metaInstance;
|
||||
|
||||
this.registerTranslations("en", {
|
||||
buildings: {
|
||||
[id]: {
|
||||
[variant]: {
|
||||
name: combination.name || "Name",
|
||||
description: combination.description || "Description",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (combination.regularImageBase64) {
|
||||
this.registerSprite(
|
||||
"sprites/buildings/" + buildingIdentifier + ".png",
|
||||
combination.regularImageBase64
|
||||
);
|
||||
}
|
||||
|
||||
if (combination.blueprintImageBase64) {
|
||||
this.registerSprite(
|
||||
"sprites/blueprints/" + buildingIdentifier + ".png",
|
||||
combination.blueprintImageBase64
|
||||
);
|
||||
}
|
||||
if (combination.tutorialImageBase64) {
|
||||
this.setBuildingTutorialImage(id, variant, combination.tutorialImageBase64);
|
||||
}
|
||||
});
|
||||
|
||||
if (buildingIconBase64) {
|
||||
this.setBuildingToolbarIcon(id, buildingIconBase64);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.id
|
||||
* @param {number} param0.keyCode
|
||||
* @param {string} param0.translation
|
||||
* @param {boolean=} param0.repeated
|
||||
* @param {((GameRoot) => void)=} param0.handler
|
||||
* @param {{shift?: boolean; alt?: boolean; ctrl?: boolean}=} param0.modifiers
|
||||
* @param {boolean=} param0.builtin
|
||||
*/
|
||||
registerIngameKeybinding({
|
||||
id,
|
||||
keyCode,
|
||||
translation,
|
||||
modifiers = {},
|
||||
repeated = false,
|
||||
builtin = false,
|
||||
handler = null,
|
||||
}) {
|
||||
if (!KEYMAPPINGS.mods) {
|
||||
KEYMAPPINGS.mods = {};
|
||||
}
|
||||
const binding = (KEYMAPPINGS.mods[id] = {
|
||||
keyCode,
|
||||
id,
|
||||
repeated,
|
||||
modifiers,
|
||||
builtin,
|
||||
});
|
||||
this.registerTranslations("en", {
|
||||
keybindings: {
|
||||
mappings: {
|
||||
[id]: translation,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (handler) {
|
||||
this.modLoader.signals.gameStarted.add(root => {
|
||||
root.keyMapper.getBindingById(id).addToTop(handler.bind(null, root));
|
||||
});
|
||||
}
|
||||
|
||||
return binding;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {HUDModalDialogs}
|
||||
*/
|
||||
get dialogs() {
|
||||
const state = this.modLoader.app.stateMgr.currentState;
|
||||
// @ts-ignore
|
||||
if (state.dialogs) {
|
||||
// @ts-ignore
|
||||
return state.dialogs;
|
||||
}
|
||||
throw new Error("Tried to access dialogs but current state doesn't support it");
|
||||
}
|
||||
|
||||
setBuildingToolbarIcon(buildingId, iconBase64) {
|
||||
this.registerCss(`
|
||||
[data-icon="building_icons/${buildingId}.png"] .icon {
|
||||
background-image: url('${iconBase64}') !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string | (new () => MetaBuilding)} buildingIdOrClass
|
||||
* @param {*} variant
|
||||
* @param {*} imageBase64
|
||||
*/
|
||||
setBuildingTutorialImage(buildingIdOrClass, variant, imageBase64) {
|
||||
if (typeof buildingIdOrClass === "function") {
|
||||
buildingIdOrClass = new buildingIdOrClass().id;
|
||||
}
|
||||
const buildingIdentifier =
|
||||
buildingIdOrClass + (variant === defaultBuildingVariant ? "" : "-" + variant);
|
||||
|
||||
this.registerCss(`
|
||||
[data-icon="building_tutorials/${buildingIdentifier}.png"] {
|
||||
background-image: url('${imageBase64}') !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.id
|
||||
* @param {string} param0.name
|
||||
* @param {Object} param0.theme
|
||||
*/
|
||||
registerGameTheme({ id, name, theme }) {
|
||||
THEMES[id] = theme;
|
||||
this.registerTranslations("en", {
|
||||
settings: {
|
||||
labels: {
|
||||
theme: {
|
||||
themes: {
|
||||
[id]: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new state class, should be a GameState derived class
|
||||
* @param {typeof import("../core/game_state").GameState} stateClass
|
||||
*/
|
||||
registerGameState(stateClass) {
|
||||
this.modLoader.app.stateMgr.register(stateClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} param0
|
||||
* @param {"regular"|"wires"} param0.toolbar
|
||||
* @param {"primary"|"secondary"} param0.location
|
||||
* @param {typeof MetaBuilding} param0.metaClass
|
||||
*/
|
||||
addNewBuildingToToolbar({ toolbar, location, metaClass }) {
|
||||
const hudElementName = toolbar === "wires" ? "HUDWiresToolbar" : "HUDBuildingsToolbar";
|
||||
const property = location === "secondary" ? "secondaryBuildings" : "primaryBuildings";
|
||||
|
||||
this.modLoader.signals.hudElementInitialized.add(element => {
|
||||
if (element.constructor.name === hudElementName) {
|
||||
element[property].push(metaClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches a method on a given class
|
||||
* @template {constructable} C the class
|
||||
* @template {C["prototype"]} P the prototype of said class
|
||||
* @template {keyof P} M the name of the method we are overriding
|
||||
* @template {extendsPrams<P[M]>} O the method that will override the old one
|
||||
* @param {C} classHandle
|
||||
* @param {M} methodName
|
||||
* @param {bindThis<beforePrams<O, P[M]>, InstanceType<C>>} override
|
||||
*/
|
||||
replaceMethod(classHandle, methodName, override) {
|
||||
const oldMethod = classHandle.prototype[methodName];
|
||||
classHandle.prototype[methodName] = function () {
|
||||
//@ts-ignore This is true I just cant tell it that arguments will be Arguments<O>
|
||||
return override.call(this, oldMethod.bind(this), arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs before a method on a given class
|
||||
* @template {constructable} C the class
|
||||
* @template {C["prototype"]} P the prototype of said class
|
||||
* @template {keyof P} M the name of the method we are overriding
|
||||
* @template {extendsPrams<P[M]>} O the method that will run before the old one
|
||||
* @param {C} classHandle
|
||||
* @param {M} methodName
|
||||
* @param {bindThis<O, InstanceType<C>>} executeBefore
|
||||
*/
|
||||
runBeforeMethod(classHandle, methodName, executeBefore) {
|
||||
const oldHandle = classHandle.prototype[methodName];
|
||||
classHandle.prototype[methodName] = function () {
|
||||
//@ts-ignore Same as above
|
||||
executeBefore.apply(this, arguments);
|
||||
return oldHandle.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after a method on a given class
|
||||
* @template {constructable} C the class
|
||||
* @template {C["prototype"]} P the prototype of said class
|
||||
* @template {keyof P} M the name of the method we are overriding
|
||||
* @template {extendsPrams<P[M]>} O the method that will run before the old one
|
||||
* @param {C} classHandle
|
||||
* @param {M} methodName
|
||||
* @param {bindThis<O, InstanceType<C>>} executeAfter
|
||||
*/
|
||||
runAfterMethod(classHandle, methodName, executeAfter) {
|
||||
const oldHandle = classHandle.prototype[methodName];
|
||||
classHandle.prototype[methodName] = function () {
|
||||
const returnValue = oldHandle.apply(this, arguments);
|
||||
//@ts-ignore
|
||||
executeAfter.apply(this, arguments);
|
||||
return returnValue;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} prototype
|
||||
* @param {({ $super, $old }) => any} extender
|
||||
*/
|
||||
extendObject(prototype, extender) {
|
||||
const $super = Object.getPrototypeOf(prototype);
|
||||
const $old = {};
|
||||
const extensionMethods = extender({ $super, $old });
|
||||
const properties = Array.from(Object.getOwnPropertyNames(extensionMethods));
|
||||
properties.forEach(propertyName => {
|
||||
if (["constructor", "prototype"].includes(propertyName)) {
|
||||
return;
|
||||
}
|
||||
$old[propertyName] = prototype[propertyName];
|
||||
prototype[propertyName] = extensionMethods[propertyName];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Class} classHandle
|
||||
* @param {({ $super, $old }) => any} extender
|
||||
*/
|
||||
extendClass(classHandle, extender) {
|
||||
this.extendObject(classHandle.prototype, extender);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {new (...args) => BaseHUDPart} element
|
||||
*/
|
||||
registerHudElement(id, element) {
|
||||
this.modLoader.signals.hudInitializer.add(root => {
|
||||
root.hud.parts[id] = new element(root);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string | (new () => MetaBuilding)} buildingIdOrClass
|
||||
* @param {string} variant
|
||||
* @param {object} param0
|
||||
* @param {string} param0.name
|
||||
* @param {string} param0.description
|
||||
* @param {string=} param0.language
|
||||
*/
|
||||
registerBuildingTranslation(buildingIdOrClass, variant, { name, description, language = "en" }) {
|
||||
if (typeof buildingIdOrClass === "function") {
|
||||
buildingIdOrClass = new buildingIdOrClass().id;
|
||||
}
|
||||
this.registerTranslations(language, {
|
||||
buildings: {
|
||||
[buildingIdOrClass]: {
|
||||
[variant]: {
|
||||
name,
|
||||
description,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string | (new () => MetaBuilding)} buildingIdOrClass
|
||||
* @param {string} variant
|
||||
* @param {object} param2
|
||||
* @param {string=} param2.regularBase64
|
||||
* @param {string=} param2.blueprintBase64
|
||||
*/
|
||||
registerBuildingSprites(buildingIdOrClass, variant, { regularBase64, blueprintBase64 }) {
|
||||
if (typeof buildingIdOrClass === "function") {
|
||||
buildingIdOrClass = new buildingIdOrClass().id;
|
||||
}
|
||||
|
||||
const spriteId =
|
||||
buildingIdOrClass + (variant === defaultBuildingVariant ? "" : "-" + variant) + ".png";
|
||||
|
||||
if (regularBase64) {
|
||||
this.registerSprite("sprites/buildings/" + spriteId, regularBase64);
|
||||
}
|
||||
|
||||
if (blueprintBase64) {
|
||||
this.registerSprite("sprites/blueprints/" + spriteId, blueprintBase64);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {new () => MetaBuilding} metaClass
|
||||
* @param {string} variant
|
||||
* @param {object} payload
|
||||
* @param {number[]=} payload.rotationVariants
|
||||
* @param {string=} payload.tutorialImageBase64
|
||||
* @param {string=} payload.regularSpriteBase64
|
||||
* @param {string=} payload.blueprintSpriteBase64
|
||||
* @param {string=} payload.name
|
||||
* @param {string=} payload.description
|
||||
* @param {Vector=} payload.dimensions
|
||||
* @param {(root: GameRoot) => [string, string][]} payload.additionalStatistics
|
||||
* @param {(root: GameRoot) => boolean[]} payload.isUnlocked
|
||||
*/
|
||||
addVariantToExistingBuilding(metaClass, variant, payload) {
|
||||
if (!payload.rotationVariants) {
|
||||
payload.rotationVariants = [0];
|
||||
}
|
||||
|
||||
if (payload.tutorialImageBase64) {
|
||||
this.setBuildingTutorialImage(metaClass, variant, payload.tutorialImageBase64);
|
||||
}
|
||||
if (payload.regularSpriteBase64) {
|
||||
this.registerBuildingSprites(metaClass, variant, { regularBase64: payload.regularSpriteBase64 });
|
||||
}
|
||||
if (payload.blueprintSpriteBase64) {
|
||||
this.registerBuildingSprites(metaClass, variant, {
|
||||
blueprintBase64: payload.blueprintSpriteBase64,
|
||||
});
|
||||
}
|
||||
if (payload.name && payload.description) {
|
||||
this.registerBuildingTranslation(metaClass, variant, {
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
});
|
||||
}
|
||||
|
||||
const internalId = new metaClass().getId() + "-" + variant;
|
||||
|
||||
// Extend static methods
|
||||
this.extendObject(metaClass, ({ $old }) => ({
|
||||
getAllVariantCombinations() {
|
||||
return [
|
||||
...$old.bind(this).getAllVariantCombinations(),
|
||||
...payload.rotationVariants.map(rotationVariant => ({
|
||||
internalId,
|
||||
variant,
|
||||
rotationVariant,
|
||||
})),
|
||||
];
|
||||
},
|
||||
}));
|
||||
|
||||
// Dimensions
|
||||
const $variant = variant;
|
||||
if (payload.dimensions) {
|
||||
this.extendClass(metaClass, ({ $old }) => ({
|
||||
getDimensions(variant) {
|
||||
if (variant === $variant) {
|
||||
return payload.dimensions;
|
||||
}
|
||||
return $old.getDimensions.bind(this)(...arguments);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if (payload.additionalStatistics) {
|
||||
this.extendClass(metaClass, ({ $old }) => ({
|
||||
getAdditionalStatistics(root, variant) {
|
||||
if (variant === $variant) {
|
||||
return payload.additionalStatistics(root);
|
||||
}
|
||||
return $old.getAdditionalStatistics.bind(this)(root, variant);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if (payload.isUnlocked) {
|
||||
this.extendClass(metaClass, ({ $old }) => ({
|
||||
getAvailableVariants(root) {
|
||||
if (payload.isUnlocked(root)) {
|
||||
return [...$old.getAvailableVariants.bind(this)(root), $variant];
|
||||
}
|
||||
return $old.getAvailableVariants.bind(this)(root);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// Register our variant finally
|
||||
payload.rotationVariants.forEach(rotationVariant =>
|
||||
shapez.registerBuildingVariant(internalId, metaClass, variant, rotationVariant)
|
||||
);
|
||||
}
|
||||
}
|
||||
18
src/js/mods/mod_meta_building.js
Normal file
18
src/js/mods/mod_meta_building.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { MetaBuilding } from "../game/meta_building";
|
||||
|
||||
export class ModMetaBuilding extends MetaBuilding {
|
||||
/**
|
||||
* @returns {({
|
||||
* variant: string;
|
||||
* rotationVariant?: number;
|
||||
* name: string;
|
||||
* description: string;
|
||||
* blueprintImageBase64?: string;
|
||||
* regularImageBase64?: string;
|
||||
* tutorialImageBase64?: string;
|
||||
* }[])}
|
||||
*/
|
||||
static getAllVariantCombinations() {
|
||||
throw new Error("Implement getAllVariantCombinations");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user