Skip to content

Commit 958ed30

Browse files
committed
simplify squad logic (no size limit for squad)
1 parent a042cfb commit 958ed30

1 file changed

Lines changed: 67 additions & 114 deletions

File tree

ZkLobbyServer/SpringieInterface/PlanetWarsMatchMaker.cs

Lines changed: 67 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,19 @@ private async void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventAr
172172
}
173173

174174

175-
// ===================== SQUAD FORMATION (PIERCING) =====================
175+
// ===================== SQUAD FORMATION =====================
176+
// Phase 1: planets with >= TeamSize attackers form one squad with ALL their attackers.
177+
// Phase 2: repeatedly complete a squad for the highest-ranked straggler whose planet
178+
// the pool can still fill, pulling other stragglers to him as fillers. Skip
179+
// leaders whose planet is too big for the pool; stop when none qualify.
180+
// Phase 3: any remaining stragglers join the strongest existing squad (drawn by its
181+
// highest-ranked member). If no squad was formed at all, they are dropped.
182+
// Result: at most one squad per planet.
176183

177184
private void RunSquadFormation()
178185
{
179186
FormedSquads.Clear();
180187

181-
// collect all attackers still connected, grouped by planet
182188
var playerPlanet = new Dictionary<string, AttackOption>(); // player -> their chosen option
183189
foreach (var opt in AttackOptions)
184190
{
@@ -213,71 +219,62 @@ private void RunSquadFormation()
213219
}
214220
}
215221

216-
var pool = new HashSet<string>(playerPlanet.Keys);
217-
218-
// Pass 1: while any planet has >= TeamSize players, form squads from top WHR
219-
bool formed;
220-
do
222+
// Phase 1: self-sufficient planets form one squad with all their attackers
223+
foreach (var opt in AttackOptions)
221224
{
222-
formed = false;
223-
foreach (var opt in AttackOptions)
225+
var available = opt.Attackers.Where(playerPlanet.ContainsKey).ToList();
226+
if (available.Count >= opt.TeamSize)
224227
{
225-
var available = opt.Attackers.Where(pool.Contains).OrderByDescending(x => playerWhr.Get(x)).ToList();
226-
while (available.Count >= opt.TeamSize)
227-
{
228-
var squad = CreateSquadFromOption(opt);
229-
squad.Attackers = available.Take(opt.TeamSize).ToList();
230-
FormedSquads.Add(squad);
231-
foreach (var p in squad.Attackers) pool.Remove(p);
232-
available = available.Skip(opt.TeamSize).ToList();
233-
formed = true;
234-
}
228+
var squad = CreateSquadFromOption(opt);
229+
squad.Attackers = available;
230+
squad.TeamSize = available.Count;
231+
FormedSquads.Add(squad);
232+
foreach (var p in available) playerPlanet.Remove(p);
235233
}
236-
} while (formed); // repeat in case removing players from one planet frees up nothing, but be safe
234+
}
237235

238-
// Pass 2: piercing — top PW-Rank player pulls others to their planet
239-
while (pool.Count > 0)
236+
// Phase 2: piercing — pick the top-ranked straggler whose planet's TeamSize
237+
// the pool can still satisfy; skip leaders whose planet is too big.
238+
while (playerPlanet.Count > 0)
240239
{
241-
// find top PW-Rank player (lowest DisplayOrder, tiebreak by WHR desc)
242-
var leader = pool
240+
var leader = playerPlanet.Keys
241+
.Where(x => playerPlanet[x].TeamSize <= playerPlanet.Count)
243242
.OrderBy(x => playerRoleOrder.GetOrDefault(x, int.MaxValue))
244243
.ThenByDescending(x => playerWhr.Get(x))
245-
.First();
244+
.FirstOrDefault();
245+
246+
if (leader == null) break; // no straggler's planet fits the remaining pool
246247

247248
var leaderOption = playerPlanet[leader];
248-
if (pool.Count < leaderOption.TeamSize)
249-
break; // not enough players for any squad
250249

251-
var fillers = pool
250+
var fillers = playerPlanet.Keys
252251
.Where(x => x != leader)
253252
.OrderByDescending(x => playerWhr.Get(x))
254253
.Take(leaderOption.TeamSize - 1)
255254
.ToList();
256255

257-
if (fillers.Count < leaderOption.TeamSize - 1)
258-
break; // not enough
259-
260256
var squad = CreateSquadFromOption(leaderOption);
261-
squad.Attackers = new List<string> { leader };
257+
squad.Attackers.Add(leader);
262258
squad.Attackers.AddRange(fillers);
259+
squad.TeamSize = squad.Attackers.Count;
263260
FormedSquads.Add(squad);
264261

265-
pool.Remove(leader);
266-
foreach (var p in fillers) pool.Remove(p);
262+
playerPlanet.Remove(leader);
263+
foreach (var p in fillers) playerPlanet.Remove(p);
267264
}
268265

