diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java index 6801a130..165abaed 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java @@ -482,6 +482,8 @@ public void time() { leader.sendMessage("§cGet more prestige or unclaim provinces to counteract this!"); } } + SimpleFactions.getInstance().getVehicleUpkeepService() + .warnBankShortfalls(Bukkit.getOnlinePlayers(), getSecondsUntilNewDay()); } if (timer >= DAY_LENGTH_SECONDS) { runDailyStep("player ledgers", () -> PlayerEconomyManager.get().clearAllDaily()); diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleMaintenanceMessages.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleMaintenanceMessages.java index 943703d7..9c13f0b8 100644 --- a/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleMaintenanceMessages.java +++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleMaintenanceMessages.java @@ -1,5 +1,6 @@ package net.tfminecraft.simplefactions.vehicles.maintenance; +import net.tfminecraft.simplefactions.utils.Formatter; import net.tfminecraft.simplefactions.vehicles.maintenance.VehicleMaintenancePayService.PaymentSource; public final class VehicleMaintenanceMessages { @@ -18,6 +19,15 @@ public static String payArmed(PaymentSource source) { + (source == PaymentSource.BANK ? "bank" : "pouch") + "."; } + public static String bankShortfall(double shortfall, int secondsUntilCharge) { + // Round up so the final partial minute does not read as already due. + int minutes = (secondsUntilCharge + 59) / 60; + return "§cYou lack " + Formatter.formatMoney(shortfall) + + " denars in your personal bank to pay vehicle maintenance in " + + minutes / 60 + "h " + minutes % 60 + "m" + + " §7(/deco deposit for the bank, it does not count the pouch)"; + } + public static String paySuccess() { return "§aPaid vehicle maintenance."; } diff --git a/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepService.java b/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepService.java index a73319e3..067151e5 100644 --- a/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepService.java +++ b/src/main/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepService.java @@ -4,6 +4,7 @@ import net.tfminecraft.simplefactions.vehicles.maintenance.DenarEconomyPlayerBank.PlayerBank; import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRegistry; import net.tfminecraft.simplefactions.vehicles.registry.VehicleOwnershipQueries; +import java.util.Collection; import java.util.UUID; import org.bukkit.Bukkit; @@ -13,6 +14,7 @@ import net.tfminecraft.simplefactions.SimpleFactions; import net.tfminecraft.simplefactions.player.PlayerEconomyManager; import net.tfminecraft.simplefactions.player.income.PlayerCashflow; +import net.tfminecraft.simplefactions.utils.Formatter; import net.tfminecraft.vehicleframework.data.OwnedVehicleSummary; public final class VehicleUpkeepService { @@ -63,6 +65,28 @@ public void processDailyUpkeep() { } } + /** Warns each player whose bank cannot cover their vehicle upkeep at the next new day. */ + public void warnBankShortfalls(Collection players, int secondsUntilCharge) { + if (secondsUntilCharge <= 0) { + return; + } + for (Player player : players) { + double upkeep = VehicleUpkeepProjection.projectedDailyUpkeep(player.getName(), registry); + if (upkeep <= 0.0) { + continue; + } + UUID playerUuid = playerBank.resolve(player.getName()); + if (playerUuid == null) { + continue; + } + // Round to cents so a float residue never reads as "lack 0.00". + double shortfall = Formatter.formatDouble(upkeep - playerBank.getBankBalance(playerUuid)); + if (shortfall > 0.0) { + player.sendMessage(VehicleMaintenanceMessages.bankShortfall(shortfall, secondsUntilCharge)); + } + } + } + public void tickHourlyDecay() { double fraction = VehiclesConfigLoader.getMaintenanceHourlyDamageFraction(); double minHealth = VehiclesConfigLoader.getMaintenanceMinHealthFraction(); diff --git a/src/test/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepServiceTest.java index 71cf5300..8ae9c214 100644 --- a/src/test/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepServiceTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/vehicles/maintenance/VehicleUpkeepServiceTest.java @@ -16,6 +16,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; @@ -30,6 +32,7 @@ import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.Server; +import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -168,6 +171,53 @@ void successfulUpkeepClearsExistingUnpaid() { assertFalse(store.isUnpaid("vehicle-1")); } + @Test + void warnsPlayerWhoseBankCannotCoverUpkeep() { + UUID playerUuid = UUID.randomUUID(); + bank.setBalance(playerUuid, 5.0); + bank.remember("Alice", playerUuid); + VehicleOwnershipQueries.setSourceForTests( + new FakeOwnedInventory().add("vehicle-1", "ironclad", "player_Alice")); + Player alice = onlinePlayer("Alice", playerUuid); + + service.warnBankShortfalls(List.of(alice), 3 * 3600 + 12 * 60); + + verify(alice).sendMessage("§cYou lack 15.00 denars in your personal bank to pay vehicle maintenance" + + " in 3h 12m §7(/deco deposit for the bank, it does not count the pouch)"); + } + + @Test + void doesNotWarnWhenBankCoversUpkeep() { + UUID playerUuid = UUID.randomUUID(); + bank.setBalance(playerUuid, 20.0); + bank.remember("Alice", playerUuid); + VehicleOwnershipQueries.setSourceForTests( + new FakeOwnedInventory().add("vehicle-1", "ironclad", "player_Alice")); + Player alice = onlinePlayer("Alice", playerUuid); + + service.warnBankShortfalls(List.of(alice), 600); + + verify(alice, never()).sendMessage(org.mockito.ArgumentMatchers.anyString()); + } + + @Test + void doesNotWarnPlayersWithoutVehicles() { + UUID playerUuid = UUID.randomUUID(); + bank.remember("Alice", playerUuid); + Player alice = onlinePlayer("Alice", playerUuid); + + service.warnBankShortfalls(List.of(alice), 600); + + verify(alice, never()).sendMessage(org.mockito.ArgumentMatchers.anyString()); + } + + private static Player onlinePlayer(String name, UUID uuid) { + Player player = mock(Player.class); + when(player.getName()).thenReturn(name); + when(player.getUniqueId()).thenReturn(uuid); + return player; + } + @Test void hourlyDecayDamagesUnpaidVehicles() { store.markUnpaid("vehicle-1", 1L);