Name emblems and boons instead of showing "Card #N"
Command-zone objects such as boons have small grpIds that aren't cards.
Name them from the log's name id (e.g. "The Ring") or the card that
created them ("Swiftspear's Teachings (boon)"), and preview the source
card on hover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f16a67e042
commit
d9ef771db8
4 files changed
+62
-10
No files matched your search
@@ -6,7 +6,7 @@ import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass, field, replace
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
@@ -117,6 +117,24 @@ def open_card_db(db: Path | None, copy_dir: Path, platform: str = sys.platform)
|
||||
return CardDB(db)
|
||||
|
||||
|
||||
def command_object_info(cards: "CardDB", obj: dict) -> CardInfo:
|
||||
"""Display info for a non-card command-zone object (an emblem, boon, ...).
|
||||
|
||||
These have their own small grpIds that aren't cards (e.g. 6 for a boon), so naming them by
|
||||
grpId gives "Card #6". Use the object's own name if the log gives one, otherwise the card that
|
||||
created it. The returned grp_id is the source card's, so hovering previews that card.
|
||||
"""
|
||||
kind = (obj.get("type") or "").removeprefix("GameObjectType_") or "Emblem"
|
||||
source = obj.get("objectSourceGrpId")
|
||||
source_info = cards.get(source) if source else None
|
||||
name = cards.loc(obj["name"]) if obj.get("name") else None
|
||||
if not name:
|
||||
name = f"{source_info.name} ({kind.lower()})" if source_info else kind
|
||||
# Without a source card there's nothing sensible to preview (grp_id 0 = no hover preview).
|
||||
base = source_info or CardInfo(grp_id=0, name=name)
|
||||
return replace(base, name=name, mana=(), types=(kind,), is_token=False)
|
||||
|
||||
|
||||
def unknown_card(grp_id: int) -> CardInfo:
|
||||
return CardInfo(grp_id=grp_id, name=f"Unknown card #{grp_id}")
|
||||
|
||||
@@ -169,6 +187,17 @@ class CardDB:
|
||||
colors=tuple(c for c in WUBRG if c in color_set),
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def loc(self, loc_id: int) -> str | None:
|
||||
"""A localized (English) string by LocId, e.g. an emblem's name."""
|
||||
if self._conn is None:
|
||||
return None
|
||||
row = self._conn.execute(
|
||||
"SELECT Loc FROM Localizations_enUS WHERE LocId = ? ORDER BY Formatted = 1 DESC LIMIT 1",
|
||||
(loc_id,),
|
||||
).fetchone()
|
||||
return _strip_tags(row[0]) if row and row[0] else None
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def counter_name(self, counter_type: int) -> str:
|
||||
if self._conn is not None:
|
||||
|
||||
@@ -295,17 +295,17 @@ class GameState:
|
||||
if seat := obj.get("ownerSeatId"):
|
||||
self.commander_casts[seat] += 1
|
||||
|
||||
def emblems(self, seat: int) -> Counter[int]:
|
||||
"""Emblems and other non-card objects in the seat's command zone (commanders excluded)."""
|
||||
out: Counter[int] = Counter()
|
||||
def emblems(self, seat: int) -> list[dict[str, Any]]:
|
||||
"""Emblems, boons and other non-card objects in the seat's command zone (commanders excluded)."""
|
||||
out = []
|
||||
for z in self.zones.values():
|
||||
if z.type != "ZoneType_Command":
|
||||
continue
|
||||
for iid in z.ids:
|
||||
obj = self.objects.get(iid)
|
||||
if obj and obj.get("grpId") and obj.get("type") != "GameObjectType_Card" \
|
||||
if obj and obj.get("type") != "GameObjectType_Card" \
|
||||
and obj.get("ownerSeatId", obj.get("controllerSeatId")) == seat:
|
||||
out[obj["grpId"]] += 1
|
||||
out.append(obj)
|
||||
return out
|
||||
|
||||
def _land_rules(self) -> dict:
|
||||
|
||||
@@ -15,7 +15,7 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from ..carddb import CardDB
|
||||
from ..carddb import CardDB, command_object_info
|
||||
from ..gamestate import GameState, MatchTracker
|
||||
from . import theme
|
||||
from .card_list import CardList, Row, Section
|
||||
@@ -164,8 +164,12 @@ class PlayerCard(QFrame):
|
||||
self.commander.setVisible(t.on("commander") and bool(status))
|
||||
|
||||
emblems = g.emblems(seat)
|
||||
counts: dict[str, list] = {} # name -> [info, count]
|
||||
for obj in emblems:
|
||||
info = command_object_info(self.cards, obj)
|
||||
counts.setdefault(info.name, [info, 0])[1] += 1
|
||||
self.emblems.set_sections([Section("emblems", "Emblems", [
|
||||
Row(self.cards.get(grp), n) for grp, n in emblems.items()
|
||||
Row(info, n) for info, n in counts.values()
|
||||
])] if emblems else [])
|
||||
self.emblems.setVisible(t.on("emblems") and bool(emblems))
|
||||
|
||||
|
||||
+21
-2
@@ -211,8 +211,8 @@ def test_commanders_are_not_emblems():
|
||||
g = t.game
|
||||
assert g.commander_status(ME) == [(8000, "command zone")]
|
||||
assert g.commander_status(OPP) == [(9000, "command zone")]
|
||||
assert g.emblems(OPP) == {7777: 1}
|
||||
assert g.emblems(ME) == {}
|
||||
assert [o["grpId"] for o in g.emblems(OPP)] == [7777]
|
||||
assert g.emblems(ME) == []
|
||||
assert sum(t.my_library_remaining().values()) == 3 # the commander isn't part of the library
|
||||
|
||||
# Opponent casts their commander from the command zone: it's on the stack, tax goes up.
|
||||
@@ -247,3 +247,22 @@ def test_known_cards_never_exceed_zone_size():
|
||||
t.handle(GreMessages([gsm(2, zones=[zone(OPP_HAND, "Hand", [201], OPP)], objects=[
|
||||
card(400, 7007, OPP_HAND, otype="RevealedCard"), card(401, 7008, OPP_HAND, otype="RevealedCard")])]))
|
||||
assert sum(t.game.opp_known_in("ZoneType_Hand").values()) == 1
|
||||
|
||||
|
||||
def test_command_zone_objects_are_named_from_log_or_source_card():
|
||||
from mtga_assistant.carddb import command_object_info
|
||||
|
||||
class Cards:
|
||||
def get(self, grp):
|
||||
return CardInfo(grp_id=grp, name={97038: "Swiftspear's Teachings", 87496: "The Ring"}.get(grp, f"Card #{grp}"))
|
||||
|
||||
def loc(self, loc_id):
|
||||
return {732510: "The Ring"}.get(loc_id)
|
||||
|
||||
boon = {"grpId": 6, "type": "GameObjectType_Boon", "objectSourceGrpId": 97038}
|
||||
emblem = {"grpId": 2, "type": "GameObjectType_Emblem", "objectSourceGrpId": 87496, "name": 732510}
|
||||
bare = {"grpId": 3, "type": "GameObjectType_Emblem"}
|
||||
b, e, x = (command_object_info(Cards(), o) for o in (boon, emblem, bare))
|
||||
assert (b.name, b.types, b.grp_id) == ("Swiftspear's Teachings (boon)", ("Boon",), 97038) # hover shows the source card
|
||||
assert (e.name, e.types, e.grp_id) == ("The Ring", ("Emblem",), 87496)
|
||||
assert x.name == "Emblem" and x.grp_id == 0 # no source card: nothing to preview
|
||||
Reference in new issue
Block a user