diff --git a/src/main/java/net/tfminecraft/simplefactions/database/WarData.java b/src/main/java/net/tfminecraft/simplefactions/database/WarData.java index 2e5ff4da..82be779d 100644 --- a/src/main/java/net/tfminecraft/simplefactions/database/WarData.java +++ b/src/main/java/net/tfminecraft/simplefactions/database/WarData.java @@ -11,6 +11,9 @@ public class WarData { public int id; public String status; public String goal; + public String defenderCounterGoal; + public String defenderCounterRelationTypeId; + public boolean firstBattleStarted; public String warType; public String attackerLeaderId; public String defenderLeaderId; diff --git a/src/main/java/net/tfminecraft/simplefactions/enums/SFGUI.java b/src/main/java/net/tfminecraft/simplefactions/enums/SFGUI.java index 57619f1e..b2a57843 100644 --- a/src/main/java/net/tfminecraft/simplefactions/enums/SFGUI.java +++ b/src/main/java/net/tfminecraft/simplefactions/enums/SFGUI.java @@ -21,6 +21,7 @@ public enum SFGUI { WAR_DECLARE_GOVERNMENT_LAW, WAR_DECLARE_SETTLEMENT, WAR_VIEW, + WAR_COUNTER_GOAL, CAMPAIGN_VIEW, CAMPAIGN_INSTALLATION_PICK_VIEW, CAMPAIGN_RAID_LAUNCH_VIEW, diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java index 370da93a..470cb44a 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/InventoryManager.java @@ -1348,7 +1348,7 @@ public void clickButton(InventoryClickEvent e) { } } else if (inv.getHolder() instanceof WarInventoryHolder warHolder) { e.setCancelled(true); - if (warHolder.getType() == SFGUI.WAR_VIEW) { + if (warHolder.getType() == SFGUI.WAR_VIEW || warHolder.getType() == SFGUI.WAR_COUNTER_GOAL) { warView.click(e, inv, p); } } else if (inv.getHolder() instanceof CampaignInventoryHolder campaignHolder) { diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java index 865b2f28..981dd4df 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarCreator.java @@ -90,6 +90,12 @@ public ItemStack createWarItem(War w, boolean button) { } } } + if (w.getDefenderCounterGoal() == null) { + lore.add(StringFormatter.formatHex("#a39ba8Defender counter: #f5ef42War Reparations (default)")); + } else { + lore.add(StringFormatter.formatHex("#a39ba8Defender counter: #f5ef42" + + w.getDefenderCounterGoal().getDisplayName())); + } m.setLore(lore); NamespacedKey key = new NamespacedKey(SimpleFactions.plugin, "id"); m.getPersistentDataContainer().set(key, PersistentDataType.INTEGER, w.getId()); diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java index 17ea2459..7daf2999 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/WarView.java @@ -12,12 +12,15 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; import net.tfminecraft.simplefactions.SimpleFactions; import net.tfminecraft.simplefactions.managers.FactionManager; import net.tfminecraft.simplefactions.managers.InventoryManager; import net.tfminecraft.simplefactions.managers.WarManager; +import net.tfminecraft.simplefactions.loaders.RelationLoader; +import net.tfminecraft.simplefactions.diplomacy.RelationType; import net.tfminecraft.simplefactions.managers.holder.SFCombinedInventoryHolder; import net.tfminecraft.simplefactions.managers.holder.WarInventoryHolder; import net.tfminecraft.simplefactions.objects.Faction; @@ -25,6 +28,11 @@ import net.tfminecraft.simplefactions.war.core.Side; import net.tfminecraft.simplefactions.war.core.War; import net.tfminecraft.simplefactions.war.enums.WarType; +import net.tfminecraft.simplefactions.war.enums.WarGoalType; +import net.tfminecraft.simplefactions.war.battle.engine.core.Battle; +import net.tfminecraft.simplefactions.war.battle.engine.core.BattleManager; +import net.tfminecraft.simplefactions.war.declare.WarGoalValidator; +import net.tfminecraft.simplefactions.war.civilwar.wartime.CivilWarBorderLock; import net.tfminecraft.simplefactions.war.battle.template.BattleTemplate; import net.tfminecraft.simplefactions.managers.holder.SFInventoryHolder; import net.tfminecraft.simplefactions.enums.SFGUI; @@ -121,9 +129,116 @@ public void warView(Inventory i, Player player, War w, boolean open) { i.setItem(49, creator.createCampaignButton(w)); } i.setItem(53, inv.createBackButton(SFGUI.WAR_VIEW)); + i.setItem(48, createCounterGoalItem(w, player)); if(open) player.openInventory(i); } + private ItemStack createCounterGoalItem(War war, Player viewer) { + syncFirstBattleStarted(war); + boolean canEdit = canEditCounterGoal(war, viewer); + ItemStack item = new ItemStack(canEdit ? Material.GOLDEN_SWORD : Material.GOLD_INGOT); + ItemMeta meta = item.getItemMeta(); + WarGoalType selected = war.getDefenderCounterGoal(); + meta.setDisplayName("§eDefender Counter Goal"); + List lore = new ArrayList<>(); + lore.add(selected == null + ? "§7Default: War Reparations" + : "§7Selected: " + selected.getDisplayName()); + if (war.hasFirstBattleStarted()) { + lore.add("§cLocked after the first battle began"); + } else if (canEdit) { + lore.add("§aClick to set or change"); + } else { + lore.add("§7Set by the defending nation before battle"); + } + meta.setLore(lore); + item.setItemMeta(meta); + return item; + } + + private boolean canEditCounterGoal(War war, Player player) { + syncFirstBattleStarted(war); + if (war == null || player == null || !war.isActive() || war.hasFirstBattleStarted() + || war.getWarType() == WarType.RAID || CivilWarBorderLock.isCivilWar(war)) { + return false; + } + Faction leader = FactionManager.getByLeader(player.getName()); + return leader != null && war.getDefenderLeaderId() != null + && war.getDefenderLeaderId().equalsIgnoreCase(leader.getId()); + } + + private void syncFirstBattleStarted(War war) { + if (war == null || war.hasFirstBattleStarted()) return; + Battle battle = BattleManager.getByWarId(war.getId()); + if (battle != null && battle.hasStarted()) { + war.setFirstBattleStarted(true); + WarManager.persist(war); + } + } + + private void counterGoalView(Player player, War war) { + Inventory inventory = SimpleFactions.plugin.getServer().createInventory( + new WarInventoryHolder(war.getId(), SFGUI.WAR_COUNTER_GOAL), + 27, + "§7Defender Counter Goal"); + int slot = 10; + addCounterGoalOption(inventory, slot++, WarGoalType.TRIBUTARY, Material.GOLD_INGOT, + "§7Make the attacker a tributary"); + addCounterGoalOption(inventory, slot++, WarGoalType.OPEN_MARKET, Material.BOOK, + "§7Force the attacker's market law open"); + Faction attacker = FactionManager.getByString(war.getAttackerLeaderId()); + Faction defender = FactionManager.getByString(war.getDefenderLeaderId()); + if (attacker != null && defender != null && attacker.getHighestTitle() != null + && WarGoalValidator.canUsurpByRank(defender.getTier().getTier(), attacker.getTier().getTier())) { + addCounterGoalOption(inventory, slot++, WarGoalType.USURP, Material.NETHER_STAR, + "§7Take the attacker's highest title"); + } + List vassalTypes = RelationLoader.getWarPickableVassalTypes(); + if (vassalTypes != null && !vassalTypes.isEmpty()) { + for (RelationType type : vassalTypes) { + if (slot >= 17) break; + ItemStack option = goalOption(Material.CHAINMAIL_CHESTPLATE, "§eSubjugate: " + type.getName(), + "§7Make the attacker your " + type.getName()); + ItemMeta meta = option.getItemMeta(); + meta.getPersistentDataContainer().set( + new NamespacedKey(SimpleFactions.plugin, "counter_relation_type"), + PersistentDataType.STRING, + type.getId()); + option.setItemMeta(meta); + inventory.setItem(slot++, option); + } + } + ItemStack defaultOption = goalOption(Material.PAPER, "§fDefault: War Reparations", + "§7Use 25% of each payer guild's gross trade income for 10 days"); + ItemMeta defaultMeta = defaultOption.getItemMeta(); + defaultMeta.getPersistentDataContainer().set( + new NamespacedKey(SimpleFactions.plugin, "counter_default"), PersistentDataType.BYTE, (byte) 1); + defaultOption.setItemMeta(defaultMeta); + inventory.setItem(22, defaultOption); + inventory.setItem(26, inv.createBackButton(SFGUI.WAR_VIEW)); + player.openInventory(inventory); + } + + private void addCounterGoalOption(Inventory inventory, int slot, WarGoalType goal, Material material, String description) { + ItemStack option = goalOption(material, "§e" + goal.getDisplayName(), description); + ItemMeta meta = option.getItemMeta(); + meta.getPersistentDataContainer().set( + new NamespacedKey(SimpleFactions.plugin, "counter_goal"), + PersistentDataType.STRING, + goal.toJson()); + option.setItemMeta(meta); + inventory.setItem(slot, option); + } + + private ItemStack goalOption(Material material, String name, String description) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(name); + meta.setLore(List.of(description, "§7Choosing this removes war reparations")); + item.setItemMeta(meta); + return item; + } + private void fillSideColumn( Inventory i, Player player, @@ -247,6 +362,43 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) { if (w == null) return; p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f); warView(null, p, w, true); + } else if (inventory.getHolder() instanceof WarInventoryHolder holder + && holder.getType() == SFGUI.WAR_COUNTER_GOAL) { + e.setCancelled(true); + War war = WarManager.getById(holder.getId()); + if (war == null) return; + if (e.getSlot() == 26) { + warView(null, p, war, true); + return; + } + if (!canEditCounterGoal(war, p)) { + p.sendMessage("§cCounter goals can only be changed by the defending nation's leader before the first battle begins."); + warView(null, p, war, true); + return; + } + ItemStack selected = e.getCurrentItem(); + if (selected == null || selected.getItemMeta() == null) return; + var data = selected.getItemMeta().getPersistentDataContainer(); + String relationTypeId = data.get( + new NamespacedKey(SimpleFactions.plugin, "counter_relation_type"), + PersistentDataType.STRING); + String goalId = data.get( + new NamespacedKey(SimpleFactions.plugin, "counter_goal"), + PersistentDataType.STRING); + boolean useDefault = data.has( + new NamespacedKey(SimpleFactions.plugin, "counter_default"), + PersistentDataType.BYTE); + if (useDefault || goalId != null || relationTypeId != null) { + WarGoalType goal = useDefault ? null : relationTypeId != null + ? WarGoalType.SUBJUGATE + : WarGoalType.fromJson(goalId); + war.setDefenderCounterGoal(goal, relationTypeId); + WarManager.persist(war); + p.sendMessage(goal == null + ? "§aWar reparations set as the default counter." + : "§aDefender counter goal set to " + goal.getDisplayName() + "."); + } + warView(null, p, war, true); } else if(inventory.getHolder() instanceof WarInventoryHolder && ((WarInventoryHolder) inventory.getHolder()).getType().equals(SFGUI.WAR_VIEW)) { e.setCancelled(true); WarInventoryHolder h = (WarInventoryHolder) inventory.getHolder(); @@ -262,6 +414,14 @@ public void click(InventoryClickEvent e, Inventory inventory, Player p) { p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f); inv.openCampaignView(p, w); return; + } else if (e.getSlot() == 48) { + if (!canEditCounterGoal(w, p)) { + p.sendMessage("§cOnly the defending nation's leader can set a counter goal before the first battle begins."); + return; + } + counterGoalView(p, w); + p.playSound(p, Sound.BLOCK_NOTE_BLOCK_BIT, 1f, 1f); + return; } if (e.getCurrentItem() == null || e.getCurrentItem().getItemMeta() == null) return; NamespacedKey overflow = new NamespacedKey(SimpleFactions.plugin, "mercenary_overflow"); diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java index a6ae9f34..079c6218 100644 --- a/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java +++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/campaign/CampaignBattleLaunchService.java @@ -12,6 +12,7 @@ import org.bukkit.entity.Player; import net.tfminecraft.simplefactions.utils.Permissions; +import net.tfminecraft.simplefactions.managers.WarManager; import net.tfminecraft.simplefactions.war.core.War; import net.tfminecraft.simplefactions.war.battle.engine.core.Battle; import net.tfminecraft.simplefactions.war.battle.engine.core.BattleFactory; @@ -126,7 +127,12 @@ public static String startPreparedBattle(War war, Battle battle) { return "Battle already started."; } CampaignBattleRosterService.ensureEnrolledForced(war, battle); - return battle.start(); + String error = battle.start(); + if (error == null) { + war.setFirstBattleStarted(true); + WarManager.persist(war); + } + return error; } private static Battle createCampaignBattle(War war, int provinceId, boolean immediateStart) { diff --git a/src/main/java/net/tfminecraft/simplefactions/war/battle/engine/core/BattleManager.java b/src/main/java/net/tfminecraft/simplefactions/war/battle/engine/core/BattleManager.java index 9200a454..7926453e 100644 --- a/src/main/java/net/tfminecraft/simplefactions/war/battle/engine/core/BattleManager.java +++ b/src/main/java/net/tfminecraft/simplefactions/war/battle/engine/core/BattleManager.java @@ -26,6 +26,8 @@ import net.tfminecraft.vehicleframework.events.VFExplosionEvent; import net.tfminecraft.simplefactions.SimpleFactions; +import net.tfminecraft.simplefactions.managers.WarManager; +import net.tfminecraft.simplefactions.war.core.War; import net.tfminecraft.simplefactions.war.battle.enums.BattleEndReason; import net.tfminecraft.simplefactions.war.battle.enums.BattleType; import net.tfminecraft.simplefactions.war.battle.enums.DefenderRespawnMode; @@ -490,6 +492,13 @@ public void invenClick(InventoryClickEvent e) { return; } BattlePersistenceService.persistBattle(b); + if (b.getWarId() != null) { + War war = WarManager.getById(b.getWarId()); + if (war != null) { + war.setFirstBattleStarted(true); + WarManager.persist(war); + } + } p.closeInventory(); } } else if(e.getSlot() == 22) { diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java index bb868bd0..0af77ffe 100644 --- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java +++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/raid/fight/CampaignRaidBattleService.java @@ -13,6 +13,7 @@ import org.bukkit.entity.Player; import net.tfminecraft.simplefactions.Cache; +import net.tfminecraft.simplefactions.managers.WarManager; import net.tfminecraft.simplefactions.war.battle.engine.core.Battle; import net.tfminecraft.simplefactions.war.battle.engine.core.BattleFactory; import net.tfminecraft.simplefactions.war.battle.engine.core.BattleJoinService; @@ -146,6 +147,8 @@ public static Battle createAndStart(War war, CampaignRaid raid, Instant now) { if (startError != null) { return null; } + war.setFirstBattleStarted(true); + WarManager.persist(war); teleportAttackerWarband(attackerWarband, sourceCenter); alertDefenders(war, raid, target); diff --git a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java index 5b6914ec..e5170011 100644 --- a/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java +++ b/src/main/java/net/tfminecraft/simplefactions/war/campaign/runtime/BattleAutoresolveService.java @@ -165,6 +165,8 @@ public static boolean resolve(War war, Random random) { war.setPostponementsThisCycle(0); return true; } + war.setFirstBattleStarted(true); + WarManager.persist(war); CampaignCoalition offensive = CampaignCapabilityService.battleOffensiveCoalition(war); if (offensive == null) { diff --git a/src/main/java/net/tfminecraft/simplefactions/war/core/War.java b/src/main/java/net/tfminecraft/simplefactions/war/core/War.java index 51b278b5..bb53a060 100644 --- a/src/main/java/net/tfminecraft/simplefactions/war/core/War.java +++ b/src/main/java/net/tfminecraft/simplefactions/war/core/War.java @@ -41,6 +41,9 @@ public class War { private Side attackers; private Side defenders; private WarGoalType goal; + private WarGoalType defenderCounterGoal; + private String defenderCounterRelationTypeId; + private boolean firstBattleStarted; private WarType warType; private WarStatus status = WarStatus.ACTIVE; private String attackerLeaderId; @@ -186,6 +189,30 @@ public void setGoal(WarGoalType goal) { this.goal = goal; } + public WarGoalType getDefenderCounterGoal() { + return defenderCounterGoal; + } + + public String getDefenderCounterRelationTypeId() { + return defenderCounterRelationTypeId; + } + + public void setDefenderCounterGoal(WarGoalType goal, String relationTypeId) { + if (firstBattleStarted) { + return; + } + this.defenderCounterGoal = goal; + this.defenderCounterRelationTypeId = goal == WarGoalType.SUBJUGATE ? relationTypeId : null; + } + + public boolean hasFirstBattleStarted() { + return firstBattleStarted; + } + + public void setFirstBattleStarted(boolean firstBattleStarted) { + this.firstBattleStarted = firstBattleStarted; + } + public WarType getWarType() { return warType; } diff --git a/src/main/java/net/tfminecraft/simplefactions/war/core/WarMapper.java b/src/main/java/net/tfminecraft/simplefactions/war/core/WarMapper.java index 454571d4..09b40039 100644 --- a/src/main/java/net/tfminecraft/simplefactions/war/core/WarMapper.java +++ b/src/main/java/net/tfminecraft/simplefactions/war/core/WarMapper.java @@ -57,6 +57,11 @@ public static WarData toData(War war) { if (war.getGoal() != null) { data.goal = war.getGoal().toJson(); } + if (war.getDefenderCounterGoal() != null) { + data.defenderCounterGoal = war.getDefenderCounterGoal().toJson(); + } + data.defenderCounterRelationTypeId = war.getDefenderCounterRelationTypeId(); + data.firstBattleStarted = war.hasFirstBattleStarted(); if (war.getWarType() != null) { data.warType = war.getWarType().toJson(); } @@ -197,6 +202,10 @@ public static War fromData(WarData data) { war.setSchemaVersion(data.schemaVersion > 0 ? data.schemaVersion : 2); war.setGoal(WarGoalType.fromJson(data.goal)); + war.setDefenderCounterGoal( + WarGoalType.fromJson(data.defenderCounterGoal), + data.defenderCounterRelationTypeId); + war.setFirstBattleStarted(data.firstBattleStarted); war.setWarType(WarType.fromJson(data.warType)); war.setStatus(WarStatus.fromJson(data.status)); if (data.attackerLeaderId != null) { diff --git a/src/main/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeService.java b/src/main/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeService.java index b2cff9ef..dd67ec28 100644 --- a/src/main/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeService.java +++ b/src/main/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeService.java @@ -14,6 +14,7 @@ import net.tfminecraft.simplefactions.objects.handler.ProvinceHandler; import net.tfminecraft.simplefactions.tiers.Title; import net.tfminecraft.simplefactions.war.core.War; +import net.tfminecraft.simplefactions.war.core.WarDeclareHelper; import net.tfminecraft.simplefactions.war.civilwar.wartime.CivilWarBorderLock; import net.tfminecraft.simplefactions.war.declare.DeJureAnnexEligibility; import net.tfminecraft.simplefactions.war.declare.OpenMarketEligibility; @@ -38,8 +39,10 @@ public static void apply(War war, WarEndReason reason) { case DEFENDER_VICTORY -> { if (CivilWarBorderLock.isCivilWar(war)) { endMovementEmpty(war); - } else { + } else if (war.getDefenderCounterGoal() == null) { WarReparationsService.applyFromWar(war); + } else { + applyDefenderCounterGoal(war); } } case WHITE_PEACE, ADMIN_END -> { @@ -50,6 +53,20 @@ public static void apply(War war, WarEndReason reason) { } } + private static void applyDefenderCounterGoal(War war) { + War counterWar = new War( + war.getId(), + war.getDefenders(), + war.getAttackers(), + war.getDefenderCounterGoal(), + WarDeclareHelper.warTypeForGoal(war.getDefenderCounterGoal()), + null, + war.getObjectiveProvinceId(), + war.getStartedAt()); + counterWar.setRelationTypeId(war.getDefenderCounterRelationTypeId()); + applyAttackerGoal(counterWar); + } + static void applyAttackerGoal(War war) { WarGoalType goal = war.getGoal(); if (goal == null) { diff --git a/src/test/java/net/tfminecraft/simplefactions/war/core/WarMapperTest.java b/src/test/java/net/tfminecraft/simplefactions/war/core/WarMapperTest.java index d651fda8..2cadbf56 100644 --- a/src/test/java/net/tfminecraft/simplefactions/war/core/WarMapperTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/war/core/WarMapperTest.java @@ -823,6 +823,37 @@ void fromData_omittedInstallationPicksDefaultsEmpty() { } } + @Test + void fromData_restoresDefenderCounterGoalAndBattleLock() { + Faction attacker = mock(Faction.class); + Faction defender = mock(Faction.class); + when(attacker.getId()).thenReturn("faction_a"); + when(defender.getId()).thenReturn("faction_b"); + FactionManager.factions.add(attacker); + FactionManager.factions.add(defender); + try { + WarData data = minimalWarData(); + data.defenderCounterGoal = "subjugate"; + data.defenderCounterRelationTypeId = "march"; + data.firstBattleStarted = true; + + War war = WarMapper.fromData(data); + assertEquals(WarGoalType.SUBJUGATE, war.getDefenderCounterGoal()); + assertEquals("march", war.getDefenderCounterRelationTypeId()); + assertTrue(war.hasFirstBattleStarted()); + + war.setDefenderCounterGoal(WarGoalType.TRIBUTARY, null); + assertEquals(WarGoalType.SUBJUGATE, war.getDefenderCounterGoal()); + WarData roundTripped = WarMapper.toData(war); + assertEquals("subjugate", roundTripped.defenderCounterGoal); + assertEquals("march", roundTripped.defenderCounterRelationTypeId); + assertTrue(roundTripped.firstBattleStarted); + } finally { + FactionManager.factions.remove(attacker); + FactionManager.factions.remove(defender); + } + } + private static WarData minimalWarData() { WarData data = new WarData(); data.id = 9; diff --git a/src/test/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeServiceTest.java index e70437c7..d14659e1 100644 --- a/src/test/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeServiceTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/war/resolution/WarOutcomeServiceTest.java @@ -619,6 +619,21 @@ void defenderVictory_addsObligationOnAttacker() { assertEquals(10, obligation.getDaysRemaining()); } + @Test + void defenderVictory_selectedCounterGoalAppliesAgainstAttackerInsteadOfReparations() { + Fixture fx = fixture(); + fx.war.setDefenderCounterGoal(WarGoalType.TRIBUTARY, null); + RelationType tributary = mock(RelationType.class); + when(tributary.getId()).thenReturn("tributary"); + try (MockedStatic loader = mockStatic(RelationLoader.class); + MockedStatic relations = mockStatic(RelationManager.class)) { + loader.when(() -> RelationLoader.getType("tributary")).thenReturn(tributary); + WarOutcomeService.apply(fx.war, WarEndReason.DEFENDER_VICTORY); + relations.verify(() -> RelationManager.setRelationForced(tributary, fx.attacker, fx.defender)); + } + assertTrue(fx.payerObligations.isEmpty()); + } + @Test void tickAfterDailySettlement_decrementsThenRemoves() { Faction payer = mock(Faction.class);