diff --git a/res/ui/icons/tutorial_arrow.png b/res/ui/icons/tutorial_arrow.png new file mode 100644 index 00000000..83cb0781 Binary files /dev/null and b/res/ui/icons/tutorial_arrow.png differ diff --git a/res/ui/steam_link_btn/0.png b/res/ui/steam_link_btn/0.png index 1827c8c4..ceb8631b 100644 Binary files a/res/ui/steam_link_btn/0.png and b/res/ui/steam_link_btn/0.png differ diff --git a/res_raw/sounds/sfx/tutorial_step.wav b/res_raw/sounds/sfx/tutorial_step.wav new file mode 100644 index 00000000..9dc0e920 Binary files /dev/null and b/res_raw/sounds/sfx/tutorial_step.wav differ diff --git a/res_raw/sounds/sfx/unlock_upgrade.wav b/res_raw/sounds/sfx/unlock_upgrade.wav index b92acb8c..d32857f7 100644 Binary files a/res_raw/sounds/sfx/unlock_upgrade.wav and b/res_raw/sounds/sfx/unlock_upgrade.wav differ diff --git a/src/css/ingame_hud/buildings_toolbar.scss b/src/css/ingame_hud/buildings_toolbar.scss index 6b34a876..4e9028b5 100644 --- a/src/css/ingame_hud/buildings_toolbar.scss +++ b/src/css/ingame_hud/buildings_toolbar.scss @@ -55,6 +55,35 @@ position: relative; @include S(height, 40px); + @at-root html[data-tutorial-step="1_1_extractor"] &[data-id="miner"]:not(.selected), + html[data-tutorial-step="1_2_conveyor"] &[data-id="belt"]:not(.selected), + html[data-tutorial-step="2_1_place_cutter"] &[data-id="cutter"]:not(.selected), + html[data-tutorial-step="2_2_place_trash"] &[data-id="trash"]:not(.selected) { + &::before { + content: ""; + + & { + /* load-async */ + background: uiResource("icons/tutorial_arrow.png") center center / contain no-repeat; + } + + @include S(width, 25px); + @include S(height, 25px); + position: absolute; + left: 50%; + bottom: 100%; + transform: translateX(-50%); + @include InlineAnimation(1s ease-in-out infinite) { + 50% { + transform: translateX(-50%) translateY(20%); + } + } + } + @include S(border-radius, $globalBorderRadius); + box-shadow: 0 0 D(10px) D(5px) rgba(74, 237, 134, 0.5) !important; + background: rgba(74, 237, 134, 0.5) !important; + } + .icon { color: $accentColorDark; display: flex; diff --git a/src/css/ingame_hud/interactive_tutorial.scss b/src/css/ingame_hud/interactive_tutorial.scss index 3e1b5c99..dad52841 100644 --- a/src/css/ingame_hud/interactive_tutorial.scss +++ b/src/css/ingame_hud/interactive_tutorial.scss @@ -32,12 +32,6 @@ pointer-events: all; transition: opacity 0.1s ease-out; - &.hovered { - opacity: 10%; - .helperGif { - opacity: 0%; - } - } .title { color: #fff; diff --git a/src/css/ingame_hud/standalone_advantages.scss b/src/css/ingame_hud/standalone_advantages.scss index 0f681242..0e8de09c 100644 --- a/src/css/ingame_hud/standalone_advantages.scss +++ b/src/css/ingame_hud/standalone_advantages.scss @@ -126,6 +126,7 @@ @include S(height, 40px); background: #171a23 center center / contain no-repeat; + box-shadow: 0 D(3px) D(10px) rgba(96, 163, 136, 0.5); overflow: visible; @include S(border-radius, $globalBorderRadius); @@ -135,6 +136,10 @@ } } + &:hover { + opacity: 0.94 !important; + } + > .discount { position: absolute; @include S(top, -7px); diff --git a/src/css/mixins.scss b/src/css/mixins.scss index 888d84d6..d0a4f367 100644 --- a/src/css/mixins.scss +++ b/src/css/mixins.scss @@ -102,30 +102,6 @@ button, opacity: 1; } -// ---------------------------------------- -/* Define a style which is only applied in horizontal mode */ -@mixin HorizontalStyle { - @include AppendGlobal(".h") { - @content; - } -} - -// ---------------------------------------- -/* Define a style which is only applied in vertical mode */ -@mixin VerticalStyle { - @include AppendGlobal(".v") { - @content; - } -} - -// ---------------------------------------- -/* Define a style which is only while the hardware keyboard is open */ -@mixin AndroidHwKeyboardOpen { - @include AppendGlobal(".kb") { - @content; - } -} - // ---------------------------------------- /* Automatically transforms the game state if a hardware keyboard is open */ @mixin TransformToMatchKeyboard { diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index d7e08c43..4599e8fa 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -61,7 +61,7 @@ opacity: 0; display: none; transform: translate(50%, 50%); - filter: blur(D(7px)); + filter: blur(D(15px)); $opacity: 0.4; &.loaded { @@ -81,34 +81,41 @@ .mainWrapper { @include S(padding, 0, 10px); + @include S(margin-top, 15px); align-items: start; justify-items: center; @include S(grid-column-gap, 10px); display: grid; + grid-template-rows: D(31px) 1fr D(93px); + &[data-columns="1"] { grid-template-columns: 1fr; } &[data-columns="2"] { - grid-template-columns: 1fr 1fr; + grid-template-columns: D(290px) 1fr; } .standaloneBanner { - background: rgba(12, 168, 93, 0.957); + background: transparent; @include S(border-radius, $globalBorderRadius); + // box-shadow: 0 D(5px) D(15px) rgba(#000, 0.2); + @include S(width, 380px); box-sizing: border-box; - @include S(padding, 15px); - box-shadow: 0 D(5px) D(15px) rgba(#000, 0.2); + @include S(padding, 0, 15px); + // backdrop-filter: blur(10px); display: flex; flex-direction: column; + margin: 0; + strong { font-weight: 700 !important; } .onlinePlayerCount { - color: #fff; + color: #333; @include S(margin-top, 15px); @include SuperSmallText; @include S(height, 15px); @@ -118,15 +125,14 @@ h3 { @include Heading; font-weight: bold; - @include S(margin-bottom, 20px); - display: none; - text-transform: uppercase; - color: #fff; + @include S(margin-bottom, 10px); + text-align: center; + color: #44484f; } p { @include Text; - color: #fff; + color: #333; } ul { @@ -139,7 +145,7 @@ } .playtimeDisclaimer { - color: #fff; + color: #333; @include S(margin-top, 15px); @include SuperSmallText; } @@ -162,6 +168,7 @@ @include S(border-radius, $globalBorderRadius); color: transparent; + box-shadow: 0 D(3px) D(10px) rgba(96, 163, 136, 0.5); &:hover { opacity: 0.9; } @@ -190,6 +197,151 @@ } } } + + .points { + display: grid; + grid-template-columns: 1fr 1fr; + width: 100%; + @include S(grid-gap, 5px); + } + + .point { + display: grid; + grid-template-columns: #{D(27px)} auto; + grid-template-rows: D(11px) D(10px); + background: #fff #{D(10px)} center / #{D(17px)} no-repeat; + @include S(grid-row-gap, 3px); + align-items: center; + @include S(padding, 6px); + @include S(border-radius, $globalBorderRadius); + + box-shadow: 0 D(5px) D(10px) rgba(#000, 0.2); + + > strong { + grid-column: 2 / 3; + grid-row: 1 / 2; + @include PlainText; + @include S(font-size, 12px); + line-height: 0.8em; + white-space: nowrap; + text-transform: uppercase; + font-weight: bold; + align-self: end; + } + + > p { + grid-column: 2 / 3; + grid-row: 2 / 3; + @include SuperSmallText; + white-space: nowrap; + @include BreakText; + @include S(font-size, 8px); + line-height: 1em; + align-self: start; + + opacity: 0.8; + } + + &.levels { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_new_levels.png"); + } + > strong { + color: #f13555; + } + } + + &.upgrades { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_upgrades.png"); + } + > strong { + color: #8a00ff; + } + } + + &.buildings { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_buildings.png"); + } + > strong { + color: #3fce8b; + } + } + + &.wires { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_wires.png"); + } + > strong { + color: #ef2fdb; + } + } + + &.markers { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_markers.png"); + } + > strong { + color: #4294ff; + } + } + + &.mods { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_mods.png"); + } + > strong { + color: #8a00ff; + } + } + + &.savegames { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_savegames.png"); + } + > strong { + color: #ff9500; + } + } + + &.darkmode { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_dark_mode.png"); + } + > strong { + color: #292c32; + } + } + + &.support { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_support.png"); + } + > strong { + color: #e72d2d; + } + } + + &.achievements { + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/advantage_achievements.png"); + } + > strong { + color: #ffac0f; + } + } + } } } @@ -203,8 +355,8 @@ @include S(padding-top, 20px); img { - @include S(width, 710px / 2.2); - @include S(height, 180 / 2.2px); + @include S(width, 710px / 2.5); + @include S(height, 180px / 2.5); } position: relative; @include S(left, -8px); @@ -243,11 +395,12 @@ .sideContainer { display: flex; flex-direction: column; - @include S(width, 300px); + width: 100%; + grid-row: 1 / 4; + grid-column: 2 / 3; .standaloneBanner { flex-grow: 1; - @include S(margin-bottom, 10px); } } @@ -258,7 +411,6 @@ flex-direction: column; background: $colorBlueBright; grid-row: 1 / 2; - grid-column: 2 / 3; position: relative; @include S(padding, 20px); @include S(border-radius, $globalBorderRadius); @@ -324,7 +476,6 @@ flex-direction: column; background: #fff; grid-row: 1 / 2; - grid-column: 2 / 3; position: relative; text-align: left; align-items: flex-start; @@ -418,18 +569,24 @@ .mainContainer { display: flex; align-items: center; - grid-row: 1 / 2; justify-content: center; flex-direction: column; - background: #fafafa; - @include S(padding, 20px); + background: rgba(#fff, 0.9); + @include S(padding, 15px); @include S(border-radius, $globalBorderRadius); - // border: #{D(2px)} solid rgba(0, 10, 20, 0.1); - - box-shadow: 0 D(5px) D(15px) rgba(#000, 0.2); height: 100%; + box-shadow: 0 D(5px) D(15px) rgba(#000, 0.2); width: 100%; position: relative; + align-self: center; + justify-self: center; + grid-row: 1 / 4; + grid-column: 1 / 2; + + // &[data-savegames="0"] { + // grid-row: 2 / 3; + // } + box-sizing: border-box; .buttons { @@ -483,6 +640,16 @@ .outer { @include S(margin-top, 15px); + display: grid; + grid-auto-flow: column; + grid-auto-columns: 1fr; + + @include S(grid-gap, 5px); + width: 100%; + + > button { + @include S(padding, 3px, 6px); + } } .importButton { @@ -491,14 +658,10 @@ .newGameButton { @include IncreasedClickArea(0px); - @include S(margin-left, 10px); } .modsButton { @include IncreasedClickArea(0px); - @include S(margin-left, 10px); - - // @include S(width, 20px); background-position: center center; background-size: D(15px); @@ -509,9 +672,11 @@ .savegames { @include S(max-height, 105px); overflow-y: auto; - @include S(width, 250px); + @include S(min-width, 230px); + width: 100%; pointer-events: all; @include S(padding-right, 5px); + margin-right: D(-5px); display: grid; grid-auto-flow: row; @include S(grid-gap, 5px); @@ -586,6 +751,7 @@ @include S(height, 15px); background-size: 80%; align-self: start; + border-radius: 0; opacity: 0.4; &:hover { @@ -608,6 +774,7 @@ @include S(height, 15px); align-self: end; background-size: 80%; + border-radius: 0; opacity: 0.4; &:hover { @@ -628,7 +795,7 @@ @include S(height, 10px); align-self: center; justify-self: center; - + border-radius: 0; background-size: 90%; opacity: 0.4; @include S(margin-left, 4px); @@ -725,7 +892,7 @@ a { &:hover img { - opacity: 0.8; + opacity: 0.85; } display: flex; align-items: center; @@ -736,7 +903,7 @@ @include S(width, 82px); @include S(height, 25px); filter: invert(100%); - opacity: 0.6; + opacity: 0.75; } } } @@ -744,7 +911,7 @@ @include S(padding, 15px); $linkBg: rgba(#fdfdff, 0.5); - $linkBgHover: darken($linkBg, 5); + $linkBgHover: rgba(#fff, 0.7); $linkColor: #55586a; > .boxLink { diff --git a/src/css/states/mobile_warning.scss b/src/css/states/mobile_warning.scss index aa4922a1..947d530c 100644 --- a/src/css/states/mobile_warning.scss +++ b/src/css/states/mobile_warning.scss @@ -1,7 +1,7 @@ #state_MobileWarningState { display: flex; align-items: center; - background: #333438 !important; + background: #555b75 !important; @include S(padding, 20px); box-sizing: border-box; justify-content: center; @@ -14,7 +14,7 @@ } p { - color: #aaacaf; + color: rgba(#fff, 0.5); display: block; margin-bottom: 13px; font-size: 16px; @@ -28,12 +28,11 @@ .standaloneLink { width: 200px; - height: 80px; + height: 48px; min-height: 40px; & { - background: uiResource("steam_link_btn/0.png") center center / contain no-repeat; + background: #000 uiResource("steam_link_btn/0.png") center center / contain no-repeat; } - overflow: hidden; display: block; text-indent: -999em; cursor: pointer; @@ -41,7 +40,9 @@ pointer-events: all; transition: all 0.12s ease-in; transition-property: opacity, transform; - transform: skewX(-0.5deg); + @include S(border-radius, $globalBorderRadius); + overflow: hidden; + &:hover { transform: skewX(-1deg) scale(1.02); opacity: 0.9; diff --git a/src/css/states/mods.scss b/src/css/states/mods.scss index eb958082..ac082ec6 100644 --- a/src/css/states/mods.scss +++ b/src/css/states/mods.scss @@ -38,6 +38,7 @@ transition: all 0.12s ease-in; transition-property: opacity, transform; + box-shadow: 0 D(3px) D(10px) rgba(96, 163, 136, 0.5); @include S(border-radius, $globalBorderRadius); &:hover { diff --git a/src/css/variables.scss b/src/css/variables.scss index e041c6e4..3ae8878d 100644 --- a/src/css/variables.scss +++ b/src/css/variables.scss @@ -178,34 +178,3 @@ $mainFontScale: 1; @function trim($string) { @return str-slice($string, _first-index($string, "left"), _first-index($string, "right")); } - -@mixin AppendGlobal($prefix) { - $strSelector: quote(&); - $selectors: str-split($strSelector, ","); - - $builtSelector: null; - - @if (& == null) { - $builtSelector: "html" + $prefix; - } @else { - $builtSelector: (); - // @debug ($strSelector, "->>>", $selectors); - @each $srcSelector in $selectors { - $srcSelector: trim($srcSelector); - // @debug ("___", $srcSelector); - $selector: "html" + $prefix + " " + $srcSelector; - @if str-index($srcSelector, "html.") { - $selector: "html" + - $prefix + - "." + - str-slice($srcSelector, str-index($srcSelector, "html.") + 5); - } - // @debug ("_______", $selector); - $builtSelector: append($builtSelector, $selector, comma); - } - } - - @at-root #{$builtSelector} { - @content; - } -} diff --git a/src/js/changelog.js b/src/js/changelog.js index 5fe89690..a038637f 100644 --- a/src/js/changelog.js +++ b/src/js/changelog.js @@ -1,4 +1,13 @@ export const CHANGELOG = [ + { + version: "1.5.5", + date: "20.06.2022", + entries: [ + "Reworked the tutorial to be simpler and more interactive", + "General polishing", + "Updated translations", + ], + }, { version: "1.5.3", date: "05.06.2022", diff --git a/src/js/game/belt_path.js b/src/js/game/belt_path.js index 80efecbe..9ef4a3f3 100644 --- a/src/js/game/belt_path.js +++ b/src/js/game/belt_path.js @@ -183,7 +183,7 @@ export class BeltPath extends BasicSerializableObject { * Recomputes cache variables once the path was changed */ onPathChanged() { - this.boundAcceptor = this.computeAcceptingEntityAndSlot(); + this.boundAcceptor = this.computeAcceptingEntityAndSlot().acceptor; /** * How many items past the first item are compressed @@ -201,7 +201,7 @@ export class BeltPath extends BasicSerializableObject { /** * Finds the entity which accepts our items * @param {boolean=} debug_Silent Whether debug output should be silent - * @return { (BaseItem, number?) => boolean } + * @return { { acceptor?: (BaseItem, number?) => boolean, entity?: Entity } } */ computeAcceptingEntityAndSlot(debug_Silent = false) { DEBUG && !debug_Silent && logger.log("Recomputing acceptor target"); @@ -224,7 +224,7 @@ export class BeltPath extends BasicSerializableObject { ); if (!targetEntity) { - return; + return {}; } const noSimplifiedBelts = !this.root.app.settings.getAllSettings().simplifiedBelts; @@ -247,10 +247,13 @@ export class BeltPath extends BasicSerializableObject { targetStaticComp.rotation ); if (ejectSlotWsDirection === beltAcceptingDirection) { - return item => { - const path = targetBeltComp.assignedPath; - assert(path, "belt has no path"); - return path.tryAcceptItem(item); + return { + entity: targetEntity, + acceptor: item => { + const path = targetBeltComp.assignedPath; + assert(path, "belt has no path"); + return path.tryAcceptItem(item); + }, }; } } @@ -259,7 +262,7 @@ export class BeltPath extends BasicSerializableObject { const targetAcceptorComp = targetEntity.components.ItemAcceptor; if (!targetAcceptorComp) { // Entity doesn't accept items - return; + return {}; } const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection); @@ -270,38 +273,41 @@ export class BeltPath extends BasicSerializableObject { if (!matchingSlot) { // No matching slot found - return; + return {}; } const matchingSlotIndex = matchingSlot.index; const passOver = this.computePassOverFunctionWithoutBelts(targetEntity, matchingSlotIndex); if (!passOver) { - return; + return {}; } const matchingDirection = enumInvertedDirections[ejectingDirection]; const filter = matchingSlot.slot.filter; - return function (item, remainingProgress = 0.0) { - // Check if the acceptor has a filter - if (filter && item._type !== filter) { - return false; - } + return { + entity: targetEntity, + acceptor: function (item, remainingProgress = 0.0) { + // Check if the acceptor has a filter + if (filter && item._type !== filter) { + return false; + } - // Try to pass over - if (passOver(item, matchingSlotIndex)) { - // Trigger animation on the acceptor comp - if (noSimplifiedBelts) { - targetAcceptorComp.onItemAccepted( - matchingSlotIndex, - matchingDirection, - item, - remainingProgress - ); + // Try to pass over + if (passOver(item, matchingSlotIndex)) { + // Trigger animation on the acceptor comp + if (noSimplifiedBelts) { + targetAcceptorComp.onItemAccepted( + matchingSlotIndex, + matchingDirection, + item, + remainingProgress + ); + } + return true; } - return true; - } - return false; + return false; + }, }; } @@ -494,7 +500,7 @@ export class BeltPath extends BasicSerializableObject { } // Check acceptor - const acceptor = this.computeAcceptingEntityAndSlot(true); + const acceptor = this.computeAcceptingEntityAndSlot(true).acceptor; if (!!acceptor !== !!this.boundAcceptor) { return fail("Acceptor target mismatch, acceptor", !!acceptor, "vs stored", !!this.boundAcceptor); } diff --git a/src/js/game/camera.js b/src/js/game/camera.js index fc90f4de..4c5def81 100644 --- a/src/js/game/camera.js +++ b/src/js/game/camera.js @@ -168,7 +168,7 @@ export class Camera extends BasicSerializableObject { * Finds a good initial zoom level */ findInitialZoom() { - const desiredWorldSpaceWidth = 15 * globalConfig.tileSize; + const desiredWorldSpaceWidth = 18 * globalConfig.tileSize; const zoomLevelX = this.root.gameWidth / desiredWorldSpaceWidth; const zoomLevelY = this.root.gameHeight / desiredWorldSpaceWidth; diff --git a/src/js/game/core.js b/src/js/game/core.js index aa411c3d..7283710e 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -189,6 +189,7 @@ export class GameCore { }); this.root.map.placeStaticEntity(hub); this.root.entityMgr.registerEntity(hub); + this.root.camera.center = new Vector(-5, 2).multiplyScalar(globalConfig.tileSize); } /** diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 3ce607fa..55eadc8e 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -287,6 +287,7 @@ export class HubGoals extends BasicSerializableObject { * @param {string} upgradeId */ canUnlockUpgrade(upgradeId) { + return true; const tiers = this.root.gameMode.getUpgrades()[upgradeId]; const currentLevel = this.getUpgradeLevel(upgradeId); diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 2f3b5629..74214f7d 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -198,6 +198,7 @@ export class GameHUD { "changesDebugger", "minerHighlight", "shapeTooltip", + "interactiveTutorial", ]; for (let i = 0; i < partsOrder.length; ++i) { diff --git a/src/js/game/hud/parts/interactive_tutorial.js b/src/js/game/hud/parts/interactive_tutorial.js index 00c02b06..ca83bd87 100644 --- a/src/js/game/hud/parts/interactive_tutorial.js +++ b/src/js/game/hud/parts/interactive_tutorial.js @@ -1,5 +1,5 @@ import { BaseHUDPart } from "../base_hud_part"; -import { makeDiv } from "../../../core/utils"; +import { clamp, makeDiv, smoothPulse } from "../../../core/utils"; import { GameRoot } from "../../root"; import { MinerComponent } from "../../components/miner"; import { DynamicDomAttach } from "../dynamic_dom_attach"; @@ -10,6 +10,15 @@ import { enumItemProcessorTypes, ItemProcessorComponent } from "../../components import { ShapeItem } from "../../items/shape_item"; import { WireComponent } from "../../components/wire"; import { LeverComponent } from "../../components/lever"; +import { DrawParameters } from "../../../core/draw_parameters"; +import { globalConfig } from "../../../core/config"; +import { Vector } from "../../../core/vector"; +import { MetaMinerBuilding } from "../../buildings/miner"; +import { gMetaBuildingRegistry } from "../../../core/global_registries"; +import { MetaBeltBuilding } from "../../buildings/belt"; +import { BeltComponent } from "../../components/belt"; +import { MetaTrashBuilding } from "../../buildings/trash"; +import { SOUNDS } from "../../../platform/sound"; // @todo: Make dictionary const tutorialsByLevel = [ @@ -24,12 +33,30 @@ const tutorialsByLevel = [ // 1.2. connect to hub { id: "1_2_conveyor", - condition: /** @param {GameRoot} root */ root => root.hubGoals.getCurrentGoalDelivered() === 0, + condition: /** @param {GameRoot} root */ root => { + const paths = root.systemMgr.systems.belt.beltPaths; + const miners = root.entityMgr.getAllWithComponent(MinerComponent); + for (let i = 0; i < paths.length; i++) { + const path = paths[i]; + const acceptingEntity = path.computeAcceptingEntityAndSlot().entity; + if (!acceptingEntity || !acceptingEntity.components.Hub) { + continue; + } + // Find a miner which delivers to this belt path + for (let k = 0; k < miners.length; ++k) { + const miner = miners[k]; + if (miner.components.ItemEjector.slots[0].cachedBeltPath === path) { + return false; + } + } + } + return true; + }, }, // 1.3 wait for completion { id: "1_3_expand", - condition: () => true, + condition: /** @param {GameRoot} root */ root => true, }, ], // Level 2 @@ -55,11 +82,7 @@ const tutorialsByLevel = [ // 2.3 place more cutters { id: "2_3_more_cutters", - condition: /** @param {GameRoot} root */ root => - root.entityMgr - .getAllWithComponent(ItemProcessorComponent) - .filter(e => e.components.ItemProcessor.type === enumItemProcessorTypes.cutter).length < - 3, + condition: /** @param {GameRoot} root */ root => true, }, ], @@ -158,7 +181,7 @@ export class HUDInteractiveTutorial extends BaseHUDPart { onHintChanged(hintId) { this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId]; - + document.documentElement.setAttribute("data-tutorial-step", hintId); const folder = G_WEGAME_VERSION ? "interactive_tutorial.cn.noinline" : "interactive_tutorial.noinline"; @@ -167,6 +190,9 @@ export class HUDInteractiveTutorial extends BaseHUDPart { "url('" + cachebust("res/ui/" + folder + "/" + hintId + ".gif") + "')"; this.element.classList.toggle("animEven"); this.element.classList.toggle("animOdd"); + if (hintId) { + this.root.app.sound.playUiSound(SOUNDS.tutorialStep); + } } update() { @@ -187,4 +213,226 @@ export class HUDInteractiveTutorial extends BaseHUDPart { this.currentHintId.set(targetHintId); this.domAttach.update(!!targetHintId); } + + /** + * + * @param {DrawParameters} parameters + */ + draw(parameters) { + const animation = smoothPulse(this.root.time.now()); + const currentBuilding = this.root.hud.parts.buildingPlacer.currentMetaBuilding.get(); + + if (["1_1_extractor"].includes(this.currentHintId.get())) { + if ( + currentBuilding && + currentBuilding.getId() === gMetaBuildingRegistry.findByClass(MetaMinerBuilding).getId() + ) { + // Find closest circle patch to hub + + let closest = null; + let closestDistance = 1e10; + + for (let i = 0; i > -globalConfig.mapChunkSize; --i) { + for (let j = 0; j < globalConfig.mapChunkSize; ++j) { + const resourceItem = this.root.map.getLowerLayerContentXY(i, j); + if ( + resourceItem instanceof ShapeItem && + resourceItem.definition.getHash() === "CuCuCuCu" + ) { + let distance = Math.hypot(i, j); + if (!closest || distance < closestDistance) { + const tile = new Vector(i, j); + if (!this.root.map.getTileContent(tile, "regular")) { + closest = tile; + closestDistance = distance; + } + } + } + } + } + + if (closest) { + parameters.context.fillStyle = "rgba(74, 237, 134, " + (0.5 - animation * 0.2) + ")"; + parameters.context.strokeStyle = "rgb(74, 237, 134)"; + parameters.context.lineWidth = 2; + parameters.context.beginRoundedRect( + closest.x * globalConfig.tileSize - 2 * animation, + closest.y * globalConfig.tileSize - 2 * animation, + globalConfig.tileSize + 4 * animation, + globalConfig.tileSize + 4 * animation, + 3 + ); + parameters.context.fill(); + parameters.context.stroke(); + parameters.context.globalAlpha = 1; + } + } + } + + if (this.currentHintId.get() === "1_2_conveyor") { + if ( + currentBuilding && + currentBuilding.getId() === gMetaBuildingRegistry.findByClass(MetaBeltBuilding).getId() + ) { + // Find closest miner + const miners = this.root.entityMgr.getAllWithComponent(MinerComponent); + + let closest = null; + let closestDistance = 1e10; + + for (let i = 0; i < miners.length; i++) { + const miner = miners[i]; + const distance = miner.components.StaticMapEntity.origin.lengthSquare(); + + if (![0, 90].includes(miner.components.StaticMapEntity.rotation)) { + continue; + } + if (!closest || distance < closestDistance) { + closest = miner; + } + } + + if (closest) { + // draw line from miner to hub -> But respect orientation + + const staticComp = closest.components.StaticMapEntity; + + const offset = staticComp.rotation === 0 ? new Vector(0.5, 0) : new Vector(1, 0.5); + + const anchor = + staticComp.rotation === 0 + ? new Vector(staticComp.origin.x + 0.5, 0.5) + : new Vector(-0.5, staticComp.origin.y + 0.5); + + const target = staticComp.rotation === 0 ? new Vector(-2.1, 0.5) : new Vector(-0.5, 2.1); + + parameters.context.globalAlpha = 0.1 + animation * 0.1; + parameters.context.strokeStyle = "rgb(74, 237, 134)"; + parameters.context.lineWidth = globalConfig.tileSize / 2; + parameters.context.beginPath(); + parameters.context.moveTo( + (staticComp.origin.x + offset.x) * globalConfig.tileSize, + (staticComp.origin.y + offset.y) * globalConfig.tileSize + ); + parameters.context.lineTo( + anchor.x * globalConfig.tileSize, + anchor.y * globalConfig.tileSize + ); + parameters.context.lineTo( + target.x * globalConfig.tileSize, + target.y * globalConfig.tileSize + ); + parameters.context.stroke(); + parameters.context.globalAlpha = 1; + + const arrowSprite = this.root.hud.parts.buildingPlacer.lockIndicatorSprites.regular; + + let arrows = []; + + let pos = staticComp.origin.add(offset); + let delta = anchor.sub(pos).normalize(); + let maxIter = 999; + + while (pos.distanceSquare(anchor) > 1 && maxIter-- > 0) { + pos = pos.add(delta); + arrows.push({ + pos: pos.sub(offset), + rotation: staticComp.rotation, + }); + } + + pos = anchor.copy(); + delta = target.sub(pos).normalize(); + const localDelta = + staticComp.rotation === 0 ? new Vector(-1.5, -0.5) : new Vector(-0.5, 0.5); + while (pos.distanceSquare(target) > 1 && maxIter-- > 0) { + pos = pos.add(delta); + arrows.push({ + pos: pos.add(localDelta), + rotation: 90 - staticComp.rotation, + }); + } + + for (let i = 0; i < arrows.length; i++) { + const { pos, rotation } = arrows[i]; + const worldPos = pos.toWorldSpaceCenterOfTile(); + const angle = Math.radians(rotation); + + parameters.context.translate(worldPos.x, worldPos.y); + parameters.context.rotate(angle); + parameters.context.drawImage( + arrowSprite, + -6, + -globalConfig.halfTileSize - + clamp((this.root.time.realtimeNow() * 1.5) % 1.0, 0, 1) * + 1 * + globalConfig.tileSize + + globalConfig.halfTileSize - + 6, + 12, + 12 + ); + parameters.context.rotate(-angle); + parameters.context.translate(-worldPos.x, -worldPos.y); + } + + parameters.context.fillStyle = "rgb(30, 40, 60)"; + parameters.context.font = "15px GameFont"; + + if (staticComp.rotation === 0) { + const pos = staticComp.origin.toWorldSpace().subScalars(2, 10); + parameters.context.translate(pos.x, pos.y); + parameters.context.rotate(-Math.radians(90)); + parameters.context.fillText( + T.ingame.interactiveTutorial.hints["1_2_hold_and_drag"], + 0, + 0 + ); + parameters.context.rotate(Math.radians(90)); + parameters.context.translate(-pos.x, -pos.y); + } else { + const pos = staticComp.origin.toWorldSpace().addScalars(40, 50); + parameters.context.fillText( + T.ingame.interactiveTutorial.hints["1_2_hold_and_drag"], + pos.x, + pos.y + ); + } + } + } + } + + if (this.currentHintId.get() === "2_2_place_trash") { + // Find cutters + if ( + currentBuilding && + currentBuilding.getId() === gMetaBuildingRegistry.findByClass(MetaTrashBuilding).getId() + ) { + const entities = this.root.entityMgr.getAllWithComponent(ItemProcessorComponent); + for (let i = 0; i < entities.length; i++) { + const entity = entities[i]; + if (entity.components.ItemProcessor.type !== enumItemProcessorTypes.cutter) { + continue; + } + + const slot = entity.components.StaticMapEntity.localTileToWorld( + new Vector(1, -1) + ).toWorldSpace(); + parameters.context.fillStyle = "rgba(74, 237, 134, " + (0.5 - animation * 0.2) + ")"; + parameters.context.strokeStyle = "rgb(74, 237, 134)"; + parameters.context.lineWidth = 2; + parameters.context.beginRoundedRect( + slot.x - 2 * animation, + slot.y - 2 * animation, + globalConfig.tileSize + 4 * animation, + globalConfig.tileSize + 4 * animation, + 3 + ); + parameters.context.fill(); + parameters.context.stroke(); + parameters.context.globalAlpha = 1; + } + } + } + } } diff --git a/src/js/game/hud/parts/tutorial_hints.js b/src/js/game/hud/parts/tutorial_hints.js index 96f7d3fd..29a07ef3 100644 --- a/src/js/game/hud/parts/tutorial_hints.js +++ b/src/js/game/hud/parts/tutorial_hints.js @@ -6,7 +6,7 @@ import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { T } from "../../../translations"; -const tutorialVideos = [2, 3, 4, 5, 6, 7, 9, 10, 11]; +const tutorialVideos = [3, 4, 5, 6, 7, 9, 10, 11]; export class HUDPartTutorialHints extends BaseHUDPart { createElements(parent) { diff --git a/src/js/states/ingame.js b/src/js/states/ingame.js index 26433bbd..aaf37269 100644 --- a/src/js/states/ingame.js +++ b/src/js/states/ingame.js @@ -110,13 +110,6 @@ export class InGameState extends GameState { return ""; } - getThemeMusic() { - if (this.creationPayload.gameModeId && this.creationPayload.gameModeId.includes("puzzle")) { - return MUSIC.puzzle; - } - return MUSIC.theme; - } - onAppPause() { // if (this.stage === stages.s10_gameRunning) { // logger.log("Saving because app got paused"); @@ -241,6 +234,15 @@ export class InGameState extends GameState { this.app.backgroundResourceLoader.getIngamePromise().then( () => { + if ( + this.creationPayload.gameModeId && + this.creationPayload.gameModeId.includes("puzzle") + ) { + this.app.sound.playThemeMusic(MUSIC.puzzle); + } else { + this.app.sound.playThemeMusic(MUSIC.theme); + } + this.loadingOverlay.loadingIndicator.innerText = ""; this.app.backgroundResourceLoader.resourceStateChangedSignal.removeAll(); diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 741370e3..1e7c8c07 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -39,8 +39,6 @@ export class MainMenuState extends GameState { getInnerHTML() { const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION; const showExitAppButton = G_IS_STANDALONE; - // const showBrowserWarning = !G_IS_STANDALONE && !isSupportedBrowser(); - const showBrowserWarning = false; const showPuzzleDLC = !G_WEGAME_VERSION && G_IS_STANDALONE && !G_IS_STEAM_DEMO; const showWegameFooter = G_WEGAME_VERSION; const hasMods = MODS.anyModsActive(); @@ -73,8 +71,23 @@ export class MainMenuState extends GameState { /** @type { PlatformWrapperImplElectron}*/ (this.app.platformWrapper).dlcs.puzzle); const bannerHtml = ` -

${T.demoBanners.title}

-

${T.demoBanners.intro}

+

${T.demoBanners.titleV2}

+ + +
+ ${Object.entries(T.ingame.standaloneAdvantages.points) + .map( + ([key, trans]) => ` +
+ ${trans.title} +

${trans.desc}

+
` + ) + .join("")} + +
+ + ${ G_IS_STEAM_DEMO ? `${T.demoBanners.playtimeDisclaimer}` @@ -110,21 +123,21 @@ export class MainMenuState extends GameState {
+
+
+
+
${showDemoAdvertisement ? `
${bannerHtml}
` : ""}
-
- ${showBrowserWarning ? `
${T.mainMenu.browserWarning}
` : ""} -
-
${ showPuzzleDLC && ownsPuzzleDLC && !hasMods @@ -425,6 +438,10 @@ export class MainMenuState extends GameState { ); } + this.htmlElement + .querySelector(".mainContainer") + .setAttribute("data-savegames", String(this.savedGames.length)); + // Mods this.trackClicks( makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title), diff --git a/src/js/states/mobile_warning.js b/src/js/states/mobile_warning.js index 24eda8e5..07aa347a 100644 --- a/src/js/states/mobile_warning.js +++ b/src/js/states/mobile_warning.js @@ -1,6 +1,5 @@ import { cachebust } from "../core/cachebust"; import { GameState } from "../core/game_state"; -import { getLogoSprite } from "../core/utils"; export class MobileWarningState extends GameState { constructor() { @@ -10,14 +9,10 @@ export class MobileWarningState extends GameState { getInnerHTML() { return ` - + -

- I'm sorry, but shapez.io is not available on mobile devices yet! - There is also no estimate when this will change, but feel to make a contribution! It's -  open source!

- -

If you want to play on your computer, you can also get the game on Steam:

+

I'm sorry, but shapez.io is not available on mobile devices yet!

+

If you have a desktop device, you can get shapez on Steam:

Play on Steam! diff --git a/translations/base-de.yaml b/translations/base-de.yaml index 8387e40b..1c69691c 100644 --- a/translations/base-de.yaml +++ b/translations/base-de.yaml @@ -57,6 +57,8 @@ global: loadingResources: Lade zusätzliche Ressourcen ( %) demoBanners: title: Demoversion + titleV2: >- + Spiele jetzt die Vollversion für: intro: |- Kaufe die Vollversion jetzt für: