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