Player data refactor before MySQL support

This commit is contained in:
Indyuce 2020-03-01 13:00:45 +01:00
parent 46f24e4677
commit a2640f9ea7
9 changed files with 272 additions and 172 deletions

View File

@ -78,6 +78,8 @@ import net.Indyuce.mmocore.manager.QuestManager;
import net.Indyuce.mmocore.manager.RestrictionManager;
import net.Indyuce.mmocore.manager.SkillManager;
import net.Indyuce.mmocore.manager.WaypointManager;
import net.Indyuce.mmocore.manager.data.PlayerDataManager;
import net.Indyuce.mmocore.manager.data.YAMLPlayerDataManager;
import net.Indyuce.mmocore.manager.profession.AlchemyManager;
import net.Indyuce.mmocore.manager.profession.EnchantManager;
import net.Indyuce.mmocore.manager.profession.FishingManager;
@ -96,30 +98,30 @@ import net.mmogroup.mmolib.version.SpigotPlugin;
public class MMOCore extends JavaPlugin {
public static MMOCore plugin;
public final ClassManager classManager = new ClassManager();
public ConfigManager configManager;
public WaypointManager waypointManager;
public RestrictionManager restrictionManager;
public LootableChestManager chestManager;
public RequestManager requestManager;
public GuildManager guildManager = new GuildManager();
public ConfigItemManager configItems;
public SkillManager skillManager;
public final ClassManager classManager = new ClassManager();
public final DropTableManager dropTableManager = new DropTableManager();
public final CustomBlockManager mineManager = new CustomBlockManager();
public final BoosterManager boosterManager = new BoosterManager();
public LootableChestManager chestManager;
public RequestManager requestManager;
public final AttributeManager attributeManager = new AttributeManager();
public final PartyManager partyManager = new PartyManager();
public GuildManager guildManager = new GuildManager();
public final QuestManager questManager = new QuestManager();
public ConfigItemManager configItems;
public SkillManager skillManager;
public final ProfessionManager professionManager = new ProfessionManager();
// public final SQLManager sqlManager = new SQLManager();
public final EntityManager entities = new EntityManager();
public VaultEconomy economy;
public HologramSupport hologramSupport;
public PlaceholderParser placeholderParser = new DefaultParser();
public final EntityManager entities = new EntityManager();
public InventoryManager inventoryManager;
public RegionHandler regionHandler;
public PlayerActionBar actionBarManager;
public final PlayerDataManager playerDataManager = new YAMLPlayerDataManager();
/*
* professions
@ -160,7 +162,7 @@ public class MMOCore extends JavaPlugin {
/*
* mmocore stats are functions of the stat base value. the function
* applies all the different stat modifiers saved in the stat map using
* applies all the different stat modifiers saved in the stat map. using
* specific stat instances let MMOLib calculate stats with set base
* value
*/
@ -290,7 +292,7 @@ public class MMOCore extends JavaPlugin {
* the player datas can't recognize what profess the player has and
* professes will be lost
*/
Bukkit.getOnlinePlayers().forEach(player -> PlayerData.setup(player));
Bukkit.getOnlinePlayers().forEach(player -> playerDataManager.setup(player));
// commands
try {
@ -338,11 +340,8 @@ public class MMOCore extends JavaPlugin {
int autosave = getConfig().getInt("auto-save.interval") * 20;
new BukkitRunnable() {
public void run() {
for (PlayerData playerData : PlayerData.getAll()) {
ConfigFile config = new ConfigFile(playerData.getUniqueId());
playerData.saveInConfig(config.getConfig());
config.save();
}
for (PlayerData loaded : PlayerData.getAll())
playerDataManager.saveData(loaded);
guildManager.save();
}
@ -351,11 +350,9 @@ public class MMOCore extends JavaPlugin {
}
public void onDisable() {
for (PlayerData playerData : PlayerData.getAll()) {
ConfigFile config = new ConfigFile(playerData.getUniqueId());
playerData.getQuestData().resetBossBar();
playerData.saveInConfig(config.getConfig());
config.save();
for (PlayerData data : PlayerData.getAll()) {
data.getQuestData().resetBossBar();
playerDataManager.saveData(data);
}
guildManager.save();

View File

@ -49,14 +49,14 @@ public class ConfigFile {
public void save() {
try {
config.save(file);
} catch (IOException e2) {
MMOCore.plugin.getLogger().log(Level.SEVERE, "Could not save " + name + ".yml!");
} catch (IOException exception) {
MMOCore.plugin.getLogger().log(Level.SEVERE, "Could not save " + name + ".yml: " + exception.getMessage());
}
}
public void delete() {
if (file.exists())
if (!file.delete())
MMOCore.plugin.getLogger().log(Level.SEVERE, "Could not delete " + name + ".yml!");
MMOCore.plugin.getLogger().log(Level.SEVERE, "Could not delete " + name + ".yml.");
}
}

View File

@ -0,0 +1,18 @@
package net.Indyuce.mmocore.api.data;
import net.Indyuce.mmocore.manager.data.PlayerDataManager;
import net.Indyuce.mmocore.manager.social.GuildManager;
public interface DataProvider {
/*
* used to separate MySQL data storage from YAML data storage. there is one
* dataProvider per storage mecanism (one for YAML, one for MySQL). a
* dataProvider provides corresponding mmoManagers to correctly save and load
* data
*/
PlayerDataManager provideDataManager();
GuildManager provideGuildManager();
}

View File

@ -0,0 +1,18 @@
package net.Indyuce.mmocore.api.data;
import net.Indyuce.mmocore.manager.data.PlayerDataManager;
import net.Indyuce.mmocore.manager.data.YAMLPlayerDataManager;
import net.Indyuce.mmocore.manager.social.GuildManager;
public class YAMLDataProvider implements DataProvider {
@Override
public PlayerDataManager provideDataManager() {
return new YAMLPlayerDataManager();
}
@Override
public GuildManager provideGuildManager() {
return new GuildManager();
}
}

View File

@ -2,6 +2,7 @@ package net.Indyuce.mmocore.api.player;
import java.util.UUID;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.profess.PlayerClass;
public abstract class OfflinePlayerData {
@ -26,6 +27,6 @@ public abstract class OfflinePlayerData {
public abstract long getLastLogin();
public static OfflinePlayerData get(UUID uuid) {
return PlayerData.isLoaded(uuid) ? PlayerData.get(uuid) : new SimpleOfflinePlayerData(uuid);
return MMOCore.plugin.playerDataManager.getOffline(uuid);
}
}

View File

@ -9,9 +9,7 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
@ -19,14 +17,12 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.attribute.Attribute;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.ConfigFile;
import net.Indyuce.mmocore.api.ConfigMessage;
import net.Indyuce.mmocore.api.Waypoint;
import net.Indyuce.mmocore.api.event.PlayerCastSkillEvent;
@ -67,18 +63,19 @@ public class PlayerData extends OfflinePlayerData {
private PlayerClass profess;
private int level, experience, classPoints, skillPoints, attributePoints, attributeReallocationPoints;// skillReallocationPoints,
private double mana, stamina, stellium;
private List<UUID> friends;
private Party party;
private Guild guild;
private final Map<String, SavedClassInformation> classSlots = new HashMap<>();
private final List<SkillInfo> boundSkills = new ArrayList<>();
private final PlayerAttributes attributes = new PlayerAttributes(this);
private final Professions collectSkills = new Professions(this);
private final PlayerQuests questData;
private final Set<String> waypoints = new HashSet<>();
private final PlayerStats playerStats;
private final List<UUID> friends = new ArrayList<>();
private final Set<String> waypoints = new HashSet<>();
private final Map<String, Integer> skills = new HashMap<>();
private final List<SkillInfo> boundSkills = new ArrayList<>();
private final Professions collectSkills = new Professions(this);
private final PlayerSkillData skillData = new PlayerSkillData(this);
private final PlayerAttributes attributes = new PlayerAttributes(this);
private final Map<String, SavedClassInformation> classSlots = new HashMap<>();
private long lastWaypoint, lastLogin, lastFriendRequest, actionBarTimeOut;
@ -90,111 +87,14 @@ public class PlayerData extends OfflinePlayerData {
public boolean nocd;
public CombatRunnable combat;
private static Map<UUID, PlayerData> playerData = new HashMap<>();
private PlayerData(Player player) {
public PlayerData(Player player) {
super(player.getUniqueId());
setPlayer(player);
playerStats = new PlayerStats(this);
questData = new PlayerQuests(this);
}
public PlayerData load(FileConfiguration config) {
this.classPoints = config.getInt("class-points");
this.skillPoints = config.getInt("skill-points");
this.attributePoints = config.getInt("attribute-points");
// this.skillReallocationPoints = config.getInt("skill-realloc-points");
this.attributeReallocationPoints = config.getInt("attribute-realloc-points");
this.level = config.getInt("level");
this.experience = config.getInt("experience");
this.profess = config.contains("class") ? MMOCore.plugin.classManager.get(config.getString("class")) : MMOCore.plugin.classManager.getDefaultClass();
this.mana = getStats().getStat(StatType.MAX_MANA);
this.stamina = getStats().getStat(StatType.MAX_STAMINA);
this.stellium = getStats().getStat(StatType.MAX_STELLIUM);
if (config.contains("guild"))
this.guild = MMOCore.plugin.guildManager.stillInGuild(getUniqueId(), config.getString("guild"));
if (config.contains("attribute"))
attributes.load(config.getConfigurationSection("attribute"));
if (config.contains("profession"))
collectSkills.load(config.getConfigurationSection("profession"));
if (config.contains("quest"))
questData.load(config.getConfigurationSection("quest"));
questData.updateBossBar();
if (config.contains("waypoints"))
waypoints.addAll(config.getStringList("waypoints"));
MMOCore.plugin.waypointManager.getDefault().forEach(waypoint -> waypoints.add(waypoint.getId()));
this.friends = config.contains("friends") ? config.getStringList("friends").stream().map((str) -> UUID.fromString(str)).collect(Collectors.toList()) : new ArrayList<>();
if (config.contains("skill"))
config.getConfigurationSection("skill").getKeys(false).forEach(id -> skills.put(id, config.getInt("skill." + id)));
if (config.contains("bound-skills"))
for (String id : config.getStringList("bound-skills"))
if (MMOCore.plugin.skillManager.has(id))
boundSkills.add(getProfess().getSkill(id));
/*
* load class slots, use try so the player can log in.
*/
if (config.contains("class-info"))
for (String key : config.getConfigurationSection("class-info").getKeys(false))
try {
PlayerClass profess = MMOCore.plugin.classManager.get(key);
Validate.notNull(profess, "Could not find class '" + key + "'");
applyClassInfo(profess, new SavedClassInformation(config.getConfigurationSection("class-info." + key)));
} catch (IllegalArgumentException exception) {
log(Level.SEVERE, "Could not load class info " + key + ": " + exception.getMessage());
}
return this;
}
public void saveInConfig(FileConfiguration config) {
config.set("class-points", classPoints);
config.set("skill-points", skillPoints);
config.set("attribute-points", attributePoints);
// config.set("skill-realloc-points", skillReallocationPoints);
config.set("attribute-realloc-points", attributeReallocationPoints);
config.set("level", getLevel());
config.set("experience", experience);
config.set("class", profess == null ? null : profess.getId());
config.set("waypoints", new ArrayList<>(waypoints));
config.set("friends", toStringList(friends));
config.set("last-login", lastLogin);
config.set("guild", guild != null ? guild.getId() : null);
config.set("skill", null);
skills.entrySet().forEach(entry -> config.set("skill." + entry.getKey(), entry.getValue()));
List<String> boundSkills = new ArrayList<>();
this.boundSkills.forEach(skill -> boundSkills.add(skill.getSkill().getId()));
config.set("bound-skills", boundSkills);
config.set("attribute", null);
config.createSection("attribute");
attributes.save(config.getConfigurationSection("attribute"));
config.set("profession", null);
config.createSection("profession");
collectSkills.save(config.getConfigurationSection("profession"));
config.set("quest", null);
config.createSection("quest");
questData.save(config.getConfigurationSection("quest"));
config.set("class-info", null);
for (String key : classSlots.keySet()) {
SavedClassInformation info = classSlots.get(key);
config.set("class-info." + key + ".level", info.getLevel());
config.set("class-info." + key + ".experience", info.getExperience());
config.set("class-info." + key + ".skill-points", info.getSkillPoints());
config.set("class-info." + key + ".attribute-points", info.getAttributePoints());
config.set("class-info." + key + ".attribute-realloc-points", info.getAttributeReallocationPoints());
info.getSkillKeys().forEach(skill -> config.set("class-info." + key + ".skill." + skill, info.getSkillLevel(skill)));
info.getAttributeKeys().forEach(attribute -> config.set("class-info." + key + ".attribute." + attribute, info.getAttributeLevel(attribute)));
}
}
/*
* update all references after /mmocore reload so there can be garbage
* collection with old plugin objects like class or skill instances.
@ -223,28 +123,14 @@ public class PlayerData extends OfflinePlayerData {
}
public static PlayerData get(UUID uuid) {
return playerData.get(uuid);
}
public static void remove(Player player) {
playerData.remove(player.getUniqueId());
}
public static PlayerData setup(Player player) {
if (!playerData.containsKey(player.getUniqueId()))
playerData.put(player.getUniqueId(), new PlayerData(player).load(new ConfigFile(player).getConfig()));
return get(player).setPlayer(player);
}
public static boolean isLoaded(UUID uuid) {
return playerData.containsKey(uuid);
return MMOCore.plugin.playerDataManager.get(uuid);
}
public static Collection<PlayerData> getAll() {
return playerData.values();
return MMOCore.plugin.playerDataManager.getLoaded();
}
private PlayerData setPlayer(Player player) {
public PlayerData setPlayer(Player player) {
this.player = player;
this.lastLogin = System.currentTimeMillis();
return this;
@ -284,6 +170,10 @@ public class PlayerData extends OfflinePlayerData {
return party;
}
public boolean hasGuild() {
return guild != null;
}
public Guild getGuild() {
return guild;
}
@ -368,8 +258,16 @@ public class PlayerData extends OfflinePlayerData {
return classSlots.containsKey(profess.getId());
}
public Set<String> getSavedClasses() {
return classSlots.keySet();
}
public SavedClassInformation getClassInfo(PlayerClass profess) {
return classSlots.get(profess.getId());
return getClassInfo(profess.getId());
}
public SavedClassInformation getClassInfo(String profess) {
return classSlots.get(profess);
}
public void applyClassInfo(PlayerClass profess, SavedClassInformation info) {
@ -397,8 +295,8 @@ public class PlayerData extends OfflinePlayerData {
}
public void heal(double heal) {
double current = player.getHealth(), newest = Math.max(0, Math.min(player.getHealth() + heal, player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()));
if (current == newest)
double newest = Math.max(0, Math.min(player.getHealth() + heal, player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()));
if (player.getHealth() == newest)
return;
PlayerRegenResourceEvent event = new PlayerRegenResourceEvent(this, PlayerResource.HEALTH, heal);
@ -761,15 +659,6 @@ public class PlayerData extends OfflinePlayerData {
combat = new CombatRunnable(this);
}
private List<String> toStringList(List<UUID> list) {
if (list.isEmpty())
return null;
List<String> stringList = new ArrayList<>();
list.forEach(uuid -> stringList.add(uuid.toString()));
return stringList;
}
public SkillResult cast(Skill skill) {
return cast(getProfess().getSkill(skill));
}

View File

@ -15,6 +15,7 @@ import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.event.PlayerCombatEvent;
import net.Indyuce.mmocore.api.event.PlayerRegenResourceEvent;
import net.Indyuce.mmocore.api.player.PlayerData;
@ -29,7 +30,7 @@ public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void a(PlayerJoinEvent event) {
Player player = event.getPlayer();
PlayerData.setup(player).getStats().getMap().updateAll();
MMOCore.plugin.playerDataManager.setup(player).getStats().getMap().updateAll();
}
/*
@ -50,10 +51,10 @@ public class PlayerListener implements Listener {
/*
* updates the player's combat log data every time he hits an entity, or
* gets hit by an entity or a projectile sent by another entity. updates
* this stuff on HIGH level so other plugins can check if the player just
* this stuff on LOW level so other plugins can check if the player just
* entered combat
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void d(EntityDamageByEntityEvent event) {
if (event.getEntity() instanceof Player && !event.getEntity().hasMetadata("NPC"))
PlayerData.get((Player) event.getEntity()).updateCombat();
@ -61,12 +62,10 @@ public class PlayerListener implements Listener {
if (event.getDamager() instanceof Player && !event.getDamager().hasMetadata("NPC"))
PlayerData.get((Player) event.getDamager()).updateCombat();
if (event.getDamager() instanceof Projectile && ((Projectile) event.getDamager()).getShooter() instanceof Player) {
if (((Player) ((Projectile) event.getDamager()).getShooter()).hasMetadata("NPC"))
return;
if (event.getDamager() instanceof Projectile && ((Projectile) event.getDamager()).getShooter() instanceof Player)
if (!((Player) ((Projectile) event.getDamager()).getShooter()).hasMetadata("NPC"))
PlayerData.get((Player) ((Projectile) event.getDamager()).getShooter()).updateCombat();
}
}
@EventHandler
public void e(PlayerQuitEvent event) {

View File

@ -0,0 +1,56 @@
package net.Indyuce.mmocore.manager.data;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import net.Indyuce.mmocore.api.player.OfflinePlayerData;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.SimpleOfflinePlayerData;
public abstract class PlayerDataManager {
private final Map<UUID, PlayerData> map = new HashMap<>();
public PlayerData get(OfflinePlayer player) {
return get(player.getUniqueId());
}
public PlayerData get(UUID uuid) {
return map.get(uuid);
}
public OfflinePlayerData getOffline(UUID uuid) {
return isLoaded(uuid) ? get(uuid) : new SimpleOfflinePlayerData(uuid);
}
public PlayerData setup(Player player) {
/*
* setup playerData based on loadData method to support both MySQL and
* YAML data storage
*/
if (!map.containsKey(player.getUniqueId())) {
PlayerData generated = new PlayerData(player);
loadData(generated);
map.put(player.getUniqueId(), generated);
}
return get(player).setPlayer(player);
}
public boolean isLoaded(UUID uuid) {
return map.containsKey(uuid);
}
public Collection<PlayerData> getLoaded() {
return map.values();
}
public abstract void loadData(PlayerData data);
public abstract void saveData(PlayerData data);
}

View File

@ -0,0 +1,122 @@
package net.Indyuce.mmocore.manager.data;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.file.FileConfiguration;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.ConfigFile;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.profess.PlayerClass;
import net.Indyuce.mmocore.api.player.profess.SavedClassInformation;
import net.Indyuce.mmocore.api.player.stats.StatType;
public class YAMLPlayerDataManager extends PlayerDataManager {
@Override
public void loadData(PlayerData data) {
FileConfiguration config = new ConfigFile(data.getPlayer()).getConfig();
data.setClassPoints(config.getInt("class-points"));
data.setSkillPoints(config.getInt("skill-points"));
data.setAttributePoints(config.getInt("attribute-points"));
data.setAttributeReallocationPoints(config.getInt("attribute-realloc-points"));
data.setLevel(config.getInt("level"));
data.setExperience(config.getInt("experience"));
if (config.contains("class"))
data.setProfess(MMOCore.plugin.classManager.get(config.getString("class")));
data.setMana(data.getStats().getStat(StatType.MAX_MANA));
data.setStamina(data.getStats().getStat(StatType.MAX_STAMINA));
data.setStellium(data.getStats().getStat(StatType.MAX_STELLIUM));
if (config.contains("guild"))
data.setGuild(MMOCore.plugin.guildManager.stillInGuild(data.getUniqueId(), config.getString("guild")));
if (config.contains("attribute"))
data.getAttributes().load(config.getConfigurationSection("attribute"));
if (config.contains("profession"))
data.getCollectionSkills().load(config.getConfigurationSection("profession"));
if (config.contains("quest"))
data.getQuestData().load(config.getConfigurationSection("quest"));
data.getQuestData().updateBossBar();
if (config.contains("waypoints"))
data.getWaypoints().addAll(config.getStringList("waypoints"));
MMOCore.plugin.waypointManager.getDefault().forEach(waypoint -> data.getWaypoints().add(waypoint.getId()));
if (config.contains("friends"))
config.getStringList("friends").forEach(str -> data.getFriends().add(UUID.fromString(str)));
if (config.contains("skill"))
config.getConfigurationSection("skill").getKeys(false).forEach(id -> data.setSkillLevel(id, config.getInt("skill." + id)));
if (config.contains("bound-skills"))
for (String id : config.getStringList("bound-skills"))
if (MMOCore.plugin.skillManager.has(id))
data.getBoundSkills().add(data.getProfess().getSkill(id));
/*
* load class slots, use try so the player can log in.
*/
if (config.contains("class-info"))
for (String key : config.getConfigurationSection("class-info").getKeys(false))
try {
PlayerClass profess = MMOCore.plugin.classManager.get(key);
Validate.notNull(profess, "Could not find class '" + key + "'");
data.applyClassInfo(profess, new SavedClassInformation(config.getConfigurationSection("class-info." + key)));
} catch (IllegalArgumentException exception) {
MMOCore.log(Level.WARNING, "Could not load class info '" + key + "': " + exception.getMessage());
}
}
@Override
public void saveData(PlayerData data) {
ConfigFile file = new ConfigFile(data.getPlayer());
FileConfiguration config = file.getConfig();
config.set("class-points", data.getClassPoints());
config.set("skill-points", data.getSkillPoints());
config.set("attribute-points", data.getAttributePoints());
// config.set("skill-realloc-points", skillReallocationPoints);
config.set("attribute-realloc-points", data.getAttributeReallocationPoints());
config.set("level", data.getLevel());
config.set("experience", data.getExperience());
config.set("class", data.getProfess().getId());
config.set("waypoints", new ArrayList<>(data.getWaypoints()));
config.set("friends", data.getFriends().stream().map(uuid -> uuid.toString()).collect(Collectors.toList()));
config.set("last-login", data.getLastLogin());
config.set("guild", data.hasGuild() ? data.getGuild().getId() : null);
config.set("skill", null);
data.mapSkillLevels().entrySet().forEach(entry -> config.set("skill." + entry.getKey(), entry.getValue()));
List<String> boundSkills = new ArrayList<>();
data.getBoundSkills().forEach(skill -> boundSkills.add(skill.getSkill().getId()));
config.set("bound-skills", boundSkills);
config.set("attribute", null);
config.createSection("attribute");
data.getAttributes().save(config.getConfigurationSection("attribute"));
config.set("profession", null);
config.createSection("profession");
data.getCollectionSkills().save(config.getConfigurationSection("profession"));
config.set("quest", null);
config.createSection("quest");
data.getQuestData().save(config.getConfigurationSection("quest"));
config.set("class-info", null);
for (String key : data.getSavedClasses()) {
SavedClassInformation info = data.getClassInfo(key);
config.set("class-info." + key + ".level", info.getLevel());
config.set("class-info." + key + ".experience", info.getExperience());
config.set("class-info." + key + ".skill-points", info.getSkillPoints());
config.set("class-info." + key + ".attribute-points", info.getAttributePoints());
config.set("class-info." + key + ".attribute-realloc-points", info.getAttributeReallocationPoints());
info.getSkillKeys().forEach(skill -> config.set("class-info." + key + ".skill." + skill, info.getSkillLevel(skill)));
info.getAttributeKeys().forEach(attribute -> config.set("class-info." + key + ".attribute." + attribute, info.getAttributeLevel(attribute)));
}
file.save();
}
}