diff --git a/bukkit/src/main/java/dev/aurelium/auraskills/bukkit/loot/entity/EntityProperties.java b/bukkit/src/main/java/dev/aurelium/auraskills/bukkit/loot/entity/EntityProperties.java index 0b232aad5..43e499cc7 100644 --- a/bukkit/src/main/java/dev/aurelium/auraskills/bukkit/loot/entity/EntityProperties.java +++ b/bukkit/src/main/java/dev/aurelium/auraskills/bukkit/loot/entity/EntityProperties.java @@ -10,14 +10,16 @@ import java.util.Locale; import java.util.Map; -public record EntityProperties(String entityId, - String name, - Integer level, - Double health, - Double damage, - Float horizontalVelocity, - Float verticalVelocity, - Map equipment) { +public record EntityProperties( + String entityId, + String name, + Integer level, + Double health, + Double damage, + Float horizontalVelocity, + Float verticalVelocity, + Map equipment +) { public static EntityProperties fromConfig(ConfigurationNode config, AuraSkills plugin) { String[] id = config.node("entity").getString("").split(":"); diff --git a/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/KeyValueRow.java b/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/KeyValueRow.java new file mode 100644 index 000000000..8564110ef --- /dev/null +++ b/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/KeyValueRow.java @@ -0,0 +1,12 @@ +package dev.aurelium.auraskills.common.storage.sql; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record KeyValueRow( + int dataId, + @Nullable String categoryId, + @NotNull String keyName, + @NotNull String value +) { +} diff --git a/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/SqlStorageProvider.java b/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/SqlStorageProvider.java index 9e325db68..1aa80309a 100644 --- a/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/SqlStorageProvider.java +++ b/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/SqlStorageProvider.java @@ -8,7 +8,6 @@ import dev.aurelium.auraskills.api.stat.StatModifier; import dev.aurelium.auraskills.api.trait.Trait; import dev.aurelium.auraskills.api.trait.TraitModifier; -import dev.aurelium.auraskills.api.util.NumberUtil; import dev.aurelium.auraskills.common.AuraSkillsPlugin; import dev.aurelium.auraskills.common.ability.AbilityData; import dev.aurelium.auraskills.common.config.Option; @@ -28,18 +27,20 @@ public class SqlStorageProvider extends StorageProvider { private final ConnectionPool pool; + private final SqlUserLoader userLoader; private final String tablePrefix = "auraskills_"; - public final int STAT_MODIFIER_ID = 1; - public final int TRAIT_MODIFIER_ID = 2; - public final int ABILITY_DATA_ID = 3; - public final int UNCLAIMED_ITEMS_ID = 4; - public final int ACTION_BAR_ID = 5; - public final int JOBS_ID = 6; + public static final int STAT_MODIFIER_ID = 1; + public static final int TRAIT_MODIFIER_ID = 2; + public static final int ABILITY_DATA_ID = 3; + public static final int UNCLAIMED_ITEMS_ID = 4; + public static final int ACTION_BAR_ID = 5; + public static final int JOBS_ID = 6; public SqlStorageProvider(AuraSkillsPlugin plugin, ConnectionPool pool) { super(plugin); this.pool = pool; + this.userLoader = new SqlUserLoader(plugin); attemptTableCreation(); } @@ -54,50 +55,11 @@ public void attemptTableCreation() { @Override protected User loadRaw(UUID uuid) throws Exception { - String loadQuery = "SELECT * FROM " + tablePrefix + "users WHERE player_uuid=?;"; try (Connection connection = pool.getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(loadQuery)) { - statement.setString(1, uuid.toString()); - try (ResultSet resultSet = statement.executeQuery()) { - User user = userManager.createNewUser(uuid); - if (!resultSet.next()) { // If the player doesn't exist in the database - return user; - } - int userId = resultSet.getInt("user_id"); - // Load skill levels and xp - SkillLevelMaps skillLevelMaps = loadSkillLevels(connection, uuid, userId); - // Apply skill levels and xp from maps - for (Map.Entry entry : skillLevelMaps.levels().entrySet()) { - user.setSkillLevel(entry.getKey(), entry.getValue()); - } - for (Map.Entry entry : skillLevelMaps.xp().entrySet()) { - user.setSkillXp(entry.getKey(), entry.getValue()); - } - // Load locale - String localeString = resultSet.getString("locale"); - if (localeString != null) { - user.setLocale(new Locale(localeString)); - } - // Load mana - double mana = resultSet.getDouble("mana"); - user.setMana(mana); - // Load stat modifiers - loadStatModifiers(connection, uuid, userId).values().forEach(m -> user.addStatModifier(m, false)); - // Load trait modifiers - loadTraitModifiers(connection, uuid, userId).values().forEach(m -> user.addTraitModifier(m, false)); - // Load ability data - loadAbilityData(connection, user, userId); - // Load job data - loadJobs(connection, user, userId); - // Load unclaimed items - user.setUnclaimedItems(loadUnclaimedItems(connection, userId)); - user.clearInvalidItems(); - // Load action bar - loadActionBar(connection, user, userId); - - return user; - } - } + User user = userManager.createNewUser(uuid); + userLoader.loadUser(uuid, user, connection); + + return user; } } @@ -179,124 +141,6 @@ private Map loadTraitModifiers(Connection connection, UUI return modifiers; } - private void loadAbilityData(Connection connection, User user, int userId) throws SQLException { - String query = "SELECT category_id, key_name, value FROM " + tablePrefix + "key_values WHERE user_id=? AND data_id=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, ABILITY_DATA_ID); - try (ResultSet resultSet = statement.executeQuery()) { - while (resultSet.next()) { - String categoryId = resultSet.getString("category_id"); - AbstractAbility ability = plugin.getAbilityManager().getAbstractAbility(NamespacedId.fromString(categoryId)); - if (ability == null) { - plugin.logger().warn("Failed to load ability data for player " + user.getUuid() + " because " + categoryId + " is not a registered ability"); - continue; - } - String keyName = resultSet.getString("key_name"); - String value = resultSet.getString("value"); - - Object parsed = castValue(value); - - if (keyName.equals("cooldown") && ability instanceof ManaAbility manaAbility) { - user.getManaAbilityData(manaAbility).setCooldown(NumberUtil.toInt(value)); - } else { - user.getAbilityData(ability).setData(keyName, parsed); - } - } - } - } - } - - private void loadJobs(Connection connection, User user, int userId) throws SQLException { - String query = "SELECT key_name, value FROM " + tablePrefix + "key_values WHERE user_id=? AND data_id=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, JOBS_ID); - try (ResultSet resultSet = statement.executeQuery()) { - while (resultSet.next()) { - String keyName = resultSet.getString("key_name"); - String value = resultSet.getString("value"); - - if (!keyName.equals("jobs")) { - continue; - } - - user.clearAllJobs(); - - String[] splitValue = value.split(","); - for (String skillName : splitValue) { - if (skillName.isEmpty()) continue; - - NamespacedId id = NamespacedId.fromString(skillName); - Skill skill = plugin.getSkillRegistry().getOrNull(id); - if (skill == null) continue; - - if (!user.canSelectJob(skill)) continue; - - user.addJob(skill); - } - // Only load one jobs row - break; - } - } - } - } - - @NotNull - private Object castValue(String value) { - Object parsed = value; - if (value.equals("true")) { - parsed = true; - } else if (value.equals("false")) { - parsed = false; - } else { - try { - parsed = Integer.parseInt(value); - } catch (NumberFormatException e) { - try { - parsed = Double.parseDouble(value); - } catch (NumberFormatException ignored) {} - } - } - return parsed; - } - - private List loadUnclaimedItems(Connection connection, int userId) throws SQLException { - List unclaimedItems = new ArrayList<>(); - String query = "SELECT key_name, value FROM " + tablePrefix + "key_values WHERE user_id=? AND data_id=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, UNCLAIMED_ITEMS_ID); - try (ResultSet resultSet = statement.executeQuery()) { - while (resultSet.next()) { - String keyName = resultSet.getString("key_name"); - String value = resultSet.getString("value"); - unclaimedItems.add(new KeyIntPair(keyName, NumberUtil.toInt(value, 1))); - } - } - } - return unclaimedItems; - } - - private void loadActionBar(Connection connection, User user, int userId) throws SQLException { - String query = "SELECT key_name, value FROM " + tablePrefix + "key_values WHERE user_id=? AND data_id=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, ACTION_BAR_ID); - try (ResultSet resultSet = statement.executeQuery()) { - while (resultSet.next()) { - String keyName = resultSet.getString("key_name"); - String value = resultSet.getString("value"); - try { - ActionBarType type = ActionBarType.valueOf(keyName.toUpperCase(Locale.ROOT)); - boolean enabled = !value.equals("false"); - user.setActionBarSetting(type, enabled); - } catch (IllegalArgumentException ignored) { } - } - } - } - } - @Override public @NotNull UserState loadState(UUID uuid) throws Exception { String query = "SELECT * FROM " + tablePrefix + "users WHERE player_uuid=?;"; @@ -352,10 +196,11 @@ public void applyState(UserState state) throws Exception { statement.executeUpdate(); } } - // Save stat modifiers - saveStatModifiers(connection, userId, state.statModifiers()); - // Save trait modifiers - saveTraitModifiers(connection, userId, state.traitModifiers()); + List rows = new ArrayList<>(); + rows.addAll(getStatModifierRows(state.statModifiers())); + rows.addAll(getTraitModifierRows(state.traitModifiers())); + + saveKeyValueRows(connection, userId, rows); } } @@ -382,7 +227,7 @@ public void save(@NotNull User user) throws Exception { if (!plugin.configBoolean(Option.SAVE_BLANK_PROFILES) && user.isBlankProfile()) { try (Connection connection = pool.getConnection()) { deleteUser(connection, user); - connection.setAutoCommit(false); + connection.setAutoCommit(true); } catch (SQLException e) { plugin.logger().severe("Error deleting blank profile of user with UUID " + user.getUuid()); throw e; @@ -438,12 +283,37 @@ private void saveKeyValuesTable(Connection connection, User user) throws SQLExce // Delete existing key values deleteKeyValues(connection, userId); // Save key values - saveStatModifiers(connection, userId, user.getStatModifiers()); - saveTraitModifiers(connection, userId, user.getTraitModifiers()); - saveAbilityData(connection, userId, user.getAbilityDataMap(), user.getManaAbilityDataMap()); - saveUnclaimedItems(connection, userId, user.getUnclaimedItems()); - saveActionBar(connection, userId, user); - saveJobs(connection, userId, user.getJobs()); + List rows = new ArrayList<>(); + rows.addAll(getStatModifierRows(user.getStatModifiers())); + rows.addAll(getTraitModifierRows(user.getTraitModifiers())); + rows.addAll(getAbilityDataRows(user.getAbilityDataMap(), user.getManaAbilityDataMap())); + rows.addAll(getUnclaimedItemsRow(user.getUnclaimedItems())); + rows.addAll(getActionBarRow(user)); + rows.addAll(getJobsRow(user.getJobs())); + // Insert all key values in a batch + saveKeyValueRows(connection, userId, rows); + } + + private void saveKeyValueRows(Connection connection, int userId, List rows) throws SQLException { + connection.setAutoCommit(false); + final String query = "INSERT INTO " + tablePrefix + "key_values (user_id, data_id, category_id, key_name, value) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value=?;"; + try (PreparedStatement ps = connection.prepareStatement(query)) { + for (KeyValueRow row : rows) { + ps.setInt(1, userId); + ps.setInt(2, row.dataId()); + ps.setString(3, row.categoryId()); + ps.setString(4, row.keyName()); + ps.setString(5, row.value()); + ps.setString(6, row.value()); + ps.addBatch(); + } + ps.executeBatch(); + connection.commit(); + } catch (SQLException e) { + connection.rollback(); + } finally { + connection.setAutoCommit(true); + } } private void deleteUser(Connection connection, User user) throws SQLException { @@ -494,94 +364,69 @@ private void deleteKeyValues(Connection connection, int userId) throws SQLExcept } } - private void saveStatModifiers(Connection connection, int userId, Map modifiers) throws SQLException { + private List getStatModifierRows(Map modifiers) { + List rows = new ArrayList<>(); if (modifiers.isEmpty()) { - return; + return rows; } - String query = "INSERT INTO " + tablePrefix + "key_values (user_id, data_id, category_id, key_name, value) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, STAT_MODIFIER_ID); - for (StatModifier modifier : modifiers.values()) { - String categoryId = modifier.stat().getId().toString(); - statement.setString(3, categoryId); - statement.setString(4, modifier.name()); - statement.setString(5, String.valueOf(modifier.value())); - statement.setString(6, String.valueOf(modifier.value())); - statement.executeUpdate(); - } + for (StatModifier modifier : modifiers.values()) { + String categoryId = modifier.stat().getId().toString(); + var row = new KeyValueRow(STAT_MODIFIER_ID, categoryId, modifier.name(), String.valueOf(modifier.value())); + rows.add(row); } + return rows; } - private void saveTraitModifiers(Connection connection, int userId, Map modifiers) throws SQLException { + private List getTraitModifierRows(Map modifiers) { + List rows = new ArrayList<>(); if (modifiers.isEmpty()) { - return; + return rows; } - String query = "INSERT INTO " + tablePrefix + "key_values (user_id, data_id, category_id, key_name, value) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, TRAIT_MODIFIER_ID); - for (TraitModifier modifier : modifiers.values()) { - String categoryId = modifier.trait().getId().toString(); - statement.setString(3, categoryId); - statement.setString(4, modifier.name()); - statement.setString(5, String.valueOf(modifier.value())); - statement.setString(6, String.valueOf(modifier.value())); - statement.executeUpdate(); - } + + for (TraitModifier modifier : modifiers.values()) { + String categoryId = modifier.trait().getId().toString(); + var row = new KeyValueRow(TRAIT_MODIFIER_ID, categoryId, modifier.name(), String.valueOf(modifier.value())); + rows.add(row); } + return rows; } - private void saveAbilityData(Connection connection, int userId, Map abilityDataMap, Map manaAbilityDataMap) throws SQLException { + private List getAbilityDataRows(Map abilityDataMap, Map manaAbilityDataMap) { + List rows = new ArrayList<>(); if (abilityDataMap.isEmpty()) { - return; + return rows; } - String query = "INSERT INTO " + tablePrefix + "key_values (user_id, data_id, category_id, key_name, value) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, ABILITY_DATA_ID); - for (AbilityData abilityData : abilityDataMap.values()) { - String categoryId = abilityData.getAbility().getId().toString(); - statement.setString(3, categoryId); - for (Map.Entry dataEntry : abilityData.getDataMap().entrySet()) { - statement.setString(4, dataEntry.getKey()); - statement.setString(5, String.valueOf(dataEntry.getValue())); - statement.setString(6, String.valueOf(dataEntry.getValue())); - statement.executeUpdate(); - } - } - for (ManaAbilityData data : manaAbilityDataMap.values()) { - if (data.getCooldown() <= 0) continue; - - String categoryId = data.getManaAbility().getId().toString(); - statement.setString(3, categoryId); - statement.setString(4, "cooldown"); - statement.setString(5, String.valueOf(data.getCooldown())); - statement.setString(6, String.valueOf(data.getCooldown())); - statement.executeUpdate(); + for (AbilityData abilityData : abilityDataMap.values()) { + String categoryId = abilityData.getAbility().getId().toString(); + for (Map.Entry dataEntry : abilityData.getDataMap().entrySet()) { + var row = new KeyValueRow(ABILITY_DATA_ID, categoryId, dataEntry.getKey(), String.valueOf(dataEntry.getValue())); + rows.add(row); } } + for (ManaAbilityData data : manaAbilityDataMap.values()) { + if (data.getCooldown() <= 0) continue; + + String categoryId = data.getManaAbility().getId().toString(); + var row = new KeyValueRow(ABILITY_DATA_ID, categoryId, "cooldown", String.valueOf(data.getCooldown())); + rows.add(row); + } + return rows; } - private void saveUnclaimedItems(Connection connection, int userId, List unclaimedItems) throws SQLException { + private List getUnclaimedItemsRow(List unclaimedItems) { + List rows = new ArrayList<>(); if (unclaimedItems.isEmpty()) { - return; + return rows; } - String query = "INSERT INTO " + tablePrefix + "key_values (user_id, data_id, category_id, key_name, value) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, UNCLAIMED_ITEMS_ID); - for (KeyIntPair unclaimedItem : unclaimedItems) { - statement.setNull(3, Types.NULL); - statement.setString(4, unclaimedItem.getKey()); - statement.setString(5, String.valueOf(unclaimedItem.getValue())); - statement.setString(6, String.valueOf(unclaimedItem.getValue())); - statement.executeUpdate(); - } + for (KeyIntPair unclaimedItem : unclaimedItems) { + var row = new KeyValueRow(UNCLAIMED_ITEMS_ID, null, unclaimedItem.getKey(), String.valueOf(unclaimedItem.getValue())); + rows.add(row); } + return rows; } - private void saveActionBar(Connection connection, int userId, User user) throws SQLException { + private List getActionBarRow(User user) { + List rows = new ArrayList<>(); boolean shouldSave = false; // Only save if one of the action bars is disabled for (ActionBarType type : ActionBarType.values()) { @@ -590,36 +435,30 @@ private void saveActionBar(Connection connection, int userId, User user) throws } } if (!shouldSave) { - return; - } - String query = "INSERT INTO " + tablePrefix + "key_values (user_id, data_id, category_id, key_name, value) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value=?;"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, ACTION_BAR_ID); - statement.setNull(3, Types.NULL); - ActionBarType type = ActionBarType.IDLE; - statement.setString(4, type.toString().toLowerCase(Locale.ROOT)); - String value = String.valueOf(user.isActionBarEnabled(type)); - statement.setString(5, value); - statement.setString(6, value); - statement.executeUpdate(); + return rows; } - } - private void saveJobs(Connection connection, int userId, Set jobs) throws SQLException { - if (jobs.isEmpty()) return; + ActionBarType type = ActionBarType.IDLE; + String keyName = type.toString().toLowerCase(Locale.ROOT); + String value = String.valueOf(user.isActionBarEnabled(type)); - String query = "INSERT INTO " + tablePrefix + "key_values (user_id, data_id, category_id, key_name, value) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value=?"; - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.setInt(1, userId); - statement.setInt(2, JOBS_ID); - statement.setNull(3, Types.NULL); - statement.setString(4, "jobs"); - String jobCommaList = String.join(",", jobs.stream().map(s -> s.getId().toString()).toList()); - statement.setString(5, jobCommaList); - statement.setString(6, jobCommaList); - statement.executeUpdate(); + var row = new KeyValueRow(ACTION_BAR_ID, null, keyName, value); + rows.add(row); + + return rows; + } + + private List getJobsRow(Set jobs) { + List rows = new ArrayList<>(); + if (jobs.isEmpty()) { + return rows; } + + String jobCommaList = String.join(",", jobs.stream().map(s -> s.getId().toString()).toList()); + var row = new KeyValueRow(JOBS_ID, null, "jobs", jobCommaList); + rows.add(row); + + return rows; } @Override diff --git a/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/SqlUserLoader.java b/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/SqlUserLoader.java new file mode 100644 index 000000000..4dd106839 --- /dev/null +++ b/common/src/main/java/dev/aurelium/auraskills/common/storage/sql/SqlUserLoader.java @@ -0,0 +1,228 @@ +package dev.aurelium.auraskills.common.storage.sql; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import dev.aurelium.auraskills.api.ability.AbstractAbility; +import dev.aurelium.auraskills.api.mana.ManaAbility; +import dev.aurelium.auraskills.api.registry.NamespacedId; +import dev.aurelium.auraskills.api.skill.Skill; +import dev.aurelium.auraskills.api.stat.Stat; +import dev.aurelium.auraskills.api.stat.StatModifier; +import dev.aurelium.auraskills.api.trait.Trait; +import dev.aurelium.auraskills.api.trait.TraitModifier; +import dev.aurelium.auraskills.api.util.NumberUtil; +import dev.aurelium.auraskills.common.AuraSkillsPlugin; +import dev.aurelium.auraskills.common.ui.ActionBarType; +import dev.aurelium.auraskills.common.user.User; +import dev.aurelium.auraskills.common.util.data.KeyIntPair; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Locale; +import java.util.UUID; + +import static dev.aurelium.auraskills.common.storage.sql.SqlStorageProvider.*; + +public class SqlUserLoader { + + private final AuraSkillsPlugin plugin; + private static final String LOAD_QUERY = """ + SELECT u.*, + ( + SELECT JSON_ARRAYAGG(JSON_OBJECT( + 'name', s.skill_name, + 'level', s.skill_level, + 'xp', s.skill_xp + )) + FROM auraskills_skill_levels s + WHERE s.user_id = u.user_id + ) AS skill_levels, + ( + SELECT JSON_ARRAYAGG(JSON_OBJECT( + 'data_id', k.data_id, + 'category_id', k.category_id, + 'key_name', k.key_name, + 'value', k.value + )) + FROM auraskills_key_values k + WHERE k.user_id = u.user_id + ) AS key_values + FROM + auraskills_users u + WHERE + u.player_uuid = ?; + """; + + public SqlUserLoader(AuraSkillsPlugin plugin) { + this.plugin = plugin; + } + + public void loadUser(UUID uuid, User user, Connection connection) { + try (PreparedStatement statement = connection.prepareStatement(LOAD_QUERY)) { + statement.setString(1, uuid.toString()); + + try (ResultSet rs = statement.executeQuery()) { + if (!rs.next()) { // If the player doesn't exist in the database + return; + } + // Parses and sets query results to user + processResultSet(rs, user); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private void processResultSet(ResultSet rs, User user) throws SQLException { + // Load locale + String localeString = rs.getString("locale"); + if (localeString != null) { + user.setLocale(new Locale(localeString)); + } + // Load mana + double mana = rs.getDouble("mana"); + user.setMana(mana); + + // Load skill levels + JsonArray skillLevels = getJsonArray("skill_levels", rs); + for (JsonElement skillLevelElement : skillLevels) { + JsonObject skillLevelObj = skillLevelElement.getAsJsonObject(); + + String name = skillLevelObj.get("name").getAsString(); + int level = skillLevelObj.get("level").getAsInt(); + double xp = skillLevelObj.get("xp").getAsDouble(); + + Skill skill = plugin.getSkillRegistry().getOrNull(NamespacedId.fromString(name)); + if (skill == null) continue; + + user.setSkillLevel(skill, level); + user.setSkillXp(skill, xp); + } + + // Load key values + JsonArray keyValues = getJsonArray("key_values", rs); + for (JsonElement keyValueElement : keyValues) { + JsonObject keyValueObj = keyValueElement.getAsJsonObject(); + + int dataId = keyValueObj.get("data_id").getAsInt(); + String categoryId = keyValueObj.get("category_id").getAsString(); + String keyName = keyValueObj.get("key_name").getAsString(); + String value = keyValueObj.get("value").getAsString(); + + switch (dataId) { + case STAT_MODIFIER_ID -> applyStatModifier(user, categoryId, keyName, value); + case TRAIT_MODIFIER_ID -> applyTraitModifier(user, categoryId, keyName, value); + case ABILITY_DATA_ID -> applyAbilityData(user, categoryId, keyName, value); + case UNCLAIMED_ITEMS_ID -> applyUnclaimedItem(user, keyName, value); + case ACTION_BAR_ID -> applyActionBar(user, keyName, value); + case JOBS_ID -> applyJobs(user, keyName, value); + } + } + + // Cleanup + user.clearInvalidItems(); + } + + private void applyStatModifier(User user, String categoryId, String keyName, String valueStr) { + try { + double valueDouble = Double.parseDouble(valueStr); + + Stat stat = plugin.getStatRegistry().getOrNull(NamespacedId.fromString(categoryId)); + if (stat == null) return; + + StatModifier modifier = new StatModifier(keyName, stat, valueDouble); + user.addStatModifier(modifier, false); + } catch (NumberFormatException ignored) {} + } + + private void applyTraitModifier(User user, String categoryId, String keyName, String valueStr) { + try { + double valueDouble = Double.parseDouble(valueStr); + + Trait trait = plugin.getTraitRegistry().getOrNull(NamespacedId.fromString(categoryId)); + if (trait == null) return; + + TraitModifier modifier = new TraitModifier(keyName, trait, valueDouble); + user.addTraitModifier(modifier, false); + } catch (NumberFormatException ignored) {} + } + + private void applyAbilityData(User user, String categoryId, String keyName, String valueStr) { + AbstractAbility ability = plugin.getAbilityManager().getAbstractAbility(NamespacedId.fromString(categoryId)); + if (ability == null) return; + + Object parsed = castValue(valueStr); + + if (keyName.equals("cooldown") && ability instanceof ManaAbility manaAbility) { + // Handle the special case for mana ability cooldown + user.getManaAbilityData(manaAbility).setCooldown(NumberUtil.toInt(valueStr)); + } else { + user.getAbilityData(ability).setData(keyName, parsed); + } + } + + private void applyUnclaimedItem(User user, String keyName, String valueStr) { + var pair = new KeyIntPair(keyName, NumberUtil.toInt(valueStr, 1)); + + user.getUnclaimedItems().add(pair); + } + + private void applyActionBar(User user, String keyName, String valueStr) { + try { + ActionBarType type = ActionBarType.valueOf(keyName.toUpperCase(Locale.ROOT)); + boolean enabled = !valueStr.equals("false"); + user.setActionBarSetting(type, enabled); + } catch (IllegalArgumentException ignored) { } + } + + private void applyJobs(User user, String keyName, String valueStr) { + if (!keyName.equals("jobs")) { + return; + } + + user.clearAllJobs(); + + String[] splitValue = valueStr.split(","); + for (String skillName : splitValue) { + if (skillName.isEmpty()) continue; + + NamespacedId id = NamespacedId.fromString(skillName); + Skill skill = plugin.getSkillRegistry().getOrNull(id); + if (skill == null) continue; + + if (!user.canSelectJob(skill)) continue; + + user.addJob(skill); + } + } + + private JsonArray getJsonArray(String name, ResultSet rs) throws SQLException { + String raw = rs.getString(name); + return JsonParser.parseString(raw).getAsJsonArray(); + } + + @NotNull + private Object castValue(String value) { + Object parsed = value; + if (value.equals("true")) { + parsed = true; + } else if (value.equals("false")) { + parsed = false; + } else { + try { + parsed = Integer.parseInt(value); + } catch (NumberFormatException e) { + try { + parsed = Double.parseDouble(value); + } catch (NumberFormatException ignored) {} + } + } + return parsed; + } + +}