Skip to content

Commit 0f0130b

Browse files
committed
testing mid game join
1 parent b8ad3b7 commit 0f0130b

5 files changed

Lines changed: 131 additions & 11 deletions

File tree

Shared/LobbyClient/DedicatedServer.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,27 @@ public override string ToString()
102102
}
103103

104104
/// <summary>
105-
/// Adds user dynamically to running game - for security reasons add his script
105+
/// Adds user dynamically to running game as a spectator.
106106
/// </summary>
107107
public void AddUser(string name, string scriptPassword, User user)
108+
{
109+
AddUser(name, scriptPassword, user, isSpectator: true, teamNumber: null);
110+
}
111+
112+
/// <summary>
113+
/// Adds user dynamically to running game, optionally as a non-spectator on a specific team.
114+
/// </summary>
115+
public void AddUser(string name, string scriptPassword, User user, bool isSpectator, int? teamNumber)
108116
{
109117
if (IsRunning)
110118
{
111-
talker.SendText($"/adduser {name} {scriptPassword}");
119+
if (!isSpectator && teamNumber.HasValue)
120+
talker.SendText($"/adduser {name} {scriptPassword} 0 {teamNumber.Value}");
121+
else
122+
talker.SendText($"/adduser {name} {scriptPassword}");
123+
112124
if (user != null) talker.SendText($"SPRINGIE:User {new MidGameJoinUser(user)}");
113125
}
114-
115126
}
116127

117128
public event EventHandler<SpringBattleContext> BattleStarted = (sender, args) => { };

Shared/LobbyClient/ScriptGenerator.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,31 @@ static void GeneratePlayerSection(StringBuilder script, SpringBattleContext setu
120120
teamNum++;
121121
}
122122

123+
// Preallocate empty team slots for mid-game player joins.
124+
// These teams exist in the script from frame 0 but start inactive (no player assigned).
125+
// The engine activates them when a mid-game joiner is assigned via /adduser.
126+
var slotsPerAlly = setup.LobbyStartContext.MidGameSlotsPerAllyTeam;
127+
if (slotsPerAlly > 0)
128+
{
129+
var allyTeamsInUse = setup.LobbyStartContext.Players
130+
.Where(x => !x.IsSpectator)
131+
.Select(x => x.AllyID)
132+
.Union(setup.LobbyStartContext.Bots.Select(x => x.AllyID))
133+
.Distinct()
134+
.OrderBy(x => x)
135+
.ToList();
136+
137+
// Use first real player as TeamLeader for the placeholder teams (engine requires a valid leader)
138+
var leaderUserNum = 0;
139+
foreach (var allyId in allyTeamsInUse)
140+
{
141+
for (var s = 0; s < slotsPerAlly; s++)
142+
{
143+
ScriptAddTeam(script, teamNum, leaderUserNum, allyId);
144+
teamNum++;
145+
}
146+
}
147+
}
123148

124149
// ALLIANCES AND START BOXES
125150
var startboxes = new StringBuilder();

Shared/LobbyClient/Spring.SpringBattleContext.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Text;
@@ -11,6 +12,11 @@ public class SpringBattleContext
1112
public List<BattlePlayerResult> ActualPlayers = new List<BattlePlayerResult>();
1213
public List<string> PlayersUnreadyOnStart = new List<string>();
1314

15+
/// <summary>
16+
/// Pool of preallocated team numbers available for mid-game player joins, keyed by ally team ID.
17+
/// </summary>
18+
public ConcurrentDictionary<int, ConcurrentQueue<int>> MidGameTeamSlots = new ConcurrentDictionary<int, ConcurrentQueue<int>>();
19+
1420
public int Duration;
1521
public string EngineBattleID;
1622

@@ -93,6 +99,34 @@ public void SetForHosting(LobbyHostingContext startContext,
9399
IsIngameReady = false,
94100
IsIngame = false,
95101
}).ToList();
102+
103+
// Build pool of preallocated team slots for mid-game joins
104+
var slotsPerAlly = startContext.MidGameSlotsPerAllyTeam;
105+
if (slotsPerAlly > 0)
106+
{
107+
// Count how many real teams were generated (non-spectator players + bots)
108+
var realTeamCount = startContext.Players.Count(x => !x.IsSpectator) + startContext.Bots.Count;
109+
110+
var allyTeamsInUse = startContext.Players
111+
.Where(x => !x.IsSpectator)
112+
.Select(x => x.AllyID)
113+
.Union(startContext.Bots.Select(x => x.AllyID))
114+
.Distinct()
115+
.OrderBy(x => x)
116+
.ToList();
117+
118+
var teamNum = realTeamCount;
119+
foreach (var allyId in allyTeamsInUse)
120+
{
121+
var queue = new ConcurrentQueue<int>();
122+
for (var s = 0; s < slotsPerAlly; s++)
123+
{
124+
queue.Enqueue(teamNum);
125+
teamNum++;
126+
}
127+
MidGameTeamSlots[allyId] = queue;
128+
}
129+
}
96130
}
97131

98132

Shared/PlasmaShared/ISpringieService/LobbyHostingContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ public class LobbyHostingContext
2121
public Dictionary<string,Dictionary<string,string>> UserParameters = new Dictionary<string, Dictionary<string, string>>();
2222
public int BattleID { get; set; }
2323

