refactor(battle-node): switch MsgEnvelope.Body to IMsgBody, migrate all sites
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using SVSim.BattleNode.Protocol;
|
||||
using SVSim.BattleNode.Protocol.Bodies;
|
||||
|
||||
namespace SVSim.BattleNode.Lifecycle;
|
||||
|
||||
@@ -7,21 +8,9 @@ namespace SVSim.BattleNode.Lifecycle;
|
||||
/// (Matched / BattleStart / Deal / Swap response / Ready) plus a trivial opponent TurnStart
|
||||
/// the dispatch pushes after the player's TurnEnd. The values are templated from the TK2
|
||||
/// captures at <c>data_dumps/captures/battle-traffic_tk2_regular.ndjson</c> — anything
|
||||
/// hardcoded here came from a real prod frame.
|
||||
/// hardcoded here came from a real prod frame, with names + provenance in
|
||||
/// <see cref="ScriptedProfiles"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>"Scripted" means the opponent never reacts to your plays. We push enough to land
|
||||
/// you on the mulligan screen, run a real mulligan exchange, give you turn 1, transition
|
||||
/// to "Opponent's turn…" after your <c>TurnEnd</c>, and then sit there indefinitely. This
|
||||
/// is the documented v1 stopping point.</para>
|
||||
/// <para>
|
||||
/// All builders go through <see cref="EnvelopeForPush"/>, which injects
|
||||
/// <c>resultCode = 1</c> into every body. The client's <c>OnReceived</c> drops any
|
||||
/// synchronize push whose <c>resultCode != Success</c> (absent counts as None=0); leaving
|
||||
/// it off silently breaks the state machine without surfacing an error.
|
||||
/// </para>
|
||||
/// <para>To make this less scripted: see the project README §"Where to extend".</para>
|
||||
/// </remarks>
|
||||
public static class ScriptedLifecycle
|
||||
{
|
||||
/// <summary>
|
||||
@@ -29,7 +18,7 @@ public static class ScriptedLifecycle
|
||||
/// every card-master version we care about, so the client can render it without
|
||||
/// triggering a card-master-mismatch error.
|
||||
/// </summary>
|
||||
public static readonly long DummyCardId = 100011010;
|
||||
public const long DummyCardId = 100011010L;
|
||||
|
||||
/// <summary>
|
||||
/// Viewer id we present as the opponent on every scripted push. Out-of-range vs. real
|
||||
@@ -37,87 +26,27 @@ public static class ScriptedLifecycle
|
||||
/// </summary>
|
||||
public const long FakeOpponentViewerId = 999_999_999L;
|
||||
|
||||
public static MsgEnvelope BuildMatched(long playerViewerId, long opponentViewerId, string battleId)
|
||||
{
|
||||
var body = new Dictionary<string, object?>
|
||||
{
|
||||
["selfInfo"] = new Dictionary<string, object?>
|
||||
{
|
||||
["country_code"] = "KOR",
|
||||
["userName"] = "Player",
|
||||
["sleeveId"] = "3000011",
|
||||
["emblemId"] = "701441011",
|
||||
["degreeId"] = "300003",
|
||||
["fieldId"] = 43,
|
||||
["isOfficial"] = 0,
|
||||
["oppoId"] = opponentViewerId,
|
||||
["seed"] = 17548138L,
|
||||
},
|
||||
["oppoInfo"] = new Dictionary<string, object?>
|
||||
{
|
||||
["country_code"] = "JPN",
|
||||
["userName"] = "Opponent",
|
||||
["sleeveId"] = "704141010",
|
||||
["emblemId"] = "400001100",
|
||||
["degreeId"] = "120027",
|
||||
["fieldId"] = 5,
|
||||
["isOfficial"] = 0,
|
||||
["oppoId"] = playerViewerId,
|
||||
["seed"] = 17548138L,
|
||||
["oppoDeckCount"] = 30,
|
||||
},
|
||||
["selfDeck"] = BuildDummyDeck(),
|
||||
};
|
||||
return EnvelopeForPush(NetworkBattleUri.Matched, body, bid: battleId);
|
||||
}
|
||||
public static MsgEnvelope BuildMatched(long playerViewerId, long opponentViewerId, string battleId) =>
|
||||
EnvelopeForPush(NetworkBattleUri.Matched,
|
||||
new MatchedBody(
|
||||
SelfInfo: ScriptedProfiles.PlayerMatchedProfile with { OppoId = opponentViewerId },
|
||||
OppoInfo: ScriptedProfiles.OpponentMatchedProfile with { OppoId = playerViewerId },
|
||||
SelfDeck: BuildDummyDeck()),
|
||||
bid: battleId);
|
||||
|
||||
public static MsgEnvelope BuildBattleStart(long playerViewerId)
|
||||
{
|
||||
var body = new Dictionary<string, object?>
|
||||
{
|
||||
["turnState"] = 0, // player goes first
|
||||
["battleType"] = 11, // TK2 NetworkBattleType
|
||||
["selfInfo"] = new Dictionary<string, object?>
|
||||
{
|
||||
["rank"] = "10",
|
||||
["battlePoint"] = "6270",
|
||||
["classId"] = "1",
|
||||
["charaId"] = "1",
|
||||
["cardMasterName"] = "card_master_node_10015",
|
||||
},
|
||||
["oppoInfo"] = new Dictionary<string, object?>
|
||||
{
|
||||
["rank"] = "1",
|
||||
["isMasterRank"] = "0",
|
||||
["battlePoint"] = 0,
|
||||
["masterPoint"] = "0",
|
||||
["classId"] = "8",
|
||||
["charaId"] = "8",
|
||||
["cardMasterName"] = "card_master_node_10015",
|
||||
},
|
||||
};
|
||||
return EnvelopeForPush(NetworkBattleUri.BattleStart, body);
|
||||
}
|
||||
public static MsgEnvelope BuildBattleStart(long playerViewerId) =>
|
||||
EnvelopeForPush(NetworkBattleUri.BattleStart,
|
||||
new BattleStartBody(
|
||||
TurnState: 0, // player goes first
|
||||
BattleType: 11, // TK2 NetworkBattleType
|
||||
SelfInfo: ScriptedProfiles.PlayerBattleStartProfile,
|
||||
OppoInfo: ScriptedProfiles.OpponentBattleStartProfile));
|
||||
|
||||
public static MsgEnvelope BuildDeal()
|
||||
{
|
||||
var body = new Dictionary<string, object?>
|
||||
{
|
||||
["self"] = new List<object?>
|
||||
{
|
||||
new Dictionary<string, object?> { ["pos"] = 0, ["idx"] = 1 },
|
||||
new Dictionary<string, object?> { ["pos"] = 1, ["idx"] = 2 },
|
||||
new Dictionary<string, object?> { ["pos"] = 2, ["idx"] = 3 },
|
||||
},
|
||||
["oppo"] = new List<object?>
|
||||
{
|
||||
new Dictionary<string, object?> { ["pos"] = 0, ["idx"] = 1 },
|
||||
new Dictionary<string, object?> { ["pos"] = 1, ["idx"] = 2 },
|
||||
new Dictionary<string, object?> { ["pos"] = 2, ["idx"] = 3 },
|
||||
},
|
||||
};
|
||||
return EnvelopeForPush(NetworkBattleUri.Deal, body);
|
||||
}
|
||||
public static MsgEnvelope BuildDeal() =>
|
||||
EnvelopeForPush(NetworkBattleUri.Deal,
|
||||
new DealBody(
|
||||
Self: new[] { new PosIdx(0, 1), new PosIdx(1, 2), new PosIdx(2, 3) },
|
||||
Oppo: new[] { new PosIdx(0, 1), new PosIdx(1, 2), new PosIdx(2, 3) }));
|
||||
|
||||
/// <summary>
|
||||
/// Initial 3-card hand idxs from <see cref="BuildDeal"/>. Each position in this array
|
||||
@@ -144,81 +73,55 @@ public static class ScriptedLifecycle
|
||||
return hand;
|
||||
}
|
||||
|
||||
public static MsgEnvelope BuildSwapResponse(IReadOnlyList<long> hand)
|
||||
{
|
||||
var body = new Dictionary<string, object?>
|
||||
{
|
||||
["self"] = BuildPosIdxList(hand),
|
||||
};
|
||||
return EnvelopeForPush(NetworkBattleUri.Swap, body);
|
||||
}
|
||||
public static MsgEnvelope BuildSwapResponse(IReadOnlyList<long> hand) =>
|
||||
EnvelopeForPush(NetworkBattleUri.Swap,
|
||||
new SwapResponseBody(Self: BuildPosIdxList(hand)));
|
||||
|
||||
public static MsgEnvelope BuildReady(IReadOnlyList<long> hand)
|
||||
{
|
||||
var body = new Dictionary<string, object?>
|
||||
{
|
||||
["self"] = BuildPosIdxList(hand),
|
||||
// Opponent hand stays at the static 3 cards for v1.
|
||||
["oppo"] = BuildPosIdxList(InitialHand),
|
||||
["idxChangeSeed"] = 771335280,
|
||||
["spin"] = 243,
|
||||
};
|
||||
return EnvelopeForPush(NetworkBattleUri.Ready, body);
|
||||
}
|
||||
public static MsgEnvelope BuildReady(IReadOnlyList<long> hand) =>
|
||||
EnvelopeForPush(NetworkBattleUri.Ready,
|
||||
new ReadyBody(
|
||||
Self: BuildPosIdxList(hand),
|
||||
Oppo: BuildPosIdxList(InitialHand),
|
||||
IdxChangeSeed: ScriptedProfiles.ReadyIdxChangeSeed,
|
||||
Spin: ScriptedProfiles.ReadySpin));
|
||||
|
||||
/// <summary>
|
||||
/// Generic TurnStart push used to transition the client into "Opponent's turn…" state
|
||||
/// after the player's TurnEnd. v1 doesn't simulate the opponent — once this lands the
|
||||
/// client sits at the opponent-turn display indefinitely.
|
||||
/// </summary>
|
||||
public static MsgEnvelope BuildOpponentTurnStart()
|
||||
{
|
||||
var body = new Dictionary<string, object?>
|
||||
{
|
||||
["spin"] = 100,
|
||||
};
|
||||
return EnvelopeForPush(NetworkBattleUri.TurnStart, body);
|
||||
}
|
||||
public static MsgEnvelope BuildOpponentTurnStart() =>
|
||||
EnvelopeForPush(NetworkBattleUri.TurnStart,
|
||||
new OpponentTurnStartBody(Spin: ScriptedProfiles.OpponentTurnStartSpin));
|
||||
|
||||
private static List<object?> BuildPosIdxList(IReadOnlyList<long> hand)
|
||||
private static IReadOnlyList<PosIdx> BuildPosIdxList(IReadOnlyList<long> hand)
|
||||
{
|
||||
var list = new List<object?>(hand.Count);
|
||||
var list = new List<PosIdx>(hand.Count);
|
||||
for (var pos = 0; pos < hand.Count; pos++)
|
||||
{
|
||||
list.Add(new Dictionary<string, object?> { ["pos"] = pos, ["idx"] = (int)hand[pos] });
|
||||
list.Add(new PosIdx(Pos: pos, Idx: (int)hand[pos]));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<object?> BuildDummyDeck()
|
||||
private static IReadOnlyList<DeckCardRef> BuildDummyDeck()
|
||||
{
|
||||
var deck = new List<object?>(30);
|
||||
var deck = new List<DeckCardRef>(30);
|
||||
for (var i = 1; i <= 30; i++)
|
||||
{
|
||||
deck.Add(new Dictionary<string, object?>
|
||||
{
|
||||
["idx"] = i,
|
||||
["cardId"] = DummyCardId,
|
||||
});
|
||||
deck.Add(new DeckCardRef(Idx: i, CardId: DummyCardId));
|
||||
}
|
||||
return deck;
|
||||
}
|
||||
|
||||
private static MsgEnvelope EnvelopeForPush(NetworkBattleUri uri, Dictionary<string, object?> body, string? bid = null)
|
||||
{
|
||||
// Synchronize-push routing in the client's OnReceived drops any frame whose
|
||||
// resultCode != Success (1). Absent counts as 0 (None) and is also dropped — so we
|
||||
// MUST include it on every scripted push, not just InitNetwork ack / BattleFinish.
|
||||
// See server-to-client.md §"Routing in OnReceived" and the matching prod captures.
|
||||
body["resultCode"] = (int)ReceiveNodeResultCode.Success;
|
||||
return new MsgEnvelope(uri,
|
||||
private static MsgEnvelope EnvelopeForPush(NetworkBattleUri uri, IMsgBody body, string? bid = null) =>
|
||||
new(uri,
|
||||
ViewerId: FakeOpponentViewerId,
|
||||
Uuid: "node-stub",
|
||||
Bid: bid,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
PubSeq: null,
|
||||
PlaySeq: null, // OutboundSequencer.AssignAndArchive stamps this
|
||||
PlaySeq: null,
|
||||
Body: body);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user