diff --git a/gulp/gulpfile.js b/gulp/gulpfile.js index 0db3f729..89b85db7 100644 --- a/gulp/gulpfile.js +++ b/gulp/gulpfile.js @@ -163,7 +163,7 @@ function serve({ standalone }) { }); // Watch .scss files, those trigger a css rebuild - gulp.watch(["../src/**/*.scss"], gulp.series("css.dev")); + gulp.watch(["../src/**/*.scss"], { usePolling: true }, gulp.series("css.dev")); // Watch .html files, those trigger a html rebuild gulp.watch("../src/**/*.html", gulp.series(standalone ? "html.standalone-dev" : "html.dev")); @@ -172,7 +172,7 @@ function serve({ standalone }) { // gulp.watch(["../res_raw/sounds/**/*.mp3", "../res_raw/sounds/**/*.wav"], gulp.series("sounds.dev")); // Watch translations - gulp.watch("../translations/**/*.yaml", gulp.series("translations.convertToJson")); + gulp.watch("../translations/**/*.yaml", { usePolling: true }, gulp.series("translations.convertToJson")); gulp.watch( ["../res_raw/sounds/sfx/*.mp3", "../res_raw/sounds/sfx/*.wav"], diff --git a/gulp/webpack.config.js b/gulp/webpack.config.js index 6e1d7388..42255b7f 100644 --- a/gulp/webpack.config.js +++ b/gulp/webpack.config.js @@ -14,6 +14,9 @@ module.exports = ({ watch = false, standalone = false }) => { "bundle.js": [path.resolve(__dirname, "../src/js/main.js")], }, watch, + watchOptions: { + poll: 1000 + }, node: { fs: "empty", }, diff --git a/src/css/states/keybindings.scss b/src/css/states/keybindings.scss index cf211403..49aecabb 100644 --- a/src/css/states/keybindings.scss +++ b/src/css/states/keybindings.scss @@ -1,5 +1,12 @@ -#state_KeybindingsState { +#state_SettingsState { + + $colorCategoryButton: #eee; + $colorCategoryButtonSelected: #5f748b; + .content { + display: flex; + overflow-y: scroll; + .topEntries { display: grid; grid-template-columns: 1fr auto; @@ -50,6 +57,148 @@ } } } + + .categoryContainer { + width: 100%; + + .category { + display: none; + + &.active { + display: block; + } + + .setting { + @include S(padding, 10px); + background: #eeeff5; + @include S(border-radius, $globalBorderRadius); + @include S(margin-bottom, 5px); + + label { + text-transform: uppercase; + @include Text; + } + + .desc { + @include S(margin-top, 5px); + @include SuperSmallText; + color: #aaadb2; + } + + > .row { + display: grid; + align-items: center; + grid-template-columns: 1fr auto; + } + + &.disabled { + // opacity: 0.3; + pointer-events: none; + * { + pointer-events: none !important; + cursor: default !important; + } + position: relative; + .standaloneOnlyHint { + @include PlainText; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: all; + display: flex; + align-items: center; + justify-content: center; + background: rgba(#fff, 0.5); + text-transform: uppercase; + color: $colorRedBright; + } + } + + .value.enum { + background: #fff; + @include PlainText; + display: flex; + align-items: flex-start; + pointer-events: all; + cursor: pointer; + justify-content: center; + @include S(min-width, 100px); + @include S(border-radius, $globalBorderRadius); + @include S(padding, 4px); + @include S(padding-right, 15px); + + background: #fff uiResource("icons/enum_selector.png") calc(100% - #{D(5px)}) + calc(50% + #{D(1px)}) / #{D(15px)} no-repeat; + + transition: background-color 0.12s ease-in-out; + &:hover { + background-color: #fafafa; + } + } + } + } + } + + .sidebar { + display: flex; + flex-direction: column; + @include S(min-width, 210px); + @include S(max-width, 320px); + width: 30%; + height: 100%; + position: sticky; + top: 0; + @include S(margin-left, 20px); + @include S(margin-right, 32px); + + .other { + margin-top: auto; + } + + button { + @include S(margin-top, 4px); + width: calc(100% - #{D(20px)}); + text-align: start; + + &::after { + content: unset; + } + } + + button.categoryButton, + button.about { + background-color: $colorCategoryButton; + color: #777a7f; + + &.active { + background-color: $colorCategoryButtonSelected; + color: #fff; + + &:hover { + opacity: 1; + } + } + + &.pressed { + transform: none !important; + } + } + + .versionbar { + @include S(margin-top, 20px); + @include SuperSmallText; + display: grid; + align-items: center; + grid-template-columns: 1fr auto; + .buildVersion { + display: flex; + flex-direction: column; + color: #aaadaf; + } + } + } } @include DarkThemeOverride { diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index 0d0a289e..67cff854 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -164,24 +164,28 @@ position: relative; .updateLabel { + background-color: $mainBgColor; + border-radius: 999em; + padding: 0px 10px; position: absolute; - transform: translateX(50%) rotate(-5deg); - color: $colorRedBright; + transform: translateX(50%) rotate(-5deg) scale(1.5); + color: #7d808a; + //color: $colorRedBright; @include Heading; - text-transform: uppercase; + //text-transform: uppercase; font-weight: bold; @include S(right, 40px); @include S(bottom, 20px); @include InlineAnimation(1.3s ease-in-out infinite) { 50% { - transform: translateX(50%) rotate(-7deg) scale(1.1); + transform: translateX(50%) rotate(-7deg) scale(1.65); } } - @include DarkThemeOverride { + /*@include DarkThemeOverride { color: $colorBlueBright; - } + }*/ } } diff --git a/src/js/GeoZ/main.js b/src/js/GeoZ/main.js new file mode 100644 index 00000000..5afb572e --- /dev/null +++ b/src/js/GeoZ/main.js @@ -0,0 +1,221 @@ +//Mod imports +import { MetaModBuilding } from "./mod_building"; +import { ModComponent } from "./mod_component"; +import { ModItem } from "./mod_item"; +import { ModProcessor } from "./mod_processor"; +import { ModWireProcessor } from "./mod_wireprocessor"; +import { ModSystem, ModSystemWithFilter } from "./mod_system"; +import { keyCodeOf } from "./mod_utils"; + +//Game imports +import { gComponentRegistry, gItemRegistry, gMetaBuildingRegistry } from "../core/global_registries"; +import { GameSystemManager } from "../game/game_system_manager"; +import { GameCore } from "../game/core"; +import { createLogger } from "../core/logging"; +import { registerBuildingVariant } from "../game/building_codes"; +import { supportedBuildings } from "../game/hud/parts/buildings_toolbar"; +import { KEYMAPPINGS, key } from "../game/key_action_mapper"; +import { T } from "../translations"; +import { ShapeData, allShapeData, initShapes } from "../game/shapes"; +import { globalConfig } from "../core/config"; + +export { MetaModBuilding } from "./mod_building"; +export { ModComponent } from "./mod_component"; +export { ModItem } from "./mod_item"; +export { ModProcessor } from "./mod_processor"; +export { ModWireProcessor } from "./mod_wireprocessor"; +export { ModSystem, ModSystemWithFilter } from "./mod_system"; + +/** + * @typedef {Object} Mod + * @property {String} name + * @property {Array=} buildings + * @property {Array=} components + * @property {Array=} items + * @property {Array=} processors + * @property {Array=} wireProcessors + * @property {Array=} systems + * @property {Array=} shapes + */ + +export const logger = createLogger("GeoZ"); + +/** @type {Array} */ +export const Mods = []; + +/** @type {Array} */ +export const ModComponents = []; + +/** @type {Array} */ +export const ModSystems = []; + +/** @type {Object.} */ +export const ModProcessors = {}; + +/** @type {Object.} */ +export const ModWireProcessors = {}; + +/** @type {Array} */ +export const ModItems = []; + +/** @type {Array} */ +export const ModBuildings = []; + +/** @type {Array} */ +export const ModShapes = []; + +// @ts-ignore +const webpack_require = require.context("../", true, /\.js$/); + +const GeoZ = { + Classes: { + MetaModBuilding, + ModComponent, + ModItem, + ModProcessor, + ModSystem, + ModSystemWithFilter, + }, + + require(module) { + return webpack_require(`./${module}.js`); + }, +}; + +export async function initMods() { + const style = "font-size: 35px; font-family: Arial;font-weight: bold; padding: 10px 0;"; + console.log( + `%cGeo%cZ%c modloader\nby %cExund\n`, + `${style} color: #aaa;`, + `${style} color: #7f7;`, + `${style} color: #aaa; font-size: 15px;`, + "color: #ff4300" + ); + + // @ts-ignore + window.GeoZ = GeoZ; + + // @ts-ignore + const local_mods = require.context("./mods", true, /.*\.mod\.js/i); + for (let key of local_mods.keys()) { + let mod = /** @type {Mod} */ (local_mods(key).default); + if (mod.name) { + Mods.push(mod); + } + } + + const local_mods_count = Mods.length; + logger.log(`${local_mods_count} local mods found`); + + /** @type {Array} */ + let external_mods = []; + let storage = localStorage.getItem("mods.external"); + + if (storage) { + external_mods = JSON.parse(storage); + } + + for (const url of external_mods) { + try { + let temp = await fetch(url); + const text = await temp.text(); + const mod = /** @type {Mod} */ (eval(text)); + + if (mod.name) { + Mods.push(mod); + } + } catch { + logger.log(`🛑 Failed to load mod at : ${url}`); + } + } + + const external_mods_count = Mods.length - local_mods_count; + logger.log(`${external_mods_count} external mods found`); + + for (const mod of Mods) { + let mod_infos = `${mod.name} : `; + if (mod.components) { + mod_infos += `${mod.components.length} components, `; + for (const component of mod.components) { + ModComponents.push(component); + gComponentRegistry.register(component); + } + } + + if (mod.systems) { + mod_infos += `${mod.systems.length} systems, `; + for (const system of mod.systems) { + ModSystems.push(system); + } + } + + if (mod.processors) { + mod_infos += `${mod.processors.length} processors, `; + for (const processor of mod.processors) { + const type = processor.getType(); + ModProcessors[type] = processor; + globalConfig.buildingSpeeds[type] = processor.getBaseSpeed(); + } + } + + if (mod.wireProcessors) { + mod_infos += `${mod.wireProcessors.length} wire processors, `; + for (const wireProcessor of mod.wireProcessors) { + const type = wireProcessor.getType(); + ModWireProcessors[type] = wireProcessor; + } + } + + if (mod.items) { + mod_infos += `${mod.items.length} items, `; + for (const item of mod.items) { + ModItems.push(item); + gItemRegistry.register(item); + } + } + + if (mod.buildings) { + mod_infos += `${mod.buildings.length} buildings, `; + for (const building of mod.buildings) { + ModBuildings.push(building); + gMetaBuildingRegistry.register(building); + const base_id = building.getId(); + registerBuildingVariant(base_id, building); + + for (const variant of building.getVariants()) { + registerBuildingVariant(`${base_id}-${variant}`, building, variant); + } + + supportedBuildings.push(building); + + KEYMAPPINGS.buildings[base_id] = { + keyCode: keyCodeOf(building.getKeybinding()), + id: base_id, + }; + + const translations = building.getTranslations(); + + T.keybindings.mappings[base_id] = translations.keybinding; + + T.buildings[base_id] = {}; + for (const variant in translations.variants) { + T.buildings[base_id][variant] = translations.variants[variant]; + } + } + } + + if (mod.shapes) { + mod_infos += `${mod.shapes.length} shapes, `; + for (const shape of mod.shapes) { + ModShapes.push(shape); + allShapeData[shape.id] = shape; + } + } + + logger.log(mod_infos); + } + + initShapes(); + + logger.log(`${Mods.length} mods loaded`); +} diff --git a/src/js/GeoZ/mod_building.js b/src/js/GeoZ/mod_building.js new file mode 100644 index 00000000..ef255847 --- /dev/null +++ b/src/js/GeoZ/mod_building.js @@ -0,0 +1,157 @@ +import { MetaBuilding, defaultBuildingVariant } from "../game/meta_building"; +import { AtlasSprite, SpriteAtlasLink } from "../core/sprites"; +import { atlasFiles } from "../core/atlas_definitions"; +import { getFileAsDataURI } from "./mod_utils"; +import { Loader } from "../core/loader"; + +/** + * @typedef {{ + * url: string, + * width: number, + * height: number + * }} ExternalSpriteMeta + */ + +/** + * @typedef {{ + * normal: ExternalSpriteMeta + * blueprint: ExternalSpriteMeta + * }} SpriteTypesMetas + */ + +/** + * @typedef {{ + * default: Array + * [variant: string]: Array + * }} BuildingSpriteMetas + */ + +/** + * @typedef {{ + * name: string, + * description: string + * }} BuildingVariantTranslation + */ + +/** + * @typedef {{ + * variants: {[variant: string]: BuildingVariantTranslation, default: BuildingVariantTranslation}, + * keybinding: string + * }} BuildingTranlsations + */ + +export class MetaModBuilding extends MetaBuilding { + /** + * Returns the building IDs + * @returns {String} + */ + static getId() { + abstract; + return ""; + } + + /** + * Returns the building variants IDs + * @returns {Array} + */ + static getVariants() { + return []; + } + + /** + * Returns the building keybinding + * @returns {String | number} + */ + static getKeybinding() { + abstract; + return ""; + } + + /** + * Returns the building translations + * @returns {BuildingTranlsations} + */ + static getTranslations() { + abstract; + return { variants: { default: { name: "", description: "" } }, keybinding: "" }; + } + + /** + * @param {string} id + */ + constructor(id) { + super(id); + + /** @type {Object} */ + this.cachedSprites = {}; + } + + /** + * Returns the sprite for a given variant + * @param {number} rotationVariant + * @param {string} variant + * @param {keyof BuildingSpriteMetas} type + * @returns {AtlasSprite} + */ + getSprite(rotationVariant, variant, type = "normal") { + const sprite_id = + this.id + + (variant === defaultBuildingVariant ? "" : "-" + variant) + + "-" + + rotationVariant + + "-" + + type; + + if (this.cachedSprites[sprite_id]) { + return this.cachedSprites[sprite_id]; + } + + const sprite = new AtlasSprite(sprite_id); + this.cachedSprites[sprite_id] = sprite; + + const meta = this.getSpriteMetas()[variant][rotationVariant][type]; + const scales = atlasFiles.map(af => af.meta.scale); + for (const res of scales) { + sprite.linksByResolution[res] = Loader.spriteNotFoundSprite.linksByResolution[res]; + } + + getFileAsDataURI(meta.url).then(data => { + const img = document.createElement("img"); + img.src = data; + + const link = new SpriteAtlasLink({ + atlas: img, + packOffsetX: 0, + packOffsetY: 0, + packedX: 0, + packedY: 0, + packedW: meta.width, + packedH: meta.height, + w: meta.width, + h: meta.width, + }); + for (const res of scales) { + sprite.linksByResolution[res] = link; + } + }); + + return sprite; + } + + getBlueprintSprite(rotationVariant = 0, variant = defaultBuildingVariant) { + return this.getSprite(rotationVariant, variant, "blueprint"); + } + + getPreviewSprite(rotationVariant = 0, variant = defaultBuildingVariant) { + return this.getSprite(rotationVariant, variant); + } + + /** + * Returns the sprite metadata for a given variant + * @returns {BuildingSpriteMetas} + */ + getSpriteMetas() { + abstract; + return null; + } +} diff --git a/src/js/GeoZ/mod_component.js b/src/js/GeoZ/mod_component.js new file mode 100644 index 00000000..4dc0d51a --- /dev/null +++ b/src/js/GeoZ/mod_component.js @@ -0,0 +1,13 @@ +import { Component } from "../game/component"; + +export class ModComponent extends Component { + static getId() { + const className = this.prototype.constructor.name; + let id = className; + const i = className.lastIndexOf("Component"); + if(i !== -1) { + id = id.slice(0, i); + } + return id; + } +} \ No newline at end of file diff --git a/src/js/GeoZ/mod_item.js b/src/js/GeoZ/mod_item.js new file mode 100644 index 00000000..e267459d --- /dev/null +++ b/src/js/GeoZ/mod_item.js @@ -0,0 +1,5 @@ +import { BaseItem } from "../game/base_item"; + +export class ModItem extends BaseItem { + +} \ No newline at end of file diff --git a/src/js/GeoZ/mod_processor.js b/src/js/GeoZ/mod_processor.js new file mode 100644 index 00000000..b337a173 --- /dev/null +++ b/src/js/GeoZ/mod_processor.js @@ -0,0 +1,50 @@ +import { Entity } from "../game/entity"; +import { ItemProcessorSystem } from "../game/systems/item_processor"; +import { BaseItem } from "../game/base_item"; + +/** + * @typedef {{ + * items: Array, + * itemsBySlot: Array<{ item: BaseItem, sourceSlot: number }>, + * itemsRaw: Array<{ item: BaseItem, sourceSlot: number }>, + * entity: Entity, + * outItems: Array<{item: BaseItem, requiredSlot?: number, preferredSlot?: number}>, + * system: ItemProcessorSystem + * }} ProcessorParameters +*/ + +export class ModProcessor { + /** + * @returns {String} + */ + static getType() { + return this.prototype.constructor.name; + } + + /** + * @returns {Number} + */ + static getBaseSpeed() { + abstract; + return 0; + } + + /** + * Checks whether it's possible to process something + * @param {Entity} entity + * @returns {Boolean} + */ + static canProcess(entity) { + return true; + } + + /** + * Process ther current item + * @param {ProcessorParameters} param0 + * @returns {Boolean} Whether to track the production towards the analytics + */ + static process({}) { + abstract; + return false; + } +} \ No newline at end of file diff --git a/src/js/GeoZ/mod_system.js b/src/js/GeoZ/mod_system.js new file mode 100644 index 00000000..5c6d34ea --- /dev/null +++ b/src/js/GeoZ/mod_system.js @@ -0,0 +1,121 @@ +import { GameSystem } from "../game/game_system"; +import { GameSystemWithFilter } from "../game/game_system_with_filter"; +import { GameRoot } from "../game/root"; +import { Component } from "../game/component"; + +/** + * @typedef { + "belt" + | "itemEjector" + | "mapResources" + | "miner" + | "itemProcessor" + | "undergroundBelt" + | "hub" + | "staticMapEntities" + | "itemAcceptor" + | "storage" + | "wiredPins" + | "beltUnderlays" + | "wire" + | "constantSignal" + | "logicGate" + | "lever" + | "display" + | "itemProcessorOverlays" + | "beltReader" + | "" + * } VanillaSystemId +*/ + +export class ModSystem extends GameSystem { + /** + * @returns {String} Mod system ID + */ + static getId() { + //abstract; + const className = this.prototype.constructor.name; + let id = className; + const i = className.lastIndexOf("System"); + if(i !== -1) { + id = id.slice(0, i); + } + id = id[0].toLowerCase() + id.slice(1); + return id; + } + + /** + * Before which vanilla system should this system update + * @returns {VanillaSystemId} + */ + static getUpdateBefore() { + return ""; + } + + /** + * After which vanilla system should this system update + * @returns {VanillaSystemId} + */ + static getUpdateAfter() { + return ""; + } + + /** + * @param {GameRoot} root + */ + constructor(root) { + super(root); + } +} + +export class ModSystemWithFilter extends GameSystemWithFilter { + /** + * @returns {String} Mod system ID + */ + static getId() { + //abstract; + const className = this.prototype.constructor.name; + let id = className; + const i = className.lastIndexOf("System"); + if(i !== -1) { + id = id.slice(0, i); + } + id = id[0].toLowerCase() + id.slice(1); + return id; + } + + /** + * Before which vanilla system should this system update + * @returns {VanillaSystemId} + */ + static getUpdateBefore() { + return ""; + } + + /** + * After which vanilla system should this system update + * @returns {VanillaSystemId} + */ + static getUpdateAfter() { + return ""; + } + + /** + * @returns {Array} + */ + static getRequiredComponents() { + abstract; + return []; + } + + /** + * Constructs a new game system with the given component filter. It will process + * all entities which have *all* of the passed components + * @param {GameRoot} root + */ + constructor(root) { + super(root, []); + this.requiredComponents = /** @type {Array} */ (Object.getPrototypeOf(this).getRequiredComponents()); + this.requiredComponentIds = this.requiredComponents.map(component => component.getId()); + } +} \ No newline at end of file diff --git a/src/js/GeoZ/mod_utils.js b/src/js/GeoZ/mod_utils.js new file mode 100644 index 00000000..ecc30741 --- /dev/null +++ b/src/js/GeoZ/mod_utils.js @@ -0,0 +1,30 @@ +/** + * Returns a file as a data URI + * @param {string} url + * @returns {Promise} + */ +export function getFileAsDataURI(url) { + return fetch(url) + .then(response => response.blob()) + .then(blob => { + return new Promise((resolve) => { + var reader = new FileReader() ; + reader.onload = function() { resolve(this.result.toString()) } ; // <--- `this.result` contains a base64 data URI + reader.readAsDataURL(blob) ; + }); + }); +} + +/** + * + * @param {number | string} key + */ +export function keyCodeOf(key) { + if (typeof key === "number") { + return key; + } + if (key.match(/F\d+/)) { + return 111 + +key.slice(1); + } + return key.toUpperCase().charCodeAt(0); +} \ No newline at end of file diff --git a/src/js/GeoZ/mod_wireprocessor.js b/src/js/GeoZ/mod_wireprocessor.js new file mode 100644 index 00000000..690ecda5 --- /dev/null +++ b/src/js/GeoZ/mod_wireprocessor.js @@ -0,0 +1,24 @@ +import { BaseItem } from "../game/base_item"; +import { LogicGateSystem } from "../game/systems/logic_gate"; + +/** + * Custom wire processor (logic gate/virtual processor) + */ +export class ModWireProcessor { + /** + * @returns {String} + */ + static getType() { + return this.prototype.constructor.name; + } + + /** + * @param {Array} parameters + * @param {LogicGateSystem} system + * @returns {Array|BaseItem} + */ + static compute(system, parameters) { + abstract; + return []; + } +} diff --git a/src/js/GeoZ/mods/test/test.mod.js b/src/js/GeoZ/mods/test/test.mod.js new file mode 100644 index 00000000..a7c43be0 --- /dev/null +++ b/src/js/GeoZ/mods/test/test.mod.js @@ -0,0 +1,362 @@ +import * as GeoZ from "../../main"; +import { Vector, enumDirection } from "../../../core/vector"; +import { Entity } from "../../../game/entity"; +import { ModProcessor, ProcessorParameters } from "../../mod_processor"; +import { ShapeItem } from "../../../game/items/shape_item"; +import { ShapeDefinition } from "../../../game/shape_definition"; +import { ItemProcessorComponent } from "../../../game/components/item_processor"; +import { ItemEjectorComponent } from "../../../game/components/item_ejector"; +import { ItemAcceptorComponent } from "../../../game/components/item_acceptor"; +import { ModWireProcessor } from "../../mod_wireprocessor"; +import { BaseItem } from "../../../game/base_item"; +import { LogicGateSystem } from "../../../game/systems/logic_gate"; +import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, isTruthyItem } from "../../../game/items/boolean_item"; +import { enumPinSlotType, WiredPinsComponent } from "../../../game/components/wired_pins"; +import { LogicGateComponent } from "../../../game/components/logic_gate"; +import { defaultBuildingVariant } from "../../../game/meta_building"; + +class MetaTestBuilding extends GeoZ.MetaModBuilding { + static getId() { + return "test"; + } + + static getKeybinding() { + return "0"; + } + + static getTranslations() { + return { + variants: { + default: { + name: "Test", + description: "Test GeoZ building", + }, + }, + keybinding: "Test", + }; + } + + constructor() { + super("test"); + } + + getSilhouetteColor() { + return "#ff00ff"; + } + + getDimensions() { + return new Vector(1, 1); + } + + /** + * @returns {import("../../mod_building").BuildingSpriteMetas} + */ + getSpriteMetas() { + const normal = { + url: + "https://raw.githubusercontent.com/Exund/shapez.io/master/res_raw/sprites/wires/boolean_false.png", + width: 64, + height: 64, + }; + + return { + default: [ + { + normal, + blueprint: normal, + }, + ], + }; + } + + /** + * Creates the entity at the given location + * @param {Entity} entity + */ + setupEntityComponents(entity) { + entity.addComponent( + new ItemProcessorComponent({ + inputsPerCharge: 1, + processorType: SquareConverter.getType(), + }) + ); + + entity.addComponent( + new ItemEjectorComponent({ + slots: [{ pos: new Vector(0, 0), direction: enumDirection.top }], + }) + ); + entity.addComponent( + new ItemAcceptorComponent({ + slots: [ + { + pos: new Vector(0, 0), + directions: [enumDirection.bottom], + filter: "shape", + }, + ], + }) + ); + } +} + +class MetaInvertedGatesBuilding extends GeoZ.MetaModBuilding { + static getId() { + return "NANDGate"; + } + + static getKeybinding() { + return "0"; + } + + static getTranslations() { + return { + variants: { + default: { + name: "NAND Gate", + description: "Test GeoZ building for custom wire processor", + }, + NORGate: { + name: "NOR Gate", + description: "Test GeoZ building for custom wire processor", + }, + XNORGate: { + name: "XNOR Gate", + description: "Test GeoZ building for custom wire processor", + }, + }, + keybinding: "NAND Gate", + }; + } + + static getVariants() { + return ["NORGate", "XNORGate"]; + } + + constructor() { + super("NANDGate"); + } + + getSilhouetteColor() { + return "#89dc60"; + } + + getDimensions() { + return new Vector(1, 1); + } + + getAvailableVariants() { + return [...super.getAvailableVariants(null), ...MetaInvertedGatesBuilding.getVariants()]; + } + + /** + * @returns {Layer} + */ + getLayer() { + return "wires"; + } + + /** + * @returns {import("../../mod_building").BuildingSpriteMetas} + */ + getSpriteMetas() { + return { + default: [ + { + normal: { + url: + "https://raw.githubusercontent.com/Exund/shapez.io/master/res_raw/sprites/buildings/logic_gate.png", + width: 192, + height: 192, + }, + blueprint: { + url: + "https://raw.githubusercontent.com/Exund/shapez.io/master/res_raw/sprites/blueprints/logic_gate.png", + width: 192, + height: 192, + }, + }, + ], + NORGate: [ + { + normal: { + url: + "https://raw.githubusercontent.com/Exund/shapez.io/master/res_raw/sprites/buildings/logic_gate-or.png", + width: 192, + height: 192, + }, + blueprint: { + url: + "https://raw.githubusercontent.com/Exund/shapez.io/master/res_raw/sprites/blueprints/logic_gate-or.png", + width: 192, + height: 192, + }, + }, + ], + XNORGate: [ + { + normal: { + url: + "https://raw.githubusercontent.com/Exund/shapez.io/master/res_raw/sprites/buildings/logic_gate-xor.png", + width: 192, + height: 192, + }, + blueprint: { + url: + "https://raw.githubusercontent.com/Exund/shapez.io/master/res_raw/sprites/blueprints/logic_gate-xor.png", + width: 192, + height: 192, + }, + }, + ], + }; + } + + /** + * @param {Entity} entity + * @param {string} variant + */ + updateVariants(entity, rotationVariant, variant) { + entity.components.LogicGate.type = enumInvertedGatesVariants[variant]; + } + + /** + * Creates the entity at the given location + * @param {Entity} entity + */ + setupEntityComponents(entity) { + entity.addComponent( + new WiredPinsComponent({ + slots: [ + { + pos: new Vector(0, 0), + direction: enumDirection.top, + type: enumPinSlotType.logicalEjector, + }, + { + pos: new Vector(0, 0), + direction: enumDirection.left, + type: enumPinSlotType.logicalAcceptor, + }, + { + pos: new Vector(0, 0), + direction: enumDirection.right, + type: enumPinSlotType.logicalAcceptor, + }, + ], + }) + ); + + entity.addComponent(new LogicGateComponent({ type: NANDGate.getType() })); + } +} + +const enumInvertedGatesVariants = { + [defaultBuildingVariant]: "NANDGate", +}; + +for (const v of MetaInvertedGatesBuilding.getVariants()) { + enumInvertedGatesVariants[v] = v; +} + +class SquareConverter extends ModProcessor { + /** + * @returns {Number} + */ + static getBaseSpeed() { + return 0.5; + } + + /** + * Process ther current item + * @param {ProcessorParameters} param0 + * @returns {Boolean} Whether to track the production towards the analytics + */ + static process({ outItems }) { + outItems.push({ item: new ShapeItem(ShapeDefinition.fromShortKey("SuSuSuSu")) }); + return true; + } +} + +class NANDGate extends ModWireProcessor { + /** + * @param {Array} parameters + * @param {LogicGateSystem} system + * @returns {Array|BaseItem} + */ + static compute(system, parameters) { + assert(parameters.length === 2, "bad parameter count for NAND"); + return isTruthyItem(parameters[0]) && isTruthyItem(parameters[1]) + ? BOOL_FALSE_SINGLETON + : BOOL_TRUE_SINGLETON; + } +} + +class NORGate extends ModWireProcessor { + /** + * @param {Array} parameters + * @param {LogicGateSystem} system + * @returns {Array|BaseItem} + */ + static compute(system, parameters) { + assert(parameters.length === 2, "bad parameter count for NOR"); + return isTruthyItem(parameters[0]) || isTruthyItem(parameters[1]) + ? BOOL_FALSE_SINGLETON + : BOOL_TRUE_SINGLETON; + } +} + +class XNORGate extends ModWireProcessor { + /** + * @param {Array} parameters + * @param {LogicGateSystem} system + * @returns {Array|BaseItem} + */ + static compute(system, parameters) { + assert(parameters.length === 2, "bad parameter count for XNOR"); + return isTruthyItem(parameters[0]) !== isTruthyItem(parameters[1]) + ? BOOL_FALSE_SINGLETON + : BOOL_TRUE_SINGLETON; + } +} + +class VirtualStacker extends ModWireProcessor { + /** + * @param {Array} parameters + * @param {LogicGateSystem} system + * @returns {Array|BaseItem} + */ + static compute(system, parameters) { + const item1 = parameters[0]; + const item2 = parameters[0]; + if (!item1 || !item2 || item1.getItemType() !== "shape" || item2.getItemType() !== "shape") { + return null; + } + + const definition1 = /** @type {ShapeItem} */ (item1).definition; + const definition2 = /** @type {ShapeItem} */ (item2).definition; + const result = system.root.shapeDefinitionMgr.shapeActionStack(definition1, definition2); + return system.root.shapeDefinitionMgr.getShapeItemFromDefinition(result); + } +} + +/**@type {GeoZ.Mod}*/ +const test = { + name: "test", + buildings: [MetaTestBuilding, MetaInvertedGatesBuilding], + processors: [SquareConverter], + wireProcessors: [NANDGate, NORGate, XNORGate, VirtualStacker], + shapes: [ + { + id: "leaf", + code: "F", + draw: "M 0 0 v 0.5 a 0.5 0.5 0 0 0 0.5 0.5 h 0.5 v -0.5 a 0.5 0.5 0 0 0 -0.5 -0.5 z", + tier: 2, + spawnData: { + color: "yellow", + }, + }, + ], +}; + +export default test; diff --git a/src/js/application.js b/src/js/application.js index e5e22b60..369cc032 100644 --- a/src/js/application.js +++ b/src/js/application.js @@ -145,7 +145,7 @@ export class Application { MobileWarningState, MainMenuState, InGameState, - SettingsState, + //SettingsState, KeybindingsState, AboutState, ChangelogState, diff --git a/src/js/core/config.js b/src/js/core/config.js index a6d6ed63..10e85f7b 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -1,136 +1,136 @@ -import { queryParamOptions } from "./query_parameters"; - -export const IS_DEBUG = - G_IS_DEV && - typeof window !== "undefined" && - window.location.port === "3005" && - (window.location.host.indexOf("localhost:") >= 0 || window.location.host.indexOf("192.168.0.") >= 0) && - window.location.search.indexOf("nodebug") < 0; - -export const IS_DEMO = queryParamOptions.fullVersion - ? false - : (!G_IS_DEV && !G_IS_STANDALONE) || - (typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0); - -export const SUPPORT_TOUCH = false; - -const smoothCanvas = true; - -export const THIRDPARTY_URLS = { - discord: "https://discord.gg/HN7EVzV", - github: "https://github.com/tobspr/shapez.io", - reddit: "https://www.reddit.com/r/shapezio", - - standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/", -}; - -export const globalConfig = { - // Size of a single tile in Pixels. - // NOTICE: Update webpack.production.config too! - tileSize: 32, - halfTileSize: 16, - - // Which dpi the assets have - assetsDpi: 192 / 32, - assetsSharpness: 1.5, - shapesSharpness: 1.4, - - // Production analytics - statisticsGraphDpi: 2.5, - statisticsGraphSlices: 100, - analyticsSliceDurationSeconds: G_IS_DEV ? 1 : 10, - - minimumTickRate: 25, - maximumTickRate: 500, - - // Map - mapChunkSize: 16, - mapChunkOverviewMinZoom: 0.9, - mapChunkWorldSize: null, // COMPUTED - - // Belt speeds - // NOTICE: Update webpack.production.config too! - beltSpeedItemsPerSecond: 2, - minerSpeedItemsPerSecond: 0, // COMPUTED - - defaultItemDiameter: 20, - - itemSpacingOnBelts: 0.63, - - wiresSpeedItemsPerSecond: 6, - - undergroundBeltMaxTilesByTier: [5, 9], - - readerAnalyzeIntervalSeconds: G_IS_DEV ? 3 : 10, - - buildingSpeeds: { - cutter: 1 / 4, - cutterQuad: 1 / 4, - rotater: 1 / 1, - rotaterCCW: 1 / 1, - rotaterFL: 1 / 1, - painter: 1 / 6, - painterDouble: 1 / 8, - painterQuad: 1 / 8, - mixer: 1 / 5, - stacker: 1 / 6, - }, - - // Zooming - initialZoom: 1.9, - minZoomLevel: 0.1, - maxZoomLevel: 3, - - // Global game speed - gameSpeed: 1, - - warmupTimeSecondsFast: 0.1, - warmupTimeSecondsRegular: 1, - - smoothing: { - smoothMainCanvas: smoothCanvas && true, - quality: "low", // Low is CRUCIAL for mobile performance! - }, - - rendering: {}, - debug: require("./config.local").default, - - // Secret vars - info: { - // Binary file salt - file: "Ec'])@^+*9zMevK3uMV4432x9%iK'=", - - // Savegame salt - sgSalt: "}95Q3%8/.837Lqym_BJx%q7)pAHJbF", - - // Analytics key - analyticsApiKey: "baf6a50f0cc7dfdec5a0e21c88a1c69a4b34bc4a", - }, -}; - -export const IS_MOBILE = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); - -// Automatic calculations -globalConfig.minerSpeedItemsPerSecond = globalConfig.beltSpeedItemsPerSecond / 5; - -globalConfig.mapChunkWorldSize = globalConfig.mapChunkSize * globalConfig.tileSize; - -// Dynamic calculations -if (globalConfig.debug.disableMapOverview) { - globalConfig.mapChunkOverviewMinZoom = 0; -} - -// Stuff for making the trailer -if (G_IS_DEV && globalConfig.debug.renderForTrailer) { - globalConfig.debug.framePausesBetweenTicks = 32; - // globalConfig.mapChunkOverviewMinZoom = 0.0; - // globalConfig.debug.instantBelts = true; - // globalConfig.debug.instantProcessors = true; - // globalConfig.debug.instantMiners = true; - globalConfig.debug.disableSavegameWrite = true; - // globalConfig.beltSpeedItemsPerSecond *= 2; -} - -if (globalConfig.debug.fastGameEnter) { - globalConfig.debug.noArtificalDelays = true; -} +import { queryParamOptions } from "./query_parameters"; + +export const IS_DEBUG = + G_IS_DEV && + typeof window !== "undefined" && + window.location.port === "3005" && + (window.location.host.indexOf("localhost:") >= 0 || window.location.host.indexOf("192.168.0.") >= 0) && + window.location.search.indexOf("nodebug") < 0; + +export const IS_DEMO = queryParamOptions.fullVersion + ? false + : (!G_IS_DEV && !G_IS_STANDALONE) || + (typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0); + +export const SUPPORT_TOUCH = false; + +const smoothCanvas = true; + +export const THIRDPARTY_URLS = { + discord: "https://discord.gg/HN7EVzV", + github: "https://github.com/tobspr/shapez.io", + reddit: "https://www.reddit.com/r/shapezio", + + standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/", +}; + +export const globalConfig = { + // Size of a single tile in Pixels. + // NOTICE: Update webpack.production.config too! + tileSize: 32, + halfTileSize: 16, + + // Which dpi the assets have + assetsDpi: 192 / 32, + assetsSharpness: 1.5, + shapesSharpness: 1.4, + + // Production analytics + statisticsGraphDpi: 2.5, + statisticsGraphSlices: 100, + analyticsSliceDurationSeconds: G_IS_DEV ? 1 : 10, + + minimumTickRate: 25, + maximumTickRate: 500, + + // Map + mapChunkSize: 16, + mapChunkOverviewMinZoom: 0.9, + mapChunkWorldSize: null, // COMPUTED + + // Belt speeds + // NOTICE: Update webpack.production.config too! + beltSpeedItemsPerSecond: 2, + minerSpeedItemsPerSecond: 0, // COMPUTED + + defaultItemDiameter: 20, + + itemSpacingOnBelts: 0.63, + + wiresSpeedItemsPerSecond: 6, + + undergroundBeltMaxTilesByTier: [5, 9], + + readerAnalyzeIntervalSeconds: G_IS_DEV ? 3 : 10, + + buildingSpeeds: { + cutter: 1 / 4, + cutterQuad: 1 / 4, + rotater: 1 / 1, + rotaterCCW: 1 / 1, + rotaterFL: 1 / 1, + painter: 1 / 6, + painterDouble: 1 / 8, + painterQuad: 1 / 8, + mixer: 1 / 5, + stacker: 1 / 6, + }, + + // Zooming + initialZoom: 1.9, + minZoomLevel: 0.1, + maxZoomLevel: 3, + + // Global game speed + gameSpeed: 1, + + warmupTimeSecondsFast: 0.1, + warmupTimeSecondsRegular: 1, + + smoothing: { + smoothMainCanvas: smoothCanvas && true, + quality: "low", // Low is CRUCIAL for mobile performance! + }, + + rendering: {}, + debug: G_IS_DEV ? require("./config.local").default : {}, + + // Secret vars + info: { + // Binary file salt + file: "Ec'])@^+*9zMevK3uMV4432x9%iK'=", + + // Savegame salt + sgSalt: "}95Q3%8/.837Lqym_BJx%q7)pAHJbF", + + // Analytics key + analyticsApiKey: "baf6a50f0cc7dfdec5a0e21c88a1c69a4b34bc4a", + }, +}; + +export const IS_MOBILE = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); + +// Automatic calculations +globalConfig.minerSpeedItemsPerSecond = globalConfig.beltSpeedItemsPerSecond / 5; + +globalConfig.mapChunkWorldSize = globalConfig.mapChunkSize * globalConfig.tileSize; + +// Dynamic calculations +if (globalConfig.debug.disableMapOverview) { + globalConfig.mapChunkOverviewMinZoom = 0; +} + +// Stuff for making the trailer +if (G_IS_DEV && globalConfig.debug.renderForTrailer) { + globalConfig.debug.framePausesBetweenTicks = 32; + // globalConfig.mapChunkOverviewMinZoom = 0.0; + // globalConfig.debug.instantBelts = true; + // globalConfig.debug.instantProcessors = true; + // globalConfig.debug.instantMiners = true; + globalConfig.debug.disableSavegameWrite = true; + // globalConfig.beltSpeedItemsPerSecond *= 2; +} + +if (globalConfig.debug.fastGameEnter) { + globalConfig.debug.noArtificalDelays = true; +} diff --git a/src/js/core/config.local.js b/src/js/core/config.local.js index b75c5650..1b1f10dd 100644 --- a/src/js/core/config.local.js +++ b/src/js/core/config.local.js @@ -1,114 +1,114 @@ -export default { - // You can set any debug options here! - /* dev:start */ - // ----------------------------------------------------------------------------------- - // Quickly enters the game and skips the main menu - good for fast iterating - // fastGameEnter: true, - // ----------------------------------------------------------------------------------- - // Skips any delays like transitions between states and such - // noArtificialDelays: true, - // ----------------------------------------------------------------------------------- - // Disables writing of savegames, useful for testing the same savegame over and over - // disableSavegameWrite: true, - // ----------------------------------------------------------------------------------- - // Shows bounds of all entities - // showEntityBounds: true, - // ----------------------------------------------------------------------------------- - // Shows arrows for every ejector / acceptor - // showAcceptorEjectors: true, - // ----------------------------------------------------------------------------------- - // Disables the music (Overrides any setting, can cause weird behaviour) - // disableMusic: true, - // ----------------------------------------------------------------------------------- - // Do not render static map entities (=most buildings) - // doNotRenderStatics: true, - // ----------------------------------------------------------------------------------- - // Allow to zoom freely without limits - // disableZoomLimits: true, - // ----------------------------------------------------------------------------------- - // Shows a border arround every chunk - // showChunkBorders: true, - // ----------------------------------------------------------------------------------- - // All rewards can be unlocked by passing just 1 of any shape - // rewardsInstant: true, - // ----------------------------------------------------------------------------------- - // Unlocks all buildings - // allBuildingsUnlocked: true, - // ----------------------------------------------------------------------------------- - // Disables cost of blueprints - // blueprintsNoCost: true, - // ----------------------------------------------------------------------------------- - // Disables cost of upgrades - // upgradesNoCost: true, - // ----------------------------------------------------------------------------------- - // Disables the dialog when completing a level - // disableUnlockDialog: true, - // ----------------------------------------------------------------------------------- - // Disables the simulation - This effectively pauses the game. - // disableLogicTicks: true, - // ----------------------------------------------------------------------------------- - // Test the rendering if everything is clipped out properly - // testClipping: true, - // ----------------------------------------------------------------------------------- - // Allows to render slower, useful for recording at half speed to avoid stuttering - // framePausesBetweenTicks: 250, - // ----------------------------------------------------------------------------------- - // Replace all translations with emojis to see which texts are translateable - // testTranslations: true, - // ----------------------------------------------------------------------------------- - // Enables an inspector which shows information about the entity below the curosr - // enableEntityInspector: true, - // ----------------------------------------------------------------------------------- - // Enables ads in the local build (normally they are deactivated there) - // testAds: true, - // ----------------------------------------------------------------------------------- - // Disables the automatic switch to an overview when zooming out - // disableMapOverview: true, - // ----------------------------------------------------------------------------------- - // Disables the notification when there are new entries in the changelog since last played - // disableUpgradeNotification: true, - // ----------------------------------------------------------------------------------- - // Makes belts almost infinitely fast - // instantBelts: true, - // ----------------------------------------------------------------------------------- - // Makes item processors almost infinitely fast - // instantProcessors: true, - // ----------------------------------------------------------------------------------- - // Makes miners almost infinitely fast - // instantMiners: true, - // ----------------------------------------------------------------------------------- - // When using fastGameEnter, controls whether a new game is started or the last one is resumed - // resumeGameOnFastEnter: true, - // ----------------------------------------------------------------------------------- - // Special option used to render the trailer - // renderForTrailer: true, - // ----------------------------------------------------------------------------------- - // Whether to render changes - // renderChanges: true, - // ----------------------------------------------------------------------------------- - // Whether to render belt paths - // renderBeltPaths: true, - // ----------------------------------------------------------------------------------- - // Whether to check belt paths - // checkBeltPaths: true, - // ----------------------------------------------------------------------------------- - // Whether to items / s instead of items / m in stats - // detailedStatistics: true, - // ----------------------------------------------------------------------------------- - // Shows detailed information about which atlas is used - // showAtlasInfo: true, - // ----------------------------------------------------------------------------------- - // Renders the rotation of all wires - // renderWireRotations: true, - // ----------------------------------------------------------------------------------- - // Renders information about wire networks - // renderWireNetworkInfos: true, - // ----------------------------------------------------------------------------------- - // Disables ejector animations and processing - // disableEjectorProcessing: true, - // ----------------------------------------------------------------------------------- - // Allows manual ticking - // manualTickOnly: true, - // ----------------------------------------------------------------------------------- - /* dev:end */ -}; +export default { + // You can set any debug options here! + /* dev:start */ + // ----------------------------------------------------------------------------------- + _fastGameEnter: "Quickly enters the game and skips the main menu - good for fast iterating", + fastGameEnter: false, + // ----------------------------------------------------------------------------------- + _noArtificialDelays: "Skips any delays like transitions between states and such", + noArtificialDelays: false, + // ----------------------------------------------------------------------------------- + _disableSavegameWrite: "Disables writing of savegames, useful for testing the same savegame over and over", + disableSavegameWrite: false, + // ----------------------------------------------------------------------------------- + _showEntityBounds: "Shows bounds of all entities", + showEntityBounds: false, + // ----------------------------------------------------------------------------------- + _showAcceptorEjectors: "Shows arrows for every ejector / acceptor", + showAcceptorEjectors: false, + // ----------------------------------------------------------------------------------- + _disableMusic: "Disables the music (Overrides any setting, can cause weird behaviour)", + disableMusic: false, + // ----------------------------------------------------------------------------------- + _doNotRenderStatics: "Do not render static map entities (=most buildings)", + doNotRenderStatics: false, + // ----------------------------------------------------------------------------------- + _disableZoomLimits: "Allow to zoom freely without limits", + disableZoomLimits: false, + // ----------------------------------------------------------------------------------- + _showChunkBorders: "Shows a border arround every chunk", + showChunkBorders: false, + // ----------------------------------------------------------------------------------- + _rewardsInstant: "All rewards can be unlocked by passing just 1 of any shape", + rewardsInstant: false, + // ----------------------------------------------------------------------------------- + _allBuildingsUnlocked: "Unlocks all buildings", + allBuildingsUnlocked: false, + // ----------------------------------------------------------------------------------- + _blueprintsNoCost: "Disables cost of blueprints", + blueprintsNoCost: false, + // ----------------------------------------------------------------------------------- + _upgradesNoCost: "Disables cost of upgrades", + upgradesNoCost: false, + // ----------------------------------------------------------------------------------- + _disableUnlockDialog: "Disables the dialog when completing a level", + disableUnlockDialog: false, + // ----------------------------------------------------------------------------------- + _disableLogicTicks: "Disables the simulation - This effectively pauses the game.", + disableLogicTicks: false, + // ----------------------------------------------------------------------------------- + _testClipping: "Test the rendering if everything is clipped out properly", + testClipping: false, + // ----------------------------------------------------------------------------------- + // Allows to render slower, useful for recording at half speed to avoid stuttering + // framePausesBetweenTicks: 250, + // ----------------------------------------------------------------------------------- + _testTranslations: "Replace all translations with emojis to see which texts are translateable", + testTranslations: false, + // ----------------------------------------------------------------------------------- + _enableEntityInspector: "Enables an inspector which shows information about the entity below the curosr", + enableEntityInspector: false, + // ----------------------------------------------------------------------------------- + _testAds: "Enables ads in the local build (normally they are deactivated there)", + testAds: false, + // ----------------------------------------------------------------------------------- + _disableMapOverview: "Disables the automatic switch to an overview when zooming out", + disableMapOverview: false, + // ----------------------------------------------------------------------------------- + _disableUpgradeNotification: "Disables the notification when there are new entries in the changelog since last played", + disableUpgradeNotification: false, + // ----------------------------------------------------------------------------------- + _instantBelts: "Makes belts almost infinitely fast", + instantBelts: false, + // ----------------------------------------------------------------------------------- + _instantProcessors: "Makes item processors almost infinitely fast", + instantProcessors: false, + // ----------------------------------------------------------------------------------- + _instantMiners: "Makes miners almost infinitely fast", + instantMiners: false, + // ----------------------------------------------------------------------------------- + _resumeGameOnFastEnter: "When using fastGameEnter, controls whether a new game is started or the last one is resumed", + resumeGameOnFastEnter: false, + // ----------------------------------------------------------------------------------- + _renderForTrailer: "Special option used to render the trailer", + renderForTrailer: false, + // ----------------------------------------------------------------------------------- + _renderChanges: "Whether to render changes", + renderChanges: false, + // ----------------------------------------------------------------------------------- + _renderBeltPaths: "Whether to render belt paths", + renderBeltPaths: false, + // ----------------------------------------------------------------------------------- + _checkBeltPaths: "Whether to check belt paths", + checkBeltPaths: false, + // ----------------------------------------------------------------------------------- + _detailedStatistics: "Whether to items / s instead of items / m in stats", + detailedStatistics: false, + // ----------------------------------------------------------------------------------- + // Shows detailed information about which atlas is used + // showAtlasInfo: true, + // ----------------------------------------------------------------------------------- + // Renders the rotation of all wires + // renderWireRotations: true, + // ----------------------------------------------------------------------------------- + // Renders information about wire networks + // renderWireNetworkInfos: true, + // ----------------------------------------------------------------------------------- + // Disables ejector animations and processing + // disableEjectorProcessing: true, + // ----------------------------------------------------------------------------------- + // Allows manual ticking + // manualTickOnly: true, + // ----------------------------------------------------------------------------------- + /* dev:end */ +}; diff --git a/src/js/core/textual_game_state.js b/src/js/core/textual_game_state.js index 52a1f946..d60f2201 100644 --- a/src/js/core/textual_game_state.js +++ b/src/js/core/textual_game_state.js @@ -71,7 +71,23 @@ export class TextualGameState extends GameState { backToStatePayload: this.backToStatePayload, }, }); - } + } + + /** + * Goes to a new state, telling him to go back to this state later + * @param {string} stateId + */ + switchToState(stateId) { + // debugger; + this.moveToState( + stateId, + { + backToStateId: this.backToStateId, + backToStatePayload: this.backToStatePayload, + }, + true + ); + } /** * Removes all click detectors, except the one on the back button. Useful when regenerating diff --git a/src/js/game/building_codes.js b/src/js/game/building_codes.js index 05c27f57..1530f218 100644 --- a/src/js/game/building_codes.js +++ b/src/js/game/building_codes.js @@ -19,7 +19,7 @@ import { Vector } from "../core/vector"; /** * Stores a lookup table for all building variants (for better performance) - * @type {Object} + * @type {{[x: string]: BuildingVariantIdentifier, [x: number]: BuildingVariantIdentifier}} */ export const gBuildingVariants = { // Set later @@ -27,7 +27,7 @@ export const gBuildingVariants = { /** * Registers a new variant - * @param {number} id + * @param {number | string} id * @param {typeof MetaBuilding} meta * @param {string} variant * @param {number} rotationVariant @@ -50,7 +50,7 @@ export function registerBuildingVariant( /** * - * @param {number} code + * @param {number | string} code * @returns {BuildingVariantIdentifier} */ export function getBuildingDataFromCode(code) { @@ -72,12 +72,12 @@ export function getCodeFromBuildingData(metaBuilding, variant, rotationVariant) data.variant === variant && data.rotationVariant === rotationVariant ) { - return +key; + return key; } } assertAlways( false, "Building not found by data: " + metaBuilding.getId() + " / " + variant + " / " + rotationVariant ); - return 0; + return "0"; } diff --git a/src/js/game/component_registry.js b/src/js/game/component_registry.js index b03c164f..ea1e4b95 100644 --- a/src/js/game/component_registry.js +++ b/src/js/game/component_registry.js @@ -44,7 +44,7 @@ export function initComponentRegistry() { assert( // @ts-ignore - require.context("./components", false, /.*\.js/i).keys().length === + require.context("./components", false, /.*\.js/i).keys().length >= gComponentRegistry.getNumEntries(), "Not all components are registered" ); diff --git a/src/js/game/components/static_map_entity.js b/src/js/game/components/static_map_entity.js index 3d138e42..9518092e 100644 --- a/src/js/game/components/static_map_entity.js +++ b/src/js/game/components/static_map_entity.js @@ -19,7 +19,7 @@ export class StaticMapEntityComponent extends Component { originalRotation: types.float, // See building_codes.js - code: types.uint, + code: types.string, }; } @@ -79,7 +79,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(), @@ -96,7 +96,7 @@ export class StaticMapEntityComponent extends Component { this.origin = origin; this.rotation = rotation; - this.code = code; + this.code = code.toString(); this.originalRotation = originalRotation; } diff --git a/src/js/game/entity.js b/src/js/game/entity.js index ca21a16d..2af5d113 100644 --- a/src/js/game/entity.js +++ b/src/js/game/entity.js @@ -189,7 +189,7 @@ export class Entity extends BasicSerializableObject { const acceptorComp = this.components.ItemAcceptor; if (acceptorComp) { - const acceptorSprite = Loader.getSprite("sprites/misc/acceptor_slot.png"); + const acceptorSprite = Loader.getSprite("sprites/debug/acceptor_slot.png"); for (let i = 0; i < acceptorComp.slots.length; ++i) { const slot = acceptorComp.slots[i]; const slotTile = staticComp.localTileToWorld(slot.pos); diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index b0ae46f2..a3722342 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -22,6 +22,7 @@ import { LeverSystem } from "./systems/lever"; import { DisplaySystem } from "./systems/display"; import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays"; import { BeltReaderSystem } from "./systems/belt_reader"; +import { ModSystems, logger as GeoZLogger } from "../GeoZ/main"; const logger = createLogger("game_system_manager"); @@ -153,6 +154,55 @@ export class GameSystemManager { add("itemProcessorOverlays", ItemProcessorOverlaysSystem); + for (const system of ModSystems) { + const before = system.getUpdateBefore(); + const after = system.getUpdateAfter(); + const system_id = system.getId(); + let override = false; + + if (this.systems[system_id]) { + GeoZLogger.log( + `⚠️ WARNING ⚠️ A system with the ID "${system_id}" already exists and will be overriden` + ); + override = true; + } + this.systems[system_id] = new system(this.root); + + if (!override) { + if (before) { + const i = this.systemUpdateOrder.indexOf(before); + if (i !== -1) { + this.systemUpdateOrder.splice(i, 0, system_id); + continue; + } + GeoZLogger.log( + `⚠️ WARNING ⚠️ System "${before}" not found and so system "${system_id}" can't be updated before it` + ); + } + + if (after) { + const i = this.systemUpdateOrder.indexOf(after); + if (i !== -1) { + this.systemUpdateOrder.splice(i + 1, 0, system_id); + continue; + } + GeoZLogger.log( + `⚠️ WARNING ⚠️ System "${after}" not found and so system "${system_id}" can't be updated after it` + ); + } + } + + if (!this.systemUpdateOrder.includes(system_id)) { + this.systemUpdateOrder.push(system_id); + } + + if (override) { + GeoZLogger.log( + `System "${system_id}" update order : ${this.systemUpdateOrder.indexOf(system_id)}` + ); + } + } + logger.log("📦 There are", this.systemUpdateOrder.length, "game systems"); } diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 71817ebd..049062ee 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -2,11 +2,13 @@ import { globalConfig } from "../core/config"; import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils"; import { BasicSerializableObject, types } from "../savegame/serialization"; import { enumColors } from "./colors"; +import { allShapeData } from "./shapes"; import { enumItemProcessorTypes } from "./components/item_processor"; import { GameRoot } from "./root"; -import { enumSubShape, ShapeDefinition } from "./shape_definition"; +import { ShapeDefinition, ShapeLayer } from "./shape_definition"; import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals"; import { UPGRADES } from "./upgrades"; +import { ModProcessors } from "../GeoZ/main"; export class HubGoals extends BasicSerializableObject { static getId() { @@ -324,16 +326,16 @@ export class HubGoals extends BasicSerializableObject { */ createRandomShape() { const layerCount = clamp(this.level / 25, 2, 4); - /** @type {Array} */ + /** @type {Array} */ let layers = []; const randomColor = () => randomChoice(Object.values(enumColors)); - const randomShape = () => randomChoice(Object.values(enumSubShape)); + const randomShape = () => randomChoice(Object.values(allShapeData).map(d => d.id)); let anyIsMissingTwo = false; for (let i = 0; i < layerCount; ++i) { - /** @type {import("./shape_definition").ShapeLayer} */ + /** @type {ShapeLayer} */ const layer = [null, null, null, null]; for (let quad = 0; quad < 4; ++quad) { @@ -439,8 +441,17 @@ export class HubGoals extends BasicSerializableObject { globalConfig.buildingSpeeds[processorType] ); } - default: + default: { + if (ModProcessors[processorType]) { + return ( + globalConfig.beltSpeedItemsPerSecond * + this.upgradeImprovements.processors * + globalConfig.buildingSpeeds[processorType] + ); + } + assertAlways(false, "invalid processor type: " + processorType); + } } return 1 / globalConfig.beltSpeedItemsPerSecond; diff --git a/src/js/game/hud/parts/buildings_toolbar.js b/src/js/game/hud/parts/buildings_toolbar.js index f8953204..82562074 100644 --- a/src/js/game/hud/parts/buildings_toolbar.js +++ b/src/js/game/hud/parts/buildings_toolbar.js @@ -13,8 +13,12 @@ import { MetaLeverBuilding } from "../../buildings/lever"; import { MetaFilterBuilding } from "../../buildings/filter"; import { MetaDisplayBuilding } from "../../buildings/display"; import { MetaReaderBuilding } from "../../buildings/reader"; +import { MetaBuilding } from "../../meta_building"; -const supportedBuildings = [ +/** + * @type {Array} + */ +export const supportedBuildings = [ MetaBeltBaseBuilding, MetaSplitterBuilding, MetaUndergroundBeltBuilding, diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 7140c927..ab1bd8bc 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -7,7 +7,7 @@ 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 key(str) { return str.toUpperCase().charCodeAt(0); } diff --git a/src/js/game/map_chunk.js b/src/js/game/map_chunk.js index 54af1125..773499e8 100644 --- a/src/js/game/map_chunk.js +++ b/src/js/game/map_chunk.js @@ -5,10 +5,11 @@ import { clamp, fastArrayDeleteValueIfContained, make2DUndefinedArray } from ".. import { Vector } from "../core/vector"; import { BaseItem } from "./base_item"; import { enumColors } from "./colors"; +import { allShapeData } from "./shapes"; import { Entity } from "./entity"; import { COLOR_ITEM_SINGLETONS } from "./items/color_item"; import { GameRoot } from "./root"; -import { enumSubShape } from "./shape_definition"; +import { enumSubShape } from "./shapes"; import { Rectangle } from "../core/rectangle"; const logger = createLogger("map_chunk"); @@ -180,56 +181,58 @@ export class MapChunk { */ internalGenerateShapePatch(rng, shapePatchSize, distanceToOriginInChunks) { /** @type {[enumSubShape, enumSubShape, enumSubShape, enumSubShape]} */ - let subShapes = null; + let quads = null; let weights = {}; - - // Later there is a mix of everything - weights = { - [enumSubShape.rect]: 100, - [enumSubShape.circle]: Math.round(50 + clamp(distanceToOriginInChunks * 2, 0, 50)), - [enumSubShape.star]: Math.round(20 + clamp(distanceToOriginInChunks, 0, 30)), - [enumSubShape.windmill]: Math.round(6 + clamp(distanceToOriginInChunks / 2, 0, 20)), - }; - - if (distanceToOriginInChunks < 7) { - // Initial chunks can not spawn the good stuff - weights[enumSubShape.star] = 0; - weights[enumSubShape.windmill] = 0; - } - - if (distanceToOriginInChunks < 10) { - // Initial chunk patches always have the same shape - const subShape = this.internalGenerateRandomSubShape(rng, weights); - subShapes = [subShape, subShape, subShape, subShape]; - } else if (distanceToOriginInChunks < 15) { - // Later patches can also have mixed ones - const subShapeA = this.internalGenerateRandomSubShape(rng, weights); - const subShapeB = this.internalGenerateRandomSubShape(rng, weights); - subShapes = [subShapeA, subShapeA, subShapeB, subShapeB]; - } else { - // Finally there is a mix of everything - subShapes = [ - this.internalGenerateRandomSubShape(rng, weights), - this.internalGenerateRandomSubShape(rng, weights), - this.internalGenerateRandomSubShape(rng, weights), - this.internalGenerateRandomSubShape(rng, weights), - ]; - } - - // Makes sure windmills never spawn as whole - let windmillCount = 0; - for (let i = 0; i < subShapes.length; ++i) { - if (subShapes[i] === enumSubShape.windmill) { - ++windmillCount; + for (let s in allShapeData) { + const data = allShapeData[s]; + if (!data.spawnData || distanceToOriginInChunks < data.spawnData.minDistance) { + continue; + } + const chances = data.spawnData.chances; + const chance = Math.round( + clamp( + chances.min + (distanceToOriginInChunks - data.spawnData.minDistance) * chances.distanceMultiplier, + 0, + chances.max + ) + ); + if (chance) { + weights[data.id] = chance; } } - if (windmillCount > 1) { - subShapes[0] = enumSubShape.rect; - subShapes[1] = enumSubShape.rect; + quads = [ + this.internalGenerateRandomSubShape(rng, weights), + this.internalGenerateRandomSubShape(rng, weights), + this.internalGenerateRandomSubShape(rng, weights), + this.internalGenerateRandomSubShape(rng, weights), + ]; + if (distanceToOriginInChunks < 10) { + // Initial chunk patches always have the same shape + quads = [quads[0], quads[0], quads[0], quads[0]]; + } else if (distanceToOriginInChunks < 15) { + // Later patches can also have mixed ones + quads = [quads[0], quads[0], quads[1], quads[1]]; + } else { + // if (quads[0] == quads[2] && quads[0] != quads[3] && quads[0] != quads[1]) { + // quads = [quads[0], quads[2], quads[1], quads[3]]; + // } + // if (quads[1] == quads[3] && quads[1] != quads[0] && quads[1] != quads[2]) { + // quads = [quads[0], quads[2], quads[1], quads[3]]; + // } } - const definition = this.root.shapeDefinitionMgr.getDefinitionFromSimpleShapes(subShapes); + if ( + quads.filter(q => q == quads[0]).length > allShapeData[quads[0]].spawnData.maxQuarters || + quads.filter(q => q == quads[1]).length > allShapeData[quads[1]].spawnData.maxQuarters || + quads.filter(q => q == quads[2]).length > allShapeData[quads[2]].spawnData.maxQuarters + ) { + return this.internalGenerateShapePatch(rng, shapePatchSize, distanceToOriginInChunks); + } + + let colors = /** @type {[string, string, string, string]} */ (quads.map(q => allShapeData[q].spawnData.color)); + + const definition = this.root.shapeDefinitionMgr.getDefinitionFromSimpleShapesAndColors(quads, colors); this.internalGeneratePatch( rng, shapePatchSize, diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js index 65b72a1a..924dbed0 100644 --- a/src/js/game/shape_definition.js +++ b/src/js/game/shape_definition.js @@ -6,6 +6,7 @@ import { Vector } from "../core/vector"; import { BasicSerializableObject, types } from "../savegame/serialization"; import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors"; import { THEME } from "./theme"; +import { allShapeData, ShapeData, enumShortcodeToSubShape, enumSubShapeToShortcode, enumSubShape } from "./shapes"; /** * @typedef {{ @@ -26,28 +27,6 @@ const arrayQuadrantIndexToOffset = [ new Vector(-1, -1), // tl ]; -/** @enum {string} */ -export const enumSubShape = { - rect: "rect", - circle: "circle", - star: "star", - windmill: "windmill", -}; - -/** @enum {string} */ -export const enumSubShapeToShortcode = { - [enumSubShape.rect]: "R", - [enumSubShape.circle]: "C", - [enumSubShape.star]: "S", - [enumSubShape.windmill]: "W", -}; - -/** @enum {enumSubShape} */ -export const enumShortcodeToSubShape = {}; -for (const key in enumSubShapeToShortcode) { - enumShortcodeToSubShape[enumSubShapeToShortcode[key]] = key; -} - /** * Converts the given parameters to a valid shape definition * @param {*} layers @@ -85,6 +64,7 @@ export class ShapeDefinition extends BasicSerializableObject { return errorCode; } const definition = ShapeDefinition.fromShortKey(data); + /** @type {Array} */ this.layers = /** @type {Array} */ (definition.layers); } @@ -336,97 +316,98 @@ export class ShapeDefinition extends BasicSerializableObject { for (let layerIndex = 0; layerIndex < this.layers.length; ++layerIndex) { const quadrants = this.layers[layerIndex]; + let quads = quadrants + .map((e, i) => ({ e, i })) + .filter(e => e.e) + .map(e => ({ ...e.e, quadrantIndex: e.i })) const layerScale = Math.max(0.1, 0.9 - layerIndex * 0.22); - for (let quadrantIndex = 0; quadrantIndex < 4; ++quadrantIndex) { - if (!quadrants[quadrantIndex]) { + for (let quad of quads) { + if (!quad) { + continue; + } + const { subShape, color, quadrantIndex } = quad; + if (subShape == "-") { continue; } - const { subShape, color } = quadrants[quadrantIndex]; const quadrantPos = arrayQuadrantIndexToOffset[quadrantIndex]; + const centerQuadrantX = quadrantPos.x * quadrantHalfSize; const centerQuadrantY = quadrantPos.y * quadrantHalfSize; const rotation = Math.radians(quadrantIndex * 90); + context.save(); context.translate(centerQuadrantX, centerQuadrantY); context.rotate(rotation); context.fillStyle = enumColorsToHexCode[color]; context.strokeStyle = THEME.items.outline; - context.lineWidth = THEME.items.outlineWidth; + const lineWidth = THEME.items.outlineWidth * Math.pow(0.8, layerIndex); + context.lineWidth = lineWidth; const insetPadding = 0.0; - switch (subShape) { - case enumSubShape.rect: { - context.beginPath(); - const dims = quadrantSize * layerScale; - context.rect( - insetPadding + -quadrantHalfSize, - -insetPadding + quadrantHalfSize - dims, - dims, - dims - ); - - break; + const dims = quadrantSize * layerScale; + const innerDims = insetPadding - quadrantHalfSize; + + let began = null; + // eslint-disable-next-line no-inner-declarations + /** @type {import("./shapes").BeginDrawShape} */ + function begin(args) { + context.save(); + context.translate(innerDims, -innerDims); + context.scale(dims, -dims); + context.lineWidth = lineWidth / dims / (args.scale || 1); + if (args.scale) { + context.scale(args.scale, args.scale); } - case enumSubShape.star: { + if (args.beginPath) { context.beginPath(); - const dims = quadrantSize * layerScale; - - 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 - moveInwards, originY + dims); - context.lineTo(originX, originY + dims); + } + if (args.moveToZero) { + context.moveTo(0, 0); + } + began = args; + } + // eslint-disable-next-line no-inner-declarations + function end() { + if (!began) { + return; + } + if (began.path) { context.closePath(); - break; - } - - case enumSubShape.windmill: { - context.beginPath(); - const dims = quadrantSize * layerScale; - - 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); } + context.restore(); } - context.fill(); - context.stroke(); + /** @type {ShapeData} */ + let shape = allShapeData[subShape]; + assertAlways(shape.draw, "shape should be drawable!"); + if (typeof shape.draw === "string") { + let draw = shape.draw; + begin({ scale: 1 }); + let p = new Path2D(draw); + context.fill(p); + context.stroke(p); + end(); + } else { + shape.draw({ + dims, + innerDims, + layer: layerIndex, + quadrant: quadrantIndex, + context, + color, + begin, + }); + end(); + context.fill(); + context.stroke(); + } - context.rotate(-rotation); - context.translate(-centerQuadrantX, -centerQuadrantY); + context.restore(); } } } diff --git a/src/js/game/shape_definition_manager.js b/src/js/game/shape_definition_manager.js index ef0d592f..7e80bb6e 100644 --- a/src/js/game/shape_definition_manager.js +++ b/src/js/game/shape_definition_manager.js @@ -3,7 +3,8 @@ import { BasicSerializableObject } from "../savegame/serialization"; import { enumColors } from "./colors"; import { ShapeItem } from "./items/shape_item"; import { GameRoot } from "./root"; -import { enumSubShape, ShapeDefinition } from "./shape_definition"; +import { ShapeDefinition } from "./shape_definition"; +import { enumSubShape } from "./shapes"; const logger = createLogger("shape_definition_manager"); @@ -256,4 +257,18 @@ export class ShapeDefinitionManager extends BasicSerializableObject { return this.registerOrReturnHandle(new ShapeDefinition({ layers: [shapeLayer] })); } + + /** + * + * @param {[enumSubShape, enumSubShape, enumSubShape, enumSubShape]} subShapes + * @param {[string, string, string, string]} colors + * @returns {ShapeDefinition} + */ + getDefinitionFromSimpleShapesAndColors(subShapes, colors) { + const shapeLayer = /** @type {import("./shape_definition").ShapeLayer} */ (subShapes.map( + (subShape, i) => ({ subShape, color: colors[i] }) + )); + + return this.registerOrReturnHandle(new ShapeDefinition({ layers: [shapeLayer] })); + } } diff --git a/src/js/game/shapes.js b/src/js/game/shapes.js new file mode 100644 index 00000000..1e3b66bb --- /dev/null +++ b/src/js/game/shapes.js @@ -0,0 +1,181 @@ +/** @enum {string} */ +export const enumSubShape = { + rect: "rect", + circle: "circle", + star: "star", + windmill: "windmill", +}; + +/** @enum {string} */ +export const enumSubShapeToShortcode = { + [enumSubShape.rect]: "R", + [enumSubShape.circle]: "C", + [enumSubShape.star]: "S", + [enumSubShape.windmill]: "W", +}; + +/** @enum {enumSubShape} */ +export const enumShortcodeToSubShape = {}; +for (const key in enumSubShapeToShortcode) { + enumShortcodeToSubShape[enumSubShapeToShortcode[key]] = key; +} + +/** + * @callback BeginDrawShape + * @param {{ + * scale?: number, + * beginPath?: boolean, + * moveToZero?: true + * }} args + */ + +/** + * @typedef {Object} DrawShapeParams + * @property {number} dims + * @property {number} innerDims + * @property {number} layer + * @property {number} quadrant + * @property {CanvasRenderingContext2D} context + * @property {string} color + * @property {BeginDrawShape} begin + */ + +/** + * @callback DrawShape + * @param {DrawShapeParams} args + */ + +/** + * @typedef {Object} SpawnChanceData + * @property {number} [min=0] + * @property {number} [max=100] + * @property {number} [distanceMultiplier=1] + */ + +/** + * @typedef {Object} ShapeSpawnData + * @property {string} [color="uncolored"] + * @property {number} [minDistance=0] + * @property {number} [maxQuarters=4] + * @property {SpawnChanceData} [chances] + */ + +/** + * @typedef {Object} ShapeData + * @property {string} id + * @property {string} code + * @property {DrawShape | string} draw + * @property {number} tier + * @property {ShapeSpawnData} [spawnData] + */ + +/** @type {Object} */ +export const allShapeData = { + rect: { + id: "rect", + code: "R", + draw: "M 0 0 v 1 h 1 v -1 z", + tier: 0, + spawnData: { + color: "uncolored", + maxQuarters: 4, + minDistance: 0, + chances: { + min: 100, + distanceMultiplier: 0, + max: 100, + }, + }, + }, + circle: { + id: "circle", + code: "C", + draw: "M 0 0 l 1 0 a 1 1 0 0 1 -1 1 z ", + tier: 0, + spawnData: { + color: "uncolored", + maxQuarters: 4, + minDistance: 0, + chances: { + min: 50, + distanceMultiplier: 15, + max: 100, + }, + }, + }, + star: { + id: "star", + code: "S", + draw: "M 0 0 L 0 0.6 1 1 0.6 0 z", + tier: 0.5, + spawnData: { + color: "uncolored", + maxQuarters: 4, + minDistance: 5, + chances: { + min: 20, + distanceMultiplier: 10, + max: 100, + }, + }, + }, + windmill: { + id: "windmill", + code: "W", + draw: "M 0 0 L 0 0.6 1 1 1 0 z", + tier: 1, + spawnData: { + color: "uncolored", + maxQuarters: 3, + minDistance: 7, + chances: { + min: 20, + distanceMultiplier: 5, + max: 100, + }, + }, + }, +}; + +export function initShapes() { + for (let k in enumSubShape) { + delete enumSubShape[k]; + } + for (let k in enumSubShapeToShortcode) { + delete enumSubShapeToShortcode[k]; + } + for (let k in enumShortcodeToSubShape) { + delete enumShortcodeToSubShape[k]; + } + + for (let s in allShapeData) { + let data = allShapeData[s]; + assert(data.id == s); + assert(data.code.toUpperCase() == data.code); + assert(data.draw); + + enumSubShape[data.id] = data.id; + enumSubShapeToShortcode[data.id] = data.code; + enumShortcodeToSubShape[data.code] = data.id; + + if (data.spawnData) { + const sdata = data.spawnData; + sdata.color = sdata.color || "uncolored"; + sdata.maxQuarters = sdata.maxQuarters || 4; + sdata.minDistance = sdata.minDistance || 0; + + if(sdata.chances) { + const chances = sdata.chances; + chances.min = chances.min || 0; + chances.max = chances.max || 100; + chances.distanceMultiplier = chances.distanceMultiplier || 1; + } else { + sdata.chances = { + min: 0, + max: 100, + distanceMultiplier: 1 + }; + } + } + } +} diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index d58aa697..6fd09865 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -238,8 +238,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const items = processorComp.inputSlots; processorComp.inputSlots = []; - /** @type {Object.} */ - const itemsBySlot = {}; + /** @type {Array<{ item: BaseItem, sourceSlot: number }>} */ + const itemsBySlot = []; for (let i = 0; i < items.length; ++i) { itemsBySlot[items[i].sourceSlot] = items[i]; } @@ -251,292 +251,309 @@ export class ItemProcessorSystem extends GameSystemWithFilter { let trackProduction = true; // DO SOME MAGIC + const ModProcessors = require("../../GeoZ/main").ModProcessors; + if (ModProcessors[processorComp.type]) { + trackProduction = ModProcessors[processorComp.type].process({ + items: itemsBySlot.map(e => e.item), + itemsBySlot, + itemsRaw: items, + entity, + outItems, + system: this, + }); + } else { + switch (processorComp.type) { + // SPLITTER + case enumItemProcessorTypes.splitterWires: + case enumItemProcessorTypes.splitter: { + trackProduction = false; + const availableSlots = entity.components.ItemEjector.slots.length; - switch (processorComp.type) { - // SPLITTER - case enumItemProcessorTypes.splitterWires: - case enumItemProcessorTypes.splitter: { - trackProduction = false; - const availableSlots = entity.components.ItemEjector.slots.length; - - let nextSlot = processorComp.nextOutputSlot++ % availableSlots; - for (let i = 0; i < items.length; ++i) { - outItems.push({ - item: items[i].item, - preferredSlot: (nextSlot + i) % availableSlots, - }); - } - break; - } - - // CUTTER - case enumItemProcessorTypes.cutter: { - const inputItem = /** @type {ShapeItem} */ (items[0].item); - assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); - const inputDefinition = inputItem.definition; - - const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutHalf(inputDefinition); - - for (let i = 0; i < cutDefinitions.length; ++i) { - const definition = cutDefinitions[i]; - if (!definition.isEntirelyEmpty()) { + let nextSlot = processorComp.nextOutputSlot++ % availableSlots; + for (let i = 0; i < items.length; ++i) { outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition), - requiredSlot: i, + item: items[i].item, + preferredSlot: (nextSlot + i) % availableSlots, }); } + break; } - break; - } + // CUTTER + case enumItemProcessorTypes.cutter: { + const inputItem = /** @type {ShapeItem} */ (items[0].item); + assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); + const inputDefinition = inputItem.definition; - // CUTTER (Quad) - case enumItemProcessorTypes.cutterQuad: { - const inputItem = /** @type {ShapeItem} */ (items[0].item); - assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); - const inputDefinition = inputItem.definition; + const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutHalf(inputDefinition); - const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutQuad(inputDefinition); - - for (let i = 0; i < cutDefinitions.length; ++i) { - const definition = cutDefinitions[i]; - if (!definition.isEntirelyEmpty()) { - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition), - requiredSlot: i, - }); + for (let i = 0; i < cutDefinitions.length; ++i) { + const definition = cutDefinitions[i]; + if (!definition.isEntirelyEmpty()) { + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition), + requiredSlot: i, + }); + } } + + break; } - break; - } + // CUTTER (Quad) + case enumItemProcessorTypes.cutterQuad: { + const inputItem = /** @type {ShapeItem} */ (items[0].item); + assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); + const inputDefinition = inputItem.definition; - // ROTATER - case enumItemProcessorTypes.rotater: { - const inputItem = /** @type {ShapeItem} */ (items[0].item); - assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); - const inputDefinition = inputItem.definition; + const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutQuad(inputDefinition); - const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(inputDefinition); - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition), - }); - break; - } - - // ROTATER (CCW) - case enumItemProcessorTypes.rotaterCCW: { - const inputItem = /** @type {ShapeItem} */ (items[0].item); - assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); - const inputDefinition = inputItem.definition; - - const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCCW(inputDefinition); - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition), - }); - break; - } - - // ROTATER (FL) - case enumItemProcessorTypes.rotaterFL: { - const inputItem = /** @type {ShapeItem} */ (items[0].item); - assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); - const inputDefinition = inputItem.definition; - - const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateFL(inputDefinition); - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition), - }); - break; - } - - // STACKER - - case enumItemProcessorTypes.stacker: { - const lowerItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); - const upperItem = /** @type {ShapeItem} */ (itemsBySlot[1].item); - - assert(lowerItem instanceof ShapeItem, "Input for lower stack is not a shape"); - assert(upperItem instanceof ShapeItem, "Input for upper stack is not a shape"); - - const stackedDefinition = this.root.shapeDefinitionMgr.shapeActionStack( - lowerItem.definition, - upperItem.definition - ); - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(stackedDefinition), - }); - break; - } - - // TRASH - - case enumItemProcessorTypes.trash: { - // Well this one is easy .. simply do nothing with the item - break; - } - - // MIXER - - case enumItemProcessorTypes.mixer: { - // Find both colors and combine them - const item1 = /** @type {ColorItem} */ (items[0].item); - const item2 = /** @type {ColorItem} */ (items[1].item); - assert(item1 instanceof ColorItem, "Input for color mixer is not a color"); - assert(item2 instanceof ColorItem, "Input for color mixer is not a color"); - - const color1 = item1.color; - const color2 = item2.color; - - // Try finding mixer color, and if we can't mix it we simply return the same color - const mixedColor = enumColorMixingResults[color1][color2]; - let resultColor = color1; - if (mixedColor) { - resultColor = mixedColor; - } - outItems.push({ - item: COLOR_ITEM_SINGLETONS[resultColor], - }); - - break; - } - - // PAINTER - - case enumItemProcessorTypes.painter: { - const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); - const colorItem = /** @type {ColorItem} */ (itemsBySlot[1].item); - - const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith( - shapeItem.definition, - colorItem.color - ); - - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition), - }); - - break; - } - - // PAINTER (DOUBLE) - - case enumItemProcessorTypes.painterDouble: { - const shapeItem1 = /** @type {ShapeItem} */ (itemsBySlot[0].item); - const shapeItem2 = /** @type {ShapeItem} */ (itemsBySlot[1].item); - const colorItem = /** @type {ColorItem} */ (itemsBySlot[2].item); - - assert(shapeItem1 instanceof ShapeItem, "Input for painter is not a shape"); - assert(shapeItem2 instanceof ShapeItem, "Input for painter is not a shape"); - assert(colorItem instanceof ColorItem, "Input for painter is not a color"); - - const colorizedDefinition1 = this.root.shapeDefinitionMgr.shapeActionPaintWith( - shapeItem1.definition, - colorItem.color - ); - - const colorizedDefinition2 = this.root.shapeDefinitionMgr.shapeActionPaintWith( - shapeItem2.definition, - colorItem.color - ); - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition1), - }); - - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition2), - }); - - break; - } - - // PAINTER (QUAD) - - case enumItemProcessorTypes.painterQuad: { - const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); - assert(shapeItem instanceof ShapeItem, "Input for painter is not a shape"); - - /** @type {Array} */ - const colors = [null, null, null, null]; - for (let i = 0; i < 4; ++i) { - if (itemsBySlot[i + 1]) { - colors[i] = /** @type {ColorItem} */ (itemsBySlot[i + 1].item).color; + for (let i = 0; i < cutDefinitions.length; ++i) { + const definition = cutDefinitions[i]; + if (!definition.isEntirelyEmpty()) { + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition), + requiredSlot: i, + }); + } } + + break; } - const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors( - shapeItem.definition, - /** @type {[string, string, string, string]} */ (colors) - ); + // ROTATER + case enumItemProcessorTypes.rotater: { + const inputItem = /** @type {ShapeItem} */ (items[0].item); + assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); + const inputDefinition = inputItem.definition; - outItems.push({ - item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition), - }); - break; - } - - // FILTER - case enumItemProcessorTypes.filter: { - // TODO - trackProduction = false; - - const item = itemsBySlot[0].item; - - const network = entity.components.WiredPins.slots[0].linkedNetwork; - if (!network || !network.currentValue) { + const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW( + inputDefinition + ); outItems.push({ - item, - requiredSlot: 1, + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition), }); break; } - const value = network.currentValue; - if (value.equals(BOOL_TRUE_SINGLETON) || value.equals(item)) { + // ROTATER (CCW) + case enumItemProcessorTypes.rotaterCCW: { + const inputItem = /** @type {ShapeItem} */ (items[0].item); + assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); + const inputDefinition = inputItem.definition; + + const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCCW( + inputDefinition + ); outItems.push({ - item, - requiredSlot: 0, - }); - } else { - outItems.push({ - item, - requiredSlot: 1, + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition), }); + break; } - break; - } + // ROTATER (FL) + case enumItemProcessorTypes.rotaterFL: { + const inputItem = /** @type {ShapeItem} */ (items[0].item); + assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); + const inputDefinition = inputItem.definition; - // READER - case enumItemProcessorTypes.reader: { - // Pass through the item - const item = itemsBySlot[0].item; - outItems.push({ item }); - - // Track the item - const readerComp = entity.components.BeltReader; - readerComp.lastItemTimes.push(this.root.time.now()); - readerComp.lastItem = item; - break; - } - - // HUB - case enumItemProcessorTypes.hub: { - trackProduction = false; - - const hubComponent = entity.components.Hub; - assert(hubComponent, "Hub item processor has no hub component"); - - for (let i = 0; i < items.length; ++i) { - const item = /** @type {ShapeItem} */ (items[i].item); - this.root.hubGoals.handleDefinitionDelivered(item.definition); + const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateFL( + inputDefinition + ); + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition), + }); + break; } - break; - } + // STACKER - default: - assertAlways(false, "Unkown item processor type: " + processorComp.type); + case enumItemProcessorTypes.stacker: { + const lowerItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); + const upperItem = /** @type {ShapeItem} */ (itemsBySlot[1].item); + + assert(lowerItem instanceof ShapeItem, "Input for lower stack is not a shape"); + assert(upperItem instanceof ShapeItem, "Input for upper stack is not a shape"); + + const stackedDefinition = this.root.shapeDefinitionMgr.shapeActionStack( + lowerItem.definition, + upperItem.definition + ); + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(stackedDefinition), + }); + break; + } + + // TRASH + + case enumItemProcessorTypes.trash: { + // Well this one is easy .. simply do nothing with the item + break; + } + + // MIXER + + case enumItemProcessorTypes.mixer: { + // Find both colors and combine them + const item1 = /** @type {ColorItem} */ (items[0].item); + const item2 = /** @type {ColorItem} */ (items[1].item); + assert(item1 instanceof ColorItem, "Input for color mixer is not a color"); + assert(item2 instanceof ColorItem, "Input for color mixer is not a color"); + + const color1 = item1.color; + const color2 = item2.color; + + // Try finding mixer color, and if we can't mix it we simply return the same color + const mixedColor = enumColorMixingResults[color1][color2]; + let resultColor = color1; + if (mixedColor) { + resultColor = mixedColor; + } + outItems.push({ + item: COLOR_ITEM_SINGLETONS[resultColor], + }); + + break; + } + + // PAINTER + + case enumItemProcessorTypes.painter: { + const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); + const colorItem = /** @type {ColorItem} */ (itemsBySlot[1].item); + + const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith( + shapeItem.definition, + colorItem.color + ); + + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition), + }); + + break; + } + + // PAINTER (DOUBLE) + + case enumItemProcessorTypes.painterDouble: { + const shapeItem1 = /** @type {ShapeItem} */ (itemsBySlot[0].item); + const shapeItem2 = /** @type {ShapeItem} */ (itemsBySlot[1].item); + const colorItem = /** @type {ColorItem} */ (itemsBySlot[2].item); + + assert(shapeItem1 instanceof ShapeItem, "Input for painter is not a shape"); + assert(shapeItem2 instanceof ShapeItem, "Input for painter is not a shape"); + assert(colorItem instanceof ColorItem, "Input for painter is not a color"); + + const colorizedDefinition1 = this.root.shapeDefinitionMgr.shapeActionPaintWith( + shapeItem1.definition, + colorItem.color + ); + + const colorizedDefinition2 = this.root.shapeDefinitionMgr.shapeActionPaintWith( + shapeItem2.definition, + colorItem.color + ); + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition1), + }); + + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition2), + }); + + break; + } + + // PAINTER (QUAD) + + case enumItemProcessorTypes.painterQuad: { + const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); + assert(shapeItem instanceof ShapeItem, "Input for painter is not a shape"); + + /** @type {Array} */ + const colors = [null, null, null, null]; + for (let i = 0; i < 4; ++i) { + if (itemsBySlot[i + 1]) { + colors[i] = /** @type {ColorItem} */ (itemsBySlot[i + 1].item).color; + } + } + + const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors( + shapeItem.definition, + /** @type {[string, string, string, string]} */ (colors) + ); + + outItems.push({ + item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition), + }); + break; + } + + // FILTER + case enumItemProcessorTypes.filter: { + // TODO + trackProduction = false; + + const item = itemsBySlot[0].item; + + const network = entity.components.WiredPins.slots[0].linkedNetwork; + if (!network || !network.currentValue) { + outItems.push({ + item, + requiredSlot: 1, + }); + break; + } + + const value = network.currentValue; + if (value.equals(BOOL_TRUE_SINGLETON) || value.equals(item)) { + outItems.push({ + item, + requiredSlot: 0, + }); + } else { + outItems.push({ + item, + requiredSlot: 1, + }); + } + + break; + } + + // READER + case enumItemProcessorTypes.reader: { + // Pass through the item + const item = itemsBySlot[0].item; + outItems.push({ item }); + + // Track the item + const readerComp = entity.components.BeltReader; + readerComp.lastItemTimes.push(this.root.time.now()); + readerComp.lastItem = item; + break; + } + + // HUB + case enumItemProcessorTypes.hub: { + trackProduction = false; + + const hubComponent = entity.components.Hub; + assert(hubComponent, "Hub item processor has no hub component"); + + for (let i = 0; i < items.length; ++i) { + const item = /** @type {ShapeItem} */ (items[i].item); + this.root.hubGoals.handleDefinitionDelivered(item.definition); + } + + break; + } + + default: + assertAlways(false, "Unkown item processor type: " + processorComp.type); + } } // Track produced items diff --git a/src/js/game/systems/logic_gate.js b/src/js/game/systems/logic_gate.js index 3bfc20cd..a8440418 100644 --- a/src/js/game/systems/logic_gate.js +++ b/src/js/game/systems/logic_gate.js @@ -25,6 +25,12 @@ export class LogicGateSystem extends GameSystemWithFilter { [enumLogicGateType.unstacker]: this.compute_UNSTACK.bind(this), [enumLogicGateType.shapecompare]: this.compute_SHAPECOMPARE.bind(this), }; + + const { ModWireProcessors } = require("../../GeoZ/main"); + for (const type in ModWireProcessors) { + //@ts-ignore + this.boundOperations[type] = ModWireProcessors[type].compute.bind(null, this); + } } update() { diff --git a/src/js/main.js b/src/js/main.js index 5b9df699..fd15f090 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -10,6 +10,7 @@ import { initDrawUtils } from "./core/draw_utils"; import { initItemRegistry } from "./game/item_registry"; import { initMetaBuildingRegistry } from "./game/meta_building_registry"; import { initGameSpeedRegistry } from "./game/game_speed_registry"; +import { initMods } from "./GeoZ/main"; const logger = createLogger("main"); @@ -18,37 +19,6 @@ 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( G_BUILD_TIME @@ -77,12 +47,6 @@ console.log("%cDEVCODE BUILT IN", "color: #f77"); logSection("Boot Process", "#f9a825"); -initDrawUtils(); -initComponentRegistry(); -initItemRegistry(); -initMetaBuildingRegistry(); -initGameSpeedRegistry(); - let app = null; function bootApp() { @@ -91,4 +55,4 @@ function bootApp() { app.boot(); } -window.addEventListener("load", bootApp); +window.addEventListener("load", bootApp); \ No newline at end of file diff --git a/src/js/profile/application_settings.js b/src/js/profile/application_settings.js index 084a6fe7..0c00807e 100644 --- a/src/js/profile/application_settings.js +++ b/src/js/profile/application_settings.js @@ -10,6 +10,7 @@ import { THEMES, THEME, applyGameTheme } from "../game/theme"; import { IS_DEMO } from "../core/config"; import { T } from "../translations"; import { LANGUAGES } from "../languages"; +import { globalConfig, IS_DEBUG } from "../core/config"; const logger = createLogger("application_settings"); @@ -21,6 +22,8 @@ export const enumCategories = { userInterface: "userInterface", performance: "performance", advanced: "advanced", + debug: "debug", + keybindings: "keybindings", }; export const uiScales = [ @@ -278,6 +281,21 @@ export const allApplicationSettings = [ new BoolSetting("lowQualityTextures", enumCategories.performance, (app, value) => {}), ]; +if (IS_DEBUG) { + for (let k in globalConfig.debug) { + if (k.startsWith("_")) continue; + const setting = new BoolSetting(`debug_${k}`, enumCategories.debug, (app, value) => { + globalConfig.debug[k] = value; + }); + setting.validate = () => true; + T.settings.labels[`debug_${k}`] = { + title: k.replace(/(?!^)([A-Z])/g, " $1"), + description: globalConfig.debug[`_${k}`], + }; + allApplicationSettings.push(setting); + } +} + export function getApplicationSettingById(id) { return allApplicationSettings.find(setting => setting.id === id); } @@ -358,7 +376,9 @@ export class ApplicationSettings extends ReadWriteProxy { * @param {string} key */ getSetting(key) { - assert(this.getAllSettings().hasOwnProperty(key), "Setting not known: " + key); + if (!key.startsWith("debug_")) { + assert(this.getAllSettings().hasOwnProperty(key), "Setting not known: " + key); + } return this.getAllSettings()[key]; } diff --git a/src/js/profile/setting_types.js b/src/js/profile/setting_types.js index 7d07ca99..46bba66d 100644 --- a/src/js/profile/setting_types.js +++ b/src/js/profile/setting_types.js @@ -185,6 +185,10 @@ export class BoolSetting extends BaseSetting { } getHtml() { + if (!T.settings.labels[this.id].description) { + let a = T; + let b = a; + } return `
${this.enabled ? "" : `${T.demo.settingNotAvailable}`} diff --git a/src/js/states/keybindings.js b/src/js/states/keybindings.js index b68626c7..d2ba27c9 100644 --- a/src/js/states/keybindings.js +++ b/src/js/states/keybindings.js @@ -4,39 +4,58 @@ import { T } from "../translations"; import { KEYMAPPINGS, getStringForKeyCode } from "../game/key_action_mapper"; import { Dialog } from "../core/modal_dialog_elements"; import { IS_DEMO } from "../core/config"; +import { SettingsState } from "./settings"; -export class KeybindingsState extends TextualGameState { - constructor() { - super("KeybindingsState"); - } +export class KeybindingsState extends SettingsState { + // constructor() { + // super(); + // super("KeybindingsState"); + // this.settingsState = settingsState; + // } - getStateHeaderTitle() { - return T.keybindings.title; - } + // getStateHeaderTitle() { + // return T.keybindings.title; + // } getMainContentHTML() { return ` +