Skip to content

Commit cb3be6f

Browse files
committed
add per-player attack charges to planetwars
Gate JoinPlanetAttack on having >=1 charge (when PwAttackChargesMax > 0 in DynamicConfig). Attackers spend 1 charge when their squad actually launches (LaunchAllBattles), so merely clicking without forming a squad does not cost anything. Defenders gain 1 charge at battle end (via ProcessBattleResult). Passive recharge: a player who has sat below max for PwAttackChargesRechargeMinutes gains 1 charge; fresh/zero-initialised accounts get their recharge timer set lazily on the first passive pass. PwCharges protocol message (Current, Max, SecondsUntilNextCharge) is pushed to the player on login and after every change. Max=0 disables the system. StartGalaxy seeds every account to the configured maximum.
1 parent 5439f0d commit cb3be6f

10 files changed

Lines changed: 337 additions & 3 deletions

File tree

Shared/LobbyClient/Protocol/Messages.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,13 @@ public class PwStatus
749749
public int MinLevel { get; set; }
750750
}
751751

752+
[Message(Origin.Server)]
753+
public class PwAttackCharges
754+
{
755+
public int Current { get; set; }
756+
public int? NextRechargeTurn { get; set; }
757+
}
758+
752759

753760

754761

Zero-K.info/Controllers/PlanetwarsAdminController.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,13 @@ public ActionResult StartGalaxy(int galaxyID)
365365
gal.Ended = null;
366366
gal.EndMessage = null;
367367
db.SaveChanges();
368+
369+
var maxCharges = DynamicConfig.Instance.PwAttackChargesMax;
370+
db.Accounts.Where(x => x.FactionID != null).Update(x => new Account()
371+
{
372+
PwAttackCharges = maxCharges,
373+
PwLastAttackTurn = null,
374+
});
368375
}
369376

370377
return RedirectToAction("Index");

