feat(battlenode): emit engine-resolved clan/tribe on knownList entries (M-HC-4e)
Prod always emits clan (int ClanType) + tribe (comma-joined int TribeType string, "0" for none) on every knownList entry (battle-traffic_tk2_regular .ndjson). Source both off the resolved engine (SessionBattleEngine.PlayedCardClan/ PlayedCardTribe -> BattleCardBase.Clan/Tribe), so skill-applied clan/tribe changes ride the wire rather than the static card-master value. Thread through KnownListBuilder.BuildPlayedCard + PlayActionsHandler; add clan/tribe to the KnownCardEntry DTO (always present, non-null). Node-side only; no engine edits, drift clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -279,6 +279,44 @@ internal sealed class SessionBattleEngine
|
||||
return card?.SpellChargeCount ?? fallback;
|
||||
}
|
||||
|
||||
/// <summary>The engine-RESOLVED clan of the card whose engine <c>Index</c> == <paramref name="idx"/> on
|
||||
/// <paramref name="playerSeat"/> (M-HC-4e), as the int <c>ClanType</c> ordinal prod sends on the
|
||||
/// knownList entry (e.g. <c>clan:8</c> in the tk2 capture). Reads <see cref="BattleCardBase.Clan"/>, whose
|
||||
/// getter returns the skill-applied clan (<c>SkillApplyInformation.ClanSkinInfo.Last()</c> when a skill
|
||||
/// changed it, else <c>BaseParameter.Clan</c>) — so a <c>change_affiliation</c> is reflected, which is WHY
|
||||
/// the engine value (not the static card-master clan) is the faithful one to emit.
|
||||
/// <para>Same post-resolution zone search + degrade-to-<paramref name="fallback"/> contract as
|
||||
/// <see cref="PlayedCardCost"/>: no engine / no card → fallback, so a non-engine session never crashes.</para></summary>
|
||||
public int PlayedCardClan(bool playerSeat, int idx, int fallback = 0)
|
||||
{
|
||||
if (_mgr is null) return fallback;
|
||||
var card = FindByIndex(Seat(playerSeat), idx);
|
||||
return card is null ? fallback : (int)card.Clan;
|
||||
}
|
||||
|
||||
/// <summary>The engine-RESOLVED tribe of the card whose engine <c>Index</c> == <paramref name="idx"/> on
|
||||
/// <paramref name="playerSeat"/> (M-HC-4e), in the EXACT wire string form prod sends: the comma-joined
|
||||
/// int <c>TribeType</c> ordinals (e.g. <c>tribe:"7,16"</c> for MACHINE+SCHOOL in the tk2 capture), and
|
||||
/// <c>"0"</c> when the card has no tribe (== <c>TribeType.ALL == 0</c> — prod never sends empty/omitted;
|
||||
/// the client reads it via <c>item.Value.ToString()</c>, NetworkBattleReceiver.cs:2382). Reads
|
||||
/// <see cref="BattleCardBase.Tribe"/>, whose getter folds in any skill-applied tribe CHANGE/ADD over
|
||||
/// <c>BaseParameter.Tribe</c> (and drops ALL when the resolved list has ≥2 entries) — so the wire carries
|
||||
/// the LIVE tribe, the faithful value over the static card-master one.
|
||||
/// <para>Same post-resolution zone search as <see cref="PlayedCardCost"/>; no engine / no card → "" (an
|
||||
/// engine that isn't owned this session emits no card, so the caller's BuildPlayedCard never fires).</para></summary>
|
||||
public string PlayedCardTribe(bool playerSeat, int idx)
|
||||
{
|
||||
if (_mgr is null) return string.Empty;
|
||||
var card = FindByIndex(Seat(playerSeat), idx);
|
||||
if (card is null) return string.Empty;
|
||||
var tribe = card.Tribe;
|
||||
// Prod's no-tribe form is the single "0" (TribeType.ALL == 0), never an empty string; an empty list
|
||||
// (defensive) renders the same "0".
|
||||
return tribe is null || tribe.Count == 0
|
||||
? "0"
|
||||
: string.Join(",", tribe.Select(t => (int)t));
|
||||
}
|
||||
|
||||
// Locate the card with the given engine Index across the seat's post-resolution zones. Order matters
|
||||
// only for disambiguation; Index is unique per card so the first hit is the card. In-play (followers)
|
||||
// and cemetery (spells) are where a just-resolved play lands; hand is the pre-resolution fallback.
|
||||
|
||||
Reference in New Issue
Block a user