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 {
${/*showUpdateLabel ? `
MODS UPDATE!` : ""*/ ""}
+
+
${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:
- Alle 26 Level + unendlich Freeplay
@@ -77,7 +79,7 @@ mainMenu:
newGame: Neues Spiel
changelog: Änderungshistorie
subreddit: Reddit
- importSavegame: Importieren
+ importSavegame: Import
openSourceHint: Dieses Spiel ist quelloffen!
discordLink: Offizieller Discord Server
helpTranslate: Hilf beim Übersetzen!
@@ -497,6 +499,7 @@ ingame:
Signal ausgibt und den Färber aktiviert.
PS: Du
musst nicht alle Eingänge verbinden! Probiere es auch mal mit
zwei."
+ 1_2_hold_and_drag: Drücke und ziehe
connectedMiners:
one_miner: Ein Extraktor
n_miners: Extraktoren
diff --git a/translations/base-en.yaml b/translations/base-en.yaml
index 0233afe6..27f00080 100644
--- a/translations/base-en.yaml
+++ b/translations/base-en.yaml
@@ -97,6 +97,9 @@ global:
demoBanners:
# This is the "advertisement" shown in the main menu and other various places
title: Demo
+ titleV2: >-
+ Play the full version now for:
+
intro: >-
Get the full game now to unlock:
- All 26 levels + infinite Freeplay
@@ -641,6 +644,8 @@ ingame:
Press the switch to make it emit a truthy signal and thus activate the painter.
PS: You don't have to connect all inputs! Try wiring only two.
+ 1_2_hold_and_drag: Hold and drag
+
# Connected miners
connectedMiners:
one_miner: 1 Extractor
@@ -655,7 +660,7 @@ ingame:
standaloneAdvantages:
titleV2: >-
- Get the full version now on Steam to unlock:
+ Play the full version now on Steam to unlock:
titleExpiredV2: Demo completed!
titleEnjoyingDemo: Enjoying the demo?
diff --git a/version b/version
index 63ebd3fe..5ebba4f0 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-1.5.4
\ No newline at end of file
+1.5.5
\ No newline at end of file