allow alliance extension Fixes #491 (#1314)

## Description:

About 30s before an alliance is about to expire, both players receive a
prompt to extend the alliance. If both players agree the alliance is
extended.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
evanpelle
2025-07-02 15:25:20 -07:00
committed by GitHub
parent cfabdfebc7
commit 5b5ac7bfca
19 changed files with 358 additions and 19 deletions
+17
View File
@@ -67,6 +67,10 @@ export class SendAllianceReplyIntentEvent implements GameEvent {
) {}
}
export class SendAllianceExtensionIntentEvent implements GameEvent {
constructor(public readonly recipient: PlayerView) {}
}
export class SendSpawnIntentEvent implements GameEvent {
constructor(public readonly cell: Cell) {}
}
@@ -194,6 +198,9 @@ export class Transport {
this.eventBus.on(SendAllianceReplyIntentEvent, (e) =>
this.onAllianceRequestReplyUIEvent(e),
);
this.eventBus.on(SendAllianceExtensionIntentEvent, (e) =>
this.onSendAllianceExtensionIntent(e),
);
this.eventBus.on(SendBreakAllianceIntentEvent, (e) =>
this.onBreakAllianceRequestUIEvent(e),
);
@@ -419,6 +426,16 @@ export class Transport {
});
}
private onSendAllianceExtensionIntent(
event: SendAllianceExtensionIntentEvent,
) {
this.sendIntent({
type: "allianceExtension",
clientID: this.lobbyConfig.clientID,
recipient: event.recipient.id(),
});
}
private onSendSpawnIntentEvent(event: SendSpawnIntentEvent) {
this.sendIntent({
type: "spawn",
+1
View File
@@ -141,6 +141,7 @@ export function getMessageTypeClasses(type: MessageType): string {
case MessageType.SAM_MISS:
case MessageType.ALLIANCE_EXPIRED:
case MessageType.NAVAL_INVASION_INBOUND:
case MessageType.RENEW_ALLIANCE:
return severityColors["warn"];
case MessageType.CHAT:
case MessageType.ALLIANCE_REQUEST:
+64 -2
View File
@@ -32,6 +32,7 @@ import {
import {
CancelAttackIntentEvent,
CancelBoatIntentEvent,
SendAllianceExtensionIntentEvent,
SendAllianceReplyIntentEvent,
} from "../../Transport";
import { Layer } from "./Layer";
@@ -45,7 +46,6 @@ import {
GoToUnitEvent,
} from "./Leaderboard";
import { UserSettings } from "../../../core/game/UserSettings";
import { getMessageTypeClasses, translateText } from "../../Utils";
interface GameEvent {
@@ -73,9 +73,11 @@ export class EventsDisplay extends LitElement implements Layer {
public eventBus: EventBus;
public game: GameView;
private userSettings: UserSettings = new UserSettings();
private active: boolean = false;
private events: GameEvent[] = [];
// allianceID -> last checked at tick
private alliancesCheckedAt = new Map<number, Tick>();
@state() private incomingAttacks: AttackUpdate[] = [];
@state() private outgoingAttacks: AttackUpdate[] = [];
@state() private outgoingLandAttacks: AttackUpdate[] = [];
@@ -182,6 +184,8 @@ export class EventsDisplay extends LitElement implements Layer {
return;
}
this.checkForAllianceExpirations();
const updates = this.game.updatesSinceLastTick();
if (updates) {
for (const [ut, fn] of this.updateMap) {
@@ -235,6 +239,64 @@ export class EventsDisplay extends LitElement implements Layer {
}
}
private checkForAllianceExpirations() {
const myPlayer = this.game.myPlayer();
if (!myPlayer) return;
for (const alliance of myPlayer.alliances()) {
if (
alliance.expiresAt >
this.game.ticks() + this.game.config().allianceExtensionPromptOffset()
) {
continue;
}
if (
(this.alliancesCheckedAt.get(alliance.id) ?? 0) >=
this.game.ticks() - this.game.config().allianceExtensionPromptOffset()
) {
// We've already displayed a message for this alliance.
continue;
}
this.alliancesCheckedAt.set(alliance.id, this.game.ticks());
const other = this.game.player(alliance.other) as PlayerView;
this.addEvent({
description: translateText("events_display.about_to_expire", {
name: other.name(),
}),
type: MessageType.RENEW_ALLIANCE,
duration: this.game.config().allianceExtensionPromptOffset() - 3 * 10, // 3 second buffer
buttons: [
{
text: translateText("events_display.focus"),
className: "btn-gray",
action: () => this.eventBus.emit(new GoToPlayerEvent(other)),
preventClose: true,
},
{
text: translateText("events_display.renew_alliance", {
name: other.name(),
}),
className: "btn",
action: () =>
this.eventBus.emit(new SendAllianceExtensionIntentEvent(other)),
},
{
text: translateText("events_display.ignore"),
className: "btn-info",
action: () => {},
},
],
highlight: true,
createdAt: this.game.ticks(),
focusID: other.smallID(),
});
}
}
private addEvent(event: GameEvent) {
this.events = [...this.events, event];
if (this._hidden === true) {
+3 -5
View File
@@ -176,11 +176,9 @@ export class PlayerPanel extends LitElement implements Layer {
if (myPlayer !== null && myPlayer.isAlive()) {
this.actions = await myPlayer.actions(this.tile);
if (this.actions?.interaction?.allianceCreatedAtTick !== undefined) {
const createdAt = this.actions.interaction.allianceCreatedAtTick;
const durationTicks = this.g.config().allianceDuration();
const expiryTick = createdAt + durationTicks;
const remainingTicks = expiryTick - this.g.ticks();
if (this.actions?.interaction?.allianceExpiresAt !== undefined) {
const expiresAt = this.actions.interaction.allianceExpiresAt;
const remainingTicks = expiresAt - this.g.ticks();
if (remainingTicks > 0) {
const remainingSeconds = Math.max(