Implement all required compat with Ascend

This commit is contained in:
2025-11-25 09:53:20 +01:00
parent 6f06da5f31
commit 17fce2fd17
14 changed files with 174 additions and 90 deletions
@@ -1,12 +1,9 @@
package net.fretux.skillengine; package net.fretux.skillengine;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import net.fretux.skillengine.capability.SkillEngineCapabilities;
import net.fretux.skillengine.network.PacketHandler; import net.fretux.skillengine.network.PacketHandler;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -1,18 +1,25 @@
package net.fretux.skillengine.client; package net.fretux.skillengine.client;
import com.mojang.math.Axis; import com.mojang.math.Axis;
import net.fretux.skillengine.SkillEngine; import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.skillengine.network.PacketHandler; import net.fretux.skillengine.network.PacketHandler;
import net.fretux.skillengine.network.ServerboundUnlockNodePacket; import net.fretux.skillengine.network.ServerboundUnlockNodePacket;
import net.fretux.skillengine.skilltree.SkillNode; import net.fretux.skillengine.skilltree.SkillNode;
import net.fretux.skillengine.skilltree.SkillNodeRegistry; import net.fretux.skillengine.skilltree.SkillNodeRegistry;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import java.util.ArrayList;
import java.util.List;
public class SkilltreeScreen extends Screen { public class SkilltreeScreen extends Screen {
private float zoom = 1.0f; private float zoom = 1.0f;
@@ -69,17 +76,14 @@ public class SkilltreeScreen extends Screen {
@Override @Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dx, double dy) { public boolean mouseDragged(double mouseX, double mouseY, int button, double dx, double dy) {
// Don't pan while overlay is open
if (selectedNode != null) { if (selectedNode != null) {
return super.mouseDragged(mouseX, mouseY, button, dx, dy); return super.mouseDragged(mouseX, mouseY, button, dx, dy);
} }
if (dragging && button == 0) { if (dragging && button == 0) {
panX += dx; panX += dx;
panY += dy; panY += dy;
return true; return true;
} }
return super.mouseDragged(mouseX, mouseY, button, dx, dy); return super.mouseDragged(mouseX, mouseY, button, dx, dy);
} }
@@ -97,13 +101,42 @@ public class SkilltreeScreen extends Screen {
hoveredNode = findNodeAt(mouseX, mouseY); hoveredNode = findNodeAt(mouseX, mouseY);
drawGraph(gfx); drawGraph(gfx);
if (hoveredNode != null && selectedNode == null) { if (hoveredNode != null && selectedNode == null) {
gfx.renderTooltip(font, hoveredNode.getTitle(), mouseX, mouseY); List<Component> tooltip = new ArrayList<>();
tooltip.add(hoveredNode.getTitle());
String desc = hoveredNode.getDescription().getString();
List<FormattedText> wrapped = font.getSplitter().splitLines(
desc,
220,
hoveredNode.getDescription().getStyle()
);
for (FormattedText ft : wrapped) {
tooltip.add(Component.literal(ft.getString()).withStyle(ChatFormatting.GRAY));
}
tooltip.add(Component.literal("Cost: " + hoveredNode.getCost())
.withStyle(ChatFormatting.DARK_AQUA));
if (!hoveredNode.getPrereqAttributes().isEmpty()) {
tooltip.add(Component.literal("Requirements:")
.withStyle(ChatFormatting.GOLD));
Minecraft.getInstance().player.getCapability(PlayerStatsProvider.PLAYER_STATS)
.ifPresent(stats -> {
hoveredNode.getPrereqAttributes().forEach((attr, required) -> {
int current = stats.getAttributeLevel(attr);
boolean ok = current >= required;
tooltip.add(
Component.literal(" - " + attr + ": " + current + "/" + required)
.withStyle(ok ? ChatFormatting.GREEN : ChatFormatting.RED)
);
});
});
}
gfx.renderComponentTooltip(font, tooltip, mouseX, mouseY);
} }
if (selectedNode != null) { if (selectedNode != null) {
renderOverlay(gfx, selectedNode); renderOverlay(gfx, selectedNode);
} }
super.render(gfx, mouseX, mouseY, partialTicks); super.render(gfx, mouseX, mouseY, partialTicks);
} }
private void drawGraph(GuiGraphics gfx) { private void drawGraph(GuiGraphics gfx) {
for (SkillNode node : SkillNodeRegistry.all()) { for (SkillNode node : SkillNodeRegistry.all()) {
int[] p1 = worldToScreen(node.getX(), node.getY()); int[] p1 = worldToScreen(node.getX(), node.getY());
@@ -123,17 +156,20 @@ public class SkilltreeScreen extends Screen {
int[] pos = worldToScreen(node.getX(), node.getY()); int[] pos = worldToScreen(node.getX(), node.getY());
int r = 12; int r = 12;
boolean unlocked = SkilltreeClientState.isUnlocked(node.getId()); boolean unlocked = SkilltreeClientState.isUnlocked(node.getId());
int color = unlocked int color = unlocked ? 0xFF44CC44 : 0xFF444444;
? 0xFF44CC44
: 0xFF444444;
if (node == hoveredNode) color = 0xFF777777; if (node == hoveredNode) color = 0xFF777777;
if (node == selectedNode) color = 0xFF999933; if (node == selectedNode) color = 0xFF999933;
gfx.fill(pos[0] - r, pos[1] - r, pos[0] + r, pos[1] + r, color); gfx.fill(pos[0] - r, pos[1] - r, pos[0] + r, pos[1] + r, color);
gfx.drawString(font, ResourceLocation icon = node.getIcons();
"", if (icon != null) {
pos[0] + r + 2, int size = 16;
pos[1] - r, gfx.blit(icon,
0xFFFFFFFF); pos[0] - size / 2,
pos[1] - size / 2,
0, 0,
size, size,
size, size);
}
} }
} }
@@ -157,16 +193,42 @@ public class SkilltreeScreen extends Screen {
private void renderOverlay(GuiGraphics gfx, SkillNode node) { private void renderOverlay(GuiGraphics gfx, SkillNode node) {
int w = 240; int w = 240;
int h = 160; int h = 180;
int x = (width - w) / 2; int x = (width - w) / 2;
int y = (height - h) / 2; int y = (height - h) / 2;
gfx.fill(0, 0, width, height, 0x88000000); gfx.fill(0, 0, width, height, 0x88000000);
gfx.fill(x, y, x + w, y + h, 0xFF222222); gfx.fill(x, y, x + w, y + h, 0xFF222222);
gfx.drawCenteredString(font, node.getTitle(), x + w / 2, y + 10, 0xFFFFFF); gfx.drawCenteredString(font, node.getTitle(), x + w / 2, y + 10, 0xFFFFFF);
gfx.drawWordWrap(font, node.getDescription(), x + 10, y + 30, w - 20, 0xDDDDDD); gfx.drawWordWrap(font, node.getDescription(), x + 10, y + 30, w - 20, 0xDDDDDD);
final int[] textY = {y + 95};
gfx.drawString(font, gfx.drawString(font,
Component.literal("Cost: " + node.getCost() + " skill points"), Component.literal("Cost: " + node.getCost() + " skill points")
x + 10, y + h - 45, 0xAAAAAA); .withStyle(ChatFormatting.AQUA),
x + 10, textY[0],
0xAAAAAA);
textY[0] += 15;
if (!node.getPrereqAttributes().isEmpty()) {
gfx.drawString(font,
Component.literal("Requirements:")
.withStyle(ChatFormatting.GOLD),
x + 10, textY[0],
0xFFFFFF);
textY[0] += 12;
Minecraft.getInstance().player.getCapability(PlayerStatsProvider.PLAYER_STATS)
.ifPresent(stats -> {
for (var entry : node.getPrereqAttributes().entrySet()) {
String attr = entry.getKey();
int required = entry.getValue();
int current = stats.getAttributeLevel(attr);
boolean ok = current >= required;
Component line = Component.literal(
" - " + attr + ": " + current + "/" + required
).withStyle(ok ? ChatFormatting.GREEN : ChatFormatting.RED);
gfx.drawString(font, line, x + 10, textY[0], 0xFFFFFF);
textY[0] += 12;
}
});
}
} }
private void rebuildOverlayButtons() { private void rebuildOverlayButtons() {
@@ -1,9 +1,7 @@
package net.fretux.skillengine.compat; package net.fretux.skillengine.compat;
import net.fretux.ascend.player.PlayerStats;
import net.fretux.ascend.player.PlayerStatsProvider; import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.skillengine.SkillEngine; import net.fretux.skillengine.SkillEngine;
import net.fretux.skillengine.capability.PlayerSkillData;
import net.fretux.skillengine.capability.SkillEngineCapabilities; import net.fretux.skillengine.capability.SkillEngineCapabilities;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
@@ -47,7 +47,7 @@ public class ServerboundUnlockNodePacket {
SkillEngine.LOGGER.info("Received unlock request for {} from {} (points={})", SkillEngine.LOGGER.info("Received unlock request for {} from {} (points={})",
msg.nodeId, player.getGameProfile().getName(), data.getSkillPoints()); msg.nodeId, player.getGameProfile().getName(), data.getSkillPoints());
if (SkillLogic.canUnlock(data, node)) { if (SkillLogic.canUnlock(data, player, node)) {
data.unlockNode(node); data.unlockNode(node);
SkillEngine.LOGGER.info("Node {} unlocked for {}", msg.nodeId, player.getGameProfile().getName()); SkillEngine.LOGGER.info("Node {} unlocked for {}", msg.nodeId, player.getGameProfile().getName());
@@ -1,17 +1,32 @@
package net.fretux.skillengine.skilltree; package net.fretux.skillengine.skilltree;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.skillengine.capability.PlayerSkillData; import net.fretux.skillengine.capability.PlayerSkillData;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import java.util.Map;
public class SkillLogic { public class SkillLogic {
public static boolean canUnlock(PlayerSkillData data, SkillNode node) { public static boolean canUnlock(PlayerSkillData data, Player player, SkillNode node) {
if (data.isUnlocked(node.getId())) return false; if (data.isUnlocked(node.getId())) return false;
if (data.getSkillPoints() < node.getCost()) return false; if (data.getSkillPoints() < node.getCost()) return false;
if (node.isRoot()) return true; if (node.isRoot()) return true;
if (!meetsAscendPrerequisites(player, node)) return false;
for (ResourceLocation neighborId : node.getLinks()) { for (ResourceLocation neighborId : node.getLinks()) {
if (data.isUnlocked(neighborId)) return true; if (data.isUnlocked(neighborId)) return true;
} }
return false; return false;
} }
public static boolean meetsAscendPrerequisites(Player player, SkillNode node) {
return player.getCapability(PlayerStatsProvider.PLAYER_STATS).map(stats -> {
for (Map.Entry<String, Integer> req : node.getPrereqAttributes().entrySet()) {
int current = stats.getAttributeLevel(req.getKey());
if (current < req.getValue()) return false;
}
return true;
}).orElse(false);
}
} }
@@ -4,6 +4,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import java.util.List; import java.util.List;
import java.util.Map;
public class SkillNode { public class SkillNode {
@@ -15,6 +16,8 @@ public class SkillNode {
private final float y; private final float y;
private final List<ResourceLocation> links; private final List<ResourceLocation> links;
private final List<ResourceLocation> tags; private final List<ResourceLocation> tags;
private final ResourceLocation icons;
private final Map<String, Integer> prereqAttributes;
public SkillNode(ResourceLocation id, public SkillNode(ResourceLocation id,
Component title, Component title,
@@ -22,7 +25,9 @@ public class SkillNode {
int cost, int cost,
float x, float y, float x, float y,
List<ResourceLocation> links, List<ResourceLocation> links,
List<ResourceLocation> tags) { List<ResourceLocation> tags,
ResourceLocation icons,
Map<String, Integer> prereqAttributes) {
this.id = id; this.id = id;
this.title = title; this.title = title;
@@ -32,18 +37,51 @@ public class SkillNode {
this.y = y; this.y = y;
this.links = List.copyOf(links); this.links = List.copyOf(links);
this.tags = List.copyOf(tags); this.tags = List.copyOf(tags);
this.icons = icons;
this.prereqAttributes = Map.copyOf(prereqAttributes);
} }
public ResourceLocation getId() { return id; } public ResourceLocation getId() {
public Component getTitle() { return title; } return id;
public Component getDescription() { return description; } }
public int getCost() { return cost; }
public float getX() { return x; } public Component getTitle() {
public float getY() { return y; } return title;
public List<ResourceLocation> getLinks() { return links; } }
public List<ResourceLocation> getTags() { return tags; }
public Component getDescription() {
return description;
}
public int getCost() {
return cost;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public List<ResourceLocation> getLinks() {
return links;
}
public List<ResourceLocation> getTags() {
return tags;
}
public ResourceLocation getIcons() {
return icons;
}
public Map<String, Integer> getPrereqAttributes() {
return prereqAttributes;
}
public boolean isRoot() { public boolean isRoot() {
return links.isEmpty(); // or a dedicated flag if you want later return links.isEmpty();
} }
} }
@@ -12,6 +12,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -33,29 +34,30 @@ public class SkillNodeLoader extends SimpleJsonResourceReloadListener {
protected void apply(Map<ResourceLocation, JsonElement> jsons, protected void apply(Map<ResourceLocation, JsonElement> jsons,
ResourceManager resourceManager, ResourceManager resourceManager,
ProfilerFiller profiler) { ProfilerFiller profiler) {
SkillEngine.LOGGER.info("Loading skill nodes..."); SkillEngine.LOGGER.info("Loading skill nodes...");
SkillNodeRegistry.clear(); SkillNodeRegistry.clear();
jsons.forEach((id, element) -> { jsons.forEach((id, element) -> {
JsonObject obj = element.getAsJsonObject(); JsonObject obj = element.getAsJsonObject();
String title = obj.get("title").getAsString(); String title = obj.get("title").getAsString();
String description = obj.get("description").getAsString(); String description = obj.get("description").getAsString();
int cost = obj.get("cost").getAsInt(); int cost = obj.get("cost").getAsInt();
JsonObject pos = obj.getAsJsonObject("position"); JsonObject pos = obj.getAsJsonObject("position");
float x = pos.get("x").getAsFloat(); float x = pos.get("x").getAsFloat();
float y = pos.get("y").getAsFloat(); float y = pos.get("y").getAsFloat();
List<ResourceLocation> links = new ArrayList<>(); List<ResourceLocation> links = new ArrayList<>();
obj.getAsJsonArray("links").forEach(e -> obj.getAsJsonArray("links").forEach(e ->
links.add(new ResourceLocation(e.getAsString()))); links.add(new ResourceLocation(e.getAsString())));
List<ResourceLocation> tags = new ArrayList<>(); List<ResourceLocation> tags = new ArrayList<>();
obj.getAsJsonArray("tags").forEach(e -> obj.getAsJsonArray("tags").forEach(e ->
tags.add(new ResourceLocation(e.getAsString()))); tags.add(new ResourceLocation(e.getAsString())));
ResourceLocation icons = new ResourceLocation(obj.get("icons").getAsString());
Map<String, Integer> prereqAttributes = new HashMap<>();
if (obj.has("prerequisites")) {
JsonObject prereqObj = obj.getAsJsonObject("prerequisites");
for (Map.Entry<String, JsonElement> entry : prereqObj.entrySet()) {
prereqAttributes.put(entry.getKey(), entry.getValue().getAsInt());
}
}
SkillNode node = new SkillNode( SkillNode node = new SkillNode(
id, id,
Component.literal(title), Component.literal(title),
@@ -63,11 +65,12 @@ public class SkillNodeLoader extends SimpleJsonResourceReloadListener {
cost, cost,
x, y, x, y,
links, links,
tags tags,
icons,
prereqAttributes
); );
SkillNodeRegistry.put(node); SkillNodeRegistry.put(node);
}); });
SkillEngine.LOGGER.info("Loaded {} skill nodes", SkillNodeRegistry.all().size()); SkillEngine.LOGGER.info("Loaded {} skill nodes", SkillNodeRegistry.all().size());
} }
} }
@@ -1,10 +0,0 @@
{
"title": "Arcane Spark",
"description": "A faint magical glow flickers.\nGrants a minor magic tag.",
"cost": 1,
"position": { "x": 150.0, "y": -50.0 },
"links": ["skillengine:welcome"],
"tags": [
"skillengine:magic_1"
]
}
@@ -1,10 +0,0 @@
{
"title": "Endurance Training",
"description": "Improves stamina regeneration slightly.",
"cost": 1,
"position": { "x": -300.0, "y": -50.0 },
"links": ["skillengine:strength_1"],
"tags": [
"skillengine:endurance"
]
}
@@ -1,10 +0,0 @@
{
"title": "Path of Resilience",
"description": "Your endurance increases.\nA defensive option.",
"cost": 2,
"position": { "x": -100.0, "y": 300.0 },
"links": ["skillengine:wanderer"],
"tags": [
"skillengine:resilience"
]
}
@@ -1,10 +0,0 @@
{
"title": "Path of Speed",
"description": "Your body learns to move quicker.",
"cost": 2,
"position": { "x": 100.0, "y": 300.0 },
"links": ["skillengine:wanderer"],
"tags": [
"skillengine:speed"
]
}
@@ -1,10 +1,12 @@
{ {
"title": "Basic Strength", "title": "Basic Strength",
"description": "You feel your muscles tighten.\nGrants +1 Strength tag.", "description": "You feel your muscles tighten.\nGrants +1 Strength.",
"cost": 1, "cost": 1,
"position": { "x": -150.0, "y": -50.0 }, "position": { "x": -150.0, "y": -50.0 },
"links": ["skillengine:welcome"], "links": ["skillengine:welcome"],
"tags": [ "tags": ["skillengine:strength_1"],
"skillengine:strength_1" "icons": "minecraft:textures/item/iron_sword.png",
] "prerequisites": {
"strength": 20
}
} }
@@ -6,5 +6,9 @@
"links": ["skillengine:welcome"], "links": ["skillengine:welcome"],
"tags": [ "tags": [
"skillengine:wanderer" "skillengine:wanderer"
] ],
"icons": "minecraft:textures/item/leather_boots.png",
"prerequisites": {
"agility": 20
}
} }
@@ -2,10 +2,15 @@
"title": "Welcome to Ascend", "title": "Welcome to Ascend",
"description": "This is your first skill node.\nClick Unlock to test the system.", "description": "This is your first skill node.\nClick Unlock to test the system.",
"cost": 0, "cost": 0,
"position": { "x": 0.0, "y": 0.0 }, "position": {
"x": 0.0,
"y": 0.0
},
"links": [], "links": [],
"tags": [ "tags": [
"skillengine:welcome", "skillengine:welcome",
"skillengine:starter" "skillengine:starter"
] ],
"icons": "minecraft:textures/item/book.png",
"prerequisites": {}
} }