Skip to content

Commit 6530c14

Browse files
authored
Update stable with new PW rules
Update stable with new PW rules
2 parents db95584 + a79e2cf commit 6530c14

38 files changed

Lines changed: 1965 additions & 378 deletions

Fixer/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,6 @@ private static void TestPwMatchMaker()
431431
{
432432
var server = new global::ZkLobbyServer.ZkLobbyServer("", new PlanetwarsEventCreator());
433433
var mm = server.PlanetWarsMatchMaker;
434-
mm.AttackerSideCounter = 1;
435434
mm.GenerateLobbyCommand();
436435

437436
// simulate defend phase with a formed squad

Shared/LobbyClient/Protocol/Messages.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,11 @@ public enum ModeType
674674
Defend = 2
675675
}
676676

677-
public string AttackerFaction { get; set; }
677+
/// <summary>
678+
/// Distinct attacker faction shortcuts across all <see cref="Options"/>. In parallel-turn PW every
679+
/// faction can be attacking simultaneously; each option also carries its own <see cref="VoteOption.AttackerFaction"/>.
680+
/// </summary>
681+
public List<string> AttackerFactions { get; set; }
678682

679683
public DateTime Deadline { get; set; }
680684

@@ -690,6 +694,7 @@ public PwMatchCommand(ModeType mode)
690694
Mode = mode;
691695
Options = new List<VoteOption>();
692696
DefenderFactions = new List<string>();
697+
AttackerFactions = new List<string>();
693698
}
694699

695700
public class VoteOption
@@ -702,32 +707,66 @@ public class VoteOption
702707
public List<string> StructureImages { get; set; }
703708
public int IconSize { get; set; }
704709
public string PlanetName { get; set; }
710+
public bool CanSelectForBattle { get; set; }
711+
public bool PlayerIsAttacker { get; set; }
712+
public bool PlayerIsDefender { get; set; }
713+
/// <summary>
714+
/// Faction shortcut of the attacker. Together with <see cref="PlanetID"/> forms the (planet, attacker)
715+
/// key that identifies this attack slot. Clients must echo it back in <see cref="PwJoinPlanet"/>.
716+
/// </summary>
717+
public string AttackerFaction { get; set; }
718+
/// <summary>Faction shortcut of the planet's current owner, or null for neutral planets. Lets the
719+
/// lobby surface "you own this", "ally owns this", "neutral" hints during attack/defense pick.</summary>
720+
public string OwnerFaction { get; set; }
721+
/// <summary>Average PW-WHR of the projected attacker squad (top-TeamSize volunteers). 0 when none.</summary>
722+
public int AttackerAvgWhr { get; set; }
723+
/// <summary>Average PW-WHR of the projected defender squad. Null in AttackCollect phase or when no volunteers.</summary>
724+
public int? DefenderAvgWhr { get; set; }
725+
/// <summary>Attacker win chance 0-100 derived from WHR delta. Null when either side is empty.</summary>
726+
public int? WinChance { get; set; }
705727
}
706728
}
707729

708730
[Message(Origin.Client)]
709731
public class PwJoinPlanet
710732
{
711733
public int PlanetID { get; set; }
734+
/// <summary>
735+
/// Which attack slot the player is interacting with. Required — a planet can be attacked by multiple
736+
/// factions simultaneously, each a separate slot. For an attacker this should equal the user's own
737+
/// faction; for a defender it identifies which incoming attack to defend against.
738+
/// </summary>
739+
public string AttackerFaction { get; set; }
740+
}
741+
742+
/// <summary>
743+
/// Client → server: cancel the player's current attack or defense commitment for the cycle.
744+
/// </summary>
745+
[Message(Origin.Client)]
746+
public class PwCancel
747+
{
712748
}
713749

714750
[Message(Origin.Server)]
715751
public class PwRequestJoinPlanet
716752
{
717753
public int PlanetID { get; set; }
754+
public string AttackerFaction { get; set; }
718755
}
719756

720757

