-
Notifications
You must be signed in to change notification settings - Fork 754
Destroy incoming nukes when alliance is created #2716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import { renderNumber } from "../../client/Utils"; | ||
| import { Config } from "../configuration/Config"; | ||
| import { NukeExecution } from "../execution/NukeExecution"; | ||
| import { AllPlayersStats, ClientID, Winner } from "../Schemas"; | ||
| import { simpleHash } from "../Util"; | ||
| import { AllianceImpl } from "./AllianceImpl"; | ||
|
|
@@ -9,6 +10,7 @@ import { | |
| AllianceRequest, | ||
| Cell, | ||
| ColoredTeams, | ||
| DestroyNukesResult, | ||
| Duos, | ||
| EmojiMessage, | ||
| Execution, | ||
|
|
@@ -277,6 +279,75 @@ export class GameImpl implements Game { | |
| return ar; | ||
| } | ||
|
|
||
| public destroyNukesBetween(p1: Player, p2: Player): DestroyNukesResult { | ||
| let inFlight = 0; | ||
| let queued = 0; | ||
| let fromRequestorToRecipient = 0; | ||
| let fromRecipientToRequestor = 0; | ||
|
|
||
| const destroy = (exec: Execution, isQueued: boolean) => { | ||
| if (!(exec instanceof NukeExecution)) return; | ||
|
|
||
| const launcher = exec.owner(); | ||
|
|
||
| // queued execution -> target not resolvable yet | ||
| const target = | ||
| exec.isInFlight() && exec.target() | ||
| ? exec.target() | ||
| : exec instanceof NukeExecution | ||
| ? exec.targetTile() | ||
| : null; | ||
|
|
||
| if (!target) return; | ||
|
|
||
| let targetOwner: Player | TerraNullius; | ||
| if (typeof target === "object" && "isPlayer" in target) { | ||
| // target is already a Player or TerraNullius (in-flight nuke) | ||
| targetOwner = target as Player | TerraNullius; | ||
| } else { | ||
| // target is a TileRef (queued nuke) | ||
| targetOwner = this.owner(target as TileRef); | ||
| } | ||
|
|
||
| const isRequestorToRecipient = launcher === p1 && targetOwner === p2; | ||
| const isRecipientToRequestor = launcher === p2 && targetOwner === p1; | ||
|
|
||
| const isBetween = isRequestorToRecipient || isRecipientToRequestor; | ||
|
|
||
| if (!isBetween) { | ||
| return; | ||
| } | ||
|
|
||
| if (isQueued) queued++; | ||
| else inFlight++; | ||
|
|
||
| if (isRequestorToRecipient) fromRequestorToRecipient++; | ||
| else fromRecipientToRequestor++; | ||
|
|
||
| exec.destroyInFlight(); | ||
| }; | ||
|
|
||
| for (const exec of this.execs) { | ||
| if (exec instanceof NukeExecution && !exec.isInFlight()) { | ||
| // initialized but not launched yet -> queued | ||
| destroy(exec, true); | ||
| } else { | ||
| destroy(exec, false); | ||
| } | ||
| } | ||
|
|
||
| for (const exec of this.unInitExecs) { | ||
| destroy(exec, true); | ||
| } | ||
|
|
||
| return { | ||
| inFlight, | ||
| queued, | ||
| fromRequestorToRecipient, | ||
| fromRecipientToRequestor, | ||
| }; | ||
| } | ||
|
|
||
| acceptAllianceRequest(request: AllianceRequestImpl) { | ||
| this.allianceRequests = this.allianceRequests.filter( | ||
| (ar) => ar !== request, | ||
|
|
@@ -311,6 +382,110 @@ export class GameImpl implements Game { | |
| if (recipient.hasEmbargoAgainst(requestor)) | ||
| recipient.endTemporaryEmbargo(requestor); | ||
|
|
||
| const { | ||
| inFlight, | ||
| queued, | ||
| fromRequestorToRecipient, | ||
| fromRecipientToRequestor, | ||
| } = this.destroyNukesBetween(requestor, recipient); | ||
|
|
||
| // Destroy counts available for display messages | ||
| this.unInitExecs = this.unInitExecs.filter((e) => e.isActive()); | ||
|
|
||
| if (fromRequestorToRecipient > 0) { | ||
| const requestorMsg = `${fromRequestorToRecipient} nuke${ | ||
| fromRequestorToRecipient > 1 ? "s" : "" | ||
| } launched towards ${recipient.displayName()} ${ | ||
| fromRequestorToRecipient > 1 ? "were" : "was" | ||
| } destroyed due to the alliance`; | ||
|
|
||
| const recipientMsg = `${fromRequestorToRecipient} nuke${ | ||
| fromRequestorToRecipient > 1 ? "s" : "" | ||
| } launched by ${requestor.displayName()} towards you ${ | ||
| fromRequestorToRecipient > 1 ? "were" : "was" | ||
| } destroyed due to the alliance`; | ||
|
|
||
| this.displayMessage( | ||
| requestorMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| requestor.id(), | ||
| ); | ||
| this.displayMessage( | ||
| recipientMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| recipient.id(), | ||
| ); | ||
| } | ||
|
|
||
| if (fromRecipientToRequestor > 0) { | ||
| const requestorMsg = `${fromRecipientToRequestor} nuke${ | ||
| fromRecipientToRequestor > 1 ? "s" : "" | ||
| } launched by ${recipient.displayName()} towards you ${ | ||
| fromRecipientToRequestor > 1 ? "were" : "was" | ||
| } destroyed due to the alliance`; | ||
|
|
||
| const recipientMsg = `${fromRecipientToRequestor} nuke${ | ||
| fromRecipientToRequestor > 1 ? "s" : "" | ||
| } launched towards ${requestor.displayName()} ${ | ||
| fromRecipientToRequestor > 1 ? "were" : "was" | ||
| } destroyed due to the alliance`; | ||
|
|
||
| this.displayMessage( | ||
| requestorMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| requestor.id(), | ||
| ); | ||
| this.displayMessage( | ||
| recipientMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| recipient.id(), | ||
| ); | ||
| } | ||
|
|
||
| if (inFlight > 0) { | ||
| const baseMsg = `${inFlight} nuke${inFlight > 1 ? "s" : ""} in flight ${ | ||
| inFlight > 1 ? "were" : "was" | ||
| } neutralized due to alliance formation with`; | ||
|
|
||
| const requestorMsg = `${inFlight} nuke${ | ||
| inFlight > 1 ? "s" : "" | ||
| } in flight ${ | ||
| inFlight > 1 ? "were" : "was" | ||
| } neutralized due to alliance formation with ${recipient.displayName()}`; | ||
| const recipientMsg = baseMsg + ` ${requestor.displayName()}`; | ||
|
|
||
| this.displayMessage( | ||
| requestorMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| requestor.id(), | ||
| ); | ||
| this.displayMessage( | ||
| recipientMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| recipient.id(), | ||
| ); | ||
| } | ||
|
|
||
| if (queued > 0) { | ||
| const baseMsg = `${queued} planned nuke${queued > 1 ? "s" : ""} ${ | ||
| queued > 1 ? "were" : "was" | ||
| } canceled due to alliance formation with`; | ||
|
|
||
| const requestorMsg = baseMsg + ` ${recipient.displayName()}`; | ||
| const recipientMsg = baseMsg + ` ${requestor.displayName()}`; | ||
|
|
||
| this.displayMessage( | ||
| requestorMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| requestor.id(), | ||
| ); | ||
| this.displayMessage( | ||
| recipientMsg, | ||
| MessageType.ALLIANCE_ACCEPTED, | ||
| recipient.id(), | ||
| ); | ||
| } | ||
|
Comment on lines
+385
to
+487
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate messages: directional and type-based counters are not mutually exclusive. The message logic sends multiple notifications for the same nukes:
Problem: These conditions are not mutually exclusive. A single in-flight nuke from requestor to recipient increments both Example: If player A has 1 in-flight nuke targeting player B when they ally:
🔎 Recommended fix: use mutually exclusive conditionsConsider this approach:
- if (fromRequestorToRecipient > 0) {
+ const hasMutualNukes = fromRequestorToRecipient > 0 && fromRecipientToRequestor > 0;
+
+ if (!hasMutualNukes && fromRequestorToRecipient > 0) {
// ... directional messages for requestor → recipient
}
- if (fromRecipientToRequestor > 0) {
+ if (!hasMutualNukes && fromRecipientToRequestor > 0) {
// ... directional messages for recipient → requestor
}
- if (inFlight > 0) {
+ if (hasMutualNukes && inFlight > 0) {
// ... non-directional in-flight messages
}
- if (queued > 0) {
+ if (hasMutualNukes && queued > 0) {
// ... non-directional queued messages
}Or alternatively, always use directional messages and remove the non-directional blocks entirely.
|
||
|
|
||
| this.addUpdate({ | ||
| type: GameUpdateType.AllianceRequestReply, | ||
| request: request.toUpdate(), | ||
|
|
@@ -357,6 +532,7 @@ export class GameImpl implements Game { | |
| } | ||
|
|
||
| executeNextTick(): GameUpdates { | ||
| const pending = this.updates; | ||
| this.updates = createGameUpdatesMap(); | ||
| this.execs.forEach((e) => { | ||
| if ( | ||
|
|
@@ -393,7 +569,15 @@ export class GameImpl implements Game { | |
| }); | ||
| } | ||
| this._ticks++; | ||
| return this.updates; | ||
|
|
||
| const merged = createGameUpdatesMap(); | ||
|
|
||
| for (const k in merged) { | ||
| merged[k] = [...pending[k], ...this.updates[k]]; | ||
| } | ||
|
|
||
| this.updates = createGameUpdatesMap(); | ||
| return merged; | ||
| } | ||
|
|
||
| private hash(): number { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Refactor type discrimination logic for clarity and safety.
Lines 294-310 use fragile runtime type checking to distinguish between
Player | TerraNullius(in-flight) vsTileRef(queued). The pattern has several issues:instanceof NukeExecutioncheck (already verified on line 289)Player | TerraNulliuswithout verifying the object actually has expected methodstypeof === "object"which is implicit and unclear🔎 Proposed refactor for explicit type checking
const destroy = (exec: Execution, isQueued: boolean) => { if (!(exec instanceof NukeExecution)) return; const launcher = exec.owner(); - // queued execution -> target not resolvable yet - const target = - exec.isInFlight() && exec.target() - ? exec.target() - : exec instanceof NukeExecution - ? exec.targetTile() - : null; - - if (!target) return; - - let targetOwner: Player | TerraNullius; - if (typeof target === "object" && "isPlayer" in target) { - // target is already a Player or TerraNullius (in-flight nuke) - targetOwner = target as Player | TerraNullius; - } else { - // target is a TileRef (queued nuke) - targetOwner = this.owner(target as TileRef); - } + let targetOwner: Player | TerraNullius | null; + + if (exec.isInFlight()) { + // In-flight: target() returns Player | TerraNullius | null + targetOwner = exec.target(); + } else { + // Queued: resolve owner from targetTile() + targetOwner = this.owner(exec.targetTile()); + } + + if (!targetOwner) return; const isRequestorToRecipient = launcher === p1 && targetOwner === p2; const isRecipientToRequestor = launcher === p2 && targetOwner === p1; const isBetween = isRequestorToRecipient || isRecipientToRequestor; if (!isBetween) { return; } if (isQueued) queued++; else inFlight++; if (isRequestorToRecipient) fromRequestorToRecipient++; else fromRecipientToRequestor++; exec.destroyInFlight(); };