extend ability system to support dynamic slot counts, add skill tree validation, new prerequisites API, and extended mod commands
This commit is contained in:
@@ -35,27 +35,23 @@ public class AbilityBindingScreen extends Screen {
|
||||
protected void init() {
|
||||
int x = (width - PANEL_WIDTH) / 2;
|
||||
int y = (height - PANEL_HEIGHT) / 2;
|
||||
int buttonRowWidth = SLOT_BUTTON_WIDTH * 3 + BUTTON_GAP * 2;
|
||||
int slotCount = SkilltreeClientState.getAbilitySlots().length;
|
||||
int visibleSlots = Math.min(slotCount, 3);
|
||||
int buttonRowWidth = SLOT_BUTTON_WIDTH * visibleSlots + BUTTON_GAP * Math.max(0, visibleSlots - 1);
|
||||
int buttonX = x + (PANEL_WIDTH - buttonRowWidth) / 2;
|
||||
int buttonY = y + PANEL_HEIGHT - 54;
|
||||
int boundSlot = SkilltreeClientState.getSlotOfAbility(ability.getId());
|
||||
Button yBtn = addRenderableWidget(Button.builder(Component.literal("Slot 1"),
|
||||
b -> bind(1)).pos(buttonX, buttonY).size(SLOT_BUTTON_WIDTH, BUTTON_HEIGHT).build());
|
||||
if (boundSlot == 1) {
|
||||
yBtn.active = false;
|
||||
yBtn.setMessage(Component.literal("Bound 1"));
|
||||
}
|
||||
Button xBtn = addRenderableWidget(Button.builder(Component.literal("Slot 2"),
|
||||
b -> bind(2)).pos(buttonX + SLOT_BUTTON_WIDTH + BUTTON_GAP, buttonY).size(SLOT_BUTTON_WIDTH, BUTTON_HEIGHT).build());
|
||||
if (boundSlot == 2) {
|
||||
xBtn.active = false;
|
||||
xBtn.setMessage(Component.literal("Bound 2"));
|
||||
}
|
||||
Button cBtn = addRenderableWidget(Button.builder(Component.literal("Slot 3"),
|
||||
b -> bind(3)).pos(buttonX + (SLOT_BUTTON_WIDTH + BUTTON_GAP) * 2, buttonY).size(SLOT_BUTTON_WIDTH, BUTTON_HEIGHT).build());
|
||||
if (boundSlot == 3) {
|
||||
cBtn.active = false;
|
||||
cBtn.setMessage(Component.literal("Bound 3"));
|
||||
for (int slot = 1; slot <= visibleSlots; slot++) {
|
||||
int currentSlot = slot;
|
||||
Button button = addRenderableWidget(Button.builder(Component.literal("Slot " + slot),
|
||||
b -> bind(currentSlot))
|
||||
.pos(buttonX + (SLOT_BUTTON_WIDTH + BUTTON_GAP) * (slot - 1), buttonY)
|
||||
.size(SLOT_BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
.build());
|
||||
if (boundSlot == slot) {
|
||||
button.active = false;
|
||||
button.setMessage(Component.literal("Bound " + slot));
|
||||
}
|
||||
}
|
||||
addRenderableWidget(Button.builder(Component.literal("Cancel"),
|
||||
b -> onClose()).pos(x + PANEL_WIDTH / 2 - 40, y + PANEL_HEIGHT - 28).size(80, BUTTON_HEIGHT).build());
|
||||
|
||||
@@ -13,7 +13,8 @@ public class AbilityHudOverlay {
|
||||
if (mc.player == null) return;
|
||||
int xStart = event.getGuiGraphics().guiWidth() - 80;
|
||||
int yStart = 10;
|
||||
for (int slot = 1; slot <= 3; slot++) {
|
||||
int slots = SkilltreeClientState.getAbilitySlots().length;
|
||||
for (int slot = 1; slot <= slots; slot++) {
|
||||
var abilityId = SkilltreeClientState.getAbilityInSlot(slot);
|
||||
if (abilityId == null) continue;
|
||||
AbilityNode ability = AbilityNodeRegistry.get(abilityId);
|
||||
|
||||
@@ -4,43 +4,45 @@ import com.mojang.blaze3d.platform.InputConstants;
|
||||
import net.fretux.skillengine.network.PacketHandler;
|
||||
import net.fretux.skillengine.network.ServerboundActivateAbilityPacket;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.InputEvent;
|
||||
import net.minecraftforge.client.settings.KeyConflictContext;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
public class AbilityKeybindHandler {
|
||||
|
||||
public static final KeyMapping ABILITY_1 = new KeyMapping(
|
||||
"key.skillengine.ability1", KeyConflictContext.IN_GAME,
|
||||
InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_Y, "key.categories.gameplay"
|
||||
);
|
||||
public static final int MAX_REGISTERED_ABILITY_SLOTS = 9;
|
||||
public static final KeyMapping[] ABILITY_KEYS = new KeyMapping[MAX_REGISTERED_ABILITY_SLOTS];
|
||||
|
||||
public static final KeyMapping ABILITY_2 = new KeyMapping(
|
||||
"key.skillengine.ability2", KeyConflictContext.IN_GAME,
|
||||
InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_X, "key.categories.gameplay"
|
||||
);
|
||||
|
||||
public static final KeyMapping ABILITY_3 = new KeyMapping(
|
||||
"key.skillengine.ability3", KeyConflictContext.IN_GAME,
|
||||
InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_C, "key.categories.gameplay"
|
||||
);
|
||||
static {
|
||||
for (int slot = 1; slot <= MAX_REGISTERED_ABILITY_SLOTS; slot++) {
|
||||
ABILITY_KEYS[slot - 1] = new KeyMapping(
|
||||
"key.skillengine.ability" + slot,
|
||||
KeyConflictContext.IN_GAME,
|
||||
InputConstants.Type.KEYSYM,
|
||||
defaultKeyForSlot(slot),
|
||||
"key.categories.gameplay"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onInput(InputEvent.Key event) {
|
||||
if (ABILITY_1.consumeClick()) {
|
||||
if (SkilltreeClientState.getClientCooldown(1) <= 0) {
|
||||
PacketHandler.CHANNEL.sendToServer(new ServerboundActivateAbilityPacket(1));
|
||||
}
|
||||
}
|
||||
if (ABILITY_2.consumeClick()) {
|
||||
if (SkilltreeClientState.getClientCooldown(2) <= 0) {
|
||||
PacketHandler.CHANNEL.sendToServer(new ServerboundActivateAbilityPacket(2));
|
||||
}
|
||||
}
|
||||
if (ABILITY_3.consumeClick()) {
|
||||
if (SkilltreeClientState.getClientCooldown(3) <= 0) {
|
||||
PacketHandler.CHANNEL.sendToServer(new ServerboundActivateAbilityPacket(3));
|
||||
int activeSlots = SkilltreeClientState.getAbilitySlots().length;
|
||||
int handledSlots = Math.min(activeSlots, ABILITY_KEYS.length);
|
||||
for (int slot = 1; slot <= handledSlots; slot++) {
|
||||
if (ABILITY_KEYS[slot - 1].consumeClick()) {
|
||||
if (SkilltreeClientState.getClientCooldown(slot) <= 0) {
|
||||
PacketHandler.CHANNEL.sendToServer(new ServerboundActivateAbilityPacket(slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int defaultKeyForSlot(int slot) {
|
||||
return switch (slot) {
|
||||
case 1 -> GLFW.GLFW_KEY_Z;
|
||||
case 2 -> GLFW.GLFW_KEY_X;
|
||||
case 3 -> GLFW.GLFW_KEY_C;
|
||||
default -> InputConstants.UNKNOWN.getValue();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AbilitySlotsScreen extends Screen {
|
||||
private static final int PANEL_WIDTH = 260;
|
||||
private static final int PANEL_HEIGHT = 180;
|
||||
private static final int MIN_PANEL_HEIGHT = 180;
|
||||
private static final int SLOT_TEXT_RIGHT_PADDING = 8;
|
||||
private static final int REBIND_BUTTON_WIDTH = 80;
|
||||
public AbilitySlotsScreen() {
|
||||
@@ -22,21 +22,19 @@ public class AbilitySlotsScreen extends Screen {
|
||||
@Override
|
||||
protected void init() {
|
||||
int x = (width - PANEL_WIDTH) / 2;
|
||||
int y = (height - PANEL_HEIGHT) / 2;
|
||||
int panelHeight = panelHeight();
|
||||
int y = (height - panelHeight) / 2;
|
||||
int rowY = y + 40;
|
||||
int buttonH = 20;
|
||||
int buttonX = x + PANEL_WIDTH - REBIND_BUTTON_WIDTH - 15;
|
||||
// Y slot = 1
|
||||
addRenderableWidget(Button.builder(Component.literal("Rebind"), b -> openSelect(1))
|
||||
.pos(buttonX, rowY).size(REBIND_BUTTON_WIDTH, buttonH).build());
|
||||
// X slot = 2
|
||||
addRenderableWidget(Button.builder(Component.literal("Rebind"), b -> openSelect(2))
|
||||
.pos(buttonX, rowY + 35).size(REBIND_BUTTON_WIDTH, buttonH).build());
|
||||
// C slot = 3
|
||||
addRenderableWidget(Button.builder(Component.literal("Rebind"), b -> openSelect(3))
|
||||
.pos(buttonX, rowY + 70).size(REBIND_BUTTON_WIDTH, buttonH).build());
|
||||
int slots = SkilltreeClientState.getAbilitySlots().length;
|
||||
for (int slot = 1; slot <= slots; slot++) {
|
||||
int currentSlot = slot;
|
||||
addRenderableWidget(Button.builder(Component.literal("Rebind"), b -> openSelect(currentSlot))
|
||||
.pos(buttonX, rowY + (slot - 1) * 35).size(REBIND_BUTTON_WIDTH, buttonH).build());
|
||||
}
|
||||
addRenderableWidget(Button.builder(Component.literal("Close"), b -> onClose())
|
||||
.pos(x + PANEL_WIDTH - 70, y + PANEL_HEIGHT - 28).size(60, 20).build());
|
||||
.pos(x + PANEL_WIDTH - 70, y + panelHeight - 28).size(60, 20).build());
|
||||
}
|
||||
|
||||
private void openSelect(int slot) {
|
||||
@@ -57,14 +55,16 @@ public class AbilitySlotsScreen extends Screen {
|
||||
public void render(@NotNull GuiGraphics gfx, int mouseX, int mouseY, float partialTick) {
|
||||
this.renderBackground(gfx);
|
||||
int x = (width - PANEL_WIDTH) / 2;
|
||||
int y = (height - PANEL_HEIGHT) / 2;
|
||||
int panelHeight = panelHeight();
|
||||
int y = (height - panelHeight) / 2;
|
||||
gfx.fill(0, 0, width, height, 0xAA000000);
|
||||
gfx.fill(x, y, x + PANEL_WIDTH, y + PANEL_HEIGHT, 0xFF222222);
|
||||
gfx.fill(x, y, x + PANEL_WIDTH, y + panelHeight, 0xFF222222);
|
||||
gfx.drawCenteredString(font, Component.literal("Ability Slots"), x + PANEL_WIDTH / 2, y + 12, 0xFFFFFF);
|
||||
int rowY = y + 40;
|
||||
drawSlotRow(gfx, x + 15, rowY, 1, "Y");
|
||||
drawSlotRow(gfx, x + 15, rowY + 35, 2, "X");
|
||||
drawSlotRow(gfx, x + 15, rowY + 70, 3, "C");
|
||||
int slots = SkilltreeClientState.getAbilitySlots().length;
|
||||
for (int slot = 1; slot <= slots; slot++) {
|
||||
drawSlotRow(gfx, x + 15, rowY + (slot - 1) * 35, slot, "Slot " + slot);
|
||||
}
|
||||
super.render(gfx, mouseX, mouseY, partialTick);
|
||||
}
|
||||
|
||||
@@ -106,4 +106,9 @@ public class AbilitySlotsScreen extends Screen {
|
||||
}
|
||||
return font.plainSubstrByWidth(title, availableWidth - suffixWidth) + suffix;
|
||||
}
|
||||
|
||||
private int panelHeight() {
|
||||
int slots = SkilltreeClientState.getAbilitySlots().length;
|
||||
return Math.max(MIN_PANEL_HEIGHT, 80 + slots * 35);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import net.minecraftforge.event.TickEvent;
|
||||
public class ClientCooldownTicker {
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int cd = SkilltreeClientState.getClientCooldown(i + 1);
|
||||
int slots = SkilltreeClientState.getAbilitySlots().length;
|
||||
for (int slot = 1; slot <= slots; slot++) {
|
||||
int cd = SkilltreeClientState.getClientCooldown(slot);
|
||||
if (cd > 0) {
|
||||
SkilltreeClientState.updateCooldown(i + 1, cd - 1);
|
||||
SkilltreeClientState.updateCooldown(slot, cd - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ public class ClientKeybinds {
|
||||
);
|
||||
event.register(OPEN_ABILITIES);
|
||||
|
||||
event.register(AbilityKeybindHandler.ABILITY_1);
|
||||
event.register(AbilityKeybindHandler.ABILITY_2);
|
||||
event.register(AbilityKeybindHandler.ABILITY_3);
|
||||
for (KeyMapping keyMapping : AbilityKeybindHandler.ABILITY_KEYS) {
|
||||
event.register(keyMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,18 @@ public class SkilltreeClientState {
|
||||
private static final Set<ResourceLocation> unlocked = new HashSet<>();
|
||||
private static final Set<ResourceLocation> unlockedAbilities = new HashSet<>();
|
||||
private static int currentSkillPoints = 0;
|
||||
private static final ResourceLocation[] abilitySlots = new ResourceLocation[3];
|
||||
private static final int[] clientCooldowns = new int[3];
|
||||
private static ResourceLocation[] abilitySlots = new ResourceLocation[3];
|
||||
private static int[] clientCooldowns = new int[3];
|
||||
|
||||
public static void updateCooldown(int slot, int cd) {
|
||||
ensureSlot(slot);
|
||||
int index = slot - 1;
|
||||
if (index < 0 || index >= clientCooldowns.length) return;
|
||||
clientCooldowns[index] = Math.max(0, cd);
|
||||
}
|
||||
|
||||
public static int getClientCooldown(int slot) {
|
||||
ensureSlot(slot);
|
||||
int index = slot - 1;
|
||||
if (index < 0 || index >= clientCooldowns.length) return 0;
|
||||
return clientCooldowns[index];
|
||||
@@ -74,16 +76,20 @@ public class SkilltreeClientState {
|
||||
}
|
||||
|
||||
public static void bindAbilityLocal(int slot, ResourceLocation abilityId) {
|
||||
ensureSlot(slot);
|
||||
PlayerSkillData.bindHelper(slot, abilityId, abilitySlots);
|
||||
}
|
||||
|
||||
public static void setAbilitySlots(ResourceLocation[] slots) {
|
||||
abilitySlots = new ResourceLocation[slots != null ? slots.length : 3];
|
||||
clientCooldowns = Arrays.copyOf(clientCooldowns, abilitySlots.length);
|
||||
for (int i = 0; i < abilitySlots.length; i++) {
|
||||
abilitySlots[i] = (slots != null && i < slots.length) ? slots[i] : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceLocation getAbilityInSlot(int slot) {
|
||||
ensureSlot(slot);
|
||||
int index = slot - 1;
|
||||
if (index < 0 || index >= abilitySlots.length) return null;
|
||||
return abilitySlots[index];
|
||||
@@ -100,4 +106,10 @@ public class SkilltreeClientState {
|
||||
public static ResourceLocation[] getAbilitySlots() {
|
||||
return Arrays.copyOf(abilitySlots, abilitySlots.length);
|
||||
}
|
||||
|
||||
private static void ensureSlot(int slot) {
|
||||
if (slot <= abilitySlots.length) return;
|
||||
abilitySlots = Arrays.copyOf(abilitySlots, slot);
|
||||
clientCooldowns = Arrays.copyOf(clientCooldowns, slot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public class SkilltreeScreen extends Screen {
|
||||
private SkillNode selectedNode = null;
|
||||
private AbilityNode hoveredAbility = null;
|
||||
private AbilityNode selectedAbility = null;
|
||||
private ResourceLocation activeTree = null;
|
||||
|
||||
public SkilltreeScreen() {
|
||||
super(Component.literal("Ascend Skilltree"));
|
||||
@@ -46,6 +47,11 @@ public class SkilltreeScreen extends Screen {
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
if (activeTree == null || !SkillNodeRegistry.trees().contains(activeTree)) {
|
||||
List<ResourceLocation> trees = SkillNodeRegistry.trees();
|
||||
activeTree = trees.isEmpty() ? null : trees.get(0);
|
||||
}
|
||||
rebuildTreeTabs();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,7 +147,7 @@ public class SkilltreeScreen extends Screen {
|
||||
}
|
||||
|
||||
private void drawGraph(GuiGraphics gfx) {
|
||||
for (SkillNode node : SkillNodeRegistry.all()) {
|
||||
for (SkillNode node : activeSkillNodes()) {
|
||||
int[] p1 = worldToScreen(node.getX(), node.getY());
|
||||
for (ResourceLocation neighborId : node.getLinks()) {
|
||||
SkillNode neighbor = SkillNodeRegistry.get(neighborId);
|
||||
@@ -193,7 +199,7 @@ public class SkilltreeScreen extends Screen {
|
||||
gfx.blit(icon, pos[0] - 8, pos[1] - 8, 0, 0, 16, 16, 16, 16);
|
||||
}
|
||||
}
|
||||
for (SkillNode node : SkillNodeRegistry.all()) {
|
||||
for (SkillNode node : activeSkillNodes()) {
|
||||
int[] pos = worldToScreen(node.getX(), node.getY());
|
||||
int color = getNodeColor(node);
|
||||
gfx.fill(
|
||||
@@ -364,6 +370,7 @@ public class SkilltreeScreen extends Screen {
|
||||
);
|
||||
selectedNode = null;
|
||||
clearWidgets();
|
||||
rebuildTreeTabs();
|
||||
}).pos(x + OVERLAY_WIDTH / 2 - 40, y + OVERLAY_CONTENT_HEIGHT - 20)
|
||||
.size(80, 20)
|
||||
.build());
|
||||
@@ -371,6 +378,7 @@ public class SkilltreeScreen extends Screen {
|
||||
addRenderableWidget(Button.builder(Component.literal("Close"), btn -> {
|
||||
selectedNode = null;
|
||||
clearWidgets();
|
||||
rebuildTreeTabs();
|
||||
}).pos(x + OVERLAY_WIDTH - 60, y - 5)
|
||||
.size(50, 20)
|
||||
.build());
|
||||
@@ -391,6 +399,7 @@ public class SkilltreeScreen extends Screen {
|
||||
);
|
||||
selectedAbility = null;
|
||||
clearWidgets();
|
||||
rebuildTreeTabs();
|
||||
}).pos(x + OVERLAY_WIDTH / 2 - 40, y + OVERLAY_CONTENT_HEIGHT - 20)
|
||||
.size(80, 20)
|
||||
.build());
|
||||
@@ -398,6 +407,7 @@ public class SkilltreeScreen extends Screen {
|
||||
addRenderableWidget(Button.builder(Component.literal("Close"), btn -> {
|
||||
selectedAbility = null;
|
||||
clearWidgets();
|
||||
rebuildTreeTabs();
|
||||
}).pos(x + OVERLAY_WIDTH - 60, y - 5)
|
||||
.size(50, 20)
|
||||
.build());
|
||||
@@ -410,7 +420,7 @@ public class SkilltreeScreen extends Screen {
|
||||
}
|
||||
|
||||
private SkillNode findNodeAt(double mouseX, double mouseY) {
|
||||
for (SkillNode node : SkillNodeRegistry.all()) {
|
||||
for (SkillNode node : activeSkillNodes()) {
|
||||
int[] pos = worldToScreen(node.getX(), node.getY());
|
||||
if (isPointInCircle(mouseX, mouseY, pos[0], pos[1], NODE_RADIUS)) {
|
||||
return node;
|
||||
@@ -483,8 +493,37 @@ public class SkilltreeScreen extends Screen {
|
||||
gfx.pose().popPose();
|
||||
}
|
||||
|
||||
private Iterable<SkillNode> activeSkillNodes() {
|
||||
return activeTree != null ? SkillNodeRegistry.byTree(activeTree) : SkillNodeRegistry.all();
|
||||
}
|
||||
|
||||
private void rebuildTreeTabs() {
|
||||
List<ResourceLocation> trees = SkillNodeRegistry.trees();
|
||||
if (trees.size() <= 1 || selectedNode != null || selectedAbility != null) {
|
||||
return;
|
||||
}
|
||||
int x = 10;
|
||||
int y = 28;
|
||||
for (ResourceLocation tree : trees) {
|
||||
String label = tree.toString();
|
||||
int buttonWidth = Math.min(140, Math.max(60, font.width(label) + 16));
|
||||
Button button = addRenderableWidget(Button.builder(Component.literal(label), btn -> {
|
||||
activeTree = tree;
|
||||
selectedNode = null;
|
||||
selectedAbility = null;
|
||||
clearWidgets();
|
||||
rebuildTreeTabs();
|
||||
})
|
||||
.pos(x, y)
|
||||
.size(buttonWidth, 20)
|
||||
.build());
|
||||
button.active = !tree.equals(activeTree);
|
||||
x += buttonWidth + 4;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPauseScreen() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user