269-
// Pass 3: absorb leftovers into an existing squad on their original planet,
270-
// so all attackers join when a planet had more people than TeamSize.
271-
foreach (var name in pool.ToList())
266+
// Phase 3: remaining stragglers all merge into the strongest existing squad,
267+
// i.e. the one whose best member has the top PW-Rank (tiebreak by WHR).
268+
if (playerPlanet.Count > 0 && FormedSquads.Count > 0)
272269
{
273-
var originalPlanetId = playerPlanet[name].PlanetID;
274-
var squad = FormedSquads.FirstOrDefault(s => s.PlanetID == originalPlanetId);
275-
if (squad != null)
276-
{
277-
squad.Attackers.Add(name);
278-
squad.TeamSize = squad.Attackers.Count;
279-
pool.Remove(name);
280-
}
270+
var strongest = FormedSquads
271+
.OrderBy(s => s.Attackers.Min(a => playerRoleOrder.GetOrDefault(a, int.MaxValue)))
272+
.ThenByDescending(s => s.Attackers.Max(a => playerWhr.Get(a)))
273+
.First();
274+
275+
strongest.Attackers.AddRange(playerPlanet.Keys);
276+
strongest.TeamSize = strongest.Attackers.Count;
277+
playerPlanet.Clear();
281278
}
282279

283280
AttackOptions.Clear();
@@ -326,38 +323,29 @@ private void RunDefenderAssignment()
326323
}
327324
}
328325

329-
// per-planet: assign defenders, overflow to pool
326+
// per-squad: assign top-WHR volunteers; overflow spills to floating pool
330327
var floatingPool = new List<string>();
331-
var assignedDefenders = new Dictionary<int, List<string>>(); // planetID -> assigned defender names
332-
var attackedPlanetIds = FormedSquads.Select(s => s.PlanetID).Distinct().ToList();
333-
334-
foreach (var planetId in attackedPlanetIds)
328+
foreach (var squad in FormedSquads)
335329
{
336-
var totalSlotsNeeded = FormedSquads.Where(s => s.PlanetID == planetId).Sum(s => s.TeamSize);
337-
var volunteers = (DefenderVotes.ContainsKey(planetId) ? DefenderVotes[planetId] : new List<string>())
330+
var volunteers = (DefenderVotes.ContainsKey(squad.PlanetID) ? DefenderVotes[squad.PlanetID] : new List<string>())
338331
.Where(x => server.ConnectedUsers.ContainsKey(x) && defenderWhr.ContainsKey(x))
339332
.OrderByDescending(x => defenderWhr[x])
340333
.ToList();
341334

342-
if (volunteers.Count > totalSlotsNeeded)
335+
if (volunteers.Count > squad.TeamSize)
343336
{
344-
assignedDefenders[planetId] = volunteers.Take(totalSlotsNeeded).ToList();
345-
floatingPool.AddRange(volunteers.Skip(totalSlotsNeeded));
337+
squad.Defenders = volunteers.Take(squad.TeamSize).ToList();
338+
floatingPool.AddRange(volunteers.Skip(squad.TeamSize));
346339
}
347340
else
348341
{
349-
assignedDefenders[planetId] = volunteers;
342+
squad.Defenders = volunteers;
350343
}
351344
}
352345

353-
// floating pool fills unfilled slots on other planets (WHR order, respecting faction eligibility)
346+
// floating pool fills deficits on other squads (WHR order, respecting faction eligibility)
354347
floatingPool = floatingPool.OrderByDescending(x => defenderWhr.Get(x)).ToList();
355348

356-
// cache defending factions per planet and defender faction IDs
357-
var planetDefendingFactions = new Dictionary<int, List<Faction>>();
358-
foreach (var pid in attackedPlanetIds)
359-
planetDefendingFactions[pid] = GetDefendingFactions(FormedSquads.First(s => s.PlanetID == pid));
360-
361349
var defenderFactionId = new Dictionary<string, int?>();
362350
using (var db = new ZkDataContext())
363351
{
@@ -368,45 +356,18 @@ private void RunDefenderAssignment()
368356
}
369357
}
370358

