extend ability system to support dynamic slot counts, add skill tree validation, new prerequisites API, and extended mod commands
This commit is contained in:
@@ -115,10 +115,11 @@ public final class SkillEngineAPI {
|
|||||||
if (unlockPlan == null) return false;
|
if (unlockPlan == null) return false;
|
||||||
for (SkillNode plannedNode : unlockPlan) {
|
for (SkillNode plannedNode : unlockPlan) {
|
||||||
data.unlockNode(plannedNode);
|
data.unlockNode(plannedNode);
|
||||||
if (plannedNode.getCost() > 0) {
|
int cost = Math.max(0, plannedNode.getCost());
|
||||||
|
if (cost > 0) {
|
||||||
MinecraftForge.EVENT_BUS.post(new SkillPointsChangedEvent(
|
MinecraftForge.EVENT_BUS.post(new SkillPointsChangedEvent(
|
||||||
player,
|
player,
|
||||||
plannedNode.getCost(),
|
cost,
|
||||||
SkillPointsChangedEvent.Reason.SPENT,
|
SkillPointsChangedEvent.Reason.SPENT,
|
||||||
plannedNode.getId()
|
plannedNode.getId()
|
||||||
));
|
));
|
||||||
@@ -131,9 +132,10 @@ public final class SkillEngineAPI {
|
|||||||
PlayerSkillData data = getPlayerData(player);
|
PlayerSkillData data = getPlayerData(player);
|
||||||
SkillNode node = SkillNodeRegistry.get(id);
|
SkillNode node = SkillNodeRegistry.get(id);
|
||||||
if (data == null || node == null || !data.refundNode(id)) return false;
|
if (data == null || node == null || !data.refundNode(id)) return false;
|
||||||
|
int refunded = Math.max(0, node.getCost());
|
||||||
MinecraftForge.EVENT_BUS.post(new SkillPointsChangedEvent(
|
MinecraftForge.EVENT_BUS.post(new SkillPointsChangedEvent(
|
||||||
player,
|
player,
|
||||||
node.getCost(),
|
refunded,
|
||||||
SkillPointsChangedEvent.Reason.REFUNDED,
|
SkillPointsChangedEvent.Reason.REFUNDED,
|
||||||
id
|
id
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class PlayerSkillData {
|
|||||||
if (node == null) return;
|
if (node == null) return;
|
||||||
if (unlockedNodes.add(node.getId())) {
|
if (unlockedNodes.add(node.getId())) {
|
||||||
activeTags.addAll(node.getTags());
|
activeTags.addAll(node.getTags());
|
||||||
skillPoints -= node.getCost();
|
skillPoints -= Math.max(0, node.getCost());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ public class PlayerSkillData {
|
|||||||
ensureSlotCount();
|
ensureSlotCount();
|
||||||
int idx = slot - 1;
|
int idx = slot - 1;
|
||||||
if (idx < 0 || idx >= abilityCooldowns.length) return;
|
if (idx < 0 || idx >= abilityCooldowns.length) return;
|
||||||
abilityCooldowns[idx] = ticks;
|
abilityCooldowns[idx] = Math.max(0, ticks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCooldown(int slot) {
|
public int getCooldown(int slot) {
|
||||||
@@ -226,7 +226,7 @@ public class PlayerSkillData {
|
|||||||
int total = 0;
|
int total = 0;
|
||||||
for (ResourceLocation id : unlockedNodes) {
|
for (ResourceLocation id : unlockedNodes) {
|
||||||
SkillNode node = SkillNodeRegistry.get(id);
|
SkillNode node = SkillNodeRegistry.get(id);
|
||||||
if (node != null) total += node.getCost();
|
if (node != null) total += Math.max(0, node.getCost());
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ public class PlayerSkillData {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skillPoints += node.getCost();
|
skillPoints += Math.max(0, node.getCost());
|
||||||
rebuildActiveTags();
|
rebuildActiveTags();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -259,8 +259,9 @@ public class PlayerSkillData {
|
|||||||
for (ResourceLocation id : toRefund) {
|
for (ResourceLocation id : toRefund) {
|
||||||
SkillNode node = SkillNodeRegistry.get(id);
|
SkillNode node = SkillNodeRegistry.get(id);
|
||||||
if (node != null && unlockedNodes.remove(id)) {
|
if (node != null && unlockedNodes.remove(id)) {
|
||||||
skillPoints += node.getCost();
|
int cost = Math.max(0, node.getCost());
|
||||||
refunded += node.getCost();
|
skillPoints += cost;
|
||||||
|
refunded += cost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rebuildActiveTags();
|
rebuildActiveTags();
|
||||||
|
|||||||
+16
-4
@@ -20,6 +20,10 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
public class ClientboundSyncSkillDefinitionsPacket {
|
public class ClientboundSyncSkillDefinitionsPacket {
|
||||||
|
|
||||||
|
private static final int MAX_SYNCED_NODES = 4096;
|
||||||
|
private static final int MAX_LIST_ENTRIES = 512;
|
||||||
|
private static final int MAX_PREREQUISITES = 256;
|
||||||
|
|
||||||
private final List<SkillNode> skillNodes;
|
private final List<SkillNode> skillNodes;
|
||||||
private final List<AbilityNode> abilityNodes;
|
private final List<AbilityNode> abilityNodes;
|
||||||
|
|
||||||
@@ -42,13 +46,13 @@ public class ClientboundSyncSkillDefinitionsPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ClientboundSyncSkillDefinitionsPacket decode(FriendlyByteBuf buf) {
|
public static ClientboundSyncSkillDefinitionsPacket decode(FriendlyByteBuf buf) {
|
||||||
int skillNodeCount = buf.readInt();
|
int skillNodeCount = readBoundedCount(buf, MAX_SYNCED_NODES, "skill node definitions");
|
||||||
List<SkillNode> skillNodes = new ArrayList<>(skillNodeCount);
|
List<SkillNode> skillNodes = new ArrayList<>(skillNodeCount);
|
||||||
for (int i = 0; i < skillNodeCount; i++) {
|
for (int i = 0; i < skillNodeCount; i++) {
|
||||||
skillNodes.add(readSkillNode(buf));
|
skillNodes.add(readSkillNode(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
int abilityNodeCount = buf.readInt();
|
int abilityNodeCount = readBoundedCount(buf, MAX_SYNCED_NODES, "ability node definitions");
|
||||||
List<AbilityNode> abilityNodes = new ArrayList<>(abilityNodeCount);
|
List<AbilityNode> abilityNodes = new ArrayList<>(abilityNodeCount);
|
||||||
for (int i = 0; i < abilityNodeCount; i++) {
|
for (int i = 0; i < abilityNodeCount; i++) {
|
||||||
abilityNodes.add(readAbilityNode(buf));
|
abilityNodes.add(readAbilityNode(buf));
|
||||||
@@ -136,7 +140,7 @@ public class ClientboundSyncSkillDefinitionsPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static List<ResourceLocation> readResourceLocationList(FriendlyByteBuf buf) {
|
private static List<ResourceLocation> readResourceLocationList(FriendlyByteBuf buf) {
|
||||||
int size = buf.readInt();
|
int size = readBoundedCount(buf, MAX_LIST_ENTRIES, "resource location list");
|
||||||
List<ResourceLocation> ids = new ArrayList<>(size);
|
List<ResourceLocation> ids = new ArrayList<>(size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
ids.add(buf.readResourceLocation());
|
ids.add(buf.readResourceLocation());
|
||||||
@@ -153,11 +157,19 @@ public class ClientboundSyncSkillDefinitionsPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, Integer> readPrerequisites(FriendlyByteBuf buf) {
|
private static Map<String, Integer> readPrerequisites(FriendlyByteBuf buf) {
|
||||||
int size = buf.readInt();
|
int size = readBoundedCount(buf, MAX_PREREQUISITES, "prerequisites");
|
||||||
Map<String, Integer> prerequisites = new HashMap<>(size);
|
Map<String, Integer> prerequisites = new HashMap<>(size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
prerequisites.put(buf.readUtf(), buf.readInt());
|
prerequisites.put(buf.readUtf(), buf.readInt());
|
||||||
}
|
}
|
||||||
return prerequisites;
|
return prerequisites;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int readBoundedCount(FriendlyByteBuf buf, int max, String fieldName) {
|
||||||
|
int count = buf.readInt();
|
||||||
|
if (count < 0 || count > max) {
|
||||||
|
throw new IllegalArgumentException("Invalid " + fieldName + " count: " + count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
public class ClientboundSyncSkillsPacket {
|
public class ClientboundSyncSkillsPacket {
|
||||||
|
|
||||||
|
private static final int MAX_SYNCED_NODES = 4096;
|
||||||
|
private static final int MAX_ABILITY_SLOTS = 64;
|
||||||
|
|
||||||
private final Set<ResourceLocation> unlocked;
|
private final Set<ResourceLocation> unlocked;
|
||||||
private final Set<ResourceLocation> unlockedAbilities;
|
private final Set<ResourceLocation> unlockedAbilities;
|
||||||
private final int skillPoints;
|
private final int skillPoints;
|
||||||
@@ -46,15 +49,15 @@ public class ClientboundSyncSkillsPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ClientboundSyncSkillsPacket decode(FriendlyByteBuf buf) {
|
public static ClientboundSyncSkillsPacket decode(FriendlyByteBuf buf) {
|
||||||
int size = buf.readInt();
|
int size = readBoundedCount(buf, MAX_SYNCED_NODES, "unlocked skill nodes");
|
||||||
Set<ResourceLocation> unlocked = new HashSet<>();
|
Set<ResourceLocation> unlocked = new HashSet<>();
|
||||||
for (int i = 0; i < size; i++) unlocked.add(buf.readResourceLocation());
|
for (int i = 0; i < size; i++) unlocked.add(buf.readResourceLocation());
|
||||||
int abilitySize = buf.readInt();
|
int abilitySize = readBoundedCount(buf, MAX_SYNCED_NODES, "unlocked abilities");
|
||||||
Set<ResourceLocation> unlockedAbilities = new HashSet<>();
|
Set<ResourceLocation> unlockedAbilities = new HashSet<>();
|
||||||
for (int i = 0; i < abilitySize; i++) unlockedAbilities.add(buf.readResourceLocation());
|
for (int i = 0; i < abilitySize; i++) unlockedAbilities.add(buf.readResourceLocation());
|
||||||
int points = buf.readInt();
|
int points = buf.readInt();
|
||||||
int slotsCount = buf.readInt();
|
int slotsCount = readBoundedCount(buf, MAX_ABILITY_SLOTS, "ability slots");
|
||||||
ResourceLocation[] slots = new ResourceLocation[Math.max(slotsCount, 0)];
|
ResourceLocation[] slots = new ResourceLocation[slotsCount];
|
||||||
for (int i = 0; i < slots.length; i++) {
|
for (int i = 0; i < slots.length; i++) {
|
||||||
boolean present = buf.readBoolean();
|
boolean present = buf.readBoolean();
|
||||||
slots[i] = present ? buf.readResourceLocation() : null;
|
slots[i] = present ? buf.readResourceLocation() : null;
|
||||||
@@ -76,4 +79,12 @@ public class ClientboundSyncSkillsPacket {
|
|||||||
});
|
});
|
||||||
ctx.get().setPacketHandled(true);
|
ctx.get().setPacketHandled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int readBoundedCount(FriendlyByteBuf buf, int max, String fieldName) {
|
||||||
|
int count = buf.readInt();
|
||||||
|
if (count < 0 || count > max) {
|
||||||
|
throw new IllegalArgumentException("Invalid " + fieldName + " count: " + count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ public class ServerboundActivateAbilityPacket {
|
|||||||
int cd = data.getCooldown(msg.slot);
|
int cd = data.getCooldown(msg.slot);
|
||||||
if (cd > 0) return;
|
if (cd > 0) return;
|
||||||
ability.execute(player);
|
ability.execute(player);
|
||||||
data.setCooldown(msg.slot, ability.getCooldown());
|
int cooldown = Math.max(0, ability.getCooldown());
|
||||||
|
data.setCooldown(msg.slot, cooldown);
|
||||||
PacketHandler.CHANNEL.send(
|
PacketHandler.CHANNEL.send(
|
||||||
PacketDistributor.PLAYER.with(() -> player),
|
PacketDistributor.PLAYER.with(() -> player),
|
||||||
new ClientboundSyncCooldownPacket(msg.slot, ability.getCooldown())
|
new ClientboundSyncCooldownPacket(msg.slot, cooldown)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,10 +42,11 @@ public class ServerboundUnlockNodePacket {
|
|||||||
if (unlockPlan != null) {
|
if (unlockPlan != null) {
|
||||||
for (SkillNode plannedNode : unlockPlan) {
|
for (SkillNode plannedNode : unlockPlan) {
|
||||||
data.unlockNode(plannedNode);
|
data.unlockNode(plannedNode);
|
||||||
if (plannedNode.getCost() > 0) {
|
int cost = Math.max(0, plannedNode.getCost());
|
||||||
|
if (cost > 0) {
|
||||||
MinecraftForge.EVENT_BUS.post(new SkillPointsChangedEvent(
|
MinecraftForge.EVENT_BUS.post(new SkillPointsChangedEvent(
|
||||||
player,
|
player,
|
||||||
plannedNode.getCost(),
|
cost,
|
||||||
SkillPointsChangedEvent.Reason.SPENT,
|
SkillPointsChangedEvent.Reason.SPENT,
|
||||||
plannedNode.getId()
|
plannedNode.getId()
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class SkillLogic {
|
|||||||
|
|
||||||
int totalCost = 0;
|
int totalCost = 0;
|
||||||
for (SkillNode plannedNode : plan) {
|
for (SkillNode plannedNode : plan) {
|
||||||
totalCost += plannedNode.getCost();
|
totalCost += Math.max(0, plannedNode.getCost());
|
||||||
}
|
}
|
||||||
if (data.getSkillPoints() < totalCost) {
|
if (data.getSkillPoints() < totalCost) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ public final class SkillTreeValidator {
|
|||||||
if (!isPngTexture(node.getIcons())) {
|
if (!isPngTexture(node.getIcons())) {
|
||||||
issues.add(node.getId() + " has invalid icon path " + node.getIcons() + " (expected a .png texture)");
|
issues.add(node.getId() + " has invalid icon path " + node.getIcons() + " (expected a .png texture)");
|
||||||
}
|
}
|
||||||
|
if (node.getCost() < 0) {
|
||||||
|
issues.add(node.getId() + " has negative cost " + node.getCost());
|
||||||
|
}
|
||||||
for (ResourceLocation parent : node.getLinks()) {
|
for (ResourceLocation parent : node.getLinks()) {
|
||||||
if (SkillNodeRegistry.get(parent) == null) {
|
if (SkillNodeRegistry.get(parent) == null) {
|
||||||
issues.add(node.getId() + " links to missing skill node " + parent);
|
issues.add(node.getId() + " links to missing skill node " + parent);
|
||||||
@@ -83,6 +86,9 @@ public final class SkillTreeValidator {
|
|||||||
if (!isPngTexture(ability.getIcon())) {
|
if (!isPngTexture(ability.getIcon())) {
|
||||||
issues.add(ability.getId() + " has invalid icon path " + ability.getIcon() + " (expected a .png texture)");
|
issues.add(ability.getId() + " has invalid icon path " + ability.getIcon() + " (expected a .png texture)");
|
||||||
}
|
}
|
||||||
|
if (ability.getCooldown() < 0) {
|
||||||
|
issues.add(ability.getId() + " has negative cooldown " + ability.getCooldown());
|
||||||
|
}
|
||||||
for (ResourceLocation parent : ability.getLinks()) {
|
for (ResourceLocation parent : ability.getLinks()) {
|
||||||
if (SkillNodeRegistry.get(parent) == null && AbilityNodeRegistry.get(parent) == null) {
|
if (SkillNodeRegistry.get(parent) == null && AbilityNodeRegistry.get(parent) == null) {
|
||||||
issues.add(ability.getId() + " links to missing node or ability " + parent);
|
issues.add(ability.getId() + " links to missing node or ability " + parent);
|
||||||
|
|||||||
Reference in New Issue
Block a user