Merge branch 'main' into team-names

This commit is contained in:
Mattia Migliorini
2026-03-31 08:07:59 +02:00
committed by GitHub
24 changed files with 808 additions and 85 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

@@ -0,0 +1,195 @@
{
"name": "mediterranean",
"nations": [
{
"coordinates": [141, 574],
"name": "Lusitania",
"flag": "West Roman Empire"
},
{
"coordinates": [464, 519],
"name": "Terraconensis",
"flag": "West Roman Empire"
},
{
"coordinates": [353, 787],
"name": "Baetica",
"flag": "West Roman Empire"
},
{
"coordinates": [699, 340],
"name": "Narbonensis",
"flag": "West Roman Empire"
},
{
"coordinates": [596, 91],
"name": "Aquitania",
"flag": "West Roman Empire"
},
{
"coordinates": [910, 218],
"name": "Alpes",
"flag": "West Roman Empire"
},
{
"coordinates": [1053, 39],
"name": "Raetia",
"flag": "West Roman Empire"
},
{
"coordinates": [1316, 88],
"name": "Noricum",
"flag": "West Roman Empire"
},
{
"coordinates": [1307, 479],
"name": "Italia",
"flag": "West Roman Empire"
},
{
"coordinates": [1058, 534],
"name": "Corsica et Sardinia",
"flag": "West Roman Empire"
},
{
"coordinates": [1350, 762],
"name": "Sicilia",
"flag": "West Roman Empire"
},
{
"coordinates": [1553, 42],
"name": "Pannonia",
"flag": "West Roman Empire"
},
{
"coordinates": [1522, 301],
"name": "Dalmatia",
"flag": "West Roman Empire"
},
{
"coordinates": [1910, 119],
"name": "Dacia",
"flag": "West Roman Empire"
},
{
"coordinates": [1811, 402],
"name": "Macedonia",
"flag": "West Roman Empire"
},
{
"coordinates": [1654, 560],
"name": "Epirus",
"flag": "West Roman Empire"
},
{
"coordinates": [1774, 774],
"name": "Achaia",
"flag": "West Roman Empire"
},
{
"coordinates": [2030, 457],
"name": "Thracia",
"flag": "West Roman Empire"
},
{
"coordinates": [2114, 250],
"name": "Moesia",
"flag": "West Roman Empire"
},
{
"coordinates": [2428, 200],
"name": "Bosporan Kingdom",
"flag": ""
},
{
"coordinates": [2065, 690],
"name": "Asia",
"flag": "West Roman Empire"
},
{
"coordinates": [2354, 505],
"name": "Pontus",
"flag": "West Roman Empire"
},
{
"coordinates": [2325, 669],
"name": "Galatia",
"flag": "West Roman Empire"
},
{
"coordinates": [2447, 809],
"name": "Cilicia",
"flag": "West Roman Empire"
},
{
"coordinates": [2196, 828],
"name": "Lycia",
"flag": "West Roman Empire"
},
{
"coordinates": [2379, 942],
"name": "Cyprus",
"flag": "West Roman Empire"
},
{
"coordinates": [2516, 648],
"name": "Cappadocia",
"flag": "West Roman Empire"
},
{
"coordinates": [2772, 845],
"name": "Mesopotamia",
"flag": "West Roman Empire"
},
{
"coordinates": [2584, 990],
"name": "Syria",
"flag": "West Roman Empire"
},
{
"coordinates": [2490, 1141],
"name": "Judaea",
"flag": "West Roman Empire"
},
{
"coordinates": [2481, 1292],
"name": "Arabia Petraea",
"flag": "West Roman Empire"
},
{
"coordinates": [2263, 1240],
"name": "Aegyptus",
"flag": "West Roman Empire"
},
{
"coordinates": [1799, 1096],
"name": "Cyrenaica et Creta",
"flag": "West Roman Empire"
},
{
"coordinates": [2815, 659],
"name": "Sassanid Empire",
"flag": "Sassanid Empire"
},
{
"coordinates": [1091, 989],
"name": "Africa Proconsularis",
"flag": "West Roman Empire"
},
{
"coordinates": [597, 910],
"name": "Mauretania Caesariensis",
"flag": "West Roman Empire"
},
{
"coordinates": [264, 1028],
"name": "Mauretania Tingitania",
"flag": "West Roman Empire"
},
{
"coordinates": [584, 1199],
"name": "Numidia",
"flag": "Amazigh flag"
}
]
}
+1
View File
@@ -84,6 +84,7 @@ var maps = []struct {
{Name: "sanfrancisco"},
{Name: "aegean"},
{Name: "milkyway"},
{Name: "mediterranean"},
{Name: "big_plains", IsTest: true},
{Name: "half_land_half_ocean", IsTest: true},
{Name: "ocean_and_land", IsTest: true},
+9 -9
View File
@@ -5617,9 +5617,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -8130,9 +8130,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9741,9 +9741,9 @@
}
},
"node_modules/minimatch/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
+3 -2
View File
@@ -6,6 +6,7 @@
"lang_code": "en"
},
"common": {
"not_logged_in": "Not logged in",
"close": "Close",
"copy": "Copy",
"paste": "Paste",
@@ -351,7 +352,8 @@
"arctic": "Arctic",
"sanfrancisco": "San Francisco",
"aegean": "Aegean",
"milkyway": "Milky Way"
"milkyway": "Milky Way",
"mediterranean": "Mediterranean"
},
"map_categories": {
"featured": "Featured",
@@ -939,7 +941,6 @@
"territory_patterns": {
"title": "Skins",
"purchase": "Purchase",
"not_logged_in": "Not logged in",
"pattern": {
"default": "Default"
},
+210
View File
@@ -0,0 +1,210 @@
{
"map": {
"height": 1448,
"num_land_tiles": 2644620,
"width": 2848
},
"map16x": {
"height": 362,
"num_land_tiles": 159261,
"width": 712
},
"map4x": {
"height": 724,
"num_land_tiles": 652563,
"width": 1424
},
"name": "mediterranean",
"nations": [
{
"coordinates": [141, 574],
"flag": "West Roman Empire",
"name": "Lusitania"
},
{
"coordinates": [464, 519],
"flag": "West Roman Empire",
"name": "Terraconensis"
},
{
"coordinates": [353, 787],
"flag": "West Roman Empire",
"name": "Baetica"
},
{
"coordinates": [699, 340],
"flag": "West Roman Empire",
"name": "Narbonensis"
},
{
"coordinates": [596, 91],
"flag": "West Roman Empire",
"name": "Aquitania"
},
{
"coordinates": [910, 218],
"flag": "West Roman Empire",
"name": "Alpes"
},
{
"coordinates": [1053, 39],
"flag": "West Roman Empire",
"name": "Raetia"
},
{
"coordinates": [1316, 88],
"flag": "West Roman Empire",
"name": "Noricum"
},
{
"coordinates": [1307, 479],
"flag": "West Roman Empire",
"name": "Italia"
},
{
"coordinates": [1058, 534],
"flag": "West Roman Empire",
"name": "Corsica et Sardinia"
},
{
"coordinates": [1350, 762],
"flag": "West Roman Empire",
"name": "Sicilia"
},
{
"coordinates": [1553, 42],
"flag": "West Roman Empire",
"name": "Pannonia"
},
{
"coordinates": [1522, 301],
"flag": "West Roman Empire",
"name": "Dalmatia"
},
{
"coordinates": [1910, 119],
"flag": "West Roman Empire",
"name": "Dacia"
},
{
"coordinates": [1811, 402],
"flag": "West Roman Empire",
"name": "Macedonia"
},
{
"coordinates": [1654, 560],
"flag": "West Roman Empire",
"name": "Epirus"
},
{
"coordinates": [1774, 774],
"flag": "West Roman Empire",
"name": "Achaia"
},
{
"coordinates": [2030, 457],
"flag": "West Roman Empire",
"name": "Thracia"
},
{
"coordinates": [2114, 250],
"flag": "West Roman Empire",
"name": "Moesia"
},
{
"coordinates": [2428, 200],
"name": "Bosporan Kingdom",
"flag": ""
},
{
"coordinates": [2065, 690],
"flag": "West Roman Empire",
"name": "Asia"
},
{
"coordinates": [2354, 505],
"flag": "West Roman Empire",
"name": "Pontus"
},
{
"coordinates": [2325, 669],
"flag": "West Roman Empire",
"name": "Galatia"
},
{
"coordinates": [2447, 809],
"flag": "West Roman Empire",
"name": "Cilicia"
},
{
"coordinates": [2196, 828],
"flag": "West Roman Empire",
"name": "Lycia"
},
{
"coordinates": [2379, 942],
"flag": "West Roman Empire",
"name": "Cyprus"
},
{
"coordinates": [2516, 648],
"flag": "West Roman Empire",
"name": "Cappadocia"
},
{
"coordinates": [2772, 845],
"flag": "West Roman Empire",
"name": "Mesopotamia"
},
{
"coordinates": [2584, 990],
"flag": "West Roman Empire",
"name": "Syria"
},
{
"coordinates": [2490, 1141],
"flag": "West Roman Empire",
"name": "Judaea"
},
{
"coordinates": [2481, 1292],
"flag": "West Roman Empire",
"name": "Arabia Petraea"
},
{
"coordinates": [2263, 1240],
"flag": "West Roman Empire",
"name": "Aegyptus"
},
{
"coordinates": [1799, 1096],
"flag": "West Roman Empire",
"name": "Cyrenaica et Creta"
},
{
"coordinates": [2815, 659],
"flag": "Sassanid Empire",
"name": "Sassanid Empire"
},
{
"coordinates": [1091, 989],
"flag": "West Roman Empire",
"name": "Africa Proconsularis"
},
{
"coordinates": [597, 910],
"flag": "West Roman Empire",
"name": "Mauretania Caesariensis"
},
{
"coordinates": [264, 1028],
"flag": "West Roman Empire",
"name": "Mauretania Tingitania"
},
{
"coordinates": [584, 1199],
"flag": "Amazigh flag",
"name": "Numidia"
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+74 -2
View File
@@ -25,6 +25,16 @@ declare global {
},
) => void;
};
banner: {
requestBanner: (options: {
id: string;
width: number;
height: number;
}) => Promise<void>;
requestResponsiveBanner: (containerId: string) => Promise<void>;
clearBanner: (containerId: string) => void;
clearAllBanners: () => void;
};
game: {
gameplayStart: () => Promise<void>;
gameplayStop: () => Promise<void>;
@@ -76,11 +86,9 @@ export class CrazyGamesSDK {
}
return false;
} catch (e) {
console.log("[CrazyGames]: ", e);
// If we get a cross-origin error, we're definitely iframed
// Check our own referrer as fallback
const isCrazyGames = document.referrer.includes("crazygames");
console.log("[CrazyGames], contains referrer: ", isCrazyGames);
if (isCrazyGames) {
return true;
}
@@ -323,6 +331,70 @@ export class CrazyGamesSDK {
}
}
private bottomLeftContainerId = "cg-bottom-left-ad";
private bottomLeftAdVisible = false;
createBottomLeftAd(): void {
console.log(
`[CrazyGames] createBottomLeftAd called, isReady=${this.isReady()}`,
);
if (!this.isReady()) {
console.log("[CrazyGames] SDK not ready, skipping bottom-left ad");
return;
}
if (this.bottomLeftAdVisible) {
console.log("[CrazyGames] Bottom-left ad already visible");
return;
}
// Remove existing container if any
document.getElementById(this.bottomLeftContainerId)?.remove();
const container = document.createElement("div");
container.id = this.bottomLeftContainerId;
container.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
width: 300px;
height: 250px;
z-index: 9999;
pointer-events: auto;
`;
document.body.appendChild(container);
console.log("[CrazyGames] Created bottom-left ad container");
(async () => {
try {
await window.CrazyGames!.SDK.banner.requestBanner({
id: this.bottomLeftContainerId,
width: 300,
height: 250,
});
console.log("[CrazyGames] Bottom-left banner loaded");
} catch (e) {
console.log("[CrazyGames] Bottom-left banner error:", e);
}
})();
this.bottomLeftAdVisible = true;
}
clearBottomLeftAd(): void {
if (!this.bottomLeftAdVisible) return;
try {
window.CrazyGames!.SDK.banner.clearBanner(this.bottomLeftContainerId);
} catch (e) {
console.error("[CrazyGames] Error clearing bottom-left banner:", e);
}
document.getElementById(this.bottomLeftContainerId)?.remove();
this.bottomLeftAdVisible = false;
console.log("[CrazyGames] Bottom-left ad cleared");
}
requestMidgameAd(): Promise<void> {
return new Promise((resolve) => {
if (!this.isReady()) {
+13
View File
@@ -10,6 +10,7 @@ import { fetchCosmetics, flagRelationship } from "./Cosmetics";
import { translateText } from "./Utils";
import { BaseModal } from "./components/BaseModal";
import "./components/FlagButton";
import "./components/NotLoggedInWarning";
import { modalHeader } from "./components/ui/ModalHeader";
@customElement("flag-input-modal")
@@ -104,6 +105,7 @@ export class FlagInputModal extends BaseModal {
title: translateText("flag_input.title"),
onBack: () => this.close(),
ariaLabel: translateText("common.back"),
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
})}
<div class="md:flex items-center gap-2 justify-center mt-4">
@@ -119,6 +121,17 @@ export class FlagInputModal extends BaseModal {
/>
</div>
</div>
<div class="flex justify-center py-3 shrink-0">
<button
class="px-4 py-2 text-sm font-bold uppercase tracking-wider rounded-lg bg-blue-600 hover:bg-blue-700 text-white cursor-pointer transition-colors"
@click=${() => {
this.close();
window.showPage?.("page-item-store");
}}
>
${translateText("main.store")}
</button>
</div>
<div
class="flex-1 overflow-y-auto px-3 pb-3 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent mr-1"
+70 -4
View File
@@ -266,6 +266,19 @@ export class InputHandler {
this.eventBus.emit(new MouseMoveEvent(e.clientX, e.clientY));
}
});
// Clear all tracked keys when the window loses focus so keys that had
// their keyup swallowed by the browser (e.g. cmd+zoom) don't stay stuck.
// Also release the hold-to-view state and any active pointer/drag state
// so the alternate view and drags aren't left latched when focus returns.
window.addEventListener("blur", () => {
this.activeKeys.clear();
if (this.alternateView) {
this.alternateView = false;
this.eventBus.emit(new AlternateViewEvent(false));
}
this.pointerDown = false;
this.pointers.clear();
});
this.pointers.clear();
this.moveInterval = setInterval(() => {
@@ -310,13 +323,15 @@ export class InputHandler {
if (
this.activeKeys.has(this.keybinds.zoomOut) ||
this.activeKeys.has("Minus")
this.activeKeys.has("Minus") ||
this.activeKeys.has("NumpadSubtract")
) {
this.eventBus.emit(new ZoomEvent(cx, cy, this.ZOOM_SPEED));
}
if (
this.activeKeys.has(this.keybinds.zoomIn) ||
this.activeKeys.has("Equal")
this.activeKeys.has("Equal") ||
this.activeKeys.has("NumpadAdd")
) {
this.eventBus.emit(new ZoomEvent(cx, cy, -this.ZOOM_SPEED));
}
@@ -358,7 +373,19 @@ export class InputHandler {
this.eventBus.emit(new ConfirmGhostStructureEvent());
}
// Don't track zoom keys when a meta/ctrl modifier is held — that means
// the browser is handling its own zoom (cmd+/cmd-) and the keyup will
// never fire, which would leave the key stuck in activeKeys forever.
// Also covers numpad zoom shortcuts (Ctrl+NumpadAdd/NumpadSubtract).
const isBrowserZoomCombo =
(e.metaKey || e.ctrlKey) &&
(e.code === "Minus" ||
e.code === "Equal" ||
e.code === "NumpadAdd" ||
e.code === "NumpadSubtract");
if (
!isBrowserZoomCombo &&
[
this.keybinds.moveUp,
this.keybinds.moveDown,
@@ -372,6 +399,8 @@ export class InputHandler {
"ArrowRight",
"Minus",
"Equal",
"NumpadAdd",
"NumpadSubtract",
this.keybinds.attackRatioDown,
this.keybinds.attackRatioUp,
this.keybinds.centerCamera,
@@ -390,6 +419,24 @@ export class InputHandler {
return;
}
// When the meta (cmd) or ctrl key is released, any keys that were held
// simultaneously will have had their keyup swallowed by the browser
// (e.g. cmd+Plus for browser zoom). Clear zoom-related keys to
// prevent them staying stuck in activeKeys.
if (
e.code === "MetaLeft" ||
e.code === "MetaRight" ||
e.code === "ControlLeft" ||
e.code === "ControlRight"
) {
this.activeKeys.delete("Minus");
this.activeKeys.delete("Equal");
this.activeKeys.delete("NumpadAdd");
this.activeKeys.delete("NumpadSubtract");
this.activeKeys.delete(this.keybinds.zoomIn);
this.activeKeys.delete(this.keybinds.zoomOut);
}
if (e.code === this.keybinds.toggleView) {
e.preventDefault();
this.alternateView = false;
@@ -537,8 +584,27 @@ export class InputHandler {
const realCtrl =
this.activeKeys.has("ControlLeft") ||
this.activeKeys.has("ControlRight");
const ratio = event.ctrlKey && !realCtrl ? 10 : 1; // Compensate pinch-zoom low sensitivity
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY * ratio));
if (event.ctrlKey) {
if (!realCtrl) {
// Pinch-to-zoom gesture (trackpad): small deltas, amplify.
// Ignore large deltas — those are browser zoom shortcuts (cmd+/cmd-)
// which fire synthetic wheel events we don't want to handle.
if (Math.abs(event.deltaY) <= 10) {
this.eventBus.emit(
new ZoomEvent(event.x, event.y, event.deltaY * 10),
);
}
}
// Always return when ctrlKey is set — whether it's a real ctrl scroll,
// a pinch gesture, or a browser zoom event, none should reach the
// regular scroll path below.
return;
}
// Regular scroll wheel: ignore tiny residual momentum events that macOS
// keeps sending after a gesture ends (especially after browser zoom changes
// devicePixelRatio, which can cause these to accumulate into runaway zoom).
if (Math.abs(event.deltaY) < 2) return;
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY));
}
}
+2 -10
View File
@@ -101,17 +101,9 @@ function updateAccountNavButton(userMeResponse: UserMeResponse | false) {
// If the avatar fails to load (bad URL / CDN issue / offline), fall back
// to the default sign-in UI instead of leaving a broken image.
avatarEl.onerror = () => {
// Only handle if this is the latest update
if (avatarEl._navToken !== navToken) return;
avatarEl.src = "";
// If the user is still logged in via email, show the email badge state.
const email =
userMeResponse !== false ? userMeResponse.user.email : undefined;
if (email) {
showEmailLoggedIn();
} else {
showSignIn();
}
avatarEl.onerror = null;
avatarEl.src = "https://cdn.discordapp.com/embed/avatars/0.png";
};
avatarEl.onload = () => {
// Only handle if this is the latest update
+12 -3
View File
@@ -19,11 +19,20 @@ export function initNavigation() {
// Close mobile sidebar if a nav item was clicked
closeMobileSidebar();
// Hide only the currently visible modal
// Close the currently visible modal properly
const visibleModal = document.querySelector(".page-content:not(.hidden)");
if (visibleModal) {
visibleModal.classList.add("hidden");
visibleModal.classList.remove("block");
// If it's an open modal component, call close() for proper cleanup (onClose callback, etc.)
if (
typeof (visibleModal as any).isOpen === "function" &&
(visibleModal as any).isOpen() &&
typeof (visibleModal as any).close === "function"
) {
(visibleModal as any).close();
} else {
visibleModal.classList.add("hidden");
visibleModal.classList.remove("block");
}
}
// Handle page-play separately (it's not a page-content element)
+1 -8
View File
@@ -145,14 +145,7 @@ export class SinglePlayerModal extends BaseModal {
return;
}
const achievements = Array.isArray(userMe.player.achievements)
? userMe.player.achievements
: [];
const completions =
achievements.find(
(achievement) => achievement?.type === "singleplayer-map",
)?.data ?? [];
const completions = userMe.player.achievements.singleplayerMap;
const winsMap = new Map<GameMapType, Set<Difficulty>>();
for (const entry of completions) {
+2 -18
View File
@@ -5,9 +5,9 @@ import { UserMeResponse } from "../core/ApiSchemas";
import { ColorPalette, Cosmetics, Pattern } from "../core/CosmeticSchemas";
import { UserSettings } from "../core/game/UserSettings";
import { PlayerPattern } from "../core/Schemas";
import { hasLinkedAccount } from "./Api";
import { BaseModal } from "./components/BaseModal";
import "./components/FlagButton";
import "./components/NotLoggedInWarning";
import "./components/PatternButton";
import { modalHeader } from "./components/ui/ModalHeader";
import {
@@ -77,11 +77,7 @@ export class StoreModal extends BaseModal {
title: translateText("store.title"),
onBack: () => this.close(),
ariaLabel: translateText("common.back"),
rightContent: !hasLinkedAccount(this.userMeResponse)
? html`<div class="flex items-center">
${this.renderNotLoggedInWarning()}
</div>`
: undefined,
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
})}
<div class="flex items-center gap-2 justify-center pt-2">
<button
@@ -214,18 +210,6 @@ export class StoreModal extends BaseModal {
`;
}
private renderNotLoggedInWarning(): TemplateResult {
return html`<button
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
@click=${() => {
this.close();
window.showPage?.("page-account");
}}
>
${translateText("territory_patterns.not_logged_in")}
</button>`;
}
render() {
if (!this.isActive && !this.inline) return html``;
+13 -18
View File
@@ -5,8 +5,8 @@ import { UserMeResponse } from "../core/ApiSchemas";
import { Cosmetics, Pattern } from "../core/CosmeticSchemas";
import { UserSettings } from "../core/game/UserSettings";
import { PlayerPattern } from "../core/Schemas";
import { hasLinkedAccount } from "./Api";
import { BaseModal } from "./components/BaseModal";
import "./components/NotLoggedInWarning";
import "./components/PatternButton";
import { modalHeader } from "./components/ui/ModalHeader";
import {
@@ -123,18 +123,6 @@ export class TerritoryPatternsModal extends BaseModal {
`;
}
private renderNotLoggedInWarning(): TemplateResult {
return html`<button
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
@click=${() => {
this.close();
window.showPage?.("page-account");
}}
>
${translateText("territory_patterns.not_logged_in")}
</button>`;
}
render() {
const content = html`
<div class="${this.modalContainerClass}">
@@ -145,13 +133,20 @@ export class TerritoryPatternsModal extends BaseModal {
title: translateText("territory_patterns.title"),
onBack: () => this.close(),
ariaLabel: translateText("common.back"),
rightContent: !hasLinkedAccount(this.userMeResponse)
? html`<div class="flex items-center">
${this.renderNotLoggedInWarning()}
</div>`
: undefined,
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
})}
</div>
<div class="flex justify-center py-3 shrink-0">
<button
class="px-4 py-2 text-sm font-bold uppercase tracking-wider rounded-lg bg-blue-600 hover:bg-blue-700 text-white cursor-pointer transition-colors"
@click=${() => {
this.close();
window.showPage?.("page-item-store");
}}
>
${translateText("main.store")}
</button>
</div>
<div
class="flex-1 overflow-y-auto px-3 pb-3 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent mr-1"
>
@@ -0,0 +1,49 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../../core/ApiSchemas";
import { hasLinkedAccount } from "../Api";
@customElement("not-logged-in-warning")
export class NotLoggedInWarning extends LitElement {
@state() private linked = false;
private _onUserMe = (event: CustomEvent<UserMeResponse | false>) => {
this.linked = hasLinkedAccount(event.detail);
};
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
document.addEventListener(
"userMeResponse",
this._onUserMe as EventListener,
);
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener(
"userMeResponse",
this._onUserMe as EventListener,
);
}
render() {
if (this.linked) return html``;
return html`<div class="flex items-center">
<button
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
data-i18n="common.not_logged_in"
@click=${() => {
window.showPage?.("page-account");
}}
>
Not logged in
</button>
</div>`;
}
}
@@ -31,15 +31,20 @@ export class DiscordUserHeader extends LitElement {
}
render() {
const defaultAvatar = "https://cdn.discordapp.com/embed/avatars/0.png";
const imgSrc = this.avatarUrl ?? defaultAvatar;
return html`
<div class="flex items-center gap-2">
${this.avatarUrl
${this._data
? html`
<div class="p-[3px] rounded-full bg-gray-500">
<img
class="w-12 h-12 rounded-full block"
src="${this.avatarUrl}"
src="${imgSrc}"
alt="${translateText("discord_user_header.avatar_alt")}"
@error=${(e: Event) => {
(e.target as HTMLImageElement).src = defaultAvatar;
}}
/>
</div>
`
+39 -1
View File
@@ -1,6 +1,7 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { GameView } from "../../../core/game/GameView";
import { crazyGamesSDK } from "../../CrazyGamesSDK";
import { Layer } from "./Layer";
const AD_TYPE = "standard_iab_left1";
@@ -30,6 +31,7 @@ export class InGamePromo extends LitElement implements Layer {
}
if (!this.cornerAdShown) {
this.cornerAdShown = true;
console.log("[InGamePromo] Spawn phase ended, triggering showAd");
this.showAd();
}
}
@@ -73,10 +75,19 @@ export class InGamePromo extends LitElement implements Layer {
}
private showAd(): void {
if (!window.adsEnabled) return;
console.log(
`[InGamePromo] showAd called, isOnCrazyGames=${crazyGamesSDK.isOnCrazyGames()}`,
);
if (window.innerWidth < 1100) return;
if (window.innerHeight < 750) return;
if (crazyGamesSDK.isOnCrazyGames()) {
this.showCrazyGamesAd();
return;
}
if (!window.adsEnabled) return;
this.shouldShow = true;
this.requestUpdate();
@@ -85,6 +96,25 @@ export class InGamePromo extends LitElement implements Layer {
});
}
private showCrazyGamesAd(): void {
console.log(
`[InGamePromo] showCrazyGamesAd called, isReady=${crazyGamesSDK.isReady()}, width=${window.innerWidth}, height=${window.innerHeight}`,
);
if (!crazyGamesSDK.isReady()) {
console.log(
"[InGamePromo] CrazyGames SDK not ready, skipping in-game ad",
);
return;
}
this.requestUpdate();
this.updateComplete.then(() => {
console.log("[InGamePromo] DOM updated, calling createBottomLeftAd");
crazyGamesSDK.createBottomLeftAd();
});
}
private loadAd(): void {
if (!window.ramp) {
console.warn("Playwire RAMP not available for in-game ad");
@@ -112,6 +142,14 @@ export class InGamePromo extends LitElement implements Layer {
public hideAd(): void {
this.destroyBottomRail();
if (crazyGamesSDK.isOnCrazyGames()) {
crazyGamesSDK.clearBottomLeftAd();
this.shouldShow = false;
this.requestUpdate();
return;
}
if (!window.ramp) {
console.warn("Playwire RAMP not available for in-game ad");
return;
+3 -8
View File
@@ -69,14 +69,9 @@ export const UserMeResponseSchema = z.object({
publicId: z.string(),
roles: z.string().array().optional(),
flares: z.string().array().optional(),
achievements: z
.array(
z.object({
type: z.literal("singleplayer-map"), // TODO: change the shape to be more flexible when we have more achievements
data: z.array(SingleplayerMapAchievementSchema),
}),
)
.optional(),
achievements: z.object({
singleplayerMap: z.array(SingleplayerMapAchievementSchema),
}),
leaderboard: z
.object({
oneVone: z
+2
View File
@@ -144,6 +144,7 @@ export enum GameMapType {
SanFrancisco = "San Francisco",
Aegean = "Aegean",
MilkyWay = "MilkyWay",
Mediterranean = "Mediterranean",
}
export type GameMapName = keyof typeof GameMapType;
@@ -195,6 +196,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.Arctic,
GameMapType.SanFrancisco,
GameMapType.Aegean,
GameMapType.Mediterranean,
],
fantasy: [
GameMapType.Pangaea,
+1
View File
@@ -85,6 +85,7 @@ const frequency: Partial<Record<GameMapName, number>> = {
SanFrancisco: 3,
Aegean: 6,
MilkyWay: 8,
Mediterranean: 6,
};
const TEAM_WEIGHTS: { config: TeamCountConfig; weight: number }[] = [