diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
deleted file mode 100644
index f95f1ee80..000000000
--- a/.mvn/wrapper/maven-wrapper.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-wrapperVersion=3.3.2
-distributionType=only-script
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip
diff --git a/co/aikar/commands/annotations.xml b/co/aikar/commands/annotations.xml
deleted file mode 100644
index 7a662ec6e..000000000
--- a/co/aikar/commands/annotations.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
- -
-
-
- -
-
-
- -
-
-
-
\ No newline at end of file
diff --git a/org/bukkit/configuration/annotations.xml b/org/bukkit/configuration/annotations.xml
deleted file mode 100644
index 2e2c43ac6..000000000
--- a/org/bukkit/configuration/annotations.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
- -
-
-
-
-
-
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index d5a110ed5..8aaf30ce3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -208,7 +208,7 @@
me.clip
placeholderapi
- 2.11.3
+ 2.11.7
provided
diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/listeners/SCPlayerListener.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/listeners/SCPlayerListener.java
index 09f871a15..0c1a53687 100644
--- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/listeners/SCPlayerListener.java
+++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/listeners/SCPlayerListener.java
@@ -174,26 +174,14 @@ private void registerChatListener() {
}
private void updatePlayerName(@NotNull final Player player) {
+ // Update in-memory ClanPlayer if exists
final ClanPlayer cp = plugin.getClanManager().getAnyClanPlayer(player.getUniqueId());
-
- ClanPlayer duplicate = null;
- for (ClanPlayer other : plugin.getClanManager().getAllClanPlayers()) {
- if (other.getName().equals(player.getName()) && !other.getUniqueId().equals(player.getUniqueId())) {
- duplicate = other;
- break;
- }
- }
-
- if (duplicate != null) {
- plugin.getLogger().warning(String.format("Found duplicate for %s, UUIDs: %s, %s", player.getName(),
- player.getUniqueId(), duplicate.getUniqueId()));
- duplicate.setName(duplicate.getUniqueId().toString());
- plugin.getStorageManager().updatePlayerName(duplicate);
- }
if (cp != null) {
cp.setName(player.getName());
- plugin.getStorageManager().updatePlayerName(cp);
}
+
+ // Synchronize player data in database asynchronously
+ plugin.getStorageManager().syncPlayerDataAsync(player);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java
index b218311cd..e9b0d5d4e 100644
--- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java
+++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java
@@ -10,6 +10,7 @@
import net.sacredlabyrinth.phaed.simpleclans.utils.ChatUtils;
import net.sacredlabyrinth.phaed.simpleclans.utils.YAMLSerializer;
import net.sacredlabyrinth.phaed.simpleclans.uuid.UUIDFetcher;
+import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -60,8 +61,8 @@ public StorageManager() {
* @param player the Player
* @return the ChatBlock
*/
- public ChatBlock getChatBlock(Player player) {
- return chatBlocks.get(player.getName());
+ public ChatBlock getChatBlock(@NotNull Player player) {
+ return chatBlocks.get(player.getName());
}
/**
@@ -69,7 +70,7 @@ public ChatBlock getChatBlock(Player player) {
*
*/
public void addChatBlock(CommandSender player, ChatBlock cb) {
- chatBlocks.put(player.getName(), cb);
+ chatBlocks.put(player.getName(), cb);
}
/**
@@ -101,13 +102,13 @@ public void initiateDB() {
+ " `packed_bb` mediumtext NOT NULL,"
+ " `cape_url` varchar(255) NOT NULL,"
+ " `flags` text NOT NULL,"
- + " `balance` double(64,2),"
- + " `fee_enabled` tinyint(1) default '0',"
- + " `fee_value` double(64,2),"
- + " `ranks` text NOT NULL,"
+ + " `balance` double(64,2),"
+ + " `fee_enabled` tinyint(1) default '0',"
+ + " `fee_value` double(64,2),"
+ + " `ranks` text NOT NULL,"
+ " `banner` text,"
- + " PRIMARY KEY (`id`),"
- + " UNIQUE KEY `uq_simpleclans_1` (`tag`));";
+ + " PRIMARY KEY (`id`),"
+ + " UNIQUE KEY `uq_simpleclans_1` (`tag`));";
core.execute(query);
}
@@ -115,25 +116,25 @@ public void initiateDB() {
plugin.getLogger().info("Creating table: " + getPrefixedTable("players"));
String query = "CREATE TABLE IF NOT EXISTS `" + getPrefixedTable("players") + "` ("
- + " `id` bigint(20) NOT NULL auto_increment,"
- + " `name` varchar(16) NOT NULL,"
- + " `leader` tinyint(1) default '0',"
- + " `tag` varchar(25) NOT NULL,"
- + " `friendly_fire` tinyint(1) default '0',"
- + " `neutral_kills` int(11) default NULL,"
- + " `rival_kills` int(11) default NULL,"
- + " `civilian_kills` int(11) default NULL,"
+ + " `id` bigint(20) NOT NULL auto_increment,"
+ + " `name` varchar(16) NOT NULL,"
+ + " `leader` tinyint(1) default '0',"
+ + " `tag` varchar(25) NOT NULL,"
+ + " `friendly_fire` tinyint(1) default '0',"
+ + " `neutral_kills` int(11) default NULL,"
+ + " `rival_kills` int(11) default NULL,"
+ + " `civilian_kills` int(11) default NULL,"
+ " `ally_kills` int(11) default NULL,"
- + " `deaths` int(11) default NULL,"
- + " `last_seen` bigint NOT NULL,"
- + " `join_date` bigint NOT NULL,"
- + " `trusted` tinyint(1) default '0',"
- + " `flags` text NOT NULL,"
- + " `packed_past_clans` text,"
- + " `resign_times` text,"
+ + " `deaths` int(11) default NULL,"
+ + " `last_seen` bigint NOT NULL,"
+ + " `join_date` bigint NOT NULL,"
+ + " `trusted` tinyint(1) default '0',"
+ + " `flags` text NOT NULL,"
+ + " `packed_past_clans` text,"
+ + " `resign_times` text,"
+ " `locale` varchar(10),"
- + " PRIMARY KEY (`id`),"
- + " UNIQUE KEY `uq_sc_players_1` (`name`));";
+ + " PRIMARY KEY (`id`),"
+ + " UNIQUE KEY `uq_sc_players_1` (`name`));";
core.execute(query);
}
@@ -141,14 +142,14 @@ public void initiateDB() {
plugin.getLogger().info("Creating table: " + getPrefixedTable("kills"));
String query = "CREATE TABLE IF NOT EXISTS `" + getPrefixedTable("kills") + "` ("
- + " `kill_id` bigint(20) NOT NULL auto_increment,"
- + " `attacker` varchar(16) NOT NULL,"
- + " `attacker_tag` varchar(16) NOT NULL,"
- + " `victim` varchar(16) NOT NULL,"
- + " `victim_tag` varchar(16) NOT NULL,"
- + " `kill_type` varchar(1) NOT NULL,"
+ + " `kill_id` bigint(20) NOT NULL auto_increment,"
+ + " `attacker` varchar(16) NOT NULL,"
+ + " `attacker_tag` varchar(16) NOT NULL,"
+ + " `victim` varchar(16) NOT NULL,"
+ + " `victim_tag` varchar(16) NOT NULL,"
+ + " `kill_type` varchar(1) NOT NULL,"
+ " `created_at` datetime NULL,"
- + " PRIMARY KEY (`kill_id`));";
+ + " PRIMARY KEY (`kill_id`));";
core.execute(query);
}
} else {
@@ -159,7 +160,7 @@ public void initiateDB() {
if (core.checkConnection()) {
- plugin.getLogger().info(lang("sqlite.connection.successful"));
+ plugin.getLogger().info(lang("sqlite.connection.successful"));
if (!core.existsTable(getPrefixedTable("clans"))) {
plugin.getLogger().info("Creating table: " + getPrefixedTable("clans"));
@@ -179,13 +180,13 @@ public void initiateDB() {
+ " `packed_bb` mediumtext NOT NULL,"
+ " `cape_url` varchar(255) NOT NULL,"
+ " `flags` text NOT NULL,"
- + " `balance` double(64,2) default 0.0,"
- + " `fee_enabled` tinyint(1) default '0',"
- + " `fee_value` double(64,2),"
- + " `ranks` text NOT NULL,"
+ + " `balance` double(64,2) default 0.0,"
+ + " `fee_enabled` tinyint(1) default '0',"
+ + " `fee_value` double(64,2),"
+ + " `ranks` text NOT NULL,"
+ " `banner` text,"
- + " PRIMARY KEY (`id`),"
- + " UNIQUE (`tag`));";
+ + " PRIMARY KEY (`id`),"
+ + " UNIQUE (`tag`));";
core.execute(query);
}
@@ -193,25 +194,25 @@ public void initiateDB() {
plugin.getLogger().info("Creating table: " + getPrefixedTable("players"));
String query = "CREATE TABLE IF NOT EXISTS `" + getPrefixedTable("players") + "` ("
- + " `id` bigint(20),"
- + " `name` varchar(16) NOT NULL,"
- + " `leader` tinyint(1) default '0',"
- + " `tag` varchar(25) NOT NULL,"
- + " `friendly_fire` tinyint(1) default '0',"
- + " `neutral_kills` int(11) default NULL,"
- + " `rival_kills` int(11) default NULL,"
- + " `civilian_kills` int(11) default NULL,"
+ + " `id` bigint(20),"
+ + " `name` varchar(16) NOT NULL,"
+ + " `leader` tinyint(1) default '0',"
+ + " `tag` varchar(25) NOT NULL,"
+ + " `friendly_fire` tinyint(1) default '0',"
+ + " `neutral_kills` int(11) default NULL,"
+ + " `rival_kills` int(11) default NULL,"
+ + " `civilian_kills` int(11) default NULL,"
+ " `ally_kills` int(11) default NULL,"
- + " `deaths` int(11) default NULL,"
- + " `last_seen` bigint NOT NULL,"
- + " `join_date` bigint NOT NULL,"
- + " `trusted` tinyint(1) default '0',"
- + " `flags` text NOT NULL,"
- + " `packed_past_clans` text,"
- + " `resign_times` text,"
+ + " `deaths` int(11) default NULL,"
+ + " `last_seen` bigint NOT NULL,"
+ + " `join_date` bigint NOT NULL,"
+ + " `trusted` tinyint(1) default '0',"
+ + " `flags` text NOT NULL,"
+ + " `packed_past_clans` text,"
+ + " `resign_times` text,"
+ " `locale` varchar(10),"
- + " PRIMARY KEY (`id`),"
- + " UNIQUE (`name`));";
+ + " PRIMARY KEY (`id`),"
+ + " UNIQUE (`name`));";
core.execute(query);
}
@@ -219,14 +220,14 @@ public void initiateDB() {
plugin.getLogger().info("Creating table: " + getPrefixedTable("kills"));
String query = "CREATE TABLE IF NOT EXISTS `" + getPrefixedTable("kills") + "` ("
- + " `kill_id` bigint(20),"
- + " `attacker` varchar(16) NOT NULL,"
- + " `attacker_tag` varchar(16) NOT NULL,"
- + " `victim` varchar(16) NOT NULL,"
- + " `victim_tag` varchar(16) NOT NULL,"
- + " `kill_type` varchar(1) NOT NULL,"
+ + " `kill_id` bigint(20),"
+ + " `attacker` varchar(16) NOT NULL,"
+ + " `attacker_tag` varchar(16) NOT NULL,"
+ + " `victim` varchar(16) NOT NULL,"
+ + " `victim_tag` varchar(16) NOT NULL,"
+ + " `kill_type` varchar(1) NOT NULL,"
+ " `created_at` datetime NULL,"
- + " PRIMARY KEY (`kill_id`));";
+ + " PRIMARY KEY (`kill_id`));";
core.execute(query);
}
} else {
@@ -260,7 +261,7 @@ public void importFromDatabase() {
}
if (!clans.isEmpty()) {
- plugin.getLogger().info(MessageFormat.format(lang("clans"), clans.size()));
+ plugin.getLogger().info(MessageFormat.format(lang("clans"), clans.size()));
}
List cps = retrieveClanPlayers();
@@ -276,7 +277,7 @@ public void importFromDatabase() {
}
if (!cps.isEmpty()) {
- plugin.getLogger().info(MessageFormat.format(lang("clan.players"), cps.size()));
+ plugin.getLogger().info(MessageFormat.format(lang("clan.players"), cps.size()));
}
}
@@ -324,7 +325,7 @@ private void purgeClans(List clans) {
}
for (Clan clan : purge) {
- plugin.getLogger().info(lang("purging.clan", clan.getName()));
+ plugin.getLogger().info(lang("purging.clan", clan.getName()));
for (ClanPlayer member : clan.getMembers()) {
clan.removePlayerFromClan(member.getUniqueId());
}
@@ -351,7 +352,7 @@ private void purgeClanPlayers(List cps) {
}
for (ClanPlayer cp : purge) {
- plugin.getLogger().info(lang("purging.player.data", cp.getName()));
+ plugin.getLogger().info(lang("purging.player.data", cp.getName()));
deleteClanPlayer(cp);
cps.remove(cp);
}
@@ -687,28 +688,8 @@ public List retrieveClanPlayers() {
public void insertClan(Clan clan) {
plugin.getProxyManager().sendUpdate(clan);
- String query = "INSERT INTO `" + getPrefixedTable("clans") + "` (`banner`, `ranks`, `description`, `fee_enabled`, `fee_value`, `verified`, `tag`," +
- " `color_tag`, `name`, `friendly_fire`, `founded`, `last_used`, `packed_allies`, `packed_rivals`, " +
- "`packed_bb`, `cape_url`, `flags`, `balance`) ";
- String values = "VALUES ( '"
- + Helper.escapeQuotes(YAMLSerializer.serialize(clan.getBanner())) + "','"
- + Helper.escapeQuotes(Helper.ranksToJson(clan.getRanks(), clan.getDefaultRank())) + "','"
- + Helper.escapeQuotes(clan.getDescription())+ "',"
- + (clan.isMemberFeeEnabled() ? 1 : 0) +","
- + Helper.escapeQuotes(String.valueOf(clan.getMemberFee())) + ","
- + (clan.isVerified() ? 1 : 0) + ",'"
- + Helper.escapeQuotes(clan.getTag()) + "','"
- + Helper.escapeQuotes(clan.getColorTag()) + "','"
- + Helper.escapeQuotes(clan.getName()) + "',"
- + (clan.isFriendlyFire() ? 1 : 0) + ",'"
- + clan.getFounded() + "','"
- + clan.getLastUsed() + "','"
- + Helper.escapeQuotes(clan.getPackedAllies()) + "','"
- + Helper.escapeQuotes(clan.getPackedRivals()) + "','"
- + Helper.escapeQuotes(clan.getPackedBb()) + "','"
- + Helper.escapeQuotes(clan.getCapeUrl()) + "','"
- + Helper.escapeQuotes(clan.getFlags()) + "','"
- + Helper.escapeQuotes(String.valueOf(clan.getBalance())) + "');";
+ String query = "INSERT INTO `" + getPrefixedTable("clans") + "` (`banner`, `ranks`, `description`, `fee_enabled`, `fee_value`, `verified`, `tag`," + " `color_tag`, `name`, `friendly_fire`, `founded`, `last_used`, `packed_allies`, `packed_rivals`, " + "`packed_bb`, `cape_url`, `flags`, `balance`) ";
+ String values = "VALUES ( '" + Helper.escapeQuotes(YAMLSerializer.serialize(clan.getBanner())) + "','" + Helper.escapeQuotes(Helper.ranksToJson(clan.getRanks(), clan.getDefaultRank())) + "','" + Helper.escapeQuotes(clan.getDescription()) + "'," + (clan.isMemberFeeEnabled() ? 1 : 0) + "," + Helper.escapeQuotes(String.valueOf(clan.getMemberFee())) + "," + (clan.isVerified() ? 1 : 0) + ",'" + Helper.escapeQuotes(clan.getTag()) + "','" + Helper.escapeQuotes(clan.getColorTag()) + "','" + Helper.escapeQuotes(clan.getName()) + "'," + (clan.isFriendlyFire() ? 1 : 0) + ",'" + clan.getFounded() + "','" + clan.getLastUsed() + "','" + Helper.escapeQuotes(clan.getPackedAllies()) + "','" + Helper.escapeQuotes(clan.getPackedRivals()) + "','" + Helper.escapeQuotes(clan.getPackedBb()) + "','" + Helper.escapeQuotes(clan.getCapeUrl()) + "','" + Helper.escapeQuotes(clan.getFlags()) + "','" + Helper.escapeQuotes(String.valueOf(clan.getBalance())) + "');";
core.executeUpdate(query + values);
}
@@ -732,19 +713,256 @@ public void run() {
* @param cp to update
*/
public void updatePlayerNameAsync(final @NotNull ClanPlayer cp) {
- new BukkitRunnable() {
- @Override
- public void run() {
+ new BukkitRunnable() {
+ @Override
+ public void run() {
updatePlayerName(cp);
- }
- }.runTaskAsynchronously(plugin);
+ }
+ }.runTaskAsynchronously(plugin);
+ }
+
+ /**
+ * Retrieves a clan player from the database by name
+ *
+ * @param name the player name to search for
+ * @return the ClanPlayer if found, null otherwise
+ */
+ public @Nullable ClanPlayer retrieveClanPlayerByName(String name) {
+ if (name == null || name.trim().isEmpty()) {
+ return null;
+ }
+ String query = "SELECT * FROM `" + getPrefixedTable("players") + "` WHERE `name` = ?;";
+ try (Connection connection = core.getConnection();
+ PreparedStatement ps = connection.prepareStatement(query)) {
+ ps.setString(1, name);
+ try (ResultSet res = ps.executeQuery()) {
+ if (res.next()) {
+ return buildClanPlayerFromResultSet(res);
+ }
+ }
+ } catch (SQLException ex) {
+ plugin.getLogger().log(Level.SEVERE, "Error retrieving ClanPlayer by name: " + name, ex);
+ }
+ return null;
+ }
+
+ /**
+ * Helper method to build a ClanPlayer from a ResultSet
+ */
+ private ClanPlayer buildClanPlayerFromResultSet(ResultSet res) throws SQLException {
+ String uuid = res.getString("uuid");
+ String name = res.getString("name");
+ String tagStr = res.getString("tag");
+ boolean leader = res.getBoolean("leader");
+ boolean friendly_fire = res.getBoolean("friendly_fire");
+ boolean trusted = res.getBoolean("trusted");
+ int neutral_kills = res.getInt("neutral_kills");
+ int rival_kills = res.getInt("rival_kills");
+ int civilian_kills = res.getInt("civilian_kills");
+ int ally_kills = res.getInt("ally_kills");
+ int deaths = res.getInt("deaths");
+ long last_seen = res.getLong("last_seen");
+ long join_date = res.getLong("join_date");
+ String flags = res.getString("flags");
+ String packed_past_clans = res.getString("packed_past_clans");
+ String resign_times = res.getString("resign_times");
+ Locale locale = Helper.forLanguageTag(res.getString("locale"));
+
+ if (last_seen == 0) {
+ last_seen = (new Date()).getTime();
+ }
+
+ ClanPlayer cp = new ClanPlayer();
+ if (uuid != null) {
+ cp.setUniqueId(UUID.fromString(uuid));
+ }
+ cp.setFlags(flags);
+ cp.setName(name);
+ cp.setLeader(leader);
+ cp.setFriendlyFire(friendly_fire);
+ cp.setNeutralKills(neutral_kills);
+ cp.setRivalKills(rival_kills);
+ cp.setCivilianKills(civilian_kills);
+ cp.setAllyKills(ally_kills);
+ cp.setDeaths(deaths);
+ cp.setLastSeen(last_seen);
+ cp.setJoinDate(join_date);
+ cp.setPackedPastClans(packed_past_clans);
+ cp.setTrusted(leader || trusted);
+ cp.setResignTimes(Helper.resignTimesFromJson(resign_times));
+ cp.setLocale(locale);
+
+ // Set clan relationship if tag exists
+ if (tagStr != null && !tagStr.isEmpty()) {
+ Clan clan = plugin.getClanManager().getClan(tagStr);
+ if (clan != null) {
+ cp.setClan(clan);
+ }
+ }
+
+ return cp;
+ }
+
+ /**
+ * Synchronizes player data in the database asynchronously, handling duplicates
+ *
+ * @param player the player to sync
+ */
+ public void syncPlayerDataAsync(@NotNull Player player) {
+ final String currentName = player.getName();
+ final UUID currentUuid = player.getUniqueId();
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ syncPlayerData(currentName, currentUuid);
+ }
+ }.runTaskAsynchronously(plugin);
+ }
+
+ /**
+ * Synchronizes player data in the database, handling duplicates
+ * - If name differs but UUID matches: update name
+ * - If names match but UUIDs differ: update UUID
+ * - If both name and UUID exist in different records: merge and delete duplicates
+ *
+ * @param currentName the player's current name
+ * @param currentUuid the player's current UUID
+ */
+ private void syncPlayerData(@NotNull String currentName, @NotNull UUID currentUuid) {
+ try {
+ ClanPlayer byName = retrieveClanPlayerByName(currentName);
+ ClanPlayer byUuid = retrieveOneClanPlayer(currentUuid);
+
+ // Case 1: Found by name only, UUID differs - update UUID
+ if (byName != null && byUuid == null) {
+ plugin.getLogger().warning(String.format("Correcting UUID for %s: %s ā %s", byName.getName(), byName.getUniqueId(), currentUuid));
+
+ UUID oldUuid = byName.getUniqueId();
+
+ // Update UUID in database directly
+ String updateQuery = "UPDATE `" + getPrefixedTable("players") + "` SET `uuid` = ?, `name` = ?, `last_seen` = ? WHERE uuid = ?;";
+ try (Connection conn = core.getConnection();
+ PreparedStatement ps = conn.prepareStatement(updateQuery)) {
+ ps.setString(1, currentUuid.toString());
+ ps.setString(2, currentName);
+ ps.setLong(3, System.currentTimeMillis());
+ ps.setString(4, oldUuid.toString());
+ ps.executeUpdate();
+ }
+
+ // Update in-memory references on the main thread
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ byName.setUniqueId(currentUuid);
+ byName.setName(currentName);
+ byName.setLastSeen(System.currentTimeMillis());
+ plugin.getClanManager().deleteClanPlayerFromMemory(oldUuid);
+ plugin.getClanManager().importClanPlayer(byName);
+ });
+
+ plugin.getLogger().info(String.format("UUID corrected in database: %s", currentUuid));
+ return;
+ }
+
+ // Case 2: Found by UUID only, name differs - update name
+ if (byName == null && byUuid != null) {
+ plugin.getLogger().info(String.format("Correcting name for %s to %s (%s)", byUuid.getName(), currentName, currentUuid));
+
+ byUuid.setName(currentName);
+ byUuid.setLastSeen(System.currentTimeMillis());
+ updateClanPlayer(byUuid, true);
+ plugin.getLogger().info(String.format("Player name updated in database: %s", currentName));
+ return;
+ }
+
+ // Case 3: Both found and they're the same record - just update
+ if (byName != null && byName.getUniqueId().equals(byUuid.getUniqueId())) {
+ byUuid.setName(currentName);
+ byUuid.setLastSeen(System.currentTimeMillis());
+ updateClanPlayer(byUuid, true);
+ return;
+ }
+
+ // Case 4: Both found but different records - merge duplicates
+ if (byName != null && byUuid != null) {
+ plugin.getLogger().warning(String.format("Duplicate detection!%n - Record A: %s/%s%n - Record B: %s/%s%nā Merging records.", byName.getName(), byName.getUniqueId(), byUuid.getName(), byUuid.getUniqueId()));
+
+ UUID oldByNameUuid = byName.getUniqueId();
+
+ // Merge data (keep the byUuid record as base, merge stats from byName)
+ ClanPlayer merged = mergeClanPlayers(byUuid, byName);
+ merged.setUniqueId(currentUuid);
+ merged.setName(currentName);
+
+ // Delete the record with wrong UUID only
+ String deleteByName = "DELETE FROM `" + getPrefixedTable("players") + "` WHERE uuid = '" + oldByNameUuid + "';";
+ core.executeUpdate(deleteByName);
+
+ // Update the kept record with merged data
+ updateClanPlayer(merged, true);
+
+ // Update in-memory on the main thread
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ plugin.getClanManager().deleteClanPlayerFromMemory(oldByNameUuid);
+ plugin.getClanManager().importClanPlayer(merged);
+ });
+
+ plugin.getLogger().info(String.format("Duplicate records merged for %s (%s)", currentName, currentUuid));
+ }
+ } catch (SQLException e) {
+ plugin.getServer().getLogger().log(Level.SEVERE, "[SimpleClans] Error synchronizing player data for " + currentName, e);
+ }
+ }
+
+ /**
+ * Merges two ClanPlayer records, combining their stats
+ */
+ private ClanPlayer mergeClanPlayers(ClanPlayer primary, ClanPlayer secondary) {
+ ClanPlayer merged = new ClanPlayer();
+
+ // Copy identity from primary
+ merged.setUniqueId(primary.getUniqueId());
+ merged.setName(primary.getName());
+
+ // Keep clan membership from primary (or secondary if primary has none)
+ Clan primaryClan = primary.getClan();
+ Clan secondaryClan = secondary.getClan();
+ if (primaryClan != null) {
+ merged.setClan(primaryClan);
+ } else if (secondaryClan != null) {
+ merged.setClan(secondaryClan);
+ }
+
+ merged.setLeader(primary.isLeader() || secondary.isLeader());
+ merged.setTrusted(primary.isTrusted() || secondary.isTrusted());
+
+ // Merge stats (sum kills/deaths)
+ merged.setNeutralKills(primary.getNeutralKills() + secondary.getNeutralKills());
+ merged.setRivalKills(primary.getRivalKills() + secondary.getRivalKills());
+ merged.setCivilianKills(primary.getCivilianKills() + secondary.getCivilianKills());
+ merged.setAllyKills(primary.getAllyKills() + secondary.getAllyKills());
+ merged.setDeaths(primary.getDeaths() + secondary.getDeaths());
+
+ // Keep earliest join date and latest last seen
+ merged.setJoinDate(Math.min(primary.getJoinDate(), secondary.getJoinDate()));
+ merged.setLastSeen(Math.max(primary.getLastSeen(), secondary.getLastSeen()));
+
+ // Merge other properties from primary
+ merged.setFriendlyFire(primary.isFriendlyFire());
+ merged.setFlags(primary.getFlags());
+ merged.setPackedPastClans(primary.getPackedPastClans());
+ merged.setResignTimes(primary.getResignTimes());
+ merged.setLocale(primary.getLocale());
+
+ return merged;
}
/**
* Change the name of a player in the database
*
* @param cp to update
+ * @deprecated Use syncPlayerData instead for proper duplicate handling
*/
+ @Deprecated
public void updatePlayerName(final @NotNull ClanPlayer cp) {
String query = "UPDATE `" + getPrefixedTable("players") + "` SET `name` = '" + cp.getName() + "' WHERE uuid = '" + cp.getUniqueId() + "';";
core.executeUpdate(query);
@@ -761,8 +979,7 @@ public void updateClan(Clan clan) {
/**
* Update a clan to the database
*
- * @param clan clan to update
- *
+ * @param clan clan to update
* @param updateLastUsed should the clan's last used time be updated as well?
*/
public void updateClan(Clan clan, boolean updateLastUsed) {
@@ -783,9 +1000,7 @@ public void updateClan(Clan clan, boolean updateLastUsed) {
}
private PreparedStatement prepareUpdateClanStatement(Connection connection) throws SQLException {
- String sql = "UPDATE `" + getPrefixedTable("clans") + "` SET ranks = ?, banner = ?, description = ?, fee_enabled = ?, fee_value = ?, " +
- "verified = ?, tag = ?, color_tag = ?, `name` = ?, friendly_fire = ?, founded = ?, last_used = ?, " +
- "packed_allies = ?, packed_rivals = ?, packed_bb = ?, balance = ?, flags = ? WHERE tag = ?;";
+ String sql = "UPDATE `" + getPrefixedTable("clans") + "` SET ranks = ?, banner = ?, description = ?, fee_enabled = ?, fee_value = ?, " + "verified = ?, tag = ?, color_tag = ?, `name` = ?, friendly_fire = ?, founded = ?, last_used = ?, " + "packed_allies = ?, packed_rivals = ?, packed_bb = ?, balance = ?, flags = ? WHERE tag = ?;";
return connection.prepareStatement(sql);
}
@@ -826,14 +1041,8 @@ public void deleteClan(Clan clan) {
public void insertClanPlayer(ClanPlayer cp) {
plugin.getProxyManager().sendUpdate(cp);
- String query = "INSERT INTO `" + getPrefixedTable("players") + "` (`uuid`, `name`, `leader`, `tag`, `friendly_fire`, `neutral_kills`, " +
- "`rival_kills`, `civilian_kills`, `deaths`, `last_seen`, `join_date`, `packed_past_clans`, `flags`) ";
- String values = "VALUES ('" + cp.getUniqueId().toString() + "', '" + cp.getName() + "',"
- + (cp.isLeader() ? 1 : 0) + ",'" + Helper.escapeQuotes(cp.getTag()) + "',"
- + (cp.isFriendlyFire() ? 1 : 0) + "," + cp.getNeutralKills() + "," + cp.getRivalKills()
- + "," + cp.getCivilianKills() + "," + cp.getDeaths() + ",'" + cp.getLastSeen() + "',' "
- + cp.getJoinDate() + "','" + Helper.escapeQuotes(cp.getPackedPastClans()) + "','"
- + Helper.escapeQuotes(cp.getFlags()) + "');";
+ String query = "INSERT INTO `" + getPrefixedTable("players") + "` (`uuid`, `name`, `leader`, `tag`, `friendly_fire`, `neutral_kills`, " + "`rival_kills`, `civilian_kills`, `deaths`, `last_seen`, `join_date`, `packed_past_clans`, `flags`) ";
+ String values = "VALUES ('" + cp.getUniqueId().toString() + "', '" + cp.getName() + "'," + (cp.isLeader() ? 1 : 0) + ",'" + Helper.escapeQuotes(cp.getTag()) + "'," + (cp.isFriendlyFire() ? 1 : 0) + "," + cp.getNeutralKills() + "," + cp.getRivalKills() + "," + cp.getCivilianKills() + "," + cp.getDeaths() + ",'" + cp.getLastSeen() + "',' " + cp.getJoinDate() + "','" + Helper.escapeQuotes(cp.getPackedPastClans()) + "','" + Helper.escapeQuotes(cp.getFlags()) + "');";
core.executeUpdate(query + values);
}
@@ -843,12 +1052,12 @@ public void insertClanPlayer(ClanPlayer cp) {
*/
@Deprecated
public void updateClanPlayerAsync(final ClanPlayer cp) {
- new BukkitRunnable() {
- @Override
- public void run() {
+ new BukkitRunnable() {
+ @Override
+ public void run() {
updateClanPlayer(cp);
- }
- }.runTaskAsynchronously(plugin);
+ }
+ }.runTaskAsynchronously(plugin);
}
/**
@@ -856,9 +1065,19 @@ public void run() {
*
*/
public void updateClanPlayer(ClanPlayer cp) {
+ updateClanPlayer(cp, false);
+ }
+
+ /**
+ * Update a clan player to the database
+ *
+ * @param cp the clan player to update
+ * @param forceImmediate if true, bypasses periodic save setting and updates immediately
+ */
+ public void updateClanPlayer(ClanPlayer cp, boolean forceImmediate) {
cp.updateLastSeen();
plugin.getProxyManager().sendUpdate(cp);
- if (plugin.getSettingsManager().is(PERFORMANCE_SAVE_PERIODICALLY)) {
+ if (!forceImmediate && plugin.getSettingsManager().is(PERFORMANCE_SAVE_PERIODICALLY)) {
modifiedClanPlayers.add(cp);
return;
}
@@ -871,9 +1090,7 @@ public void updateClanPlayer(ClanPlayer cp) {
}
private PreparedStatement prepareUpdateClanPlayerStatement(Connection connection) throws SQLException {
- String sql = "UPDATE `" + getPrefixedTable("players") + "` SET locale = ?, resign_times = ?, leader = ?, tag = ?, friendly_fire = ?," +
- " neutral_kills = ?, ally_kills = ?, rival_kills = ?, civilian_kills = ?, deaths = ?, last_seen = ?," +
- " packed_past_clans = ?, trusted = ?, flags = ?, `name` = ? WHERE `uuid` = ?;";
+ String sql = "UPDATE `" + getPrefixedTable("players") + "` SET locale = ?, resign_times = ?, leader = ?, tag = ?, friendly_fire = ?," + " neutral_kills = ?, ally_kills = ?, rival_kills = ?, civilian_kills = ?, deaths = ?, last_seen = ?," + " packed_past_clans = ?, trusted = ?, flags = ?, `name` = ? WHERE `uuid` = ?;";
return connection.prepareStatement(sql);
}
@@ -918,22 +1135,20 @@ public void deleteClanPlayer(ClanPlayer cp) {
@Deprecated
public void insertKill(Player attacker, String attackerTag, Player victim, String victimTag, String type) {
String query = "INSERT INTO `" + getPrefixedTable("kills") + "` ( `attacker_uuid`, `attacker`, `attacker_tag`, `victim_uuid`, `victim`, `victim_tag`, `kill_type`) ";
- String values = "VALUES ( '" + attacker.getUniqueId() + "','" + attacker.getName() + "','" + attackerTag + "','" + victim.getUniqueId() + "','" + victim.getName() + "','" + victimTag + "','" + type + "');";
- core.executeUpdate(query + values);
+ String values = "VALUES ( '" + attacker.getUniqueId() + "','" + attacker.getName() + "','" + attackerTag + "','" + victim.getUniqueId() + "','" + victim.getName() + "','" + victimTag + "','" + type + "');";
+ core.executeUpdate(query + values);
}
/**
* Insert a kill into the database
*
* @param attacker the attacker
- * @param victim the victim
- * @param type the kill type
+ * @param victim the victim
+ * @param type the kill type
*/
public void insertKill(@NotNull ClanPlayer attacker, @NotNull ClanPlayer victim, @NotNull String type, @NotNull LocalDateTime time) {
- String query = "INSERT INTO `sc_kills` ( `attacker_uuid`, `attacker`, `attacker_tag`, `victim_uuid`, " +
- "`victim`, `victim_tag`, `kill_type`, `created_at`) ";
- String values = "VALUES ( '" + attacker.getUniqueId() + "','" + attacker.getName() + "','" + attacker.getTag()
- + "','" + victim.getUniqueId() + "','" + victim.getName() + "','" + victim.getTag() + "','" + type + "','" + time + "');";
+ String query = "INSERT INTO `sc_kills` ( `attacker_uuid`, `attacker`, `attacker_tag`, `victim_uuid`, " + "`victim`, `victim_tag`, `kill_type`, `created_at`) ";
+ String values = "VALUES ( '" + attacker.getUniqueId() + "','" + attacker.getName() + "','" + attacker.getTag() + "','" + victim.getUniqueId() + "','" + victim.getName() + "','" + victim.getTag() + "','" + type + "','" + time + "');";
core.executeUpdate(query + values);
}
@@ -960,7 +1175,6 @@ public void deleteKills(UUID playerUniqueId) {
* Returns a map of victim-{@literal >}count of all kills that specific player did
*
* @param playerName the attacker name
- *
* @return a map of kills per victim
*
*/
@@ -1032,13 +1246,13 @@ public Map getMostKilled() {
* @param callback the callback
*/
public void getMostKilled(DataCallback