721758
[Message(Origin.Server)]
722759
public class PwJoinPlanetSuccess
723760
{
724761
public int PlanetID { get; set; }
762+
public string AttackerFaction { get; set; }
725763
}
726764

727765
[Message(Origin.Server)]
728766
public class PwAttackingPlanet
729767
{
730768
public int PlanetID { get; set; }
769+
public string AttackerFaction { get; set; }
731770
}
732771

733772
[Message(Origin.Client)]
@@ -744,6 +783,19 @@ public class PwStatus
744783
public PlanetWarsModes? PlanetWarsNextMode { get; set; }
745784
public DateTime? PlanetWarsNextModeTime { get; set; }
746785
public int MinLevel { get; set; }
786+
public int AttackerPhaseMinutes { get; set; }
787+
public int DefenderPhaseMinutes { get; set; }
788+
/// <summary>Server's configured max attack charges. Lobby renders X/Max in the charges UI;
789+
/// before this field existed Chobby hardcoded 2 as the fallback default.</summary>
790+
public int MaxAttackCharges { get; set; }
791+
}
792+
793+
[Message(Origin.Server)]
794+
public class PwAttackCharges
795+
{
796+
public int Current { get; set; }
797+
/// <summary>UTC time at which the next charge will be granted, rounded up to a full minute. Null when at max or charges are disabled.</summary>
798+
public DateTime? NextRechargeTime { get; set; }
747799
}
748800

749801

Shared/PlasmaShared/GlobalConst.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ static void SetMode(ModeType newMode)
161161
public const double PlanetMetalPerTurn = 1;
162162
public const double PlanetWarsEnergyToMetalRatio = 0.0;
163163
public const double PlanetWarsMaximumIP = 100.0; //maximum IP on each planet
164-
public const int PlanetWarsVictoryPointsToWin = 100;
164+
public const int PlanetWarsVictoryPointsToWin = 50;
165165
public const int VictoryPointDecay = 1;
166166
public const int BaseInfluencePerBattle = 32;
167167
public const int InfluencePerAttacker = 1;
@@ -211,8 +211,8 @@ static void SetMode(ModeType newMode)
211211
public const int PostVoteHideThreshold = -6;
212212
public const bool OnlyAdminsSeePostVoters = false;
213213
public const int PlanetWarsMinutesToAttackIfNoOption = 2;
214-
public const int PlanetWarsMinutesToAttack = 5;
215-
public const int PlanetWarsMinutesToAccept = 10;
214+
public const int PlanetWarsMinutesToAttack = 2;
215+
public const int PlanetWarsMinutesToAccept = 2;
216216
public const int PlanetWarsDropshipsStayForMinutes = 2*60;
217217
public const int PlanetWarsMaxTeamsize = 4;
218218
public const double PlanetWarsDefenderWinKillCcMultiplier = 0.2;

Shared/PlasmaShared/Utils.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ public static void SafeDispose(this IDisposable o)
3434
if (o != null) o.Dispose();
3535
}
3636

37+
public static DateTime CeilingToMinute(this DateTime d)
38+
{
39+
var floored = new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0, d.Kind);
40+
return d == floored ? d : floored.AddMinutes(1);
41+
}
42+
3743
public static IEnumerable<Indexed<T>> ToIndexedList<T>(this IEnumerable<T> enumeration)
3844
{
3945
return enumeration.Select((x, i) => new Indexed<T>(x, i));

Zero-K.info/Controllers/PlanetwarsAdminController.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,15 @@ public ActionResult StartGalaxy(int galaxyID)
365365
gal.Ended = null;
366366
gal.EndMessage = null;
367367
db.SaveChanges();
368+
369+
// Seed everyone at the passive recharge limit, not the absolute max — defending as the very
370+
// first PW action of the new galaxy then has a tangible reward (stockpiling above passive cap).
371+
var initialCharges = Math.Min(DynamicConfig.Instance.PwAttackChargesPassiveLimit, DynamicConfig.Instance.PwAttackChargesMax);
372+
db.Accounts.Where(x => x.FactionID != null).Update(x => new Account()
373+
{
374+
PwAttackCharges = initialCharges,
375+
PwLastChargeChange = null,
376+
});
368377
}
369378

