Skip to content

feat: add block player feature. I should not have to form ally partnerships to…#3389

Open
rtaylorgarlock wants to merge 1 commit intoopenfrontio:mainfrom
rtaylorgarlock:feature/block-player
Open

feat: add block player feature. I should not have to form ally partnerships to…#3389
rtaylorgarlock wants to merge 1 commit intoopenfrontio:mainfrom
rtaylorgarlock:feature/block-player

Conversation

@rtaylorgarlock
Copy link

…compensate for bad teammates.

I became frustrated enough with playing this game that I decided to improve it. Local tests pass, which means I'm ready to offer this as a 'goodbye.' May the lobbies continue to be flooded with negative emojis. I hope >50% of players block each other.

Changes:

--Added BlockPlayerIntentSchema in src/core/Schemas.ts.
--Added blockedPlayersSet to PlayerImpl, with standard blockPlayer(), unblockPlayer(), hasBlocked(), and isBlockedBy() accessors. Handled side effects like clearing alliances and requests automatically when blocking.
--Created BlockPlayerExecution.ts to apply the intent and registered it in ExecutionManager.ts.
--Wired up SendBlockPlayerIntentEvent in Transport.ts and PlayerActionHandler.ts.
--Exposed blockedPlayers via PlayerInteraction inside GameRunner.ts to easily show if a player is blocked from the UI without polling UserSettings. Updated translations (en.json) to include "Block" and "Unblock". Modified PlayerPanel.ts to show "Block" (in red) / "Unblock" (in green) instead of trading actions (if user is clicking on a human player).
--Modified UserSettings.ts to manage the local storage array blocked-players that persists blocks across games.
--EventsDisplay.ts has been modified to discard emoji messages, chat messages, and alliance requests silently if they come from someone on the block list (either server-side game block or client-side UserSettings block).
--Refined canSendAllianceRequest, canDonateGold, and canDonateTroops to instantly return false if isBlocked is true for either party.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

DISCORD_USERNAME: > N/A - I am stepping away from this project and will not be available for ongoing maintenance or bug fixes.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

Walkthrough

This pull request adds player blocking functionality across the application. It introduces new UI controls to block/unblock players, stores blocked player lists locally, transmits blocking intents through a new event class, and enforces blocking rules throughout game logic including alliance requests, trades, and messaging.

Changes

Cohort / File(s) Summary
Localization
resources/lang/en.json
Added two new translation keys: "block" and "unblock" under player_panel section.
Intent & Schema
src/core/Schemas.ts, src/client/Transport.ts
Introduced BlockPlayerIntent schema with type "block", targetID, and action ("block" | "unblock"). Added SendBlockPlayerIntentEvent class to handle and forward blocking intents through Transport.
Event Filtering
src/client/graphics/layers/EventsDisplay.ts
Integrated UserSettings to filter chat, messages, alliance requests, and emoji events from blocked players; silently excludes interactions involving blocked parties before event creation.
Player Actions & UI
src/client/graphics/layers/PlayerActionHandler.ts, src/client/graphics/layers/PlayerPanel.ts
Added handleBlockPlayer method to emit blocking intents; extended PlayerPanel with Block/Unblock toggle button and handlers that update UserSettings, emit intents, and close panel.
Game Core - Blocking Logic
src/core/game/PlayerImpl.ts, src/core/game/Game.ts
Implemented blockPlayer, unblockPlayer, hasBlocked, isBlockedBy, and blockedPlayers methods in Player interface and PlayerImpl; integrated blocking checks into alliance requests, emoji, donations, and trade embargo logic; breaks existing alliances when blocking occurs.
Client-Side State
src/core/game/UserSettings.ts
Added blockedPlayers, isPlayerBlocked, blockPlayer, and unblockPlayer methods to manage blocked players list in localStorage with persistence and change event emission.
View Layer & Data
src/core/game/GameView.ts, src/core/game/GameUpdates.ts, src/core/GameRunner.ts
Added hasBlocked and isBlockedBy helper methods to PlayerView; extended PlayerUpdate and interaction data with blockedPlayers array and isBlocked boolean.
Tests
tests/BlockPlayer.test.ts
Added comprehensive test suite validating blocking, unblocking, alliance breaking, and pending request rejection scenarios across multiple player interactions.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as PlayerPanel
    participant Handler as PlayerActionHandler
    participant Transport
    participant Game as GameRunner
    participant Player as PlayerImpl
    participant Display as EventsDisplay
    
    User->>UI: Click Block Button
    UI->>UI: handleBlockPlayerClick
    activate UI
    UI->>UI: UserSettings.blockPlayer
    UI->>Handler: handleBlockPlayer(recipient, "block")
    deactivate UI
    Handler->>Handler: emit SendBlockPlayerIntentEvent
    Handler->>Transport: onSendBlockPlayerIntent
    Transport->>Game: BlockPlayerIntent received
    activate Game
    Game->>Player: blockPlayer(other)
    activate Player
    Player->>Player: add to blockedPlayers set
    Player->>Player: break alliances
    Player->>Player: reject pending requests
    deactivate Player
    Game->>Game: update interaction data (isBlocked, blockedPlayers)
    deactivate Game
    
    Note over Display: Subsequent events from blocked player
    Display->>Display: onDisplayChatEvent
    Display->>Display: Check UserSettings.isPlayerBlocked
    Display->>Display: Filter & skip event creation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🛡️ A shield now rises, swift and clear,
Block those players you don't want near,
Alliances break, emojis fade,
Peace of mind, a blocking blade,
Trade and talk on your own terms made!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main feature—block player functionality—though it's truncated and incomplete, making it partially clear.
Description check ✅ Passed The description is thorough and directly related to the changeset, detailing all major modifications including block/unblock logic, UI changes, and persistence.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/client/graphics/layers/PlayerActionHandler.ts (1)

97-102: Handler follows existing patterns.

The implementation mirrors handleEmbargo(). Small nitpick: recipient.id() already returns string (PlayerID), so String() wrapper is redundant.

✨ Optional simplification
  handleBlockPlayer(recipient: PlayerView, action: "block" | "unblock") {
    this.eventBus.emit(
-      new SendBlockPlayerIntentEvent(String(recipient.id()), action),
+      new SendBlockPlayerIntentEvent(recipient.id(), action),
    );
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PlayerActionHandler.ts` around lines 97 - 102, The
SendBlockPlayerIntentEvent is being constructed with String(recipient.id()) but
recipient.id() already returns a string (PlayerID); remove the redundant
String(...) wrapper in handleBlockPlayer so it calls new
SendBlockPlayerIntentEvent(recipient.id(), action) to match the pattern used by
handleEmbargo and avoid unnecessary conversions.
src/client/graphics/layers/EventsDisplay.ts (3)

420-427: Redundant server-side block check.

The condition myPlayer.isBlockedBy(player) || player.hasBlocked(myPlayer) is redundant. Looking at PlayerImpl.isBlockedBy():

isBlockedBy(other: Player): boolean {
  return other.hasBlocked(this);
}

So myPlayer.isBlockedBy(player) is equivalent to player.hasBlocked(myPlayer). You can simplify to just one check.

♻️ Simplify redundant condition
-        if (myPlayer.isBlockedBy(player) || player.hasBlocked(myPlayer)) {
+        if (player.hasBlocked(myPlayer)) {
           return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/EventsDisplay.ts` around lines 420 - 427, The
condition redundantly checks server-side blocking twice; remove one of the
equivalent calls and keep a single check (either myPlayer.isBlockedBy(player) or
player.hasBlocked(myPlayer)) to filter blocked players. Update the block in
EventsDisplay.ts to only evaluate one of the methods (e.g., use
player.hasBlocked(myPlayer)) and leave the client-side user setting check with
UserSettings.isPlayerBlocked(player.displayName()) unchanged; ensure references
to myPlayer.isBlockedBy and player.hasBlocked are updated accordingly to avoid
duplicate logic.

474-481: Same redundant pattern here.

The condition myPlayer.isBlockedBy(requestor) || requestor.hasBlocked(myPlayer) is also redundant for the same reason.

♻️ Simplify redundant condition
-    if (myPlayer.isBlockedBy(requestor) || requestor.hasBlocked(myPlayer)) {
+    if (requestor.hasBlocked(myPlayer)) {
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/EventsDisplay.ts` around lines 474 - 481, The
blocked-player check in EventsDisplay.ts redundantly tests both
myPlayer.isBlockedBy(requestor) and requestor.hasBlocked(myPlayer); simplify by
leaving the single canonical check (use requestor.hasBlocked(myPlayer)) and
remove the redundant myPlayer.isBlockedBy(...) part, keeping the
UserSettings.isPlayerBlocked(requestor.displayName()) check unchanged; update
the conditional around requestor and myPlayer accordingly so behavior remains
the same but without the duplicate test.

698-705: Same redundant pattern for emoji filtering.

♻️ Simplify redundant condition
-    if (myPlayer.isBlockedBy(sender) || sender.hasBlocked(myPlayer)) {
+    if (sender.hasBlocked(myPlayer)) {
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/EventsDisplay.ts` around lines 698 - 705,
Duplicate blocked-player checks are being performed
(myPlayer.isBlockedBy(sender), sender.hasBlocked(myPlayer), and
userSettings.isPlayerBlocked(sender.displayName())); consolidate them into a
single check to avoid redundancy and reuse the same UserSettings instance.
Create or use a helper (e.g., isBlockedBetween(myPlayer, sender) or a method on
UserSettings) that returns true if any of myPlayer.isBlockedBy(sender),
sender.hasBlocked(myPlayer), or
userSettings.isPlayerBlocked(sender.displayName()) are true, then replace the
three separate checks with one call to that helper wherever emoji/player
filtering occurs (reference myPlayer.isBlockedBy, sender.hasBlocked,
UserSettings, and isPlayerBlocked).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/PlayerPanel.ts`:
- Around line 266-292: handleBlockPlayerClick and handleUnblockPlayerClick
currently mutate UserSettings immediately before sending the intent, causing
potential desync if the server rejects the request; change both handlers to
follow the embargo/alliance pattern: remove the immediate
userSettings.blockPlayer/unblockPlayer calls, only emit
SendBlockPlayerIntentEvent(String(other.id()), "block"|"unblock") and call
this.hide(), and instead update UserSettings in the server-confirmation handler
(the existing success callback that processes server responses for block
intents) so local state is only changed after confirmation.

In `@src/core/game/PlayerImpl.ts`:
- Around line 489-492: The current call to this.breakAlliance(alliance) in
PlayerImpl (after allianceWith) routes to mg.breakAlliance(this, alliance),
which will call markTraitor() unless the other player is already traitor or
disconnected; to avoid penalizing the ally when blocking should be a silent
separation, change this code to use a removal path that doesn't trigger
markTraitor — e.g., add and call a new method like
PlayerImpl.separateAlliance(alliance) or Game.separateAlliance(breaker,
alliance) that removes/unlinks the alliance state on both players without
invoking mg.breakAlliance(...) logic (avoid the branch that checks
other.isTraitor()/isDisconnected() and calls breaker.markTraitor()), or reuse an
existing low-level alliance removal method if one exists; update references so
blocking uses this silent removal instead of breakAlliance.

In `@tests/BlockPlayer.test.ts`:
- Around line 150-157: The test calls p2.createAllianceRequest(p1) while p1
already has p2 blocked, so the request is never created and the subsequent
p1.blockPlayer(p2) is a no-op; modify the test to first unblock p2 from p1 (call
p1.unblockPlayer(p2) or the project-specific equivalent), then call
p2.createAllianceRequest(p1), assert p1.incomingAllianceRequests() is 1, then
call p1.blockPlayer(p2) again and assert p1.incomingAllianceRequests() is 0;
update references to p1.blockPlayer, p1.unblockPlayer (or the correct unblock
method), p2.createAllianceRequest, and p1.incomingAllianceRequests accordingly.

---

Nitpick comments:
In `@src/client/graphics/layers/EventsDisplay.ts`:
- Around line 420-427: The condition redundantly checks server-side blocking
twice; remove one of the equivalent calls and keep a single check (either
myPlayer.isBlockedBy(player) or player.hasBlocked(myPlayer)) to filter blocked
players. Update the block in EventsDisplay.ts to only evaluate one of the
methods (e.g., use player.hasBlocked(myPlayer)) and leave the client-side user
setting check with UserSettings.isPlayerBlocked(player.displayName()) unchanged;
ensure references to myPlayer.isBlockedBy and player.hasBlocked are updated
accordingly to avoid duplicate logic.
- Around line 474-481: The blocked-player check in EventsDisplay.ts redundantly
tests both myPlayer.isBlockedBy(requestor) and requestor.hasBlocked(myPlayer);
simplify by leaving the single canonical check (use
requestor.hasBlocked(myPlayer)) and remove the redundant
myPlayer.isBlockedBy(...) part, keeping the
UserSettings.isPlayerBlocked(requestor.displayName()) check unchanged; update
the conditional around requestor and myPlayer accordingly so behavior remains
the same but without the duplicate test.
- Around line 698-705: Duplicate blocked-player checks are being performed
(myPlayer.isBlockedBy(sender), sender.hasBlocked(myPlayer), and
userSettings.isPlayerBlocked(sender.displayName())); consolidate them into a
single check to avoid redundancy and reuse the same UserSettings instance.
Create or use a helper (e.g., isBlockedBetween(myPlayer, sender) or a method on
UserSettings) that returns true if any of myPlayer.isBlockedBy(sender),
sender.hasBlocked(myPlayer), or
userSettings.isPlayerBlocked(sender.displayName()) are true, then replace the
three separate checks with one call to that helper wherever emoji/player
filtering occurs (reference myPlayer.isBlockedBy, sender.hasBlocked,
UserSettings, and isPlayerBlocked).

In `@src/client/graphics/layers/PlayerActionHandler.ts`:
- Around line 97-102: The SendBlockPlayerIntentEvent is being constructed with
String(recipient.id()) but recipient.id() already returns a string (PlayerID);
remove the redundant String(...) wrapper in handleBlockPlayer so it calls new
SendBlockPlayerIntentEvent(recipient.id(), action) to match the pattern used by
handleEmbargo and avoid unnecessary conversions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c99c516-d8cf-4201-ad5d-088e2fd13560

📥 Commits

Reviewing files that changed from the base of the PR and between c63b304 and baddf1d.

📒 Files selected for processing (13)
  • resources/lang/en.json
  • src/client/Transport.ts
  • src/client/graphics/layers/EventsDisplay.ts
  • src/client/graphics/layers/PlayerActionHandler.ts
  • src/client/graphics/layers/PlayerPanel.ts
  • src/core/GameRunner.ts
  • src/core/Schemas.ts
  • src/core/game/Game.ts
  • src/core/game/GameUpdates.ts
  • src/core/game/GameView.ts
  • src/core/game/PlayerImpl.ts
  • src/core/game/UserSettings.ts
  • tests/BlockPlayer.test.ts

Comment on lines +266 to +292
private handleBlockPlayerClick(
e: Event,
myPlayer: PlayerView,
other: PlayerView,
) {
e.stopPropagation();
const userSettings = new UserSettings();
userSettings.blockPlayer(other.displayName());
this.eventBus.emit(
new SendBlockPlayerIntentEvent(String(other.id()), "block"),
);
this.hide();
}

private handleUnblockPlayerClick(
e: Event,
myPlayer: PlayerView,
other: PlayerView,
) {
e.stopPropagation();
const userSettings = new UserSettings();
userSettings.unblockPlayer(other.displayName());
this.eventBus.emit(
new SendBlockPlayerIntentEvent(String(other.id()), "unblock"),
);
this.hide();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if other intent patterns (embargo, alliance) also mutate local state before server confirmation
rg -n -A5 "handleEmbargoClick|handleAllianceClick" src/client/graphics/layers/PlayerPanel.ts

Repository: openfrontio/OpenFrontIO

Length of output: 1090


🏁 Script executed:

sed -n '246,265p' src/client/graphics/layers/PlayerPanel.ts

Repository: openfrontio/OpenFrontIO

Length of output: 497


🏁 Script executed:

sed -n '184,210p' src/client/graphics/layers/PlayerPanel.ts

Repository: openfrontio/OpenFrontIO

Length of output: 708


🏁 Script executed:

rg -n "blockPlayer|unblockPlayer" src/client --type ts | head -20

Repository: openfrontio/OpenFrontIO

Length of output: 461


🏁 Script executed:

rg -n "SendBlockPlayerIntentEvent\|block-player" src/client --type ts -A3 | head -30

Repository: openfrontio/OpenFrontIO

Length of output: 49


Block handlers mutate local state before server confirmation, inconsistent with embargo/alliance patterns.

handleBlockPlayerClick and handleUnblockPlayerClick mutate UserSettings immediately, then emit the intent event. This differs from handleEmbargoClick and handleAllianceClick, which emit the event directly without touching local state.

If the server rejects the block intent, the client's block list will be out of sync with server state, with no rollback mechanism.

Consider following the embargo/alliance pattern: emit the intent event and only update UserSettings if the server confirms success. Alternatively, document why block state mutation differs from these other operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PlayerPanel.ts` around lines 266 - 292,
handleBlockPlayerClick and handleUnblockPlayerClick currently mutate
UserSettings immediately before sending the intent, causing potential desync if
the server rejects the request; change both handlers to follow the
embargo/alliance pattern: remove the immediate
userSettings.blockPlayer/unblockPlayer calls, only emit
SendBlockPlayerIntentEvent(String(other.id()), "block"|"unblock") and call
this.hide(), and instead update UserSettings in the server-confirmation handler
(the existing success callback that processes server responses for block
intents) so local state is only changed after confirmation.

Comment on lines +489 to +492
const alliance = this.allianceWith(other);
if (alliance) {
this.breakAlliance(alliance);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check breakAlliance implementation to confirm traitor marking
ast-grep --pattern $'breakAlliance($_, $_) {
  $$$
  markTraitor()
  $$$
}'

Repository: openfrontio/OpenFrontIO

Length of output: 49


🏁 Script executed:

rg -t ts "breakAlliance\(" --max-count=20

Repository: openfrontio/OpenFrontIO

Length of output: 1018


🏁 Script executed:

rg -t ts -A 30 "public breakAlliance\(breaker: Player, alliance: MutableAlliance\)" src/core/game/GameImpl.ts

Repository: openfrontio/OpenFrontIO

Length of output: 960


Blocking an ally marks the player as a traitor (unless the ally is already a traitor or disconnected).

Calling this.breakAlliance(alliance) chains to mg.breakAlliance(this, alliance), which executes:

if (!other.isTraitor() && !other.isDisconnected()) {
  breaker.markTraitor();
}

This means blocking an ally incurs the traitor penalty unless the ally is already a traitor or disconnected. Is this the intended behavior? If blocking is meant to be a silent separation, consider using a different code path that does not trigger breakAlliance().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/game/PlayerImpl.ts` around lines 489 - 492, The current call to
this.breakAlliance(alliance) in PlayerImpl (after allianceWith) routes to
mg.breakAlliance(this, alliance), which will call markTraitor() unless the other
player is already traitor or disconnected; to avoid penalizing the ally when
blocking should be a silent separation, change this code to use a removal path
that doesn't trigger markTraitor — e.g., add and call a new method like
PlayerImpl.separateAlliance(alliance) or Game.separateAlliance(breaker,
alliance) that removes/unlinks the alliance state on both players without
invoking mg.breakAlliance(...) logic (avoid the branch that checks
other.isTraitor()/isDisconnected() and calls breaker.markTraitor()), or reuse an
existing low-level alliance removal method if one exists; update references so
blocking uses this silent removal instead of breakAlliance.

Comment on lines +150 to +157
p1.blockPlayer(p2);
expect(p1.outgoingAllianceRequests().length).toBe(0);

p2.createAllianceRequest(p1);
expect(p1.incomingAllianceRequests().length).toBe(1);

p1.blockPlayer(p2);
expect(p1.incomingAllianceRequests().length).toBe(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test logic error causing pipeline failure.

After line 150, p1 already has p2 blocked. Based on test 1 (line 46), p2.canSendAllianceRequest(p1) should be false when p1 has p2 blocked. Yet line 153 calls p2.createAllianceRequest(p1) without checking if it's allowed.

Then line 156 tries to block p2 again (already blocked), which is a no-op. The request created in line 153 is not cleared because blocking only processes on state change.

🐛 Proposed fix - unblock before creating new request
     p1.blockPlayer(p2);
     expect(p1.outgoingAllianceRequests().length).toBe(0);

-    p2.createAllianceRequest(p1);
-    expect(p1.incomingAllianceRequests().length).toBe(1);
-
-    p1.blockPlayer(p2);
+    // Unblock first to reset state, then test incoming request clearing
+    p1.unblockPlayer(p2);
+    for (let i = 0; i < 100; i++) game.executeNextTick(); // Clear cooldowns
+    
+    p2.createAllianceRequest(p1);
+    expect(p1.incomingAllianceRequests().length).toBe(1);
+    
+    p1.blockPlayer(p2);
     expect(p1.incomingAllianceRequests().length).toBe(0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
p1.blockPlayer(p2);
expect(p1.outgoingAllianceRequests().length).toBe(0);
p2.createAllianceRequest(p1);
expect(p1.incomingAllianceRequests().length).toBe(1);
p1.blockPlayer(p2);
expect(p1.incomingAllianceRequests().length).toBe(0);
p1.blockPlayer(p2);
expect(p1.outgoingAllianceRequests().length).toBe(0);
// Unblock first to reset state, then test incoming request clearing
p1.unblockPlayer(p2);
for (let i = 0; i < 100; i++) game.executeNextTick(); // Clear cooldowns
p2.createAllianceRequest(p1);
expect(p1.incomingAllianceRequests().length).toBe(1);
p1.blockPlayer(p2);
expect(p1.incomingAllianceRequests().length).toBe(0);
🧰 Tools
🪛 GitHub Actions: 🧪 CI

[error] 157-157: Test failed: expected incomingAllianceRequests length to be 0 after blocking, but was 1.

🪛 GitHub Check: 🔬 Test

[failure] 157-157: tests/BlockPlayer.test.ts > Block Player Feature > Should reject pending alliance requests when blocking
AssertionError: expected 1 to be +0 // Object.is equality

  • Expected
  • Received
  • 0
  • 1

❯ tests/BlockPlayer.test.ts:157:50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/BlockPlayer.test.ts` around lines 150 - 157, The test calls
p2.createAllianceRequest(p1) while p1 already has p2 blocked, so the request is
never created and the subsequent p1.blockPlayer(p2) is a no-op; modify the test
to first unblock p2 from p1 (call p1.unblockPlayer(p2) or the project-specific
equivalent), then call p2.createAllianceRequest(p1), assert
p1.incomingAllianceRequests() is 1, then call p1.blockPlayer(p2) again and
assert p1.incomingAllianceRequests() is 0; update references to p1.blockPlayer,
p1.unblockPlayer (or the correct unblock method), p2.createAllianceRequest, and
p1.incomingAllianceRequests accordingly.

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

2 participants