371-
foreach (var planetId in attackedPlanetIds)
359+
foreach (var squad in FormedSquads)
372360
{
373-
var totalSlotsNeeded = FormedSquads.Where(s => s.PlanetID == planetId).Sum(s => s.TeamSize);
374-
var assigned = assignedDefenders[planetId];
375-
var deficit = totalSlotsNeeded - assigned.Count;
361+
var deficit = squad.TeamSize - squad.Defenders.Count;
376362
if (deficit > 0 && floatingPool.Count > 0)
377363
{
378-
var allowedFactionIds = planetDefendingFactions[planetId].Select(f => f.FactionID).ToHashSet();
364+
var allowedFactionIds = GetDefendingFactions(squad).Select(f => f.FactionID).ToHashSet();
379365
var eligible = floatingPool.Where(x => defenderFactionId.ContainsKey(x) && defenderFactionId[x].HasValue && allowedFactionIds.Contains(defenderFactionId[x].Value)).ToList();
380366
var toAdd = eligible.Take(deficit).ToList();
381-
assigned.AddRange(toAdd);
367+
squad.Defenders.AddRange(toAdd);
382368
foreach (var p in toAdd) floatingPool.Remove(p);
383369
}
384370
}
385-
386-
// slice defenders into squads: sort squads by avg attacker WHR desc, assign best defenders to best attackers
387-
foreach (var planetId in attackedPlanetIds)
388-
{
389-
var squadsForPlanet = FormedSquads
390-
.Where(s => s.PlanetID == planetId)
391-
.OrderByDescending(s => s.Attackers.Average(a => GetPlayerWhr(a))) // sort by attacker strength
392-
.ToList();
393-
394-
var defenders = assignedDefenders.ContainsKey(planetId)
395-
? assignedDefenders[planetId].OrderByDescending(x => defenderWhr.Get(x)).ToList()
396-
: new List<string>();
397-
398-
int idx = 0;
399-
foreach (var squad in squadsForPlanet)
400-
{
401-
var count = Math.Min(squad.TeamSize, defenders.Count - idx);
402-
if (count > 0)
403-
{
404-
squad.Defenders = defenders.Skip(idx).Take(count).ToList();
405-
idx += count;
406-
}
407-
// else: no defenders at all for this squad (concede)
408-
}
409-
}
410371
}
411372

412373
private double GetPlayerWhr(string name)
@@ -423,38 +384,30 @@ private async Task LaunchAllBattles()
423384
{
424385
var attackerNamesToChargeSpend = new List<string>();
425386

426-
// merge squads on the same planet into one battle per planet
427-
foreach (var planetId in FormedSquads.Select(s => s.PlanetID).Distinct().ToList())
387+
foreach (var squad in FormedSquads)
428388
{
429-
var squads = FormedSquads.Where(s => s.PlanetID == planetId).ToList();
430-
var first = squads.First();
431-
432-
// merge all squads into one AttackOption
433-
var merged = CreateSquadFromOption(first);
434-
foreach (var squad in squads)
435-
{
436-
merged.Attackers.AddRange(squad.Attackers.Where(x => server.ConnectedUsers.ContainsKey(x)));
437-
merged.Defenders.AddRange(squad.Defenders.Where(x => server.ConnectedUsers.ContainsKey(x)));
438-
}
389+
// drop anyone who disconnected between squad formation and launch
390+
squad.Attackers = squad.Attackers.Where(x => server.ConnectedUsers.ContainsKey(x)).ToList();
391+
squad.Defenders = squad.Defenders.Where(x => server.ConnectedUsers.ContainsKey(x)).ToList();
439392

440-
if (merged.Attackers.Count > 0) attackerNamesToChargeSpend.AddRange(merged.Attackers);
393+
if (squad.Attackers.Count > 0) attackerNamesToChargeSpend.AddRange(squad.Attackers);
441394

442-
if (merged.Defenders.Count > 0 && merged.Attackers.Count > 0)
395+
if (squad.Defenders.Count > 0 && squad.Attackers.Count > 0)
443396
{
444397
// battle (may be uneven)
445398
try
446399
{
447-
merged.TeamSize = Math.Max(merged.Attackers.Count, merged.Defenders.Count);
448-
var battle = new PlanetWarsServerBattle(server, merged);
400+
squad.TeamSize = Math.Max(squad.Attackers.Count, squad.Defenders.Count);
401+
var battle = new PlanetWarsServerBattle(server, squad);
449402
await server.AddBattle(battle);
450-
RunningBattles[battle.BattleID] = merged;
403+
RunningBattles[battle.BattleID] = squad;
451404

452-
foreach (var usr in merged.Attackers.Union(merged.Defenders))
405+
foreach (var usr in squad.Attackers.Union(squad.Defenders))
453406
await server.ForceJoinBattle(usr, battle);
454407

455408
if (await battle.StartGame())
456409
{
457-
var text = $"Battle for planet {merged.Name} starts on zk://@join_player:{merged.Attackers.FirstOrDefault()} Roster: {string.Join(",", merged.Attackers)} vs {string.Join(",", merged.Defenders)}";
410+
var text = $"Battle for planet {squad.Name} starts on zk://@join_player:{squad.Attackers.FirstOrDefault()} Roster: {string.Join(",", squad.Attackers)} vs {string.Join(",", squad.Defenders)}";
458411
foreach (var fac in factions) await server.GhostChanSay(fac.Shortcut, text);
459412
}
460413
else
@@ -468,10 +421,10 @@ private async Task LaunchAllBattles()
468421
Trace.TraceError("PlanetWars LaunchBattle error: {0}", ex);
469422
}
470423
}
471-
else if (merged.Attackers.Count > 0)
424+
else if (squad.Attackers.Count > 0)
472425
{
473426
// concede - zero defenders
474-
RecordPlanetwarsLoss(merged);
427+
RecordPlanetwarsLoss(squad);
475428
}
476429
// else: no attackers left, skip entirely
477430
}

0 commit comments

Comments
 (0)