370379
return RedirectToAction("Index");

Zero-K.info/Controllers/PlanetwarsController.cs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,23 @@ public ActionResult BombPlanet(int planetID, int count, bool? useWarp)
7070
double ipKillAmmount = ipKillCount * GlobalConst.BomberKillIpAmount;
7171
if (ipKillAmmount > 0)
7272
{
73-
var influenceDecayMin = planet.PlanetStructures.Where(x => x.IsActive && x.StructureType.EffectPreventInfluenceDecayBelow != null).Select(x => x.StructureType.EffectPreventInfluenceDecayBelow).OrderByDescending(x => x).FirstOrDefault() ?? 0;
73+
var structureFloor = planet.PlanetStructures.Where(x => x.IsActive && x.StructureType.EffectPreventInfluenceDecayBelow != null).Select(x => x.StructureType.EffectPreventInfluenceDecayBelow).OrderByDescending(x => x).FirstOrDefault() ?? 0;
74+
var globalFloor = DynamicConfig.Instance.PwBomberMinimumIpFloor;
75+
var selfRate = DynamicConfig.Instance.PwBomberSelfIpRate;
7476

75-
76-
foreach (PlanetFaction pf in planet.PlanetFactions.Where(x => x.FactionID != acc.FactionID))
77+
foreach (PlanetFaction pf in planet.PlanetFactions)
7778
{
78-
pf.Influence -= ipKillAmmount;
79-
if (pf.Influence < 0) pf.Influence = 0;
80-
81-
// prevent bombing below influence decaymin for owner - set by active structures
82-
if (pf.FactionID == planet.OwnerFactionID && pf.Influence < influenceDecayMin) pf.Influence = influenceDecayMin;
83-
}
79+
bool isOwn = pf.FactionID == acc.FactionID;
80+
double damage = isOwn ? ipKillAmmount * selfRate : ipKillAmmount;
81+
if (damage <= 0) continue;
8482

83+
// owner gets max of global floor and any structure-provided floor; everyone else gets global only
84+
double floor = (pf.FactionID == planet.OwnerFactionID) ? Math.Max(globalFloor, structureFloor) : globalFloor;
8585

86+
// floor only prevents pushing below it — a faction already below the floor is not raised by bombing
87+
double effectiveFloor = Math.Min(pf.Influence, floor);
88+
pf.Influence = Math.Max(pf.Influence - damage, Math.Max(effectiveFloor, 0));
89+
}
8690
}
8791

8892
var args = new List<object>
@@ -793,10 +797,11 @@ public ActionResult MatchMakerAttack(int planetID)
793797
{
794798
var db = new ZkDataContext();
795799
var planet = db.Planets.Single(x => x.PlanetID == planetID);
796-
if (Global.IsAccountAuthorized && Global.Account.CanPlayerPlanetWars() && planet.CanMatchMakerPlay(db.CurrentAccount().Faction))
800+
var account = db.CurrentAccount();
801+
if (Global.IsAccountAuthorized && Global.Account.CanPlayerPlanetWars() && account?.FactionID != null && planet.CanMatchMakerPlay(account.Faction))
797802
{
798-
Global.Server.PlanetWarsMatchMaker.AddAttackOption(planet);
799-
Global.Server.RequestJoinPlanet(Global.Account.Name, planet.PlanetID);
803+
Global.Server.PlanetWarsMatchMaker.AddAttackOption(planet, account.FactionID.Value);
804+
Global.Server.RequestJoinPlanet(Global.Account.Name, planet.PlanetID, account.Faction.Shortcut);
800805
}
801806
return RedirectToAction("Planet", new { id = planetID });
802807
}
@@ -808,16 +813,17 @@ public ActionResult MatchMaker()
808813
var pwm = Global.Server.PlanetWarsMatchMaker;
809814
if (pwm != null)
810815
{
811-
var state = Global.Server.PlanetWarsMatchMaker.GenerateLobbyCommand();
816+
// admin view gets a per-viewer command so per-option flags render correctly
817+
var state = Global.Server.PlanetWarsMatchMaker.GenerateLobbyCommand(Global.Account?.Name, Global.Account?.Faction?.Shortcut);
812818
if (state != null) return View("PwMatchMaker", state);
813819
}
814820
return Content("Match maker offline");
815821
}
816822

