-
Notifications
You must be signed in to change notification settings - Fork 976
feat: add territory hover highlight setting #3701
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 1 commit
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 |
|---|---|---|
|
|
@@ -12,11 +12,17 @@ import { | |
| import { euclDistFN, TileRef } from "../../../core/game/GameMap"; | ||
| import { GameUpdateType } from "../../../core/game/GameUpdates"; | ||
| import { GameView, PlayerView } from "../../../core/game/GameView"; | ||
| import { | ||
| TERRITORY_HIGHLIGHT_KEY, | ||
| USER_SETTINGS_CHANGED_EVENT, | ||
| UserSettings, | ||
| } from "../../../core/game/UserSettings"; | ||
| import { PseudoRandom } from "../../../core/PseudoRandom"; | ||
| import { | ||
| AlternateViewEvent, | ||
| DragEvent, | ||
| MouseOverEvent, | ||
| TerritoryHighlightKeyEvent, | ||
| } from "../../InputHandler"; | ||
| import { FrameProfiler } from "../FrameProfiler"; | ||
| import { TransformHandler } from "../TransformHandler"; | ||
|
|
@@ -47,6 +53,8 @@ export class TerritoryLayer implements Layer { | |
| private highlightedTerritory: PlayerView | null = null; | ||
|
|
||
| private alternativeView = false; | ||
| private highlightMode: "always" | "onKeyPress" | "never"; | ||
| private highlightKeyHeld = false; | ||
| private lastDragTime = 0; | ||
| private nodrawDragDuration = 200; | ||
| private lastMousePosition: { x: number; y: number } | null = null; | ||
|
|
@@ -63,6 +71,7 @@ export class TerritoryLayer implements Layer { | |
| ) { | ||
| this.theme = game.config().theme(); | ||
| this.cachedTerritoryPatternsEnabled = undefined; | ||
| this.highlightMode = new UserSettings().territoryHighlight(); | ||
| } | ||
|
|
||
| shouldTransform(): boolean { | ||
|
|
@@ -329,10 +338,28 @@ export class TerritoryLayer implements Layer { | |
| this.eventBus.on(AlternateViewEvent, (e) => { | ||
| this.alternativeView = e.alternateView; | ||
| }); | ||
| this.eventBus.on(TerritoryHighlightKeyEvent, (e) => { | ||
| this.highlightKeyHeld = e.active; | ||
| this.updateHighlightedTerritory(); | ||
| }); | ||
| this.eventBus.on(DragEvent, (e) => { | ||
| // TODO: consider re-enabling this on mobile or low end devices for smoother dragging. | ||
| // this.lastDragTime = Date.now(); | ||
| }); | ||
|
|
||
| globalThis.addEventListener?.( | ||
| `${USER_SETTINGS_CHANGED_EVENT}:${TERRITORY_HIGHLIGHT_KEY}`, | ||
| ((e: CustomEvent) => { | ||
| const value = e.detail; | ||
| if (value === "always" || value === "onKeyPress") { | ||
| this.highlightMode = value; | ||
| } else { | ||
| this.highlightMode = "never"; | ||
| } | ||
| this.updateHighlightedTerritory(); | ||
| }) as EventListener, | ||
| ); | ||
|
Comment on lines
+361
to
+364
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. Settings listener added on The listener attached in A small fix is to keep a 🧹 Suggested teardown+ destroy() {
+ globalThis.removeEventListener?.(
+ `${USER_SETTINGS_CHANGED_EVENT}:${TERRITORY_HIGHLIGHT_KEY}`,
+ this.onHighlightSettingChanged,
+ );
+ }🤖 Prompt for AI Agents |
||
|
|
||
| this.redraw(); | ||
| } | ||
|
|
||
|
|
@@ -342,7 +369,17 @@ export class TerritoryLayer implements Layer { | |
| } | ||
|
|
||
| private updateHighlightedTerritory() { | ||
| if (!this.alternativeView) { | ||
| const shouldHighlight = | ||
| this.highlightMode === "always" || | ||
| (this.highlightMode === "onKeyPress" && this.highlightKeyHeld) || | ||
| this.alternativeView; | ||
|
|
||
| if (!shouldHighlight) { | ||
| if (this.highlightedTerritory) { | ||
| const prev = this.highlightedTerritory; | ||
| this.highlightedTerritory = null; | ||
| this.updateHighlightAlpha(prev, null); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -368,17 +405,32 @@ export class TerritoryLayer implements Layer { | |
| } | ||
|
|
||
| if (previousTerritory?.id() !== this.highlightedTerritory?.id()) { | ||
| const territories: PlayerView[] = []; | ||
| if (previousTerritory) { | ||
| territories.push(previousTerritory); | ||
| } | ||
| if (this.highlightedTerritory) { | ||
| territories.push(this.highlightedTerritory); | ||
| } | ||
| this.redrawBorder(...territories); | ||
| this.updateHighlightAlpha(previousTerritory, this.highlightedTerritory); | ||
| } | ||
| } | ||
|
|
||
| private updateHighlightAlpha( | ||
| oldPlayer: PlayerView | null, | ||
| newPlayer: PlayerView | null, | ||
| ) { | ||
| const data = this.imageData.data; | ||
| const oldID = oldPlayer?.id(); | ||
| const newID = newPlayer?.id(); | ||
| this.game.forEachTile((tile) => { | ||
| const offset = tile * 4; | ||
| const alpha = data[offset + 3]; | ||
| // Only update non-border territory fill tiles (alpha 150 or 230) | ||
| if (alpha !== 150 && alpha !== 230) return; | ||
| if (!this.game.hasOwner(tile)) return; | ||
| const ownerID = (this.game.owner(tile) as PlayerView).id(); | ||
| if (newID !== undefined && ownerID === newID) { | ||
| data[offset + 3] = 230; | ||
| } else if (alpha === 230 && oldID !== undefined && ownerID === oldID) { | ||
| data[offset + 3] = 150; | ||
| } | ||
| }); | ||
| } | ||
|
Comment on lines
+415
to
+434
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Look for tile-iteration methods on PlayerView and related classes.
rg -nP --type=ts -C2 '\b(tiles|ownedTiles|allTiles|borderTiles)\s*\(' -g 'src/core/game/**'
ast-grep --pattern $'class PlayerView {
$$$
}'Repository: openfrontio/OpenFrontIO Length of output: 25984 🏁 Script executed: cd src/core/game && grep -n "^\s*\(tiles\|ownedTiles\|allTiles\)(" GameView.ts | head -20Repository: openfrontio/OpenFrontIO Length of output: 49
However, To address this, consider:
🤖 Prompt for AI Agents
Author
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. Addressed in 6b19661. The main bottleneck was calling Replaced with The
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.
const oldSmallID = oldPlayer?.smallID() ?? -1;
const newSmallID = newPlayer?.smallID() ?? -1;
this.game.forEachTile((tile) => {
const offset = tile * 4;
const alpha = data[offset + 3];
if (alpha !== 150 && alpha !== 230) return; // skips ~60% early
const tileOwner = this.game.ownerID(tile); // Uint16Array read + bitmask
if (tileOwner === newSmallID) { ... } // int comparison
else if (alpha === 230 && tileOwner === oldSmallID) { ... }
});The per-tile cost is now just a byte read, an early return for most tiles, a
🧠 Learnings usedIf you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
||
|
|
||
| private getTerritoryAtCell(cell: { x: number; y: number }) { | ||
| const tile = this.game.ref(cell.x, cell.y); | ||
| if (!tile) { | ||
|
|
@@ -559,15 +611,12 @@ export class TerritoryLayer implements Layer { | |
| return; | ||
| } | ||
| const owner = this.game.owner(tile) as PlayerView; | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| const isHighlighted = | ||
| this.highlightedTerritory && | ||
| this.highlightedTerritory.id() === owner.id(); | ||
| const myPlayer = this.game.myPlayer(); | ||
|
|
||
| if (this.game.isBorder(tile)) { | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| const playerIsFocused = owner && this.game.focusedPlayer() === owner; | ||
| if (myPlayer) { | ||
| const alternativeColor = this.alternateViewColor(owner); | ||
| this.paintTile(this.alternativeImageData, tile, alternativeColor, 255); | ||
|
|
@@ -589,7 +638,8 @@ export class TerritoryLayer implements Layer { | |
| // Alternative view only shows borders. | ||
| this.clearAlternativeTile(tile); | ||
|
|
||
| this.paintTile(this.imageData, tile, owner.territoryColor(tile), 150); | ||
| const alpha = isHighlighted ? 230 : 150; | ||
| this.paintTile(this.imageData, tile, owner.territoryColor(tile), alpha); | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
The "default H" hint will become stale after rebinding.
This label literally says "default H", but the keybind is user-configurable. If someone rebinds
highlightTerritoryto another key, the select option will keep advertising H, which is a small but real source of confusion. Consider dropping the key hint here (the keybinds tab is the source of truth) or passing the current key in as an ICU argument.✏️ Possible simplification
🤖 Prompt for AI Agents
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.
Intentionally keeping the "default H" hint. The benefit is that a user can see the default key right in the dropdown without having to navigate to the keybinds tab first. If they rebind it, they already know where to find keybind settings — but for the majority of users who never rebind, this tells them exactly which key to press at a glance.
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.
@luctrate, that's a fair point — showing the default key inline reduces friction for the majority of users who never touch keybind settings. The trade-off (slightly stale label for the small group who rebind) is reasonable given that those users already know where keybinds live.Makes sense, thanks for the context!
🧠 Learnings used