diff --git a/pom.xml b/pom.xml index 5aedb503..364fc0d2 100644 --- a/pom.xml +++ b/pom.xml @@ -125,7 +125,7 @@ io.lumine MythicLib-dist - 1.3 + 1.3-R17 provided diff --git a/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java b/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java index babf946e..a10b9048 100644 --- a/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java +++ b/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java @@ -27,12 +27,12 @@ import net.Indyuce.mmocore.api.util.MMOCoreUtils; import net.Indyuce.mmocore.api.util.math.particle.SmallParticleEffect; import net.Indyuce.mmocore.experience.EXPSource; import net.Indyuce.mmocore.experience.PlayerProfessions; -import net.Indyuce.mmocore.manager.SoundManager; import net.Indyuce.mmocore.skill.ClassSkill; import net.Indyuce.mmocore.skill.RegisteredSkill; -import net.Indyuce.mmocore.skill.cast.listener.SkillBar.SkillCasting; +import net.Indyuce.mmocore.skill.cast.SkillCastingHandler; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TextComponent; +import org.apache.commons.lang.Validate; import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.entity.Player; @@ -65,6 +65,7 @@ public class PlayerData extends OfflinePlayerData implements Closable { private double mana, stamina, stellium; private Party party; private Guild guild; + private SkillCastingHandler skillCasting; private final PlayerQuests questData; private final PlayerStats playerStats; @@ -79,7 +80,6 @@ public class PlayerData extends OfflinePlayerData implements Closable { // NON-FINAL player data stuff made public to facilitate field change public int skillGuiDisplayOffset; - public Object skillCasting; public boolean noCooldown; public CombatRunnable combat; @@ -676,6 +676,22 @@ public class PlayerData extends OfflinePlayerData implements Closable { return skillCasting != null; } + public void setSkillCasting(SkillCastingHandler skillCasting) { + Validate.isTrue(!isCasting(), "Player already in casting mode"); + this.skillCasting = skillCasting; + } + + @NotNull + public SkillCastingHandler getSkillCasting() { + return Objects.requireNonNull(skillCasting, "Player not in casting mode"); + } + + public void leaveCastingMode() { + Validate.isTrue(isCasting(), "Player not in casting mode"); + skillCasting.close(); + this.skillCasting = null; + } + public void displayActionBar(String message) { if (!isOnline()) return; @@ -809,7 +825,7 @@ public class PlayerData extends OfflinePlayerData implements Closable { * checks if they could potentially upgrade to one of these * * @return If the player can change its current class to - * a subclass + * a subclass */ public boolean canChooseSubclass() { for (Subclass subclass : getProfess().getSubclasses()) diff --git a/src/main/java/net/Indyuce/mmocore/skill/cast/KeyCombo.java b/src/main/java/net/Indyuce/mmocore/skill/cast/KeyCombo.java new file mode 100644 index 00000000..874d8638 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/skill/cast/KeyCombo.java @@ -0,0 +1,40 @@ +package net.Indyuce.mmocore.skill.cast; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * There is one key combo per skill slot. This means + * that independently of both the player's class and + * the skill bounty to the n-th slot, the key combo to + * perform to cast the n-th skill is always the same + */ +public class KeyCombo { + private final List keys = new ArrayList<>(); + + public int countKeys() { + return keys.size(); + } + + public void registerKey(PlayerKey key) { + keys.add(key); + } + + public PlayerKey getAt(int index) { + return keys.get(index); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KeyCombo keyCombo = (KeyCombo) o; + return Objects.equals(keys, keyCombo.keys); + } + + @Override + public int hashCode() { + return keys.hashCode(); + } +} diff --git a/src/main/java/net/Indyuce/mmocore/skill/cast/PlayerKey.java b/src/main/java/net/Indyuce/mmocore/skill/cast/PlayerKey.java index c470b910..18e08d39 100644 --- a/src/main/java/net/Indyuce/mmocore/skill/cast/PlayerKey.java +++ b/src/main/java/net/Indyuce/mmocore/skill/cast/PlayerKey.java @@ -5,25 +5,40 @@ public enum PlayerKey { /** * When a player left clicks */ - LEFT_CLICK, + LEFT_CLICK(false), /** * When a player right clicks */ - RIGHT_CLICK, + RIGHT_CLICK(false), /** * When a player drops the item they are holding */ - DROP, + DROP(true), /** * When a player swaps their hand items */ - SWAP_HANDS, + SWAP_HANDS(true), /** * When a player sneaks (doesn't trigger when unsneaking) */ - CROUCH; + CROUCH(false); + + private final boolean cancellableEvent; + + private PlayerKey(boolean cancelableEvent) { + this.cancellableEvent = cancelableEvent; + } + + /** + * @return Whether or not the event causing the key press event + * should be cancelled when this key is actually being registered + * as a key combo action. + */ + public boolean shouldCancelEvent() { + return cancellableEvent; + } } diff --git a/src/main/java/net/Indyuce/mmocore/skill/cast/SkillCastingHandler.java b/src/main/java/net/Indyuce/mmocore/skill/cast/SkillCastingHandler.java new file mode 100644 index 00000000..bfdd854d --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/skill/cast/SkillCastingHandler.java @@ -0,0 +1,48 @@ +package net.Indyuce.mmocore.skill.cast; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.scheduler.BukkitRunnable; + +public abstract class SkillCastingHandler extends BukkitRunnable implements Listener { + private final PlayerData caster; + + private boolean open = true; + + public SkillCastingHandler(PlayerData caster, int runnablePeriod) { + this.caster = caster; + + runTaskTimer(MMOCore.plugin, 0, runnablePeriod); + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + public PlayerData getCaster() { + return caster; + } + + public void close() { + Validate.isTrue(open, "Skill casting already ended"); + + open = false; + + // Unregister listeners + HandlerList.unregisterAll(this); + + // Cancel runnable + cancel(); + } + + @Override + public void run() { + if (!caster.isOnline() || caster.getPlayer().isDead()) + caster.leaveCastingMode(); + else + onTick(); + } + + public abstract void onTick(); +} diff --git a/src/main/java/net/Indyuce/mmocore/skill/cast/CastingMethod.java b/src/main/java/net/Indyuce/mmocore/skill/cast/SkillCastingMode.java similarity index 57% rename from src/main/java/net/Indyuce/mmocore/skill/cast/CastingMethod.java rename to src/main/java/net/Indyuce/mmocore/skill/cast/SkillCastingMode.java index a43f15d3..88a0cc39 100644 --- a/src/main/java/net/Indyuce/mmocore/skill/cast/CastingMethod.java +++ b/src/main/java/net/Indyuce/mmocore/skill/cast/SkillCastingMode.java @@ -1,12 +1,14 @@ package net.Indyuce.mmocore.skill.cast; +import net.Indyuce.mmocore.skill.cast.listener.KeyCombos; import net.Indyuce.mmocore.skill.cast.listener.SkillBar; +import net.Indyuce.mmocore.skill.cast.listener.SkillScroller; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.event.Listener; import java.util.function.Function; -public enum CastingMethod { +public enum SkillCastingMode { /** * The first ever casting method to be implemented in MMOCore. @@ -14,14 +16,19 @@ public enum CastingMethod { * When pressing a key, the list of bound skills display on the * action bar */ - SKILL_BAR(config-> new SkillBar(config)), - - SKILL_SCROLL; + SKILL_BAR(config -> new SkillBar(config)), /** - * Initialize your skill combo by pressing some key + * TODO */ - KEY_COMBOS(), + SKILL_SCROLL(config -> new SkillScroller(config)), + + /** + * Initialize your skill combo by pressing some key. + *

+ * Then press a certain amount of keys to + */ + KEY_COMBOS(config -> new KeyCombos(config)), /** * Not implemented yet. @@ -30,7 +37,7 @@ public enum CastingMethod { * a book with all the skills displayed into it and click * some clickable text to cast the skill. */ - SPELL_BOOK(), + // SPELL_BOOK(null), /** * Not implemented yet. @@ -38,11 +45,17 @@ public enum CastingMethod { * Much like the spell book but using a custom GUI instead * of a spell book to display the available skills. */ - SPELL_GUI(); + // SPELL_GUI(null), + + ; private final Function listenerLoader; - CastingMethod(Function listenerLoader) { + SkillCastingMode(Function listenerLoader) { this.listenerLoader = listenerLoader; } + + public Listener loadFromConfig(ConfigurationSection config) { + return listenerLoader.apply(config); + } } diff --git a/src/main/java/net/Indyuce/mmocore/skill/cast/listener/KeyCombos.java b/src/main/java/net/Indyuce/mmocore/skill/cast/listener/KeyCombos.java new file mode 100644 index 00000000..d163d5ff --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/skill/cast/listener/KeyCombos.java @@ -0,0 +1,193 @@ +package net.Indyuce.mmocore.skill.cast.listener; + +import io.lumine.mythic.lib.MythicLib; +import io.lumine.mythic.lib.UtilityMethods; +import io.lumine.mythic.lib.api.player.EquipmentSlot; +import io.lumine.mythic.lib.player.PlayerMetadata; +import io.lumine.mythic.lib.skill.trigger.TriggerMetadata; +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.SoundObject; +import net.Indyuce.mmocore.api.event.PlayerKeyPressEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.skill.cast.KeyCombo; +import net.Indyuce.mmocore.skill.cast.PlayerKey; +import net.Indyuce.mmocore.skill.cast.SkillCastingHandler; +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; + +public class KeyCombos implements Listener { + + /** + * Using instances of KeyCombo as keys work because + * {@link KeyCombo} has a working implementation for the + * hash code method + */ + private final Map combos = new HashMap<>(); + + /** + * Key players need to press to start a combo + */ + private final PlayerKey initializerKey; + private final int longestCombo; + + /** + * Handles the display of the action bar when casting a skill. + * Set to null if disabled + */ + @Nullable + private final ActionBarOptions actionBarOptions; + + @Nullable + private final SoundObject beginComboSound, comboClickSound, failComboSound; + + /** + * Essentially the inverse of the {@link #combos} map. This maps + * the skill slot to the corresponding key combo. There's no problem + * because the maps are 100% bijective + */ + private static final Map PUBLIC_COMBOS = new HashMap<>(); + + public KeyCombos(ConfigurationSection config) { + + int longestCombo = 0; + + // Load different combos + for (String key : config.getConfigurationSection("combos").getKeys(false)) + try { + int spellSlot = Integer.valueOf(key); + Validate.isTrue(spellSlot >= 0, "Spell slot must be at least 0"); + Validate.isTrue(!combos.values().contains(spellSlot), "There is already a key combo with the same skill slot"); + KeyCombo combo = new KeyCombo(); + for (String str : config.getStringList("combos." + key)) + combo.registerKey(PlayerKey.valueOf(UtilityMethods.enumName(str))); + + combos.put(combo, spellSlot); + longestCombo = Math.max(longestCombo, combo.countKeys()); + + PUBLIC_COMBOS.put(spellSlot, combo); + } catch (RuntimeException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load key combo '" + key + "': " + exception.getMessage()); + } + + this.longestCombo = longestCombo; + + // Load player key names + actionBarOptions = config.contains("action-bar") ? new ActionBarOptions(config.getConfigurationSection("action-bar")) : null; + + // Load sounds + beginComboSound = config.contains("sound.begin-combo") ? new SoundObject(config.getConfigurationSection("sound.begin-combo")) : null; + comboClickSound = config.contains("sound.combo-key") ? new SoundObject(config.getConfigurationSection("sound.combo-key")) : null; + failComboSound = config.contains("sound.fail-combo") ? new SoundObject(config.getConfigurationSection("sound.fail-combo")) : null; + + // Find initializer key + initializerKey = PlayerKey.valueOf(UtilityMethods.enumName(Objects.requireNonNull(config.getString("initializer-key"), "Could not find initializer key"))); + } + + @EventHandler + public void whenPressingKey(PlayerKeyPressEvent event) { + PlayerData playerData = event.getData(); + Player player = playerData.getPlayer(); + + if (!event.getData().isCasting()) { + if (event.getPressed() == initializerKey) { + + // Cancel event if necessary + if (event.getPressed().shouldCancelEvent()) + event.setCancelled(true); + + // Start combo + playerData.setSkillCasting(new CustomSkillCastingHandler(playerData)); + if (beginComboSound != null) + beginComboSound.playTo(player); + } + return; + } + + // Adding pressed key + CustomSkillCastingHandler casting = (CustomSkillCastingHandler) playerData.getSkillCasting(); + casting.current.registerKey(event.getPressed()); + casting.onTick(); + if (comboClickSound != null) + comboClickSound.playTo(player); + + // Cancel event if necessary + if (event.getPressed().shouldCancelEvent()) + event.setCancelled(true); + + // Hash current combo and check + if (combos.containsKey(casting.current)) { + int spellSlot = combos.get(casting.current) - 1; + playerData.leaveCastingMode(); + + // Cast spell + if (playerData.hasSkillBound(spellSlot)) { + PlayerMetadata caster = playerData.getMMOPlayerData().getStatMap().cache(EquipmentSlot.MAIN_HAND); + playerData.getBoundSkill(spellSlot).toCastable(playerData).cast(new TriggerMetadata(caster, null, null)); + } + return; + } + + // Check if current combo is too large + if (casting.current.countKeys() >= longestCombo) { + playerData.leaveCastingMode(); + if (failComboSound != null) + failComboSound.playTo(player); + } + } + + private class CustomSkillCastingHandler extends SkillCastingHandler { + private final KeyCombo current = new KeyCombo(); + + CustomSkillCastingHandler(PlayerData caster) { + super(caster, 10); + } + + @Override + public void onTick() { + if (actionBarOptions != null) + getCaster().displayActionBar(actionBarOptions.format(current)); + } + } + + private class ActionBarOptions { + private final String separator, noKey; + + /** + * Saves the names for all the players keys. Used when displaying + * the current player's key combo on the action bar + */ + private final Map keyNames = new HashMap<>(); + + ActionBarOptions(ConfigurationSection config) { + this.separator = Objects.requireNonNull(config.getString("separator"), "Could not find action bar option 'separator'"); + this.noKey = Objects.requireNonNull(config.getString("no-key"), "Could not find action bar option 'no-key'"); + + for (PlayerKey key : PlayerKey.values()) + keyNames.put(key, Objects.requireNonNull(config.getString("key-name." + key.name()), "Could not find translation for key " + key.name())); + } + + public String format(KeyCombo currentCombo) { + + // Join all keys with separator + String builder = currentCombo.countKeys() == 0 ? noKey : keyNames.get(currentCombo.getAt(0)); + int j = 1; + for (; j < currentCombo.countKeys(); j++) + builder += separator + keyNames.get(currentCombo.getAt(j)); + + // All remaining + for (; j < longestCombo; j++) + builder += separator + noKey; + + return MythicLib.plugin.parseColors(builder); + } + } +} diff --git a/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java b/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java index 8190076e..643db2e7 100644 --- a/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java +++ b/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java @@ -4,24 +4,20 @@ import io.lumine.mythic.lib.api.player.EquipmentSlot; import io.lumine.mythic.lib.player.PlayerMetadata; import io.lumine.mythic.lib.skill.trigger.TriggerMetadata; import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.SoundEvent; import net.Indyuce.mmocore.api.event.PlayerKeyPressEvent; import net.Indyuce.mmocore.api.player.PlayerData; -import net.Indyuce.mmocore.listener.event.PlayerPressKeyListener; import net.Indyuce.mmocore.manager.ConfigManager; -import net.Indyuce.mmocore.manager.SoundManager; import net.Indyuce.mmocore.skill.ClassSkill; import net.Indyuce.mmocore.skill.cast.PlayerKey; -import org.bukkit.Bukkit; +import net.Indyuce.mmocore.skill.cast.SkillCastingHandler; import org.bukkit.GameMode; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerItemHeldEvent; import org.bukkit.event.player.PlayerSwapHandItemsEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; import java.util.Objects; @@ -38,7 +34,8 @@ public class SkillBar implements Listener { return; // Always cancel event - event.setCancelled(true); + if (event.getPressed().shouldCancelEvent()) + event.setCancelled(true); // Enter spell casting Player player = event.getData().getPlayer(); @@ -46,15 +43,13 @@ public class SkillBar implements Listener { if (player.getGameMode() != GameMode.SPECTATOR && (MMOCore.plugin.configManager.canCreativeCast || player.getGameMode() != GameMode.CREATIVE) && !playerData.isCasting() - && playerData.getBoundSkills().size() > 0) { - playerData.skillCasting = new SkillCasting(playerData); - MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SPELL_CAST_BEGIN); + && !playerData.getBoundSkills().isEmpty()) { + playerData.setSkillCasting(new CustomSkillCastingHandler(playerData)); + MMOCore.plugin.soundManager.getSound(SoundEvent.SPELL_CAST_BEGIN).playTo(player); } } - public class SkillCasting extends BukkitRunnable implements Listener { - private final PlayerData playerData; - + private class CustomSkillCastingHandler extends SkillCastingHandler { private final String ready = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.ready").message(); private final String onCooldown = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.on-cooldown").message(); private final String noMana = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.no-mana").message(); @@ -62,18 +57,15 @@ public class SkillBar implements Listener { private int j; - public SkillCasting(PlayerData playerData) { - this.playerData = playerData; - - Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); - runTaskTimer(MMOCore.plugin, 0, 1); + CustomSkillCastingHandler(PlayerData playerData) { + super(playerData, 1); } @EventHandler() public void onSkillCast(PlayerItemHeldEvent event) { Player player = event.getPlayer(); - if (!playerData.isOnline()) return; - if (!event.getPlayer().equals(playerData.getPlayer())) + if (!getCaster().isOnline()) return; + if (!event.getPlayer().equals(getCaster().getPlayer())) return; /* @@ -92,9 +84,9 @@ public class SkillBar implements Listener { * cancelling the first one, the player held item slot must go back * to the previous one. */ - if (slot >= 0 && playerData.hasSkillBound(slot)) { - PlayerMetadata caster = playerData.getMMOPlayerData().getStatMap().cache(EquipmentSlot.MAIN_HAND); - playerData.getBoundSkill(slot).toCastable(playerData).cast(new TriggerMetadata(caster, null, null)); + if (slot >= 0 && getCaster().hasSkillBound(slot)) { + PlayerMetadata caster = getCaster().getMMOPlayerData().getStatMap().cache(EquipmentSlot.MAIN_HAND); + getCaster().getBoundSkill(slot).toCastable(getCaster()).cast(new TriggerMetadata(caster, null, null)); } } @@ -104,20 +96,14 @@ public class SkillBar implements Listener { ConfigManager.SwapAction action = player.isSneaking() ? MMOCore.plugin.configManager.sneakingSwapAction : MMOCore.plugin.configManager.normalSwapAction; - if (action != ConfigManager.SwapAction.SPELL_CAST || !playerData.isOnline()) return; - if (event.getPlayer().equals(playerData.getPlayer())) { - MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SPELL_CAST_END); - MMOCore.plugin.configManager.getSimpleMessage("casting.no-longer").send(playerData.getPlayer()); - close(); + if (action != ConfigManager.SwapAction.SPELL_CAST || !getCaster().isOnline()) return; + if (event.getPlayer().equals(getCaster().getPlayer())) { + MMOCore.plugin.soundManager.getSound(SoundEvent.SPELL_CAST_END).playTo(player); + MMOCore.plugin.configManager.getSimpleMessage("casting.no-longer").send(getCaster().getPlayer()); + PlayerData.get(player).leaveCastingMode(); } } - private void close() { - playerData.skillCasting = null; - HandlerList.unregisterAll(this); - cancel(); - } - private String getFormat(PlayerData data) { StringBuilder str = new StringBuilder(); if (!data.isOnline()) return str.toString(); @@ -146,19 +132,14 @@ public class SkillBar implements Listener { } @Override - public void run() { - if (!playerData.isOnline() || playerData.getPlayer().isDead()) { - close(); - return; - } - + public void onTick() { if (j % 20 == 0) - playerData.displayActionBar(getFormat(playerData)); + getCaster().displayActionBar(getFormat(getCaster())); for (int k = 0; k < 2; k++) { double a = (double) j++ / 5; - playerData.getProfess().getCastParticle() - .display(playerData.getPlayer().getLocation().add(Math.cos(a), 1 + Math.sin(a / 3) / 1.3, Math.sin(a))); + getCaster().getProfess().getCastParticle() + .display(getCaster().getPlayer().getLocation().add(Math.cos(a), 1 + Math.sin(a / 3) / 1.3, Math.sin(a))); } } } diff --git a/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillScroller.java b/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillScroller.java new file mode 100644 index 00000000..f9fdd078 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillScroller.java @@ -0,0 +1,135 @@ +package net.Indyuce.mmocore.skill.cast.listener; + +import io.lumine.mythic.lib.UtilityMethods; +import io.lumine.mythic.lib.api.player.EquipmentSlot; +import io.lumine.mythic.lib.player.PlayerMetadata; +import io.lumine.mythic.lib.skill.trigger.TriggerMetadata; +import net.Indyuce.mmocore.api.SoundObject; +import net.Indyuce.mmocore.api.event.PlayerKeyPressEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.skill.cast.PlayerKey; +import net.Indyuce.mmocore.skill.cast.SkillCastingHandler; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerItemHeldEvent; + +import javax.annotation.Nullable; +import java.util.Objects; + +public class SkillScroller implements Listener { + + /** + * Key players need to press to start casting + */ + private final PlayerKey enterKey, castKey; + + @Nullable + private final SoundObject enterSound, changeSound, leaveSound; + + public SkillScroller(ConfigurationSection config) { + + // Load sounds + enterSound = config.contains("sound.enter") ? new SoundObject(config.getConfigurationSection("sound.enter")) : null; + changeSound = config.contains("sound.change") ? new SoundObject(config.getConfigurationSection("sound.change")) : null; + leaveSound = config.contains("sound.leave") ? new SoundObject(config.getConfigurationSection("sound.leave")) : null; + + // Find keybinds + enterKey = PlayerKey.valueOf(UtilityMethods.enumName(Objects.requireNonNull(config.getString("enter-key"), "Could not find enter key"))); + castKey = PlayerKey.valueOf(UtilityMethods.enumName(Objects.requireNonNull(config.getString("cast-key"), "Could not find cast key"))); + } + + @EventHandler + public void whenPressingKey(PlayerKeyPressEvent event) { + PlayerData playerData = event.getData(); + Player player = playerData.getPlayer(); + + if (event.getPressed() == enterKey) { + + // Leave casting mode + if (playerData.isCasting()) { + + // Cancel event if necessary + if (event.getPressed().shouldCancelEvent()) + event.setCancelled(true); + + playerData.leaveCastingMode(); + if (leaveSound != null) + leaveSound.playTo(player); + return; + } + + // Check if there are skills bound + if (playerData.getBoundSkills().isEmpty()) + return; + + // Cancel event if necessary + if (event.getPressed().shouldCancelEvent()) + event.setCancelled(true); + + // Enter casting mode + playerData.setSkillCasting(new CustomSkillCastingHandler(playerData)); + if (enterSound != null) + enterSound.playTo(player); + } + + if (event.getPressed() == castKey && playerData.isCasting()) { + + // Cancel event if necessary + if (event.getPressed().shouldCancelEvent()) + event.setCancelled(true); + + CustomSkillCastingHandler casting = (CustomSkillCastingHandler) playerData.getSkillCasting(); + PlayerMetadata caster = playerData.getMMOPlayerData().getStatMap().cache(EquipmentSlot.MAIN_HAND); + playerData.getBoundSkill(casting.index).toCastable(playerData).cast(new TriggerMetadata(caster, null, null)); + } + } + + @EventHandler + public void onScroll(PlayerItemHeldEvent event) { + PlayerData playerData = PlayerData.get(event.getPlayer()); + if (!playerData.isCasting()) + return; + + if (playerData.getBoundSkills().isEmpty()) { + playerData.leaveCastingMode(); + return; + } + + event.setCancelled(true); + + int previous = event.getPreviousSlot(), current = event.getNewSlot(); + int dist1 = 9 + current - previous, dist2 = current - previous, dist3 = current - previous - 9; + int change = Math.abs(dist1) < Math.abs(dist2) ? (Math.abs(dist1) < Math.abs(dist3) ? dist1 : dist3) : (Math.abs(dist3) < Math.abs(dist2) ? dist3 : dist2); + + // Scroll trough items + CustomSkillCastingHandler casting = (CustomSkillCastingHandler) playerData.getSkillCasting(); + casting.index = mod(casting.index + change, playerData.getBoundSkills().size()); + casting.onTick(); + } + + private int mod(int x, int n) { + + while (x < 0) + x += n; + + while (x >= n) + x -= n; + + return x; + } + + private class CustomSkillCastingHandler extends SkillCastingHandler { + private int index = 0; + + CustomSkillCastingHandler(PlayerData caster) { + super(caster, 10); + } + + @Override + public void onTick() { + getCaster().displayActionBar("CLICK: " + getCaster().getBoundSkill(index).getSkill().getName()); + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2198b14f..a3306b47 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -62,6 +62,47 @@ protect-custom-mine: false # provide the player with experience points or not should-cobblestone-generators-give-exp: false +# Edit how to cast skills here. This part of the config is +# pretty tricky so it's best to read the wiki page before editing anything +skill-casting: + mode: KEY_COMBOS + initializer-key: SWAP_HANDS + sound: + begin-combo: + sound: BLOCK_END_PORTAL_FRAME_FILL + volume: 1 + pitch: 2 + combo-key: + sound: BLOCK_LEVER_CLICK + volume: 1 + pitch: 2 + fail-combo: + sound: BLOCK_FIRE_EXTINGUISH + volume: 1 + pitch: 2 + action-bar: + separator: ' - ' + no-key: '***' + key-name: + LEFT_CLICK: 'LEFT' + RIGHT_CLICK: 'RGHT' + DROP: 'DROP' + SWAP_HANDS: 'SWAP' + CROUCH: 'SHFT' + combos: + '1': + - LEFT_CLICK + - LEFT_CLICK + '2': + - LEFT_CLICK + - RIGHT_CLICK + '3': + - RIGHT_CLICK + - LEFT_CLICK + '4': + - RIGHT_CLICK + - RIGHT_CLICK + loot-chests: # Time in seconds it takes for a loot chest to @@ -140,17 +181,19 @@ death-exp-loss: # Percentage of current EXP you lose when dying. percent: 30 -# Modify the keybinds for the 'swap hand items' key (F by default) -# -# Available actions: -# - spell_cast (enters the default spell casting mode) -# - vanilla (swaps hand items) -# - hotbar_swap (swap the player's horbat with the 9 lowest inventory slots) -# -# If the action is invalid, it will use 'vanilla' by default -swap-keybind: - normal: spell_cast - sneaking: hotbar_swap +# Fun extra RPG feature that switches the player's hotbar with +# the 9 lower row items of his inventory. This allows the player +# to have two different item sets or quickly have access to pots +# TODO +hotbar-swapping: + enabled: true + + # Keybind which triggers + # Available keybinds + keybind: SWAP_HANDS + + # If the player has to sneak to swap hotbars + crouching: true # Set this to true to allow players # in creative mode to enter casting mode