refactor(battle-node): extract frame factories into BattleFrames
This commit is contained in:
@@ -67,7 +67,7 @@ public sealed class BattleSession
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await survivor.PushAsync(
|
await survivor.PushAsync(
|
||||||
BuildBattleFinish(BattleResult.DisconnectWin), noStock: true, cancellation)
|
BattleFrames.BuildBattleFinish(BattleResult.DisconnectWin), noStock: true, cancellation)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -143,7 +143,7 @@ public sealed class BattleSession
|
|||||||
switch (env.Uri)
|
switch (env.Uri)
|
||||||
{
|
{
|
||||||
case NetworkBattleUri.InitNetwork when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitNetwork:
|
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;
|
phaseFrom!.Phase = BattleSessionPhase.AwaitingInitBattle;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ public sealed class BattleSession
|
|||||||
case NetworkBattleUri.InitBattle
|
case NetworkBattleUri.InitBattle
|
||||||
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||||
// Ack only — NO Matched push.
|
// 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;
|
phaseFrom!.Phase = BattleSessionPhase.AwaitingLoaded;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ public sealed class BattleSession
|
|||||||
// Judge to sender ONLY (not broadcast — there's no real other side).
|
// Judge to sender ONLY (not broadcast — there's no real other side).
|
||||||
// The client's JudgeOperation → ControlTurnStartPlayer flips back to
|
// The client's JudgeOperation → ControlTurnStartPlayer flips back to
|
||||||
// the local AI's turn after this Judge arrives.
|
// 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;
|
break;
|
||||||
|
|
||||||
case NetworkBattleUri.InitBattle when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
case NetworkBattleUri.InitBattle when phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||||
@@ -226,7 +226,7 @@ public sealed class BattleSession
|
|||||||
|
|
||||||
case NetworkBattleUri.Swap when phaseFrom?.Phase == BattleSessionPhase.AwaitingSwap:
|
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.
|
// SwapResponse is always immediate — it completes the sender's own mulligan UI.
|
||||||
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
||||||
_state.PostSwapHands[from] = hand;
|
_state.PostSwapHands[from] = hand;
|
||||||
@@ -257,8 +257,8 @@ public sealed class BattleSession
|
|||||||
case NetworkBattleUri.TurnEnd when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
case NetworkBattleUri.TurnEnd when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
||||||
if (Type == BattleType.Pvp && BothAfterReady())
|
if (Type == BattleType.Pvp && BothAfterReady())
|
||||||
{
|
{
|
||||||
var turnEndBroadcast = BuildTurnEndBroadcast();
|
var turnEndBroadcast = BattleFrames.BuildTurnEndBroadcast();
|
||||||
var judgeBroadcast = BuildJudgeBroadcast();
|
var judgeBroadcast = BattleFrames.BuildJudgeBroadcast();
|
||||||
result.Add(new DispatchRoute(from, turnEndBroadcast, false));
|
result.Add(new DispatchRoute(from, turnEndBroadcast, false));
|
||||||
result.Add(new DispatchRoute(other, turnEndBroadcast, false));
|
result.Add(new DispatchRoute(other, turnEndBroadcast, false));
|
||||||
result.Add(new DispatchRoute(from, judgeBroadcast, 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.
|
// so the RunAsync cascade doesn't synthesize a follow-up BattleFinish.
|
||||||
case NetworkBattleUri.TurnEndFinal when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
case NetworkBattleUri.TurnEndFinal when phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
||||||
result.Add(new DispatchRoute(other, env, false));
|
result.Add(new DispatchRoute(other, env, false));
|
||||||
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.LifeWin), true));
|
result.Add(new DispatchRoute(from, BattleFrames.BuildBattleFinish(BattleResult.LifeWin), true));
|
||||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.LifeLose), true));
|
result.Add(new DispatchRoute(other, BattleFrames.BuildBattleFinish(BattleResult.LifeLose), true));
|
||||||
_state.SessionPhase = BattleSessionPhase.Terminal;
|
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -292,8 +292,8 @@ public sealed class BattleSession
|
|||||||
// proper retire codes. Bots swallow their push (no real-opponent state).
|
// proper retire codes. Bots swallow their push (no real-opponent state).
|
||||||
case NetworkBattleUri.Retire:
|
case NetworkBattleUri.Retire:
|
||||||
case NetworkBattleUri.Kill:
|
case NetworkBattleUri.Kill:
|
||||||
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.RetireLose), true));
|
result.Add(new DispatchRoute(from, BattleFrames.BuildBattleFinish(BattleResult.RetireLose), true));
|
||||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.RetireWin), true));
|
result.Add(new DispatchRoute(other, BattleFrames.BuildBattleFinish(BattleResult.RetireWin), true));
|
||||||
_state.SessionPhase = BattleSessionPhase.Terminal;
|
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -355,69 +355,4 @@ public sealed class BattleSession
|
|||||||
(A as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady &&
|
(A as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady &&
|
||||||
(B 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