From 2d4a3cd3bd103628387172c3084710efea58528c Mon Sep 17 00:00:00 2001 From: Jules Date: Fri, 14 Jan 2022 11:11:11 +0100 Subject: [PATCH] party exp splitting --- .../java/net/Indyuce/mmocore/MMOCore.java | 2 +- .../mmocore/api/player/PlayerData.java | 23 ++- .../mmocore/api/player/social/Party.java | 133 ++++++++++-------- .../api/player/social/PartyInvite.java | 2 +- .../comp/MMOCoreTargetRestriction.java | 2 +- .../comp/placeholder/RPGPlaceholders.java | 2 +- .../Indyuce/mmocore/experience/EXPSource.java | 5 + .../net/Indyuce/mmocore/gui/PlayerStats.java | 4 +- .../gui/social/party/EditablePartyView.java | 11 +- .../mmocore/listener/PartyListener.java | 2 +- .../mmocore/manager/social/PartyManager.java | 4 +- 11 files changed, 109 insertions(+), 81 deletions(-) diff --git a/src/main/java/net/Indyuce/mmocore/MMOCore.java b/src/main/java/net/Indyuce/mmocore/MMOCore.java index e097e976..bab37527 100644 --- a/src/main/java/net/Indyuce/mmocore/MMOCore.java +++ b/src/main/java/net/Indyuce/mmocore/MMOCore.java @@ -39,6 +39,7 @@ import net.Indyuce.mmocore.manager.profession.*; import net.Indyuce.mmocore.manager.social.BoosterManager; import net.Indyuce.mmocore.manager.social.PartyManager; import net.Indyuce.mmocore.manager.social.RequestManager; +import net.Indyuce.mmocore.skill.cast.listener.SkillBar; import net.Indyuce.mmocore.skill.list.Ambers; import net.Indyuce.mmocore.skill.list.Neptune_Gift; import net.Indyuce.mmocore.skill.list.Sneaky_Picky; @@ -236,7 +237,6 @@ public class MMOCore extends LuminePlugin { Bukkit.getPluginManager().registerEvents(new GoldPouchesListener(), this); Bukkit.getPluginManager().registerEvents(new BlockListener(), this); Bukkit.getPluginManager().registerEvents(new LootableChestsListener(), this); - Bukkit.getPluginManager().registerEvents(new SpellCast(), this); Bukkit.getPluginManager().registerEvents(new PartyListener(), this); Bukkit.getPluginManager().registerEvents(new GuildListener(), this); Bukkit.getPluginManager().registerEvents(new FishingListener(), this); 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 0582d2d3..7933e177 100644 --- a/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java +++ b/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java @@ -6,7 +6,9 @@ import io.lumine.mythic.lib.player.cooldown.CooldownMap; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.ConfigMessage; import net.Indyuce.mmocore.api.Waypoint; -import net.Indyuce.mmocore.api.event.*; +import net.Indyuce.mmocore.api.event.PlayerExperienceGainEvent; +import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent; +import net.Indyuce.mmocore.api.event.PlayerResourceUpdateEvent; import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute; import net.Indyuce.mmocore.api.player.attribute.PlayerAttributes; import net.Indyuce.mmocore.api.player.profess.PlayerClass; @@ -24,10 +26,10 @@ 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.listener.SpellCast.SkillCasting; 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.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.*; @@ -485,7 +487,7 @@ public class PlayerData extends OfflinePlayerData implements Closable { * @param source How the player earned experience */ public void giveExperience(int value, EXPSource source) { - giveExperience(value, source, null); + giveExperience(value, source, null, true); } /** @@ -495,8 +497,9 @@ public class PlayerData extends OfflinePlayerData implements Closable { * @param source How the player earned experience * @param hologramLocation Location used to display the hologram. If it's null, no * hologram will be displayed + * @param splitExp Should the exp be split among party members */ - public void giveExperience(double value, EXPSource source, @Nullable Location hologramLocation) { + public void giveExperience(double value, EXPSource source, @Nullable Location hologramLocation, boolean splitExp) { if (hasReachedMaxLevel()) { setExperience(0); return; @@ -509,6 +512,14 @@ public class PlayerData extends OfflinePlayerData implements Closable { value = MMOCore.plugin.boosterManager.calculateExp(null, value); value *= 1 + getStats().getStat(StatType.ADDITIONAL_EXPERIENCE) / 100; + // Splitting exp through party members + if (splitExp && hasParty()) { + List onlineMembers = getParty().getOnlineMembers(); + value /= onlineMembers.size(); + for (PlayerData member : onlineMembers) + member.giveExperience(value, EXPSource.PARTY_SHARING, null, false); + } + PlayerExperienceGainEvent event = new PlayerExperienceGainEvent(this, (int) value, source); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) @@ -678,7 +689,7 @@ public class PlayerData extends OfflinePlayerData implements Closable { /** * @return If the action bar is not being used to display anything else - * i.e if the "general info" action bar can be displayed + * i.e if the "general info" action bar can be displayed */ public boolean canSeeActionBar() { return actionBarTimeOut < System.currentTimeMillis(); @@ -825,7 +836,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/api/player/social/Party.java b/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java index c5f355d2..c2cc27b1 100644 --- a/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java +++ b/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java @@ -12,7 +12,7 @@ import java.util.*; import java.util.function.Consumer; public class Party { - private final PartyMembers members = new PartyMembers(); + private final List members = new ArrayList<>(); private final Map invites = new HashMap<>(); // used to check if two parties are the same @@ -36,10 +36,24 @@ public class Party { return owner; } - public PartyMembers getMembers() { + public List getMembers() { return members; } + public List getOnlineMembers() { + List online = new ArrayList<>(); + + for (PlayerData member : members) + if (member.isOnline()) + online.add(member); + + return online; + } + + public PlayerData getMember(int index) { + return members.get(index); + } + public long getLastInvite(Player player) { return invites.containsKey(player.getUniqueId()) ? invites.get(player.getUniqueId()) : 0; } @@ -48,6 +62,21 @@ public class Party { invites.remove(player.getUniqueId()); } + public boolean hasMember(PlayerData playerData) { + return hasMember(playerData.getUniqueId()); + } + + public boolean hasMember(Player player) { + return hasMember(player.getUniqueId()); + } + + public boolean hasMember(UUID uuid) { + for (PlayerData member : members) + if (member.getUniqueId().equals(uuid)) + return true; + return false; + } + public void removeMember(PlayerData data) { removeMember(data, true); } @@ -59,11 +88,12 @@ public class Party { members.remove(data); data.setParty(null); - - reopenInventories(); + clearStatBonuses(data); + members.forEach(this::applyStatBonuses); + updateOpenInventories(); // Disband the party if no member left - if (members.count() < 1) { + if (members.size() < 1) { MMOCore.plugin.partyManager.unregisterParty(this); return; } @@ -82,18 +112,24 @@ public class Party { data.setParty(this); members.add(data); + members.forEach(this::applyStatBonuses); - reopenInventories(); + updateOpenInventories(); } - public void reopenInventories() { - for (PlayerData member : members.members) + private void updateOpenInventories() { + for (PlayerData member : members) if (member.isOnline() && member.getPlayer().getOpenInventory() != null && member.getPlayer().getOpenInventory().getTopInventory().getHolder() instanceof PartyViewInventory) ((PluginInventory) member.getPlayer().getOpenInventory().getTopInventory().getHolder()).open(); } + @Deprecated public void sendPartyInvite(PlayerData inviter, PlayerData target) { + sendInvite(inviter, target); + } + + public void sendInvite(PlayerData inviter, PlayerData target) { invites.put(target.getUniqueId(), System.currentTimeMillis()); Request request = new PartyInvite(this, inviter, target); if (inviter.isOnline() && target.isOnline()) @@ -102,61 +138,36 @@ public class Party { MMOCore.plugin.requestManager.registerRequest(request); } + /** + * An issue can happen if the consumer given as parameter + * modifies the member list during list iteration. + *

+ * To solve this, this method first copies the member list + * and iterates through it to avoid the exception. + */ + public void forEachMember(Consumer action) { + new ArrayList<>(members).forEach(action); + } + + private static final String PARTY_BUFF_MODIFIER_KEY = "mmocoreParty"; + + /** + * Applies party stat bonuses to a specific player + */ + private void applyStatBonuses(PlayerData player) { + MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).addModifier(PARTY_BUFF_MODIFIER_KEY, + MMOCore.plugin.partyManager.getBonus(stat).multiply(members.size() - 1))); + } + + /** + * Clear party stat bonuses from a player + */ + private void clearStatBonuses(PlayerData player) { + MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).remove(PARTY_BUFF_MODIFIER_KEY)); + } + @Override public boolean equals(Object obj) { return obj instanceof Party && ((Party) obj).getUniqueId().equals(getUniqueId()); } - - /* - * this class makes controling entries and departures and APPLYING PARTY - * STAT ATTRIBUTES much easier - */ - public static class PartyMembers { - private final List members = new ArrayList<>(); - - public PlayerData get(int count) { - return members.get(count); - } - - public boolean has(PlayerData player) { - return members.contains(player); - } - - public boolean has(Player player) { - for (PlayerData member : members) - if (member.getUniqueId().equals(player.getUniqueId())) - return true; - return false; - } - - public void add(PlayerData player) { - members.add(player); - - members.forEach(this::applyAttributes); - } - - public void remove(PlayerData player) { - members.remove(player); - - members.forEach(this::applyAttributes); - clearAttributes(player); - } - - public void forEach(Consumer action) { - members.forEach(action); - } - - public int count() { - return members.size(); - } - - private void applyAttributes(PlayerData player) { - MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).addModifier("mmocoreParty", - MMOCore.plugin.partyManager.getBonus(stat).multiply(members.size() - 1))); - } - - private void clearAttributes(PlayerData player) { - MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).remove("mmocoreParty")); - } - } } diff --git a/src/main/java/net/Indyuce/mmocore/api/player/social/PartyInvite.java b/src/main/java/net/Indyuce/mmocore/api/player/social/PartyInvite.java index 54e0920e..1777fe53 100644 --- a/src/main/java/net/Indyuce/mmocore/api/player/social/PartyInvite.java +++ b/src/main/java/net/Indyuce/mmocore/api/player/social/PartyInvite.java @@ -28,7 +28,7 @@ public class PartyInvite extends Request { } public void accept() { - if(party.getMembers().count() >= Math.min(8, Math.max(2, MMOCore.plugin.getConfig().getInt("party.max-players", 8)))) { + if(party.getMembers().size() >= Math.max(2, MMOCore.plugin.getConfig().getInt("party.max-players", 8))) { MMOCore.plugin.configManager.getSimpleMessage("party-is-full").send(target.getPlayer()); return; } diff --git a/src/main/java/net/Indyuce/mmocore/comp/MMOCoreTargetRestriction.java b/src/main/java/net/Indyuce/mmocore/comp/MMOCoreTargetRestriction.java index 9fccabf4..265bba11 100644 --- a/src/main/java/net/Indyuce/mmocore/comp/MMOCoreTargetRestriction.java +++ b/src/main/java/net/Indyuce/mmocore/comp/MMOCoreTargetRestriction.java @@ -15,7 +15,7 @@ public class MMOCoreTargetRestriction implements TargetRestriction { PlayerData targetData = PlayerData.get(target.getUniqueId()); // Check for the same party - if (targetData.hasParty() && targetData.getParty().getMembers().has(player)) + if (targetData.hasParty() && targetData.getParty().hasMember(player)) return false; } diff --git a/src/main/java/net/Indyuce/mmocore/comp/placeholder/RPGPlaceholders.java b/src/main/java/net/Indyuce/mmocore/comp/placeholder/RPGPlaceholders.java index f1c52ebc..2259b749 100644 --- a/src/main/java/net/Indyuce/mmocore/comp/placeholder/RPGPlaceholders.java +++ b/src/main/java/net/Indyuce/mmocore/comp/placeholder/RPGPlaceholders.java @@ -118,7 +118,7 @@ public class RPGPlaceholders extends PlaceholderExpansion { .getLevelUpExperience(identifier.substring(22).replace(" ", "-").replace("_", "-").toLowerCase()); else if (identifier.startsWith("party_count")) - return playerData.hasParty() ? String.valueOf(playerData.getParty().getMembers().count()) : "0"; + return playerData.hasParty() ? String.valueOf(playerData.getParty().getMembers().size()) : "0"; else if (identifier.startsWith("profession_")) return String diff --git a/src/main/java/net/Indyuce/mmocore/experience/EXPSource.java b/src/main/java/net/Indyuce/mmocore/experience/EXPSource.java index 916f819d..89f522f3 100644 --- a/src/main/java/net/Indyuce/mmocore/experience/EXPSource.java +++ b/src/main/java/net/Indyuce/mmocore/experience/EXPSource.java @@ -17,6 +17,11 @@ public enum EXPSource { */ VANILLA, + /** + * When party members share exp + */ + PARTY_SHARING, + /** * When using the experience trigger. Keep in mind the * experience trigger can also use another experience source diff --git a/src/main/java/net/Indyuce/mmocore/gui/PlayerStats.java b/src/main/java/net/Indyuce/mmocore/gui/PlayerStats.java index 52582271..2005ce42 100644 --- a/src/main/java/net/Indyuce/mmocore/gui/PlayerStats.java +++ b/src/main/java/net/Indyuce/mmocore/gui/PlayerStats.java @@ -176,7 +176,7 @@ public class PlayerStats extends EditableInventory { public Placeholders getPlaceholders(GeneratedInventory inv, int n) { Placeholders holders = new Placeholders(); - int count = inv.getPlayerData().getParty().getMembers().count(); + int count = inv.getPlayerData().getParty().getMembers().size(); holders.register("count", "" + count); for (StatType stat : MMOCore.plugin.partyManager.getBonuses()) holders.register("buff_" + stat.name().toLowerCase(), MMOCore.plugin.partyManager.getBonus(stat).multiply(count - 1).toString()); @@ -186,7 +186,7 @@ public class PlayerStats extends EditableInventory { @Override public boolean canDisplay(GeneratedInventory inv) { - return inv.getPlayerData().hasParty() && inv.getPlayerData().getParty().getMembers().count() > 1; + return inv.getPlayerData().hasParty() && inv.getPlayerData().getParty().getMembers().size() > 1; } } diff --git a/src/main/java/net/Indyuce/mmocore/gui/social/party/EditablePartyView.java b/src/main/java/net/Indyuce/mmocore/gui/social/party/EditablePartyView.java index 8601146c..ec99da6d 100644 --- a/src/main/java/net/Indyuce/mmocore/gui/social/party/EditablePartyView.java +++ b/src/main/java/net/Indyuce/mmocore/gui/social/party/EditablePartyView.java @@ -8,7 +8,6 @@ import net.Indyuce.mmocore.api.util.input.PlayerInput.InputType; import net.Indyuce.mmocore.api.util.math.format.DelayFormat; import net.Indyuce.mmocore.gui.api.EditableInventory; import net.Indyuce.mmocore.gui.api.GeneratedInventory; -import net.Indyuce.mmocore.gui.api.PluginInventory; import net.Indyuce.mmocore.gui.api.item.InventoryItem; import net.Indyuce.mmocore.gui.api.item.Placeholders; import net.Indyuce.mmocore.gui.api.item.SimplePlaceholderItem; @@ -98,7 +97,7 @@ public class EditablePartyView extends EditableInventory { @Override public ItemStack display(GeneratedInventory inv, int n) { - return inv.getPlayerData().getParty().getMembers().count() > n ? member.display(inv, n) : empty.display(inv, n); + return inv.getPlayerData().getParty().getMembers().size() > n ? member.display(inv, n) : empty.display(inv, n); } @Override @@ -127,7 +126,7 @@ public class EditablePartyView extends EditableInventory { @Override public String calculateName() { - return getName().replace("{max}", "" + max).replace("{players}", "" + getPlayerData().getParty().getMembers().count()); + return getName().replace("{max}", "" + max).replace("{players}", "" + getPlayerData().getParty().getMembers().size()); } @Override @@ -142,7 +141,7 @@ public class EditablePartyView extends EditableInventory { if (item.getFunction().equals("invite")) { - if (playerData.getParty().getMembers().count() >= max) { + if (playerData.getParty().getMembers().size() >= max) { MMOCore.plugin.configManager.getSimpleMessage("party-is-full").send(player); player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); return; @@ -165,14 +164,14 @@ public class EditablePartyView extends EditableInventory { } PlayerData targetData = PlayerData.get(target); - if (playerData.getParty().getMembers().has(targetData)) { + if (playerData.getParty().hasMember(targetData)) { MMOCore.plugin.configManager.getSimpleMessage("already-in-party", "player", target.getName()).send(player); player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); open(); return; } - playerData.getParty().sendPartyInvite(playerData, targetData); + playerData.getParty().sendInvite(playerData, targetData); MMOCore.plugin.configManager.getSimpleMessage("sent-party-invite", "player", target.getName()).send(player); player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); open(); diff --git a/src/main/java/net/Indyuce/mmocore/listener/PartyListener.java b/src/main/java/net/Indyuce/mmocore/listener/PartyListener.java index 72e6da93..6c9b9ab0 100644 --- a/src/main/java/net/Indyuce/mmocore/listener/PartyListener.java +++ b/src/main/java/net/Indyuce/mmocore/listener/PartyListener.java @@ -54,7 +54,7 @@ public class PartyListener implements Listener { LivingEntity entity = event.getEntity(); if (entity instanceof Player && !entity.hasMetadata("NPC")) { PlayerData targetData = PlayerData.get((Player) event.getEntity()); - if (targetData.hasParty() && targetData.getParty().getMembers().has(PlayerData.get(event.getData().getUniqueId()))) + if (targetData.hasParty() && targetData.getParty().hasMember(PlayerData.get(event.getData().getUniqueId()))) event.setCancelled(true); } } diff --git a/src/main/java/net/Indyuce/mmocore/manager/social/PartyManager.java b/src/main/java/net/Indyuce/mmocore/manager/social/PartyManager.java index 40104745..8b72d6fe 100644 --- a/src/main/java/net/Indyuce/mmocore/manager/social/PartyManager.java +++ b/src/main/java/net/Indyuce/mmocore/manager/social/PartyManager.java @@ -6,6 +6,7 @@ import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.api.player.social.Party; import net.Indyuce.mmocore.api.player.stats.StatType; import net.Indyuce.mmocore.manager.MMOCoreManager; +import org.apache.commons.lang.Validate; import org.bukkit.configuration.ConfigurationSection; import java.util.HashMap; @@ -34,7 +35,8 @@ public class PartyManager implements MMOCoreManager { public void unregisterParty(Party party) { // IMPORTANT: clears all party members before unregistering the party - party.getMembers().forEach(party::removeMember); + party.forEachMember(party::removeMember); + Validate.isTrue(party.getMembers().isEmpty(), "Tried unregistering a non-empty party"); parties.remove(party); }