From e58bc89b6718e1898a04e1d5ff98ea6db4c6dbfc Mon Sep 17 00:00:00 2001 From: evanpelle Date: Thu, 19 Jun 2025 10:36:46 -0700 Subject: [PATCH] add spawn ads (#1228) ## Description: Adds a bottom rail add during the spawn phase if player has played over 5 games. Also only show the death screen ad if player has played a couple of games. This keeps the experience ad-free for the first few games. 74fb6676-273d-4b58-9fcb-50ec438c4e27 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: evan --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/client/Main.ts | 3 +- src/client/Utils.ts | 17 +++++++ src/client/graphics/GameRenderer.ts | 14 +++--- src/client/graphics/layers/GutterAdModal.ts | 17 ++++--- .../layers/{LeftInGameAd.ts => SpawnAd.ts} | 49 +++++++++---------- src/client/index.html | 2 +- 6 files changed, 60 insertions(+), 42 deletions(-) rename src/client/graphics/layers/{LeftInGameAd.ts => SpawnAd.ts} (71%) diff --git a/src/client/Main.ts b/src/client/Main.ts index 68e16f421..2474d5d24 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -24,7 +24,7 @@ import { SinglePlayerModal } from "./SinglePlayerModal"; import { UserSettingModal } from "./UserSettingModal"; import "./UsernameInput"; import { UsernameInput } from "./UsernameInput"; -import { generateCryptoRandomUUID } from "./Utils"; +import { generateCryptoRandomUUID, incrementGamesPlayed } from "./Utils"; import "./components/NewsButton"; import { NewsButton } from "./components/NewsButton"; import "./components/baseComponents/Button"; @@ -365,6 +365,7 @@ class Client { () => { this.joinModal.close(); this.publicLobby.stop(); + incrementGamesPlayed(); try { window.PageOS.session.newPageView(); diff --git a/src/client/Utils.ts b/src/client/Utils.ts index d8e782f16..c6445c170 100644 --- a/src/client/Utils.ts +++ b/src/client/Utils.ts @@ -168,3 +168,20 @@ export function getAltKey(): string { return "Alt"; } } + +export function getGamesPlayed(): number { + try { + return parseInt(localStorage.getItem("gamesPlayed") || "0", 10) || 0; + } catch (error) { + console.warn("Failed to read games played from localStorage:", error); + return 0; + } +} + +export function incrementGamesPlayed(): void { + try { + localStorage.setItem("gamesPlayed", (getGamesPlayed() + 1).toString()); + } catch (error) { + console.warn("Failed to increment games played in localStorage:", error); + } +} diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index ce808df29..200239e74 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -16,7 +16,6 @@ import { GutterAdModal } from "./layers/GutterAdModal"; import { HeadsUpMessage } from "./layers/HeadsUpMessage"; import { Layer } from "./layers/Layer"; import { Leaderboard } from "./layers/Leaderboard"; -import { LeftInGameAd } from "./layers/LeftInGameAd"; import { MainRadialMenu } from "./layers/MainRadialMenu"; import { MultiTabModal } from "./layers/MultiTabModal"; import { NameLayer } from "./layers/NameLayer"; @@ -24,6 +23,7 @@ import { OptionsMenu } from "./layers/OptionsMenu"; import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay"; import { PlayerPanel } from "./layers/PlayerPanel"; import { ReplayPanel } from "./layers/ReplayPanel"; +import { SpawnAd } from "./layers/SpawnAd"; import { SpawnTimer } from "./layers/SpawnTimer"; import { StructureLayer } from "./layers/StructureLayer"; import { TeamStats } from "./layers/TeamStats"; @@ -198,13 +198,11 @@ export function createRenderer( unitInfoModal.structureLayer = structureLayer; // unitInfoModal.eventBus = eventBus; - const leftInGameAd = document.querySelector( - "left-in-game-ad", - ) as LeftInGameAd; - if (!(leftInGameAd instanceof LeftInGameAd)) { - console.error("left in game ad not found"); + const spawnAd = document.querySelector("spawn-ad") as SpawnAd; + if (!(spawnAd instanceof SpawnAd)) { + console.error("spawn ad not found"); } - leftInGameAd.g = game; + spawnAd.g = game; const gutterAdModal = document.querySelector( "gutter-ad-modal", @@ -249,7 +247,7 @@ export function createRenderer( headsUpMessage, unitInfoModal, multiTabModal, - leftInGameAd, + spawnAd, gutterAdModal, ]; diff --git a/src/client/graphics/layers/GutterAdModal.ts b/src/client/graphics/layers/GutterAdModal.ts index 3c2c6dd24..707a04054 100644 --- a/src/client/graphics/layers/GutterAdModal.ts +++ b/src/client/graphics/layers/GutterAdModal.ts @@ -1,6 +1,7 @@ import { LitElement, css, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { EventBus, GameEvent } from "../../../core/EventBus"; +import { getGamesPlayed } from "../../Utils"; import { Layer } from "./Layer"; export class GutterAdModalEvent implements GameEvent { @@ -29,13 +30,15 @@ export class GutterAdModal extends LitElement implements Layer { } init() { - this.eventBus.on(GutterAdModalEvent, (event) => { - if (event.isVisible) { - this.show(); - } else { - this.hide(); - } - }); + if (getGamesPlayed() > 1) { + this.eventBus.on(GutterAdModalEvent, (event) => { + if (event.isVisible) { + this.show(); + } else { + this.hide(); + } + }); + } } tick() {} diff --git a/src/client/graphics/layers/LeftInGameAd.ts b/src/client/graphics/layers/SpawnAd.ts similarity index 71% rename from src/client/graphics/layers/LeftInGameAd.ts rename to src/client/graphics/layers/SpawnAd.ts index 2e370d8cc..eb8533937 100644 --- a/src/client/graphics/layers/LeftInGameAd.ts +++ b/src/client/graphics/layers/SpawnAd.ts @@ -1,18 +1,14 @@ import { LitElement, css, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { GameView } from "../../../core/game/GameView"; +import { getGamesPlayed } from "../../Utils"; import { Layer } from "./Layer"; -const BREAKPOINT = { - width: 1000, - height: 800, -}; +const AD_TYPE = "bottom_rail"; +const AD_CONTAINER_ID = "bottom-rail-ad-container"; -const AD_TYPE = "standard_iab_modl2"; // 320x50/320x100 - better for bottom positioning -const AD_CONTAINER_ID = "bottom-left-ad-container"; - -@customElement("left-in-game-ad") -export class LeftInGameAd extends LitElement implements Layer { +@customElement("spawn-ad") +export class SpawnAd extends LitElement implements Layer { public g: GameView; @state() @@ -21,6 +17,8 @@ export class LeftInGameAd extends LitElement implements Layer { @state() private adLoaded: boolean = false; + private gamesPlayed: number = 0; + // Override createRenderRoot to disable shadow DOM createRenderRoot() { return this; @@ -32,39 +30,40 @@ export class LeftInGameAd extends LitElement implements Layer { super(); } + init() { + this.gamesPlayed = getGamesPlayed(); + } + public show(): void { this.isVisible = true; + this.loadAd(); this.requestUpdate(); - // Load the ad when showing (with small delay to ensure DOM is ready) - setTimeout(() => this.loadAd(), 100); } public hide(): void { + // Destroy the ad when hiding + this.destroyAd(); this.isVisible = false; this.adLoaded = false; this.requestUpdate(); - // Destroy the ad when hiding - this.destroyAd(); } public async tick() { - if (!this.isVisible && !this.g.inSpawnPhase() && this.screenLargeEnough()) { + if ( + !this.isVisible && + this.g.inSpawnPhase() && + this.g.ticks() > 10 && + this.gamesPlayed > 5 + ) { console.log("showing bottom left ad"); this.show(); } - if (this.isVisible && !this.screenLargeEnough()) { + if (this.isVisible && !this.g.inSpawnPhase()) { console.log("hiding bottom left ad"); this.hide(); } } - private screenLargeEnough(): boolean { - return ( - window.innerWidth > BREAKPOINT.width && - window.innerHeight > BREAKPOINT.height - ); - } - private loadAd(): void { if (!window.ramp) { console.warn("Playwire RAMP not available"); @@ -96,8 +95,8 @@ export class LeftInGameAd extends LitElement implements Layer { } try { window.ramp.que.push(() => { - window.ramp.destroyUnits(AD_TYPE); - console.log("Playwire ad destroyed:", AD_TYPE); + window.ramp.destroyUnits("all"); + console.log("Playwire spawn ad destroyed"); }); } catch (error) { console.error("Failed to destroy Playwire ad:", error); @@ -117,7 +116,7 @@ export class LeftInGameAd extends LitElement implements Layer { return html`
-
@@ -368,6 +367,7 @@ +