From 7425b9e79b73a49486ae1e8611292e1f423b93cd Mon Sep 17 00:00:00 2001 From: Jake <84546680+Nergly@users.noreply.github.com> Date: Fri, 24 Apr 2026 14:37:33 -0500 Subject: [PATCH] Persist zero-ban death records as standalone ban history rows --- .../augmentedhardcore/domain/Ban.java | 18 ++++- .../domain/data/PlayerData.java | 6 +- .../domain/data/ServerData.java | 1 + .../mappers/ban/MySQLBanMapper.java | 68 +++++++++++++++---- src/resources/dbsetup.sql | 3 +- 5 files changed, 80 insertions(+), 16 deletions(-) diff --git a/src/com/backtobedrock/augmentedhardcore/domain/Ban.java b/src/com/backtobedrock/augmentedhardcore/domain/Ban.java index 0fff177..57b3fee 100644 --- a/src/com/backtobedrock/augmentedhardcore/domain/Ban.java +++ b/src/com/backtobedrock/augmentedhardcore/domain/Ban.java @@ -31,8 +31,13 @@ public class Ban { private final long timeSincePreviousDeathBan; private final long timeSincePreviousDeath; private LocalDateTime expirationDate; + private boolean nullified; public Ban(LocalDateTime startDate, LocalDateTime expirationDate, int banTime, DamageCause damageCause, DamageCauseType damageCauseType, Location location, Killer killer, Killer inCombatWith, String deathMessage, long timeSincePreviousDeathBan, long timeSincePreviousDeath) { + this(startDate, expirationDate, banTime, damageCause, damageCauseType, location, killer, inCombatWith, deathMessage, timeSincePreviousDeathBan, timeSincePreviousDeath, false); + } + + public Ban(LocalDateTime startDate, LocalDateTime expirationDate, int banTime, DamageCause damageCause, DamageCauseType damageCauseType, Location location, Killer killer, Killer inCombatWith, String deathMessage, long timeSincePreviousDeathBan, long timeSincePreviousDeath, boolean nullified) { this.plugin = JavaPlugin.getPlugin(AugmentedHardcore.class); this.startDate = startDate; this.expirationDate = expirationDate; @@ -45,6 +50,7 @@ public Ban(LocalDateTime startDate, LocalDateTime expirationDate, int banTime, D this.deathMessage = deathMessage; this.timeSincePreviousDeathBan = timeSincePreviousDeathBan; this.timeSincePreviousDeath = timeSincePreviousDeath; + this.nullified = nullified; } public static Ban Deserialize(ConfigurationSection section) { @@ -59,6 +65,7 @@ public static Ban Deserialize(ConfigurationSection section) { int cBanTime = section.getInt("BanTime", 0); long cTimeSincePreviousDeathBan = section.getLong("TimeSincePreviousDeathBan", 0); long cTimeSincePreviousDeath = section.getLong("TimeSincePreviousDeath", 0); + boolean cNullified = section.getBoolean("Nullified", false); ConfigurationSection locationSection = section.getConfigurationSection("Location"); if (locationSection != null) { @@ -76,7 +83,7 @@ public static Ban Deserialize(ConfigurationSection section) { cInCombatWith = Killer.Deserialize(inCombatWithSection); } - return new Ban(cStartDate, cExpirationDate, cBanTime, cDamageCause, cDamageCauseType, cLocation, cKiller, cInCombatWith, cDeathMessage, cTimeSincePreviousDeathBan, cTimeSincePreviousDeath); + return new Ban(cStartDate, cExpirationDate, cBanTime, cDamageCause, cDamageCauseType, cLocation, cKiller, cInCombatWith, cDeathMessage, cTimeSincePreviousDeathBan, cTimeSincePreviousDeath, cNullified); } public String getBanMessage() { @@ -120,6 +127,14 @@ public int getBanTime() { return banTime; } + public boolean isNullified() { + return nullified; + } + + public void setNullified(boolean nullified) { + this.nullified = nullified; + } + public Map serialize() { Map map = new HashMap<>(); Map locationMap = new HashMap<>(); @@ -140,6 +155,7 @@ public Map serialize() { map.put("BanTime", this.banTime); map.put("TimeSincePreviousDeathBan", this.timeSincePreviousDeathBan); map.put("TimeSincePreviousDeath", this.timeSincePreviousDeath); + map.put("Nullified", this.nullified); return map; } diff --git a/src/com/backtobedrock/augmentedhardcore/domain/data/PlayerData.java b/src/com/backtobedrock/augmentedhardcore/domain/data/PlayerData.java index 3fde0ab..7292649 100644 --- a/src/com/backtobedrock/augmentedhardcore/domain/data/PlayerData.java +++ b/src/com/backtobedrock/augmentedhardcore/domain/data/PlayerData.java @@ -332,8 +332,12 @@ private void ban(PlayerDeathEvent event, Player player) { return; } - //ban player + //always store a death record, even when it does not trigger an active death ban if (ban.getBanTime() == 0) { + ban.setNullified(true); + BanEntry entry = this.addBan(ban); + this.plugin.getServerRepository().removeBanFromServerData(this.player.getUniqueId(), entry); + this.plugin.getPlayerRepository().updatePlayerData(this); return; } diff --git a/src/com/backtobedrock/augmentedhardcore/domain/data/ServerData.java b/src/com/backtobedrock/augmentedhardcore/domain/data/ServerData.java index 96564b4..723603f 100644 --- a/src/com/backtobedrock/augmentedhardcore/domain/data/ServerData.java +++ b/src/com/backtobedrock/augmentedhardcore/domain/data/ServerData.java @@ -143,6 +143,7 @@ public void removeBan(OfflinePlayer player) { Unban unban = this.ongoingBans.remove(player.getUniqueId()); if (unban != null) { unban.getBan().ban().setExpirationDate(LocalDateTime.now()); + unban.getBan().ban().setNullified(true); this.plugin.getPlayerRepository().getByPlayer(player).thenAcceptAsync(playerData -> { this.ongoingIPBans.remove(playerData.getLastKnownIp()); if (player.getPlayer() != null) { diff --git a/src/com/backtobedrock/augmentedhardcore/mappers/ban/MySQLBanMapper.java b/src/com/backtobedrock/augmentedhardcore/mappers/ban/MySQLBanMapper.java index 213cc30..c83bd39 100644 --- a/src/com/backtobedrock/augmentedhardcore/mappers/ban/MySQLBanMapper.java +++ b/src/com/backtobedrock/augmentedhardcore/mappers/ban/MySQLBanMapper.java @@ -17,6 +17,8 @@ import java.util.logging.Level; public class MySQLBanMapper extends AbstractMapper implements IBanMapper { + private volatile Boolean hasNullifiedColumn = null; + public MySQLBanMapper(AugmentedHardcore plugin) { super(plugin); } @@ -24,6 +26,12 @@ public MySQLBanMapper(AugmentedHardcore plugin) { public BanEntry getBanFromResultSetSync(ResultSet resultSet) { try { if (resultSet.getObject("ban_id") != null) { + boolean nullified; + try { + nullified = resultSet.getBoolean("nullified"); + } catch (SQLException ignored) { + nullified = false; + } return new BanEntry(resultSet.getInt("ban_id"), new Ban( resultSet.getTimestamp("start_date").toLocalDateTime(), @@ -36,7 +44,8 @@ public BanEntry getBanFromResultSetSync(ResultSet resultSet) { resultSet.getBoolean("in_combat") ? new Killer(resultSet.getString("in_combat_with_name"), resultSet.getString("in_combat_with_display_name"), ConfigUtils.getEntityType(resultSet.getString("in_combat_with_entity_type"))) : null, resultSet.getString("death_message"), resultSet.getLong("time_since_previous_death_ban"), - resultSet.getLong("time_since_previous_death") + resultSet.getLong("time_since_previous_death"), + nullified ) ); } @@ -69,9 +78,40 @@ public CompletableFuture updateBanAsync(Server server, UUID uuid, BanEntry } public void updateBanSync(Server server, UUID uuid, BanEntry ban) { - String sql = "INSERT INTO ah_ban (`ban_id`,`player_uuid`,`server_ip`,`server_port`,`start_date`,`expiration_date`,`ban_time`,`damage_cause`,`damage_cause_type`,`world`,`x`,`y`,`z`,`has_killer`,`killer_name`,`killer_display_name`,`killer_entity_type`,`in_combat`,`in_combat_with_name`,`in_combat_with_display_name`,`in_combat_with_entity_type`,`death_message`,`time_since_previous_death_ban`,`time_since_previous_death`)" - + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - + "ON DUPLICATE KEY UPDATE " + try (Connection connection = this.database.getConnection()) { + boolean includeNullified = this.hasNullifiedColumn(connection); + String sql = this.getUpdateSql(includeNullified); + int updateOffset = includeNullified ? 26 : 25; + try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { + bindBanParameters(preparedStatement, 1, server, uuid, ban, includeNullified); + bindBanParameters(preparedStatement, updateOffset, server, uuid, ban, includeNullified); + preparedStatement.execute(); + } + } catch (SQLException | UnknownHostException e) { + this.plugin.getLogger().log(Level.SEVERE, "Could not update ban.", e); + } + } + + private boolean hasNullifiedColumn(Connection connection) throws SQLException { + if (this.hasNullifiedColumn != null) { + return this.hasNullifiedColumn; + } + + synchronized (this) { + if (this.hasNullifiedColumn != null) { + return this.hasNullifiedColumn; + } + try (ResultSet columns = connection.getMetaData().getColumns(connection.getCatalog(), null, "ah_ban", "nullified")) { + this.hasNullifiedColumn = columns.next(); + } + return this.hasNullifiedColumn; + } + } + + private String getUpdateSql(boolean includeNullified) { + String base = "INSERT INTO ah_ban (`ban_id`,`player_uuid`,`server_ip`,`server_port`,`start_date`,`expiration_date`,`ban_time`,`damage_cause`,`damage_cause_type`,`world`,`x`,`y`,`z`,`has_killer`,`killer_name`,`killer_display_name`,`killer_entity_type`,`in_combat`,`in_combat_with_name`,`in_combat_with_display_name`,`in_combat_with_entity_type`,`death_message`,`time_since_previous_death_ban`,`time_since_previous_death`"; + String values = "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?"; + String update = "ON DUPLICATE KEY UPDATE " + "`server_ip` = ?," + "`server_port` = ?," + "`start_date` = ?," @@ -93,18 +133,17 @@ public void updateBanSync(Server server, UUID uuid, BanEntry ban) { + "`in_combat_with_entity_type`= ?," + "`death_message` = ?," + "`time_since_previous_death_ban` = ?," - + "`time_since_previous_death` = ?;"; + + "`time_since_previous_death` = ?"; - try (Connection connection = this.database.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql)) { - bindBanParameters(preparedStatement, 1, server, uuid, ban); - bindBanParameters(preparedStatement, 25, server, uuid, ban); - preparedStatement.execute(); - } catch (SQLException | UnknownHostException e) { - this.plugin.getLogger().log(Level.SEVERE, "Could not update ban.", e); + if (includeNullified) { + return base + ",`nullified`) " + + values + ",?) " + + update + ",`nullified` = ?;"; } + return base + ") " + values + ") " + update + ";"; } - private void bindBanParameters(PreparedStatement preparedStatement, int startIndex, Server server, UUID uuid, BanEntry ban) throws SQLException, UnknownHostException { + private void bindBanParameters(PreparedStatement preparedStatement, int startIndex, Server server, UUID uuid, BanEntry ban, boolean includeNullified) throws SQLException, UnknownHostException { int index = startIndex; if (startIndex == 1) { preparedStatement.setInt(index++, ban.id()); @@ -131,7 +170,10 @@ private void bindBanParameters(PreparedStatement preparedStatement, int startInd preparedStatement.setString(index++, ban.ban().getInCombatWith() == null ? null : ban.ban().getInCombatWith().getType().name()); preparedStatement.setString(index++, ban.ban().getDeathMessage()); preparedStatement.setLong(index++, ban.ban().getTimeSincePreviousDeathBan()); - preparedStatement.setLong(index, ban.ban().getTimeSincePreviousDeath()); + preparedStatement.setLong(index++, ban.ban().getTimeSincePreviousDeath()); + if (includeNullified) { + preparedStatement.setBoolean(index, ban.ban().isNullified()); + } } @Override diff --git a/src/resources/dbsetup.sql b/src/resources/dbsetup.sql index ce8b7bb..69c7fa0 100644 --- a/src/resources/dbsetup.sql +++ b/src/resources/dbsetup.sql @@ -47,7 +47,8 @@ CREATE TABLE IF NOT EXISTS ah_ban death_message TINYTEXT NOT NULL, time_since_previous_death_ban BIGINT UNSIGNED NOT NULL, time_since_previous_death BIGINT UNSIGNED NOT NULL, + nullified TINYINT NOT NULL DEFAULT 0, PRIMARY KEY (ban_id,player_uuid), CONSTRAINT player_uuid FOREIGN KEY (player_uuid) REFERENCES ah_player (player_uuid) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_server FOREIGN KEY (server_ip, server_port) REFERENCES ah_server (server_ip, server_port) ON DELETE SET NULL ON UPDATE SET NULL -); \ No newline at end of file +);