Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@
"exit": "Exit Game",
"keep": "Keep Playing",
"spectate": "Spectate",
"requeue": "Play Again",
"wishlist": "Wishlist on Steam!",
"ofm_winter": "OpenFront Masters Winter Tournament!",
"ofm_winter_description": "Join the competitive tournament and compete against the best players",
Expand Down
26 changes: 26 additions & 0 deletions src/client/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,32 @@ class Client {
if (decodedHash.startsWith("#refresh")) {
window.location.href = "/";
}

// Handle requeue parameter for ranked matchmaking
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has("requeue")) {
// Remove only the requeue parameter, preserving other params and hash
searchParams.delete("requeue");
const newUrl =
window.location.pathname +
(searchParams.toString() ? "?" + searchParams.toString() : "") +
window.location.hash;
history.replaceState(null, "", newUrl);
// Wait for matchmaking button to be defined, then trigger its click handler
// This goes through username validation instead of bypassing it
customElements.whenDefined("matchmaking-button").then(() => {
const matchmakingButton = document.querySelector(
"matchmaking-button button",
) as HTMLButtonElement | null;
if (matchmakingButton) {
matchmakingButton.click();
} else {
console.warn(
"Requeue requested, but matchmaking button not found in DOM.",
);
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

private async handleJoinLobby(event: CustomEvent<JoinLobbyEvent>) {
Expand Down
2 changes: 1 addition & 1 deletion src/client/Matchmaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export class MatchmakingButton extends LitElement {
window.showPage?.("page-account");
}

private open() {
public open() {
this.matchmakingModal?.open();
}

Expand Down
23 changes: 23 additions & 0 deletions src/client/graphics/layers/WinModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "../../../client/Utils";
import { ColorPalette, Pattern } from "../../../core/CosmeticSchemas";
import { EventBus } from "../../../core/EventBus";
import { RankedType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { getUserMe } from "../../Api";
Expand Down Expand Up @@ -37,6 +38,9 @@ export class WinModal extends LitElement implements Layer {
@state()
private isWin = false;

@state()
private isRankedGame = false;

@state()
private patternContent: TemplateResult | null = null;

Expand Down Expand Up @@ -75,6 +79,16 @@ export class WinModal extends LitElement implements Layer {
>
${translateText("win_modal.exit")}
</button>
${this.isRankedGame
? html`
<button
@click=${this._handleRequeue}
class="flex-1 px-3 py-3 text-base cursor-pointer bg-purple-600 text-white border-0 rounded-sm transition-all duration-200 hover:bg-purple-500 hover:-translate-y-px active:translate-y-px"
>
${translateText("win_modal.requeue")}
</button>
`
: null}
<button
@click=${this.hide}
class="flex-1 px-3 py-3 text-base cursor-pointer bg-blue-500/60 text-white border-0 rounded-sm transition-all duration-200 hover:bg-blue-500/80 hover:-translate-y-px active:translate-y-px"
Expand Down Expand Up @@ -251,6 +265,9 @@ export class WinModal extends LitElement implements Layer {
async show() {
crazyGamesSDK.gameplayStop();
await this.loadPatternContent();
// Check if this is a ranked game
this.isRankedGame =
this.game.config().gameConfig().rankedType === RankedType.OneVOne;
this.isVisible = true;
this.requestUpdate();
setTimeout(() => {
Expand All @@ -270,6 +287,12 @@ export class WinModal extends LitElement implements Layer {
window.location.href = "/";
}

private _handleRequeue() {
this.hide();
// Navigate to homepage and open matchmaking modal
window.location.href = "/?requeue";
}

init() {}

tick() {
Expand Down
117 changes: 117 additions & 0 deletions tests/client/graphics/layers/WinModal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { RankedType } from "../../../../src/core/game/Game";

vi.mock("../../../../src/client/Utils", () => ({
translateText: vi.fn((key: string) => {
const translations: Record<string, string> = {
"win_modal.exit": "Exit",
"win_modal.requeue": "Play Again",
"win_modal.keep": "Keep Playing",
"win_modal.spectate": "Spectate",
};
return translations[key] || key;
}),
getGamesPlayed: vi.fn(() => 10),
isInIframe: vi.fn(() => false),
TUTORIAL_VIDEO_URL: "https://example.com/tutorial",
}));

vi.mock("../../../../src/client/Api", () => ({
getUserMe: vi.fn(async () => null),
}));

vi.mock("../../../../src/client/Cosmetics", () => ({
fetchCosmetics: vi.fn(async () => []),
handlePurchase: vi.fn(),
patternRelationship: vi.fn(() => ({})),
}));

vi.mock("../../../../src/client/CrazyGamesSDK", () => ({
crazyGamesSDK: {
happytime: vi.fn(),
requestAd: vi.fn(),
gameplayStop: vi.fn(),
},
}));
Comment thread
coderabbitai[bot] marked this conversation as resolved.

describe("WinModal Requeue", () => {
let mockLocationHref = "";

beforeEach(() => {
mockLocationHref = "";
// Mock window.location.href using Object.defineProperty
const locationMock = {
get href() {
return mockLocationHref;
},
set href(value: string) {
mockLocationHref = value;
},
};
Object.defineProperty(window, "location", {
value: locationMock,
writable: true,
configurable: true,
});
});

afterEach(() => {
vi.restoreAllMocks();
});

describe("isRankedGame detection", () => {
it("should detect ranked 1v1 game", () => {
const gameConfig = {
rankedType: RankedType.OneVOne,
};
const isRankedGame = gameConfig.rankedType === RankedType.OneVOne;
expect(isRankedGame).toBe(true);
});

it("should not detect non-ranked game", () => {
const gameConfig = {
rankedType: undefined,
};
const isRankedGame = gameConfig.rankedType === RankedType.OneVOne;
expect(isRankedGame).toBe(false);
});
});

describe("requeue navigation", () => {
it("should navigate to /?requeue when requeue is triggered", () => {
// Simulate the _handleRequeue behavior
const handleRequeue = () => {
window.location.href = "/?requeue";
};

handleRequeue();

expect(window.location.href).toBe("/?requeue");
});

it("should navigate to / when exit is triggered", () => {
// Simulate the _handleExit behavior
const handleExit = () => {
window.location.href = "/";
};

handleExit();

expect(window.location.href).toBe("/");
});
});

describe("requeue URL parameter handling", () => {
it("should parse requeue parameter from URL", () => {
const url = new URL("http://localhost:9000/?requeue");
const hasRequeue = url.searchParams.has("requeue");
expect(hasRequeue).toBe(true);
});

it("should not find requeue parameter when absent", () => {
const url = new URL("http://localhost:9000/");
const hasRequeue = url.searchParams.has("requeue");
expect(hasRequeue).toBe(false);
});
});
});
Comment thread
Skigim marked this conversation as resolved.
Loading