diff --git a/resources/lang/en.json b/resources/lang/en.json index 9d601ad66..4398c7214 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -871,13 +871,6 @@ "pattern": { "default": "Default" }, - "try_me": "Try me!", - "trial_remaining": "remaining", - "trial_granted": "Skin trial granted!", - "trial_cooldown": "Only one trial per 24 hours. Please try again later.", - "trial_login_required": "Must be logged in to trial a skin", - "reward_countdown": "Reward in {seconds} seconds...", - "steam_wishlist_prompt": "Support OpenFront by adding it to your Steam wishlist", "select_skin": "Select Skin", "selected": "selected" }, diff --git a/src/client/Api.ts b/src/client/Api.ts index 336385a5f..d128ad2f2 100644 --- a/src/client/Api.ts +++ b/src/client/Api.ts @@ -125,31 +125,6 @@ export async function createCheckoutSession( } } -export async function grantTemporaryFlare(flare: string): Promise { - try { - const response = await fetch(`${getApiBase()}/flares_granted/temporary`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: await getAuthHeader(), - }, - body: JSON.stringify({ flare }), - }); - if (!response.ok) { - console.error( - "grantTemporaryFlare: request failed", - response.status, - response.statusText, - ); - return false; - } - return true; - } catch (e) { - console.error("grantTemporaryFlare: request failed", e); - return false; - } -} - export function getApiBase() { const domainname = getAudience(); diff --git a/src/client/Cosmetics.ts b/src/client/Cosmetics.ts index 4bf764032..a09470d33 100644 --- a/src/client/Cosmetics.ts +++ b/src/client/Cosmetics.ts @@ -85,19 +85,14 @@ export async function getCosmeticsHash(): Promise { return __cosmeticsHash; } -// When a number is returned it signifies when the pattern expires. export function patternRelationship( pattern: Pattern, colorPalette: { name: string; isArchived?: boolean } | null, userMeResponse: UserMeResponse | false, affiliateCode: string | null, -): "owned" | "purchasable" | "purchasable_no_trial" | "blocked" | number { +): "owned" | "purchasable" | "blocked" { const flares = userMeResponse === false ? [] : (userMeResponse.player.flares ?? []); - const expirations: Record = - userMeResponse === false - ? {} - : (userMeResponse.player.flareExpiration ?? {}); if (flares.includes("pattern:*")) { return "owned"; } @@ -113,14 +108,6 @@ export function patternRelationship( const requiredFlare = `pattern:${pattern.name}:${colorPalette.name}`; if (flares.includes(requiredFlare)) { - const expiresAt = expirations[requiredFlare]; - if (expiresAt) { - if (expiresAt - Date.now() <= TEMP_FLARE_OFFSET) { - // Already expired or about to expire so just show it as purchasable. - return "purchasable"; - } - return expiresAt; - } return "owned"; } @@ -139,12 +126,7 @@ export function patternRelationship( return "blocked"; } - // --- Patterns is for sale, and it's the right store to show it on. --- - - if (pattern.name === "custom") { - // Don't allow trying a custom pattern. - return "purchasable_no_trial"; - } + // Patterns is for sale, and it's the right store to show it on. return "purchasable"; } @@ -162,16 +144,9 @@ export async function getPlayerCosmeticsRefs(): Promise { ? `pattern:${pattern.name}` : `pattern:${pattern.name}:${pattern.colorPalette.name}`; const flares = userMe.player.flares ?? []; - const expirations = userMe.player.flareExpiration ?? {}; const hasWildcard = flares.includes("pattern:*"); - if (!hasWildcard) { - if (!flares.includes(flareName)) { - pattern = null; - } else if (expirations[flareName]) { - if (expirations[flareName]! - Date.now() <= TEMP_FLARE_OFFSET) { - pattern = null; - } - } + if (!hasWildcard && !flares.includes(flareName)) { + pattern = null; } } if (pattern === null) { diff --git a/src/client/Main.ts b/src/client/Main.ts index 7d4812b42..339c9049f 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -182,12 +182,6 @@ declare global { onPlayerReady: (() => void) | null; addUnits: (units: Array<{ type: string }>) => Promise; displayUnits: () => void; - // Rewarded video ad methods - manuallyCreateRewardUi?: (options: { - skipConfirmation?: boolean; - watchAdId?: string; - closeId?: string; - }) => Promise | void; }; Bolt: { on: (unitType: string, event: string, callback: () => void) => void; diff --git a/src/client/RewardedVideoPromo.ts b/src/client/RewardedVideoPromo.ts deleted file mode 100644 index fdc2906ca..000000000 --- a/src/client/RewardedVideoPromo.ts +++ /dev/null @@ -1,179 +0,0 @@ -let rewardedUnitRegistered = false; -let rewardedAdReady = false; - -// Listen for when rewarded ad becomes available -if (typeof window !== "undefined") { - window.addEventListener("rewardedAdVideoRewardReady", () => { - console.log("[RewardedVideoPromo] Rewarded ad is ready"); - rewardedAdReady = true; - }); -} - -const AD_READY_TIMEOUT_MS = 3000; - -function ensureRewardedUnitRegistered(): Promise { - console.log("[ensureRewardedUnitRegistered] Called", { - rewardedUnitRegistered, - rewardedAdReady, - hasSpaAddAds: !!window.ramp?.spaAddAds, - }); - - return new Promise((resolve, reject) => { - // Check for real SDK (not just stub from index.html) - if (!window.ramp?.spaAddAds) { - console.log( - "[ensureRewardedUnitRegistered] Rejecting: spaAddAds not available", - ); - reject(new Error("Ramp SDK not available")); - return; - } - - // If already registered and ready, resolve immediately - if (rewardedUnitRegistered && rewardedAdReady) { - console.log( - "[ensureRewardedUnitRegistered] Already registered and ready", - ); - resolve(); - return; - } - - // Register the unit if not already registered - if (!rewardedUnitRegistered) { - try { - window.ramp.spaAddAds([{ type: "rewarded_ad_video", selectorId: "" }]); - rewardedUnitRegistered = true; - console.log("[RewardedVideoPromo] Rewarded unit registered"); - } catch (e) { - reject(e); - return; - } - } - - // If ad is already ready, resolve - if (rewardedAdReady) { - console.log("[ensureRewardedUnitRegistered] Ad already ready"); - resolve(); - return; - } - - // Wait for the rewardedAdVideoRewardReady event or no-fill event - console.log("[ensureRewardedUnitRegistered] Waiting for ad to be ready..."); - let timeoutId: ReturnType | null = null; - - const cleanup = () => { - if (timeoutId) clearTimeout(timeoutId); - window.removeEventListener("rewardedAdVideoRewardReady", onReady); - window.removeEventListener("rewardedVideoNoFill", onNoFill); - window.removeEventListener("rewardedAdNoFill", onNoFill); - window.removeEventListener("pwNoFillEvent", onNoFill); - }; - - const onReady = () => { - console.log("[ensureRewardedUnitRegistered] Ad is now ready"); - cleanup(); - resolve(); - }; - - const onNoFill = () => { - console.log("[ensureRewardedUnitRegistered] No fill event received"); - cleanup(); - reject(new Error("No rewarded ad available")); - }; - - timeoutId = setTimeout(() => { - cleanup(); - console.log("[ensureRewardedUnitRegistered] Timeout waiting for ad"); - reject(new Error("Ad timeout")); - }, AD_READY_TIMEOUT_MS); - - window.addEventListener("rewardedAdVideoRewardReady", onReady); - window.addEventListener("rewardedVideoNoFill", onNoFill); - window.addEventListener("rewardedAdNoFill", onNoFill); - window.addEventListener("pwNoFillEvent", onNoFill); - }); -} - -export function showRewardedAd(): Promise { - console.log("[showRewardedAd] Called", { - rewardedUnitRegistered, - }); - - return new Promise((resolve, reject) => { - console.log("[showRewardedAd] Calling ensureRewardedUnitRegistered..."); - ensureRewardedUnitRegistered() - .then(() => { - console.log("[showRewardedAd] ensureRewardedUnitRegistered resolved"); - if (!window.ramp?.manuallyCreateRewardUi) { - reject(new Error("Ramp SDK manuallyCreateRewardUi not available")); - return; - } - - // Set up event listeners before triggering the ad - const cleanup = () => { - window.removeEventListener( - "rewardedAdRewardGranted", - onRewardGranted, - ); - window.removeEventListener("rewardedAdCompleted", onCompleted); - window.removeEventListener("rewardedCloseButtonTriggered", onClosed); - window.removeEventListener("rejectAdCloseCta", onRejected); - // Destroy old unit and reset state so next ad attempt will re-register - try { - window.ramp?.destroyUnits?.("rewarded_ad_video"); - } catch (e) { - console.error("[showRewardedAd] Failed to destroy unit:", e); - } - rewardedUnitRegistered = false; - rewardedAdReady = false; - }; - - const onRewardGranted = () => { - console.log("[showRewardedAd] Reward granted"); - cleanup(); - resolve(); - }; - - const onCompleted = () => { - console.log("[showRewardedAd] Ad completed without reward"); - // Don't resolve here - wait for rewardedAdRewardGranted - }; - - const onClosed = () => { - console.log("[showRewardedAd] User closed ad early"); - cleanup(); - reject(new Error("User closed ad early")); - }; - - const onRejected = () => { - console.log("[showRewardedAd] User rejected ad"); - cleanup(); - reject(new Error("User rejected ad")); - }; - - window.addEventListener("rewardedAdRewardGranted", onRewardGranted); - window.addEventListener("rewardedAdCompleted", onCompleted); - window.addEventListener("rewardedCloseButtonTriggered", onClosed); - window.addEventListener("rejectAdCloseCta", onRejected); - - // Trigger the ad - const result = window.ramp.manuallyCreateRewardUi({ - skipConfirmation: true, - }); - - // If it returns a promise that rejects, handle that too - if (result && typeof result.then === "function") { - result.catch((error: unknown) => { - cleanup(); - reject(error); - }); - } - }) - .catch((err) => { - console.log( - "[showRewardedAd] ensureRewardedUnitRegistered rejected:", - err, - ); - reject(err); - }); - }); -} diff --git a/src/client/TerritoryPatternsModal.ts b/src/client/TerritoryPatternsModal.ts index 105e3fb63..6e7f8e01f 100644 --- a/src/client/TerritoryPatternsModal.ts +++ b/src/client/TerritoryPatternsModal.ts @@ -15,7 +15,6 @@ import { getPlayerCosmetics, handlePurchase, patternRelationship, - TEMP_FLARE_OFFSET, } from "./Cosmetics"; import { translateText } from "./Utils"; @@ -124,7 +123,7 @@ export class TerritoryPatternsModal extends BaseModal { ? [...(pattern.colorPalettes ?? []), null] : [null]; for (const colorPalette of colorPalettes) { - let rel: string | number = "owned"; + let rel = "owned"; if (pattern) { rel = patternRelationship( pattern, @@ -136,9 +135,8 @@ export class TerritoryPatternsModal extends BaseModal { if (rel === "blocked") { continue; } - const isTrial = typeof rel === "number"; if (this.showOnlyOwned) { - if (rel !== "owned" && !isTrial) continue; + if (rel !== "owned") continue; } else { // Store mode: hide owned items if (rel === "owned") continue; @@ -158,20 +156,7 @@ export class TerritoryPatternsModal extends BaseModal { .colorPalette=${this.cosmetics?.colorPalettes?.[ colorPalette?.name ?? "" ] ?? null} - .requiresPurchase=${rel === "purchasable" || - rel === "purchasable_no_trial"} - .allowTrial=${rel === "purchasable"} - .hasLinkedAccount=${hasLinkedAccount(this.userMeResponse)} - .trialCooldown=${this.userMeResponse !== false && - this.userMeResponse.player.tempFlaresCooldown} - .trialTimeRemaining=${isTrial - ? Math.max( - 0, - Math.floor( - ((rel as number) - TEMP_FLARE_OFFSET - Date.now()) / 1000, - ), - ) - : 0} + .requiresPurchase=${rel === "purchasable"} .selected=${isSelected} .onSelect=${(p: PlayerPattern | null) => this.selectPattern(p)} .onPurchase=${(p: Pattern, colorPalette: ColorPalette | null) => diff --git a/src/client/components/PatternButton.ts b/src/client/components/PatternButton.ts index 59156b854..6d6b94b18 100644 --- a/src/client/components/PatternButton.ts +++ b/src/client/components/PatternButton.ts @@ -1,17 +1,14 @@ import { Colord } from "colord"; import { base64url } from "jose"; import { html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; import { ColorPalette, DefaultPattern, Pattern, } from "../../core/CosmeticSchemas"; -import { UserSettings } from "../../core/game/UserSettings"; import { PatternDecoder } from "../../core/PatternDecoder"; import { PlayerPattern } from "../../core/Schemas"; -import { grantTemporaryFlare } from "../Api"; -import { showRewardedAd } from "../RewardedVideoPromo"; import { translateText } from "../Utils"; export const BUTTON_WIDTH = 150; @@ -29,64 +26,16 @@ export class PatternButton extends LitElement { @property({ type: Boolean }) requiresPurchase: boolean = false; - @property({ type: Number }) - trialTimeRemaining: number = 0; - - @property({ type: Boolean }) - allowTrial: boolean = true; - - @property({ type: Boolean }) - trialCooldown: boolean = false; - - @property({ type: Boolean }) - hasLinkedAccount: boolean = false; - @property({ type: Function }) onSelect?: (pattern: PlayerPattern | null) => void; @property({ type: Function }) onPurchase?: (pattern: Pattern, colorPalette: ColorPalette | null) => void; - private _countdownInterval: ReturnType | null = null; - - @state() - private _adLoading: boolean = false; - createRenderRoot() { return this; } - updated(changedProperties: Map) { - if (changedProperties.has("trialTimeRemaining")) { - this.setupCountdown(); - } - } - - disconnectedCallback() { - super.disconnectedCallback(); - this.clearCountdown(); - } - - private setupCountdown() { - this.clearCountdown(); - if (this.trialTimeRemaining > 0) { - this._countdownInterval = setInterval(() => { - this.trialTimeRemaining--; - if (this.trialTimeRemaining <= 0) { - this.trialTimeRemaining = 0; - this.clearCountdown(); - } - }, 1000); - } - } - - private clearCountdown() { - if (this._countdownInterval !== null) { - clearInterval(this._countdownInterval); - this._countdownInterval = null; - } - } - private translateCosmetic(prefix: string, patternName: string): string { const translation = translateText(`${prefix}.${patternName}`); if (translation.startsWith(prefix)) { @@ -111,104 +60,6 @@ export class PatternButton extends LitElement { } satisfies PlayerPattern); } - private async grantTrial() { - const flare = - this.colorPalette?.name === undefined - ? `pattern:${this.pattern!.name}` - : `pattern:${this.pattern!.name}:${this.colorPalette.name}`; - await grantTemporaryFlare(flare); - new UserSettings().setSelectedPatternName(flare); - alert(translateText("territory_patterns.trial_granted")); - window.location.reload(); - } - - private showSteamModal(): Promise { - return new Promise((resolve) => { - const overlay = document.createElement("div"); - overlay.className = - "fixed inset-0 bg-black/80 flex items-center justify-center z-[9999]"; - - let secondsLeft = 10; - const updateContent = () => { - overlay.innerHTML = ` -
-

Wishlist on Steam!

-

${translateText("territory_patterns.steam_wishlist_prompt")}

- - - - - - Wishlist on Steam - - -
- ${translateText("territory_patterns.reward_countdown", { seconds: secondsLeft.toString() })} -
-
- `; - }; - - updateContent(); - document.body.appendChild(overlay); - - const interval = setInterval(() => { - secondsLeft--; - if (secondsLeft <= 0) { - clearInterval(interval); - overlay.remove(); - resolve(); - } else { - updateContent(); - } - }, 1000); - }); - } - - private async handleTryMe(e: Event) { - e.stopPropagation(); - if (this.pattern === null || this._adLoading) return; - - if (!this.hasLinkedAccount) { - alert(translateText("territory_patterns.trial_login_required")); - return; - } - - if (this.trialCooldown) { - alert(translateText("territory_patterns.trial_cooldown")); - return; - } - - console.log("[PatternButton] handleTryMe called"); - this._adLoading = true; - - try { - console.log("[PatternButton] Calling showRewardedAd..."); - await showRewardedAd(); - console.log("[PatternButton] showRewardedAd resolved"); - await this.grantTrial(); - } catch (error) { - console.error("[PatternButton] Rewarded ad failed:", error); - // Show Steam wishlist modal with countdown - await this.showSteamModal(); - await this.grantTrial(); - } finally { - this._adLoading = false; - } - } - - private formatTimeRemaining(seconds: number): string { - const m = Math.floor(seconds / 60); - const s = seconds % 60; - if (m > 0) return `${m}m ${s}s`; - return `${s}s`; - } - private handlePurchase(e: Event) { e.stopPropagation(); if (this.pattern?.product) { @@ -286,52 +137,9 @@ export class PatternButton extends LitElement { - ${(this.requiresPurchase || this.trialTimeRemaining > 0) && - this.pattern?.product + ${this.requiresPurchase && this.pattern?.product ? html` -
- ${this.trialTimeRemaining > 0 - ? html` -
- ${this.formatTimeRemaining(this.trialTimeRemaining)} - ${translateText("territory_patterns.trial_remaining")} -
- ` - : this.allowTrial - ? html` - - ` - : null} +