817823
[Auth]
818-
public ActionResult MatchMakerJoin(int planetID)
824+
public ActionResult MatchMakerJoin(int planetID, string attackerFaction)
819825
{
820-
Global.Server.RequestJoinPlanet(Global.Account.Name, planetID);
826+
Global.Server.RequestJoinPlanet(Global.Account.Name, planetID, attackerFaction);
821827
return MatchMaker();
822828
}
823829

Zero-K.info/Views/Planetwars/Planet.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
</tr>
3939
</table>
4040

41-
@if (Global.IsAccountAuthorized && db.CurrentAccount().CanPlayerPlanetWars() && (Global.Server.PlanetWarsMatchMaker != null && Global.Server?.PlanetWarsMatchMaker?.AttackingFaction?.FactionID == Global.FactionID) && Model.CanMatchMakerPlay(db.CurrentAccount().Faction))
41+
@if (Global.IsAccountAuthorized && db.CurrentAccount().CanPlayerPlanetWars() && Global.Server.PlanetWarsMatchMaker != null && Global.Server.PlanetWarsMatchMaker.Phase == ZeroKWeb.PwPhase.AttackCollect && Model.OwnerFactionID != Global.FactionID && Model.CanMatchMakerPlay(db.CurrentAccount().Faction))
4242
{
4343
<a href="@Url.Action("MatchMakerAttack", "Planetwars", new { planetID = Model.PlanetID })">ATTACK PLANET</a>
4444
}
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1-
@using LobbyClient
1+
@using LobbyClient
22
@using PlasmaShared
33
@using ZeroKWeb
44
@using ZkData
55
@model LobbyClient.PwMatchCommand
66
@{
77
PwMatchCommand pw = Model;
8-
string text = "";
8+
string text;
99
var db = new ZkDataContext();
1010
if (pw.Mode == PwMatchCommand.ModeType.Attack)
1111
{
12-
text = string.Format("{0} picks a planet to attack", pw.AttackerFaction);
12+
text = "Pick a planet to attack";
1313
}
1414
else
1515
{
16-
var planetNames = string.Join(", ", pw.Options.Select(o => o.PlanetName));
17-
text = string.Format("{0} attacks {2}, {1} defends", pw.AttackerFaction, string.Join(",", pw.DefenderFactions), planetNames);
16+
var planetNames = string.Join(", ", pw.Options.Select(o => o.PlanetName + " (" + (o.AttackerFaction ?? "?") + ")"));
17+
text = string.Format("Defend options: {0}", planetNames);
1818
}
19-
20-
bool canClick = (pw.Mode == PwMatchCommand.ModeType.Attack && pw.AttackerFaction == Global.Account.Faction.Shortcut) || (pw.Mode == PwMatchCommand.ModeType.Defend && pw.DefenderFactions.Contains(Global.Account.Faction.Shortcut));
2119
}
2220

