Merge remote-tracking branch 'origin/master'

This commit is contained in:
Jules 2024-12-05 00:43:53 +01:00
commit 2316a01809
15 changed files with 303 additions and 97 deletions

View File

@ -1,13 +1,12 @@
package net.Indyuce.mmocore.api.event;
import javax.annotation.Nullable;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.experience.EXPSource;
import net.Indyuce.mmocore.experience.Profession;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import net.Indyuce.mmocore.experience.Profession;
import javax.annotation.Nullable;
public class PlayerExperienceGainEvent extends PlayerDataEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
@ -35,7 +34,12 @@ public class PlayerExperienceGainEvent extends PlayerDataEvent implements Cancel
return experience;
}
@Deprecated
public void setExperience(int experience) {
setExperience((double) experience);
}
public void setExperience(double experience) {
this.experience = experience;
}

View File

@ -1136,7 +1136,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) return false;
this.skillCasting = SkillCastingMode.getCurrent().newInstance(this);
this.skillCasting = SkillCastingMode.getInstance().newInstance(this);
return true;
}
@ -1321,6 +1321,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
unbindSkill(slot);
final SkillSlot skillSlot = getProfess().getSkillSlot(slot);
boundSkills.put(slot, new BoundSkillInfo(skillSlot, skill, this));
SkillCastingMode.getInstance().onSkillBound(this);
}
@Nullable

View File

@ -3,6 +3,7 @@ package net.Indyuce.mmocore.command;
import io.lumine.mythic.lib.command.api.CommandTreeRoot;
import io.lumine.mythic.lib.command.api.Parameter;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.command.rpg.CastCommandTreeNode;
import net.Indyuce.mmocore.command.rpg.CoinsCommandTreeNode;
import net.Indyuce.mmocore.command.rpg.NoteCommandTreeNode;
import net.Indyuce.mmocore.command.rpg.ReloadCommandTreeNode;
@ -26,6 +27,7 @@ public class MMOCoreCommandTreeRoot extends CommandTreeRoot implements CommandEx
super("mmocore", "mmocore.admin");
addChild(new ReloadCommandTreeNode(this));
addChild(new CastCommandTreeNode(this));
addChild(new CoinsCommandTreeNode(this));
addChild(new NoteCommandTreeNode(this));
addChild(new AdminCommandTreeNode(this));

View File

@ -0,0 +1,21 @@
package net.Indyuce.mmocore.command.rpg;
import io.lumine.mythic.lib.command.api.CommandTreeNode;
import net.Indyuce.mmocore.command.rpg.cast.FirstCommandTreeNode;
import net.Indyuce.mmocore.command.rpg.cast.SpecificCommandTreeNode;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class CastCommandTreeNode extends CommandTreeNode {
public CastCommandTreeNode(CommandTreeNode parent) {
super(parent, "cast");
addChild(new FirstCommandTreeNode(this));
addChild(new SpecificCommandTreeNode(this));
}
@Override
public @NotNull CommandResult execute(CommandSender commandSender, String[] strings) {
return CommandResult.THROW_USAGE;
}
}

View File

@ -0,0 +1,61 @@
package net.Indyuce.mmocore.command.rpg.cast;
import io.lumine.mythic.lib.command.api.CommandTreeNode;
import io.lumine.mythic.lib.command.api.Parameter;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.skill.ClassSkill;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class FirstCommandTreeNode extends CommandTreeNode {
public FirstCommandTreeNode(CommandTreeNode parent) {
super(parent, "first");
addParameter(Parameter.PLAYER);
addParameter(SpecificCommandTreeNode.INTEGER);
}
@Override
public CommandResult execute(CommandSender sender, String[] args) {
if (args.length < 3) return CommandResult.THROW_USAGE;
Player player = Bukkit.getPlayer(args[2]);
if (player == null) {
sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + ".");
return CommandResult.FAILURE;
}
PlayerData data = PlayerData.get(player);
int slot;
try {
slot = Integer.parseInt(args[3]);
Validate.isTrue(slot > 0);
} catch (Exception exception) {
sender.sendMessage(ChatColor.RED + args[3] + " is not a valid integer.");
return CommandResult.FAILURE;
}
List<Integer> slots = data.getBoundSkills().entrySet().stream()
.filter(e -> !e.getValue().isPassive())
.map(Map.Entry::getKey)
.sorted(Integer::compare).collect(Collectors.toList());
if (slot > slots.size()) {
sender.sendMessage(ChatColor.RED + "Player " + player.getName() + " only has active skills on slots " + slots + ".");
return CommandResult.FAILURE;
}
ClassSkill skill = data.getBoundSkill(slots.get(slot - 1));
Validate.notNull(skill, "Internal error: skill is null");
Validate.isTrue(!skill.getSkill().getTrigger().isPassive(), "Internal error: skill is passive");
boolean success = skill.toCastable(data).cast(data.getMMOPlayerData()).isSuccessful();
return success ? CommandResult.SUCCESS : CommandResult.FAILURE;
}
}

