diff --git a/src/main/java/net/fretux/skillengine/abilities/AbilityCooldownHandler.java b/src/main/java/net/fretux/skillengine/abilities/AbilityCooldownHandler.java new file mode 100644 index 0000000..0df0610 --- /dev/null +++ b/src/main/java/net/fretux/skillengine/abilities/AbilityCooldownHandler.java @@ -0,0 +1,36 @@ +package net.fretux.skillengine.abilities; + +import net.fretux.skillengine.SkillEngine; +import net.fretux.skillengine.capability.PlayerSkillData; +import net.fretux.skillengine.capability.SkillEngineCapabilities; +import net.fretux.skillengine.network.ClientboundSyncCooldownPacket; +import net.fretux.skillengine.network.PacketHandler; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.network.PacketDistributor; + +@Mod.EventBusSubscriber(modid = SkillEngine.MODID) +public class AbilityCooldownHandler { + private static int syncCounter = 0; + + @SubscribeEvent + public static void onServerTick(TickEvent.PlayerTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + if (event.player.level().isClientSide) return; + event.player.getCapability(SkillEngineCapabilities.PLAYER_SKILLS).ifPresent(data -> { + data.tickCooldowns(); + syncCounter++; + if (syncCounter >= 10) { + syncCounter = 0; + for (int i = 1; i <= 3; i++) { + PacketHandler.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.player), + new ClientboundSyncCooldownPacket(i, data.getCooldown(i)) + ); + } + } + }); + } +} diff --git a/src/main/java/net/fretux/skillengine/capability/PlayerSkillData.java b/src/main/java/net/fretux/skillengine/capability/PlayerSkillData.java index 9a74ebd..d07801e 100644 --- a/src/main/java/net/fretux/skillengine/capability/PlayerSkillData.java +++ b/src/main/java/net/fretux/skillengine/capability/PlayerSkillData.java @@ -18,9 +18,15 @@ public class PlayerSkillData { private final Set unlockedAbilities = new HashSet<>(); private final Set activeTags = new HashSet<>(); private final ResourceLocation[] abilitySlots = new ResourceLocation[3]; + private final int[] abilityCooldowns = new int[3]; - public int getSkillPoints() { return skillPoints; } - public void addSkillPoints(int amount) { skillPoints += amount; } + public int getSkillPoints() { + return skillPoints; + } + + public void addSkillPoints(int amount) { + skillPoints += amount; + } public boolean isUnlocked(ResourceLocation nodeId) { return unlockedNodes.contains(nodeId); @@ -33,7 +39,7 @@ public class PlayerSkillData { skillPoints -= node.getCost(); } } - + public Set getUnlockedNodes() { return unlockedNodes; } @@ -86,6 +92,26 @@ public class PlayerSkillData { return abilitySlots.clone(); } + public void setCooldown(int slot, int ticks) { + int idx = slot - 1; + if (idx < 0 || idx >= abilityCooldowns.length) return; + abilityCooldowns[idx] = ticks; + } + + public int getCooldown(int slot) { + int idx = slot - 1; + if (idx < 0 || idx >= abilityCooldowns.length) return 0; + return abilityCooldowns[idx]; + } + + public void tickCooldowns() { + for (int i = 0; i < abilityCooldowns.length; i++) { + if (abilityCooldowns[i] > 0) { + abilityCooldowns[i]--; + } + } + } + public CompoundTag save() { CompoundTag tag = new CompoundTag(); tag.putInt("SkillPoints", skillPoints); @@ -117,6 +143,13 @@ public class PlayerSkillData { abilityList.add(t); } tag.put("UnlockedAbilities", abilityList); + ListTag cdList = new ListTag(); + for (int cd : abilityCooldowns) { + CompoundTag t = new CompoundTag(); + t.putInt("CD", cd); + cdList.add(t); + } + tag.put("AbilityCooldowns", cdList); return tag; } @@ -127,14 +160,14 @@ public class PlayerSkillData { activeTags.clear(); ListTag nodeList = tag.getList("UnlockedNodes", Tag.TAG_COMPOUND); for (Tag t : nodeList) { - ResourceLocation id = new ResourceLocation(((CompoundTag)t).getString("Id")); + ResourceLocation id = new ResourceLocation(((CompoundTag) t).getString("Id")); unlockedNodes.add(id); SkillNode node = SkillNodeRegistry.get(id); if (node != null) activeTags.addAll(node.getTags()); } ListTag tagList = tag.getList("ActiveTags", Tag.TAG_COMPOUND); for (Tag t : tagList) { - activeTags.add(new ResourceLocation(((CompoundTag)t).getString("Tag"))); + activeTags.add(new ResourceLocation(((CompoundTag) t).getString("Tag"))); } ListTag slots = tag.getList("AbilitySlots", Tag.TAG_COMPOUND); for (int i = 0; i < 3 && i < slots.size(); i++) { @@ -143,10 +176,15 @@ public class PlayerSkillData { } ListTag abilityList = tag.getList("UnlockedAbilities", Tag.TAG_COMPOUND); for (Tag t : abilityList) { - unlockedAbilities.add(new ResourceLocation(((CompoundTag)t).getString("Id"))); + unlockedAbilities.add(new ResourceLocation(((CompoundTag) t).getString("Id"))); + } + ListTag cdList = tag.getList("AbilityCooldowns", Tag.TAG_COMPOUND); + for (int i = 0; i < abilityCooldowns.length && i < cdList.size(); i++) { + CompoundTag t = cdList.getCompound(i); + abilityCooldowns[i] = t.getInt("CD"); } } - + public int getTotalSkillCost() { int total = 0; for (ResourceLocation id : unlockedNodes) { diff --git a/src/main/java/net/fretux/skillengine/client/AbilityHudOverlay.java b/src/main/java/net/fretux/skillengine/client/AbilityHudOverlay.java new file mode 100644 index 0000000..609d393 --- /dev/null +++ b/src/main/java/net/fretux/skillengine/client/AbilityHudOverlay.java @@ -0,0 +1,38 @@ +package net.fretux.skillengine.client; + +import net.fretux.skillengine.skilltree.AbilityNode; +import net.fretux.skillengine.skilltree.AbilityNodeRegistry; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.event.RenderGuiEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber +public class AbilityHudOverlay { + + @SubscribeEvent + public static void render(RenderGuiEvent.Post event) { + Minecraft mc = Minecraft.getInstance(); + if (mc.player == null) return; + int xStart = event.getGuiGraphics().guiWidth() - 80; + int yStart = 10; + for (int slot = 1; slot <= 3; slot++) { + var abilityId = SkilltreeClientState.getAbilityInSlot(slot); + if (abilityId == null) continue; + AbilityNode ability = AbilityNodeRegistry.get(abilityId); + if (ability == null) continue; + int cd = SkilltreeClientState.getClientCooldown(slot); + ResourceLocation icon = ability.getIcon(); + GuiGraphics gfx = event.getGuiGraphics(); + gfx.blit(icon, xStart, yStart, 0, 0, 16, 16, 16, 16); + if (cd > 0) { + String text = String.valueOf(cd / 20); + gfx.drawString(mc.font, text, xStart + 20, yStart + 4, 0xFF5555); + } + gfx.drawString(mc.font, "Ability " + slot, xStart + 40, yStart + 4, 0xFFFFFF); + yStart += 20; + } + } +} diff --git a/src/main/java/net/fretux/skillengine/client/AbilityKeybindHandler.java b/src/main/java/net/fretux/skillengine/client/AbilityKeybindHandler.java new file mode 100644 index 0000000..e349e94 --- /dev/null +++ b/src/main/java/net/fretux/skillengine/client/AbilityKeybindHandler.java @@ -0,0 +1,49 @@ +package net.fretux.skillengine.client; + +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.client.event.InputEvent; +import net.minecraftforge.client.settings.KeyConflictContext; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import org.lwjgl.glfw.GLFW; + +@Mod.EventBusSubscriber +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 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" + ); + + @SubscribeEvent + 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)); + } + } + } +} diff --git a/src/main/java/net/fretux/skillengine/client/ClientCooldownTicker.java b/src/main/java/net/fretux/skillengine/client/ClientCooldownTicker.java new file mode 100644 index 0000000..62e67a8 --- /dev/null +++ b/src/main/java/net/fretux/skillengine/client/ClientCooldownTicker.java @@ -0,0 +1,22 @@ +package net.fretux.skillengine.client; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.fretux.skillengine.SkillEngine; + +@Mod.EventBusSubscriber(modid = SkillEngine.MODID, value = Dist.CLIENT) +public class ClientCooldownTicker { + + @SubscribeEvent + 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); + if (cd > 0) { + SkilltreeClientState.updateCooldown(i + 1, cd - 1); + } + } + } +} diff --git a/src/main/java/net/fretux/skillengine/client/SkilltreeClientState.java b/src/main/java/net/fretux/skillengine/client/SkilltreeClientState.java index bf992bc..0932e02 100644 --- a/src/main/java/net/fretux/skillengine/client/SkilltreeClientState.java +++ b/src/main/java/net/fretux/skillengine/client/SkilltreeClientState.java @@ -17,6 +17,15 @@ public class SkilltreeClientState { private static final Set unlockedAbilities = new HashSet<>(); private static int currentSkillPoints = 0; private static final ResourceLocation[] abilitySlots = new ResourceLocation[3]; + private static final int[] clientCooldowns = new int[3]; + + public static void updateCooldown(int slot, int cd) { + clientCooldowns[slot - 1] = cd; + } + + public static int getClientCooldown(int slot) { + return clientCooldowns[slot - 1]; + } public static void unlockNode(ResourceLocation id) { SkillEngine.LOGGER.info("[CLIENT] Unlocking node: {}", id); diff --git a/src/main/java/net/fretux/skillengine/client/SkilltreeScreen.java b/src/main/java/net/fretux/skillengine/client/SkilltreeScreen.java index a704954..9d42cf1 100644 --- a/src/main/java/net/fretux/skillengine/client/SkilltreeScreen.java +++ b/src/main/java/net/fretux/skillengine/client/SkilltreeScreen.java @@ -246,6 +246,14 @@ public class SkilltreeScreen extends Screen { ); })); } + if (!hoveredNode.getExclusiveWith().isEmpty()) { + tooltip.add(Component.literal("Mutually Exclusive with:") + .withStyle(ChatFormatting.RED)); + for (ResourceLocation ex : hoveredNode.getExclusiveWith()) { + tooltip.add(Component.literal(" - " + ex.toString()) + .withStyle(ChatFormatting.RED)); + } + } gfx.renderComponentTooltip(font, tooltip, mouseX, mouseY); } diff --git a/src/main/java/net/fretux/skillengine/network/ClientboundSyncCooldownPacket.java b/src/main/java/net/fretux/skillengine/network/ClientboundSyncCooldownPacket.java new file mode 100644 index 0000000..8159cc7 --- /dev/null +++ b/src/main/java/net/fretux/skillengine/network/ClientboundSyncCooldownPacket.java @@ -0,0 +1,32 @@ +package net.fretux.skillengine.network; + +import net.fretux.skillengine.client.SkilltreeClientState; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public class ClientboundSyncCooldownPacket { + + private final int slot; + private final int cooldown; + + public ClientboundSyncCooldownPacket(int slot, int cooldown) { + this.slot = slot; + this.cooldown = cooldown; + } + + public static void encode(ClientboundSyncCooldownPacket msg, FriendlyByteBuf buf) { + buf.writeInt(msg.slot); + buf.writeInt(msg.cooldown); + } + + public static ClientboundSyncCooldownPacket decode(FriendlyByteBuf buf) { + return new ClientboundSyncCooldownPacket(buf.readInt(), buf.readInt()); + } + + public static void handle(ClientboundSyncCooldownPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> SkilltreeClientState.updateCooldown(msg.slot, msg.cooldown)); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/java/net/fretux/skillengine/network/PacketHandler.java b/src/main/java/net/fretux/skillengine/network/PacketHandler.java index 91fb883..4fe9b7c 100644 --- a/src/main/java/net/fretux/skillengine/network/PacketHandler.java +++ b/src/main/java/net/fretux/skillengine/network/PacketHandler.java @@ -42,6 +42,16 @@ public class PacketHandler { .decoder(ServerboundBindAbilityPacket::decode) .consumerMainThread(ServerboundBindAbilityPacket::handle) .add(); + CHANNEL.messageBuilder(ClientboundSyncCooldownPacket.class, id++, NetworkDirection.PLAY_TO_CLIENT) + .encoder(ClientboundSyncCooldownPacket::encode) + .decoder(ClientboundSyncCooldownPacket::decode) + .consumerMainThread(ClientboundSyncCooldownPacket::handle) + .add(); + CHANNEL.messageBuilder(ServerboundActivateAbilityPacket.class, id++, NetworkDirection.PLAY_TO_SERVER) + .encoder(ServerboundActivateAbilityPacket::encode) + .decoder(ServerboundActivateAbilityPacket::decode) + .consumerMainThread(ServerboundActivateAbilityPacket::handle) + .add(); } public static void syncSkillsTo(ServerPlayer player) { diff --git a/src/main/java/net/fretux/skillengine/network/ServerboundActivateAbilityPacket.java b/src/main/java/net/fretux/skillengine/network/ServerboundActivateAbilityPacket.java new file mode 100644 index 0000000..cf4294d --- /dev/null +++ b/src/main/java/net/fretux/skillengine/network/ServerboundActivateAbilityPacket.java @@ -0,0 +1,56 @@ +package net.fretux.skillengine.network; + +import net.fretux.skillengine.capability.SkillEngineCapabilities; +import net.fretux.skillengine.skilltree.AbilityNode; +import net.fretux.skillengine.skilltree.AbilityNodeRegistry; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.PacketDistributor; + +import java.util.function.Supplier; + +public class ServerboundActivateAbilityPacket { + + private final int slot; + + public ServerboundActivateAbilityPacket(int slot) { + this.slot = slot; + } + + public static void encode(ServerboundActivateAbilityPacket msg, FriendlyByteBuf buf) { + buf.writeInt(msg.slot); + } + + public static ServerboundActivateAbilityPacket decode(FriendlyByteBuf buf) { + return new ServerboundActivateAbilityPacket(buf.readInt()); + } + + public static void handle(ServerboundActivateAbilityPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + + ServerPlayer player = ctx.get().getSender(); + if (player == null) return; + + player.getCapability(SkillEngineCapabilities.PLAYER_SKILLS).ifPresent(data -> { + + ResourceLocation abilityId = data.getAbilityInSlot(msg.slot); + if (abilityId == null) return; + + AbilityNode ability = AbilityNodeRegistry.get(abilityId); + if (ability == null) return; + + int cd = data.getCooldown(msg.slot); + if (cd > 0) return; + ability.execute(player); + data.setCooldown(msg.slot, ability.getCooldown()); + PacketHandler.CHANNEL.send( + PacketDistributor.PLAYER.with(() -> player), + new ClientboundSyncCooldownPacket(msg.slot, ability.getCooldown()) + ); + }); + }); + ctx.get().setPacketHandled(true); + } +} diff --git a/src/main/java/net/fretux/skillengine/skilltree/AbilityNode.java b/src/main/java/net/fretux/skillengine/skilltree/AbilityNode.java index 0c74e30..0eb35d8 100644 --- a/src/main/java/net/fretux/skillengine/skilltree/AbilityNode.java +++ b/src/main/java/net/fretux/skillengine/skilltree/AbilityNode.java @@ -2,6 +2,7 @@ package net.fretux.skillengine.skilltree; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; import java.util.List; @@ -45,4 +46,8 @@ public class AbilityNode { public List getTags() { return tags; } public ResourceLocation getIcon() { return icon; } public int getCooldown() { return cooldown; } + + public void execute(ServerPlayer player) { + player.sendSystemMessage(Component.literal("Activated ability: " + id)); + } }