ZkData/Ef/Account.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ public static double AdjustEloWeight(double currentWeight, double sumWeight, int
153153
public double PwWarpProduced { get; set; }
154154
public double PwWarpUsed { get; set; }
155155
public double PwAttackPoints { get; set; }
156+
public int PwAttackCharges { get; set; }
157+
public int? PwLastAttackTurn { get; set; }
156158
public bool HasVpnException { get; set; }
157159
public bool HasKudos { get; set; }
158160
public int ForumTotalUpvotes { get; set; }
@@ -530,6 +532,19 @@ public void ResetQuotas()
530532

531533

532534

535+
public void SpendPwAttackCharge(int currentTurn)
536+
{
537+
if (PwAttackCharges > 0) PwAttackCharges--;
538+
PwLastAttackTurn = currentTurn;
539+
}
540+
541+
public void GrantPwAttackCharge(int maxCharges)
542+
{
543+
if (maxCharges <= 0) return;
544+
if (PwAttackCharges < maxCharges) PwAttackCharges++;
545+
}
546+
547+
533548
public void SpendBombers(double count)
534549
{
535550
PwBombersUsed += count;

ZkData/Ef/DynamicConfig.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public class DynamicConfig
4646
[Description("PlanetWars: maximum attack charges a player can hold. 0 disables the charge system.")]
4747
public int PwAttackChargesMax { get; set; } = 2;
4848

49-
[Description("PlanetWars: minutes a player must sit below max charges before gaining one passively.")]
50-
public int PwAttackChargesRechargeMinutes { get; set; } = 60;
49+
[Description("PlanetWars: turns of cooldown after an attack before the player starts regaining charges (1 per subsequent turn, up to max).")]
50+
public int PwAttackChargesCooldownTurns { get; set; } = 3;
5151

5252
[Description("PlanetWars: fraction of enemy bomber IP damage also applied to the bomber's own faction. 0 disables self-damage.")]
5353
public double PwBomberSelfIpRate { get; set; } = 0.5;

ZkData/Migrations/202604232250090_AddPwAttackCharges.Designer.cs

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace ZkData.Migrations
2+
{
3+
using System;
4+
using System.Data.Entity.Migrations;
5+
6+
public partial class AddPwAttackCharges : DbMigration
7+
{
8+
public override void Up()
9+
{
10+
AddColumn("dbo.Accounts", "PwAttackCharges", c => c.Int(nullable: false, defaultValue: 2));
11+
AddColumn("dbo.Accounts", "PwLastAttackTurn", c => c.Int());
12+
AddColumn("dbo.DynamicConfigs", "PwAttackChargesCooldownTurns", c => c.Int(nullable: false, defaultValue: 3));
13+
DropColumn("dbo.DynamicConfigs", "PwAttackChargesRechargeMinutes");
14+
}
15+
16+
public override void Down()
17+
{
18+
AddColumn("dbo.DynamicConfigs", "PwAttackChargesRechargeMinutes", c => c.Int(nullable: false, defaultValue: 60));
19+
DropColumn("dbo.DynamicConfigs", "PwAttackChargesCooldownTurns");
20+
DropColumn("dbo.Accounts", "PwLastAttackTurn");
21+
DropColumn("dbo.Accounts", "PwAttackCharges");
22+
}
23+
}
24+
}

ZkData/Migrations/202604232250090_AddPwAttackCharges.resx

Lines changed: 126 additions & 0 deletions
Large diffs are not rendered by default.

ZkData/ZkData.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,10 @@
507507
<Compile Include="Migrations\202604231826040_AddPwBomberNerfConfigs.Designer.cs">
508508
<DependentUpon>202604231826040_AddPwBomberNerfConfigs.cs</DependentUpon>
509509
</Compile>
510+
<Compile Include="Migrations\202604232250090_AddPwAttackCharges.cs" />
511+
<Compile Include="Migrations\202604232250090_AddPwAttackCharges.Designer.cs">
512+
<DependentUpon>202604232250090_AddPwAttackCharges.cs</DependentUpon>
513+
</Compile>
510514
<Compile Include="SpringFilesUnitsyncAttempt.cs" />
511515
<Compile Include="SteamWebApi.cs" />
512516
<Compile Include="UserLanguageNoteAttribute.cs" />
@@ -967,6 +971,9 @@
967971
<EmbeddedResource Include="Migrations\202604231826040_AddPwBomberNerfConfigs.resx">
968972
<DependentUpon>202604231826040_AddPwBomberNerfConfigs.cs</DependentUpon>
969973
</EmbeddedResource>
974+
<EmbeddedResource Include="Migrations\202604232250090_AddPwAttackCharges.resx">
975+
<DependentUpon>202604232250090_AddPwAttackCharges.cs</DependentUpon>
976+
</EmbeddedResource>
970977
<EmbeddedResource Include="ZkDataResources.resx">
971978
<Generator>ResXFileCodeGenerator</Generator>
972979
<LastGenOutput>ZkDataResources.Designer.cs</LastGenOutput>

ZkLobbyServer/SpringieInterface/PlanetWarsMatchMaker.cs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ private async void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventAr
154154
RunDefenderAssignment();
155155
await LaunchAllBattles();
156156
RunGalaxyTick();
157+
await ApplyTurnEndChargeBump();
157158
AttackerSideCounter++;
158159
ResetAttackOptions();
159160
}
@@ -420,6 +421,8 @@ private double GetPlayerWhr(string name)
420421

421422
private async Task LaunchAllBattles()
422423
{
424+
var attackerNamesToChargeSpend = new List<string>();
425+
423426
// merge squads on the same planet into one battle per planet
424427
foreach (var planetId in FormedSquads.Select(s => s.PlanetID).Distinct().ToList())
425428
{
@@ -434,6 +437,8 @@ private async Task LaunchAllBattles()
434437
merged.Defenders.AddRange(squad.Defenders.Where(x => server.ConnectedUsers.ContainsKey(x)));
435438
}
436439

440+
if (merged.Attackers.Count > 0) attackerNamesToChargeSpend.AddRange(merged.Attackers);
441+
437442
if (merged.Defenders.Count > 0 && merged.Attackers.Count > 0)
438443
{
439444
// battle (may be uneven)
@@ -473,6 +478,30 @@ private async Task LaunchAllBattles()
473478

474479
FormedSquads.Clear();
475480
DefenderVotes.Clear();
481+
482+
if (attackerNamesToChargeSpend.Count > 0) await SpendAttackCharges(attackerNamesToChargeSpend);
483+
}
484+
485+
private async Task SpendAttackCharges(List<string> playerNames)
486+
{
487+
var max = DynamicConfig.Instance.PwAttackChargesMax;
488+
if (max <= 0) return;
489+
try
490+
{
491+
List<Account> accounts;
492+
using (var db = new ZkDataContext())
493+
{
494+
var turn = db.Galaxies.First(g => g.IsDefault).Turn;
495+
accounts = db.Accounts.Where(a => playerNames.Contains(a.Name)).ToList();
496+
foreach (var acc in accounts) acc.SpendPwAttackCharge(turn);
497+
db.SaveChanges();
498+
}
499+
await Task.WhenAll(accounts.Select(acc => SendPwAttackCharges(server, acc.Name, acc)));
500+
}
501+
catch (Exception ex)
502+
{
503+
Trace.TraceError("PlanetWars SpendAttackCharges error: {0}", ex);
504+
}
476505
}
477506

478507

@@ -540,6 +569,13 @@ private async Task JoinPlanetAttack(int targetPlanetId, string userName)
540569
var account = db.Accounts.Find(user.AccountID);
541570
if (account == null || account.FactionID != AttackingFaction.FactionID || !account.CanPlayerPlanetWars()) return;
542571

572+
var maxCharges = DynamicConfig.Instance.PwAttackChargesMax;
573+
if (maxCharges > 0 && account.PwAttackCharges <= 0)
574+
{
575+
await server.GhostChanSay(user.Faction, $"{userName} cannot attack: out of attack charges");
576+
return;
577+
}
578+
543579
// remove from other options
544580
foreach (var aop in AttackOptions.Where(x => x.PlanetID != targetPlanetId))
545581
aop.Attackers.RemoveAll(x => x == userName);
@@ -604,7 +640,11 @@ public async Task OnLoginAccepted(ConnectedUser connectedUser)
604640
if (MiscVar.PlanetWarsMode == PlanetWarsModes.Running)
605641
{
606642
var u = connectedUser.User;
607-
if (u.CanUserPlanetWars()) await UpdateLobby(u.Name);
643+
if (u.CanUserPlanetWars())
644+
{
645+
await UpdateLobby(u.Name);
646+
await SendPwAttackChargesForUser(u.Name);
647+
}
608648
}
609649
}
610650

@@ -939,6 +979,75 @@ public void RemoveFromRunningBattles(int battleID)
939979
RunningBattles.Remove(battleID);
940980
}
941981

