diff --git a/res/ui/icons/advantage_buildings.png b/res/ui/icons/advantage_buildings.png new file mode 100644 index 00000000..91e7a565 Binary files /dev/null and b/res/ui/icons/advantage_buildings.png differ diff --git a/res/ui/icons/advantage_dark_mode.png b/res/ui/icons/advantage_dark_mode.png new file mode 100644 index 00000000..c176b8ad Binary files /dev/null and b/res/ui/icons/advantage_dark_mode.png differ diff --git a/res/ui/icons/advantage_markers.png b/res/ui/icons/advantage_markers.png new file mode 100644 index 00000000..af3835c7 Binary files /dev/null and b/res/ui/icons/advantage_markers.png differ diff --git a/res/ui/icons/advantage_new_levels.png b/res/ui/icons/advantage_new_levels.png new file mode 100644 index 00000000..730732fe Binary files /dev/null and b/res/ui/icons/advantage_new_levels.png differ diff --git a/res/ui/icons/advantage_savegames.png b/res/ui/icons/advantage_savegames.png new file mode 100644 index 00000000..79e7c327 Binary files /dev/null and b/res/ui/icons/advantage_savegames.png differ diff --git a/res/ui/icons/advantage_support.png b/res/ui/icons/advantage_support.png new file mode 100644 index 00000000..b86703a5 Binary files /dev/null and b/res/ui/icons/advantage_support.png differ diff --git a/res/ui/icons/advantage_upgrades.png b/res/ui/icons/advantage_upgrades.png new file mode 100644 index 00000000..e06b6d82 Binary files /dev/null and b/res/ui/icons/advantage_upgrades.png differ diff --git a/res/ui/icons/advantage_wires.png b/res/ui/icons/advantage_wires.png new file mode 100644 index 00000000..bb26fa87 Binary files /dev/null and b/res/ui/icons/advantage_wires.png differ diff --git a/res/ui/icons/demo_steam_link_indicator.png b/res/ui/icons/demo_steam_link_indicator.png new file mode 100644 index 00000000..98add9df Binary files /dev/null and b/res/ui/icons/demo_steam_link_indicator.png differ diff --git a/src/css/ingame_hud/blueprint_placer.scss b/src/css/ingame_hud/blueprint_placer.scss index e1cf06ef..36d1cdad 100644 --- a/src/css/ingame_hud/blueprint_placer.scss +++ b/src/css/ingame_hud/blueprint_placer.scss @@ -1,39 +1,40 @@ -#ingame_HUD_BlueprintPlacer { - position: absolute; - @include S(top, 50px); - left: 50%; - transform: translateX(-50%); - color: #333; - z-index: 9999; - background: $ingameHudBg; - @include S(padding, 5px); - display: flex; - flex-direction: column; - color: #fff; - @include S(width, 120px); - align-items: center; - justify-content: center; - - .label { - @include PlainText; - text-transform: uppercase; - } - .costContainer { - display: flex; - align-items: center; - @include Heading; - - > canvas { - @include S(margin-left, 5px); - @include S(width, 30px); - @include S(height, 30px); - } - } - - &:not(.canAfford) { - background: rgba(98, 27, 41, 0.8); - // .costContainer { - color: rgb(255, 97, 128); - // } - } -} +#ingame_HUD_BlueprintPlacer { + position: absolute; + @include S(top, 70px); + left: 50%; + transform: translateX(-50%); + color: #333; + z-index: 9999; + background: $ingameHudBg; + @include S(padding, 5px); + display: flex; + flex-direction: column; + color: #fff; + @include S(width, 120px); + align-items: center; + justify-content: center; + @include S(border-radius, $globalBorderRadius); + + .label { + @include PlainText; + text-transform: uppercase; + } + .costContainer { + display: flex; + align-items: center; + @include Heading; + + > canvas { + @include S(margin-left, 5px); + @include S(width, 30px); + @include S(height, 30px); + } + } + + &:not(.canAfford) { + background: rgba(98, 27, 41, 0.8); + // .costContainer { + color: rgb(255, 97, 128); + // } + } +} diff --git a/src/css/ingame_hud/standalone_advantages.scss b/src/css/ingame_hud/standalone_advantages.scss new file mode 100644 index 00000000..02c0f5e2 --- /dev/null +++ b/src/css/ingame_hud/standalone_advantages.scss @@ -0,0 +1,171 @@ +#ingame_HUD_StandaloneAdvantages { + .content { + @include S(width, 440px); + @include S(min-height, 300px); + } + p { + @include PlainText; + } + + .points { + display: grid; + grid-template-columns: 1fr 1fr; + @include S(grid-column-gap, 10px); + @include S(grid-row-gap, 20px); + @include S(margin, 10px, 0, 20px); + grid-template-rows: #{D(40px)}; + align-items: center; + } + .lowerBar { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + overflow: hidden; + + > button { + transition: opacity 0.12s ease-in-out; + &:hover { + opacity: 0.85; + } + } + + .otherCloseButton { + @include SuperSmallText; + @include S(margin-right, 30px); + color: #aaa; + @include S(margin, 0); + @include IncreasedClickArea(0px); + @include S(margin-top, 15px); + + @include InlineAnimation(5s ease-in-out) { + 0% { + opacity: 0.05; + } + 50% { + opacity: 0.05; + } + 100% { + opacity: 1; + } + } + } + + .steamLinkButton { + @include IncreasedClickArea(5px); + @include S(margin, 0); + @include S(width, 180px); + @include S(height, 40px); + & { + /* @load-async */ + background: #171a23 uiResource("get_on_steam.png") center center / contain no-repeat; + } + + @include S(border-radius, $globalBorderRadius); + } + } + + .point { + display: grid; + grid-template-columns: #{D(55px)} auto; + grid-template-rows: 1fr 1fr; + + > strong { + grid-column: 2 / 3; + grid-row: 1 / 2; + @include PlainText; + text-transform: uppercase; + font-weight: bold; + } + + > p { + grid-column: 2 / 3; + grid-row: 2 / 3; + @include SuperSmallText; + opacity: 0.8; + } + + background: transparent #{D(10px)} center / #{D(30px)} no-repeat; + + &.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; + } + } + + &.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; + } + } + } +} diff --git a/src/css/ingame_hud/watermark.scss b/src/css/ingame_hud/watermark.scss index 78a56462..76ec224c 100644 --- a/src/css/ingame_hud/watermark.scss +++ b/src/css/ingame_hud/watermark.scss @@ -1,22 +1,85 @@ #ingame_HUD_Watermark { position: absolute; - & { - /* @load-async */ - background: uiResource("get_on_steam.png") center center / contain no-repeat; + + @include S(border-radius, $globalBorderRadius); + @include S(top, 70px); + pointer-events: all; + cursor: pointer; + left: 50%; + text-align: center; + + background: rgba(207, 65, 65, 0.8); + color: #fff; + transform: translateX(-50%); + @include PlainText; + @include S(padding, 10px); + + &:hover { + transform: translateX(-50%) scale(1.02) !important; + } + + > strong { + @include PlainText; + text-transform: uppercase; + } + > p { + @include SuperSmallText; + opacity: 0.7; } - @include S(width, 110px); - @include S(height, 40px); - @include S(top, 10px); + opacity: 0; + + &.visible { + @include InlineAnimation(0.5s ease-in-out) { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + opacity: 1; + } + + &:not(.visible) { + @include InlineAnimation(0.5s ease-in-out) { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } + } + } +} + +#ingame_HUD_WatermarkClicker { + @include S(top, 55px); + position: absolute; + left: 50%; + transform: translateX(-50%) !important; + @include SuperSmallText; + color: $colorBlueBright; + text-transform: uppercase; pointer-events: all; cursor: pointer; - @include S(left, 160px); + display: flex; + align-items: center; - transition: all 0.12s ease-in; - transition-property: opacity, transform; - transform: skewX(-0.5deg); &:hover { - transform: skewX(-1deg) scale(1.02); opacity: 0.9; } + + &::after { + @include S(margin-left, 4px); + content: ""; + @include S(width, 10px); + @include S(height, 10px); + display: inline-flex; + background: center center / contain no-repeat; + & { + /* @load-async */ + background-image: uiResource("res/ui/icons/demo_steam_link_indicator.png"); + } + } } diff --git a/src/css/main.scss b/src/css/main.scss index a045beef..9e26fa95 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -52,6 +52,7 @@ @import "ingame_hud/color_blind_helper"; @import "ingame_hud/shape_viewer"; @import "ingame_hud/sandbox_controller"; +@import "ingame_hud/standalone_advantages"; // prettier-ignore $elements: @@ -77,6 +78,7 @@ ingame_HUD_buildings_toolbar, ingame_HUD_wires_toolbar, ingame_HUD_BlueprintPlacer, ingame_HUD_Waypoints_Hint, +ingame_HUD_WatermarkClicker, ingame_HUD_Watermark, ingame_HUD_ColorBlindBelowTileHelper, ingame_HUD_SandboxController, @@ -88,6 +90,7 @@ ingame_HUD_BetaOverlay, ingame_HUD_Shop, ingame_HUD_Statistics, ingame_HUD_ShapeViewer, +ingame_HUD_StandaloneAdvantages, ingame_HUD_UnlockNotification, ingame_HUD_SettingsMenu, ingame_HUD_ModalDialogs; diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 50de7d76..e01ab868 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -1,4 +1,4 @@ -import { globalConfig } from "../core/config"; +import { globalConfig, IS_DEMO } from "../core/config"; import { RandomNumberGenerator } from "../core/rng"; import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils"; import { BasicSerializableObject, types } from "../savegame/serialization"; @@ -29,6 +29,10 @@ export class HubGoals extends BasicSerializableObject { return errorCode; } + if (IS_DEMO) { + this.level = Math.min(this.level, tutorialGoals.length); + } + // Compute gained rewards for (let i = 0; i < this.level - 1; ++i) { if (i < tutorialGoals.length) { @@ -102,13 +106,23 @@ export class HubGoals extends BasicSerializableObject { if (ev.key === "b") { // root is not guaranteed to exist within ~0.5s after loading in if (this.root && this.root.app && this.root.app.gameAnalytics) { - this.onGoalCompleted(); + if (!this.isEndOfDemoReached()) { + this.onGoalCompleted(); + } } } }); } } + /** + * Returns whether the end of the demo is reached + * @returns {boolean} + */ + isEndOfDemoReached() { + return IS_DEMO && this.level >= tutorialGoals.length; + } + /** * Returns how much of the current shape is stored * @param {ShapeDefinition} definition @@ -190,7 +204,9 @@ export class HubGoals extends BasicSerializableObject { this.getCurrentGoalDelivered() >= this.currentGoal.required || (G_IS_DEV && globalConfig.debug.rewardsInstant) ) { - this.onGoalCompleted(); + if (!this.isEndOfDemoReached()) { + this.onGoalCompleted(); + } } } @@ -254,6 +270,11 @@ export class HubGoals extends BasicSerializableObject { return false; } + if (IS_DEMO && currentLevel >= 4) { + // DEMO + return false; + } + if (G_IS_DEV && globalConfig.debug.upgradesNoCost) { return true; } diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 898c9ff7..189654c1 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -46,6 +46,7 @@ import { HUDLayerPreview } from "./parts/layer_preview"; import { HUDMinerHighlight } from "./parts/miner_highlight"; import { HUDBetaOverlay } from "./parts/beta_overlay"; import { HUDPerformanceWarning } from "./parts/performance_warning"; +import { HUDStandaloneAdvantages } from "./parts/standalone_advantages"; export class GameHUD { /** @@ -116,6 +117,7 @@ export class GameHUD { if (IS_DEMO) { this.parts.watermark = new HUDWatermark(this.root); + this.parts.standaloneAdvantages = new HUDStandaloneAdvantages(this.root); } if (G_IS_DEV && globalConfig.debug.renderChanges) { @@ -139,9 +141,9 @@ export class GameHUD { this.parts.sandboxController = new HUDSandboxController(this.root); } - // if (!G_IS_RELEASE) { - this.parts.betaOverlay = new HUDBetaOverlay(this.root); - // } + if (!G_IS_RELEASE && !G_IS_DEV) { + this.parts.betaOverlay = new HUDBetaOverlay(this.root); + } const frag = document.createDocumentFragment(); for (const key in this.parts) { diff --git a/src/js/game/hud/parts/modal_dialogs.js b/src/js/game/hud/parts/modal_dialogs.js index 06993616..263b23dd 100644 --- a/src/js/game/hud/parts/modal_dialogs.js +++ b/src/js/game/hud/parts/modal_dialogs.js @@ -122,7 +122,7 @@ export class HUDModalDialogs extends BaseHUDPart { dialog.buttonSignals.getStandalone.add(() => { this.app.analytics.trackUiClick("demo_dialog_click"); - window.open(THIRDPARTY_URLS.standaloneStorePage); + window.open(THIRDPARTY_URLS.standaloneStorePage + "?ref=ddc"); }); return dialog.buttonSignals; diff --git a/src/js/game/hud/parts/settings_menu.js b/src/js/game/hud/parts/settings_menu.js index 31afe348..b4850e0f 100644 --- a/src/js/game/hud/parts/settings_menu.js +++ b/src/js/game/hud/parts/settings_menu.js @@ -88,13 +88,8 @@ export class HUDSettingsMenu extends BaseHUDPart { this.close(); } - cleanup() { - document.body.classList.remove("ingameDialogOpen"); - } - show() { this.visible = true; - document.body.classList.add("ingameDialogOpen"); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60); @@ -120,7 +115,6 @@ export class HUDSettingsMenu extends BaseHUDPart { close() { this.visible = false; - document.body.classList.remove("ingameDialogOpen"); this.root.app.inputMgr.makeSureDetached(this.inputReciever); this.update(); } diff --git a/src/js/game/hud/parts/shape_viewer.js b/src/js/game/hud/parts/shape_viewer.js index 18f55c74..a7f5d206 100644 --- a/src/js/game/hud/parts/shape_viewer.js +++ b/src/js/game/hud/parts/shape_viewer.js @@ -67,7 +67,6 @@ export class HUDShapeViewer extends BaseHUDPart { */ close() { this.visible = false; - document.body.classList.remove("ingameDialogOpen"); this.root.app.inputMgr.makeSureDetached(this.inputReciever); this.update(); } @@ -78,7 +77,6 @@ export class HUDShapeViewer extends BaseHUDPart { */ renderForShape(definition) { this.visible = true; - document.body.classList.add("ingameDialogOpen"); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); removeAllChildren(this.renderArea); @@ -124,13 +122,6 @@ export class HUDShapeViewer extends BaseHUDPart { } } - /** - * Cleans up everything - */ - cleanup() { - document.body.classList.remove("ingameDialogOpen"); - } - update() { this.domAttach.update(this.visible); } diff --git a/src/js/game/hud/parts/shop.js b/src/js/game/hud/parts/shop.js index 6c1bdc3f..4a25d16e 100644 --- a/src/js/game/hud/parts/shop.js +++ b/src/js/game/hud/parts/shop.js @@ -205,8 +205,6 @@ export class HUDShop extends BaseHUDPart { } cleanup() { - document.body.classList.remove("ingameDialogOpen"); - // Cleanup detectors for (const upgradeId in this.upgradeToElements) { const handle = this.upgradeToElements[upgradeId]; @@ -222,15 +220,12 @@ export class HUDShop extends BaseHUDPart { show() { this.visible = true; - document.body.classList.add("ingameDialogOpen"); - // this.background.classList.add("visible"); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); this.rerenderFull(); } close() { this.visible = false; - document.body.classList.remove("ingameDialogOpen"); this.root.app.inputMgr.makeSureDetached(this.inputReciever); this.update(); } diff --git a/src/js/game/hud/parts/standalone_advantages.js b/src/js/game/hud/parts/standalone_advantages.js new file mode 100644 index 00000000..8eb3d478 --- /dev/null +++ b/src/js/game/hud/parts/standalone_advantages.js @@ -0,0 +1,84 @@ +import { THIRDPARTY_URLS } from "../../../core/config"; +import { InputReceiver } from "../../../core/input_receiver"; +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; + +const showIntervalSeconds = 30 * 60; + +export class HUDStandaloneAdvantages extends BaseHUDPart { + createElements(parent) { + this.background = makeDiv(parent, "ingame_HUD_StandaloneAdvantages", ["ingameDialog"]); + + // DIALOG Inner / Wrapper + this.dialogInner = makeDiv(this.background, null, ["dialogInner"]); + this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.standaloneAdvantages.title); + this.contentDiv = makeDiv( + this.dialogInner, + null, + ["content"], + ` +
+ ${Object.entries(T.ingame.standaloneAdvantages.points) + .map( + ([key, trans]) => ` +
+ ${trans.title} +

${trans.desc}

+
` + ) + .join("")} + +
+ +
+ + +
+ ` + ); + + this.trackClicks(this.contentDiv.querySelector("button.steamLinkButton"), () => { + this.root.app.analytics.trackUiClick("standalone_advantage_visit_steam"); + this.root.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage + "?ref=savs"); + this.close(); + }); + this.trackClicks(this.contentDiv.querySelector("button.otherCloseButton"), () => { + this.root.app.analytics.trackUiClick("standalone_advantage_no_thanks"); + this.close(); + }); + } + + initialize() { + this.domAttach = new DynamicDomAttach(this.root, this.background, { + attachClass: "visible", + }); + + this.inputReciever = new InputReceiver("standalone-advantages"); + this.close(); + + this.lastShown = this.root.gameIsFresh ? this.root.time.now() : 0; + } + + show() { + this.lastShown = this.root.time.now(); + this.visible = true; + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + } + + close() { + this.visible = false; + this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.update(); + } + + update() { + if (!this.visible && this.root.time.now() - this.lastShown > showIntervalSeconds) { + this.show(); + } + + this.domAttach.update(this.visible); + } +} diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js index 910c49d0..a28ed288 100644 --- a/src/js/game/hud/parts/statistics.js +++ b/src/js/game/hud/parts/statistics.js @@ -151,17 +151,12 @@ export class HUDStatistics extends BaseHUDPart { } } - cleanup() { - document.body.classList.remove("ingameDialogOpen"); - } - isBlockingOverlay() { return this.visible; } show() { this.visible = true; - document.body.classList.add("ingameDialogOpen"); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); this.rerenderFull(); this.update(); @@ -169,7 +164,6 @@ export class HUDStatistics extends BaseHUDPart { close() { this.visible = false; - document.body.classList.remove("ingameDialogOpen"); this.root.app.inputMgr.makeSureDetached(this.inputReciever); this.update(); } diff --git a/src/js/game/hud/parts/tutorial_hints.js b/src/js/game/hud/parts/tutorial_hints.js index 428923d0..c9499f85 100644 --- a/src/js/game/hud/parts/tutorial_hints.js +++ b/src/js/game/hud/parts/tutorial_hints.js @@ -1,109 +1,106 @@ -import { InputReceiver } from "../../../core/input_receiver"; -import { TrackedState } from "../../../core/tracked_state"; -import { makeDiv } from "../../../core/utils"; -import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; -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]; - -export class HUDPartTutorialHints extends BaseHUDPart { - createElements(parent) { - this.element = makeDiv( - parent, - "ingame_HUD_TutorialHints", - [], - ` -
- ${T.ingame.tutorialHints.title} - -
- - - ` - ); - - this.videoElement = this.element.querySelector("video"); - } - - shouldPauseGame() { - return this.enlarged; - } - - initialize() { - this.trackClicks(this.element.querySelector(".toggleHint"), this.toggleHintEnlarged); - - this.videoAttach = new DynamicDomAttach(this.root, this.videoElement, { - timeToKeepSeconds: 0.3, - }); - - this.videoAttach.update(false); - this.enlarged = false; - - this.inputReciever = new InputReceiver("tutorial_hints"); - this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); - this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); - - this.domAttach = new DynamicDomAttach(this.root, this.element); - - this.currentShownLevel = new TrackedState(this.updateVideoUrl, this); - } - - updateVideoUrl(level) { - if (tutorialVideos.indexOf(level) < 0) { - this.videoElement.querySelector("source").setAttribute("src", ""); - this.videoElement.pause(); - } else { - this.videoElement - .querySelector("source") - .setAttribute("src", "https://static.shapez.io/tutorial_videos/level_" + level + ".webm"); - this.videoElement.currentTime = 0; - this.videoElement.load(); - } - } - - close() { - this.enlarged = false; - document.body.classList.remove("ingameDialogOpen"); - this.element.classList.remove("enlarged", "noBlur"); - this.root.app.inputMgr.makeSureDetached(this.inputReciever); - this.update(); - } - - show() { - this.root.app.analytics.trackUiClick("tutorial_hint_show"); - this.root.app.analytics.trackUiClick("tutorial_hint_show_lvl_" + this.root.hubGoals.level); - - document.body.classList.add("ingameDialogOpen"); - this.element.classList.add("enlarged", "noBlur"); - this.enlarged = true; - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); - this.update(); - - this.videoElement.currentTime = 0; - this.videoElement.play(); - } - - update() { - this.videoAttach.update(this.enlarged); - - this.currentShownLevel.set(this.root.hubGoals.level); - - const tutorialVisible = tutorialVideos.indexOf(this.root.hubGoals.level) >= 0; - this.domAttach.update(tutorialVisible); - } - - toggleHintEnlarged() { - if (this.enlarged) { - this.close(); - } else { - this.show(); - } - } -} +import { InputReceiver } from "../../../core/input_receiver"; +import { TrackedState } from "../../../core/tracked_state"; +import { makeDiv } from "../../../core/utils"; +import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; +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]; + +export class HUDPartTutorialHints extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv( + parent, + "ingame_HUD_TutorialHints", + [], + ` +
+ ${T.ingame.tutorialHints.title} + +
+ + + ` + ); + + this.videoElement = this.element.querySelector("video"); + } + + shouldPauseGame() { + return this.enlarged; + } + + initialize() { + this.trackClicks(this.element.querySelector(".toggleHint"), this.toggleHintEnlarged); + + this.videoAttach = new DynamicDomAttach(this.root, this.videoElement, { + timeToKeepSeconds: 0.3, + }); + + this.videoAttach.update(false); + this.enlarged = false; + + this.inputReciever = new InputReceiver("tutorial_hints"); + this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); + this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); + + this.domAttach = new DynamicDomAttach(this.root, this.element); + + this.currentShownLevel = new TrackedState(this.updateVideoUrl, this); + } + + updateVideoUrl(level) { + if (tutorialVideos.indexOf(level) < 0) { + this.videoElement.querySelector("source").setAttribute("src", ""); + this.videoElement.pause(); + } else { + this.videoElement + .querySelector("source") + .setAttribute("src", "https://static.shapez.io/tutorial_videos/level_" + level + ".webm"); + this.videoElement.currentTime = 0; + this.videoElement.load(); + } + } + + close() { + this.enlarged = false; + this.element.classList.remove("enlarged", "noBlur"); + this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.update(); + } + + show() { + this.root.app.analytics.trackUiClick("tutorial_hint_show"); + this.root.app.analytics.trackUiClick("tutorial_hint_show_lvl_" + this.root.hubGoals.level); + this.element.classList.add("enlarged", "noBlur"); + this.enlarged = true; + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.update(); + + this.videoElement.currentTime = 0; + this.videoElement.play(); + } + + update() { + this.videoAttach.update(this.enlarged); + + this.currentShownLevel.set(this.root.hubGoals.level); + + const tutorialVisible = tutorialVideos.indexOf(this.root.hubGoals.level) >= 0; + this.domAttach.update(tutorialVisible); + } + + toggleHintEnlarged() { + if (this.enlarged) { + this.close(); + } else { + this.show(); + } + } +} diff --git a/src/js/game/hud/parts/watermark.js b/src/js/game/hud/parts/watermark.js index d10bc07c..10e48e6d 100644 --- a/src/js/game/hud/parts/watermark.js +++ b/src/js/game/hud/parts/watermark.js @@ -1,44 +1,67 @@ -import { BaseHUDPart } from "../base_hud_part"; -import { DrawParameters } from "../../../core/draw_parameters"; -import { makeDiv } from "../../../core/utils"; -import { THIRDPARTY_URLS } from "../../../core/config"; -import { T } from "../../../translations"; - -export class HUDWatermark extends BaseHUDPart { - createElements(parent) { - this.element = makeDiv(parent, "ingame_HUD_Watermark"); - } - - initialize() { - this.trackClicks(this.element, this.onWatermarkClick); - } - - onWatermarkClick() { - this.root.app.analytics.trackUiClick("watermark_click_2"); - this.root.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage); - } - - /** - * - * @param {DrawParameters} parameters - */ - drawOverlays(parameters) { - const w = this.root.gameWidth; - const x = 280 * this.root.app.getEffectiveUiScale(); - - parameters.context.fillStyle = "#f77"; - parameters.context.font = "bold " + this.root.app.getEffectiveUiScale() * 17 + "px GameFont"; - // parameters.context.textAlign = "center"; - parameters.context.fillText( - T.demoBanners.title.toUpperCase(), - x, - this.root.app.getEffectiveUiScale() * 27 - ); - - parameters.context.font = "bold " + this.root.app.getEffectiveUiScale() * 12 + "px GameFont"; - // parameters.context.textAlign = "center"; - parameters.context.fillText(T.demoBanners.intro, x, this.root.app.getEffectiveUiScale() * 45); - - // parameters.context.textAlign = "left"; - } -} +import { THIRDPARTY_URLS } from "../../../core/config"; +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; + +export class HUDWatermark extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv( + parent, + "ingame_HUD_Watermark", + [], + ` + ${T.ingame.watermark.title} +

