diff --git a/README.md b/README.md index 84be6af7..4a042cd9 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ You can use [Gitpod](https://www.gitpod.io/) (an Online Open Source VS Code-like - install all of the dependencies. - start `gulp` in `gulp/` directory. -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/) +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/tobspr/shapez.io) ## Helping translate diff --git a/electron_wegame/wegame.js b/electron_wegame/wegame.js index b9ea367a..066434cc 100644 --- a/electron_wegame/wegame.js +++ b/electron_wegame/wegame.js @@ -1,5 +1,5 @@ const railsdk = require("./wegame_sdk/railsdk.js"); -const { dialog, app } = require("electron"); +const { dialog, app, remote, ipcMain } = require("electron"); function init(isDev) { console.log("Step 1: wegame: init"); @@ -47,6 +47,14 @@ function init(isDev) { function listen() { console.log("wegame: listen"); + ipcMain.handle("profanity-check", async (event, data) => { + const result = railsdk.RailUtils.DirtyWordsFilter(data, true); + if (result.check_result.dirty_type !== 0 /** kRailDirtyWordsTypeNormalAllowWords */) { + return result.check_result; + } + + return data; + }); } module.exports = { init, listen }; diff --git a/res/logo_wegame.png b/res/logo_wegame.png index eb7d35fe..ac9092b3 100644 Binary files a/res/logo_wegame.png and b/res/logo_wegame.png differ diff --git a/res/ui/wegame_isbn_rating.jpg b/res/ui/wegame_isbn_rating.jpg new file mode 100644 index 00000000..581dd744 Binary files /dev/null and b/res/ui/wegame_isbn_rating.jpg differ diff --git a/src/css/main.scss b/src/css/main.scss index 1bd82828..7419a5df 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -21,6 +21,7 @@ @import "adinplay"; @import "changelog_skins"; +@import "states/wegame_splash"; @import "states/preload"; @import "states/main_menu"; @import "states/ingame"; diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index 2b44b56e..9a04baf4 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -565,6 +565,40 @@ grid-template-columns: auto 1fr; } + &.wegameDisclaimer { + @include SuperSmallText; + display: grid; + justify-content: center; + grid-template-columns: 1fr auto 1fr; + + > .disclaimer { + grid-column: 2 / 3; + + @include DarkThemeOverride { + color: #fff; + } + } + + > .rating { + grid-column: 3 / 4; + justify-self: end; + align-self: end; + + @include S(width, 32px); + @include S(height, 40px); + background: green; + cursor: pointer !important; + pointer-events: all; + @include S(border-radius, 4px); + overflow: hidden; + + & { + /* @load-async */ + background: #fff uiResource("wegame_isbn_rating.jpg") center center / contain no-repeat; + } + } + } + .author { flex-grow: 1; text-align: right; diff --git a/src/css/states/wegame_splash.scss b/src/css/states/wegame_splash.scss new file mode 100644 index 00000000..961cfa67 --- /dev/null +++ b/src/css/states/wegame_splash.scss @@ -0,0 +1,38 @@ +#state_WegameSplashState { + background: #000 !important; + display: flex; + align-items: center; + justify-content: center; + + .wrapper { + opacity: 0; + @include InlineAnimation(5.9s ease-in-out) { + 0% { + opacity: 0; + } + 20% { + opacity: 1; + } + 90% { + opacity: 1; + } + 100% { + opacity: 0; + } + } + + text-align: center; + color: #fff; + @include Heading; + + strong { + display: block; + @include SuperHeading; + @include S(margin-bottom, 20px); + } + + div { + @include S(margin-bottom, 10px); + } + } +} diff --git a/src/js/application.js b/src/js/application.js index 4e74b014..c49b7027 100644 --- a/src/js/application.js +++ b/src/js/application.js @@ -34,6 +34,7 @@ import { RestrictionManager } from "./core/restriction_manager"; import { PuzzleMenuState } from "./states/puzzle_menu"; import { ClientAPI } from "./platform/api"; import { LoginState } from "./states/login"; +import { WegameSplashState } from "./states/wegame_splash"; /** * @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface @@ -155,6 +156,7 @@ export class Application { registerStates() { /** @type {Array} */ const states = [ + WegameSplashState, PreloadState, MobileWarningState, MainMenuState, @@ -330,8 +332,12 @@ export class Application { Loader.linkAppAfterBoot(this); + if (G_WEGAME_VERSION) { + this.stateMgr.moveToState("WegameSplashState"); + } + // Check for mobile - if (IS_MOBILE) { + else if (IS_MOBILE) { this.stateMgr.moveToState("MobileWarningState"); } else { this.stateMgr.moveToState("PreloadState"); diff --git a/src/js/changelog.js b/src/js/changelog.js index 9b497ff8..1ea3c2a7 100644 --- a/src/js/changelog.js +++ b/src/js/changelog.js @@ -1,4 +1,15 @@ export const CHANGELOG = [ + { + version: "1.4.3", + date: "unreleased", + entries: [ + "Edit signal dialog now has the previous signal filled (Thanks to EmeraldBlock)", + "Further performance improvements (Thanks to PFedak)", + "Improved puzzle validation (Thanks to Sense101)", + "Input fields in dialogs should now automatically focus", + "Updated translations", + ], + }, { version: "1.4.2", date: "24.06.2021", diff --git a/src/js/core/modal_dialog_forms.js b/src/js/core/modal_dialog_forms.js index aac81d82..ccf9bfb2 100644 --- a/src/js/core/modal_dialog_forms.js +++ b/src/js/core/modal_dialog_forms.js @@ -1,6 +1,7 @@ import { BaseItem } from "../game/base_item"; import { ClickDetector } from "./click_detector"; import { Signal } from "./signal"; +import { getIPCRenderer } from "./utils"; /* * *************************************************** @@ -107,6 +108,19 @@ export class FormElementInput extends FormElement { updateErrorState() { this.element.classList.toggle("errored", !this.isValid()); + + // profanity filter + if (G_WEGAME_VERSION) { + const value = String(this.element.value); + + getIPCRenderer() + .invoke("profanity-check", value) + .then(newValue => { + if (value !== newValue && this.element) { + this.element.value = newValue; + } + }); + } } isValid() { @@ -124,6 +138,7 @@ export class FormElementInput extends FormElement { focus() { this.element.focus(); + this.element.select(); } } diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js index 4c0e1835..f7dddec1 100644 --- a/src/js/game/components/item_processor.js +++ b/src/js/game/components/item_processor.js @@ -73,6 +73,12 @@ export class ItemProcessorComponent extends Component { // Type of processing requirement this.processingRequirement = processingRequirement; + /** + * Our current inputs + * @type {Map} + */ + this.inputSlots = new Map(); + this.clear(); } @@ -82,11 +88,13 @@ export class ItemProcessorComponent extends Component { // sure the outputs always match this.nextOutputSlot = 0; + this.inputSlots.clear(); + /** - * Our current inputs - * @type {Array<{ item: BaseItem, sourceSlot: number }>} + * Current input count + * @type {number} */ - this.inputSlots = []; + this.inputCount = 0; /** * What we are currently processing, empty if we don't produce anything rn @@ -115,19 +123,17 @@ export class ItemProcessorComponent extends Component { this.type === enumItemProcessorTypes.goal ) { // Hub has special logic .. not really nice but efficient. - this.inputSlots.push({ item, sourceSlot }); + this.inputSlots.set(this.inputCount, item); + this.inputCount++; return true; } // Check that we only take one item per slot - for (let i = 0; i < this.inputSlots.length; ++i) { - const slot = this.inputSlots[i]; - if (slot.sourceSlot === sourceSlot) { - return false; - } + if (this.inputSlots.has(sourceSlot)) { + return false; } - - this.inputSlots.push({ item, sourceSlot }); + this.inputSlots.set(sourceSlot, item); + this.inputCount++; return true; } } diff --git a/src/js/game/hud/parts/puzzle_editor_review.js b/src/js/game/hud/parts/puzzle_editor_review.js index 727006d6..4a044ba5 100644 --- a/src/js/game/hud/parts/puzzle_editor_review.js +++ b/src/js/game/hud/parts/puzzle_editor_review.js @@ -81,7 +81,7 @@ export class HUDPuzzleEditorReview extends BaseHUDPart { closeLoading(); //if it took so little ticks that it must have autocompeted - if (simulatedTicks <= 300) { + if (simulatedTicks <= 500) { this.root.hud.parts.dialogs.showWarning( T.puzzleMenu.validation.title, T.puzzleMenu.validation.autoComplete diff --git a/src/js/game/logic.js b/src/js/game/logic.js index 20caca31..79104958 100644 --- a/src/js/game/logic.js +++ b/src/js/game/logic.js @@ -80,6 +80,15 @@ export class GameLogic { } // Perform additional placement checks + if (this.root.gameMode.getIsEditor()) { + const toolbar = this.root.hud.parts.buildingsToolbar; + const id = entity.components.StaticMapEntity.getMetaBuilding().getId(); + + if (toolbar.buildingHandles[id].puzzleLocked) { + return false; + } + } + if (this.root.signals.prePlacementCheck.dispatch(entity, offset) === STOP_PROPAGATION) { return false; } diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 87bb764f..7fcfbf60 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -585,12 +585,12 @@ export class RegularGameMode extends GameMode { } /** @type {(typeof MetaBuilding)[]} */ - this.hiddenBuildings = [ - MetaConstantProducerBuilding, - MetaGoalAcceptorBuilding, - MetaBlockBuilding, - MetaItemProducerBuilding, - ]; + this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding, MetaBlockBuilding]; + + // @ts-expect-error + if (!(G_IS_DEV || window.sandboxMode || queryParamOptions.sandboxMode)) { + this.hiddenBuildings.push(MetaItemProducerBuilding); + } } /** diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js index 5fabb80e..29079825 100644 --- a/src/js/game/systems/constant_signal.js +++ b/src/js/game/systems/constant_signal.js @@ -49,11 +49,12 @@ export class ConstantSignalSystem extends GameSystemWithFilter { // Ok, query, but also save the uid because it could get stale const uid = entity.uid; + const signal = entity.components.ConstantSignal.signal; const signalValueInput = new FormElementInput({ id: "signalValue", label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer), placeholder: "", - defaultValue: "", + defaultValue: signal ? signal.getAsCopyableKey() : "", validator: val => this.parseSignalCode(entity, val), }); diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index 3794473f..b57d735c 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -32,8 +32,8 @@ const MAX_QUEUED_CHARGES = 2; * Type of a processor implementation * @typedef {{ * entity: Entity, - * items: Array<{ item: BaseItem, sourceSlot: number }>, - * itemsBySlot: Object, + * items: Map, + * inputCount: number, * outItems: Array * }} ProcessorImplementationPayload */ @@ -189,7 +189,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // DEFAULT // By default, we can start processing once all inputs are there case null: { - return processorComp.inputSlots.length >= processorComp.inputsPerCharge; + return processorComp.inputCount >= processorComp.inputsPerCharge; } // QUAD PAINTER @@ -197,18 +197,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter { case enumItemProcessorRequirements.painterQuad: { const pinsComp = entity.components.WiredPins; - /** @type {Object.} */ - const itemsBySlot = {}; - for (let i = 0; i < processorComp.inputSlots.length; ++i) { - itemsBySlot[processorComp.inputSlots[i].sourceSlot] = processorComp.inputSlots[i]; - } - // First slot is the shape, so if it's not there we can't do anything - if (!itemsBySlot[0]) { + const shapeItem = /** @type {ShapeItem} */ (processorComp.inputSlots.get(0)); + if (!shapeItem) { return false; } - const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); const slotStatus = []; // Check which slots are enabled @@ -233,7 +227,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // Check if all colors of the enabled slots are there for (let i = 0; i < slotStatus.length; ++i) { - if (slotStatus[i] && !itemsBySlot[1 + i]) { + if (slotStatus[i] && !processorComp.inputSlots.get(1 + i)) { // A slot which is enabled wasn't enabled. Make sure if there is anything on the quadrant, // it is not possible to paint, but if there is nothing we can ignore it for (let j = 0; j < 4; ++j) { @@ -262,13 +256,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter { // First, take items const items = processorComp.inputSlots; - processorComp.inputSlots = []; - - /** @type {Object} */ - const itemsBySlot = {}; - for (let i = 0; i < items.length; ++i) { - itemsBySlot[items[i].sourceSlot] = items[i].item; - } /** @type {Array} */ const outItems = []; @@ -281,8 +268,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { handler({ entity, items, - itemsBySlot, outItems, + inputCount: processorComp.inputCount, }); // Track produced items @@ -304,6 +291,9 @@ export class ItemProcessorSystem extends GameSystemWithFilter { items: outItems, remainingTime: timeToProcess, }); + + processorComp.inputSlots.clear(); + processorComp.inputCount = 0; } /** @@ -319,9 +309,14 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const nextSlot = processorComp.nextOutputSlot++ % availableSlots; - for (let i = 0; i < payload.items.length; ++i) { + // Hardcoded to 2, to avoid accessing the length + for (let i = 0; i < 2; ++i) { + const item = payload.items.get(i); + if (!item) { + continue; + } payload.outItems.push({ - item: payload.items[i].item, + item, preferredSlot: (nextSlot + i) % availableSlots, doNotTrack: true, }); @@ -333,7 +328,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_CUTTER(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.items.get(0)); assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); const inputDefinition = inputItem.definition; @@ -354,7 +349,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_CUTTER_QUAD(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.items.get(0)); assert(inputItem instanceof ShapeItem, "Input for cut is not a shape"); const inputDefinition = inputItem.definition; @@ -375,7 +370,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_ROTATER(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.items.get(0)); assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); const inputDefinition = inputItem.definition; @@ -389,7 +384,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_ROTATER_CCW(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.items.get(0)); assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); const inputDefinition = inputItem.definition; @@ -403,7 +398,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_ROTATER_180(payload) { - const inputItem = /** @type {ShapeItem} */ (payload.items[0].item); + const inputItem = /** @type {ShapeItem} */ (payload.items.get(0)); assert(inputItem instanceof ShapeItem, "Input for rotation is not a shape"); const inputDefinition = inputItem.definition; @@ -417,8 +412,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_STACKER(payload) { - const lowerItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]); - const upperItem = /** @type {ShapeItem} */ (payload.itemsBySlot[1]); + const lowerItem = /** @type {ShapeItem} */ (payload.items.get(0)); + const upperItem = /** @type {ShapeItem} */ (payload.items.get(1)); assert(lowerItem instanceof ShapeItem, "Input for lower stack is not a shape"); assert(upperItem instanceof ShapeItem, "Input for upper stack is not a shape"); @@ -444,8 +439,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { */ process_MIXER(payload) { // Find both colors and combine them - const item1 = /** @type {ColorItem} */ (payload.items[0].item); - const item2 = /** @type {ColorItem} */ (payload.items[1].item); + const item1 = /** @type {ColorItem} */ (payload.items.get(0)); + const item2 = /** @type {ColorItem} */ (payload.items.get(1)); assert(item1 instanceof ColorItem, "Input for color mixer is not a color"); assert(item2 instanceof ColorItem, "Input for color mixer is not a color"); @@ -467,8 +462,8 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_PAINTER(payload) { - const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]); - const colorItem = /** @type {ColorItem} */ (payload.itemsBySlot[1]); + const shapeItem = /** @type {ShapeItem} */ (payload.items.get(0)); + const colorItem = /** @type {ColorItem} */ (payload.items.get(1)); const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith( shapeItem.definition, @@ -484,9 +479,9 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_PAINTER_DOUBLE(payload) { - const shapeItem1 = /** @type {ShapeItem} */ (payload.itemsBySlot[0]); - const shapeItem2 = /** @type {ShapeItem} */ (payload.itemsBySlot[1]); - const colorItem = /** @type {ColorItem} */ (payload.itemsBySlot[2]); + const shapeItem1 = /** @type {ShapeItem} */ (payload.items.get(0)); + const shapeItem2 = /** @type {ShapeItem} */ (payload.items.get(1)); + const colorItem = /** @type {ColorItem} */ (payload.items.get(2)); assert(shapeItem1 instanceof ShapeItem, "Input for painter is not a shape"); assert(shapeItem2 instanceof ShapeItem, "Input for painter is not a shape"); @@ -514,14 +509,15 @@ export class ItemProcessorSystem extends GameSystemWithFilter { * @param {ProcessorImplementationPayload} payload */ process_PAINTER_QUAD(payload) { - const shapeItem = /** @type {ShapeItem} */ (payload.itemsBySlot[0]); + const shapeItem = /** @type {ShapeItem} */ (payload.items.get(0)); 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 (payload.itemsBySlot[i + 1]) { - colors[i] = /** @type {ColorItem} */ (payload.itemsBySlot[i + 1]).color; + const colorItem = /** @type {ColorItem} */ (payload.items.get(i + 1)); + if (colorItem) { + colors[i] = colorItem.color; } } @@ -540,7 +536,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { */ process_READER(payload) { // Pass through the item - const item = payload.itemsBySlot[0]; + const item = payload.items.get(0); payload.outItems.push({ item, doNotTrack: true, @@ -559,8 +555,12 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const hubComponent = payload.entity.components.Hub; assert(hubComponent, "Hub item processor has no hub component"); - for (let i = 0; i < payload.items.length; ++i) { - const item = /** @type {ShapeItem} */ (payload.items[i].item); + // Hardcoded + for (let i = 0; i < payload.inputCount; ++i) { + const item = /** @type {ShapeItem} */ (payload.items.get(i)); + if (!item) { + continue; + } this.root.hubGoals.handleDefinitionDelivered(item.definition); } } @@ -570,7 +570,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { */ process_GOAL(payload) { const goalComp = payload.entity.components.GoalAcceptor; - const item = payload.items[0].item; + const item = payload.items.get(0); const now = this.root.time.now(); if (goalComp.item && !item.equals(goalComp.item)) { @@ -584,7 +584,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { if (this.root.gameMode.getIsEditor()) { // while playing in editor, assign the item - goalComp.item = payload.items[0].item; + goalComp.item = item; } goalComp.lastDelivery = { diff --git a/src/js/platform/browser/game_analytics.js b/src/js/platform/browser/game_analytics.js index 80e19c7a..9411b258 100644 --- a/src/js/platform/browser/game_analytics.js +++ b/src/js/platform/browser/game_analytics.js @@ -111,6 +111,10 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface { * @returns {Promise} */ sendToApi(endpoint, data) { + if (G_WEGAME_VERSION) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { const timeout = setTimeout(() => reject("Request to " + endpoint + " timed out"), 20000); diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 5885b2ce..279699d2 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -130,7 +130,17 @@ export class MainMenuState extends GameState { ${ G_WEGAME_VERSION - ? "" + ? ` + ` : `