1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Merge branch 'GeoZ' into component-editor

This commit is contained in:
dengr1065 2020-09-17 15:14:41 +03:00 committed by GitHub
commit 94f32ad226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2186 additions and 730 deletions

View File

@ -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"],

View File

@ -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",
},

View File

@ -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 {

View File

@ -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;
}
}*/
}
}

221
src/js/GeoZ/main.js Normal file
View File

@ -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<typeof MetaModBuilding>=} buildings
* @property {Array<typeof ModComponent>=} components
* @property {Array<typeof ModItem>=} items
* @property {Array<typeof ModProcessor>=} processors
* @property {Array<typeof ModWireProcessor>=} wireProcessors
* @property {Array<typeof ModSystem | typeof ModSystemWithFilter>=} systems
* @property {Array<ShapeData>=} shapes
*/
export const logger = createLogger("GeoZ");
/** @type {Array<Mod>} */
export const Mods = [];
/** @type {Array<typeof ModComponent>} */
export const ModComponents = [];
/** @type {Array<typeof ModSystem | typeof ModSystemWithFilter>} */
export const ModSystems = [];
/** @type {Object.<string, typeof ModProcessor>} */
export const ModProcessors = {};
/** @type {Object.<string, typeof ModWireProcessor>} */
export const ModWireProcessors = {};
/** @type {Array<typeof ModItem>} */
export const ModItems = [];
/** @type {Array<typeof MetaModBuilding>} */
export const ModBuildings = [];
/** @type {Array<ShapeData>} */
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<string>} */
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`);
}

157
src/js/GeoZ/mod_building.js Normal file
View File

@ -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<SpriteTypesMetas>
* [variant: string]: Array<SpriteTypesMetas>
* }} 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<String>}
*/
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<string, AtlasSprite>} */
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;
}
}

View File

@ -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;
}
}

5
src/js/GeoZ/mod_item.js Normal file
View File

@ -0,0 +1,5 @@
import { BaseItem } from "../game/base_item";
export class ModItem extends BaseItem {
}

View File

@ -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<BaseItem>,
* 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;
}
}

121
src/js/GeoZ/mod_system.js Normal file
View File

@ -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<typeof Component>}
*/
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<typeof Component>} */ (Object.getPrototypeOf(this).getRequiredComponents());
this.requiredComponentIds = this.requiredComponents.map(component => component.getId());
}
}

30
src/js/GeoZ/mod_utils.js Normal file
View File

@ -0,0 +1,30 @@
/**
* Returns a file as a data URI
* @param {string} url
* @returns {Promise<String>}
*/
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);
}

View File

@ -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<BaseItem|null>} parameters
* @param {LogicGateSystem} system
* @returns {Array<BaseItem>|BaseItem}
*/
static compute(system, parameters) {
abstract;
return [];
}
}

View File

@ -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<BaseItem|null>} parameters
* @param {LogicGateSystem} system
* @returns {Array<BaseItem>|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<BaseItem|null>} parameters
* @param {LogicGateSystem} system
* @returns {Array<BaseItem>|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<BaseItem|null>} parameters
* @param {LogicGateSystem} system
* @returns {Array<BaseItem>|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<BaseItem|null>} parameters
* @param {LogicGateSystem} system
* @returns {Array<BaseItem>|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;

View File

@ -145,7 +145,7 @@ export class Application {
MobileWarningState,
MainMenuState,
InGameState,
SettingsState,
//SettingsState,
KeybindingsState,
AboutState,
ChangelogState,

View File

@ -93,7 +93,7 @@ export const globalConfig = {
},
rendering: {},
debug: require("./config.local").default,
debug: G_IS_DEV ? require("./config.local").default : {},
// Secret vars
info: {

View File

@ -2,98 +2,98 @@ 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,
_fastGameEnter: "Quickly enters the game and skips the main menu - good for fast iterating",
fastGameEnter: false,
// -----------------------------------------------------------------------------------
// Skips any delays like transitions between states and such
// noArtificialDelays: true,
_noArtificialDelays: "Skips any delays like transitions between states and such",
noArtificialDelays: false,
// -----------------------------------------------------------------------------------
// Disables writing of savegames, useful for testing the same savegame over and over
// disableSavegameWrite: true,
_disableSavegameWrite: "Disables writing of savegames, useful for testing the same savegame over and over",
disableSavegameWrite: false,
// -----------------------------------------------------------------------------------
// Shows bounds of all entities
// showEntityBounds: true,
_showEntityBounds: "Shows bounds of all entities",
showEntityBounds: false,
// -----------------------------------------------------------------------------------
// Shows arrows for every ejector / acceptor
// showAcceptorEjectors: true,
_showAcceptorEjectors: "Shows arrows for every ejector / acceptor",
showAcceptorEjectors: false,
// -----------------------------------------------------------------------------------
// Disables the music (Overrides any setting, can cause weird behaviour)
// disableMusic: true,
_disableMusic: "Disables the music (Overrides any setting, can cause weird behaviour)",
disableMusic: false,
// -----------------------------------------------------------------------------------
// Do not render static map entities (=most buildings)
// doNotRenderStatics: true,
_doNotRenderStatics: "Do not render static map entities (=most buildings)",
doNotRenderStatics: false,
// -----------------------------------------------------------------------------------
// Allow to zoom freely without limits
// disableZoomLimits: true,
_disableZoomLimits: "Allow to zoom freely without limits",
disableZoomLimits: false,
// -----------------------------------------------------------------------------------
// Shows a border arround every chunk
// showChunkBorders: true,
_showChunkBorders: "Shows a border arround every chunk",
showChunkBorders: false,
// -----------------------------------------------------------------------------------
// All rewards can be unlocked by passing just 1 of any shape
// rewardsInstant: true,
_rewardsInstant: "All rewards can be unlocked by passing just 1 of any shape",
rewardsInstant: false,
// -----------------------------------------------------------------------------------
// Unlocks all buildings
// allBuildingsUnlocked: true,
_allBuildingsUnlocked: "Unlocks all buildings",
allBuildingsUnlocked: false,
// -----------------------------------------------------------------------------------
// Disables cost of blueprints
// blueprintsNoCost: true,
_blueprintsNoCost: "Disables cost of blueprints",
blueprintsNoCost: false,
// -----------------------------------------------------------------------------------
// Disables cost of upgrades
// upgradesNoCost: true,
_upgradesNoCost: "Disables cost of upgrades",
upgradesNoCost: false,
// -----------------------------------------------------------------------------------
// Disables the dialog when completing a level
// disableUnlockDialog: true,
_disableUnlockDialog: "Disables the dialog when completing a level",
disableUnlockDialog: false,
// -----------------------------------------------------------------------------------
// Disables the simulation - This effectively pauses the game.
// disableLogicTicks: true,
_disableLogicTicks: "Disables the simulation - This effectively pauses the game.",
disableLogicTicks: false,
// -----------------------------------------------------------------------------------
// Test the rendering if everything is clipped out properly
// testClipping: true,
_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,
// -----------------------------------------------------------------------------------
// Replace all translations with emojis to see which texts are translateable
// testTranslations: true,
_testTranslations: "Replace all translations with emojis to see which texts are translateable",
testTranslations: false,
// -----------------------------------------------------------------------------------
// Enables an inspector which shows information about the entity below the curosr
// enableEntityInspector: true,
_enableEntityInspector: "Enables an inspector which shows information about the entity below the curosr",
enableEntityInspector: false,
// -----------------------------------------------------------------------------------
// Enables ads in the local build (normally they are deactivated there)
// testAds: true,
_testAds: "Enables ads in the local build (normally they are deactivated there)",
testAds: false,
// -----------------------------------------------------------------------------------
// Disables the automatic switch to an overview when zooming out
// disableMapOverview: true,
_disableMapOverview: "Disables the automatic switch to an overview when zooming out",
disableMapOverview: false,
// -----------------------------------------------------------------------------------
// Disables the notification when there are new entries in the changelog since last played
// disableUpgradeNotification: true,
_disableUpgradeNotification: "Disables the notification when there are new entries in the changelog since last played",
disableUpgradeNotification: false,
// -----------------------------------------------------------------------------------
// Makes belts almost infinitely fast
// instantBelts: true,
_instantBelts: "Makes belts almost infinitely fast",
instantBelts: false,
// -----------------------------------------------------------------------------------
// Makes item processors almost infinitely fast
// instantProcessors: true,
_instantProcessors: "Makes item processors almost infinitely fast",
instantProcessors: false,
// -----------------------------------------------------------------------------------
// Makes miners almost infinitely fast
// instantMiners: true,
_instantMiners: "Makes miners almost infinitely fast",
instantMiners: false,
// -----------------------------------------------------------------------------------
// When using fastGameEnter, controls whether a new game is started or the last one is resumed
// resumeGameOnFastEnter: true,
_resumeGameOnFastEnter: "When using fastGameEnter, controls whether a new game is started or the last one is resumed",
resumeGameOnFastEnter: false,
// -----------------------------------------------------------------------------------
// Special option used to render the trailer
// renderForTrailer: true,
_renderForTrailer: "Special option used to render the trailer",
renderForTrailer: false,
// -----------------------------------------------------------------------------------
// Whether to render changes
// renderChanges: true,
_renderChanges: "Whether to render changes",
renderChanges: false,
// -----------------------------------------------------------------------------------
// Whether to render belt paths
// renderBeltPaths: true,
_renderBeltPaths: "Whether to render belt paths",
renderBeltPaths: false,
// -----------------------------------------------------------------------------------
// Whether to check belt paths
// checkBeltPaths: true,
_checkBeltPaths: "Whether to check belt paths",
checkBeltPaths: false,
// -----------------------------------------------------------------------------------
// Whether to items / s instead of items / m in stats
// detailedStatistics: true,
_detailedStatistics: "Whether to items / s instead of items / m in stats",
detailedStatistics: false,
// -----------------------------------------------------------------------------------
// Shows detailed information about which atlas is used
// showAtlasInfo: true,

View File

@ -73,6 +73,22 @@ export class TextualGameState extends GameState {
});
}
/**
* 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
* content.

View File

@ -19,7 +19,7 @@ import { Vector } from "../core/vector";
/**
* Stores a lookup table for all building variants (for better performance)
* @type {Object<number, BuildingVariantIdentifier>}
* @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";
}

View File

@ -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"
);

View File

@ -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;
}

View File

@ -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);

View File

@ -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");
}

View File

@ -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<import("./shape_definition").ShapeLayer>} */
/** @type {Array<ShapeLayer>} */
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;

View File

@ -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<typeof MetaBuilding>}
*/
export const supportedBuildings = [
MetaBeltBaseBuilding,
MetaSplitterBuilding,
MetaUndergroundBeltBuilding,

View File

@ -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);
}

View File

@ -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,

View File

@ -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<ShapeLayer>} */
this.layers = /** @type {Array<ShapeLayer>} */ (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
);
const dims = quadrantSize * layerScale;
const innerDims = insetPadding - quadrantHalfSize;
break;
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();
}
}
}

View File

@ -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] }));
}
}

181
src/js/game/shapes.js Normal file
View File

@ -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<string, ShapeData>} */
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
};
}
}
}
}

View File

@ -238,8 +238,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const items = processorComp.inputSlots;
processorComp.inputSlots = [];
/** @type {Object.<string, { item: BaseItem, sourceSlot: number }>} */
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<enumColors>} */
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<enumColors>} */
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

View File

@ -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() {

View File

@ -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() {

View File

@ -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];
}

View File

@ -185,6 +185,10 @@ export class BoolSetting extends BaseSetting {
}
getHtml() {
if (!T.settings.labels[this.id].description) {
let a = T;
let b = a;
}
return `
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
${this.enabled ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}

