v1.5.5 - Rework tutorial and polishing

pull/1449/head
tobspr 2 years ago
parent 482a4990ba
commit 8c5e593ceb

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

@ -55,6 +55,35 @@
position: relative; position: relative;
@include S(height, 40px); @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 { .icon {
color: $accentColorDark; color: $accentColorDark;
display: flex; display: flex;

@ -32,12 +32,6 @@
pointer-events: all; pointer-events: all;
transition: opacity 0.1s ease-out; transition: opacity 0.1s ease-out;
&.hovered {
opacity: 10%;
.helperGif {
opacity: 0%;
}
}
.title { .title {
color: #fff; color: #fff;

@ -126,6 +126,7 @@
@include S(height, 40px); @include S(height, 40px);
background: #171a23 center center / contain no-repeat; background: #171a23 center center / contain no-repeat;
box-shadow: 0 D(3px) D(10px) rgba(96, 163, 136, 0.5);
overflow: visible; overflow: visible;
@include S(border-radius, $globalBorderRadius); @include S(border-radius, $globalBorderRadius);
@ -135,6 +136,10 @@
} }
} }
&:hover {
opacity: 0.94 !important;
}
> .discount { > .discount {
position: absolute; position: absolute;
@include S(top, -7px); @include S(top, -7px);

@ -102,30 +102,6 @@ button,
opacity: 1; 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 */ /* Automatically transforms the game state if a hardware keyboard is open */
@mixin TransformToMatchKeyboard { @mixin TransformToMatchKeyboard {

@ -61,7 +61,7 @@
opacity: 0; opacity: 0;
display: none; display: none;
transform: translate(50%, 50%); transform: translate(50%, 50%);
filter: blur(D(7px)); filter: blur(D(15px));
$opacity: 0.4; $opacity: 0.4;
&.loaded { &.loaded {
@ -81,34 +81,41 @@
.mainWrapper { .mainWrapper {
@include S(padding, 0, 10px); @include S(padding, 0, 10px);
@include S(margin-top, 15px);
align-items: start; align-items: start;
justify-items: center; justify-items: center;
@include S(grid-column-gap, 10px); @include S(grid-column-gap, 10px);
display: grid; display: grid;
grid-template-rows: D(31px) 1fr D(93px);
&[data-columns="1"] { &[data-columns="1"] {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
&[data-columns="2"] { &[data-columns="2"] {
grid-template-columns: 1fr 1fr; grid-template-columns: D(290px) 1fr;
} }
.standaloneBanner { .standaloneBanner {
background: rgba(12, 168, 93, 0.957); background: transparent;
@include S(border-radius, $globalBorderRadius); @include S(border-radius, $globalBorderRadius);
// box-shadow: 0 D(5px) D(15px) rgba(#000, 0.2);
@include S(width, 380px);
box-sizing: border-box; box-sizing: border-box;
@include S(padding, 15px); @include S(padding, 0, 15px);
box-shadow: 0 D(5px) D(15px) rgba(#000, 0.2); // backdrop-filter: blur(10px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 0;
strong { strong {
font-weight: 700 !important; font-weight: 700 !important;
} }
.onlinePlayerCount { .onlinePlayerCount {
color: #fff; color: #333;
@include S(margin-top, 15px); @include S(margin-top, 15px);
@include SuperSmallText; @include SuperSmallText;
@include S(height, 15px); @include S(height, 15px);
@ -118,15 +125,14 @@
h3 { h3 {
@include Heading; @include Heading;
font-weight: bold; font-weight: bold;
@include S(margin-bottom, 20px); @include S(margin-bottom, 10px);
display: none; text-align: center;
text-transform: uppercase; color: #44484f;
color: #fff;
} }
p { p {
@include Text; @include Text;
color: #fff; color: #333;
} }
ul { ul {
@ -139,7 +145,7 @@
} }
.playtimeDisclaimer { .playtimeDisclaimer {
color: #fff; color: #333;
@include S(margin-top, 15px); @include S(margin-top, 15px);
@include SuperSmallText; @include SuperSmallText;
} }
@ -162,6 +168,7 @@
@include S(border-radius, $globalBorderRadius); @include S(border-radius, $globalBorderRadius);
color: transparent; color: transparent;
box-shadow: 0 D(3px) D(10px) rgba(96, 163, 136, 0.5);
&:hover { &:hover {
opacity: 0.9; 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); @include S(padding-top, 20px);
img { img {
@include S(width, 710px / 2.2); @include S(width, 710px / 2.5);
@include S(height, 180 / 2.2px); @include S(height, 180px / 2.5);
} }
position: relative; position: relative;
@include S(left, -8px); @include S(left, -8px);
@ -243,11 +395,12 @@
.sideContainer { .sideContainer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@include S(width, 300px); width: 100%;
grid-row: 1 / 4;
grid-column: 2 / 3;
.standaloneBanner { .standaloneBanner {
flex-grow: 1; flex-grow: 1;
@include S(margin-bottom, 10px);
} }
} }
@ -258,7 +411,6 @@
flex-direction: column; flex-direction: column;
background: $colorBlueBright; background: $colorBlueBright;
grid-row: 1 / 2; grid-row: 1 / 2;
grid-column: 2 / 3;
position: relative; position: relative;
@include S(padding, 20px); @include S(padding, 20px);
@include S(border-radius, $globalBorderRadius); @include S(border-radius, $globalBorderRadius);
@ -324,7 +476,6 @@
flex-direction: column; flex-direction: column;
background: #fff; background: #fff;
grid-row: 1 / 2; grid-row: 1 / 2;
grid-column: 2 / 3;
position: relative; position: relative;
text-align: left; text-align: left;
align-items: flex-start; align-items: flex-start;
@ -418,18 +569,24 @@
.mainContainer { .mainContainer {
display: flex; display: flex;
align-items: center; align-items: center;
grid-row: 1 / 2;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
background: #fafafa; background: rgba(#fff, 0.9);
@include S(padding, 20px); @include S(padding, 15px);
@include S(border-radius, $globalBorderRadius); @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%; height: 100%;
box-shadow: 0 D(5px) D(15px) rgba(#000, 0.2);
width: 100%; width: 100%;
position: relative; 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; box-sizing: border-box;
.buttons { .buttons {
@ -483,6 +640,16 @@
.outer { .outer {
@include S(margin-top, 15px); @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 { .importButton {
@ -491,14 +658,10 @@
.newGameButton { .newGameButton {
@include IncreasedClickArea(0px); @include IncreasedClickArea(0px);
@include S(margin-left, 10px);
} }
.modsButton { .modsButton {
@include IncreasedClickArea(0px); @include IncreasedClickArea(0px);
@include S(margin-left, 10px);
// @include S(width, 20px);
background-position: center center; background-position: center center;
background-size: D(15px); background-size: D(15px);
@ -509,9 +672,11 @@
.savegames { .savegames {
@include S(max-height, 105px); @include S(max-height, 105px);
overflow-y: auto; overflow-y: auto;
@include S(width, 250px); @include S(min-width, 230px);
width: 100%;
pointer-events: all; pointer-events: all;
@include S(padding-right, 5px); @include S(padding-right, 5px);
margin-right: D(-5px);
display: grid; display: grid;
grid-auto-flow: row; grid-auto-flow: row;
@include S(grid-gap, 5px); @include S(grid-gap, 5px);
@ -586,6 +751,7 @@
@include S(height, 15px); @include S(height, 15px);
background-size: 80%; background-size: 80%;
align-self: start; align-self: start;
border-radius: 0;
opacity: 0.4; opacity: 0.4;
&:hover { &:hover {
@ -608,6 +774,7 @@
@include S(height, 15px); @include S(height, 15px);
align-self: end; align-self: end;
background-size: 80%; background-size: 80%;
border-radius: 0;
opacity: 0.4; opacity: 0.4;
&:hover { &:hover {
@ -628,7 +795,7 @@
@include S(height, 10px); @include S(height, 10px);
align-self: center; align-self: center;
justify-self: center; justify-self: center;
border-radius: 0;
background-size: 90%; background-size: 90%;
opacity: 0.4; opacity: 0.4;
@include S(margin-left, 4px); @include S(margin-left, 4px);
@ -725,7 +892,7 @@
a { a {
&:hover img { &:hover img {
opacity: 0.8; opacity: 0.85;
} }
display: flex; display: flex;
align-items: center; align-items: center;
@ -736,7 +903,7 @@
@include S(width, 82px); @include S(width, 82px);
@include S(height, 25px); @include S(height, 25px);
filter: invert(100%); filter: invert(100%);
opacity: 0.6; opacity: 0.75;
} }
} }
} }
@ -744,7 +911,7 @@
@include S(padding, 15px); @include S(padding, 15px);
$linkBg: rgba(#fdfdff, 0.5); $linkBg: rgba(#fdfdff, 0.5);
$linkBgHover: darken($linkBg, 5); $linkBgHover: rgba(#fff, 0.7);
$linkColor: #55586a; $linkColor: #55586a;
> .boxLink { > .boxLink {

@ -1,7 +1,7 @@
#state_MobileWarningState { #state_MobileWarningState {
display: flex; display: flex;
align-items: center; align-items: center;
background: #333438 !important; background: #555b75 !important;
@include S(padding, 20px); @include S(padding, 20px);
box-sizing: border-box; box-sizing: border-box;
justify-content: center; justify-content: center;
@ -14,7 +14,7 @@
} }
p { p {
color: #aaacaf; color: rgba(#fff, 0.5);
display: block; display: block;
margin-bottom: 13px; margin-bottom: 13px;
font-size: 16px; font-size: 16px;
@ -28,12 +28,11 @@
.standaloneLink { .standaloneLink {
width: 200px; width: 200px;
height: 80px; height: 48px;
min-height: 40px; 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; display: block;
text-indent: -999em; text-indent: -999em;
cursor: pointer; cursor: pointer;
@ -41,7 +40,9 @@
pointer-events: all; pointer-events: all;
transition: all 0.12s ease-in; transition: all 0.12s ease-in;
transition-property: opacity, transform; transition-property: opacity, transform;
transform: skewX(-0.5deg); @include S(border-radius, $globalBorderRadius);
overflow: hidden;
&:hover { &:hover {
transform: skewX(-1deg) scale(1.02); transform: skewX(-1deg) scale(1.02);
opacity: 0.9; opacity: 0.9;

@ -38,6 +38,7 @@
transition: all 0.12s ease-in; transition: all 0.12s ease-in;
transition-property: opacity, transform; transition-property: opacity, transform;
box-shadow: 0 D(3px) D(10px) rgba(96, 163, 136, 0.5);
@include S(border-radius, $globalBorderRadius); @include S(border-radius, $globalBorderRadius);
&:hover { &:hover {

@ -178,34 +178,3 @@ $mainFontScale: 1;
@function trim($string) { @function trim($string) {
@return str-slice($string, _first-index($string, "left"), _first-index($string, "right")); @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;
}
}

@ -1,4 +1,13 @@
export const CHANGELOG = [ 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", version: "1.5.3",
date: "05.06.2022", date: "05.06.2022",

@ -183,7 +183,7 @@ export class BeltPath extends BasicSerializableObject {
* Recomputes cache variables once the path was changed * Recomputes cache variables once the path was changed
*/ */
onPathChanged() { onPathChanged() {
this.boundAcceptor = this.computeAcceptingEntityAndSlot(); this.boundAcceptor = this.computeAcceptingEntityAndSlot().acceptor;
/** /**
* How many items past the first item are compressed * 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 * Finds the entity which accepts our items
* @param {boolean=} debug_Silent Whether debug output should be silent * @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) { computeAcceptingEntityAndSlot(debug_Silent = false) {
DEBUG && !debug_Silent && logger.log("Recomputing acceptor target"); DEBUG && !debug_Silent && logger.log("Recomputing acceptor target");
@ -224,7 +224,7 @@ export class BeltPath extends BasicSerializableObject {
); );
if (!targetEntity) { if (!targetEntity) {
return; return {};
} }
const noSimplifiedBelts = !this.root.app.settings.getAllSettings().simplifiedBelts; const noSimplifiedBelts = !this.root.app.settings.getAllSettings().simplifiedBelts;
@ -247,10 +247,13 @@ export class BeltPath extends BasicSerializableObject {
targetStaticComp.rotation targetStaticComp.rotation
); );
if (ejectSlotWsDirection === beltAcceptingDirection) { if (ejectSlotWsDirection === beltAcceptingDirection) {
return item => { return {
const path = targetBeltComp.assignedPath; entity: targetEntity,
assert(path, "belt has no path"); acceptor: item => {
return path.tryAcceptItem(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; const targetAcceptorComp = targetEntity.components.ItemAcceptor;
if (!targetAcceptorComp) { if (!targetAcceptorComp) {
// Entity doesn't accept items // Entity doesn't accept items
return; return {};
} }
const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection); const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection);
@ -270,38 +273,41 @@ export class BeltPath extends BasicSerializableObject {
if (!matchingSlot) { if (!matchingSlot) {
// No matching slot found // No matching slot found
return; return {};
} }
const matchingSlotIndex = matchingSlot.index; const matchingSlotIndex = matchingSlot.index;
const passOver = this.computePassOverFunctionWithoutBelts(targetEntity, matchingSlotIndex); const passOver = this.computePassOverFunctionWithoutBelts(targetEntity, matchingSlotIndex);
if (!passOver) { if (!passOver) {
return; return {};
} }
const matchingDirection = enumInvertedDirections[ejectingDirection]; const matchingDirection = enumInvertedDirections[ejectingDirection];
const filter = matchingSlot.slot.filter; const filter = matchingSlot.slot.filter;
return function (item, remainingProgress = 0.0) { return {
// Check if the acceptor has a filter entity: targetEntity,
if (filter && item._type !== filter) { acceptor: function (item, remainingProgress = 0.0) {
return false; // Check if the acceptor has a filter
} if (filter && item._type !== filter) {
return false;
}
// Try to pass over // Try to pass over
if (passOver(item, matchingSlotIndex)) { if (passOver(item, matchingSlotIndex)) {
// Trigger animation on the acceptor comp // Trigger animation on the acceptor comp
if (noSimplifiedBelts) { if (noSimplifiedBelts) {
targetAcceptorComp.onItemAccepted( targetAcceptorComp.onItemAccepted(
matchingSlotIndex, matchingSlotIndex,
matchingDirection, matchingDirection,
item, item,
remainingProgress remainingProgress
); );
}
return true;
} }
return true; return false;
} },
return false;
}; };
} }
@ -494,7 +500,7 @@ export class BeltPath extends BasicSerializableObject {
} }
// Check acceptor // Check acceptor
const acceptor = this.computeAcceptingEntityAndSlot(true); const acceptor = this.computeAcceptingEntityAndSlot(true).acceptor;
if (!!acceptor !== !!this.boundAcceptor) { if (!!acceptor !== !!this.boundAcceptor) {
return fail("Acceptor target mismatch, acceptor", !!acceptor, "vs stored", !!this.boundAcceptor); return fail("Acceptor target mismatch, acceptor", !!acceptor, "vs stored", !!this.boundAcceptor);
} }

@ -168,7 +168,7 @@ export class Camera extends BasicSerializableObject {
* Finds a good initial zoom level * Finds a good initial zoom level
*/ */
findInitialZoom() { findInitialZoom() {
const desiredWorldSpaceWidth = 15 * globalConfig.tileSize; const desiredWorldSpaceWidth = 18 * globalConfig.tileSize;
const zoomLevelX = this.root.gameWidth / desiredWorldSpaceWidth; const zoomLevelX = this.root.gameWidth / desiredWorldSpaceWidth;
const zoomLevelY = this.root.gameHeight / desiredWorldSpaceWidth; const zoomLevelY = this.root.gameHeight / desiredWorldSpaceWidth;

@ -189,6 +189,7 @@ export class GameCore {
}); });
this.root.map.placeStaticEntity(hub); this.root.map.placeStaticEntity(hub);
this.root.entityMgr.registerEntity(hub); this.root.entityMgr.registerEntity(hub);
this.root.camera.center = new Vector(-5, 2).multiplyScalar(globalConfig.tileSize);
} }
/** /**

@ -287,6 +287,7 @@ export class HubGoals extends BasicSerializableObject {
* @param {string} upgradeId * @param {string} upgradeId
*/ */
canUnlockUpgrade(upgradeId) { canUnlockUpgrade(upgradeId) {
return true;
const tiers = this.root.gameMode.getUpgrades()[upgradeId]; const tiers = this.root.gameMode.getUpgrades()[upgradeId];
const currentLevel = this.getUpgradeLevel(upgradeId); const currentLevel = this.getUpgradeLevel(upgradeId);

@ -198,6 +198,7 @@ export class GameHUD {
"changesDebugger", "changesDebugger",
"minerHighlight", "minerHighlight",
"shapeTooltip", "shapeTooltip",
"interactiveTutorial",
]; ];
for (let i = 0; i < partsOrder.length; ++i) { for (let i = 0; i < partsOrder.length; ++i) {

@ -1,5 +1,5 @@
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { makeDiv } from "../../../core/utils"; import { clamp, makeDiv, smoothPulse } from "../../../core/utils";
import { GameRoot } from "../../root"; import { GameRoot } from "../../root";
import { MinerComponent } from "../../components/miner"; import { MinerComponent } from "../../components/miner";
import { DynamicDomAttach } from "../dynamic_dom_attach"; import { DynamicDomAttach } from "../dynamic_dom_attach";
@ -10,6 +10,15 @@ import { enumItemProcessorTypes, ItemProcessorComponent } from "../../components
import { ShapeItem } from "../../items/shape_item"; import { ShapeItem } from "../../items/shape_item";
import { WireComponent } from "../../components/wire"; import { WireComponent } from "../../components/wire";
import { LeverComponent } from "../../components/lever"; 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 // @todo: Make dictionary
const tutorialsByLevel = [ const tutorialsByLevel = [
@ -24,12 +33,30 @@ const tutorialsByLevel = [
// 1.2. connect to hub // 1.2. connect to hub
{ {
id: "1_2_conveyor", 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 // 1.3 wait for completion
{ {
id: "1_3_expand", id: "1_3_expand",
condition: () => true, condition: /** @param {GameRoot} root */ root => true,
}, },
], ],
// Level 2 // Level 2
@ -55,11 +82,7 @@ const tutorialsByLevel = [
// 2.3 place more cutters // 2.3 place more cutters
{ {
id: "2_3_more_cutters", id: "2_3_more_cutters",
condition: /** @param {GameRoot} root */ root => condition: /** @param {GameRoot} root */ root => true,
root.entityMgr
.getAllWithComponent(ItemProcessorComponent)
.filter(e => e.components.ItemProcessor.type === enumItemProcessorTypes.cutter).length <
3,
}, },
], ],
@ -158,7 +181,7 @@ export class HUDInteractiveTutorial extends BaseHUDPart {
onHintChanged(hintId) { onHintChanged(hintId) {
this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId]; this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId];
document.documentElement.setAttribute("data-tutorial-step", hintId);
const folder = G_WEGAME_VERSION const folder = G_WEGAME_VERSION
? "interactive_tutorial.cn.noinline" ? "interactive_tutorial.cn.noinline"
: "interactive_tutorial.noinline"; : "interactive_tutorial.noinline";
@ -167,6 +190,9 @@ export class HUDInteractiveTutorial extends BaseHUDPart {
"url('" + cachebust("res/ui/" + folder + "/" + hintId + ".gif") + "')"; "url('" + cachebust("res/ui/" + folder + "/" + hintId + ".gif") + "')";
this.element.classList.toggle("animEven"); this.element.classList.toggle("animEven");
this.element.classList.toggle("animOdd"); this.element.classList.toggle("animOdd");
if (hintId) {
this.root.app.sound.playUiSound(SOUNDS.tutorialStep);
}
} }
update() { update() {
@ -187,4 +213,226 @@ export class HUDInteractiveTutorial extends BaseHUDPart {
this.currentHintId.set(targetHintId); this.currentHintId.set(targetHintId);
this.domAttach.update(!!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;
}
}
}
}
} }

@ -6,7 +6,7 @@ import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach"; import { DynamicDomAttach } from "../dynamic_dom_attach";
import { T } from "../../../translations"; 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 { export class HUDPartTutorialHints extends BaseHUDPart {
createElements(parent) { createElements(parent) {

@ -110,13 +110,6 @@ export class InGameState extends GameState {
return ""; return "";
} }
getThemeMusic() {
if (this.creationPayload.gameModeId && this.creationPayload.gameModeId.includes("puzzle")) {
return MUSIC.puzzle;
}
return MUSIC.theme;
}
onAppPause() { onAppPause() {
// if (this.stage === stages.s10_gameRunning) { // if (this.stage === stages.s10_gameRunning) {
// logger.log("Saving because app got paused"); // logger.log("Saving because app got paused");
@ -241,6 +234,15 @@ export class InGameState extends GameState {
this.app.backgroundResourceLoader.getIngamePromise().then( 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.loadingOverlay.loadingIndicator.innerText = "";
this.app.backgroundResourceLoader.resourceStateChangedSignal.removeAll(); this.app.backgroundResourceLoader.resourceStateChangedSignal.removeAll();

@ -39,8 +39,6 @@ export class MainMenuState extends GameState {
getInnerHTML() { getInnerHTML() {
const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION; const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION;
const showExitAppButton = G_IS_STANDALONE; 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 showPuzzleDLC = !G_WEGAME_VERSION && G_IS_STANDALONE && !G_IS_STEAM_DEMO;
const showWegameFooter = G_WEGAME_VERSION; const showWegameFooter = G_WEGAME_VERSION;
const hasMods = MODS.anyModsActive(); const hasMods = MODS.anyModsActive();
@ -73,8 +71,23 @@ export class MainMenuState extends GameState {
/** @type { PlatformWrapperImplElectron}*/ (this.app.platformWrapper).dlcs.puzzle); /** @type { PlatformWrapperImplElectron}*/ (this.app.platformWrapper).dlcs.puzzle);
const bannerHtml = ` const bannerHtml = `
<h3>${T.demoBanners.title}</h3> <h3>${T.demoBanners.titleV2}</h3>
<p>${T.demoBanners.intro}</p>
<div class="points">
${Object.entries(T.ingame.standaloneAdvantages.points)
.map(
([key, trans]) => `
<div class="point ${key}">
<strong>${trans.title}</strong>
<p>${trans.desc}</p>
</div>`
)
.join("")}
</div>
${ ${
G_IS_STEAM_DEMO G_IS_STEAM_DEMO
? `<span class="playtimeDisclaimer">${T.demoBanners.playtimeDisclaimer}</span>` ? `<span class="playtimeDisclaimer">${T.demoBanners.playtimeDisclaimer}</span>`
@ -110,21 +123,21 @@ export class MainMenuState extends GameState {
<div class="logo"> <div class="logo">
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo" <img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo"
width="${Math.round((710 / 2.2) * this.app.getEffectiveUiScale())}" width="${Math.round((710 / 2.5) * this.app.getEffectiveUiScale())}"
height="${Math.round((180 / 2.2) * this.app.getEffectiveUiScale())}" height="${Math.round((180 / 2.5) * this.app.getEffectiveUiScale())}"
> >
${/*showUpdateLabel ? `<span class="updateLabel">MODS UPDATE!</span>` : ""*/ ""} ${/*showUpdateLabel ? `<span class="updateLabel">MODS UPDATE!</span>` : ""*/ ""}
</div> </div>
<div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}"> <div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}">
<div class="mainContainer">
<div class="buttons"></div>
</div>
<div class="sideContainer"> <div class="sideContainer">
${showDemoAdvertisement ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""} ${showDemoAdvertisement ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
</div> </div>
<div class="mainContainer">
${showBrowserWarning ? `<div class="browserWarning">${T.mainMenu.browserWarning}</div>` : ""}
<div class="buttons"></div>
</div>
${ ${
showPuzzleDLC && ownsPuzzleDLC && !hasMods showPuzzleDLC && ownsPuzzleDLC && !hasMods
@ -425,6 +438,10 @@ export class MainMenuState extends GameState {
); );
} }
this.htmlElement
.querySelector(".mainContainer")
.setAttribute("data-savegames", String(this.savedGames.length));
// Mods // Mods
this.trackClicks( this.trackClicks(
makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title), makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title),

@ -1,6 +1,5 @@
import { cachebust } from "../core/cachebust"; import { cachebust } from "../core/cachebust";
import { GameState } from "../core/game_state"; import { GameState } from "../core/game_state";
import { getLogoSprite } from "../core/utils";
export class MobileWarningState extends GameState { export class MobileWarningState extends GameState {
constructor() { constructor() {
@ -10,14 +9,10 @@ export class MobileWarningState extends GameState {
getInnerHTML() { getInnerHTML() {
return ` return `
<img class="logo" src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo"> <img class="logo" src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<p> <p>I'm sorry, but shapez.io is not available on mobile devices yet!</p>
I'm sorry, but shapez.io is not available on mobile devices yet! <p>If you have a desktop device, you can get shapez on Steam:</p>
There is also no estimate when this will change, but feel to make a contribution! It's
&nbsp;<a href="https://github.com/tobspr/shapez.io" target="_blank">open source</a>!</p>
<p>If you want to play on your computer, you can also get the game on Steam:</p>
<a href="https://get.shapez.io/shapez_mobile" class="standaloneLink" target="_blank">Play on Steam!</a> <a href="https://get.shapez.io/shapez_mobile" class="standaloneLink" target="_blank">Play on Steam!</a>

@ -57,6 +57,8 @@ global:
loadingResources: Lade zusätzliche Ressourcen (<percentage> %) loadingResources: Lade zusätzliche Ressourcen (<percentage> %)
demoBanners: demoBanners:
title: Demoversion title: Demoversion
titleV2: >-
Spiele jetzt die Vollversion für:
intro: |- intro: |-
Kaufe die Vollversion <strong>jetzt</strong> für:<ul> Kaufe die Vollversion <strong>jetzt</strong> für:<ul>
<li>Alle 26 Level + unendlich Freeplay</li> <li>Alle 26 Level + unendlich Freeplay</li>
@ -77,7 +79,7 @@ mainMenu:
newGame: Neues Spiel newGame: Neues Spiel
changelog: Änderungshistorie changelog: Änderungshistorie
subreddit: Reddit subreddit: Reddit
importSavegame: Importieren importSavegame: Import
openSourceHint: Dieses Spiel ist quelloffen! openSourceHint: Dieses Spiel ist quelloffen!
discordLink: Offizieller Discord Server discordLink: Offizieller Discord Server
helpTranslate: Hilf beim Übersetzen! helpTranslate: Hilf beim Übersetzen!
@ -497,6 +499,7 @@ ingame:
Signal</strong> ausgibt und den Färber aktiviert.<br><br> PS: Du Signal</strong> ausgibt und den Färber aktiviert.<br><br> PS: Du
musst nicht alle Eingänge verbinden! Probiere es auch mal mit musst nicht alle Eingänge verbinden! Probiere es auch mal mit
zwei." zwei."
1_2_hold_and_drag: Drücke und ziehe
connectedMiners: connectedMiners:
one_miner: Ein Extraktor one_miner: Ein Extraktor
n_miners: <amount> Extraktoren n_miners: <amount> Extraktoren

@ -97,6 +97,9 @@ global:
demoBanners: demoBanners:
# This is the "advertisement" shown in the main menu and other various places # This is the "advertisement" shown in the main menu and other various places
title: Demo title: Demo
titleV2: >-
Play the full version now for:
intro: >- intro: >-
Get the full game <strong>now</strong> to unlock:<ul> Get the full game <strong>now</strong> to unlock:<ul>
<li>All 26 levels + infinite Freeplay</li> <li>All 26 levels + infinite Freeplay</li>
@ -641,6 +644,8 @@ ingame:
Press the switch to make it <strong>emit a truthy signal</strong> and thus activate the painter.<br><br> Press the switch to make it <strong>emit a truthy signal</strong> and thus activate the painter.<br><br>
PS: You don't have to connect all inputs! Try wiring only two. PS: You don't have to connect all inputs! Try wiring only two.
1_2_hold_and_drag: Hold and drag
# Connected miners # Connected miners
connectedMiners: connectedMiners:
one_miner: 1 Extractor one_miner: 1 Extractor
@ -655,7 +660,7 @@ ingame:
standaloneAdvantages: standaloneAdvantages:
titleV2: >- titleV2: >-
Get the full version now on Steam to unlock: Play the full version now on Steam to unlock:
titleExpiredV2: Demo completed! titleExpiredV2: Demo completed!
titleEnjoyingDemo: Enjoying the demo? titleEnjoyingDemo: Enjoying the demo?

@ -1 +1 @@
1.5.4 1.5.5
Loading…
Cancel
Save