982+
983+
// ===================== ATTACK CHARGES =====================
984+
985+
public static PwAttackCharges BuildPwAttackCharges(Account account)
986+
{
987+
var max = DynamicConfig.Instance.PwAttackChargesMax;
988+
int? nextRechargeTurn = null;
989+
if (max > 0 && account.PwAttackCharges < max && account.PwLastAttackTurn != null)
990+
nextRechargeTurn = account.PwLastAttackTurn.Value + DynamicConfig.Instance.PwAttackChargesCooldownTurns;
991+
return new PwAttackCharges
992+
{
993+
Current = account.PwAttackCharges,
994+
NextRechargeTurn = nextRechargeTurn,
995+
};
996+
}
997+
998+
public static async Task SendPwAttackCharges(ZkLobbyServer.ZkLobbyServer server, string userName, Account account)
999+
{
1000+
var conus = server.ConnectedUsers.Get(userName);
1001+
if (conus == null) return;
1002+
await conus.SendCommand(BuildPwAttackCharges(account));
1003+
}
1004+
1005+
private async Task SendPwAttackChargesForUser(string userName)
1006+
{
1007+
var conus = server.ConnectedUsers.Get(userName);
1008+
if (conus?.User == null) return;
1009+
using (var db = new ZkDataContext())
1010+
{
1011+
var account = db.Accounts.Find(conus.User.AccountID);
1012+
if (account == null) return;
1013+
await conus.SendCommand(BuildPwAttackCharges(account));
1014+
}
1015+
}
1016+
1017+
private async Task ApplyTurnEndChargeBump()
1018+
{
1019+
try
1020+
{
1021+
var max = DynamicConfig.Instance.PwAttackChargesMax;
1022+
if (max <= 0) return;
1023+
var cooldown = DynamicConfig.Instance.PwAttackChargesCooldownTurns;
1024+
1025+
List<Account> bumped;
1026+
using (var db = new ZkDataContext())
1027+
{
1028+
var turn = db.Galaxies.First(g => g.IsDefault).Turn;
1029+
var query = db.Accounts.Where(a =>
1030+
a.FactionID != null &&
1031+
a.PwAttackCharges < max &&
1032+
(a.PwLastAttackTurn == null || turn - a.PwLastAttackTurn >= cooldown));
1033+
1034+
bumped = query.Select(a => new { a.Name, a.PwAttackCharges, a.PwLastAttackTurn })
1035+
.AsEnumerable()
1036+
.Select(x => new Account { Name = x.Name, PwAttackCharges = x.PwAttackCharges + 1, PwLastAttackTurn = x.PwLastAttackTurn })
1037+
.ToList();
1038+
1039+
if (bumped.Count > 0)
1040+
query.Update(a => new Account { PwAttackCharges = a.PwAttackCharges + 1 });
1041+
}
1042+
1043+
await Task.WhenAll(bumped.Select(a => SendPwAttackCharges(server, a.Name, a)));
1044+
}
1045+
catch (Exception ex)
1046+
{
1047+
Trace.TraceError("PlanetWars turn-end charge bump error: {0}", ex);
1048+
}
1049+
}
1050+
9421051
private async Task UpdateLobby()
9431052
{
9441053
// per-player: flags (CanSelectForBattle / PlayerIsAttacker) depend on the viewer

ZkLobbyServer/SpringieInterface/PlanetWarsTurnHandler.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ public static void ProcessBattleResult(string mapName, List<string> extraData, Z
174174
db.Events.InsertOnSubmit(ev);
175175
text.AppendLine(ev.PlainText);
176176
}
177+
178+
var maxCharges = DynamicConfig.Instance.PwAttackChargesMax;
179+
if (maxCharges > 0)
180+
{
181+
foreach (Account w in defenders)
182+
{
183+
w.GrantPwAttackCharge(maxCharges);
184+
_ = ZeroKWeb.PlanetWarsMatchMaker.SendPwAttackCharges(server, w.Name, w);
185+
}
186+
}
177187
}
178188
else
179189
{

0 commit comments

Comments
 (0)