View File

@ -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 `
<div class="sidebar">
${this.getCategoryButtonsHtml()}
<div class="topEntries">
<span class="hint">${T.keybindings.hint}</span>
<button class="styledButton resetBindings">${T.keybindings.resetKeybindings}</button>
<div class="other">
<button class="styledButton about">${T.about.title}</button>
<div class="versionbar">
<div class="buildVersion">${T.global.loading} ...</div>
</div>
</div>
</div>
<div class="keybindings">
<div class="categoryContainer">
<div class="category keybindings" data-category="keybindings">
<div class="topEntries">
<span class="hint">${T.keybindings.hint}</span>
<button class="styledButton resetBindings">${T.keybindings.resetKeybindings}</button>
</div>
</div>
${this.getSettingsHtml()}
</div>
`;
}
onEnter() {
this.onEnterCommon();
const keybindingsElem = this.htmlElement.querySelector(".keybindings");
this.trackClicks(this.htmlElement.querySelector(".resetBindings"), this.resetBindings);
for (const category in KEYMAPPINGS) {
const categoryDiv = document.createElement("div");
categoryDiv.classList.add("category");
categoryDiv.classList.add("keyCategory");
keybindingsElem.appendChild(categoryDiv);
const labelDiv = document.createElement("strong");
@ -173,7 +192,7 @@ export class KeybindingsState extends TextualGameState {
});
}
getDefaultPreviousState() {
return "SettingsState";
}
// getDefaultPreviousState() {
// return "SettingsState";
// }
}

