Skip to content

Commit 3d267ec

Browse files
Drefvelinryanbarlow97claude
authored
Autoresolve campaign battles by simulation (#56)
Autoresolve no longer starts a live battle. Each side's committed regiments become lives, a luck roll scales them, and Lanchester's square law picks the winner and the winner's losses. The loser loses a set share of its lives. Losses go through the normal casualty and outcome path, with the offensive pinned so fuel is charged correctly. A stalled battle vote postpones once (war.battle_voting.max_postponements) and then autoresolves. Legacy AUTORESOLVE_PENDING wars are resolved on the next tick. Battle start hours are now 21 to 23; the window end hour is not a start time. Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f23feb9 commit 3d267ec

21 files changed

Lines changed: 831 additions & 52 deletions

‎src/main/java/net/tfminecraft/simplefactions/Cache.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public static boolean requireProvinces(org.bukkit.command.CommandSender sender)
106106
public static boolean warBattleVotingPassIfEither;
107107
public static int warBattleVotingDevMinPlayers;
108108
public static boolean warBattleVotingDevMinPlayersEnabled;
109+
public static int warBattleVotingMaxPostponements = 1;
110+
public static double warAutoresolveLuck = 0.15;
111+
public static double warAutoresolveLoserLossFraction = 0.5;
109112
public static int warBattleLivesPerRegiment;
110113
public static int warBattleMinSideLives;
111114
public static int warBattleDeathsPerRegimentLoss;

‎src/main/java/net/tfminecraft/simplefactions/loaders/ConfigLoader.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,18 @@ public void loadWar(File warFile) {
213213
} else {
214214
Cache.warBattleVotingDevMinPlayers = Cache.warBattleVotingMinPlayers;
215215
}
216+
Cache.warBattleVotingMaxPostponements = Math.max(
217+
0,
218+
config.getInt("war.battle_voting.max_postponements", 1));
219+
double autoresolveLuck = config.getDouble("war.autoresolve.luck", 0.15);
220+
if (autoresolveLuck < 0) {
221+
autoresolveLuck = 0;
222+
} else if (autoresolveLuck > 1) {
223+
autoresolveLuck = 1;
224+
}
225+
Cache.warAutoresolveLuck = autoresolveLuck;
226+
double loserLossFraction = config.getDouble("war.autoresolve.loser_loss_fraction", 0.5);
227+
Cache.warAutoresolveLoserLossFraction = loserLossFraction < 0 ? 0 : loserLossFraction;
216228
Cache.warDevmodePhantomCount = config.getInt("war.devmode.phantom_count", 10);
217229
validateBattleScheduleConfig();
218230

‎src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java‎

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import net.tfminecraft.simplefactions.war.campaign.progression.AttackerNavalContestService;
2626
import net.tfminecraft.simplefactions.war.campaign.progression.CampaignCapabilityService;
2727
import net.tfminecraft.simplefactions.war.campaign.progression.postbattle.CampaignOffensiveForfeitService;
28+
import net.tfminecraft.simplefactions.war.campaign.runtime.BattleAutoresolveService;
2829
import net.tfminecraft.simplefactions.war.campaign.runtime.BattleScheduleService;
2930
import net.tfminecraft.simplefactions.war.campaign.runtime.BattleSideMembers;
3031
import net.tfminecraft.simplefactions.war.campaign.runtime.BattleWindowService;
@@ -69,37 +70,13 @@ public static Battle prepareScheduledBattle(War war) {
6970
return battle;
7071
}
7172

73+
/** Simulated result. Does not create or start a live battle. */
7274
public static Battle launchAutoresolveBattle(War war) {
7375
if (war == null || !war.isActive()) {
7476
return null;
7577
}
76-
Battle existing = BattleManager.getByWarId(war.getId());
77-
Integer provinceId = BattleScheduleService.resolveScheduledProvinceId(war);
78-
if (provinceId != null
79-
&& AttackerNavalContestService.applyIfAttackerHasNoBerthedNavy(war, provinceId)) {
80-
return null;
81-
}
82-
83-
if (existing != null) {
84-
String startError = startPreparedBattle(war, existing);
85-
if (startError != null) {
86-
logWarning("Could not start autoresolve battle: " + startError);
87-
}
88-
return existing;
89-
}
90-
91-
if (provinceId == null) {
92-
return null;
93-
}
94-
95-
Battle battle = createCampaignBattle(war, provinceId, true);
96-
if (battle != null) {
97-
String startError = startPreparedBattle(war, battle);
98-
if (startError != null) {
99-
logWarning("Could not start autoresolve battle: " + startError);
100-
}
101-
}
102-
return battle;
78+
BattleAutoresolveService.resolve(war);
79+
return null;
10380
}
10481

10582
public static boolean tryStartScheduledBattle(War war, Instant now) {

‎src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleOutcomeService.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public static CampaignBattleApplyResult applyCampaignBattleOutcome(
126126
return new CampaignBattleApplyResult(false, false, Optional.empty());
127127
}
128128

129+
war.setPostponementsThisCycle(0);
130+
129131
CampaignPushTarget preBattlePushTarget = war.getPushTarget();
130132
ObjectiveHolder preBattleObjectiveHeldBy = war.getObjectiveHeldBy();
131133

‎src/main/java/net/tfminecraft/simplefactions/war/campaign/admin/WarScheduleAdminService.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ public static WarScheduleAdminResult closeVote(War war, Instant now) {
147147

148148
case AUTORESOLVE_PENDING -> WarScheduleAdminResult.ok("Vote close set AUTORESOLVE_PENDING.");
149149

150+
case AUTORESOLVED -> WarScheduleAdminResult.ok("Vote close autoresolved the battle.");
151+
150152
case BLOCKED_DEFENDER_CHOICE -> WarScheduleAdminResult.error(
151153

152154
"Vote close blocked: defender choice unresolved.");

‎src/main/java/net/tfminecraft/simplefactions/war/campaign/admin/WarScheduleFeedbackFormatter.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ private static String formatCloseVote(War war) {
6464
return "§7Phase: §e" + formatPhase(war);
6565
}
6666
if (phase == BattleSchedulePhase.VOTING && war.getScheduledBattleAt() == null) {
67+
if (war.getPostponementsThisCycle() == 0) {
68+
return "§7Autoresolved §7· Battle day: §e" + formatBattleDay(war)
69+
+ " §7· Phase: §e" + formatPhase(war);
70+
}
6771
return "§7Postponed §7· Battle day: §e" + formatBattleDay(war)
6872
+ " §7· Phase: §e" + formatPhase(war);
6973
}

0 commit comments

Comments
 (0)