mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:20:43 +00:00
Merge branch 'v26'
This commit is contained in:
@@ -40,7 +40,6 @@ export default [
|
||||
rules: {
|
||||
// Disable rules that would fail. The failures should be fixed, and the entries here removed.
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "off",
|
||||
"no-unused-vars": "off",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -426,13 +426,6 @@ export class ClientGameRunner {
|
||||
} else if (this.canAutoBoat(actions, tile)) {
|
||||
this.sendBoatAttackIntent(tile);
|
||||
}
|
||||
|
||||
const owner = this.gameView.owner(tile);
|
||||
if (owner.isPlayer()) {
|
||||
this.gameView.setFocusedPlayer(owner as PlayerView);
|
||||
} else {
|
||||
this.gameView.setFocusedPlayer(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -612,45 +605,6 @@ export class ClientGameRunner {
|
||||
|
||||
private onMouseMove(event: MouseMoveEvent) {
|
||||
this.lastMousePosition = { x: event.x, y: event.y };
|
||||
this.checkTileUnderCursor();
|
||||
}
|
||||
|
||||
private checkTileUnderCursor() {
|
||||
if (!this.lastMousePosition || !this.renderer.transformHandler) return;
|
||||
|
||||
const cell = this.renderer.transformHandler.screenToWorldCoordinates(
|
||||
this.lastMousePosition.x,
|
||||
this.lastMousePosition.y,
|
||||
);
|
||||
|
||||
if (!cell || !this.gameView.isValidCoord(cell.x, cell.y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tile = this.gameView.ref(cell.x, cell.y);
|
||||
|
||||
if (this.gameView.isLand(tile)) {
|
||||
const owner = this.gameView.owner(tile);
|
||||
if (owner.isPlayer()) {
|
||||
this.gameView.setFocusedPlayer(owner as PlayerView);
|
||||
} else {
|
||||
this.gameView.setFocusedPlayer(null);
|
||||
}
|
||||
} else {
|
||||
const units = this.gameView
|
||||
.nearbyUnits(tile, 50, [
|
||||
UnitType.Warship,
|
||||
UnitType.TradeShip,
|
||||
UnitType.TransportShip,
|
||||
])
|
||||
.sort((a, b) => a.distSquared - b.distSquared);
|
||||
|
||||
if (units.length > 0) {
|
||||
this.gameView.setFocusedPlayer(units[0].unit.owner() as PlayerView);
|
||||
} else {
|
||||
this.gameView.setFocusedPlayer(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onConnectionCheck() {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { isInIframe } from "./Utils";
|
||||
|
||||
const LEFT_FUSE = "gutter-ad-container-left";
|
||||
const RIGHT_FUSE = "gutter-ad-container-right";
|
||||
// Minimum screen width to show ads (larger than typical Chromebook)
|
||||
const MIN_SCREEN_WIDTH = 1400;
|
||||
|
||||
@customElement("gutter-ads")
|
||||
export class GutterAds extends LitElement {
|
||||
@state()
|
||||
private isVisible: boolean = false;
|
||||
|
||||
// Override createRenderRoot to disable shadow DOM
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private isScreenLargeEnough(): boolean {
|
||||
return window.innerWidth >= MIN_SCREEN_WIDTH;
|
||||
}
|
||||
|
||||
// Called after the component's DOM is first rendered
|
||||
firstUpdated() {
|
||||
// DOM is guaranteed to be available here
|
||||
console.log("GutterAd DOM is ready");
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (!this.isScreenLargeEnough()) {
|
||||
console.log("Screen too small for gutter ads, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInIframe()) {
|
||||
console.log("In iframe, showing gutter ads");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("showing GutterAds");
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
|
||||
// Wait for the update to complete, then load ads
|
||||
this.updateComplete.then(() => {
|
||||
this.loadAds();
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
console.log("hiding GutterAds");
|
||||
this.destroyAds();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private loadAds(): void {
|
||||
// Ensure the container elements exist before loading ads
|
||||
const leftContainer = this.querySelector(`#${LEFT_FUSE}`);
|
||||
const rightContainer = this.querySelector(`#${RIGHT_FUSE}`);
|
||||
|
||||
if (!leftContainer || !rightContainer) {
|
||||
console.warn("Ad containers not found in DOM");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.fusetag) {
|
||||
console.warn("Fuse tag not available");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("registering zones");
|
||||
window.fusetag.que.push(() => {
|
||||
window.fusetag.registerZone(LEFT_FUSE);
|
||||
window.fusetag.registerZone(RIGHT_FUSE);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load fuse ads:", error);
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private destroyAds(): void {
|
||||
if (!window.fusetag) {
|
||||
return;
|
||||
}
|
||||
window.fusetag.que.push(() => {
|
||||
window.fusetag.destroyZone(LEFT_FUSE);
|
||||
window.fusetag.destroyZone(RIGHT_FUSE);
|
||||
});
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.destroyAds();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="fixed left-0 top-1/2 -translate-y-1/2 z-10">
|
||||
<div id="${LEFT_FUSE}" data-fuse="lhs_sticky_vrec"></div>
|
||||
</div>
|
||||
<div class="fixed right-0 top-1/2 -translate-y-1/2 z-10">
|
||||
<div id="${RIGHT_FUSE}" data-fuse="rhs_sticky_vrec"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
+82
-18
@@ -15,6 +15,7 @@ import { FlagInput } from "./FlagInput";
|
||||
import { FlagInputModal } from "./FlagInputModal";
|
||||
import { GameStartingModal } from "./GameStartingModal";
|
||||
import "./GoogleAdElement";
|
||||
import { GutterAds } from "./GutterAds";
|
||||
import { HelpModal } from "./HelpModal";
|
||||
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
|
||||
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
|
||||
@@ -51,6 +52,12 @@ declare global {
|
||||
newPageView: () => void;
|
||||
};
|
||||
};
|
||||
fusetag: {
|
||||
registerZone: (id: string) => void;
|
||||
destroyZone: (id: string) => void;
|
||||
pageInit: (options?: any) => void;
|
||||
que: Array<() => void>;
|
||||
};
|
||||
ramp: {
|
||||
que: Array<() => void>;
|
||||
passiveMode: boolean;
|
||||
@@ -94,6 +101,8 @@ class Client {
|
||||
private patternsModal: TerritoryPatternsModal;
|
||||
private tokenLoginModal: TokenLoginModal;
|
||||
|
||||
private gutterAds: GutterAds;
|
||||
|
||||
constructor() {}
|
||||
|
||||
initialize(): void {
|
||||
@@ -106,10 +115,9 @@ class Client {
|
||||
gameVersion.innerText = version;
|
||||
|
||||
const newsModal = document.querySelector("news-modal") as NewsModal;
|
||||
if (!newsModal) {
|
||||
if (!newsModal || !(newsModal instanceof NewsModal)) {
|
||||
console.warn("News modal element not found");
|
||||
}
|
||||
newsModal instanceof NewsModal;
|
||||
const newsButton = document.querySelector("news-button") as NewsButton;
|
||||
if (!newsButton) {
|
||||
console.warn("News button element not found");
|
||||
@@ -161,6 +169,11 @@ class Client {
|
||||
}
|
||||
});
|
||||
|
||||
const gutterAds = document.querySelector("gutter-ads");
|
||||
if (!(gutterAds instanceof GutterAds))
|
||||
throw new Error("Missing gutter-ads");
|
||||
this.gutterAds = gutterAds;
|
||||
|
||||
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
|
||||
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
|
||||
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
|
||||
@@ -168,7 +181,9 @@ class Client {
|
||||
const spModal = document.querySelector(
|
||||
"single-player-modal",
|
||||
) as SinglePlayerModal;
|
||||
spModal instanceof SinglePlayerModal;
|
||||
if (!spModal || !(spModal instanceof SinglePlayerModal)) {
|
||||
console.warn("Singleplayer modal element not found");
|
||||
}
|
||||
|
||||
const singlePlayer = document.getElementById("single-player");
|
||||
if (singlePlayer === null) throw new Error("Missing single-player");
|
||||
@@ -178,14 +193,10 @@ class Client {
|
||||
}
|
||||
});
|
||||
|
||||
// const ctModal = document.querySelector("chat-modal") as ChatModal;
|
||||
// ctModal instanceof ChatModal;
|
||||
// document.getElementById("chat-button").addEventListener("click", () => {
|
||||
// ctModal.open();
|
||||
// });
|
||||
|
||||
const hlpModal = document.querySelector("help-modal") as HelpModal;
|
||||
hlpModal instanceof HelpModal;
|
||||
if (!hlpModal || !(hlpModal instanceof HelpModal)) {
|
||||
console.warn("Help modal element not found");
|
||||
}
|
||||
const helpButton = document.getElementById("help-button");
|
||||
if (helpButton === null) throw new Error("Missing help-button");
|
||||
helpButton.addEventListener("click", () => {
|
||||
@@ -195,7 +206,10 @@ class Client {
|
||||
const flagInputModal = document.querySelector(
|
||||
"flag-input-modal",
|
||||
) as FlagInputModal;
|
||||
flagInputModal instanceof FlagInputModal;
|
||||
if (!flagInputModal || !(flagInputModal instanceof FlagInputModal)) {
|
||||
console.warn("Flag input modal element not found");
|
||||
}
|
||||
|
||||
const flgInput = document.getElementById("flag-input_");
|
||||
if (flgInput === null) throw new Error("Missing flag-input_");
|
||||
flgInput.addEventListener("click", () => {
|
||||
@@ -205,6 +219,12 @@ class Client {
|
||||
this.patternsModal = document.querySelector(
|
||||
"territory-patterns-modal",
|
||||
) as TerritoryPatternsModal;
|
||||
if (
|
||||
!this.patternsModal ||
|
||||
!(this.patternsModal instanceof TerritoryPatternsModal)
|
||||
) {
|
||||
console.warn("Territory patterns modal element not found");
|
||||
}
|
||||
const patternButton = document.getElementById(
|
||||
"territory-patterns-input-preview-button",
|
||||
);
|
||||
@@ -212,7 +232,12 @@ class Client {
|
||||
patternButton.style.display = "none";
|
||||
}
|
||||
|
||||
this.patternsModal instanceof TerritoryPatternsModal;
|
||||
if (
|
||||
!this.patternsModal ||
|
||||
!(this.patternsModal instanceof TerritoryPatternsModal)
|
||||
) {
|
||||
console.warn("Territory patterns modal element not found");
|
||||
}
|
||||
if (patternButton === null)
|
||||
throw new Error("territory-patterns-input-preview-button");
|
||||
this.patternsModal.previewButton = patternButton;
|
||||
@@ -224,7 +249,12 @@ class Client {
|
||||
this.tokenLoginModal = document.querySelector(
|
||||
"token-login",
|
||||
) as TokenLoginModal;
|
||||
this.tokenLoginModal instanceof TokenLoginModal;
|
||||
if (
|
||||
!this.tokenLoginModal ||
|
||||
!(this.tokenLoginModal instanceof TokenLoginModal)
|
||||
) {
|
||||
console.warn("Token login modal element not found");
|
||||
}
|
||||
|
||||
const onUserMe = async (userMeResponse: UserMeResponse | false) => {
|
||||
document.dispatchEvent(
|
||||
@@ -335,7 +365,9 @@ class Client {
|
||||
const settingsModal = document.querySelector(
|
||||
"user-setting",
|
||||
) as UserSettingModal;
|
||||
settingsModal instanceof UserSettingModal;
|
||||
if (!settingsModal || !(settingsModal instanceof UserSettingModal)) {
|
||||
console.warn("User settings modal element not found");
|
||||
}
|
||||
document
|
||||
.getElementById("settings-button")
|
||||
?.addEventListener("click", () => {
|
||||
@@ -345,7 +377,9 @@ class Client {
|
||||
const hostModal = document.querySelector(
|
||||
"host-lobby-modal",
|
||||
) as HostPrivateLobbyModal;
|
||||
hostModal instanceof HostPrivateLobbyModal;
|
||||
if (!hostModal || !(hostModal instanceof HostPrivateLobbyModal)) {
|
||||
console.warn("Host private lobby modal element not found");
|
||||
}
|
||||
const hostLobbyButton = document.getElementById("host-lobby-button");
|
||||
if (hostLobbyButton === null) throw new Error("Missing host-lobby-button");
|
||||
hostLobbyButton.addEventListener("click", () => {
|
||||
@@ -358,7 +392,9 @@ class Client {
|
||||
this.joinModal = document.querySelector(
|
||||
"join-private-lobby-modal",
|
||||
) as JoinPrivateLobbyModal;
|
||||
this.joinModal instanceof JoinPrivateLobbyModal;
|
||||
if (!this.joinModal || !(this.joinModal instanceof JoinPrivateLobbyModal)) {
|
||||
console.warn("Join private lobby modal element not found");
|
||||
}
|
||||
const joinPrivateLobbyButton = document.getElementById(
|
||||
"join-private-lobby-button",
|
||||
);
|
||||
@@ -410,6 +446,8 @@ class Client {
|
||||
updateSliderProgress(slider);
|
||||
slider.addEventListener("input", () => updateSliderProgress(slider));
|
||||
});
|
||||
|
||||
this.initializeFuseTag();
|
||||
}
|
||||
|
||||
private handleHash() {
|
||||
@@ -573,8 +611,10 @@ class Client {
|
||||
const startingModal = document.querySelector(
|
||||
"game-starting-modal",
|
||||
) as GameStartingModal;
|
||||
startingModal instanceof GameStartingModal;
|
||||
startingModal.show();
|
||||
if (startingModal && startingModal instanceof GameStartingModal) {
|
||||
startingModal.show();
|
||||
}
|
||||
this.gutterAds.hide();
|
||||
},
|
||||
() => {
|
||||
this.joinModal.close();
|
||||
@@ -607,6 +647,7 @@ class Client {
|
||||
console.log("leaving lobby, cancelling game");
|
||||
this.gameStop();
|
||||
this.gameStop = null;
|
||||
this.gutterAds.hide();
|
||||
this.publicLobby.leaveLobby();
|
||||
}
|
||||
|
||||
@@ -618,6 +659,29 @@ class Client {
|
||||
this.eventBus.emit(new SendKickPlayerIntentEvent(target));
|
||||
}
|
||||
}
|
||||
|
||||
private initializeFuseTag() {
|
||||
const tryInitFuseTag = (): boolean => {
|
||||
if (window.fusetag && typeof window.fusetag.pageInit === "function") {
|
||||
console.log("initializing fuse tag");
|
||||
window.fusetag.que.push(() => {
|
||||
window.fusetag.pageInit({
|
||||
blockingFuseIds: ["lhs_sticky_vrec", "rhs_sticky_vrec"],
|
||||
});
|
||||
this.gutterAds.show();
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (tryInitFuseTag()) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the client when the DOM is loaded
|
||||
|
||||
@@ -16,7 +16,6 @@ import { FPSDisplay } from "./layers/FPSDisplay";
|
||||
import { FxLayer } from "./layers/FxLayer";
|
||||
import { GameLeftSidebar } from "./layers/GameLeftSidebar";
|
||||
import { GameRightSidebar } from "./layers/GameRightSidebar";
|
||||
import { GutterAdModal } from "./layers/GutterAdModal";
|
||||
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
|
||||
import { Layer } from "./layers/Layer";
|
||||
import { Leaderboard } from "./layers/Leaderboard";
|
||||
@@ -216,14 +215,6 @@ export function createRenderer(
|
||||
}
|
||||
spawnAd.g = game;
|
||||
|
||||
const gutterAdModal = document.querySelector(
|
||||
"gutter-ad-modal",
|
||||
) as GutterAdModal;
|
||||
if (!(gutterAdModal instanceof GutterAdModal)) {
|
||||
console.error("gutter ad modal not found");
|
||||
}
|
||||
gutterAdModal.eventBus = eventBus;
|
||||
|
||||
const alertFrame = document.querySelector("alert-frame") as AlertFrame;
|
||||
if (!(alertFrame instanceof AlertFrame)) {
|
||||
console.error("alert frame not found");
|
||||
@@ -270,7 +261,6 @@ export function createRenderer(
|
||||
headsUpMessage,
|
||||
multiTabModal,
|
||||
spawnAd,
|
||||
gutterAdModal,
|
||||
alertFrame,
|
||||
fpsDisplay,
|
||||
];
|
||||
|
||||
@@ -158,7 +158,6 @@ export class TransformHandler {
|
||||
}
|
||||
|
||||
onGoToPlayer(event: GoToPlayerEvent) {
|
||||
this.game.setFocusedPlayer(event.player);
|
||||
this.clearTarget();
|
||||
const nameLocation = event.player.nameLocation();
|
||||
if (!nameLocation) {
|
||||
|
||||
@@ -1051,7 +1051,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
? this.renderButton({
|
||||
content: this.getEventDescription(event),
|
||||
onClick: () => {
|
||||
event.focusID &&
|
||||
if (event.focusID)
|
||||
this.emitGoToPlayerEvent(event.focusID);
|
||||
},
|
||||
className: "text-left",
|
||||
@@ -1060,7 +1060,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
? this.renderButton({
|
||||
content: this.getEventDescription(event),
|
||||
onClick: () => {
|
||||
event.unitView &&
|
||||
if (event.unitView)
|
||||
this.emitGoToUnitEvent(
|
||||
event.unitView,
|
||||
);
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
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 {
|
||||
constructor(public readonly isVisible: boolean) {}
|
||||
}
|
||||
|
||||
@customElement("gutter-ad-modal")
|
||||
export class GutterAdModal extends LitElement implements Layer {
|
||||
public eventBus: EventBus;
|
||||
|
||||
@state()
|
||||
private isVisible: boolean = false;
|
||||
|
||||
@state()
|
||||
private adLoaded: boolean = false;
|
||||
|
||||
private leftAdType: string = "left_rail";
|
||||
private rightAdType: string = "right_rail";
|
||||
private leftContainerId: string = "gutter-ad-container-left";
|
||||
private rightContainerId: string = "gutter-ad-container-right";
|
||||
private margin: string = "10px";
|
||||
|
||||
// Override createRenderRoot to disable shadow DOM
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (getGamesPlayed() > 1) {
|
||||
this.eventBus.on(GutterAdModalEvent, (event) => {
|
||||
if (event.isVisible) {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tick() {}
|
||||
|
||||
static styles = css``;
|
||||
|
||||
// Called after the component's DOM is first rendered
|
||||
firstUpdated() {
|
||||
// DOM is guaranteed to be available here
|
||||
console.log("GutterAdModal DOM is ready");
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
console.log("showing GutterAdModal");
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
|
||||
// Wait for the update to complete, then load ads
|
||||
this.updateComplete.then(() => {
|
||||
this.loadAds();
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
console.log("hiding GutterAdModal");
|
||||
this.isVisible = false;
|
||||
this.destroyAds();
|
||||
this.adLoaded = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private loadAds(): void {
|
||||
// Ensure the container elements exist before loading ads
|
||||
const leftContainer = this.querySelector(`#${this.leftContainerId}`);
|
||||
const rightContainer = this.querySelector(`#${this.rightContainerId}`);
|
||||
|
||||
if (!leftContainer || !rightContainer) {
|
||||
console.warn("Ad containers not found in DOM");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.ramp) {
|
||||
console.warn("Playwire RAMP not available");
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.adLoaded) {
|
||||
console.log("Ads already loaded, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.ramp.que.push(() => {
|
||||
window.ramp.spaAddAds([
|
||||
{
|
||||
type: this.leftAdType,
|
||||
selectorId: this.leftContainerId,
|
||||
},
|
||||
{
|
||||
type: this.rightAdType,
|
||||
selectorId: this.rightContainerId,
|
||||
},
|
||||
]);
|
||||
this.adLoaded = true;
|
||||
console.log("Playwire ads loaded:", this.leftAdType, this.rightAdType);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load Playwire ads:", error);
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private destroyAds(): void {
|
||||
if (!window.ramp || !this.adLoaded) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.ramp.destroyUnits("all");
|
||||
} catch (error) {
|
||||
console.error("Failed to destroy Playwire ad:", error);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.destroyAds();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<!-- Left Gutter Ad -->
|
||||
<div
|
||||
class="hidden xl:flex fixed left-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] z-[10] pointer-events-auto items-center justify-center"
|
||||
style="margin-left: ${this.margin};"
|
||||
>
|
||||
<div
|
||||
id="${this.leftContainerId}"
|
||||
class="w-full h-full flex items-center justify-center p-2"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Right Gutter Ad -->
|
||||
<div
|
||||
class="hidden xl:flex fixed right-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] z-[10] pointer-events-auto items-center justify-center"
|
||||
style="margin-right: ${this.margin};"
|
||||
>
|
||||
<div
|
||||
id="${this.rightContainerId}"
|
||||
class="w-full h-full flex items-center justify-center p-2"
|
||||
></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { GameType } from "../../../core/game/Game";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { UserSettings } from "../../../core/game/UserSettings";
|
||||
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
|
||||
import { PauseGameEvent } from "../../Transport";
|
||||
import { translateText } from "../../Utils";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
const button = ({
|
||||
classes = "",
|
||||
onClick = () => {},
|
||||
title = "",
|
||||
children = "",
|
||||
}) => html`
|
||||
<button
|
||||
class="flex items-center justify-center p-1
|
||||
bg-opacity-70 bg-gray-700 text-opacity-90 text-white
|
||||
border-none rounded cursor-pointer
|
||||
hover:bg-opacity-60 hover:bg-gray-600
|
||||
transition-colors duration-200
|
||||
text-sm lg:text-xl ${classes}"
|
||||
@click=${onClick}
|
||||
aria-label=${title}
|
||||
title=${title}
|
||||
>
|
||||
${children}
|
||||
</button>
|
||||
`;
|
||||
|
||||
const secondsToHms = (d: number): string => {
|
||||
const h = Math.floor(d / 3600);
|
||||
const m = Math.floor((d % 3600) / 60);
|
||||
const s = Math.floor((d % 3600) % 60);
|
||||
let time = d === 0 ? "-" : `${s}s`;
|
||||
if (m > 0) time = `${m}m` + time;
|
||||
if (h > 0) time = `${h}h` + time;
|
||||
return time;
|
||||
};
|
||||
|
||||
@customElement("options-menu")
|
||||
export class OptionsMenu extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
public eventBus: EventBus;
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
|
||||
@state()
|
||||
private showPauseButton: boolean = true;
|
||||
|
||||
@state()
|
||||
private isPaused: boolean = false;
|
||||
|
||||
@state()
|
||||
private timer: number = 0;
|
||||
|
||||
@state()
|
||||
private showSettings: boolean = false;
|
||||
|
||||
private isVisible = false;
|
||||
|
||||
private hasWinner = false;
|
||||
|
||||
@state()
|
||||
private alternateView: boolean = false;
|
||||
|
||||
private onTerrainButtonClick() {
|
||||
this.alternateView = !this.alternateView;
|
||||
this.eventBus.emit(new AlternateViewEvent(this.alternateView));
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onExitButtonClick() {
|
||||
const isAlive = this.game.myPlayer()?.isAlive();
|
||||
if (isAlive) {
|
||||
const isConfirmed = confirm(
|
||||
translateText("help_modal.exit_confirmation"),
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
}
|
||||
// redirect to the home page
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private onSettingsButtonClick() {
|
||||
this.showSettings = !this.showSettings;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onPauseButtonClick() {
|
||||
this.isPaused = !this.isPaused;
|
||||
this.eventBus.emit(new PauseGameEvent(this.isPaused));
|
||||
}
|
||||
|
||||
private onToggleEmojisButtonClick() {
|
||||
this.userSettings.toggleEmojis();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onToggleAlertFrameButtonClick() {
|
||||
this.userSettings.toggleAlertFrame();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onToggleSpecialEffectsButtonClick() {
|
||||
this.userSettings.toggleFxLayer();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onToggleDarkModeButtonClick() {
|
||||
this.userSettings.toggleDarkMode();
|
||||
this.requestUpdate();
|
||||
this.eventBus.emit(new RefreshGraphicsEvent());
|
||||
}
|
||||
|
||||
private onToggleRandomNameModeButtonClick() {
|
||||
this.userSettings.toggleRandomName();
|
||||
}
|
||||
|
||||
private onToggleFocusLockedButtonClick() {
|
||||
this.userSettings.toggleFocusLocked();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onToggleLeftClickOpensMenu() {
|
||||
this.userSettings.toggleLeftClickOpenMenu();
|
||||
}
|
||||
|
||||
private onToggleTerritoryPatterns() {
|
||||
this.userSettings.toggleTerritoryPatterns();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onTogglePerformanceOverlayButtonClick() {
|
||||
this.userSettings.togglePerformanceOverlay();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log("init called from OptionsMenu");
|
||||
this.showPauseButton =
|
||||
this.game.config().gameConfig().gameType === GameType.Singleplayer ||
|
||||
this.game.config().isReplay();
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
tick() {
|
||||
const updates = this.game.updatesSinceLastTick();
|
||||
if (updates) {
|
||||
this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0;
|
||||
}
|
||||
if (this.game.inSpawnPhase()) {
|
||||
this.timer = 0;
|
||||
} else if (!this.hasWinner && this.game.ticks() % 10 === 0) {
|
||||
this.timer++;
|
||||
}
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
class="top-0 lg:top-4 right-0 lg:right-4 z-50 pointer-events-auto"
|
||||
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
||||
>
|
||||
<div
|
||||
class="bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-es-sm lg:rounded-lg backdrop-blur-md"
|
||||
>
|
||||
<div class="flex items-stretch gap-1 lg:gap-2">
|
||||
${button({
|
||||
classes: !this.showPauseButton ? "hidden" : "",
|
||||
onClick: this.onPauseButtonClick,
|
||||
title: this.isPaused ? "Resume game" : "Pause game",
|
||||
children: this.isPaused ? "▶️" : "⏸",
|
||||
})}
|
||||
<div
|
||||
class="w-[55px] h-8 lg:w-24 lg:h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
||||
rounded text-sm lg:text-xl"
|
||||
>
|
||||
${secondsToHms(this.timer)}
|
||||
</div>
|
||||
${button({
|
||||
onClick: this.onExitButtonClick,
|
||||
title: "Exit game",
|
||||
children: "❌",
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onSettingsButtonClick,
|
||||
title: "Settings",
|
||||
children: "⚙️",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="options-menu flex flex-col justify-around gap-y-3 mt-2 bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-lg backdrop-blur-md ${!this
|
||||
.showSettings
|
||||
? "hidden"
|
||||
: ""}"
|
||||
>
|
||||
${button({
|
||||
onClick: this.onTerrainButtonClick,
|
||||
title: "Toggle Terrain",
|
||||
children: "🌲: " + (this.alternateView ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleEmojisButtonClick,
|
||||
title: "Toggle Emojis",
|
||||
children: "🙂: " + (this.userSettings.emojis() ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleAlertFrameButtonClick,
|
||||
title: "Toggle Alert frame",
|
||||
children: "🚨: " + (this.userSettings.alertFrame() ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleSpecialEffectsButtonClick,
|
||||
title: "Toggle Special effects",
|
||||
children: "💥: " + (this.userSettings.fxLayer() ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleTerritoryPatterns,
|
||||
title: "Territory Patterns",
|
||||
children:
|
||||
"🏳️: " + (this.userSettings.territoryPatterns() ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleDarkModeButtonClick,
|
||||
title: "Dark Mode",
|
||||
children: "🌙: " + (this.userSettings.darkMode() ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleRandomNameModeButtonClick,
|
||||
title: "Random name mode",
|
||||
children:
|
||||
"🥷: " + (this.userSettings.anonymousNames() ? "On" : "Off"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleLeftClickOpensMenu,
|
||||
title: "Left click",
|
||||
children:
|
||||
"🖱️: " +
|
||||
(this.userSettings.leftClickOpensMenu()
|
||||
? "Opens menu"
|
||||
: "Attack"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onTogglePerformanceOverlayButtonClick,
|
||||
title: "Performance Overlay",
|
||||
children:
|
||||
"🚀: " + (this.userSettings.performanceOverlay() ? "On" : "Off"),
|
||||
})}
|
||||
<!-- ${button({
|
||||
onClick: this.onToggleFocusLockedButtonClick,
|
||||
title: "Lock Focus",
|
||||
children:
|
||||
"🗺: " +
|
||||
(this.userSettings.focusLocked()
|
||||
? "Focus locked"
|
||||
: "Hover focus"),
|
||||
})} -->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
SendDonateTroopsIntentEvent,
|
||||
SendEmbargoIntentEvent,
|
||||
SendEmojiIntentEvent,
|
||||
SendQuickChatEvent,
|
||||
SendSpawnIntentEvent,
|
||||
SendTargetPlayerIntentEvent,
|
||||
} from "../../Transport";
|
||||
@@ -97,10 +96,6 @@ export class PlayerActionHandler {
|
||||
this.eventBus.emit(new SendEmojiIntentEvent(targetPlayer, emojiIndex));
|
||||
}
|
||||
|
||||
handleQuickChat(recipient: PlayerView, chatKey: string, params: any = {}) {
|
||||
this.eventBus.emit(new SendQuickChatEvent(recipient, chatKey, params));
|
||||
}
|
||||
|
||||
handleDeleteUnit(unitId: number) {
|
||||
this.eventBus.emit(new SendDeleteUnitIntentEvent(unitId));
|
||||
}
|
||||
|
||||
@@ -255,7 +255,9 @@ export class StructureIconsLayer implements Layer {
|
||||
this.potentialUpgrade.iconContainer.filters = [];
|
||||
this.potentialUpgrade.dotContainer.filters = [];
|
||||
}
|
||||
this.ghostUnit?.container && (this.ghostUnit.container.filters = []);
|
||||
if (this.ghostUnit?.container) {
|
||||
this.ghostUnit.container.filters = [];
|
||||
}
|
||||
|
||||
if (!this.ghostUnit) return;
|
||||
|
||||
|
||||
+6
-21
@@ -91,12 +91,11 @@
|
||||
document.documentElement.className = "preload";
|
||||
</script>
|
||||
|
||||
<!-- Playwire ads -->
|
||||
<script>
|
||||
window.ramp = window.ramp || {};
|
||||
window.ramp.que = window.ramp.que || [];
|
||||
window.ramp.passiveMode = true;
|
||||
</script>
|
||||
<!-- Publift/Fuse ads -->
|
||||
<script
|
||||
async
|
||||
src="https://cdn.fuseplatform.net/publift/tags/2/4121/fuse.js"
|
||||
></script>
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
@@ -197,7 +196,7 @@
|
||||
</header>
|
||||
<div class="bg-image"></div>
|
||||
|
||||
<gutter-ad-modal></gutter-ad-modal>
|
||||
<gutter-ads></gutter-ads>
|
||||
|
||||
<!-- Main container with responsive padding -->
|
||||
<main class="flex justify-center flex-grow">
|
||||
@@ -286,7 +285,6 @@
|
||||
<div id="app"></div>
|
||||
<div id="radialMenu" class="radial-menu"></div>
|
||||
<div class="flex gap-2 fixed right-[10px] top-[10px] z-50 flex-col">
|
||||
<options-menu></options-menu>
|
||||
<player-info-overlay></player-info-overlay>
|
||||
</div>
|
||||
<div
|
||||
@@ -370,16 +368,6 @@
|
||||
>
|
||||
Terms of Service
|
||||
</a>
|
||||
<p style="text-align: center">
|
||||
<a
|
||||
href="https://www.playwire.com/contact-direct-sales"
|
||||
data-i18n="main.advertise"
|
||||
class="t-link"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Advertise</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -439,9 +427,6 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Playwire ads -->
|
||||
<script async src="//cdn.intergient.com/1025558/75940/ramp.js"></script>
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
defer
|
||||
|
||||
@@ -129,6 +129,20 @@ export function boundingBoxTiles(
|
||||
return tiles;
|
||||
}
|
||||
|
||||
export function getMode<T>(counts: Map<T, number>): T | null {
|
||||
let mode: T | null = null;
|
||||
let maxCount = 0;
|
||||
|
||||
for (const [item, count] of counts) {
|
||||
if (count > maxCount) {
|
||||
maxCount = count;
|
||||
mode = item;
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
export function calculateBoundingBoxCenter(
|
||||
gm: GameMap,
|
||||
borderTiles: ReadonlySet<TileRef>,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Config } from "../configuration/Config";
|
||||
import { Execution, Game, Player, UnitType } from "../game/Game";
|
||||
import { GameImpl } from "../game/GameImpl";
|
||||
import { GameMap, TileRef } from "../game/GameMap";
|
||||
import { calculateBoundingBox, inscribed, simpleHash } from "../Util";
|
||||
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util";
|
||||
|
||||
export class PlayerExecution implements Execution {
|
||||
private readonly ticksPerClusterCalc = 20;
|
||||
@@ -221,28 +221,20 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
private getCapturingPlayer(cluster: Set<TileRef>): Player | null {
|
||||
// Collect unique neighbor IDs (excluding self) as candidates
|
||||
const candidatesIDs = new Set<number>();
|
||||
const selfID = this.player.smallID();
|
||||
|
||||
const neighbors = new Map<Player, number>();
|
||||
for (const t of cluster) {
|
||||
for (const neighbor of this.mg.neighbors(t)) {
|
||||
if (this.mg.ownerID(neighbor) !== selfID) {
|
||||
candidatesIDs.add(this.mg.ownerID(neighbor));
|
||||
const owner = this.mg.owner(neighbor);
|
||||
if (
|
||||
owner.isPlayer() &&
|
||||
owner !== this.player &&
|
||||
!owner.isFriendly(this.player)
|
||||
) {
|
||||
neighbors.set(owner, (neighbors.get(owner) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out friendly and non-player candidates
|
||||
const neighbors = new Set<Player>();
|
||||
for (const id of candidatesIDs) {
|
||||
const neighbor = this.mg.playerBySmallID(id);
|
||||
if (!neighbor.isPlayer() || neighbor.isFriendly(this.player)) {
|
||||
continue;
|
||||
}
|
||||
neighbors.add(neighbor);
|
||||
}
|
||||
|
||||
// If there are no enemies, return null
|
||||
if (neighbors.size === 0) {
|
||||
return null;
|
||||
@@ -251,7 +243,7 @@ export class PlayerExecution implements Execution {
|
||||
// Get the largest attack from the neighbors
|
||||
let largestNeighborAttack: Player | null = null;
|
||||
let largestTroopCount = 0;
|
||||
for (const neighbor of neighbors) {
|
||||
for (const [neighbor] of neighbors) {
|
||||
for (const attack of neighbor.outgoingAttacks()) {
|
||||
if (attack.target() === this.player) {
|
||||
if (attack.troops() > largestTroopCount) {
|
||||
@@ -262,9 +254,12 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
// Return the largest neighbor attack
|
||||
// If there is no largest neighbor attack, this will return null
|
||||
return largestNeighborAttack;
|
||||
if (largestNeighborAttack !== null) {
|
||||
return largestNeighborAttack;
|
||||
}
|
||||
|
||||
// There are no ongoing attacks, so find the enemy with the largest border.
|
||||
return getMode(neighbors);
|
||||
}
|
||||
|
||||
private calculateClusters(): Set<TileRef>[] {
|
||||
|
||||
@@ -164,7 +164,7 @@ export class RailroadExecution implements Execution {
|
||||
}
|
||||
|
||||
private redrawBuildings() {
|
||||
this.railRoad.from.unit.isActive() && this.railRoad.from.unit.touch();
|
||||
this.railRoad.to.unit.isActive() && this.railRoad.to.unit.touch();
|
||||
if (this.railRoad.from.unit.isActive()) this.railRoad.from.unit.touch();
|
||||
if (this.railRoad.to.unit.isActive()) this.railRoad.to.unit.touch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ export interface PlayerUpdate {
|
||||
alliances: AllianceView[];
|
||||
hasSpawned: boolean;
|
||||
betrayals?: bigint;
|
||||
lastDeleteUnitTick: Tick;
|
||||
}
|
||||
|
||||
export interface AllianceView {
|
||||
|
||||
@@ -426,8 +426,15 @@ export class PlayerView {
|
||||
return this.data.isDisconnected;
|
||||
}
|
||||
|
||||
lastDeleteUnitTick(): Tick {
|
||||
return this.data.lastDeleteUnitTick;
|
||||
}
|
||||
|
||||
canDeleteUnit(): boolean {
|
||||
return true;
|
||||
return (
|
||||
this.game.ticks() + 1 - this.lastDeleteUnitTick() >=
|
||||
this.game.config().deleteUnitCooldown()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +446,6 @@ export class GameView implements GameMap {
|
||||
private updatedTiles: TileRef[] = [];
|
||||
|
||||
private _myPlayer: PlayerView | null = null;
|
||||
private _focusedPlayer: PlayerView | null = null;
|
||||
|
||||
private unitGrid: UnitGrid;
|
||||
|
||||
@@ -756,10 +762,6 @@ export class GameView implements GameMap {
|
||||
}
|
||||
|
||||
focusedPlayer(): PlayerView | null {
|
||||
// TODO: renable when performance issues are fixed.
|
||||
return this.myPlayer();
|
||||
}
|
||||
setFocusedPlayer(player: PlayerView | null): void {
|
||||
this._focusedPlayer = player;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ export class PlayerImpl implements Player {
|
||||
),
|
||||
hasSpawned: this.hasSpawned(),
|
||||
betrayals: stats?.betrayals,
|
||||
lastDeleteUnitTick: this.lastDeleteUnitTick,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user