diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java index 6958393f..9acb27a1 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyCreator.java @@ -230,8 +230,10 @@ public List buildSlotLore(String occupant) { if (occupant == null) { lore.add(StringFormatter.formatHex("#877e7cEmpty")); lore.add(StringFormatter.formatHex("#877e7cAn empty slot blocks expansion")); + lore.add(StringFormatter.formatHex("#877e7cClick to remove this unused slot")); } else { lore.add("§7Mercenary: §e" + occupant); + lore.add(StringFormatter.formatHex("#877e7cDismiss from Roster before removing a slot")); } lore.add("§7Upkeep: §e" + Formatter.formatMoney(Cache.mercenarySlotUpkeep) + "d"); return lore; diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java index 3ad5f3b7..a9a2ddf2 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/CompanyView.java @@ -28,6 +28,8 @@ public class CompanyView { private static final int UPGRADES_BUTTON = 15; private static final int CONTRACTS_BUTTON = 22; private static final int EXPAND_BUTTON = 4; + private static final int FIRST_SLOT_BUTTON = 9; + private static final int LAST_SLOT_BUTTON = 44; public InventoryManager inv; public CompanyCreator creator = new CompanyCreator(); @@ -84,8 +86,10 @@ public void slotsView(Player player, Guild guild, Inventory i) { i.clear(); MercenaryCompany company = guild.getCompany(); if (company == null) return; - for (int slot = 0; slot < company.getSlots() && slot < 36; slot++) { - i.setItem(9 + slot, creator.createSlotItem(company, slot)); + int visibleSlots = Math.min(company.getSlots(), LAST_SLOT_BUTTON - FIRST_SLOT_BUTTON + 1); + int firstVisibleSlot = company.getSlots() - visibleSlots; + for (int slot = 0; slot < visibleSlots; slot++) { + i.setItem(FIRST_SLOT_BUTTON + slot, creator.createSlotItem(company, firstVisibleSlot + slot)); } if (company.isLeader(player.getName())) { i.setItem(EXPAND_BUTTON, creator.createExpandButton(company)); @@ -200,6 +204,17 @@ private void clickCompany( private void clickSlots( InventoryClickEvent e, Inventory inventory, Player p, Guild guild, MercenaryCompany company) { + if (e.getClickedInventory() != inventory) return; + int slot = e.getSlot(); + if (slot >= FIRST_SLOT_BUTTON && slot <= LAST_SLOT_BUTTON) { + int visibleSlots = Math.min(company.getSlots(), LAST_SLOT_BUTTON - FIRST_SLOT_BUTTON + 1); + int slotIndex = company.getSlots() - visibleSlots + slot - FIRST_SLOT_BUTTON; + if (slotIndex < company.getSlots() && slotIndex >= company.getEnlisted().size()) { + report(p, MercenaryCompanyService.removeSlot(guild, p.getName())); + slotsView(p, guild, inventory); + } + return; + } if (e.getSlot() == EXPAND_BUTTON) { report(p, MercenaryCompanyService.expand(guild, p.getName())); slotsView(p, guild, inventory); diff --git a/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryCompanyService.java b/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryCompanyService.java index 9ec55903..b33d20a1 100644 --- a/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryCompanyService.java +++ b/src/main/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryCompanyService.java @@ -157,6 +157,12 @@ public static MercenaryResult kick(Guild guild, String actor, String player) { if (!company.isLeader(actor)) { return MercenaryResult.deny("Only the guild leader can dismiss mercenaries."); } + if (!company.isEnlisted(player)) { + return MercenaryResult.deny(player + " does not serve in your company."); + } + if (company.hasFreeSlot()) { + return MercenaryResult.deny("Fill every slot before dismissing a mercenary."); + } if (!company.kick(player)) { return MercenaryResult.deny(player + " does not serve in your company."); } @@ -178,6 +184,27 @@ public static MercenaryResult expand(Guild guild, String actor) { return company.enqueueExpansion(); } + /** Remove one unused slot. A dismissal must create the free slot first. */ + public static MercenaryResult removeSlot(Guild guild, String actor) { + if (guild == null || guild.getCompany() == null) { + return MercenaryResult.deny("Your guild has no mercenary company."); + } + MercenaryCompany company = guild.getCompany(); + if (!company.isLeader(actor)) { + return MercenaryResult.deny("Only the guild leader can remove company slots."); + } + if (company.isForming()) { + return MercenaryResult.deny("Your company is still being founded."); + } + if (!company.hasFreeSlot()) { + return MercenaryResult.deny("Dismiss a mercenary before removing a slot."); + } + if (!company.dropSlot()) { + return MercenaryResult.deny("There is no unused slot to remove."); + } + return MercenaryResult.ok("Removed one unused company slot."); + } + public static MercenaryResult upgrade(Guild guild, String actor, String upgradeId) { if (guild == null || guild.getCompany() == null) { return MercenaryResult.deny("Your guild has no mercenary company."); diff --git a/src/test/java/net/tfminecraft/simplefactions/mercenary/company/CompanyGuiTest.java b/src/test/java/net/tfminecraft/simplefactions/mercenary/company/CompanyGuiTest.java index 1c55687d..0427f0b5 100644 --- a/src/test/java/net/tfminecraft/simplefactions/mercenary/company/CompanyGuiTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/mercenary/company/CompanyGuiTest.java @@ -4,10 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.List; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,7 +18,9 @@ import net.tfminecraft.simplefactions.Cache; import net.tfminecraft.simplefactions.guild.income.Ledger; import net.tfminecraft.simplefactions.guild.upgrade.Upgrade; +import net.tfminecraft.simplefactions.managers.InventoryManager; import net.tfminecraft.simplefactions.managers.inventory.CompanyCreator; +import net.tfminecraft.simplefactions.managers.inventory.CompanyView; import net.tfminecraft.simplefactions.utils.Formatter; /** The company screens, checked through the lore builders as the ledger screen is. */ @@ -94,6 +99,20 @@ void aCompanyWithNoContractsShowsPeacetimeBurnOnly() { assertTrue(lore.stream().anyMatch(line -> line.contains("Net position"))); } + @Test + void aLargeCompanyShowsItsLastSlotsInTheRemovalMenu() { + MercenaryCompany company = formedCompany(); + company.getRegiment().setCurrentSlots(40); + Inventory inventory = mock(Inventory.class); + CompanyView view = new CompanyView(mock(InventoryManager.class)); + view.creator = mock(CompanyCreator.class); + + view.slotsView(mock(Player.class), fixture.guild, inventory); + + verify(view.creator).createSlotItem(company, 4); + verify(view.creator).createSlotItem(company, 39); + } + @Test void theWarningTriggersOnTheBoundaryAndNotBefore() { MercenaryCompany company = formedCompany(); diff --git a/src/test/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryEnlistmentTest.java b/src/test/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryEnlistmentTest.java index caf9827f..ec61afb8 100644 --- a/src/test/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryEnlistmentTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/mercenary/company/MercenaryEnlistmentTest.java @@ -140,6 +140,48 @@ void kickingFreesTheSlotAndFreezesExpansion() { assertEquals("Fill every slot before adding another.", company.getExpansionBlockedReason()); } + @Test + void kickingIsRefusedWhileAnotherSlotIsAlreadyFree() { + CompanyFixture fixture = new CompanyFixture(0); + MercenaryCompany company = formed(fixture); + company.getRegiment().sizeIncrease(); + company.enlist("Sigrun"); + + MercenaryResult result = MercenaryCompanyService.kick(fixture.guild, "Ivar", "Sigrun"); + + assertFalse(result.ok()); + assertEquals("Fill every slot before dismissing a mercenary.", result.message()); + assertEquals(List.of("Sigrun"), company.getEnlisted()); + } + + @Test + void anUnusedSlotCanBeRemovedAfterDismissingItsMercenary() { + CompanyFixture fixture = new CompanyFixture(0); + MercenaryCompany company = formed(fixture); + company.enlist("Sigrun"); + + assertTrue(MercenaryCompanyService.kick(fixture.guild, "Ivar", "Sigrun").ok()); + MercenaryResult result = MercenaryCompanyService.removeSlot(fixture.guild, "Ivar"); + + assertTrue(result.ok()); + assertEquals(0, company.getSlots()); + assertTrue(company.getEnlisted().isEmpty()); + } + + @Test + void aFilledSlotCannotBeRemoved() { + CompanyFixture fixture = new CompanyFixture(0); + MercenaryCompany company = formed(fixture); + company.enlist("Sigrun"); + + MercenaryResult result = MercenaryCompanyService.removeSlot(fixture.guild, "Ivar"); + + assertFalse(result.ok()); + assertEquals("Dismiss a mercenary before removing a slot.", result.message()); + assertEquals(1, company.getSlots()); + assertEquals(List.of("Sigrun"), company.getEnlisted()); + } + @Test void onlyTheLeaderMayKick() { CompanyFixture fixture = new CompanyFixture(0);