Fixes points spent in skill tree not updating when unlocking a node.

This commit is contained in:
Jules 2024-08-14 11:20:26 -07:00
parent 62e1ace7b5
commit cba1631d44
3 changed files with 126 additions and 63 deletions

View File

@ -45,6 +45,7 @@ import net.Indyuce.mmocore.skill.binding.BoundSkillInfo;
import net.Indyuce.mmocore.skill.binding.SkillSlot;
import net.Indyuce.mmocore.skill.cast.SkillCastingInstance;
import net.Indyuce.mmocore.skill.cast.SkillCastingMode;
import net.Indyuce.mmocore.skilltree.NodeIncrementResult;
import net.Indyuce.mmocore.skilltree.SkillTreeNode;
import net.Indyuce.mmocore.skilltree.SkillTreeStatus;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
@ -63,6 +64,7 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -107,17 +109,13 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
private final Map<PlayerActivity, Long> lastActivity = new HashMap<>();
private final CombatHandler combat = new CombatHandler(this);
/**
* Cached for easier access. Amount of points spent in each skill tree.
*/
private final Map<SkillTree, Integer> pointSpent = new HashMap<>();
/**
* Cached for easier access. Current status of each skill tree node.
*/
private final Map<SkillTreeNode, SkillTreeStatus> nodeStates = new HashMap<>();
private final Map<SkillTreeNode, Integer> nodeLevels = new HashMap<>();
private final Map<String, Integer> skillTreePoints = new HashMap<>();
private final Map<SkillTree, Integer> skillTreePointsSpent = new HashMap<>();
/**
* Saves the namespacedkeys of the items that have been unlocked in the form "namespace:key".
@ -224,19 +222,24 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
skillTree.setupNodeStates(this);
}
public int getPointSpent(SkillTree skillTree) {
return pointSpent.getOrDefault(skillTree, 0);
public int getPointsSpent(@NotNull SkillTree skillTree) {
return skillTreePointsSpent.getOrDefault(skillTree, 0);
}
public void setSkillTreePoints(String treeId, int points) {
@Deprecated
public int getPointSpent(SkillTree skillTree) {
return getPointsSpent(skillTree);
}
public void setSkillTreePoints(@NotNull String treeId, int points) {
skillTreePoints.put(treeId, points);
}
public void giveSkillTreePoints(String id, int val) {
skillTreePoints.put(id, skillTreePoints.getOrDefault(id, 0) + val);
public void giveSkillTreePoints(@NotNull String id, int val) {
skillTreePoints.merge(id, val, (points, ignored) -> points + val);
}
public int countSkillTreePoints(SkillTree skillTree) {
public int countSkillTreePoints(@NotNull SkillTree skillTree) {
return nodeLevels.keySet().stream().filter(node -> node.getTree().equals(skillTree)).mapToInt(node -> nodeLevels.get(node) * node.getSkillTreePointsConsumed()).sum();
}
@ -244,6 +247,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
* Make a copy to make sure that the object
* created is independent of the state of playerData.
*/
@NotNull
public Map<String, Integer> mapSkillTreePoints() {
return new HashMap(skillTreePoints);
}
@ -290,7 +294,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
// Node levels, states and points spent
nodeLevels.clear();
nodeStates.clear();
pointSpent.clear();
skillTreePointsSpent.clear();
// Skill tree points
skillTreePoints.clear();
@ -299,11 +303,24 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
tableItemClaims.keySet().removeIf(s -> s.startsWith(SkillTreeNode.KEY_PREFIX));
}
public boolean canIncrementNodeLevel(@NotNull SkillTreeNode node) {
SkillTreeStatus skillTreeStatus = nodeStates.get(node);
//Check the State of the node
if (skillTreeStatus != SkillTreeStatus.UNLOCKED && skillTreeStatus != SkillTreeStatus.UNLOCKABLE) return false;
return node.hasPermissionRequirement(this) && getNodeLevel(node) < node.getMaxLevel() && (skillTreePoints.getOrDefault(node.getTree().getId(), 0) + skillTreePoints.getOrDefault("global", 0) >= node.getSkillTreePointsConsumed());
@NotNull
public NodeIncrementResult canIncrementNodeLevel(@NotNull SkillTreeNode node) {
final SkillTreeStatus skillTreeStatus = nodeStates.get(node);
// Check node state
if (skillTreeStatus != SkillTreeStatus.UNLOCKED && skillTreeStatus != SkillTreeStatus.UNLOCKABLE)
return NodeIncrementResult.LOCKED_NODE;
// Check permission
if (!node.hasPermissionRequirement(this)) return NodeIncrementResult.PERMISSION_DENIED;
// Max node level
if (getNodeLevel(node) >= node.getMaxLevel()) return NodeIncrementResult.MAX_LEVEL_REACHED;
final int skillTreePoints = this.skillTreePoints.getOrDefault(node.getTree().getId(), 0) + this.skillTreePoints.getOrDefault("global", 0);
if (skillTreePoints < node.getSkillTreePointsConsumed()) return NodeIncrementResult.NOT_ENOUGH_POINTS;
return NodeIncrementResult.SUCCESS;
}
/**
@ -312,19 +329,22 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
* global skill-tree points ('all')
*/
public void incrementNodeLevel(@NotNull SkillTreeNode node) {
final int newLevel = nodeLevels.merge(node, 1, (level, ignored) -> level + 1);
final int newLevel = addNodeLevels(node, 1);
node.updateAdvancement(this, newLevel); // Claim the node exp table
if (nodeStates.get(node) == SkillTreeStatus.UNLOCKABLE) setNodeState(node, SkillTreeStatus.UNLOCKED);
int cost = node.getSkillTreePointsConsumed();
final int skillTreeSpecificPoints = skillTreePoints.getOrDefault(node.getTree().getId(), 0);
if (skillTreeSpecificPoints > 0) {
int pointWithdrawn = Math.min(cost, skillTreeSpecificPoints);
withdrawSkillTreePoints(node.getTree().getId(), pointWithdrawn);
cost -= pointWithdrawn;
}
if (cost > 0) withdrawSkillTreePoints("global", cost);
// Unload the nodeStates map (for the skill tree) and reload it completely
// Update node state
nodeStates.compute(node, (key, status) -> status == SkillTreeStatus.UNLOCKABLE ? SkillTreeStatus.UNLOCKED : status);
// Consume skill tree points
final AtomicInteger cost = new AtomicInteger(node.getSkillTreePointsConsumed());
skillTreePoints.computeIfPresent(node.getTree().getId(), (key, points) -> {
final int withdrawn = Math.min(points, cost.get());
cost.set(cost.get() - withdrawn);
return points == withdrawn ? null : points - withdrawn;
});
if (cost.get() > 0) withdrawSkillTreePoints("global", cost.get());
// Reload node states from full skill tree
for (SkillTreeNode node1 : node.getTree().getNodes()) nodeStates.remove(node1);
node.getTree().setupNodeStates(this);
}
@ -359,10 +379,19 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
return nodeLevels.getOrDefault(node, 0);
}
public void setNodeLevel(SkillTreeNode node, int nodeLevel) {
int delta = (nodeLevel - nodeLevels.getOrDefault(node, 0)) * node.getSkillTreePointsConsumed();
pointSpent.put(node.getTree(), pointSpent.getOrDefault(node.getTree(), 0) + delta);
nodeLevels.put(node, nodeLevel);
public void setNodeLevel(@NotNull SkillTreeNode node, int nodeLevel) {
nodeLevels.compute(node, (ignored, currentLevelInteger) -> {
final int currentLevel = currentLevelInteger == null ? 0 : currentLevelInteger;
final int delta = (nodeLevel - currentLevel) * node.getSkillTreePointsConsumed();
skillTreePointsSpent.merge(node.getTree(), delta, (level, ignored2) -> level + delta);
return nodeLevel;
});
}
public int addNodeLevels(@NotNull SkillTreeNode node, int increment) {
final int delta = increment * node.getSkillTreePointsConsumed();
skillTreePointsSpent.merge(node.getTree(), delta, (points, ignored) -> points + delta);
return nodeLevels.merge(node, increment, (level, ignored) -> level + increment);
}
public void resetSkillTree(SkillTree skillTree) {

View File

@ -108,7 +108,7 @@ public class SkillTreeViewer extends EditableInventory {
holders.register("realloc-points", inv.getPlayerData().getSkillTreeReallocationPoints());
int maxPointSpent = inv.getSkillTree().getMaxPointSpent();
holders.register("max-point-spent", maxPointSpent == Integer.MAX_VALUE ? "" : maxPointSpent);
holders.register("point-spent", inv.getPlayerData().getPointSpent(inv.getSkillTree()));
holders.register("point-spent", inv.getPlayerData().getPointsSpent(inv.getSkillTree()));
return holders;
}
@ -184,7 +184,7 @@ public class SkillTreeViewer extends EditableInventory {
holders.register("id", skillTree.getId());
int maxPointSpent = inv.getSkillTree().getMaxPointSpent();
holders.register("max-point-spent", maxPointSpent == Integer.MAX_VALUE ? "" : maxPointSpent);
holders.register("point-spent", inv.getPlayerData().getPointSpent(inv.getSkillTree()));
holders.register("point-spent", inv.getPlayerData().getPointsSpent(inv.getSkillTree()));
holders.register("skill-tree-points", inv.getPlayerData().getSkillTreePoints(inv.getSkillTree().getId()));
holders.register("global-points", inv.getPlayerData().getSkillTreePoints("global"));
return holders;
@ -315,7 +315,7 @@ public class SkillTreeViewer extends EditableInventory {
}
int maxPointSpent = inv.getSkillTree().getMaxPointSpent();
holders.register("max-point-spent", maxPointSpent == Integer.MAX_VALUE ? "" : maxPointSpent);
holders.register("point-spent", inv.getPlayerData().getPointSpent(inv.getSkillTree()));
holders.register("point-spent", inv.getPlayerData().getPointsSpent(inv.getSkillTree()));
holders.register("skill-tree-points", inv.getPlayerData().getSkillTreePoints(inv.getSkillTree().getId()));
holders.register("global-points", inv.getPlayerData().getSkillTreePoints("global"));
@ -466,7 +466,7 @@ public class SkillTreeViewer extends EditableInventory {
open();
}
if (item.getFunction().equals("reallocation")) {
int spent = playerData.getPointSpent(skillTree);
int spent = playerData.getPointsSpent(skillTree);
if (spent < 1) {
ConfigMessage.fromKey("no-skill-tree-points-spent").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
@ -478,7 +478,7 @@ public class SkillTreeViewer extends EditableInventory {
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
return;
} else {
int reallocated = playerData.getPointSpent(skillTree);
int reallocated = playerData.getPointsSpent(skillTree);
//We remove all the nodeStates progress
playerData.giveSkillTreePoints(skillTree.getId(), reallocated);
playerData.giveSkillTreeReallocationPoints(-1);
@ -501,47 +501,55 @@ public class SkillTreeViewer extends EditableInventory {
return;
}
if (item.getFunction().equals("skill-tree-node")) {
if (event.getClickType() == ClickType.LEFT) {
PersistentDataContainer container = event.getClickedItem().getItemMeta().getPersistentDataContainer();
int x = container.get(new NamespacedKey(MMOCore.plugin, "coordinates.x"), PersistentDataType.INTEGER);
int y = container.get(new NamespacedKey(MMOCore.plugin, "coordinates.y"), PersistentDataType.INTEGER);
if (!skillTree.isNode(new IntegerCoordinates(x, y))) {
return;
}
SkillTreeNode node = skillTree.getNode(new IntegerCoordinates(x, y));
if (playerData.getPointSpent(skillTree) >= skillTree.getMaxPointSpent()) {
ConfigMessage.fromKey("max-points-reached").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
return;
}
if (item.getFunction().equals("skill-tree-node") && event.getClickType() == ClickType.LEFT) {
if (playerData.canIncrementNodeLevel(node)) {
final PersistentDataContainer container = event.getClickedItem().getItemMeta().getPersistentDataContainer();
final int x = container.get(new NamespacedKey(MMOCore.plugin, "coordinates.x"), PersistentDataType.INTEGER);
final int y = container.get(new NamespacedKey(MMOCore.plugin, "coordinates.y"), PersistentDataType.INTEGER);
if (!skillTree.isNode(new IntegerCoordinates(x, y))) return;
// Maximum amount of skill points spent in node
final SkillTreeNode node = skillTree.getNode(new IntegerCoordinates(x, y));
if (playerData.getPointsSpent(skillTree) >= skillTree.getMaxPointSpent()) {
ConfigMessage.fromKey("max-points-reached").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
return;
}
switch (playerData.canIncrementNodeLevel(node)) {
case SUCCESS: {
playerData.incrementNodeLevel(node);
ConfigMessage.fromKey("upgrade-skill-node", "skill-node", node.getName(), "level", "" + playerData.getNodeLevel(node)).send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.LEVEL_SKILL_TREE_NODE).playTo(getPlayer());
open();
} else if (playerData.getNodeStatus(node) == SkillTreeStatus.LOCKED || playerData.getNodeStatus(node) == SkillTreeStatus.FULLY_LOCKED) {
ConfigMessage.fromKey("locked-node").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
break;
}
} else if (playerData.getNodeLevel(node) >= node.getMaxLevel()) {
ConfigMessage.fromKey("skill-node-max-level-hit").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
} else if (!node.hasPermissionRequirement(playerData)) {
case PERMISSION_DENIED: {
ConfigMessage.fromKey("missing-skill-node-permission").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
break;
}
//Else the player doesn't doesn't have the skill tree points
else {
case LOCKED_NODE: {
ConfigMessage.fromKey("locked-node").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
break;
}
case MAX_LEVEL_REACHED: {
ConfigMessage.fromKey("skill-node-max-level-hit").send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
break;
}
case NOT_ENOUGH_POINTS: {
ConfigMessage.fromKey("not-enough-skill-tree-points", "point", "" + node.getSkillTreePointsConsumed()).send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
break;
}
}
}
}
}
}

View File

@ -0,0 +1,26 @@
package net.Indyuce.mmocore.skilltree;
public enum NodeIncrementResult {
SUCCESS,
/**
* Node is still locked/not unlockable
*/
LOCKED_NODE,
/**
* Player does not have required permission
*/
PERMISSION_DENIED,
/**
* Maximum level of node is reached
*/
MAX_LEVEL_REACHED,
/**
* Player does not have enough skill tree points
*/
NOT_ENOUGH_POINTS,
}