View File

@ -75,7 +75,7 @@ export class MainMenuState extends GameState {
<div class="logo">
<img src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<span class="updateLabel">Wires update!</span>
<span class="updateLabel">Geo<span style="color:#66bb6a">Z</span></span>
</div>
@ -119,7 +119,7 @@ export class MainMenuState extends GameState {
<div class="author">${T.mainMenu.madeBy.replace(
"<author-link>",
'<a class="producerLink" target="_blank">Tobias Springer</a>'
'<a class="producerLink" target="_blank">Tobias Springer & modded by Exund</a>'
)}</div>
</div>

View File

@ -8,6 +8,12 @@ import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { CHANGELOG } from "../changelog";
import { globalConfig } from "../core/config";
import { initComponentRegistry } from "../game/component_registry";
import { initDrawUtils } from "../core/draw_utils";
import { initItemRegistry } from "../game/item_registry";
import { initMetaBuildingRegistry } from "../game/meta_building_registry";
import { initGameSpeedRegistry } from "../game/game_speed_registry";
const logger = createLogger("state/preload");
export class PreloadState extends GameState {
@ -108,6 +114,18 @@ export class PreloadState extends GameState {
return this.app.settings.initialize();
})
.then(() => this.setStatus("Loading GeoZ mods"))
.then(() => require("../GeoZ/main").initMods())
.then(() => this.setStatus("Initializing registeries"))
.then(() => {
initDrawUtils();
initComponentRegistry();
initItemRegistry();
initMetaBuildingRegistry();
initGameSpeedRegistry();
})
.then(() => {
// Initialize fullscreen
if (this.app.platformWrapper.getSupportsFullscreen()) {

View File

@ -4,8 +4,8 @@ import { allApplicationSettings, enumCategories } from "../profile/application_s
import { T } from "../translations";
export class SettingsState extends TextualGameState {
constructor() {
super("SettingsState");
constructor(key = "SettingsState") {
super(key);
}
getStateHeaderTitle() {
@ -92,7 +92,7 @@ export class SettingsState extends TextualGameState {
</span>`;
}
onEnter(payload) {
onEnterCommon() {
this.renderBuildText();
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
preventDefault: false,
@ -109,6 +109,12 @@ export class SettingsState extends TextualGameState {
this.htmlElement.querySelector(".category").classList.add("active");
this.htmlElement.querySelector(".categoryButton").classList.add("active");
this.setActiveCategory(enumCategories.general);
}
onEnter(payload) {
this.onEnterCommon();
}
setActiveCategory(category) {
@ -164,6 +170,6 @@ export class SettingsState extends TextualGameState {
}
onKeybindingsClicked() {
this.moveToStateAddGoBack("KeybindingsState");
this.switchToState("KeybindingsState");
}
}

View File

@ -727,6 +727,8 @@ settings:
userInterface: User Interface
advanced: Advanced
performance: Performance
debug: Debug
keybindings: Keybindings
versionBadges:
dev: Development