diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java index c47dd73b..aa3bb34b 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java @@ -147,7 +147,6 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD public void reload() { try { profess = profess == null ? null : MMOCore.plugin.classManager.get(profess.getId()); - getStats().updateStats(); } catch (NullPointerException exception) { MMOCore.log(Level.SEVERE, "[Userdata] Could not find class " + getProfess().getId() + " while refreshing player data."); } @@ -172,20 +171,69 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD if (!nodeLevels.containsKey(node)) nodeLevels.put(node, 0); setupSkillTree(); - setupRemovableTrigger(); + updateTemporaryTriggers(); + getStats().updateStats(); } + /** + * This script is called when the player data has been successfully + * loaded from the SQL/local text database. + */ + @Override + public void markAsSynchronized() { + + /* + * If the player is not dead and the health is 0, this means that the data was + * missing from the database and it gives full health to the player. If the + * player is dead however, it must not account for that subtle edge case. + */ + if (isOnline() && !getPlayer().isDead()) + getPlayer().setHealth(MMOCoreUtils.fixResource(getHealth(), getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue())); + + setupSkillTree(); + updateTemporaryTriggers(); + getStats().updateStats(true); + + // Finally mark synchronized + super.markAsSynchronized(); + } + + @Deprecated public void setupRemovableTrigger() { - //We remove all the stats and buffs associated to triggers. + updateTemporaryTriggers(); + } + + /** + * Some triggers are marked with the Removable interface as + * they are non-permanent triggers and they need to be updated + * everytime their MMOPlayerData gets flushed from ML cache. + *

+ * This method should go through ALL {@link ExperienceTable} + * that the player has spent points into and register all + * non-permanent triggers. + *

+ * For ease of implementation, these non-permanent triggers are + * refreshed everytime the player joins the server ie on every + * player data fetch. + * + * @see {@link net.Indyuce.mmocore.api.quest.trigger.api.Removable} + */ + public void updateTemporaryTriggers() { + + // Remove all stats and buffs associated to triggers getMMOPlayerData().getStatMap().getInstances().forEach(statInstance -> statInstance.removeIf(Trigger.STAT_MODIFIER_KEY::equals)); getMMOPlayerData().getSkillModifierMap().getInstances().forEach(skillModifierInstance -> skillModifierInstance.removeIf(Trigger.STAT_MODIFIER_KEY::equals)); + // Experience tables from main class if (profess.hasExperienceTable()) profess.getExperienceTable().claimRemovableTrigger(this, profess); + + // Experience tables from professions for (Profession profession : MMOCore.plugin.professionManager.getAll()) if (profession.hasExperienceTable()) profession.getExperienceTable().claimRemovableTrigger(this, profession); - // Stat triggers setup + + // Experience tables from skill tree nodes for (SkillTree skillTree : MMOCore.plugin.skillTreeManager.getAll()) for (SkillTreeNode node : skillTree.getNodes()) node.getExperienceTable().claimRemovableTrigger(this, node); @@ -358,7 +406,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD /** * @return If the item is unlocked by the player - * This is used for skills that can be locked & unlocked. + * This is used for skills that can be locked & unlocked. */ public boolean hasUnlocked(Unlockable unlockable) { return unlockable.isUnlockedByDefault() || unlockedItems.contains(unlockable.getUnlockNamespacedKey()); @@ -506,10 +554,10 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD return sum; } - @Deprecated + @NotNull public List getUnlockedSkills() { return getProfess().getSkills().stream() - .filter((classSkill) -> hasUnlocked(classSkill)) + .filter(skill -> hasUnlocked(skill) && hasUnlockedLevel(skill)) .collect(Collectors.toList()); } @@ -567,9 +615,8 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD public void setLevel(int level) { this.level = Math.max(1, level); - if (isOnline()) { - getStats().updateStats(); + if (isSynchronized()) getStats().updateStats(); refreshVanillaExp(); } } @@ -1018,7 +1065,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD /** * @return If the PlayerEnterCastingModeEvent successfully put the player - * into casting mode, otherwise if the event is cancelled, returns false. + * into casting mode, otherwise if the event is cancelled, returns false. */ public boolean setSkillCasting() { Validate.isTrue(!isCasting(), "Player already in casting mode"); @@ -1037,7 +1084,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD /** * @return If player successfully left skill casting i.e the Bukkit - * event has not been cancelled + * event has not been cancelled */ public boolean leaveSkillCasting() { return leaveSkillCasting(false); @@ -1046,7 +1093,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD /** * @param skipEvent Skip firing the exit event * @return If player successfully left skill casting i.e the Bukkit - * event has not been cancelled + * event has not been cancelled */ public boolean leaveSkillCasting(boolean skipEvent) { Validate.isTrue(isCasting(), "Player not in casting mode"); @@ -1166,10 +1213,10 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD // Clear bound skills boundSkills.forEach((slot, info) -> info.close()); boundSkills.clear(); - setupRemovableTrigger(); + updateTemporaryTriggers(); // Update stats - if (isOnline()) getStats().updateStats(); + if (isOnline() && isSynchronized()) getStats().updateStats(); } public boolean hasSkillBound(int slot) { @@ -1225,7 +1272,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD * checks if they could potentially upgrade to one of these * * @return If the player can change its current class to - * a subclass + * a subclass */ @Deprecated public boolean canChooseSubclass() { diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/SavedClassInformation.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/SavedClassInformation.java index 47e2cfe6..96cffb6d 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/SavedClassInformation.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/SavedClassInformation.java @@ -352,12 +352,12 @@ public class SavedClassInformation implements ClassDataContainer { // This needs to be done at the end to make sure the MAX_HEALTH/MAX_MANA/... stats are loaded. player.getPlayer().setHealth(MMOCoreUtils.fixResource(health, player.getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue())); + player.setHealth(health); player.setMana(mana); player.setStellium(stellium); player.setStamina(stamina); - player.setupRemovableTrigger(); - // Updates level on exp bar - player.refreshVanillaExp(); + player.updateTemporaryTriggers(); + player.getStats().updateStats(); } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java index 05e4c4d5..257c6f1b 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java @@ -8,10 +8,12 @@ import io.lumine.mythic.lib.player.modifier.ModifierSource; import io.lumine.mythic.lib.player.modifier.ModifierType; import io.lumine.mythic.lib.player.skill.PassiveSkill; import io.lumine.mythic.lib.player.skill.PassiveSkillMap; +import io.lumine.mythic.lib.skill.trigger.TriggerType; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.experience.Profession; import net.Indyuce.mmocore.player.stats.StatInfo; +import net.Indyuce.mmocore.skill.ClassSkill; public class PlayerStats { private final PlayerData data; @@ -65,14 +67,22 @@ public class PlayerStats { return data.getProfess().calculateStat(stat, profession == null ? data.getLevel() : data.getCollectionSkills().getLevel(profession)); } + public void updateStats() { + updateStats(false); + } + /** * Used to update MMOCore stat modifiers due to class and send them over to - * MythicLib. Must be ran everytime the player levels up or changes class. + * MythicLib. Must be ran everytime the player levels up, changes class or + * when the plugin reloads. *

- * This is also called when reloading the plugin to make class setup easier, - * see {@link PlayerData#reload()} for more info + * Login scripts are a pretty special case of scripts/skills since they are + * not loaded yet when MythicLib triggers them naturally. Therefore, they + * need to be cast as soon as they are loaded into the MMOCore player data. + * + * @param castLoginScripts Should login scripts be cast */ - public synchronized void updateStats() { + public synchronized void updateStats(boolean castLoginScripts) { // Update player stats for (String stat : MMOCore.plugin.statManager.getRegistered()) { @@ -91,19 +101,33 @@ public class PlayerStats { packet.runUpdate(); } - // Updates the player's unbindable passive skills. + // Updates the player's unbindable CLASS passive skills final PassiveSkillMap skillMap = data.getMMOPlayerData().getPassiveSkillMap(); skillMap.removeModifiers("MMOCorePassiveSkillNotBound"); - data.getProfess().getSkills().stream() - .filter((classSkill) -> !classSkill.needsBound() && classSkill.getSkill().getTrigger().isPassive() && data.hasUnlocked(classSkill) && data.hasUnlockedLevel(classSkill)) - .forEach(classSkill -> skillMap.addModifier(classSkill.toPassive(data))); + for (ClassSkill skill : data.getProfess().getSkills()) + if (!skill.needsBound() + && skill.getSkill().getTrigger().isPassive() + && skill.getSkill().getTrigger() != TriggerType.LOGIN + && data.hasUnlocked(skill) + && data.hasUnlockedLevel(skill)) + skillMap.addModifier(skill.toPassive(data)); - /* - * Updates the player's class scripts, which act just - * like non-bindable passive skills. - */ + // Updates the player's CLASS scripts skillMap.removeModifiers("MMOCoreClassScript"); for (PassiveSkill script : data.getProfess().getScripts()) - skillMap.addModifier(script); + if (script.getType() != TriggerType.LOGIN) skillMap.addModifier(script); + + // If data hasn't been synchronized yet, cast LOGIN scripts + if (castLoginScripts) { + + // Call class login skills + for (ClassSkill skill : data.getProfess().getSkills()) + if (skill.getSkill().getTrigger() == TriggerType.LOGIN) + skill.toCastable(data).cast(data.getMMOPlayerData()); + + // Call class login scripts + for (PassiveSkill skill : data.getProfess().getScripts()) + if (skill.getType() == TriggerType.LOGIN) skill.getTriggeredSkill().cast(data.getMMOPlayerData()); + } } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/quest/trigger/api/Removable.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/quest/trigger/api/Removable.java index 533683ca..eb0c90eb 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/quest/trigger/api/Removable.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/quest/trigger/api/Removable.java @@ -2,6 +2,8 @@ package net.Indyuce.mmocore.api.quest.trigger.api; import net.Indyuce.mmocore.api.player.PlayerData; +// TODO rename to non-permanent +// TODO merge with MythicLib public interface Removable { public void remove(PlayerData playerData); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/SkillList.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/SkillList.java index 5966f2d6..6f7aaeb8 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/SkillList.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/SkillList.java @@ -382,7 +382,7 @@ public class SkillList extends EditableInventory { super(playerData, editable); skills = playerData.getProfess().getSkills() .stream() - .filter((classSkill) -> playerData.hasUnlocked(classSkill)) + .filter(skill -> playerData.hasUnlocked(skill)) .sorted(Comparator.comparingInt(ClassSkill::getUnlockLevel)) .collect(Collectors.toList()); skillSlots = getEditable().getByFunction("skill").getSlots(); diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java index 99d2f774..bd8eaff0 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java @@ -16,7 +16,6 @@ import net.Indyuce.mmocore.skill.ClassSkill; import net.Indyuce.mmocore.skilltree.SkillTreeNode; import net.Indyuce.mmocore.skilltree.tree.SkillTree; import org.apache.commons.lang.Validate; -import org.bukkit.attribute.Attribute; import org.jetbrains.annotations.Nullable; import java.sql.ResultSet; @@ -68,7 +67,6 @@ public class MMOCoreDataSynchronizer extends SQLDataSynchronizer { getData().setNodeLevel(skillTreeNode, json.has(skillTreeNode.getFullId()) ? json.get(skillTreeNode.getFullId()).getAsInt() : 0); } } - getData().setupSkillTree(); Set unlockedItems = new HashSet<>(); if (!isEmpty(result.getString("unlocked_items"))) { JsonArray unlockedItemsArray = MythicLib.plugin.getGson().fromJson(result.getString("unlocked_items"), JsonArray.class); @@ -101,7 +99,6 @@ public class MMOCoreDataSynchronizer extends SQLDataSynchronizer { for (Map.Entry entry : object.entrySet()) { ClassSkill skill = getData().getProfess().getSkill(entry.getValue().getAsString()); if (skill != null) getData().bindSkill(Integer.parseInt(entry.getKey()), skill); - } } if (!isEmpty(result.getString("class_info"))) { @@ -121,20 +118,10 @@ public class MMOCoreDataSynchronizer extends SQLDataSynchronizer { * These should be loaded after to make sure that the * MAX_MANA, MAX_STAMINA & MAX_STELLIUM stats are already loaded. */ + getData().setHealth(result.getDouble("health")); getData().setMana(result.getDouble("mana")); getData().setStamina(result.getDouble("stamina")); getData().setStellium(result.getDouble("stellium")); - getData().setupRemovableTrigger(); - if (getData().isOnline() && !getData().getPlayer().isDead()) { - - /* - * If the player is not dead and the health is 0, this means that the data was - * missing from the data base and it gives full health to the player. If the - * player is dead however, it must not account for that subtle edge case. - */ - final double health = MMOCoreUtils.fixResource(result.getDouble("health"), getData().getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); - getData().getPlayer().setHealth(health); - } UtilityMethods.debug(MMOCore.plugin, "SQL", String.format("{ class: %s, level: %d }", getData().getProfess().getId(), getData().getLevel())); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java index fd1e2a74..a1c82e27 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java @@ -110,20 +110,15 @@ public class YAMLPlayerDataHandler extends YAMLSynchronizedDataHandler