refactor(battle-node): extract frame factories into BattleFrames
This commit is contained in:
@@ -67,7 +67,7 @@ public sealed class BattleSession
|
||||
try
|
||||
{
|
||||
await survivor.PushAsync(
|
||||
BuildBattleFinish(BattleResult.DisconnectWin), noStock: true, cancellation)
|
||||
BattleFrames.BuildBattleFinish(BattleResult.DisconnectWin), noStock: true, cancellation)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -143,7 +143,7 @@ public sealed class BattleSession
|
||||
switch (env.Uri)
|
||||
{
|
||||
case NetworkBattleUri.InitNetwork when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitNetwork:
|
||||
result.Add(new DispatchRoute(from, BuildAck(NetworkBattleUri.InitNetwork), true));
|
||||
result.Add(new DispatchRoute(from, BattleFrames.BuildAck(NetworkBattleUri.InitNetwork), true));
|
||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingInitBattle;
|
||||
break;
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class BattleSession
|
||||
case NetworkBattleUri.InitBattle
|
||||
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||
// Ack only — NO Matched push.
|
||||
result.Add(new DispatchRoute(from, BuildAck(NetworkBattleUri.InitBattle), true));
|
||||
result.Add(new DispatchRoute(from, BattleFrames.BuildAck(NetworkBattleUri.InitBattle), true));
|
||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingLoaded;
|
||||
break;
|
||||
|
||||
@@ -194,7 +194,7 @@ public sealed class BattleSession
|
||||
// Judge to sender ONLY (not broadcast — there's no real other side).
|
||||
// The client's JudgeOperation → ControlTurnStartPlayer flips back to
|
||||
// the local AI's turn after this Judge arrives.
|
||||
result.Add(new DispatchRoute(from, BuildJudgeBroadcast(), false));
|
||||
result.Add(new DispatchRoute(from, BattleFrames.BuildJudgeBroadcast(), false));
|
||||
break;
|
||||
|
||||
case NetworkBattleUri.InitBattle when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||
@@ -226,7 +226,7 @@ public sealed class BattleSession
|
||||
|
||||
case NetworkBattleUri.Swap when phaseFrom?.Phase == BattleSessionPhase.AwaitingSwap:
|
||||
{
|
||||
var hand = ScriptedLifecycle.ComputeHandAfterSwap(ExtractIdxList(env));
|
||||
var hand = ScriptedLifecycle.ComputeHandAfterSwap(BattleFrames.ExtractIdxList(env));
|
||||
// SwapResponse is always immediate — it completes the sender's own mulligan UI.
|
||||
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
||||
_state.PostSwapHands[from] = hand;
|
||||
@@ -257,8 +257,8 @@ public sealed class BattleSession
|
||||
case NetworkBattleUri.TurnEnd when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
||||
if (Type == BattleType.Pvp && BothAfterReady())
|
||||
{
|
||||
var turnEndBroadcast = BuildTurnEndBroadcast();
|
||||
var judgeBroadcast = BuildJudgeBroadcast();
|
||||
var turnEndBroadcast = BattleFrames.BuildTurnEndBroadcast();
|
||||
var judgeBroadcast = BattleFrames.BuildJudgeBroadcast();
|
||||
result.Add(new DispatchRoute(from, turnEndBroadcast, false));
|
||||
result.Add(new DispatchRoute(other, turnEndBroadcast, false));
|
||||
result.Add(new DispatchRoute(from, judgeBroadcast, false));
|
||||
@@ -282,8 +282,8 @@ public sealed class BattleSession
|
||||
// so the RunAsync cascade doesn't synthesize a follow-up BattleFinish.
|
||||
case NetworkBattleUri.TurnEndFinal when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
||||
result.Add(new DispatchRoute(other, env, false));
|
||||
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.LifeWin), true));
|
||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.LifeLose), true));
|
||||
result.Add(new DispatchRoute(from, BattleFrames.BuildBattleFinish(BattleResult.LifeWin), true));
|
||||
result.Add(new DispatchRoute(other, BattleFrames.BuildBattleFinish(BattleResult.LifeLose), true));
|
||||
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||
break;
|
||||
|
||||
@@ -292,8 +292,8 @@ public sealed class BattleSession
|
||||
// proper retire codes. Bots swallow their push (no real-opponent state).
|
||||
case NetworkBattleUri.Retire:
|
||||
case NetworkBattleUri.Kill:
|
||||
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.RetireLose), true));
|
||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.RetireWin), true));
|
||||
result.Add(new DispatchRoute(from, BattleFrames.BuildBattleFinish(BattleResult.RetireLose), true));
|
||||
result.Add(new DispatchRoute(other, BattleFrames.BuildBattleFinish(BattleResult.RetireWin), true));
|
||||
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||
break;
|
||||
|
||||
@@ -355,69 +355,4 @@ public sealed class BattleSession
|
||||
(A as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady &&
|
||||
(B as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady;
|
||||
|
||||
private MsgEnvelope BuildAck(NetworkBattleUri uri) => new(
|
||||
uri,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.General,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new ResultCodeOnlyBody());
|
||||
|
||||
private MsgEnvelope BuildTurnEndBroadcast() => new(
|
||||
NetworkBattleUri.TurnEnd,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new TurnEndBody(TurnState: 0));
|
||||
|
||||
private MsgEnvelope BuildJudgeBroadcast() => new(
|
||||
NetworkBattleUri.Judge,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new JudgeBody(Spin: ScriptedProfiles.OpponentJudgeSpin));
|
||||
|
||||
private MsgEnvelope BuildBattleFinish(BattleResult result) => new(
|
||||
NetworkBattleUri.BattleFinish,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new BattleFinishBody(Result: result));
|
||||
|
||||
private static IReadOnlyList<long> ExtractIdxList(MsgEnvelope env)
|
||||
{
|
||||
if (env.Body is not RawBody rawBody) return Array.Empty<long>();
|
||||
if (rawBody.Entries.TryGetValue("idxList", out var raw) && raw is System.Collections.IEnumerable seq && raw is not string)
|
||||
{
|
||||
var result = new List<long>();
|
||||
foreach (var item in seq)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case long l: result.Add(l); break;
|
||||
case int i: result.Add(i); break;
|
||||
case double d: result.Add((long)d); break;
|
||||
case decimal m: result.Add((long)m); break;
|
||||
case string s when long.TryParse(s, out var p): result.Add(p); break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return Array.Empty<long>();
|
||||
}
|
||||
}
|
||||
|
||||
76
SVSim.BattleNode/Sessions/Dispatch/BattleFrames.cs
Normal file
76
SVSim.BattleNode/Sessions/Dispatch/BattleFrames.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using SVSim.BattleNode.Lifecycle;
|
||||
using SVSim.BattleNode.Protocol;
|
||||
using SVSim.BattleNode.Protocol.Bodies;
|
||||
|
||||
namespace SVSim.BattleNode.Sessions.Dispatch;
|
||||
|
||||
/// <summary>Server-synthesized control/broadcast frames + inbound-body helpers, relocated verbatim
|
||||
/// from BattleSession so the per-URI handlers can build them. Pure: no session state.</summary>
|
||||
internal static class BattleFrames
|
||||
{
|
||||
internal static MsgEnvelope BuildAck(NetworkBattleUri uri) => new(
|
||||
uri,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.General,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new ResultCodeOnlyBody());
|
||||
|
||||
internal static MsgEnvelope BuildTurnEndBroadcast() => new(
|
||||
NetworkBattleUri.TurnEnd,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new TurnEndBody(TurnState: 0));
|
||||
|
||||
internal static MsgEnvelope BuildJudgeBroadcast() => new(
|
||||
NetworkBattleUri.Judge,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new JudgeBody(Spin: ScriptedProfiles.OpponentJudgeSpin));
|
||||
|
||||
internal static MsgEnvelope BuildBattleFinish(BattleResult result) => new(
|
||||
NetworkBattleUri.BattleFinish,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new BattleFinishBody(Result: result));
|
||||
|
||||
internal static IReadOnlyList<long> ExtractIdxList(MsgEnvelope env)
|
||||
{
|
||||
if (env.Body is not RawBody rawBody) return Array.Empty<long>();
|
||||
if (rawBody.Entries.TryGetValue("idxList", out var raw) && raw is System.Collections.IEnumerable seq && raw is not string)
|
||||
{
|
||||
var result = new List<long>();
|
||||
foreach (var item in seq)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case long l: result.Add(l); break;
|
||||
case int i: result.Add(i); break;
|
||||
case double d: result.Add((long)d); break;
|
||||
case decimal m: result.Add((long)m); break;
|
||||
case string s when long.TryParse(s, out var p): result.Add(p); break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return Array.Empty<long>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user