24+
/// <summary>
25+
/// Number of extra team slots to preallocate per ally team for mid-game player joins.
26+
/// These teams exist in the start script but have no player assigned initially.
27+
/// </summary>
28+
public int MidGameSlotsPerAllyTeam { get; set; }
29+
2430
public void ApplyBalance(BalanceTeamsResult balance)
2531
{
2632
if (balance.DeleteBots) Bots.Clear();

ZkLobbyServer/ServerBattle.cs

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,21 @@ public string GenerateClientScriptPassword(string name)
167167
return Hash.HashString(battleInstanceGuid + name).ToString();
168168
}
169169

170+
/// <summary>
171+
/// Tries to claim a preallocated team slot for a mid-game joiner on the given ally team.
172+
/// Returns the team number if a slot is available, null otherwise.
173+
/// </summary>
174+
private int? TryClaimMidGameTeamSlot(int allyTeamId)
175+
{
176+
if (spring?.Context?.MidGameTeamSlots != null &&
177+
spring.Context.MidGameTeamSlots.TryGetValue(allyTeamId, out var queue) &&
178+
queue.TryDequeue(out var teamNum))
179+
{
180+
return teamNum;
181+
}
182+
return null;
183+
}
184+
170185
public void Dispose()
171186
{
172187
spring.UnsubscribeEvents(this);
@@ -349,11 +364,28 @@ public virtual async Task ProcessPlayerJoin(ConnectedUser user, string joinPassw
349364

350365
if (spring.IsRunning)
351366
{
352-
spring.AddUser(ubs.Name, ubs.ScriptPassword, ubs.LobbyUser);
353-
var started = DateTime.UtcNow.Subtract(spring.IngameStartTime ?? RunningSince ?? DateTime.UtcNow);
354-
started = new TimeSpan((int)started.TotalHours, started.Minutes, started.Seconds);
355-
await SayBattle($"THIS GAME IS CURRENTLY IN PROGRESS, PLEASE WAIT UNTIL IT ENDS! Running for {started}", ubs.Name);
356-
await SayBattle("If you say !notify, I will message you when the current game ends.", ubs.Name);
367+
// Try to assign a preallocated mid-game team slot if available
368+
int? midGameTeam = null;
369+
if (!ubs.IsSpectator)
370+
{
371+
midGameTeam = TryClaimMidGameTeamSlot(ubs.AllyNumber);
372+
if (midGameTeam == null)
373+
ubs.IsSpectator = true; // no slot available, force spectator
374+
}
375+
376+
spring.AddUser(ubs.Name, ubs.ScriptPassword, ubs.LobbyUser, ubs.IsSpectator, midGameTeam);
377+
378+
if (midGameTeam.HasValue)
379+
{
380+
await SayBattle($"Game is in progress — {ubs.Name} is joining as a player on team {midGameTeam.Value}.");
381+
}
382+
else
383+
{
384+
var started = DateTime.UtcNow.Subtract(spring.IngameStartTime ?? RunningSince ?? DateTime.UtcNow);
385+
started = new TimeSpan((int)started.TotalHours, started.Minutes, started.Seconds);
386+
await SayBattle($"THIS GAME IS CURRENTLY IN PROGRESS, PLEASE WAIT UNTIL IT ENDS! Running for {started}", ubs.Name);
387+
await SayBattle("If you say !notify, I will message you when the current game ends.", ubs.Name);
388+
}
357389
}
358390

359391
try
@@ -406,22 +438,34 @@ public async Task RequestConnectSpring(ConnectedUser conus, string joinPassword)
406438
UserBattleStatus ubs;
407439

408440
startGameStatus = spring.LobbyStartContext.Players.FirstOrDefault(x => x.Name == conus.Name);
409-
441+
410442
if (!Users.TryGetValue(conus.Name, out ubs) && !(IsInGame && startGameStatus != null))
411443
if (IsPassworded && (Password != joinPassword))
412444
{
413445
await conus.Respond("Invalid password");
414446
return;
415447
}
448+
449+
var isSpectator = startGameStatus?.IsSpectator != false;
450+
451+
// For mid-game joins of users not in the original player list, try to assign a team slot
452+
int? midGameTeam = null;
453+
if (isSpectator && startGameStatus == null && Users.TryGetValue(conus.Name, out ubs) && !ubs.IsSpectator)
454+
{
455+
midGameTeam = TryClaimMidGameTeamSlot(ubs.AllyNumber);
456+
if (midGameTeam.HasValue)
457+
isSpectator = false;
458+
}
459+
416460
var pwd = GenerateClientScriptPassword(conus.Name);
417-
spring.AddUser(conus.Name, pwd, conus.User);
461+
spring.AddUser(conus.Name, pwd, conus.User, isSpectator, midGameTeam);
418462

419463
if (spring.Context.LobbyStartContext.Players.Any(x => x.Name == conus.Name) && conus.MyBattle != this)
420464
{
421465
await ProcessPlayerJoin(conus, joinPassword);
422466
}
423467

424-
await conus.SendCommand(GetConnectSpringStructure(pwd, startGameStatus?.IsSpectator != false));
468+
await conus.SendCommand(GetConnectSpringStructure(pwd, isSpectator));
425469
}
426470

427471

0 commit comments

Comments
 (0)