add spawn ads (#1228)

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.
<img width="1003" alt="74fb6676-273d-4b58-9fcb-50ec438c4e27"
src="https://github.com/user-attachments/assets/fedca20f-7b31-4a06-be57-bde5bd0118f0"
/>

- [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
regression is found:
evan

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
evanpelle
2025-06-19 10:36:46 -07:00
committed by evanpelle
parent abe58261f8
commit 8d16da13e7
6 changed files with 62 additions and 45 deletions
+2 -1
View File
@@ -25,7 +25,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";
@@ -340,6 +340,7 @@ class Client {
() => {
this.joinModal.close();
this.publicLobby.stop();
incrementGamesPlayed();
try {
window.PageOS.session.newPageView();
+17
View File
@@ -95,3 +95,20 @@ export const translateText = (
return langSelector.translateText(key, params);
};
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);
}
}
+6 -8
View File
@@ -16,7 +16,6 @@ import { FxLayer } from "./layers/FxLayer";
import { GutterAdModal } from "./layers/GutterAdModal";
import { Layer } from "./layers/Layer";
import { Leaderboard } from "./layers/Leaderboard";
import { LeftInGameAd } from "./layers/LeftInGameAd";
import { MultiTabModal } from "./layers/MultiTabModal";
import { NameLayer } from "./layers/NameLayer";
import { OptionsMenu } from "./layers/OptionsMenu";
@@ -24,6 +23,7 @@ import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
import { PlayerPanel } from "./layers/PlayerPanel";
import { PlayerTeamLabel } from "./layers/PlayerTeamLabel";
import { RadialMenu } from "./layers/RadialMenu";
import { SpawnAd } from "./layers/SpawnAd";
import { SpawnTimer } from "./layers/SpawnTimer";
import { StructureLayer } from "./layers/StructureLayer";
import { TeamStats } from "./layers/TeamStats";
@@ -173,13 +173,11 @@ export function createRenderer(
}
playerTeamLabel.game = game;
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",
@@ -222,7 +220,7 @@ export function createRenderer(
playerPanel,
playerTeamLabel,
multiTabModal,
leftInGameAd,
spawnAd,
gutterAdModal,
];
+10 -7
View File
@@ -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() {}
@@ -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,40 +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()) {
// TODO: uncomment to enable in-game ads
// console.log("showing bottom left ad");
// this.show();
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");
@@ -97,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);
@@ -118,7 +116,7 @@ export class LeftInGameAd extends LitElement implements Layer {
return html`
<div
class="w-[320px] min-h-[100px] bg-gray-900 border border-gray-600 flex items-center justify-center"
class="fixed bottom-0 left-0 w-full min-h-[100px] bg-gray-900 border border-gray-600 flex items-center justify-center z-50"
>
<div
id="${AD_CONTAINER_ID}"
+1 -1
View File
@@ -280,7 +280,6 @@
style="pointer-events: auto"
>
<control-panel></control-panel>
<left-in-game-ad></left-in-game-ad>
</div>
</div>
@@ -358,6 +357,7 @@
<multi-tab-modal></multi-tab-modal>
<news-modal></news-modal>
<game-left-sidebar></game-left-sidebar>
<spawn-ad></spawn-ad>
<div
id="language-modal"
class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex justify-center items-center"