Skip to content
Open
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
4 changes: 4 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,10 @@
"betrayed_you": "{name} broke their alliance with you",
"about_to_expire": "Your alliance with {name} is about to expire!",
"alliance_expired": "Your alliance with {name} expired",
"alliances_disabled_warning_5min": "⚠ All alliances will be disabled in 5 minutes (at the 45 minute mark) ⚠",
"alliances_disabled_warning": "⚠ All alliances will be disabled at the 45 minute mark (1 minute left) ⚠",
"alliances_disabled": "All alliances have been dissolved. No new alliances can be formed.",
"alliances_ending_countdown": "⚠ Alliances ending in {time}",
"attack_request": "{name} requests you attack {target}",
"sent_emoji": "Sent {name}: {emoji}",
"renew_alliance": "Request to renew",
Expand Down
1 change: 1 addition & 0 deletions src/client/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ export function getMessageTypeClasses(type: MessageType): string {
case MessageType.HYDROGEN_BOMB_INBOUND:
case MessageType.SAM_MISS:
case MessageType.ALLIANCE_EXPIRED:
case MessageType.ALLIANCES_DISABLED:
case MessageType.NAVAL_INVASION_INBOUND:
case MessageType.RENEW_ALLIANCE:
return severityColors["warn"];
Expand Down
5 changes: 4 additions & 1 deletion src/client/graphics/PlayerIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ export function getPlayerIcons(
const userSettings = game.config().userSettings();
const isDarkMode = darkMode ?? userSettings?.darkMode() ?? false;
const emojisEnabled = userSettings?.emojis() ?? false;
const alliancesOff = alliancesDisabled ?? game.config().disableAlliances();
const cutoff = game.config().alliancesCutoffTick();
const pastCutoff = cutoff !== null && game.ticks() >= cutoff;
const alliancesOff =
alliancesDisabled || game.config().disableAlliances() || pastCutoff;

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const icons: PlayerIconDescriptor[] = [];

Expand Down
83 changes: 83 additions & 0 deletions src/client/graphics/layers/EventsDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export class EventsDisplay extends LitElement implements Layer {
[MessageCategory.ALLIANCE, false],
[MessageCategory.CHAT, false],
]);
@state() private allianceCutoffCountdown: string | null = null;
@state() private allianceCutoffCenterWarning: string | null = null;

@query(".events-container")
private _eventsContainer?: HTMLDivElement;
Expand Down Expand Up @@ -237,6 +239,7 @@ export class EventsDisplay extends LitElement implements Layer {
}

this.checkForAllianceExpirations();
this.updateAllianceCutoffCountdown();

const updates = this.game.updatesSinceLastTick();
if (updates) {
Expand Down Expand Up @@ -352,6 +355,38 @@ export class EventsDisplay extends LitElement implements Layer {
this.requestUpdate();
}

private updateAllianceCutoffCountdown() {
const cutoff = this.game.config().alliancesCutoffTick();
if (cutoff === null) {
this.allianceCutoffCountdown = null;
return;
}
const fiveMinStart = cutoff - 5 * 60 * 10;
const oneMinStart = cutoff - 60 * 10;
const ticks = this.game.ticks();
if (ticks < fiveMinStart || ticks >= cutoff) {
this.allianceCutoffCountdown = null;
return;
}
const remainingTicks = cutoff - ticks;
const seconds = Math.ceil(remainingTicks / 10);
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
this.allianceCutoffCountdown = `${mins}:${secs.toString().padStart(2, "0")}`;
const warningKey =
ticks === fiveMinStart
? "events_display.alliances_disabled_warning_5min"
: ticks === oneMinStart
? "events_display.alliances_disabled_warning"
: null;
if (warningKey && !this.allianceCutoffCenterWarning) {
this.allianceCutoffCenterWarning = warningKey;
setTimeout(() => {
this.allianceCutoffCenterWarning = null;
}, 5000);
}
}

private removeEvent(index: number) {
this.events = [
...this.events.slice(0, index),
Expand Down Expand Up @@ -799,6 +834,24 @@ export class EventsDisplay extends LitElement implements Layer {
transform: scale(1);
}
}
@keyframes fadeInOut {
0% {
opacity: 0;
transform: translate(-50%, -50%) scale(0.9);
}
15% {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
85% {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
100% {
opacity: 0;
transform: translate(-50%, -50%) scale(0.9);
}
}
Comment thread
Zixer1 marked this conversation as resolved.
</style>
`;

Expand All @@ -818,6 +871,22 @@ export class EventsDisplay extends LitElement implements Layer {

return html`
${styles}
<!-- Alliance Cutoff Center Warning -->
${this.allianceCutoffCenterWarning
? html`
<div
class="fixed top-1/2 left-1/2 z-[10000] pointer-events-none"
style="animation: fadeInOut 5s ease-in-out forwards;"
>
<p
class="text-red-300/90 text-lg lg:text-2xl font-bold text-center m-0 whitespace-nowrap"
style="text-shadow: 0 0 4px #000, 0 0 4px #000, 0 0 4px #000, 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;"
>
${translateText(this.allianceCutoffCenterWarning)}
</p>
</div>
`
: ""}
<!-- Events Toggle (when hidden) -->
${this._hidden
? html`
Expand Down Expand Up @@ -889,6 +958,20 @@ export class EventsDisplay extends LitElement implements Layer {
</div>
</div>

<!-- Alliance Cutoff Countdown -->
${this.allianceCutoffCountdown
? html`
<div
class="w-full px-3 py-1.5 bg-amber-600/90 text-white text-xs lg:text-sm font-semibold text-center"
>
${translateText(
"events_display.alliances_ending_countdown",
{ time: this.allianceCutoffCountdown },
)}
</div>
`
: ""}

<!-- Content Area -->
<div
class="bg-gray-800/92 backdrop-blur-sm max-h-[15vh] lg:max-h-[30vh] overflow-y-auto w-full h-full min-[1200px]:rounded-b-xl events-container"
Expand Down
10 changes: 9 additions & 1 deletion src/client/graphics/layers/NameLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,17 @@ export class NameLayer implements Layer {
}

public tick() {
// Precompute the first-place player for performance
this.firstPlace = getFirstPlacePlayer(this.game);

const cutoff = this.config.alliancesCutoffTick();
if (
cutoff !== null &&
!this.alliancesDisabled &&
this.game.ticks() >= cutoff
) {
this.alliancesDisabled = true;
}
Comment thread
Zixer1 marked this conversation as resolved.

for (const player of this.game.playerViews()) {
if (player.isAlive()) {
if (!this.seenPlayers.has(player)) {
Expand Down
1 change: 1 addition & 0 deletions src/core/configuration/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface Config {
structureMinDist(): number;
isReplay(): boolean;
allianceExtensionPromptOffset(): number;
alliancesCutoffTick(): Tick | null;
}

export interface Theme {
Expand Down
5 changes: 5 additions & 0 deletions src/core/configuration/DefaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1011,4 +1011,9 @@ export class DefaultConfig implements Config {
allianceExtensionPromptOffset(): number {
return 300; // 30 seconds before expiration
}

alliancesCutoffTick(): Tick | null {
if (this._gameConfig.disableAlliances) return 0;
return this.numSpawnPhaseTurns() + 45 * 60 * 10;
}
}
4 changes: 3 additions & 1 deletion src/core/execution/TribeExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ export class TribeExecution implements Execution {
}

private acceptAllAllianceRequests() {
// Accept all alliance requests
const cutoff = this.mg.config().alliancesCutoffTick();
if (cutoff !== null && this.mg.ticks() >= cutoff) return;

for (const req of this.tribe.incomingAllianceRequests()) {
req.accept();
}
Expand Down
3 changes: 3 additions & 0 deletions src/core/execution/alliance/AllianceExtensionExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export class AllianceExtensionExecution implements Execution {
return;
}

const cutoff = mg.config().alliancesCutoffTick();
if (cutoff !== null && mg.ticks() >= cutoff) return;

const alliance = this.from.allianceWith(to);
if (!alliance) {
console.warn(
Expand Down
11 changes: 8 additions & 3 deletions src/core/execution/nation/NationAllianceBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ export class NationAllianceBehavior {
private emojiBehavior: NationEmojiBehavior,
) {}

private isAlliancesBlocked(): boolean {
const cutoff = this.game.config().alliancesCutoffTick();
return cutoff !== null && this.game.ticks() >= cutoff;
}

handleAllianceRequests() {
if (this.game.config().disableAlliances()) return;
if (this.isAlliancesBlocked()) return;

for (const req of this.player.incomingAllianceRequests()) {
// Alliance Request intents created during the spawn phase are executed on
Expand All @@ -46,7 +51,7 @@ export class NationAllianceBehavior {
}

handleAllianceExtensionRequests() {
if (this.game.config().disableAlliances()) return;
if (this.isAlliancesBlocked()) return;

for (const alliance of this.player.alliances()) {
// Alliance expiration tracked by Events Panel, only human ally can click Request to Renew
Expand All @@ -63,7 +68,7 @@ export class NationAllianceBehavior {
}

maybeSendAllianceRequests(borderingEnemies: Player[]) {
if (this.game.config().disableAlliances()) return;
if (this.isAlliancesBlocked()) return;

// Only easy nations are allowed to send alliance requests to bots
const isAcceptablePlayerType = (p: Player) =>
Expand Down
2 changes: 2 additions & 0 deletions src/core/game/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ export enum MessageType {
RECEIVED_TROOPS_FROM_PLAYER,
CHAT,
RENEW_ALLIANCE,
ALLIANCES_DISABLED,
}

// Message categories used for filtering events in the EventsDisplay
Expand Down Expand Up @@ -1071,6 +1072,7 @@ export const MESSAGE_TYPE_CATEGORIES: Record<MessageType, MessageCategory> = {
[MessageType.ALLIANCE_BROKEN]: MessageCategory.ALLIANCE,
[MessageType.ALLIANCE_EXPIRED]: MessageCategory.ALLIANCE,
[MessageType.RENEW_ALLIANCE]: MessageCategory.ALLIANCE,
[MessageType.ALLIANCES_DISABLED]: MessageCategory.ALLIANCE,
[MessageType.SENT_GOLD_TO_PLAYER]: MessageCategory.TRADE,
[MessageType.RECEIVED_GOLD_FROM_PLAYER]: MessageCategory.TRADE,
[MessageType.RECEIVED_GOLD_FROM_TRADE]: MessageCategory.TRADE,
Expand Down
43 changes: 43 additions & 0 deletions src/core/game/GameImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ export class GameImpl implements Game {
for (const tile of waterChangedTiles) {
this.recordTileUpdate(tile);
}
this.checkAlliancesCutoff();
this._ticks++;
return this.updates;
}
Expand Down Expand Up @@ -817,6 +818,48 @@ export class GameImpl implements Game {
);
}

private broadcastToAlivePlayers(message: string, type: MessageType): void {
for (const player of this._players.values()) {
if (player.isAlive()) {
this.displayMessage(message, type, player.id());
}
}
}

private checkAlliancesCutoff(): void {
const cutoff = this._config.alliancesCutoffTick();
if (cutoff === null) return;

const fiveMinWarningTick = cutoff - 5 * 60 * 10;
if (this._ticks === fiveMinWarningTick && fiveMinWarningTick > 0) {
this.broadcastToAlivePlayers(
"events_display.alliances_disabled_warning_5min",
MessageType.ALLIANCES_DISABLED,
);
}

const oneMinWarningTick = cutoff - 60 * 10;
if (this._ticks === oneMinWarningTick && oneMinWarningTick > 0) {
this.broadcastToAlivePlayers(
"events_display.alliances_disabled_warning",
MessageType.ALLIANCES_DISABLED,
);
}

if (this._ticks !== cutoff) return;

for (const alliance of [...this.alliances_]) {
this.expireAlliance(alliance);
}
for (const req of [...this.allianceRequests]) {
req.reject();
}
this.broadcastToAlivePlayers(
"events_display.alliances_disabled",
MessageType.ALLIANCES_DISABLED,
);
}

public isSpawnImmunityActive(): boolean {
return (
this.config().numSpawnPhaseTurns() +
Expand Down
4 changes: 4 additions & 0 deletions src/core/game/PlayerImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,10 @@ export class PlayerImpl implements Player {
if (this.mg.config().disableAlliances()) {
return false;
}
const cutoff = this.mg.config().alliancesCutoffTick();
if (cutoff !== null && this.mg.ticks() >= cutoff) {
return false;
}
if (other === this) {
return false;
}
Expand Down
Loading
Loading