View File

@ -0,0 +1,59 @@
package net.Indyuce.mmocore.command.rpg.cast;
import io.lumine.mythic.lib.command.api.CommandTreeNode;
import io.lumine.mythic.lib.command.api.Parameter;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.skill.ClassSkill;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Arrays;
public class SpecificCommandTreeNode extends CommandTreeNode {
public static final Parameter INTEGER = new Parameter("slot", false, (explorer, list) -> list.addAll(Arrays.asList("1", "2", "3", "4", "5", "6")));
public SpecificCommandTreeNode(CommandTreeNode parent) {
super(parent, "specific");
addParameter(Parameter.PLAYER);
addParameter(INTEGER);
}
@Override
public CommandResult execute(CommandSender sender, String[] args) {
if (args.length < 3) return CommandResult.THROW_USAGE;
Player player = Bukkit.getPlayer(args[2]);
if (player == null) {
sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + ".");
return CommandResult.FAILURE;
}
PlayerData data = PlayerData.get(player);
int slot;
try {
slot = Integer.parseInt(args[3]);
Validate.isTrue(slot > 0);
} catch (Exception exception) {
sender.sendMessage(ChatColor.RED + args[3] + " is not a valid integer.");
return CommandResult.FAILURE;
}
ClassSkill skill = data.getBoundSkill(slot);
if (skill == null) {
sender.sendMessage(ChatColor.RED + "Found no skill bound to slot " + slot + " of player " + player.getName() + ".");
return CommandResult.FAILURE;
}
if (skill.getSkill().getTrigger().isPassive()) {
sender.sendMessage(ChatColor.RED + "Skill '" + skill.getSkill().getName() + "' bound to slot " + slot + " is passive.");
return CommandResult.FAILURE;
}
boolean success = skill.toCastable(data).cast(data.getMMOPlayerData()).isSuccessful();
return success ? CommandResult.SUCCESS : CommandResult.FAILURE;
}
}

View File

@ -16,6 +16,7 @@ import net.Indyuce.mmocore.party.AbstractParty;
import net.Indyuce.mmocore.skill.CastableSkill;
import net.Indyuce.mmocore.skill.ClassSkill;
import net.Indyuce.mmocore.skill.RegisteredSkill;
import net.Indyuce.mmocore.skill.binding.BoundSkillInfo;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@ -25,6 +26,7 @@ import org.bukkit.entity.Player;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public class RPGPlaceholders extends PlaceholderExpansion {
@ -102,20 +104,14 @@ public class RPGPlaceholders extends PlaceholderExpansion {
final CastableSkill castable = playerData.getProfess().getSkill(skill).toCastable(playerData);
final double value = playerData.getMMOPlayerData().getSkillModifierMap().calculateValue(castable, parameterId);
return MythicLib.plugin.getMMOConfig().decimal.format(value);
}
else if (identifier.startsWith("attribute_points_spent_")) {
} else if (identifier.startsWith("attribute_points_spent_")) {
final String attributeId = identifier.substring(23);
final PlayerAttributes.AttributeInstance attributeInstance = Objects.requireNonNull(playerData.getAttributes().getInstance(attributeId), "Could not find attribute with ID '" + attributeId + "'");
return String.valueOf(attributeInstance.getBase());
}
else if (identifier.equals("level_percent")) {
} else if (identifier.equals("level_percent")) {
double current = playerData.getExperience(), next = playerData.getLevelUpExperience();
return MythicLib.plugin.getMMOConfig().decimal.format(current / next * 100);
}
else if (identifier.equals("health"))
} else if (identifier.equals("health"))
return StatManager.format("MAX_HEALTH", player.getPlayer().getHealth());
else if (identifier.equals("max_health"))
@ -162,12 +158,19 @@ public class RPGPlaceholders extends PlaceholderExpansion {
return info == null ? "" : info.getSkill().getHandler().getId();
}
// Returns the casting slot taking into account the skill slot offset
// Returns the key that needs to be pressed to cast slot in slot N
else if (identifier.startsWith("cast_slot_offset_")) {
final Player online = player.getPlayer();
Validate.notNull(online, "Player is offline");
final int slot = Integer.parseInt(identifier.substring(17));
return String.valueOf(slot + (online.getInventory().getHeldItemSlot() < slot ? 1 : 0));
final int query = Integer.parseInt(identifier.substring(17));
BoundSkillInfo bound = playerData.getBoundSkills().get(query);
if (bound == null || bound.isPassive()) return String.valueOf(0);
int slot = bound.skillBarCastSlot;
// Offset due to player's hotbar location
if (online.getInventory().getHeldItemSlot() < slot) slot++;
return String.valueOf(slot);
}
// Is there a passive skill bound to given slot