2321
<div id="matchMaker">
@@ -30,14 +28,23 @@
3028
foreach (PwMatchCommand.VoteOption opt in pw.Options)
3129
{
3230
<span style="border: 1px solid cyan;">
33-
@if (canClick)
31+
@if (opt.CanSelectForBattle)
3432
{
35-
@Ajax.ActionLink("Join", "MatchMakerJoin", new { planetID = opt.PlanetID }, new AjaxOptions { UpdateTargetId = "matchMaker", InsertionMode = InsertionMode.Replace })
33+
@Ajax.ActionLink("Join", "MatchMakerJoin", new { planetID = opt.PlanetID, attackerFaction = opt.AttackerFaction }, new AjaxOptions { UpdateTargetId = "matchMaker", InsertionMode = InsertionMode.Replace })
3634
}
3735
@Html.PrintPlanet(db.Planets.First(x => x.PlanetID == opt.PlanetID))
36+
<span>[@opt.AttackerFaction]</span>
3837
<span>[@opt.Count/@opt.Needed]</span>
38+
@if (opt.WinChance != null)
39+
{
40+
<span>(WHR A:@opt.AttackerAvgWhr D:@opt.DefenderAvgWhr, @opt.WinChance% attacker)</span>
41+
}
42+
else if (opt.AttackerAvgWhr > 0)
43+
{
44+
<span>(WHR @opt.AttackerAvgWhr)</span>
45+
}
3946
</span>
4047
<text>&nbsp; &nbsp;&nbsp;</text>
4148
}
4249
}
43-
</div>
50+
</div>

ZeroKLobby/Notifications/PwBar.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Windows.Forms;
45
using LobbyClient;
@@ -59,9 +60,11 @@ void UpdateGui()
5960
deadline = DateTime.Now.AddSeconds(pw.DeadlineSeconds);
6061
timerLabel.Text = PlasmaShared.Utils.PrintTimeRemaining(pw.DeadlineSeconds);
6162

63+
var attackerFactionsText = string.Join(",", pw.AttackerFactions ?? new List<string>());
64+
6265
if (pw.Mode == PwMatchCommand.ModeType.Attack)
6366
{
64-
headerLabel.Text = string.Format("{0} picks a planet to attack", pw.AttackerFaction);
67+
headerLabel.Text = string.Format("{0} picks a planet to attack", attackerFactionsText);
6568

6669
foreach (Button c in pnl.Controls.OfType<Button>().ToList()) pnl.Controls.Remove(c);
6770

@@ -72,7 +75,7 @@ void UpdateGui()
7275
var but = new Button { Text = string.Format("{0} [{1}/{2}]", opt.PlanetName, opt.Count, opt.Needed), AutoSize = true };
7376
Program.ToolTip.SetMap(but, opt.Map);
7477

75-
if (pw.AttackerFaction == tas.MyUser.Faction) // NOTE this is for cases where nightwatch self faction info is delayed
78+
if (pw.AttackerFactions != null && pw.AttackerFactions.Contains(tas.MyUser.Faction))
7679
{
7780
AddButtonClick(opt, but);
7881
}
@@ -83,7 +86,7 @@ void UpdateGui()
8386
else if (pw.Mode == PwMatchCommand.ModeType.Defend)
8487
{
8588
headerLabel.Text = string.Format("{0} attacks, {1} defends",
86-
pw.AttackerFaction,
89+
attackerFactionsText,
8790
string.Join(",", pw.DefenderFactions));
8891

8992
foreach (Button c in pnl.Controls.OfType<Button>().ToList()) pnl.Controls.Remove(c);

ZkData/Ef/Account.cs

Lines changed: 17 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 DateTime? PwLastChargeChange { get; set; }
156158
public bool HasVpnException { get; set; }
157159
public bool HasKudos { get; set; }
158160
public int ForumTotalUpvotes { get; set; }
@@ -530,6 +532,21 @@ public void ResetQuotas()
530532

531533

532534

535+
public void SpendPwAttackCharge()
536+
{
537+
if (PwAttackCharges > 0) PwAttackCharges--;
538+
PwLastChargeChange = DateTime.UtcNow;
539+
}
540+
541+
public void GrantPwAttackCharge(int maxCharges)
542+
{
543+
if (maxCharges <= 0) return;
544+
if (PwAttackCharges >= maxCharges) return;
545+
PwAttackCharges++;
546+
PwLastChargeChange = DateTime.UtcNow;
547+
}
548+
549+
533550
public void SpendBombers(double count)
534551
{
535552
PwBombersUsed += count;

0 commit comments

Comments
 (0)