${T.ingame.watermark.desc}

+ ` + ); + + this.linkElement = makeDiv( + parent, + "ingame_HUD_WatermarkClicker", + [], + T.ingame.watermark.get_on_steam + ); + this.trackClicks(this.linkElement, () => { + this.root.app.analytics.trackUiClick("watermark_click_2_direct"); + this.root.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage + "?ref=wtmd"); + }); + } + + initialize() { + this.trackClicks(this.element, this.onWatermarkClick); + + this.domAttach = new DynamicDomAttach(this.root, this.element, { + attachClass: "visible", + timeToKeepSeconds: 0.5, + }); + } + + update() { + this.domAttach.update(this.root.time.realtimeNow() % (G_IS_DEV ? 20 : 180) < 5); + } + + onWatermarkClick() { + this.root.app.analytics.trackUiClick("watermark_click_2_new"); + this.root.hud.parts.standaloneAdvantages.show(); + } + + /** + * + * @param {import("../../../core/draw_utils").DrawParameters} parameters + */ + drawOverlays(parameters) { + const w = this.root.gameWidth; + + parameters.context.fillStyle = "rgba(230, 230, 230, 0.9)"; + parameters.context.font = "bold " + this.root.app.getEffectiveUiScale() * 40 + "px GameFont"; + parameters.context.textAlign = "center"; + parameters.context.fillText( + T.demoBanners.title.toUpperCase(), + w / 2, + this.root.app.getEffectiveUiScale() * 50 + ); + + parameters.context.textAlign = "left"; + } +} diff --git a/src/js/game/systems/hub.js b/src/js/game/systems/hub.js index a2ae1aff..2f1f7c05 100644 --- a/src/js/game/systems/hub.js +++ b/src/js/game/systems/hub.js @@ -1,4 +1,4 @@ -import { globalConfig } from "../../core/config"; +import { globalConfig, IS_DEMO } from "../../core/config"; import { smoothenDpi } from "../../core/dpi_manager"; import { DrawParameters } from "../../core/draw_parameters"; import { drawSpriteClipped } from "../../core/draw_utils"; @@ -65,6 +65,17 @@ export class HubSystem extends GameSystemWithFilter { this.hubSprite.draw(context, 0, 0, w, h); + if (this.root.hubGoals.isEndOfDemoReached()) { + // End of demo + context.font = "bold 12px GameFont"; + context.fillStyle = "#fd0752"; + context.textAlign = "center"; + context.fillText(T.buildings.hub.endOfDemo.toUpperCase(), w / 2, h / 2 + 6); + context.textAlign = "left"; + + return; + } + const definition = this.root.hubGoals.currentGoal.definition; definition.drawCentered(45, 58, parameters, 36); diff --git a/src/js/game/tutorial_goals.js b/src/js/game/tutorial_goals.js index 3e0ce840..f7b56ffe 100644 --- a/src/js/game/tutorial_goals.js +++ b/src/js/game/tutorial_goals.js @@ -1,3 +1,4 @@ +import { IS_DEMO } from "../core/config"; import { ShapeDefinition } from "./shape_definition"; import { finalGameShape } from "./upgrades"; @@ -31,6 +32,8 @@ export const enumHubGoalRewards = { reward_virtual_processing: "reward_virtual_processing", reward_filter: "reward_filter", + reward_demo_end: "reward_demo_end", + reward_blueprints: "reward_blueprints", reward_freeplay: "reward_freeplay", @@ -140,107 +143,118 @@ export const tutorialGoals = [ reward: enumHubGoalRewards.reward_underground_belt_tier_2, }, - // 14 - // Belt reader - { - shape: "--Cg----:--Cr----", // unused - required: 16, // Per second! - reward: enumHubGoalRewards.reward_belt_reader, - throughputOnly: true, - }, + // DEMO STOPS HERE + ...(IS_DEMO + ? [ + { + shape: "RpRpRpRp:CwCwCwCw", + required: 0, + reward: enumHubGoalRewards.reward_demo_end, + }, + ] + : [ + // 14 + // Belt reader + { + shape: "--Cg----:--Cr----", // unused + required: 16, // Per second! + reward: enumHubGoalRewards.reward_belt_reader, + throughputOnly: true, + }, - // 15 - // Storage - { - shape: "SrSrSrSr:CyCyCyCy", // unused - required: 10000, - reward: enumHubGoalRewards.reward_storage, - }, + // 15 + // Storage + { + shape: "SrSrSrSr:CyCyCyCy", // unused + required: 10000, + reward: enumHubGoalRewards.reward_storage, + }, - // 16 - // Quad Cutter - { - shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants) - required: 6000, - reward: enumHubGoalRewards.reward_cutter_quad, - }, + // 16 + // Quad Cutter + { + shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants) + required: 6000, + reward: enumHubGoalRewards.reward_cutter_quad, + }, - // 17 - // Double painter - { - shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants) - required: 20000, - reward: enumHubGoalRewards.reward_painter_double, - }, + // 17 + // Double painter + { + shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants) + required: 20000, + reward: enumHubGoalRewards.reward_painter_double, + }, - // 18 - // Rotater (180deg) - { - shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused - required: 20000, - reward: enumHubGoalRewards.reward_rotater_180, - }, + // 18 + // Rotater (180deg) + { + shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused + required: 20000, + reward: enumHubGoalRewards.reward_rotater_180, + }, - // 19 - // Compact splitter - { - shape: "CpRpCp--:SwSwSwSw", - required: 25000, - reward: enumHubGoalRewards.reward_splitter, - }, + // 19 + // Compact splitter + { + shape: "CpRpCp--:SwSwSwSw", + required: 25000, + reward: enumHubGoalRewards.reward_splitter, + }, - // 20 - // WIRES - { - shape: finalGameShape, - required: 25000, - reward: enumHubGoalRewards.reward_wires_painter_and_levers, - }, + // 20 + // WIRES + { + shape: finalGameShape, + required: 25000, + reward: enumHubGoalRewards.reward_wires_painter_and_levers, + }, - // 21 - // Filter - { - shape: "CrCwCrCw:CwCrCwCr:CrCwCrCw:CwCrCwCr", - required: 25000, - reward: enumHubGoalRewards.reward_filter, - }, + // 21 + // Filter + { + shape: "CrCwCrCw:CwCrCwCr:CrCwCrCw:CwCrCwCr", + required: 25000, + reward: enumHubGoalRewards.reward_filter, + }, - // 22 - // Constant signal - { - shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy", - required: 25000, - reward: enumHubGoalRewards.reward_constant_signal, - }, + // 22 + // Constant signal + { + shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy", + required: 25000, + reward: enumHubGoalRewards.reward_constant_signal, + }, - // 23 - // Display - { - shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy", - required: 25000, - reward: enumHubGoalRewards.reward_display, - }, + // 23 + // Display + { + shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy", + required: 25000, + reward: enumHubGoalRewards.reward_display, + }, - // 24 Logic gates - { - shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy", - required: 25000, - reward: enumHubGoalRewards.reward_logic_gates, - }, + // 24 Logic gates + { + shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy", + required: 25000, + reward: enumHubGoalRewards.reward_logic_gates, + }, - // 25 Virtual Processing - { - shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg", - required: 25000, - reward: enumHubGoalRewards.reward_virtual_processing, - }, + // 25 Virtual Processing + { + shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg", + required: 25000, + reward: enumHubGoalRewards.reward_virtual_processing, + }, - // 26 Freeplay - { - shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw", - required: 50000, - reward: enumHubGoalRewards.reward_freeplay, - }, + // 26 Freeplay + { + shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw", + required: 50000, + reward: enumHubGoalRewards.reward_freeplay, + }, + ]), ]; if (G_IS_DEV) { diff --git a/src/js/game/tutorial_goals_mappings.js b/src/js/game/tutorial_goals_mappings.js index c195ddc0..5cd966b9 100644 --- a/src/js/game/tutorial_goals_mappings.js +++ b/src/js/game/tutorial_goals_mappings.js @@ -64,6 +64,7 @@ export const enumHubGoalRewardsToContentUnlocked = { [enumHubGoalRewards.reward_blueprints]: null, [enumHubGoalRewards.no_reward]: null, [enumHubGoalRewards.no_reward_freeplay]: null, + [enumHubGoalRewards.reward_demo_end]: null, }; if (G_IS_DEV) { diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 02c1e690..b8191306 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -312,7 +312,7 @@ export class MainMenuState extends GameState { onSteamLinkClicked() { this.app.analytics.trackUiClick("main_menu_steam_link_2"); - this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage); + this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage + "?ref=mmsl2"); return false; } @@ -537,7 +537,7 @@ export class MainMenuState extends GameState { ); getStandalone.add(() => { this.app.analytics.trackUiClick("visit_steampage_from_slot_limit"); - this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage); + this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage + "?reF=ssll"); }); } diff --git a/src/js/states/mobile_warning.js b/src/js/states/mobile_warning.js index a48a69ed..98eb1cd8 100644 --- a/src/js/states/mobile_warning.js +++ b/src/js/states/mobile_warning.js @@ -21,7 +21,7 @@ export class MobileWarningState extends GameState { Get the shapez.io standalone! `; } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 3759af54..d69c4c45 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -449,6 +449,49 @@ ingame: n_miners: Miners limited_items: Limited to + # Pops up in the demo every few minutes + watermark: + title: Demo version + desc: Click here to see the Steam version advantages! + get_on_steam: Get on steam + + standaloneAdvantages: + title: Get the full version! + no_thanks: No, thanks! + + points: + levels: + title: 12 New Levels + desc: For a total of 26 levels! + + buildings: + title: 18 New Buildings + desc: Fully automate your factory! + + savegames: + title: ∞ Savegames + desc: As many as your heart desires! + + upgrades: + title: 20 Upgrade Tiers + desc: This demo version has only 5! + + markers: + title: ∞ Markers + desc: Never get lost in your factory! + + wires: + title: Wires + desc: An entirely new dimension! + + darkmode: + title: Dark Mode + desc: Stop hurting your eyes! + + support: + title: Support me + desc: I develop it in my spare time! + # All shop upgrades shopUpgrades: belt: @@ -470,6 +513,7 @@ buildings: deliver: Deliver toUnlock: to unlock levelShortcut: LVL + endOfDemo: End of Demo belt: default: @@ -817,6 +861,11 @@ storyRewards: Since the hub will require a throughput from now on, I highly recommend to build a machine which automatically delivers the requested shape!

The HUB outputs the requested shape on the wires layer, so all you have to do is to analyze it and automatically configure your factory based on that. + reward_demo_end: + title: End of Demo + desc: >- + You have reached the end of the demo version! + settings: title: Settings categories: