diff --git a/artwork/gamedistribution/1280x550.jpg b/artwork/gamedistribution/1280x550.jpg new file mode 100644 index 00000000..9f5c0f44 Binary files /dev/null and b/artwork/gamedistribution/1280x550.jpg differ diff --git a/artwork/gamedistribution/1280x550.png b/artwork/gamedistribution/1280x550.png new file mode 100644 index 00000000..5086af18 Binary files /dev/null and b/artwork/gamedistribution/1280x550.png differ diff --git a/artwork/gamedistribution/1280x720.jpg b/artwork/gamedistribution/1280x720.jpg new file mode 100644 index 00000000..ea2bb545 Binary files /dev/null and b/artwork/gamedistribution/1280x720.jpg differ diff --git a/artwork/gamedistribution/1280x720.png b/artwork/gamedistribution/1280x720.png new file mode 100644 index 00000000..191fe91a Binary files /dev/null and b/artwork/gamedistribution/1280x720.png differ diff --git a/artwork/gamedistribution/1280x720.psd b/artwork/gamedistribution/1280x720.psd new file mode 100644 index 00000000..e1c7a047 Binary files /dev/null and b/artwork/gamedistribution/1280x720.psd differ diff --git a/artwork/gamedistribution/512x340.jpg b/artwork/gamedistribution/512x340.jpg new file mode 100644 index 00000000..d34f34af Binary files /dev/null and b/artwork/gamedistribution/512x340.jpg differ diff --git a/artwork/gamedistribution/512x340.png b/artwork/gamedistribution/512x340.png new file mode 100644 index 00000000..33eb371c Binary files /dev/null and b/artwork/gamedistribution/512x340.png differ diff --git a/artwork/gamedistribution/512x384.jpg b/artwork/gamedistribution/512x384.jpg new file mode 100644 index 00000000..77d3b3ad Binary files /dev/null and b/artwork/gamedistribution/512x384.jpg differ diff --git a/artwork/gamedistribution/512x384.png b/artwork/gamedistribution/512x384.png new file mode 100644 index 00000000..65fd95c4 Binary files /dev/null and b/artwork/gamedistribution/512x384.png differ diff --git a/artwork/gamedistribution/512x384.psd b/artwork/gamedistribution/512x384.psd new file mode 100644 index 00000000..1bc2e2bc Binary files /dev/null and b/artwork/gamedistribution/512x384.psd differ diff --git a/artwork/gamedistribution/512x512.jpg b/artwork/gamedistribution/512x512.jpg new file mode 100644 index 00000000..db293db4 Binary files /dev/null and b/artwork/gamedistribution/512x512.jpg differ diff --git a/artwork/gamedistribution/512x512.png b/artwork/gamedistribution/512x512.png new file mode 100644 index 00000000..81c8b737 Binary files /dev/null and b/artwork/gamedistribution/512x512.png differ diff --git a/artwork/gamedistribution/iframe/iframe.zip b/artwork/gamedistribution/iframe/iframe.zip new file mode 100644 index 00000000..133206b0 Binary files /dev/null and b/artwork/gamedistribution/iframe/iframe.zip differ diff --git a/artwork/gamedistribution/iframe/index.html b/artwork/gamedistribution/iframe/index.html new file mode 100644 index 00000000..801b3c20 --- /dev/null +++ b/artwork/gamedistribution/iframe/index.html @@ -0,0 +1,85 @@ + + + + shapez.io + + + + + + + + + diff --git a/artwork/gamedistribution/iframe/manifest.json b/artwork/gamedistribution/iframe/manifest.json new file mode 100644 index 00000000..539af604 --- /dev/null +++ b/artwork/gamedistribution/iframe/manifest.json @@ -0,0 +1,8 @@ +{ + "name": "shapez.io", + "short_name": "shapez.io", + "start_url": "index.html", + "display": "standalone", + "background_color": "#222428", + "description": "shapez.io" +} diff --git a/src/css/common.scss b/src/css/common.scss index 34d6f5b5..242bb9c7 100644 --- a/src/css/common.scss +++ b/src/css/common.scss @@ -72,6 +72,33 @@ body { overflow: hidden; @include Text; + &.externalAdOpen { + &::before { + text-transform: uppercase; + @include SuperSmallText; + content: "Loading Advertisement..."; + color: #333; + position: fixed; + top: 0; + pointer-events: all; + left: 0; + right: 0; + bottom: 0; + background: rgba(50, 60, 70, 0.8); + z-index: 9999; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + + @include InlineAnimation(1s ease-in-out infinite) { + 50% { + transform: scale(1.05); + } + } + } + } + // For recording the bg video // filter: blur(5px); diff --git a/src/css/main.scss b/src/css/main.scss index 2d3f6857..7a3c6df3 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -96,6 +96,7 @@ body.uiHidden { } body.modalDialogActive, +body.externalAdOpen, body.ingameDialogOpen { > *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs) { filter: blur(5px) !important; diff --git a/src/js/core/click_detector.js b/src/js/core/click_detector.js index fe0b5690..a4aece50 100644 --- a/src/js/core/click_detector.js +++ b/src/js/core/click_detector.js @@ -38,6 +38,7 @@ export let clickDetectorGlobals = { * targetOnly?: boolean, * maxDistance?: number, * clickSound?: string, + * preventClick?: boolean, * }} ClickDetectorConstructorArgs */ @@ -55,6 +56,7 @@ export class ClickDetector { * @param {boolean=} param1.targetOnly Whether to also accept clicks on child elements (e.target !== element) * @param {number=} param1.maxDistance The maximum distance in pixels to accept clicks * @param {string=} param1.clickSound Sound key to play on touchdown + * @param {boolean=} param1.preventClick Whether to prevent click events */ constructor( element, @@ -66,6 +68,7 @@ export class ClickDetector { targetOnly = false, maxDistance = MAX_MOVE_DISTANCE_PX, clickSound = SOUNDS.uiClick, + preventClick = false, } ) { assert(element, "No element given!"); @@ -78,6 +81,7 @@ export class ClickDetector { this.targetOnly = targetOnly; this.clickSound = clickSound; this.maxDistance = maxDistance; + this.preventClick = preventClick; // Signals this.click = new Signal(); @@ -128,6 +132,10 @@ export class ClickDetector { this.element.removeEventListener("mousemove", this.handlerTouchMove, options); } + if (this.preventClick) { + this.element.removeEventListener("click", this.handlerPreventClick, options); + } + this.click.removeAll(); this.touchstart.removeAll(); this.touchmove.removeAll(); @@ -142,6 +150,14 @@ export class ClickDetector { // INTERNAL METHODS + /** + * + * @param {Event} event + */ + internalPreventClick(event) { + event.preventDefault(); + } + /** * Internal method to get the options to pass to an event listener */ @@ -164,6 +180,11 @@ export class ClickDetector { this.handlerTouchMove = this.internalOnPointerMove.bind(this); this.handlerTouchCancel = this.internalOnTouchCancel.bind(this); + if (this.preventClick) { + this.handlerPreventClick = this.internalPreventClick.bind(this); + element.addEventListener("click", this.handlerPreventClick, options); + } + element.addEventListener("touchstart", this.handlerTouchStart, options); element.addEventListener("touchend", this.handlerTouchEnd, options); element.addEventListener("touchcancel", this.handlerTouchCancel, options); diff --git a/src/js/core/config.js b/src/js/core/config.js index 74436202..67277dba 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -101,6 +101,7 @@ export const globalConfig = { // framePausesBetweenTicks: 40, // testTranslations: true, // enableEntityInspector: true, + testAds: true, /* dev:end */ }, diff --git a/src/js/game/hud/parts/unlock_notification.js b/src/js/game/hud/parts/unlock_notification.js index 13c0cd76..44fa7deb 100644 --- a/src/js/game/hud/parts/unlock_notification.js +++ b/src/js/game/hud/parts/unlock_notification.js @@ -47,7 +47,7 @@ export class HUDUnlockNotification extends BaseHUDPart { this.btnClose.innerText = "Next level"; dialog.appendChild(this.btnClose); - this.trackClicks(this.btnClose, this.close); + this.trackClicks(this.btnClose, this.requestClose); } /** @@ -119,6 +119,12 @@ export class HUDUnlockNotification extends BaseHUDPart { this.root.soundProxy.playUi(SOUNDS.levelComplete); } + requestClose() { + this.root.app.adProvider.showVideoAd().then(() => { + this.close(); + }); + } + close() { this.visible = false; } diff --git a/src/js/platform/ad_providers/gamedistribution.js b/src/js/platform/ad_providers/gamedistribution.js new file mode 100644 index 00000000..431d6096 --- /dev/null +++ b/src/js/platform/ad_providers/gamedistribution.js @@ -0,0 +1,125 @@ +/* typehints:start */ +import { Application } from "../../application"; +/* typehints:end */ + +import { AdProviderInterface } from "../ad_provider"; +import { performanceNow } from "../../core/builtins"; +import { createLogger } from "../../core/logging"; + +const minimumTimeBetweenVideoAdsMs = G_IS_DEV ? 1 : 5 * 60 * 1000; + +const logger = createLogger("gamedistribution"); + +export class GamedistributionAdProvider extends AdProviderInterface { + /** + * + * @param {Application} app + */ + constructor(app) { + super(app); + + /** + * The resolve function to finish the current video ad. Null if none is currently running + * @type {Function} + */ + this.videoAdResolveFunction = null; + + /** + * The current timer which will timeout the resolve + */ + this.videoAdResolveTimer = null; + + /** + * When we showed the last video ad + */ + this.lastVideoAdShowTime = -1e20; + + console.error("X"); + } + + getHasAds() { + return true; + } + + getCanShowVideoAd() { + return ( + this.getHasAds() && + !this.videoAdResolveFunction && + performanceNow() - this.lastVideoAdShowTime > minimumTimeBetweenVideoAdsMs + ); + } + + initialize() { + // No point to initialize everything if ads are not supported + if (!this.getHasAds()) { + return Promise.resolve(); + } + + logger.log("🎬 Initializing gamedistribution ads"); + + try { + parent.postMessage("shapezio://gd.game_loaded", "*"); + } catch (ex) { + return Promise.reject("Frame communication not allowed"); + } + + window.addEventListener( + "message", + event => { + if (event.data === "shapezio://gd.ad_started") { + console.log("🎬 Got ad started callback"); + } else if (event.data === "shapezio://gd.ad_finished") { + console.log("🎬 Got ad finished callback"); + if (this.videoAdResolveFunction) { + this.videoAdResolveFunction(); + } + } + }, + false + ); + + return Promise.resolve(); + } + + showVideoAd() { + assert(this.getHasAds(), "Called showVideoAd but ads are not supported!"); + assert(!this.videoAdResolveFunction, "Video ad still running, can not show again!"); + this.lastVideoAdShowTime = performanceNow(); + + console.log("🎬 Gamedistribution: Start ad"); + try { + parent.postMessage("shapezio://gd.show_ad", "*"); + } catch (ex) { + logger.warn("🎬 Failed to send message for gd ad:", ex); + return Promise.resolve(); + } + + document.body.classList.add("externalAdOpen"); + + return new Promise(resolve => { + // So, wait for the remove call but also remove after N seconds + this.videoAdResolveFunction = () => { + this.videoAdResolveFunction = null; + clearTimeout(this.videoAdResolveTimer); + this.videoAdResolveTimer = null; + + // When the ad closed, also set the time + this.lastVideoAdShowTime = performanceNow(); + resolve(); + }; + + this.videoAdResolveTimer = setTimeout(() => { + logger.warn("Automatically closing ad after not receiving callback"); + if (this.videoAdResolveFunction) { + this.videoAdResolveFunction(); + } + }, 35000); + }) + .catch(err => { + logger.error(this, "Error while resolving video ad:", err); + }) + .then(() => { + document.body.classList.remove("externalAdOpen"); + }); + } +} diff --git a/src/js/platform/browser/embed_provider.js b/src/js/platform/browser/embed_provider.js deleted file mode 100644 index 8703ec27..00000000 --- a/src/js/platform/browser/embed_provider.js +++ /dev/null @@ -1,47 +0,0 @@ -import { AdProviderInterface } from "../ad_provider"; - -/** - * Stores information about where we are iframed - */ -export class EmbedProvider { - /** - * @returns {string} - */ - getId() { - abstract; - return ""; - } - - /** - * Whether this provider supports ads - * @returns {boolean} - */ - getSupportsAds() { - return false; - } - - /** - * Returns the ad provider - * @returns {typeof AdProviderInterface} - */ - getAdProvider() { - abstract; - return null; - } - - /** - * Whetherexternal links are supported - * @returns {boolean} - */ - getSupportsExternalLinks() { - return true; - } - - /** - * Returns whether this provider is iframed - * @returns {boolean} - */ - getIsIframed() { - return true; - } -} diff --git a/src/js/platform/browser/embed_providers/armorgames.js b/src/js/platform/browser/embed_providers/armorgames.js deleted file mode 100644 index 867e0a71..00000000 --- a/src/js/platform/browser/embed_providers/armorgames.js +++ /dev/null @@ -1,16 +0,0 @@ -import { AdinplayAdProvider } from "../../ad_providers/adinplay"; -import { ShapezioWebsiteEmbedProvider } from "./shapezio_website"; - -export class ArmorgamesEmbedProvider extends ShapezioWebsiteEmbedProvider { - getId() { - return "armorgames"; - } - - getAdProvider() { - return AdinplayAdProvider; - } - - getIsIframed() { - return true; - } -} diff --git a/src/js/platform/browser/embed_providers/crazygames.js b/src/js/platform/browser/embed_providers/crazygames.js deleted file mode 100644 index dc0b09dc..00000000 --- a/src/js/platform/browser/embed_providers/crazygames.js +++ /dev/null @@ -1,11 +0,0 @@ -import { ShapezioWebsiteEmbedProvider } from "./shapezio_website"; - -export class CrazygamesEmbedProvider extends ShapezioWebsiteEmbedProvider { - getId() { - return "crazygames"; - } - - getIsIframed() { - return true; - } -} diff --git a/src/js/platform/browser/embed_providers/gamedistribution.js b/src/js/platform/browser/embed_providers/gamedistribution.js deleted file mode 100644 index 49dda339..00000000 --- a/src/js/platform/browser/embed_providers/gamedistribution.js +++ /dev/null @@ -1,20 +0,0 @@ -import { AdinplayAdProvider } from "../../ad_providers/adinplay"; -import { ShapezioWebsiteEmbedProvider } from "./shapezio_website"; - -export class GamedistributionEmbedProvider extends ShapezioWebsiteEmbedProvider { - getId() { - return "gamedistribution"; - } - - getAdProvider() { - return AdinplayAdProvider; - } - - getSupportsExternalLinks() { - return false; - } - - getIsIframed() { - return true; - } -} diff --git a/src/js/platform/browser/embed_providers/iogames_space.js b/src/js/platform/browser/embed_providers/iogames_space.js deleted file mode 100644 index 5f7d317d..00000000 --- a/src/js/platform/browser/embed_providers/iogames_space.js +++ /dev/null @@ -1,15 +0,0 @@ -import { ShapezioWebsiteEmbedProvider } from "./shapezio_website"; - -export class IogamesSpaceEmbedProvider extends ShapezioWebsiteEmbedProvider { - getId() { - return "iogames.space"; - } - - getShowUpvoteHints() { - return true; - } - - getIsIframed() { - return true; - } -} diff --git a/src/js/platform/browser/embed_providers/kongregate.js b/src/js/platform/browser/embed_providers/kongregate.js deleted file mode 100644 index b0ed4d8b..00000000 --- a/src/js/platform/browser/embed_providers/kongregate.js +++ /dev/null @@ -1,24 +0,0 @@ -import { NoAdProvider } from "../../ad_providers/no_ad_provider"; -import { EmbedProvider } from "../embed_provider"; - -export class KongregateEmbedProvider extends EmbedProvider { - getId() { - return "kongregate"; - } - - getSupportsAds() { - return false; - } - - getAdProvider() { - return NoAdProvider; - } - - getSupportsExternalLinks() { - return true; - } - - getIsIframed() { - return true; - } -} diff --git a/src/js/platform/browser/embed_providers/miniclip.js b/src/js/platform/browser/embed_providers/miniclip.js deleted file mode 100644 index e0612978..00000000 --- a/src/js/platform/browser/embed_providers/miniclip.js +++ /dev/null @@ -1,11 +0,0 @@ -import { ShapezioWebsiteEmbedProvider } from "./shapezio_website"; - -export class MiniclipEmbedProvider extends ShapezioWebsiteEmbedProvider { - getId() { - return "miniclip"; - } - - getIsIframed() { - return true; - } -} diff --git a/src/js/platform/browser/embed_providers/shapezio_website.js b/src/js/platform/browser/embed_providers/shapezio_website.js deleted file mode 100644 index 2a5359ed..00000000 --- a/src/js/platform/browser/embed_providers/shapezio_website.js +++ /dev/null @@ -1,24 +0,0 @@ -import { EmbedProvider } from "../embed_provider"; -import { AdinplayAdProvider } from "../../ad_providers/adinplay"; - -export class ShapezioWebsiteEmbedProvider extends EmbedProvider { - getId() { - return "shapezio"; - } - - getSupportsAds() { - return true; - } - - getAdProvider() { - return AdinplayAdProvider; - } - - getIsIframed() { - return false; - } - - getSupportsExternalLinks() { - return true; - } -} diff --git a/src/js/platform/browser/wrapper.js b/src/js/platform/browser/wrapper.js index 93565342..32c0e5f3 100644 --- a/src/js/platform/browser/wrapper.js +++ b/src/js/platform/browser/wrapper.js @@ -1,18 +1,11 @@ import { Math_min } from "../../core/builtins"; +import { globalConfig, IS_MOBILE } from "../../core/config"; import { createLogger } from "../../core/logging"; import { queryParamOptions } from "../../core/query_parameters"; import { clamp } from "../../core/utils"; -import { globalConfig, IS_MOBILE } from "../../core/config"; import { NoAdProvider } from "../ad_providers/no_ad_provider"; import { PlatformWrapperInterface } from "../wrapper"; -import { ShapezioWebsiteEmbedProvider } from "./embed_providers/shapezio_website"; -import { ArmorgamesEmbedProvider } from "./embed_providers/armorgames"; -import { IogamesSpaceEmbedProvider } from "./embed_providers/iogames_space"; -import { MiniclipEmbedProvider } from "./embed_providers/miniclip"; -import { GamedistributionEmbedProvider } from "./embed_providers/gamedistribution"; -import { KongregateEmbedProvider } from "./embed_providers/kongregate"; -import { CrazygamesEmbedProvider } from "./embed_providers/crazygames"; -import { EmbedProvider } from "./embed_provider"; +import { GamedistributionAdProvider } from "../ad_providers/gamedistribution"; const logger = createLogger("platform/browser"); @@ -20,39 +13,50 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { initialize() { this.recaptchaTokenCallback = null; - this.embedProvider = new ShapezioWebsiteEmbedProvider(); + this.embedProvider = { + id: "shapezio-website", + adProvider: NoAdProvider, + iframed: false, + externalLinks: true, + iogLink: true, + }; if (!G_IS_STANDALONE && queryParamOptions.embedProvider) { const providerId = queryParamOptions.embedProvider; + this.embedProvider.iframed = true; + this.embedProvider.iogLink = false; switch (providerId) { case "armorgames": { - this.embedProvider = new ArmorgamesEmbedProvider(); + this.embedProvider.id = "armorgames"; break; } case "iogames.space": { - this.embedProvider = new IogamesSpaceEmbedProvider(); + this.embedProvider.id = "iogames.space"; + this.embedProvider.iogLink = true; break; } case "miniclip": { - this.embedProvider = new MiniclipEmbedProvider(); + this.embedProvider.id = "miniclip"; break; } case "gamedistribution": { - this.embedProvider = new GamedistributionEmbedProvider(); + this.embedProvider.id = "gamedistribution"; + this.embedProvider.externalLinks = false; + this.embedProvider.adProvider = GamedistributionAdProvider; break; } case "kongregate": { - this.embedProvider = new KongregateEmbedProvider(); + this.embedProvider.id = "kongregate"; break; } case "crazygames": { - this.embedProvider = new CrazygamesEmbedProvider(); + this.embedProvider.id = "crazygames"; break; } @@ -62,27 +66,9 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { } } - logger.log("Embed provider:", this.embedProvider.getId()); + logger.log("Embed provider:", this.embedProvider.id); - return super.initialize().then(() => { - // SENTRY - if (!G_IS_DEV && false) { - logger.log(this, "Loading sentry"); - const sentryTag = document.createElement("script"); - sentryTag.src = "https://browser.sentry-cdn.com/5.7.1/bundle.min.js"; - sentryTag.setAttribute("integrity", "TODO_SENTRY"); - sentryTag.setAttribute("crossorigin", "anonymous"); - sentryTag.addEventListener("load", this.onSentryLoaded.bind(this)); - document.head.appendChild(sentryTag); - } - }); - } - - /** - * @returns {EmbedProvider} - */ - getEmbedProvider() { - return this.embedProvider; + return super.initialize().then(() => this.initializeAdProvider()); } onSentryLoaded() { @@ -151,7 +137,7 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { } getId() { - return "browser@" + this.embedProvider.getId(); + return "browser@" + this.embedProvider.id; } getUiScale() { @@ -173,16 +159,15 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { openExternalLink(url, force = false) { logger.log("Opening external:", url); - // if (force || this.embedProvider.getSupportsExternalLinks()) { - window.open(url); - // } else { - // // Do nothing - // alert("This platform does not allow opening external links. You can play on the website directly to open them."); - // } - } - - getSupportsAds() { - return this.embedProvider.getSupportsAds(); + if (force || this.embedProvider.externalLinks) { + window.open(url); + } else { + // Do nothing + alert( + "This platform does not allow opening external links. You can play on https://shapez.io directly to open them.\n\nClicked Link: " + + url + ); + } } performRestart() { @@ -218,16 +203,18 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { initializeAdProvider() { if (G_IS_DEV && !globalConfig.debug.testAds) { + logger.log("Ads disabled in local environment"); return Promise.resolve(); } // First, detect adblocker return this.detectAdblock().then(hasAdblocker => { if (hasAdblocker) { + logger.log("Adblock detected"); return; } - const adProvider = this.embedProvider.getAdProvider(); + const adProvider = this.embedProvider.adProvider; this.app.adProvider = new adProvider(this.app); return this.app.adProvider.initialize().catch(err => { logger.error("Failed to initialize ad provider, disabling ads:", err); diff --git a/src/js/states/about.js b/src/js/states/about.js index 81d556e0..e527b737 100644 --- a/src/js/states/about.js +++ b/src/js/states/about.js @@ -28,7 +28,16 @@ export class AboutState extends TextualGameState { `; } - onEnter() {} + onEnter() { + const links = this.htmlElement.querySelectorAll("a[href]"); + links.forEach(link => { + this.trackClicks( + link, + () => this.app.platformWrapper.openExternalLink(link.getAttribute("href")), + { preventClick: true } + ); + }); + } getDefaultPreviousState() { return "SettingsState"; diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 5665bb0b..0f4481ce 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -11,6 +11,7 @@ import { import { ReadWriteProxy } from "../core/read_write_proxy"; import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs"; import { T } from "../translations"; +import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper"; export class MainMenuState extends GameState { constructor() { @@ -76,23 +77,25 @@ export class MainMenuState extends GameState { `; @@ -100,6 +103,7 @@ export class MainMenuState extends GameState { requestImportSavegame() { if (IS_DEMO && this.app.savegameMgr.getSavegamesMetaData().length > 0) { + this.app.analytics.trackUiClick("importgame_slot_limit_show"); this.dialogs.showWarning(T.dialogs.oneSavegameLimit.title, T.dialogs.oneSavegameLimit.desc); return; } @@ -210,16 +214,36 @@ export class MainMenuState extends GameState { this.renderSavegames(); - const steamLinks = this.htmlElement.querySelectorAll(".steamLink"); - steamLinks.forEach(steamLink => { - steamLink.addEventListener("click", this.onSteamLinkClicked.bind(this)); - }); + const steamLink = this.htmlElement.querySelector(".steamLink"); + if (steamLink) { + this.trackClicks(steamLink, () => this.onSteamLinkClicked(), { preventClick: true }); + } + + const discordLink = this.htmlElement.querySelector(".discordLink"); + this.trackClicks( + discordLink, + () => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.discord), + { preventClick: true } + ); + + const githubLink = this.htmlElement.querySelector(".githubLink"); + this.trackClicks( + githubLink, + () => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github), + { preventClick: true } + ); + + const producerLink = this.htmlElement.querySelector(".producerLink"); + this.trackClicks( + producerLink, + () => this.app.platformWrapper.openExternalLink("https://tobspr.com"), + { preventClick: true } + ); } - onSteamLinkClicked(event) { + onSteamLinkClicked() { this.app.analytics.trackUiClick("main_menu_steam_link"); - window.open(THIRDPARTY_URLS.standaloneStorePage); - event.preventDefault(); + this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage); return false; } @@ -272,15 +296,13 @@ export class MainMenuState extends GameState { resumeGame(game) { this.app.analytics.trackUiClick("resume_game"); - // if (IS_DEMO) { - // this.dialogs.showFeatureRestrictionInfo(T.demo.features.restoringGames); - // return; - // } - - const savegame = this.app.savegameMgr.getSavegameById(game.internalId); - savegame.readAsync().then(() => { - this.moveToState("InGameState", { - savegame, + this.app.adProvider.showVideoAd().then(() => { + this.app.analytics.trackUiClick("resume_game_adcomplete"); + const savegame = this.app.savegameMgr.getSavegameById(game.internalId); + savegame.readAsync().then(() => { + this.moveToState("InGameState", { + savegame, + }); }); }); } @@ -289,6 +311,8 @@ export class MainMenuState extends GameState { * @param {object} game */ deleteGame(game) { + this.app.analytics.trackUiClick("delete_game"); + const signals = this.dialogs.showWarning( T.dialogs.confirmSavegameDelete.title, T.dialogs.confirmSavegameDelete.text, @@ -329,20 +353,26 @@ export class MainMenuState extends GameState { doStartNewGame() { this.app.analytics.trackUiClick("startgame"); - const savegame = this.app.savegameMgr.createNewSavegame(); - this.moveToState("InGameState", { - savegame, + this.app.adProvider.showVideoAd().then(() => { + const savegame = this.app.savegameMgr.createNewSavegame(); + + this.moveToState("InGameState", { + savegame, + }); + this.app.analytics.trackUiClick("startgame_adcomplete"); }); } onPlayButtonClicked() { if (IS_DEMO && this.app.savegameMgr.getSavegamesMetaData().length > 0) { + this.app.analytics.trackUiClick("startgame_slot_limit_show"); this.dialogs.showWarning(T.dialogs.oneSavegameLimit.title, T.dialogs.oneSavegameLimit.desc); return; } if (IS_DEMO) { + this.app.analytics.trackUiClick("startgame_pre_show"); const { ok } = this.dialogs.showWarning( T.dialogs.demoExplanation.title, T.dialogs.demoExplanation.desc