diff --git a/src/client/java/com/ottohg/ultcraft/UltcraftClient.java b/src/client/java/com/ottohg/ultcraft/UltcraftClient.java index 86d2ed5..7a99a83 100644 --- a/src/client/java/com/ottohg/ultcraft/UltcraftClient.java +++ b/src/client/java/com/ottohg/ultcraft/UltcraftClient.java @@ -38,6 +38,10 @@ public class UltcraftClient implements ClientModInitializer { Ultcraft.LOGGER.info("Ultimate activation requested!"); } } + + while (UltimateKeybinding.switchUltimateMenuKey.consumeClick()) { + client.setScreen(new com.ottohg.ultcraft.client.UltimateSelectionScreen()); + } }); Ultcraft.LOGGER.info("Ultcraft Client initialized successfully!"); diff --git a/src/client/java/com/ottohg/ultcraft/client/ClientUltimateData.java b/src/client/java/com/ottohg/ultcraft/client/ClientUltimateData.java index e3ff66c..38711d8 100644 --- a/src/client/java/com/ottohg/ultcraft/client/ClientUltimateData.java +++ b/src/client/java/com/ottohg/ultcraft/client/ClientUltimateData.java @@ -7,8 +7,12 @@ package com.ottohg.ultcraft.client; public class ClientUltimateData { private static float targetCharge = 0.0f; private static float displayedCharge = 0.0f; + + // Animation state private static long lastAnimationTime = 0; - private static boolean isAnimatingReset = false; + private static boolean isAnimating = false; + private static float animationStartCharge = 0.0f; + private static float animationTargetCharge = 0.0f; private static final float RESET_DURATION = 500.0f; // 0.5 seconds public static float getCharge() { @@ -16,17 +20,17 @@ public class ClientUltimateData { } public static float getDisplayCharge() { - if (isAnimatingReset) { + if (isAnimating) { long currentTime = System.currentTimeMillis(); float timeDelta = currentTime - lastAnimationTime; float progress = timeDelta / RESET_DURATION; if (progress >= 1.0f) { - displayedCharge = targetCharge; - isAnimatingReset = false; + displayedCharge = animationTargetCharge; + isAnimating = false; } else { - // Lerp from 100 to 0 - displayedCharge = 100.0f * (1.0f - progress); + // Lerp from start to target + displayedCharge = animationStartCharge + (animationTargetCharge - animationStartCharge) * progress; } } else { displayedCharge = targetCharge; @@ -37,11 +41,16 @@ public class ClientUltimateData { public static void setCharge(float charge) { float newCharge = Math.min(charge, 100.0f); - // Check if we should trigger reset animation (high charge -> low charge) - // Relaxed condition to catch cases where passive charge might have ticked once - if (targetCharge > 90.0f && newCharge < 10.0f) { - isAnimatingReset = true; + // Check for significant drops that should be animated + // 1. Reset: > 90 -> < 10 + // 2. Switch penalty: > 15 -> ~15 + if ((targetCharge > 90.0f && newCharge < 10.0f) || + (targetCharge > 15.1f && Math.abs(newCharge - 15.0f) < 0.1f)) { + + isAnimating = true; lastAnimationTime = System.currentTimeMillis(); + animationStartCharge = displayedCharge; // Start from current visual position (usually 100 or current) + animationTargetCharge = newCharge; } targetCharge = newCharge; diff --git a/src/client/java/com/ottohg/ultcraft/client/UltimateKeybinding.java b/src/client/java/com/ottohg/ultcraft/client/UltimateKeybinding.java index bd93492..94062f1 100644 --- a/src/client/java/com/ottohg/ultcraft/client/UltimateKeybinding.java +++ b/src/client/java/com/ottohg/ultcraft/client/UltimateKeybinding.java @@ -11,6 +11,7 @@ import org.lwjgl.glfw.GLFW; */ public class UltimateKeybinding { public static KeyMapping activateUltimateKey; + public static KeyMapping switchUltimateMenuKey; public static final KeyMapping.Category ULTIMATE_CATEGORY = KeyMapping.Category.register(Identifier.fromNamespaceAndPath("ultcraft", "ultimate")); public static void register() { @@ -20,5 +21,12 @@ public class UltimateKeybinding { GLFW.GLFW_KEY_Z, // Default to 'Z' key ULTIMATE_CATEGORY // Category )); + + switchUltimateMenuKey = KeyBindingHelper.registerKeyBinding(new KeyMapping( + "key.ultcraft.switch_ultimate_menu", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_H, + ULTIMATE_CATEGORY + )); } } diff --git a/src/client/java/com/ottohg/ultcraft/client/UltimateSelectionScreen.java b/src/client/java/com/ottohg/ultcraft/client/UltimateSelectionScreen.java new file mode 100644 index 0000000..8549671 --- /dev/null +++ b/src/client/java/com/ottohg/ultcraft/client/UltimateSelectionScreen.java @@ -0,0 +1,46 @@ +package com.ottohg.ultcraft.client; + +import com.ottohg.ultcraft.UltimateType; +import com.ottohg.ultcraft.network.UltimateSwitchPacket; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class UltimateSelectionScreen extends Screen { + public UltimateSelectionScreen() { + super(Component.literal("Select Ultimate")); + } + + @Override + protected void init() { + super.init(); + + int centerX = this.width / 2; + int centerY = this.height / 2; + + this.addRenderableWidget(Button.builder(UltimateType.ARMED_AND_DANGEROUS.getDisplayName(), button -> { + selectUltimate(UltimateType.ARMED_AND_DANGEROUS); + }) + .bounds(centerX - 100, centerY - 25, 200, 20).build()); + + this.addRenderableWidget(Button.builder(UltimateType.LIKE_A_RABBIT.getDisplayName(), button -> { + selectUltimate(UltimateType.LIKE_A_RABBIT); + }) + .bounds(centerX - 100, centerY + 5, 200, 20).build()); + } + + private void selectUltimate(UltimateType type) { + ClientPlayNetworking.send(new UltimateSwitchPacket(type.getId())); + this.onClose(); + } + + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + // Draw semi-transparent background manually to avoid overlapping blur issues + guiGraphics.fill(0, 0, this.width, this.height, 0x90000000); + guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, this.height / 2 - 50, 0xFFFFFF); + super.render(guiGraphics, mouseX, mouseY, partialTick); + } +} diff --git a/src/main/java/com/ottohg/ultcraft/Ultcraft.java b/src/main/java/com/ottohg/ultcraft/Ultcraft.java index b7f22cc..f27cebe 100644 --- a/src/main/java/com/ottohg/ultcraft/Ultcraft.java +++ b/src/main/java/com/ottohg/ultcraft/Ultcraft.java @@ -1,6 +1,7 @@ package com.ottohg.ultcraft; import com.ottohg.ultcraft.network.UltimateActivatePacket; +import com.ottohg.ultcraft.network.UltimateSwitchPacket; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; @@ -20,10 +21,12 @@ public class Ultcraft implements ModInitializer { // Register network packets PayloadTypeRegistry.playC2S().register(UltimateActivatePacket.TYPE, UltimateActivatePacket.CODEC); + PayloadTypeRegistry.playC2S().register(UltimateSwitchPacket.TYPE, UltimateSwitchPacket.CODEC); PayloadTypeRegistry.playS2C().register(com.ottohg.ultcraft.network.UltimateSyncPacket.TYPE, com.ottohg.ultcraft.network.UltimateSyncPacket.CODEC); UltimateActivatePacket.register(); + ServerPlayNetworking.registerGlobalReceiver(UltimateSwitchPacket.TYPE, UltimateSwitchPacket::handle); // Register server tick event for passive charging ServerTickEvents.END_SERVER_TICK.register(server -> { diff --git a/src/main/java/com/ottohg/ultcraft/UltimateData.java b/src/main/java/com/ottohg/ultcraft/UltimateData.java index f92f9ae..310953d 100644 --- a/src/main/java/com/ottohg/ultcraft/UltimateData.java +++ b/src/main/java/com/ottohg/ultcraft/UltimateData.java @@ -21,6 +21,7 @@ import java.util.UUID; public class UltimateData { private static final Map ultimateCharges = new HashMap<>(); private static final Map lastTickTimes = new HashMap<>(); + private static final Map selectedUltimates = new HashMap<>(); // Constants private static final float PASSIVE_CHARGE_PER_MINUTE = 100.0f / 15.0f; // 100% over 15 minutes @@ -77,29 +78,67 @@ public class UltimateData { return false; } - // Apply effects: Strength 2 and Regeneration 2 for 5 seconds - Holder strength = BuiltInRegistries.MOB_EFFECT.wrapAsHolder( - BuiltInRegistries.MOB_EFFECT.getValue(Identifier.fromNamespaceAndPath("minecraft", "strength")) - ); - Holder regeneration = BuiltInRegistries.MOB_EFFECT.wrapAsHolder( - BuiltInRegistries.MOB_EFFECT.getValue(Identifier.fromNamespaceAndPath("minecraft", "regeneration")) - ); + UltimateType type = getUltimateType(player.getUUID()); - player.addEffect(new MobEffectInstance(strength, 200, 1)); - player.addEffect(new MobEffectInstance(regeneration, 200, 1)); + if (type == UltimateType.ARMED_AND_DANGEROUS) { + // Apply effects: Strength 2 and Regeneration 2 for 10 seconds + Holder strength = BuiltInRegistries.MOB_EFFECT.wrapAsHolder( + BuiltInRegistries.MOB_EFFECT.getValue(Identifier.fromNamespaceAndPath("minecraft", "strength")) + ); + Holder regeneration = BuiltInRegistries.MOB_EFFECT.wrapAsHolder( + BuiltInRegistries.MOB_EFFECT.getValue(Identifier.fromNamespaceAndPath("minecraft", "regeneration")) + ); + + player.addEffect(new MobEffectInstance(strength, 200, 1)); + player.addEffect(new MobEffectInstance(regeneration, 200, 1)); + } else if (type == UltimateType.LIKE_A_RABBIT) { + // Swiftness 2 and Jump Boost 2 for 15 seconds + Holder speed = BuiltInRegistries.MOB_EFFECT.wrapAsHolder( + BuiltInRegistries.MOB_EFFECT.getValue(Identifier.fromNamespaceAndPath("minecraft", "speed")) + ); + Holder jumpBoost = BuiltInRegistries.MOB_EFFECT.wrapAsHolder( + BuiltInRegistries.MOB_EFFECT.getValue(Identifier.fromNamespaceAndPath("minecraft", "jump_boost")) + ); + + player.addEffect(new MobEffectInstance(speed, 300, 1)); + player.addEffect(new MobEffectInstance(jumpBoost, 300, 1)); + } // Reset charge setCharge(player.getUUID(), 0.0f); - Ultcraft.LOGGER.info("Player {} activated ultimate!", player.getName().getString()); + Ultcraft.LOGGER.info("Player {} activated ultimate: {}", player.getName().getString(), type); return true; } public static void removePlayer(UUID playerId) { ultimateCharges.remove(playerId); lastTickTimes.remove(playerId); + selectedUltimates.remove(playerId); } + public static UltimateType getUltimateType(UUID playerId) { + return selectedUltimates.getOrDefault(playerId, UltimateType.ARMED_AND_DANGEROUS); + } + + public static void switchUltimate(ServerPlayer player, String ultimateId) { + UltimateType newType = UltimateType.fromId(ultimateId); + UltimateType currentType = getUltimateType(player.getUUID()); + + if (newType != currentType) { + selectedUltimates.put(player.getUUID(), newType); + float currentCharge = getCharge(player.getUUID()); + if (currentCharge > 15.0f) { + setCharge(player.getUUID(), 15.0f); + // Sync charge immediately + ServerPlayNetworking.send(player, new UltimateSyncPacket(15.0f)); + } + // You might want to sync the selected ultimate type to the client here as well, + // but for now, we'll assume the client knows what it selected locally or we rely on charge sync. + // If the UI needs to know the server state (e.g. after relog), we'd need another packet. + } + } + public static void saveToNBT(UUID playerId, CompoundTag tag) { tag.putFloat("UltimateCharge", getCharge(playerId)); } diff --git a/src/main/java/com/ottohg/ultcraft/UltimateType.java b/src/main/java/com/ottohg/ultcraft/UltimateType.java new file mode 100644 index 0000000..a7631de --- /dev/null +++ b/src/main/java/com/ottohg/ultcraft/UltimateType.java @@ -0,0 +1,33 @@ +package com.ottohg.ultcraft; + +import net.minecraft.network.chat.Component; + +public enum UltimateType { + ARMED_AND_DANGEROUS("Armed and Dangerous", Component.literal("Armed and Dangerous")), + LIKE_A_RABBIT("Like a Rabbit", Component.literal("Like a Rabbit")); + + private final String id; + private final Component displayName; + + UltimateType(String id, Component displayName) { + this.id = id; + this.displayName = displayName; + } + + public String getId() { + return id; + } + + public Component getDisplayName() { + return displayName; + } + + public static UltimateType fromId(String id) { + for (UltimateType type : values()) { + if (type.id.equals(id)) { + return type; + } + } + return ARMED_AND_DANGEROUS; // Default + } +} diff --git a/src/main/java/com/ottohg/ultcraft/network/UltimateSwitchPacket.java b/src/main/java/com/ottohg/ultcraft/network/UltimateSwitchPacket.java new file mode 100644 index 0000000..dee2687 --- /dev/null +++ b/src/main/java/com/ottohg/ultcraft/network/UltimateSwitchPacket.java @@ -0,0 +1,30 @@ +package com.ottohg.ultcraft.network; + +import com.ottohg.ultcraft.Ultcraft; +import com.ottohg.ultcraft.UltimateData; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.Identifier; + +public record UltimateSwitchPacket(String ultimateId) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = + new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath(Ultcraft.MOD_ID, "switch_ultimate")); + + public static final StreamCodec CODEC = StreamCodec.of( + (buf, packet) -> buf.writeUtf(packet.ultimateId), + (buf) -> new UltimateSwitchPacket(buf.readUtf()) + ); + + @Override + public Type type() { + return TYPE; + } + + public static void handle(UltimateSwitchPacket packet, net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.Context context) { + context.server().execute(() -> { + UltimateData.switchUltimate(context.player(), packet.ultimateId); + }); + } +} diff --git a/src/main/resources/assets/ultcraft/lang/en_us.json b/src/main/resources/assets/ultcraft/lang/en_us.json index 35bee7f..5706cf0 100644 --- a/src/main/resources/assets/ultcraft/lang/en_us.json +++ b/src/main/resources/assets/ultcraft/lang/en_us.json @@ -1,4 +1,5 @@ { "key.ultcraft.activate_ultimate": "Activate Ultimate", + "key.ultcraft.switch_ultimate_menu": "Switch Ultimate Menu", "category.ultcraft.ultimate": "Ultcraft" }