View File

@ -14,6 +14,16 @@ public class BoundSkillInfo implements Closeable {
private final PlayerData playerData;
private final ClassSkill classSkill;
/**
* If the skill casting mode is set to skill bar, then
* this corresponds to the slot the player needs to click
* (not taking into account casting slot offset) in order
* to cast this skill.
* <p>
* If it is a passive skill, this value has no meaning.
*/
public int skillBarCastSlot;
/**
* Non-permanent passive skills must be registered inside
* MythicLib when bound. When set to null, the skill is either

View File

@ -29,4 +29,8 @@ public abstract class SkillCastingHandler implements Listener {
@NotNull
public abstract SkillCastingInstance newInstance(@NotNull PlayerData player);
public void onSkillBound(@NotNull PlayerData player) {
// Nothing by default
}
}

View File

@ -78,7 +78,16 @@ public enum SkillCastingMode {
}
@NotNull
public static SkillCastingHandler getCurrent() {
public static SkillCastingHandler getInstance() {
return Objects.requireNonNull(current, "Skill casting mode hasn't been initialized yet");
}
/**
* @see #getInstance()
* @deprecated
*/
@Deprecated
public static SkillCastingHandler getCurrent() {
return getInstance();
}
}

View File

@ -1,9 +1,6 @@
package net.Indyuce.mmocore.skill.cast.handler;
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.ConfigMessage;
import net.Indyuce.mmocore.api.SoundEvent;
@ -21,18 +18,20 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class SkillBar extends SkillCastingHandler {
private final PlayerKey mainKey;
private final boolean disableSneak;
private final boolean disableSneak, lowestKeybinds;
public SkillBar(@NotNull ConfigurationSection config) {
super(config);
mainKey = PlayerKey.valueOf(UtilityMethods.enumName(Objects.requireNonNull(config.getString("open"), "Could not find open key")));
disableSneak = config.getBoolean("disable-sneak");
lowestKeybinds = config.getBoolean("use-lowest-keybinds");
}
@Override
@ -45,6 +44,23 @@ public class SkillBar extends SkillCastingHandler {
return SkillCastingMode.SKILL_BAR;
}
@Override
public void onSkillBound(@NotNull PlayerData player) {
// Lowest indices = start at slot 1 and increase
if (lowestKeybinds) {
int slot = 1;
for (BoundSkillInfo bound : player.getBoundSkills().values())
// Set cast slot and increment slot
if (!bound.isPassive()) bound.skillBarCastSlot = slot++;
}
// Otherwise, direct correspondance
else player.getBoundSkills().forEach((slot, bound) -> bound.skillBarCastSlot = slot);
}
@EventHandler
public void enterSkillCasting(PlayerKeyPressEvent event) {
if (event.getPressed() != mainKey) return;
@ -80,7 +96,7 @@ public class SkillBar extends SkillCastingHandler {
}
@EventHandler
public void onSkillCast(PlayerItemHeldEvent event) {
public void onItemHeld(PlayerItemHeldEvent event) {
if (!event.getPlayer().equals(getCaster().getPlayer())) return;
// Extra option to improve support with other plugins
@ -96,18 +112,18 @@ public class SkillBar extends SkillCastingHandler {
event.setCancelled(true);
refreshTimeOut();
final int activeSlot = event.getNewSlot() + (event.getNewSlot() >= player.getInventory().getHeldItemSlot() ? -1 : 0);
/*
* The event is called again soon after the first since when
* cancelling the first one, the player held item slot must go back
* to the previous one.
*/
if (activeSlot < getActiveSkills().size()) {
final ClassSkill classSkill = getActiveSkills().get(activeSlot).getClassSkill();
final PlayerMetadata caster = getCaster().getMMOPlayerData().getStatMap().cache(EquipmentSlot.MAIN_HAND);
classSkill.toCastable(getCaster()).cast(new TriggerMetadata(caster, null, null));
// Look for skill with given slot
ClassSkill classSkill = findSkillToCast(player.getInventory().getHeldItemSlot(), event.getNewSlot());
if (classSkill != null) classSkill.toCastable(getCaster()).cast(getCaster().getMMOPlayerData());
}
@Nullable
private ClassSkill findSkillToCast(int currentSlot, int clickedSlot) {
for (BoundSkillInfo info : this.getActiveSkills())
if (info.skillBarCastSlot + (currentSlot < info.skillBarCastSlot ? 1 : 0) == 1 + clickedSlot)
return info.getClassSkill();
return null;
}
@EventHandler
@ -128,12 +144,12 @@ public class SkillBar extends SkillCastingHandler {
@NotNull
private String getFormat(PlayerData data) {
final StringBuilder str = new StringBuilder();
if (!data.isOnline()) return str.toString();
if (!data.isOnline()) return "";
int slot = 1;
final StringBuilder str = new StringBuilder();
for (BoundSkillInfo active : getActiveSkills()) {
final ClassSkill skill = active.getClassSkill();
final int slot = active.skillBarCastSlot;
str.append(str.isEmpty() ? "" : split).append(
(onCooldown(data, skill) ? onCooldown.replace("{cooldown}",
@ -141,8 +157,8 @@ public class SkillBar extends SkillCastingHandler {
noMana(data, skill) ? noMana : (noStamina(data, skill) ? noStamina : ready))
.replace("{index}", String.valueOf(slot + (data.getPlayer().getInventory().getHeldItemSlot() < slot ? 1 : 0)))
.replace("{skill}", skill.getSkill().getName()));
slot++;
}
return MMOCore.plugin.placeholderParser.parse(data.getPlayer(), str.toString());
}

View File

@ -9,8 +9,10 @@ import io.lumine.mythic.lib.skill.SkillMetadata;
import io.lumine.mythic.lib.skill.handler.SkillHandler;
import io.lumine.mythic.lib.skill.result.def.SimpleSkillResult;
import io.lumine.mythic.lib.skill.trigger.TriggerMetadata;
import io.lumine.mythic.lib.skill.trigger.TriggerType;
import io.lumine.mythic.lib.util.EntityLocationType;
import io.lumine.mythic.lib.util.ParabolicProjectile;
import io.lumine.mythic.lib.util.annotation.BackwardsCompatibility;
import io.lumine.mythic.lib.version.VParticle;
import net.Indyuce.mmocore.api.event.PlayerResourceUpdateEvent;
import net.Indyuce.mmocore.api.player.PlayerData;
@ -22,17 +24,25 @@ import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
public class Ambers extends SkillHandler<SimpleSkillResult> implements Listener {
@BackwardsCompatibility(version = "1.20.5")
private final boolean legacyParticles = MythicLib.plugin.getVersion().isUnder(1, 20, 5);
public Ambers() {
super(false);
registerModifiers("percent");
}
@NotNull
@Override
public SimpleSkillResult getResult(SkillMetadata meta) {
return new SimpleSkillResult(meta.hasAttackBound() && meta.hasTargetEntity() && meta.getTargetEntityOrNull() instanceof LivingEntity);
return new SimpleSkillResult(meta.hasAttackSource()
&& meta.hasTargetEntity()
&& meta.getTargetEntityOrNull() instanceof LivingEntity);
}
@Override
@ -47,23 +57,23 @@ public class Ambers extends SkillHandler<SimpleSkillResult> implements Listener
@EventHandler
public void spawnAmber(PlayerAttackEvent event) {
MMOPlayerData data = event.getAttacker().getData();
if (!event.getAttack().getDamage().hasType(DamageType.SKILL))
return;
if (!event.getAttack().getDamage().hasType(DamageType.SKILL)) return;
PassiveSkill passive = data.getPassiveSkillMap().getSkill(this);
if (passive == null)
return;
if (passive == null) return;
passive.getTriggeredSkill().cast(new TriggerMetadata(event));
passive.getTriggeredSkill().cast(new TriggerMetadata(event, TriggerType.API));
}
public static class Amber extends BukkitRunnable {
class Amber extends BukkitRunnable {
private final Location loc;
private final MMOPlayerData data;
private final double percent;
private int j;
private static final double RADIUS_SQUARED = 3;
private Amber(MMOPlayerData data, Location source, Location loc, double percent) {
this.loc = loc;
this.data = data;
@ -80,10 +90,9 @@ public class Ambers extends SkillHandler<SimpleSkillResult> implements Listener
return;
}
if (data.getPlayer().getLocation().distanceSquared(loc) < 2) {
if (data.getPlayer().getLocation().add(0, 1, 0).distanceSquared(loc) < RADIUS_SQUARED) {
data.getPlayer().playSound(data.getPlayer().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 1);
// data.getSkillData().ambers++;
// Give mana back
PlayerData playerData = PlayerData.get(data);
@ -95,7 +104,11 @@ public class Ambers extends SkillHandler<SimpleSkillResult> implements Listener
}
for (int j = 0; j < 5; j++)
if (legacyParticles)
loc.getWorld().spawnParticle(VParticle.ENTITY_EFFECT.get(), loc, 0, 1, 0.647, 0, 1);
else
loc.getWorld().spawnParticle(VParticle.ENTITY_EFFECT.get(), loc, 0, 1, 0.647, 0, 1, Color.ORANGE);
loc.getWorld().spawnParticle(VParticle.REDSTONE.get(), loc, 1, new Particle.DustOptions(Color.ORANGE, 1.3f));
}
}

View File

@ -4,12 +4,11 @@ import io.lumine.mythic.lib.player.skill.PassiveSkill;
import io.lumine.mythic.lib.skill.SkillMetadata;
import io.lumine.mythic.lib.skill.handler.SkillHandler;
import io.lumine.mythic.lib.skill.result.def.SimpleSkillResult;
import io.lumine.mythic.lib.skill.trigger.TriggerMetadata;
import io.lumine.mythic.lib.skill.trigger.TriggerType;
import net.Indyuce.mmocore.api.event.PlayerResourceUpdateEvent;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
public class Neptune_Gift extends SkillHandler<SimpleSkillResult> implements Listener {
public Neptune_Gift() {
@ -18,6 +17,7 @@ public class Neptune_Gift extends SkillHandler<SimpleSkillResult> implements Lis
registerModifiers("extra");
}
@NotNull
@Override
public SimpleSkillResult getResult(SkillMetadata meta) {
throw new RuntimeException("Not supported");

View File

@ -14,6 +14,7 @@ import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
public class Sneaky_Picky extends SkillHandler<SimpleSkillResult> implements Listener {
public Sneaky_Picky() {
@ -22,15 +23,16 @@ public class Sneaky_Picky extends SkillHandler<SimpleSkillResult> implements Lis
registerModifiers("extra");
}
@NotNull
@Override
public SimpleSkillResult getResult(SkillMetadata meta) {
return new SimpleSkillResult(meta.hasAttackBound() && meta.hasTargetEntity() && meta.getTargetEntityOrNull() instanceof LivingEntity);
return new SimpleSkillResult(meta.hasAttackSource() && meta.hasTargetEntity() && meta.getTargetEntityOrNull() instanceof LivingEntity);
}
@Override
public void whenCast(SimpleSkillResult result, SkillMetadata skillMeta) {
LivingEntity target = (LivingEntity) skillMeta.getTargetEntity();
skillMeta.getAttack().getDamage().multiplicativeModifier(1 + skillMeta.getParameter("extra") / 100, DamageType.WEAPON);
skillMeta.getAttackSource().getDamage().multiplicativeModifier(1 + skillMeta.getParameter("extra") / 100, DamageType.WEAPON);
target.getWorld().spawnParticle(VParticle.SMOKE.get(), target.getLocation().add(0, target.getHeight() / 2, 0), 64, 0, 0, 0, .05);
target.getWorld().playSound(target.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 1, 2);
}

View File

@ -106,6 +106,7 @@ skill-casting:
mode: SKILL_BAR
open: SWAP_HANDS
disable-sneak: false
use-lowest-keybinds: true
loot-chests: