S3A Bots: frühere Helden, Rotationsfehler behoben, Champion-Kopfgeld halbiert
Erweiterte Regeln: Bots sparen ab ihrer Heldenrunde für den Helden (Helden in Runde 4-6 statt 11-12). Rotation mit sieben Profilen gab allen Plätzen dasselbe Profil (Schritt 7); jetzt Schritt 5. Angriffschampions geben dem Verteidiger das halbe Kopfgeld (Legion TD 2); Assassinen-Profil ohne Champion. Balance-Werkzeug spielt jeden Seed gespiegelt: Blau/Rot 59/61, Sylvara 41 %. Referenz unverändert (Golden 0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b79bf5c3d2
commit
e756a90f4c
7 changed files with 42 additions and 9 deletions
|
|
@ -168,7 +168,7 @@ namespace Luneraria.Core
|
||||||
/// bot match). Usable as a fixed profile per slot (MatchOptions.Profiles, balance runs).
|
/// bot match). Usable as a fixed profile per slot (MatchOptions.Profiles, balance runs).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly OrderedMap<string, BotProfile> ExtraBotProfiles = Ordered(
|
public static readonly OrderedMap<string, BotProfile> ExtraBotProfiles = Ordered(
|
||||||
new BotProfile { Name = "assassine", Front = 0.3, Melee = 0.25, Ranged = 0.25, Splash = 0.1, Support = 0.1, UpgradeFrom = 6, SupportLane = 0.0, Hero = "held_sylvara", HeroRound = 3, Items = new[] { "glutklinge", "funkenfokus", "basaltschild" }, TalentChoices = new[] { 1, 0 }, Champion = "ch_druck" }
|
new BotProfile { Name = "assassine", Front = 0.3, Melee = 0.25, Ranged = 0.25, Splash = 0.1, Support = 0.1, UpgradeFrom = 6, SupportLane = 0.0, Hero = "held_sylvara", HeroRound = 3, Items = new[] { "glutklinge", "funkenfokus", "basaltschild" }, TalentChoices = new[] { 1, 0 }, Champion = "" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ namespace Luneraria.Core
|
||||||
// S1: extended rules (MatchOptions.Extended; the reference rules ignore all of these).
|
// S1: extended rules (MatchOptions.Extended; the reference rules ignore all of these).
|
||||||
/// <summary>Legion TD 2: a booked attack champion raises the booker's income by cost / this, for good.</summary>
|
/// <summary>Legion TD 2: a booked attack champion raises the booker's income by cost / this, for good.</summary>
|
||||||
public int ChampionIncomeDivisor = 12;
|
public int ChampionIncomeDivisor = 12;
|
||||||
|
/// <summary>S3: share of a champion's bounty the defenders get under the extended rules (Legion TD 2: less than the send costs).</summary>
|
||||||
|
public double ChampionBountyShare = 0.5;
|
||||||
/// <summary>Dota 2 barracks: waves of a team on a lane whose enemy inner tower fell get stronger.</summary>
|
/// <summary>Dota 2 barracks: waves of a team on a lane whose enemy inner tower fell get stronger.</summary>
|
||||||
public double BarracksBoost = 0.25;
|
public double BarracksBoost = 0.25;
|
||||||
public int BarracksExtraMonsters = 1;
|
public int BarracksExtraMonsters = 1;
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,15 @@ namespace Luneraria.Core
|
||||||
if (match.BotProfiles.Count == 6)
|
if (match.BotProfiles.Count == 6)
|
||||||
return match.BotProfiles[slot];
|
return match.BotProfiles[slot];
|
||||||
// S1: with extended rules the rotation also holds the profiles of later heroes (Sylvara).
|
// S1: with extended rules the rotation also holds the profiles of later heroes (Sylvara).
|
||||||
|
if (!match.Extended)
|
||||||
|
{
|
||||||
|
IReadOnlyList<string> reference = ContentData.BotProfiles.Keys;
|
||||||
|
return reference[(slot * 7 + match.SeedValue) % reference.Count];
|
||||||
|
}
|
||||||
var names = new List<string>(ContentData.BotProfiles.Keys);
|
var names = new List<string>(ContentData.BotProfiles.Keys);
|
||||||
if (match.Extended)
|
|
||||||
names.AddRange(ContentData.ExtraBotProfiles.Keys);
|
names.AddRange(ContentData.ExtraBotProfiles.Keys);
|
||||||
return names[(slot * 7 + match.SeedValue) % names.Count];
|
// S3 fix: with seven profiles the reference step 7 gave every seat the same profile.
|
||||||
|
return names[(slot * 5 + match.SeedValue) % names.Count];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Plan(LuneMatch match, int slot, string profileName = "")
|
public static void Plan(LuneMatch match, int slot, string profileName = "")
|
||||||
|
|
@ -43,6 +48,14 @@ namespace Luneraria.Core
|
||||||
// S2 team actions come before the army too, so a threatened base gets its Ancient level.
|
// S2 team actions come before the army too, so a threatened base gets its Ancient level.
|
||||||
if (match.Extended)
|
if (match.Extended)
|
||||||
PlanTeam(match, slot);
|
PlanTeam(match, slot);
|
||||||
|
// S3 (extended rules): from its hero round a bot saves for the hero instead of buying troops
|
||||||
|
// (the reference bots spend everything and recruit around round 11).
|
||||||
|
if (match.Extended && player.Draft.Hero == null && match.RoundNumber >= profile.HeroRound && player.Draft.Roster.Count >= 3)
|
||||||
|
{
|
||||||
|
PlanBoss(match, slot, profile);
|
||||||
|
match.SetReady(slot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int guard = 0; guard < 40; guard++)
|
for (int guard = 0; guard < 40; guard++)
|
||||||
{
|
{
|
||||||
List<RosterEntry> roster = player.Draft.Roster;
|
List<RosterEntry> roster = player.Draft.Roster;
|
||||||
|
|
|
||||||
|
|
@ -644,6 +644,9 @@ namespace Luneraria.Core
|
||||||
/// <summary>Legion TD 2: income a booked champion adds for good.</summary>
|
/// <summary>Legion TD 2: income a booked champion adds for good.</summary>
|
||||||
public int ChampionIncome(int cost) { return cost / GdMath.Max(1, Balance.ChampionIncomeDivisor); }
|
public int ChampionIncome(int cost) { return cost / GdMath.Max(1, Balance.ChampionIncomeDivisor); }
|
||||||
|
|
||||||
|
/// <summary>S3: the defenders' bounty of a champion (halved under the extended rules).</summary>
|
||||||
|
public int ChampionBountyFor(int bounty) { return Extended ? (int)Math.Floor(bounty * Balance.ChampionBountyShare) : bounty; }
|
||||||
|
|
||||||
public int BuybackCost(HeroState hero) { return Balance.BuybackCost(LuneContent.LevelFor(hero.Xp)); }
|
public int BuybackCost(HeroState hero) { return Balance.BuybackCost(LuneContent.LevelFor(hero.Xp)); }
|
||||||
|
|
||||||
/// <summary>Dota 2 barracks: the enemy inner tower of this lane has fallen, so this team's waves there are reinforced.</summary>
|
/// <summary>Dota 2 barracks: the enemy inner tower of this lane has fallen, so this team's waves there are reinforced.</summary>
|
||||||
|
|
@ -711,6 +714,8 @@ namespace Luneraria.Core
|
||||||
if (booking.DeployRound == RoundNumber && booking.Lane == lane)
|
if (booking.DeployRound == RoundNumber && booking.Lane == lane)
|
||||||
{
|
{
|
||||||
UnitDef definition = LuneContent.ChampionDefinition(booking.ChampionId, RoundNumber, Balance);
|
UnitDef definition = LuneContent.ChampionDefinition(booking.ChampionId, RoundNumber, Balance);
|
||||||
|
if (Extended)
|
||||||
|
definition.Bounty = ChampionBountyFor(definition.Bounty);
|
||||||
Combat.Spawn("champion", team, lane, definition, 13.0, ((double)(index % 3) - 1.0) * 1.4, slot, booking.Uid);
|
Combat.Spawn("champion", team, lane, definition, 13.0, ((double)(index % 3) - 1.0) * 1.4, slot, booking.Uid);
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ namespace Luneraria.UI
|
||||||
Subtitle = spec.RoleText + " · " + spec.Cost + " Gold · Einsatz in Welle " + round + " – " + LuneUi.Upper(LuneMap.LaneNames[TargetLane]),
|
Subtitle = spec.RoleText + " · " + spec.Cost + " Gold · Einsatz in Welle " + round + " – " + LuneUi.Upper(LuneMap.LaneNames[TargetLane]),
|
||||||
// S1 (Legion TD 2): sending is also an investment in income.
|
// S1 (Legion TD 2): sending is also an investment in income.
|
||||||
Text = spec.Text + (_model != null && _model.State.Extended ? "\nSöldner-Einkommen: +" + _model.State.ChampionIncome(spec.Cost) + " Gold in jeder folgenden Runde, dauerhaft." : ""),
|
Text = spec.Text + (_model != null && _model.State.Extended ? "\nSöldner-Einkommen: +" + _model.State.ChampionIncome(spec.Cost) + " Gold in jeder folgenden Runde, dauerhaft." : ""),
|
||||||
Note = "Einmaliger Einsatz: kämpft eine Welle lang und verschwindet danach. Die Ankündigung ist für alle sichtbar; sein Tod bringt dem Gegner " + definition.Bounty + " Gold.",
|
Note = "Einmaliger Einsatz: kämpft eine Welle lang und verschwindet danach. Die Ankündigung ist für alle sichtbar; sein Tod bringt dem Gegner " + (_model != null ? _model.State.ChampionBountyFor(definition.Bounty) : definition.Bounty) + " Gold.",
|
||||||
Warn = Cards[championId].Reason,
|
Warn = Cards[championId].Reason,
|
||||||
};
|
};
|
||||||
tip.Stats.AddRange(LuneStats.Rows(definition));
|
tip.Stats.AddRange(LuneStats.Rows(definition));
|
||||||
|
|
|
||||||
3
docs/port/logs/s3/balance-s3a-bots.txt
Normal file
3
docs/port/logs/s3/balance-s3a-bots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
rules; matches; blue wins; red wins; draws; open; mean rounds; min; max; first tower wins; champion income/seat; barracks waves; exhausted heroes; buybacks; blessings; sylvara seats; sylvara wins; glyphs fired; Ancient levels; recipe items
|
||||||
|
referenz; 120; 60; 60; 0; 0; 10.3; 9; 12; 100/120; 0.0; 0; 0; 0; 0; 0; 0; 0; 0; 0
|
||||||
|
erweitert; 120; 59; 61; 0; 0; 10.1; 9; 11; 104/120; 14.6; 287; 4035; 490; 60; 102; 42; 231; 333; 0
|
||||||
|
|
@ -84,10 +84,20 @@ public static class Program
|
||||||
{
|
{
|
||||||
int blue = 0, red = 0, draws = 0, open = 0, rounds = 0, min = 999, max = 0, firstTowerWins = 0, firstTowerGames = 0;
|
int blue = 0, red = 0, draws = 0, open = 0, rounds = 0, min = 999, max = 0, firstTowerWins = 0, firstTowerGames = 0;
|
||||||
long income = 0; int barracks = 0, exhausted = 0, buybacks = 0, blessings = 0, sylvaraSeats = 0, sylvaraWins = 0, glyphs = 0, cores = 0, recipes = 0;
|
long income = 0; int barracks = 0, exhausted = 0, buybacks = 0, blessings = 0, sylvaraSeats = 0, sylvaraWins = 0, glyphs = 0, cores = 0, recipes = 0;
|
||||||
for (int seed = 1; seed <= seeds; seed++)
|
// S3: every seed twice, the second time with the teams' profiles swapped, so a strong
|
||||||
|
// profile combination counts for both sides and only a real side advantage remains.
|
||||||
|
for (int game = 0; game < seeds * 2; game++)
|
||||||
{
|
{
|
||||||
|
int seed = game / 2 + 1;
|
||||||
|
var probe = new LuneMatch();
|
||||||
|
probe.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = extended });
|
||||||
|
var rotation = new List<string>();
|
||||||
|
for (int slot = 0; slot < 6; slot++)
|
||||||
|
rotation.Add(LuneBot.ProfileFor(probe, slot));
|
||||||
|
if (game % 2 == 1)
|
||||||
|
rotation = rotation.Skip(3).Concat(rotation.Take(3)).ToList();
|
||||||
var match = new LuneMatch();
|
var match = new LuneMatch();
|
||||||
match.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = extended });
|
match.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = extended, Profiles = rotation });
|
||||||
int firstTower = -1;
|
int firstTower = -1;
|
||||||
while (match.Phase != Phase.Over && match.RoundNumber <= 80)
|
while (match.Phase != Phase.Over && match.RoundNumber <= 80)
|
||||||
{
|
{
|
||||||
|
|
@ -127,8 +137,8 @@ public static class Program
|
||||||
if (player.Hero != null) recipes += player.Hero.Items.Count(id => ContentData.Items[id].IsRecipe);
|
if (player.Hero != null) recipes += player.Hero.Items.Count(id => ContentData.Items[id].IsRecipe);
|
||||||
}
|
}
|
||||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}; {1}; {2}; {3}; {4}; {5}; {6:F1}; {7}; {8}; {9}/{10}; {11:F1}; {12}; {13}; {14}; {15}; {16}; {17}; {18}; {19}; {20}",
|
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}; {1}; {2}; {3}; {4}; {5}; {6:F1}; {7}; {8}; {9}/{10}; {11:F1}; {12}; {13}; {14}; {15}; {16}; {17}; {18}; {19}; {20}",
|
||||||
extended ? "erweitert" : "referenz", seeds, blue, red, draws, open, (double)rounds / seeds, min, max, firstTowerWins, firstTowerGames,
|
extended ? "erweitert" : "referenz", seeds * 2, blue, red, draws, open, (double)rounds / (seeds * 2), min, max, firstTowerWins, firstTowerGames,
|
||||||
(double)income / (seeds * 6), barracks, exhausted, buybacks, blessings, sylvaraSeats, sylvaraWins, glyphs, cores, recipes));
|
(double)income / (seeds * 12), barracks, exhausted, buybacks, blessings, sylvaraSeats, sylvaraWins, glyphs, cores, recipes));
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue