diff --git a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java index 83046258..227cc33d 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java @@ -1,41 +1,40 @@ package com.minelittlepony.unicopia.ability; -import java.util.HashMap; +import java.util.EnumMap; +import java.util.HashSet; import java.util.Map; - -import org.lwjgl.glfw.GLFW; +import java.util.Set; import net.minecraft.util.Identifier; import net.minecraft.util.registry.MutableRegistry; import net.minecraft.util.registry.SimpleRegistry; public interface Abilities { - Map KEYS_CODES = new HashMap<>(); + Map>> BY_SLOT = new EnumMap<>(AbilitySlot.class); MutableRegistry> REGISTRY = new SimpleRegistry<>(); // unicorn / alicorn - Ability TELEPORT = register(new UnicornTeleportAbility(), "teleport", GLFW.GLFW_KEY_O); - Ability CAST = register(new UnicornCastingAbility(), "cast", GLFW.GLFW_KEY_P); + Ability TELEPORT = register(new UnicornTeleportAbility(), "teleport", AbilitySlot.PRIMARY); + Ability CAST = register(new UnicornCastingAbility(), "cast", AbilitySlot.SECONDARY); // earth / alicorn - Ability GROW = register(new EarthPonyGrowAbility(), "grow", GLFW.GLFW_KEY_N); - Ability STOMP = register(new EarthPonyStompAbility(), "stomp", GLFW.GLFW_KEY_M); + Ability STOMP = register(new EarthPonyStompAbility(), "stomp", AbilitySlot.PRIMARY); + Ability GROW = register(new EarthPonyGrowAbility(), "grow", AbilitySlot.SECONDARY); // pegasus / bat / alicorn / changeling - Ability CARRY = register(new CarryAbility(), "carry", GLFW.GLFW_KEY_K); + Ability CARRY = register(new CarryAbility(), "carry", AbilitySlot.PASSIVE); // pegasus / alicorn - Ability CLOUD = register(new PegasusCloudInteractionAbility(), "cloud", GLFW.GLFW_KEY_J); + Ability CLOUD = register(new PegasusCloudInteractionAbility(), "cloud", AbilitySlot.TERTIARY); // changeling - Ability FEED = register(new ChangelingFeedAbility(), "feed", GLFW.GLFW_KEY_O); - Ability TRAP = register(new ChangelingTrapAbility(), "trap", GLFW.GLFW_KEY_L); + Ability DISGUISE = register(new ChangelingDisguiseAbility(), "disguise", AbilitySlot.PRIMARY); + Ability FEED = register(new ChangelingFeedAbility(), "feed", AbilitySlot.SECONDARY); + Ability TRAP = register(new ChangelingTrapAbility(), "trap", AbilitySlot.TERTIARY); - Ability DISGUISE = register(new ChangelingDisguiseAbility(), "disguise", GLFW.GLFW_KEY_P); - - static > T register(T power, String name, int keyCode) { + static > T register(T power, String name, AbilitySlot slot) { Identifier id = new Identifier("unicopia", name); - KEYS_CODES.put(id, keyCode); + BY_SLOT.computeIfAbsent(slot, s -> new HashSet<>()).add(power); return REGISTRY.add(id, power); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java index 140e8712..1378ebab 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java @@ -1,9 +1,12 @@ package com.minelittlepony.unicopia.ability; +import java.util.EnumMap; +import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; +import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.entity.Updatable; import com.minelittlepony.unicopia.entity.player.Pony; @@ -18,15 +21,7 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable { private final Pony player; - /** - * Ticks of warmup before an ability is triggered. - */ - private int warmup; - - /** - * Ticks of cooldown after an ability has been triggered. - */ - private int cooldown; + private final Map stats = new EnumMap<>(AbilitySlot.class); /** * True once the current ability has been triggered. @@ -35,69 +30,90 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable { private Optional> activeAbility = Optional.empty(); + private AbilitySlot activeSlot = AbilitySlot.NONE; + public AbilityDispatcher(Pony player) { this.player = player; } /** - * Returns true if the currrent ability can we swapped out. + * Returns true if the current ability can we swapped out. */ boolean canSwitchStates() { - return !activeAbility.isPresent() || (warmup != 0) || (triggered && cooldown == 0); + return !activeAbility.isPresent() || getStat(getActiveSlot()).canSwitchStates(); } - public void tryUseAbility(Ability power) { - if (canSwitchStates()) { - setAbility(power); + public AbilitySlot getActiveSlot() { + return activeSlot; + } + + public void cancelAbility(AbilitySlot slot) { + if (getActiveSlot() == slot && canSwitchStates()) { + setActiveAbility(slot, null); } } - public void tryClearAbility() { + public void activate(AbilitySlot slot) { if (canSwitchStates()) { - setAbility(null); + getAbility(slot).ifPresent(ability -> setActiveAbility(slot, ability)); } } - protected synchronized void setAbility(Ability power) { + public Stat getStat(AbilitySlot slot) { + return stats.computeIfAbsent(slot, Stat::new); + } + + public Optional> getAbility(AbilitySlot slot) { + Race race = player.getSpecies(); + return Abilities.BY_SLOT.get(slot).stream().filter(a -> a.canUse(race)).findFirst(); + } + + protected synchronized void setActiveAbility(AbilitySlot slot, Ability power) { if (activeAbility.orElse(null) != power) { + activeSlot = slot; triggered = false; activeAbility = Optional.ofNullable(power); - warmup = activeAbility.map(p -> p.getWarmupTime(player)).orElse(0); - cooldown = 0; + Stat stat = getStat(slot); + stat.setWarmup(activeAbility.map(p -> p.getWarmupTime(player)).orElse(0)); + stat.setCooldown(0); } } @Nullable - protected synchronized Optional> getUsableAbility() { + protected synchronized Optional> getActiveAbility() { + Stat stat = getStat(getActiveSlot()); return activeAbility.filter(ability -> { - return (!(ability == null || (triggered && warmup == 0 && cooldown == 0)) && ability.canUse(player.getSpecies())); + return (!(ability == null || (triggered && stat.warmup == 0 && stat.cooldown == 0)) && ability.canUse(player.getSpecies())); }); } - public int getRemainingCooldown() { - return cooldown; - } - @Override public void onUpdate() { - getUsableAbility().ifPresent(this::activate); + getActiveAbility().ifPresent(this::activate); } private void activate(Ability ability) { - if (warmup > 0) { - warmup--; + Stat stat = getStat(getActiveSlot()); + + stats.values().forEach(s -> { + if (s != stat) { + s.idle(); + } + }); + + if (stat.warmup > 0) { + stat.warmup--; System.out.println("warming up"); ability.preApply(player); return; } - if (cooldown > 0) { - cooldown--; + if (stat.tickInactive()) { System.out.println("cooling down"); ability.postApply(player); - if (cooldown <= 0) { - setAbility(null); + if (stat.cooldown <= 0) { + setActiveAbility(AbilitySlot.NONE, null); } return; } @@ -108,7 +124,7 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable { if (ability.canActivate(player.getWorld(), player)) { triggered = true; - cooldown = ability.getCooldownTime(player); + stat.setCooldown(ability.getCooldownTime(player)); if (player.isClientPlayer()) { T data = ability.tryActivate(player); @@ -116,22 +132,28 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable { if (data != null) { Channel.PLAYER_ABILITY.send(new MsgPlayerAbility<>(ability, data)); } else { - cooldown = 0; + stat.setCooldown(0); } } } - if (cooldown <= 0) { - setAbility(null); + if (stat.cooldown <= 0) { + setActiveAbility(AbilitySlot.NONE, null); } } @Override public void toNBT(CompoundTag compound) { compound.putBoolean("triggered", triggered); - compound.putInt("warmup", warmup); - compound.putInt("cooldown", cooldown); - getUsableAbility().ifPresent(ability -> { + if (compound.contains("stats")) { + stats.clear(); + CompoundTag li = compound.getCompound("stats"); + li.getKeys().forEach(key -> { + getStat(AbilitySlot.valueOf(key)).fromNBT(li.getCompound(key)); + }); + } + compound.putInt("activeSlot", activeSlot.ordinal()); + getActiveAbility().ifPresent(ability -> { compound.putString("activeAbility", Abilities.REGISTRY.getId(ability).toString()); }); } @@ -139,8 +161,97 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable { @Override public void fromNBT(CompoundTag compound) { triggered = compound.getBoolean("triggered"); - warmup = compound.getInt("warmup"); - cooldown = compound.getInt("cooldown"); + CompoundTag li = new CompoundTag(); + stats.forEach((key, value) -> li.put(key.name(), value.toNBT())); + compound.put("stats", li); + activeSlot = compound.contains("activeSlot") ? AbilitySlot.values()[compound.getInt("activeSlot")] : activeSlot; activeAbility = Abilities.REGISTRY.getOrEmpty(new Identifier(compound.getString("activeAbility"))); } + + public class Stat implements NbtSerialisable { + + /** + * Ticks of warmup before an ability is triggered. + */ + private int warmup; + private int maxWarmup; + + /** + * Ticks of cooldown after an ability has been triggered. + */ + private int cooldown; + private int maxCooldown; + + public final AbilitySlot slot; + + private Stat(AbilitySlot slot) { + this.slot = slot; + } + + /** + * Returns true if the current ability can we swapped out. + */ + boolean canSwitchStates() { + return (warmup != 0) || (triggered && cooldown == 0); + } + + public int getRemainingCooldown() { + return cooldown; + } + + public float getFillProgress() { + float cooldown = getWarmup(); + if (cooldown <= 0 || cooldown >= 1) { + return getCooldown(); + } + return 1 - cooldown; + } + + public float getCooldown() { + return maxCooldown <= 0 ? 0 : ((float)cooldown / (float)maxCooldown); + } + + public void setCooldown(int value) { + cooldown = value; + maxCooldown = value; + } + + public float getWarmup() { + return maxWarmup <= 0 ? 0 : ((float)warmup / (float)maxWarmup); + } + + public void setWarmup(int value) { + maxWarmup = value; + warmup = value; + } + + public void idle() { + if (warmup > 0) { + warmup--; + } + if (cooldown > 0) { + cooldown--; + } + } + + public boolean tickInactive() { + return cooldown > 0 && cooldown-- > 0; + } + + @Override + public void toNBT(CompoundTag compound) { + compound.putInt("warmup", warmup); + compound.putInt("cooldown", cooldown); + compound.putInt("maxWarmup", maxWarmup); + compound.putInt("maxCooldown", maxCooldown); + } + + @Override + public void fromNBT(CompoundTag compound) { + warmup = compound.getInt("warmup"); + cooldown = compound.getInt("cooldown"); + maxWarmup = compound.getInt("maxWarmup"); + maxCooldown = compound.getInt("maxCooldown"); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/AbilitySlot.java b/src/main/java/com/minelittlepony/unicopia/ability/AbilitySlot.java new file mode 100644 index 00000000..b0bb973a --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/AbilitySlot.java @@ -0,0 +1,13 @@ +package com.minelittlepony.unicopia.ability; + +public enum AbilitySlot { + NONE, + PRIMARY, + SECONDARY, + TERTIARY, + PASSIVE; + + public boolean isPassive() { + return this == PASSIVE; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java index 83006cac..54640ed5 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java @@ -193,7 +193,7 @@ public class EarthPonyStompAbility implements Ability { @Override public void postApply(Pony player) { - int timeDiff = getCooldownTime(player) - player.getAbilities().getRemainingCooldown(); + int timeDiff = getCooldownTime(player) - player.getAbilities().getStat(player.getAbilities().getActiveSlot()).getRemainingCooldown(); if (player.getOwner().getEntityWorld().getTime() % 1 == 0 || timeDiff == 0) { spawnParticleRing(player.getOwner(), timeDiff, 1); diff --git a/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java b/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java index 4944cf8c..9ced3467 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java +++ b/src/main/java/com/minelittlepony/unicopia/client/KeyBindingsHandler.java @@ -1,17 +1,13 @@ package com.minelittlepony.unicopia.client; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; -import com.minelittlepony.unicopia.Race; -import com.minelittlepony.unicopia.ability.Abilities; -import com.minelittlepony.unicopia.ability.Ability; -import com.minelittlepony.unicopia.ability.data.Hit; +import org.lwjgl.glfw.GLFW; + +import com.minelittlepony.unicopia.ability.AbilitySlot; import com.minelittlepony.unicopia.entity.player.Pony; import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding; @@ -24,27 +20,25 @@ import net.minecraft.util.Identifier; class KeyBindingsHandler { private final String KEY_CATEGORY = "unicopia.category.name"; - private final Map>> keyPools = new HashMap<>(); - - private final Set bindings = new HashSet<>(); + private final Map keys = new HashMap<>(); private final Set pressed = new HashSet<>(); - private Collection> getKeyCodePool(KeyBinding keyCode) { - return keyPools.computeIfAbsent(keyCode.getDefaultKeyCode(), i -> new ArrayList<>()); - } - - public void addKeybind(Ability p) { + public KeyBindingsHandler() { KeyBindingRegistry.INSTANCE.addCategory(KEY_CATEGORY); - Identifier id = Abilities.REGISTRY.getId(p); - int code = Abilities.KEYS_CODES.get(id); + addKeybind(GLFW.GLFW_KEY_O, AbilitySlot.PRIMARY); + addKeybind(GLFW.GLFW_KEY_P, AbilitySlot.SECONDARY); + addKeybind(GLFW.GLFW_KEY_L, AbilitySlot.TERTIARY); + } - FabricKeyBinding b = FabricKeyBinding.Builder.create(id, InputUtil.Type.KEYSYM, code, KEY_CATEGORY).build(); + public void addKeybind(int code, AbilitySlot slot) { + KeyBindingRegistry.INSTANCE.addCategory(KEY_CATEGORY); + + FabricKeyBinding b = FabricKeyBinding.Builder.create(new Identifier("unicopia", slot.name().toLowerCase()), InputUtil.Type.KEYSYM, code, KEY_CATEGORY).build(); KeyBindingRegistry.INSTANCE.register(b); - getKeyCodePool(b).add(p); - bindings.add(b); + keys.put(b, slot); } public void tick(MinecraftClient client) { @@ -54,19 +48,20 @@ class KeyBindingsHandler { } Pony iplayer = Pony.of(client.player); - for (KeyBinding i : bindings) { + for (KeyBinding i : keys.keySet()) { + AbilitySlot slot = keys.get(i); + if (slot == AbilitySlot.PRIMARY && client.options.keySneak.isPressed()) { + slot = AbilitySlot.PASSIVE; + } + if (i.isPressed()) { if (pressed.add(i)) { - System.out.println("key press " + i); - Race race = iplayer.getSpecies(); - getKeyCodePool(i).stream() - .filter(power -> power.canUse(race)) - .findFirst() - .ifPresent(iplayer.getAbilities()::tryUseAbility); + System.out.println("Key down " + slot); + iplayer.getAbilities().activate(slot); } } else if (pressed.remove(i)) { - System.out.println("key release " + i); - iplayer.getAbilities().tryClearAbility(); + System.out.println("Key up " + slot); + iplayer.getAbilities().cancelAbility(slot); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java b/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java index 1ac18198..80450867 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java +++ b/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java @@ -4,10 +4,8 @@ import static com.minelittlepony.unicopia.EquinePredicates.PLAYER_UNICORN; import javax.annotation.Nullable; -import com.minelittlepony.common.event.ClientReadyCallback; import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.Race; -import com.minelittlepony.unicopia.ability.Abilities; import com.minelittlepony.unicopia.block.UBlocks; import com.minelittlepony.unicopia.container.SpellbookResultSlot; import com.minelittlepony.unicopia.ducks.Colourful; @@ -45,8 +43,6 @@ public class UnicopiaClient implements ClientModInitializer { URenderers.bootstrap(); ClientTickCallback.EVENT.register(this::tick); - ClientReadyCallback.EVENT.register(client -> Abilities.REGISTRY.stream().forEach(keyboard::addKeybind)); - DefaultTexturesRegistry.getDefaultTextures().add(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, SpellbookResultSlot.EMPTY_GEM_SLOT)); ColorProviderRegistry.ITEM.register((stack, tint) -> { diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java index fea1859f..a4f98402 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java @@ -1,5 +1,8 @@ package com.minelittlepony.unicopia.client.gui; +import com.minelittlepony.unicopia.ability.AbilityDispatcher; +import com.minelittlepony.unicopia.ability.AbilitySlot; +import com.minelittlepony.unicopia.entity.player.Pony; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.MinecraftClient; @@ -14,8 +17,8 @@ public class UHud extends DrawableHelper { public static final Identifier HUD_TEXTURE = new Identifier("unicopia", "textures/gui/hud.png"); - private Slot secondarySlot = new Slot(26, 0); - private Slot tertiarySlot = new Slot(36, 24); + private Slot secondarySlot = new Slot(AbilitySlot.SECONDARY, 26, 0); + private Slot tertiarySlot = new Slot(AbilitySlot.TERTIARY, 36, 24); public void render(InGameHud hud, float tickDelta) { MinecraftClient client = MinecraftClient.getInstance(); @@ -34,55 +37,60 @@ public class UHud extends DrawableHelper { int frameHeight = 54; int frameWidth = 54; + AbilityDispatcher abilities = Pony.of(client.player).getAbilities(); + blit(x, y, 0, 0, frameWidth, frameHeight, 128, 128); // background - float progressPercent = 0.25F; - int progressHeight = (int)(frameHeight * progressPercent); + float progressPercent = abilities.getStat(AbilitySlot.PRIMARY).getFillProgress(); - blit(x, y + (frameHeight - progressHeight), - 61, frameHeight - progressHeight, - frameWidth, progressHeight, 128, 128); // progress + if (progressPercent > 0 && progressPercent < 1) { + int progressHeight = (int)(frameHeight * progressPercent); + + blit(x, y + (frameHeight - progressHeight), + 61, frameHeight - progressHeight, + frameWidth, progressHeight, 128, 128); // progress + } blit(x, y, 0, 54, frameWidth, frameHeight, 128, 128); // frame - - - secondarySlot.render(x, y, 50, 100, tickDelta); - tertiarySlot.render(x, y, 5, 10, tickDelta); + secondarySlot.render(abilities, x, y, tickDelta); + tertiarySlot.render(abilities, x, y, tickDelta); RenderSystem.disableBlend(); RenderSystem.disableAlphaTest(); } - static class Slot { + private final AbilitySlot slot; + private int x; private int y; private float lastCooldown; - public Slot(int x, int y) { + public Slot(AbilitySlot slot, int x, int y) { + this.slot = slot; this.x = x; this.y = y; } - void render(int x, int y, float cooldown, float maxCooldown, float tickDelta) { + void render(AbilityDispatcher abilities, int x, int y, float tickDelta) { x += this.x; y += this.y; - if (cooldown > 0 && maxCooldown > 0 && cooldown < maxCooldown) { + float cooldown = abilities.getStat(slot).getFillProgress(); + + if (cooldown > 0 && cooldown < 1) { float lerpCooldown = MathHelper.lerp(tickDelta, cooldown, lastCooldown); lastCooldown = lerpCooldown; - float cooldownPercent = 1 - lerpCooldown / maxCooldown; - int slotPadding = 4; int slotSize = 15; int progressBottom = y + slotPadding + slotSize; - int progressTop = progressBottom - (int)(15F * cooldownPercent); + int progressTop = progressBottom - (int)(15F * cooldown); fill(x + slotPadding, progressTop, x + slotPadding + slotSize, progressBottom, 0xAAFFFFFF); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index ecb46936..2a07008a 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -4,9 +4,9 @@ import javax.annotation.Nullable; import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.ability.AbilityDispatcher; +import com.minelittlepony.unicopia.ducks.PonyContainer; import com.minelittlepony.unicopia.enchanting.PageOwner; import com.minelittlepony.unicopia.entity.FlightControl; -import com.minelittlepony.unicopia.entity.Ponylike; import com.minelittlepony.unicopia.entity.RaceContainer; import com.minelittlepony.unicopia.magic.Caster; import com.minelittlepony.unicopia.network.Transmittable; @@ -97,9 +97,10 @@ public interface Pony extends Caster, RaceContainer, return InteractionManager.instance().isClientPlayer(getOwner()); } + @SuppressWarnings("unchecked") @Nullable static Pony of(@Nullable PlayerEntity player) { - return Ponylike.of(player); + return player == null ? null : ((PonyContainer)player).get(); } static boolean equal(GameProfile one, GameProfile two) {