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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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 = `
<h3>${T.demoBanners.title}</h3>
<p>${T.demoBanners.intro}</p>
<h3>${T.demoBanners.titleV2}</h3>
<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
? `<span class="playtimeDisclaimer">${T.demoBanners.playtimeDisclaimer}</span>`
@ -110,21 +123,21 @@ export class MainMenuState extends GameState {
<div class="logo">
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo"
width="${Math.round((710 / 2.2) * this.app.getEffectiveUiScale())}"
height="${Math.round((180 / 2.2) * this.app.getEffectiveUiScale())}"
width="${Math.round((710 / 2.5) * this.app.getEffectiveUiScale())}"
height="${Math.round((180 / 2.5) * this.app.getEffectiveUiScale())}"
>
${/*showUpdateLabel ? `<span class="updateLabel">MODS UPDATE!</span>` : ""*/ ""}
</div>
<div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}">
<div class="mainContainer">
<div class="buttons"></div>
</div>
<div class="sideContainer">
${showDemoAdvertisement ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
</div>
<div class="mainContainer">
${showBrowserWarning ? `<div class="browserWarning">${T.mainMenu.browserWarning}</div>` : ""}
<div class="buttons"></div>
</div>
${
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),

@ -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 `
<img class="logo" src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo">
<img class="logo" src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<p>
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
&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>
<p>I'm sorry, but shapez.io is not available on mobile devices yet!</p>
<p>If you have a desktop device, you can get shapez on Steam:</p>
<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> %)
demoBanners:
title: Demoversion
titleV2: >-
Spiele jetzt die Vollversion für:
intro: |-
Kaufe die Vollversion <strong>jetzt</strong> für:<ul>
<li>Alle 26 Level + unendlich Freeplay</li>
@ -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</strong> ausgibt und den Färber aktiviert.<br><br> 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: <amount> Extraktoren

@ -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 <strong>now</strong> to unlock:<ul>
<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>
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?

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