diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7bc342a
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 03b0d5a..c763ce2 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -31,10 +31,20 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index d99a48f..61fc0fe 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,6 +12,7 @@
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
new file mode 100644
index 0000000..8c1c9cf
--- /dev/null
+++ b/dependency-reduced-pom.xml
@@ -0,0 +1,105 @@
+
+
+ 4.0.0
+ me.xIdentified
+ TavernBard
+ TavernBard
+ 1.2.1
+
+
+
+ true
+ src/main/resources
+
+
+
+
+ maven-compiler-plugin
+ 3.8.1
+
+ ${java.version}
+ ${java.version}
+
+
+
+ maven-shade-plugin
+ 3.5.0
+
+
+ package
+
+ shade
+
+
+
+
+ net.kyori.adventure
+ me.xidentified.tavernbard.adventure
+
+
+
+
+
+
+
+
+
+
+ papermc-repo
+ https://repo.papermc.io/repository/maven-public/
+
+
+ sonatype
+ https://oss.sonatype.org/content/groups/public/
+
+
+ citizens-repo
+ https://maven.citizensnpcs.co/repo
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+ nexus
+ Lumine Releases
+ https://mvn.lumine.io/repository/maven-public/
+
+
+
+
+ io.papermc.paper
+ paper-api
+ 1.20.2-R0.1-SNAPSHOT
+ provided
+
+
+ net.citizensnpcs
+ citizens-main
+ 2.0.32-SNAPSHOT
+ provided
+
+
+ *
+ *
+
+
+
+
+ com.github.MilkBowl
+ VaultAPI
+ 1.7
+ provided
+
+
+ io.lumine
+ Mythic-Dist
+ 5.3.5
+ provided
+
+
+
+ 16
+ UTF-8
+
+
diff --git a/pom.xml b/pom.xml
index 3cd3467..dc22b90 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,9 +6,8 @@
me.xIdentifiedTavernBard
- 1.0
+ 1.2.2jar
-
TavernBard
@@ -27,22 +26,6 @@
${java.version}
-
- org.apache.maven.plugins
- maven-shade-plugin
- 3.2.4
-
-
- package
-
- shade
-
-
- false
-
-
-
-
@@ -69,13 +52,18 @@
jitpack.iohttps://jitpack.io
+
+ nexus
+ Lumine Releases
+ https://mvn.lumine.io/repository/maven-public/
+ io.papermc.paperpaper-api
- 1.20.1-R0.1-SNAPSHOT
+ 1.20.2-R0.1-SNAPSHOTprovided
@@ -97,5 +85,17 @@
1.7provided
+
+ io.lumine
+ Mythic-Dist
+ 5.3.5
+ provided
+
+
+ net.kyori
+ adventure-text-minimessage
+ 4.14.0
+ compile
+
diff --git a/src/main/java/me/xidentified/tavernbard/BardTrait.java b/src/main/java/me/xidentified/tavernbard/BardTrait.java
index 7153520..cbe7843 100644
--- a/src/main/java/me/xidentified/tavernbard/BardTrait.java
+++ b/src/main/java/me/xidentified/tavernbard/BardTrait.java
@@ -1,31 +1,20 @@
package me.xidentified.tavernbard;
-import me.xidentified.tavernbard.managers.SongManager;
-import me.xidentified.tavernbard.util.MessageUtil;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitName;
import net.citizensnpcs.api.util.DataKey;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import net.citizensnpcs.api.event.NPCRightClickEvent;
-
-import static org.bukkit.plugin.java.JavaPlugin.getPlugin;
-
@TraitName("bard")
public class BardTrait extends Trait {
- private final TavernBard plugin;
- private final SongManager songManager;
+
+ @Persist private boolean isBard = true;
public BardTrait() {
super("bard");
- this.plugin = getPlugin(TavernBard.class);
- this.songManager = plugin.getSongManager();
}
- @Persist private boolean isBard = true;
-
+ // Keeping load and save methods as they are
@Override
public void load(DataKey key) {
isBard = key.getBoolean("isBard", true);
@@ -36,13 +25,5 @@ public void save(DataKey key) {
key.setBoolean("isBard", isBard);
}
- @EventHandler
- public void onRightClick(NPCRightClickEvent event) {
- if (event.getNPC() == this.getNPC()) {
- Player player = event.getClicker();
- SongSelectionGUI gui = new SongSelectionGUI(plugin, songManager, event.getNPC(), this.plugin.getMessageUtil());
- player.openInventory(gui.getInventory());
- }
- }
-
}
+
diff --git a/src/main/java/me/xidentified/tavernbard/CommandHandler.java b/src/main/java/me/xidentified/tavernbard/CommandHandler.java
index 3de4d5b..e1352b7 100644
--- a/src/main/java/me/xidentified/tavernbard/CommandHandler.java
+++ b/src/main/java/me/xidentified/tavernbard/CommandHandler.java
@@ -9,6 +9,7 @@
import org.jetbrains.annotations.NotNull;
import java.util.Queue;
+import java.util.UUID;
public class CommandHandler implements CommandExecutor {
private final SongManager songManager;
@@ -20,7 +21,20 @@ public CommandHandler(SongManager songManager, QueueManager queueManager) {
}
@Override
- public boolean onCommand(@NotNull CommandSender sender, Command cmd, @NotNull String label, String[] args) {
+ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) {
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage("§cOnly players can use bard commands.");
+ return true;
+ }
+
+ // Assume a method getNearestBardNpc which returns the closest Bard NPC to a player
+ UUID npcId = songManager.getNearestBard(player, 8);
+
+ if (npcId == null) {
+ sender.sendMessage("§cNo nearby bard NPCs found!");
+ return true;
+ }
+
if (cmd.getName().equalsIgnoreCase("bard") && args.length > 0) {
if (args[0].equalsIgnoreCase("reload")) {
if (sender.hasPermission("bard.reload")) {
@@ -32,7 +46,7 @@ public boolean onCommand(@NotNull CommandSender sender, Command cmd, @NotNull St
}
} else if (args[0].equalsIgnoreCase("queue")) {
if (sender.hasPermission("bard.play")) {
- Queue queue = queueManager.getQueueStatus();
+ Queue queue = queueManager.getQueueStatus(npcId); // Updated to use NPC ID
if (queue.isEmpty()) {
sender.sendMessage("§cThere are no songs in the queue.");
return true;
@@ -46,14 +60,11 @@ public boolean onCommand(@NotNull CommandSender sender, Command cmd, @NotNull St
return true;
}
} else if (args[0].equalsIgnoreCase("vote")) {
- if (!(sender instanceof Player)) {
- sender.sendMessage("§cOnly players can vote to skip songs.");
- return true;
- } else if (!songManager.isSongPlaying()) {
+ if (!songManager.isSongPlaying(npcId)) {
sender.sendMessage("§cThere are no songs playing to vote against.");
return true;
}
- queueManager.voteToSkip((Player) sender);
+ queueManager.voteToSkip(player, npcId);
return true;
}
}
diff --git a/src/main/java/me/xidentified/tavernbard/SongSelectionGUI.java b/src/main/java/me/xidentified/tavernbard/SongSelectionGUI.java
index 494bff0..2469389 100644
--- a/src/main/java/me/xidentified/tavernbard/SongSelectionGUI.java
+++ b/src/main/java/me/xidentified/tavernbard/SongSelectionGUI.java
@@ -2,8 +2,6 @@
import me.xidentified.tavernbard.managers.SongManager;
import me.xidentified.tavernbard.util.MessageUtil;
-import net.citizensnpcs.api.npc.NPC;
-import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
@@ -20,30 +18,30 @@
import java.util.UUID;
public class SongSelectionGUI implements InventoryHolder {
-
private final TavernBard plugin;
private final SongManager songManager;
- private final NPC bardNpc;
- private final MessageUtil messageUtil;
+ private final UUID bardId;
private final int ITEMS_PER_PAGE = 45;
private int currentPage = 0;
private final Inventory cachedGUI;
- public SongSelectionGUI(TavernBard plugin, SongManager songManager, NPC bardNpc, MessageUtil messageUtil) {
+ public SongSelectionGUI(TavernBard plugin, SongManager songManager, UUID bardId) {
this.plugin = plugin;
this.songManager = songManager;
- this.bardNpc = bardNpc;
- this.messageUtil = messageUtil;
+ this.bardId = bardId;
// Initialize the cached GUI
- String guiTitle = messageUtil.getConfigMessage("gui-title", "Song Selection");
- Component titleComponent = messageUtil.parse(guiTitle);
- this.cachedGUI = Bukkit.getServer().createInventory(this, getInventorySize(songManager.getSongs().size()), titleComponent);
+ String guiTitle = messageUtil().getConfigMessage("gui-title", "Song Selection");
+ // Convert to string for Spigot
+ String titleString = messageUtil().convertToUniversalFormat(guiTitle);
+
+ this.cachedGUI = Bukkit.getServer().createInventory(this, getInventorySize(songManager.getSongs().size()), titleString);
populateCachedGUI();
}
private void populateCachedGUI() {
populateInventory(cachedGUI);
+ updateNowPlayingInfo();
}
@Override
@@ -52,13 +50,16 @@ private void populateCachedGUI() {
}
public Inventory getClonedGUIForPlayer() {
- String guiTitle = messageUtil.getConfigMessage("gui-title", "Song Selection");
- Component titleComponent = messageUtil.parse(guiTitle);
- Inventory playerGUI = Bukkit.createInventory(this, cachedGUI.getSize(), titleComponent);
+ String guiTitle = messageUtil().getConfigMessage("gui-title", "Song Selection");
+ // Convert to string for Spigot
+ String titleString = messageUtil().convertToUniversalFormat(guiTitle);
+
+ Inventory playerGUI = Bukkit.createInventory(this, cachedGUI.getSize(), titleString);
playerGUI.setContents(cachedGUI.getContents().clone());
return playerGUI;
}
+
private void populateInventory(Inventory cachedGUI) {
cachedGUI.clear();
List songs = songManager.getSongs();
@@ -96,14 +97,12 @@ private void populateInventory(Inventory cachedGUI) {
PersistentDataContainer container = songMeta.getPersistentDataContainer();
container.set(new NamespacedKey(plugin, "songName"), PersistentDataType.STRING, song.getName());
- // Set the song display name for player visibility
- Component displayNameComponent = messageUtil.parse("" + song.getDisplayName());
- songMeta.displayName(displayNameComponent);
+ // Convert Component to String for Spigot servers
+ songMeta.setDisplayName(messageUtil().convertToUniversalFormat("" + song.getDisplayName()));
- // Add artist credit to the lore
- List lore = new ArrayList<>();
- lore.add(messageUtil.parse("By " + song.getArtist()));
- songMeta.lore(lore);
+ List lore = new ArrayList<>();
+ lore.add(messageUtil().convertToUniversalFormat("By " + song.getArtist()));
+ songMeta.setLore(lore);
// Add custom model data to song items
if (customModelData != -1) {
@@ -120,20 +119,16 @@ private void populateInventory(Inventory cachedGUI) {
if (currentPage > 0) {
addNavigationItem(Material.ARROW, "Previous Page", "previousPage", invSize - 9);
}
-
// Next page
if (endIndex < songs.size()) {
addNavigationItem(Material.ARROW, "Next Page", "nextPage", invSize - 1);
}
-
// Stop Song
addNavigationItem(Material.BARRIER, "Stop Song", "stopSong", invSize - 5);
-
// Vote skip button
addNavigationItem(Material.ARROW, "Vote to Skip", "voteSkip", invSize - 7);
-
// Now playing info
- Song currentSong = songManager.getCurrentSong();
+ Song currentSong = songManager.getCurrentSong(bardId);
if (currentSong != null) {
addNowPlayingInfo(currentSong, invSize - 8, cachedGUI); // 2nd slot from the left
}
@@ -143,13 +138,9 @@ private void addNavigationItem(Material material, String displayName, String act
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
- // Create a Component for the display name
- Component displayNameComponent = messageUtil.parse(displayName);
-
- // Use the Component with the item meta
- meta.displayName(displayNameComponent);
+ // Convert Component to String for Spigot servers
+ meta.setDisplayName(messageUtil().convertToUniversalFormat(displayName));
- // Set metadata for navigation
PersistentDataContainer container = meta.getPersistentDataContainer();
container.set(new NamespacedKey(plugin, "action"), PersistentDataType.STRING, action);
@@ -170,20 +161,22 @@ private void addNowPlayingInfo(Song song, int slot, Inventory cachedGUI) {
ItemStack item = new ItemStack(Material.NAME_TAG);
ItemMeta meta = item.getItemMeta();
- meta.displayName(messageUtil.parse("Currently Playing"));
- List lore = new ArrayList<>();
- lore.add(messageUtil.parse("Title: " + song.getDisplayName()));
- lore.add(messageUtil.parse("Artist: " + song.getArtist()));
- lore.add(messageUtil.parse("Added by: " + playerName));
- meta.lore(lore);
+ meta.setDisplayName(messageUtil().convertToUniversalFormat("Currently Playing"));
+
+ List lore = new ArrayList<>();
+ lore.add(messageUtil().convertToUniversalFormat("Title: " + song.getDisplayName()));
+ lore.add(messageUtil().convertToUniversalFormat("Artist: " + song.getArtist()));
+ lore.add(messageUtil().convertToUniversalFormat("Added by: " + playerName));
+ meta.setLore(lore);
+
item.setItemMeta(meta);
cachedGUI.setItem(slot, item);
}
public void updateNowPlayingInfo() {
- Song currentSong = songManager.getCurrentSong();
+ Song currentSong = songManager.getCurrentSong(bardId);
if (currentSong != null) {
addNowPlayingInfo(currentSong, cachedGUI.getSize() - 8, cachedGUI);
} else {
@@ -210,7 +203,12 @@ private int getInventorySize(int numSongs) {
return (rowsForSongs + 1) * 9; // +1 is for the navigation bar that lets you control the music
}
- public NPC getBardNpc() {
- return bardNpc;
+ public UUID getBardId() {
+ return bardId;
}
+
+ private MessageUtil messageUtil(){
+ return this.plugin.getMessageUtil();
+ }
+
}
diff --git a/src/main/java/me/xidentified/tavernbard/TavernBard.java b/src/main/java/me/xidentified/tavernbard/TavernBard.java
index ed5c963..ff29ba4 100644
--- a/src/main/java/me/xidentified/tavernbard/TavernBard.java
+++ b/src/main/java/me/xidentified/tavernbard/TavernBard.java
@@ -1,50 +1,109 @@
package me.xidentified.tavernbard;
-import me.xidentified.tavernbard.listeners.EventListener;
+import me.xidentified.tavernbard.listeners.CitizensInteractListener;
+import me.xidentified.tavernbard.listeners.GUIListener;
+import me.xidentified.tavernbard.listeners.MythicMobInteractListener;
+import me.xidentified.tavernbard.listeners.ResourcePackListener;
+import me.xidentified.tavernbard.managers.CooldownManager;
import me.xidentified.tavernbard.managers.QueueManager;
import me.xidentified.tavernbard.managers.SongManager;
import me.xidentified.tavernbard.util.MessageUtil;
import net.citizensnpcs.api.CitizensAPI;
import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
+import java.util.Objects;
+import java.util.UUID;
import java.util.logging.Level;
public final class TavernBard extends JavaPlugin {
private SongManager songManager;
private MessageUtil messageUtil;
+ private CooldownManager cooldownManager;
@Override
public void onEnable() {
- // Check if Citizens is enabled
- if (getServer().getPluginManager().getPlugin("Citizens") == null ||
- !getServer().getPluginManager().getPlugin("Citizens").isEnabled()) {
- getLogger().log(Level.SEVERE, "Citizens not found or not enabled");
+ boolean citizensFound = getServer().getPluginManager().isPluginEnabled("Citizens");
+ boolean mythicMobsFound = Bukkit.getPluginManager().isPluginEnabled("MythicMobs");
+
+ // Check if neither Citizens nor MythicMobs is enabled
+ if (!citizensFound && !mythicMobsFound) {
+ getLogger().log(Level.SEVERE, "Neither Citizens nor MythicMobs found. At least one is required for the plugin to work!");
getServer().getPluginManager().disablePlugin(this);
return;
}
+ // Check if Citizens is enabled and register bard trait
+ if (citizensFound) {
+ // Check if trait is already registered
+ if (CitizensAPI.getTraitFactory().getTrait("bard") == null) {
+ CitizensAPI.getTraitFactory().registerTrait(net.citizensnpcs.api.trait.TraitInfo.create(BardTrait.class).withName("bard"));
+ getLogger().info("Bard trait registered with Citizens.");
+ }
+ }
+
+ if (mythicMobsFound) {
+ getServer().getPluginManager().registerEvents(new MythicMobInteractListener(this), this);
+ getLogger().info("MythicMobs detected. Support enabled!");
+ }
+
// Load default config if missing and saved plugin configuration
saveDefaultConfig();
reloadConfig();
// Initialize SongManager, QueueManager, and MessageUtil classes
this.messageUtil = new MessageUtil(getConfig());
+ cooldownManager = new CooldownManager();
songManager = new SongManager(this);
- QueueManager queueManager = new QueueManager(this, songManager);
+ QueueManager queueManager = new QueueManager(this, songManager, cooldownManager);
- // Register the bard trait with Citizens.
- CitizensAPI.getTraitFactory().registerTrait(net.citizensnpcs.api.trait.TraitInfo.create(BardTrait.class));
+ // Initialize and register listeners
+ GUIListener GUIListener = new GUIListener(this, songManager);
+ getServer().getPluginManager().registerEvents(GUIListener, this);
- // Initialize and register EventListener
- EventListener eventListener = new EventListener(this, songManager, messageUtil);
- getServer().getPluginManager().registerEvents(eventListener, this);
+ ResourcePackListener resourcePackListener = new ResourcePackListener(this);
+ getServer().getPluginManager().registerEvents(resourcePackListener, this);
+
+ CitizensInteractListener citizensInteractListener = new CitizensInteractListener(this);
+ getServer().getPluginManager().registerEvents(citizensInteractListener, this);
// Register commands
- this.getCommand("bard").setExecutor(new CommandHandler(songManager, queueManager));
+ Objects.requireNonNull(this.getCommand("bard")).setExecutor(new CommandHandler(songManager, queueManager));
+
+ }
+
+ public void handleInteraction(UUID bardEntityId, Player player) {
+ debugLog("handleInteraction method fired");
+
+ SongSelectionGUI gui = new SongSelectionGUI(this, getSongManager(), bardEntityId);
+ player.openInventory(gui.getInventory());
+
+ if (!getSongManager().bardNpcs.containsKey(bardEntityId)) {
+ debugLog("Adding entity with ID: " + bardEntityId);
+ if (bardEntityId != null) {
+ getSongManager().bardNpcs.put(bardEntityId, player.getUniqueId());
+ }
+ } else {
+ debugLog("Entity with ID: " + bardEntityId + " already added.");
+ }
+ }
+
+ // Get entity from UUID to check if it's a bard or not
+ public Entity getEntityFromUUID(World world, UUID uuid) {
+ for (Entity entity : world.getEntities()) {
+ if (entity.getUniqueId().equals(uuid)) {
+ return entity;
+ }
+ }
+ return null;
}
+
// Plugin shutdown logic
@Override
public void onDisable() {
@@ -53,8 +112,12 @@ public void onDisable() {
CitizensAPI.getTraitFactory().deregisterTrait(net.citizensnpcs.api.trait.TraitInfo.create(BardTrait.class));
}
+ // Unregister listeners
+ HandlerList.unregisterAll();
+
// Cancel all tasks
Bukkit.getScheduler().cancelTasks(this);
+
}
// Getter methods
@@ -66,6 +129,10 @@ public MessageUtil getMessageUtil() {
return messageUtil;
}
+ public CooldownManager getCooldownManager() {
+ return cooldownManager;
+ }
+
public void debugLog(String message) {
if (getConfig().getBoolean("debug_mode", false)) {
getLogger().info("[DEBUG] " + message);
diff --git a/src/main/java/me/xidentified/tavernbard/listeners/CitizensInteractListener.java b/src/main/java/me/xidentified/tavernbard/listeners/CitizensInteractListener.java
new file mode 100644
index 0000000..201746d
--- /dev/null
+++ b/src/main/java/me/xidentified/tavernbard/listeners/CitizensInteractListener.java
@@ -0,0 +1,26 @@
+package me.xidentified.tavernbard.listeners;
+
+import me.xidentified.tavernbard.BardTrait;
+import me.xidentified.tavernbard.TavernBard;
+import net.citizensnpcs.api.event.NPCRightClickEvent;
+import net.citizensnpcs.api.npc.NPC;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+public class CitizensInteractListener implements Listener {
+ private final TavernBard plugin;
+
+ public CitizensInteractListener(TavernBard plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler
+ public void onCitizensNPCInteract(NPCRightClickEvent event) {
+ plugin.debugLog("Citizens NPC interacted: " + event.getNPC().getName());
+ NPC npc = event.getNPC();
+ if (npc.hasTrait(BardTrait.class)) {
+ plugin.handleInteraction(npc.getMinecraftUniqueId(), event.getClicker());
+ }
+ }
+
+}
diff --git a/src/main/java/me/xidentified/tavernbard/listeners/EventListener.java b/src/main/java/me/xidentified/tavernbard/listeners/GUIListener.java
similarity index 63%
rename from src/main/java/me/xidentified/tavernbard/listeners/EventListener.java
rename to src/main/java/me/xidentified/tavernbard/listeners/GUIListener.java
index 177b4d1..4914521 100644
--- a/src/main/java/me/xidentified/tavernbard/listeners/EventListener.java
+++ b/src/main/java/me/xidentified/tavernbard/listeners/GUIListener.java
@@ -4,52 +4,26 @@
import me.xidentified.tavernbard.SongSelectionGUI;
import me.xidentified.tavernbard.TavernBard;
import me.xidentified.tavernbard.managers.SongManager;
-import me.xidentified.tavernbard.util.MessageUtil;
-import net.citizensnpcs.api.npc.NPC;
import org.bukkit.NamespacedKey;
-import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
-import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
-public class EventListener implements Listener {
+import java.util.UUID;
+
+public class GUIListener implements Listener {
private final SongManager songManager;
private final TavernBard plugin;
- private final MessageUtil messageUtil;
- public EventListener(TavernBard plugin, SongManager songManager, MessageUtil messageUtil) {
+ public GUIListener(TavernBard plugin, SongManager songManager) {
this.plugin = plugin;
this.songManager = songManager;
- this.messageUtil = messageUtil;
- }
-
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent event) {
- Player player = event.getPlayer();
- applyResourcePackToPlayer(player);
- }
-
- public void applyResourcePackToPlayer(Player player) {
- FileConfiguration config = plugin.getConfig();
-
- if (config.getBoolean("resource_pack.external-host.enabled")) {
- String packLink = config.getString("resource_pack.external-host.pack-link");
- if (packLink != null && !packLink.isEmpty()) {
- player.setResourcePack(packLink);
- plugin.debugLog("Set resource pack to external link for player " + player.getName() + ": " + packLink);
- } else {
- plugin.debugLog("External resource pack link not set or is empty!");
- }
- } else {
- plugin.debugLog("Resource pack settings are not enabled in the config.");
- }
}
@EventHandler
@@ -68,14 +42,17 @@ public void onInventoryClick(InventoryClickEvent event) {
ItemMeta meta = clicked.getItemMeta();
PersistentDataContainer container = meta.getPersistentDataContainer();
+ UUID npcId = songSelectionGUI.getBardId();
if (container.has(new NamespacedKey(plugin, "songName"), PersistentDataType.STRING)) {
String actualSongName = container.get(new NamespacedKey(plugin, "songName"), PersistentDataType.STRING);
Song selectedSong = songManager.getSongByName(actualSongName);
if (selectedSong != null) {
plugin.debugLog("Song selected: " + selectedSong.getDisplayName());
- NPC bardNpc = songSelectionGUI.getBardNpc();
- songManager.playSongForNearbyPlayers(player, bardNpc, selectedSong, true);
+
+ songManager.songStarter.put(npcId, player);
+
+ songManager.playSongForNearbyPlayers(player, npcId, selectedSong, true);
player.closeInventory();
} else {
plugin.debugLog("Song not found for name: " + actualSongName);
@@ -94,14 +71,17 @@ public void onInventoryClick(InventoryClickEvent event) {
player.closeInventory();
}
case "stopSong" -> {
- if (player.hasPermission("bard.stop.any") || player.equals(songManager.getSongStarter())) {
- if (songManager.isSongPlaying()) {
- songManager.stopCurrentSong();
+ if (songManager.isSongPlaying(npcId)) {
+ boolean canStop = player.hasPermission("bard.stop.any") || player.equals(songManager.getSongStarter(npcId));
+ if (canStop) {
+ songManager.stopCurrentSong(npcId);
player.closeInventory();
- messageUtil.sendParsedMessage(player, "Song ended");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("Song ended"));
+ } else {
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("You can only stop your own songs."));
}
} else {
- messageUtil.sendParsedMessage(player, "You can only stop your own songs.");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("No song is currently playing."));
}
}
default -> plugin.debugLog("Invalid action: " + action);
@@ -111,5 +91,4 @@ public void onInventoryClick(InventoryClickEvent event) {
}
}
}
-
}
\ No newline at end of file
diff --git a/src/main/java/me/xidentified/tavernbard/listeners/MythicMobInteractListener.java b/src/main/java/me/xidentified/tavernbard/listeners/MythicMobInteractListener.java
new file mode 100644
index 0000000..8d057ad
--- /dev/null
+++ b/src/main/java/me/xidentified/tavernbard/listeners/MythicMobInteractListener.java
@@ -0,0 +1,35 @@
+package me.xidentified.tavernbard.listeners;
+
+import io.lumine.mythic.bukkit.events.MythicMobInteractEvent;
+import io.lumine.mythic.core.mobs.ActiveMob;
+import me.xidentified.tavernbard.TavernBard;
+import org.bukkit.Bukkit;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+public class MythicMobInteractListener implements Listener {
+ private final TavernBard plugin;
+ public MythicMobInteractListener(TavernBard plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler
+ public void onMythicMobInteract(MythicMobInteractEvent event) {
+ ActiveMob interactedMob = event.getActiveMob();
+ plugin.debugLog("MythicMob interacted with: " + interactedMob.getUniqueId());
+
+ if (!Bukkit.getPluginManager().isPluginEnabled("MythicMobs")) {
+ plugin.debugLog("MythicMobs is not enabled.");
+ return;
+ }
+
+ if (!interactedMob.getType().getConfig().getBoolean("Options.IsBard")) {
+ plugin.debugLog("MythicMob " + interactedMob.getUniqueId() + " is not a bard according to its configuration.");
+ return;
+ }
+
+ plugin.debugLog("Attempting to handle interaction for MythicMob: " + interactedMob.getUniqueId());
+ plugin.handleInteraction(interactedMob.getUniqueId(), event.getPlayer());
+ }
+
+}
diff --git a/src/main/java/me/xidentified/tavernbard/listeners/ResourcePackListener.java b/src/main/java/me/xidentified/tavernbard/listeners/ResourcePackListener.java
new file mode 100644
index 0000000..42934b3
--- /dev/null
+++ b/src/main/java/me/xidentified/tavernbard/listeners/ResourcePackListener.java
@@ -0,0 +1,38 @@
+package me.xidentified.tavernbard.listeners;
+
+import me.xidentified.tavernbard.TavernBard;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+
+public class ResourcePackListener implements Listener {
+ private final TavernBard plugin;
+
+ public ResourcePackListener(TavernBard plugin) {
+ this.plugin = plugin;
+ }
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ Player player = event.getPlayer();
+ applyResourcePackToPlayer(player);
+ }
+
+ public void applyResourcePackToPlayer(Player player) {
+ FileConfiguration config = plugin.getConfig();
+
+ if (config.getBoolean("resource_pack.external-host.enabled")) {
+ String packLink = config.getString("resource_pack.external-host.pack-link");
+ if (packLink != null && !packLink.isEmpty()) {
+ player.setResourcePack(packLink);
+ plugin.debugLog("Set resource pack to external link for player " + player.getName() + ": " + packLink);
+ } else {
+ plugin.debugLog("External resource pack link not set or is empty!");
+ }
+ } else {
+ plugin.debugLog("Resource pack settings are not enabled in the config.");
+ }
+ }
+
+}
diff --git a/src/main/java/me/xidentified/tavernbard/managers/CooldownManager.java b/src/main/java/me/xidentified/tavernbard/managers/CooldownManager.java
new file mode 100644
index 0000000..befd6fe
--- /dev/null
+++ b/src/main/java/me/xidentified/tavernbard/managers/CooldownManager.java
@@ -0,0 +1,26 @@
+package me.xidentified.tavernbard.managers;
+
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+public class CooldownManager {
+ private final Map cooldowns = new HashMap<>();
+ public static final long ADD_SONG_COOLDOWN = TimeUnit.SECONDS.toMillis(60); // 60 seconds cooldown
+
+ public void setCooldown(Player player) {
+ cooldowns.put(player.getUniqueId(), System.currentTimeMillis() + ADD_SONG_COOLDOWN);
+ }
+
+ public long getTimeLeft(Player player) {
+ return cooldowns.getOrDefault(player.getUniqueId(), 0L) - System.currentTimeMillis();
+ }
+
+ public boolean isOnCooldown(Player player) {
+ return cooldowns.containsKey(player.getUniqueId()) && getTimeLeft(player) > 0;
+ }
+
+}
diff --git a/src/main/java/me/xidentified/tavernbard/managers/QueueManager.java b/src/main/java/me/xidentified/tavernbard/managers/QueueManager.java
index ba73b9d..09c0be1 100644
--- a/src/main/java/me/xidentified/tavernbard/managers/QueueManager.java
+++ b/src/main/java/me/xidentified/tavernbard/managers/QueueManager.java
@@ -2,131 +2,105 @@
import me.xidentified.tavernbard.Song;
import me.xidentified.tavernbard.TavernBard;
-import me.xidentified.tavernbard.util.MessageUtil;
-import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
import java.util.*;
+import java.util.concurrent.TimeUnit;
public class QueueManager {
private final SongManager songManager;
-<<<<<<< Updated upstream
- private final MessageUtil messageUtil;
- protected final Queue songQueue = new LinkedList<>();
-=======
private final CooldownManager cooldownManager;
private final Map> npcSongQueues = new HashMap<>();
private final Map> npcPlayersVotedToSkip = new HashMap<>();
private final Map npcSkipVotesCount = new HashMap<>();
private final TavernBard plugin;
->>>>>>> Stashed changes
private final int MAX_QUEUE_SIZE;
- private final Set playersVotedToSkip = new HashSet<>();
- private final TavernBard plugin;
- private int skipVotesCount = 0;
- private final Map playerCooldowns = new HashMap<>(); // cooldown for adding songs to queue
- private final long EIGHT_HOURS_IN_MILLIS = 8 * 60 * 60 * 1000; // 8hrs in milliseconds
- public QueueManager(TavernBard plugin, SongManager songManager) {
+ public QueueManager(TavernBard plugin, SongManager songManager, CooldownManager cooldownManager) {
this.plugin = plugin;
this.songManager = songManager;
-<<<<<<< Updated upstream
- this.messageUtil = plugin.getMessageUtil();
-=======
this.cooldownManager = cooldownManager;
->>>>>>> Stashed changes
this.MAX_QUEUE_SIZE = plugin.getConfig().getInt("max-queue-size", 10);
- long COOLDOWN_CLEANUP_PERIOD = 36000L; // 30 mins
- Bukkit.getScheduler().runTaskTimer(plugin, this::cleanupOldCooldowns, COOLDOWN_CLEANUP_PERIOD, COOLDOWN_CLEANUP_PERIOD);
}
- public void addSongToQueue(Song song, Player player) {
- plugin.debugLog("Attempting to add song to queue: " + song.getDisplayName() + " by " + (player != null ? player.getName() : "NULL"));
+ public void addSongToQueue(UUID bardEntityId, Song song, @NotNull Player player) {
+ npcSongQueues.computeIfAbsent(bardEntityId, k -> new LinkedList<>());
- if (isOnCooldown(player)) {
- plugin.debugLog("Player is on cooldown. Cannot add song to queue: " + song.getDisplayName() + " by " + player.getName());
- messageUtil.sendParsedMessage(player, "You need to wait before queueing another song.");
+ if (cooldownManager.isOnCooldown(player)) {
+ long timeLeft = TimeUnit.MILLISECONDS.toSeconds(cooldownManager.getTimeLeft(player));
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("You can't add another song for " + timeLeft + " seconds!"));
return;
}
- if (songQueue.size() >= MAX_QUEUE_SIZE) {
- messageUtil.sendParsedMessage(player, "The queue is full! Please wait for a few songs to finish.");
+ if (npcSongQueues.size() >= MAX_QUEUE_SIZE) {
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("The queue is full! Please wait for a few songs to finish."));
return;
}
- player.sendMessage("§aThe song has been added to the queue.");
- songQueue.add(new Song(song.getNamespace(), song.getName(), song.getDisplayName(), song.getArtist(), song.getDuration(), player.getUniqueId()));
- updateCooldown(player);
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("The song has been added to the queue."));
+ npcSongQueues.get(bardEntityId).add(new Song(song.getNamespace(), song.getName(), song.getDisplayName(), song.getArtist(), song.getDuration(), player.getUniqueId()));
+ cooldownManager.setCooldown(player);
plugin.debugLog("Last song added to queue by: " + (song.getAddedByName() != null ? song.getAddedByName() : "NULL"));
}
- public Song getNextSongFromQueue() {
- return songQueue.poll();
+ public Song getNextSongFromQueue(UUID bardEntityId) {
+ Queue queue = npcSongQueues.get(bardEntityId);
+ return (queue != null) ? queue.poll() : null;
}
- public Queue getQueueStatus() {
- return new LinkedList<>(songQueue);
+ public Queue getQueueStatus(UUID bardEntityId) {
+ Queue queue = npcSongQueues.get(bardEntityId);
+ return (queue != null) ? new LinkedList<>(queue) : new LinkedList<>();
}
- public void voteToSkip(Player player) {
+ public void voteToSkip(Player player, UUID bardEntityId) {
+ // Ensure NPC has skip vote data
+ npcPlayersVotedToSkip.computeIfAbsent(bardEntityId, k -> new HashSet<>());
+ npcSkipVotesCount.putIfAbsent(bardEntityId, 0);
+
+ // Retrieve NPC-specific skip votes and count
+ Set playersVotedToSkip = npcPlayersVotedToSkip.get(bardEntityId);
+ int skipVotesCount = npcSkipVotesCount.get(bardEntityId);
+ // Check if the player has already voted to skip
if (playersVotedToSkip.contains(player.getUniqueId())) {
- messageUtil.sendParsedMessage(player, "You have already voted to skip this song.");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("You have already voted to skip this song."));
return;
}
+ // Add the player's vote to skip
playersVotedToSkip.add(player.getUniqueId());
- skipVotesCount++;
+ npcSkipVotesCount.put(bardEntityId, ++skipVotesCount);
- int nearbyPlayersCount = (int) songManager.bardNpc.getEntity().getLocation().getWorld().getPlayers().stream()
- .filter(nearbyPlayer -> nearbyPlayer.getLocation().distance(songManager.bardNpc.getEntity().getLocation()) <= songManager.songPlayRadius)
+ // Calculate the number of nearby players to the NPC
+ int nearbyPlayersCount = (int) plugin.getEntityFromUUID(player.getWorld(), bardEntityId).getLocation().getWorld().getPlayers().stream()
+ .filter(nearbyPlayer -> nearbyPlayer.getLocation().distance(plugin.getEntityFromUUID(player.getWorld(), bardEntityId).getLocation()) <= songManager.songPlayRadius)
.count();
- if (songManager.isSongPlaying() && skipVotesCount > nearbyPlayersCount / 2) {
- songManager.stopCurrentSong();
- resetSkipVotes();
- Song nextSong = getNextSongFromQueue();
+ // Check if the song should be skipped based on the majority vote
+ if (songManager.isSongPlaying(bardEntityId) && skipVotesCount > nearbyPlayersCount / 2) {
+ songManager.stopCurrentSong(bardEntityId);
+ resetSkipVotes(bardEntityId);
+ Song nextSong = getNextSongFromQueue(bardEntityId);
if (nextSong != null) {
- songManager.playSongForNearbyPlayers(songManager.songStarter, songManager.bardNpc, nextSong, true);
+ Player songStarter = songManager.getSongStarter(bardEntityId);
+ if (songStarter != null) {
+ songManager.playSongForNearbyPlayers(songStarter, bardEntityId, nextSong, true);
+ }
}
- messageUtil.sendParsedMessage(player, "The song has been skipped due to majority vote.");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("The song has been skipped due to majority vote."));
} else {
- messageUtil.sendParsedMessage(player, "You have voted to skip the current song.");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("You have voted to skip the current song."));
}
}
- // When a song ends or is skipped, call to reset queue votes
- private void resetSkipVotes() {
- playersVotedToSkip.clear();
- skipVotesCount = 0;
- }
-
-
- //Handle cooldowns for adding songs to queue
- public boolean isOnCooldown(Player player) {
- if (!playerCooldowns.containsKey(player.getUniqueId())) {
- return false;
- }
-
- long lastAdded = playerCooldowns.get(player.getUniqueId());
- long currentTimestamp = System.currentTimeMillis();
- long COOLDOWN_DURATION = 60000;
- boolean onCooldown = (currentTimestamp - lastAdded) < COOLDOWN_DURATION;
-
- plugin.debugLog("Player: " + player.getName() + " Last Added: " + lastAdded + " Current Time: " + currentTimestamp + " On Cooldown: " + onCooldown);
-
- return onCooldown;
- }
-
- public void updateCooldown(Player player) {
- plugin.debugLog("Updating cooldown for player: " + player.getName());
- playerCooldowns.put(player.getUniqueId(), System.currentTimeMillis());
- }
-
- public void cleanupOldCooldowns() {
- long currentTimestamp = System.currentTimeMillis();
- playerCooldowns.entrySet().removeIf(entry -> currentTimestamp - entry.getValue() > EIGHT_HOURS_IN_MILLIS);
+ // When a song ends or is skipped, call to reset queue votes
+ private void resetSkipVotes(UUID bardEntityId) {
+ Set playersVotedToSkip = npcPlayersVotedToSkip.get(bardEntityId);
+ if (playersVotedToSkip != null) playersVotedToSkip.clear();
+ npcSkipVotesCount.put(bardEntityId, 0);
}
}
diff --git a/src/main/java/me/xidentified/tavernbard/managers/SongManager.java b/src/main/java/me/xidentified/tavernbard/managers/SongManager.java
index 25e3c05..bb10e4b 100644
--- a/src/main/java/me/xidentified/tavernbard/managers/SongManager.java
+++ b/src/main/java/me/xidentified/tavernbard/managers/SongManager.java
@@ -1,51 +1,62 @@
package me.xidentified.tavernbard.managers;
+import io.lumine.mythic.bukkit.MythicBukkit;
+import io.lumine.mythic.core.mobs.ActiveMob;
import me.xidentified.tavernbard.*;
+import me.xidentified.tavernbard.BardTrait;
import me.xidentified.tavernbard.util.MessageUtil;
+import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
-import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.title.Title;
import org.bukkit.*;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
public class SongManager {
-
private final TavernBard plugin;
private final QueueManager queueManager;
private final EconomyManager economyManager;
private final ItemCostManager itemCostManager;
- private final SongSelectionGUI songSelectionGUI;
private final List songs;
- private boolean isSongPlaying = false;
protected final double songPlayRadius;
private final int defaultSongDuration;
- protected Player songStarter = null;
- private Song currentSong;
- private int currentSongTaskId = -1;
- protected NPC bardNpc;
+ public final Map songStarter = new ConcurrentHashMap<>();
+ private final Map isSongPlaying = new ConcurrentHashMap<>();
+ private final Map currentSong = new ConcurrentHashMap<>();
+ private final Map currentSongTaskId = new ConcurrentHashMap<>();
+ public final Map bardNpcs = new ConcurrentHashMap<>();
public SongManager(TavernBard plugin) {
this.plugin = plugin;
- this.queueManager = new QueueManager(this.plugin, this);
+ this.queueManager = new QueueManager(this.plugin, this, plugin.getCooldownManager());
this.economyManager = new EconomyManager(this.plugin);
this.songs = loadSongsFromConfig();
this.songPlayRadius = plugin.getConfig().getDouble("song-play-radius", 20.0);
this.defaultSongDuration = plugin.getConfig().getInt("default-song-duration", 180);
- this.songSelectionGUI = new SongSelectionGUI(this.plugin, this, bardNpc, this.plugin.getMessageUtil());
this.itemCostManager = new ItemCostManager(
plugin.getConfig().getString("item-cost.item", "GOLD_NUGGET"),
plugin.getConfig().getInt("item-cost.amount", 3),
plugin.getConfig().getBoolean("item-cost.enabled", false),
- plugin // pass plugin instance to log warnings if needed
+ plugin
);
}
+ public SongSelectionGUI getSongSelectionGUIForNPC(UUID npcId) {
+ UUID bardNpc = bardNpcs.get(npcId);
+ if (bardNpc == null) {
+ return null;
+ }
+ return new SongSelectionGUI(plugin, plugin.getSongManager(), bardNpc);
+ }
+
// Reload songs from config
public void reloadSongs() {
plugin.reloadConfig();
@@ -75,46 +86,49 @@ private List loadSongsFromConfig() {
return loadedSongs;
}
-
- public void playSongForNearbyPlayers(Player player, NPC bardNpc, Song selectedSong, boolean chargePlayer) {
+ public void playSongForNearbyPlayers(@NotNull Player player, @NotNull UUID npcId, @NotNull Song selectedSong, boolean chargePlayer) {
MessageUtil messageUtil = this.plugin.getMessageUtil();
- songStarter = player;
- this.bardNpc = bardNpc;
+ CooldownManager cooldownManager = this.plugin.getCooldownManager();
+ UUID bardNpc = bardNpcs.get(npcId);
+ Object message = selectedSong.getDisplayName();
+
+ if (bardNpc == null) {
+ plugin.getLogger().severe("Could not retrieve NPC for ID: " + npcId);
+ return;
+ }
- plugin.debugLog("Attempting to play song: " + selectedSong.getDisplayName() + " for " + (songStarter != null ? songStarter.getName() : "Unknown Player"));
+ plugin.debugLog("Attempting to play song: " + message + " for " + songStarter.get(player.getUniqueId()));
// Check if item cost is enabled, return if they can't afford it
- if (chargePlayer && itemCostManager.isEnabled() && !itemCostManager.canAfford(player) && !queueManager.isOnCooldown(player)) {
- messageUtil.sendParsedMessage(player, "You need " + itemCostManager.getCostAmount() + " " + itemCostManager.formatEnumName(itemCostManager.getCostItem().name()) + "(s) to play a song!");
+ if (!cooldownManager.isOnCooldown(player) && chargePlayer && itemCostManager.isEnabled() && !itemCostManager.canAfford(player)) {
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("You need " + itemCostManager.getCostAmount() + " " + itemCostManager.formatEnumName(itemCostManager.getCostItem().name()) + "(s) to play a song!"));
return;
}
-
// Check if economy is enabled
- if(!queueManager.isOnCooldown(player) && chargePlayer && plugin.getConfig().getBoolean("economy.enabled")) {
+ if(!cooldownManager.isOnCooldown(player) && chargePlayer && plugin.getConfig().getBoolean("economy.enabled")) {
double costPerSong = plugin.getConfig().getDouble("economy.cost-per-song");
// Check and charge the player
if(!economyManager.chargePlayer(player, costPerSong)) {
- messageUtil.sendParsedMessage(player, "You need " + costPerSong + " coins to play a song!");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("You need " + costPerSong + " coins to play a song!"));
return;
} else {
- messageUtil.sendParsedMessage(player, "Paid " + costPerSong + " coins to play a song!");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("Paid " + costPerSong + " coins to play a song!"));
}
}
-
- if (!queueManager.isOnCooldown(player) && chargePlayer && itemCostManager.isEnabled()) {
+ if (!cooldownManager.isOnCooldown(player) && chargePlayer && itemCostManager.isEnabled()) {
itemCostManager.deductCost(player);
- messageUtil.sendParsedMessage(player, "Charged " + itemCostManager.getCostAmount() + " " + itemCostManager.formatEnumName(itemCostManager.getCostItem().name()) + "(s) to play a song!");
+ player.sendMessage(plugin.getMessageUtil().convertToUniversalFormat("Charged " + itemCostManager.getCostAmount() + " " + itemCostManager.formatEnumName(itemCostManager.getCostItem().name()) + "(s) to play a song!"));
}
// If something is already playing, add song to queue
- if (isSongPlaying()) {
- queueManager.addSongToQueue(selectedSong, player);
+ if (isSongPlaying(npcId)) {
+ queueManager.addSongToQueue(npcId, selectedSong, player);
return;
}
- setSongPlaying(true);
- Location bardLocation = bardNpc.getEntity().getLocation();
+ setSongPlaying(npcId, true);
+ Location bardLocation = Objects.requireNonNull(plugin.getEntityFromUUID(player.getWorld(), bardNpc)).getLocation();
plugin.debugLog("Playing sound reference: " + selectedSong.getSoundReference());
// Play song and show title to players within bard's radius
@@ -122,73 +136,100 @@ public void playSongForNearbyPlayers(Player player, NPC bardNpc, Song selectedSo
if (nearbyPlayer.getLocation().distance(bardLocation) <= songPlayRadius) {
nearbyPlayer.playSound(bardLocation, selectedSong.getSoundReference(), 1.0F, 1.0F);
- // Parse song display name and artist
- var mm = MiniMessage.miniMessage();
- Component parsedDisplayNameComponent = mm.deserialize(selectedSong.getDisplayName());
+ // Convert display name to a universally formatted string
+ String universalFormattedString = messageUtil.convertToUniversalFormat(selectedSong.getDisplayName());
+
+ if (messageUtil.isPaper()) {
+ // If Paper, parse the string back to a Component for displaying
+ Component parsedDisplayNameComponent = messageUtil.parse(universalFormattedString);
+ Component mainTitle = Component.text("");
+ Component subtitle = Component.text("Now playing: ", NamedTextColor.YELLOW)
+ .append(parsedDisplayNameComponent);
+
+ Title title = Title.title(mainTitle, subtitle);
+ nearbyPlayer.showTitle(title);
+ } else {
+ // If Spigot, use the universally formatted string directly
+ nearbyPlayer.sendTitle("", "Now playing: " + universalFormattedString, 10, 70, 20);
+ }
+ }
- // Sending the title
- Component mainTitle = Component.text("");
- Component subtitle = Component.text("Now playing: ", NamedTextColor.GOLD)
- .append(parsedDisplayNameComponent);
+ // Set current song
+ currentSong.put(npcId, new Song(selectedSong.getNamespace(), selectedSong.getName(), selectedSong.getDisplayName(), selectedSong.getArtist(), selectedSong.getDuration(), songStarter.get(npcId).getUniqueId()));
- Title title = Title.title(mainTitle, subtitle);
- nearbyPlayer.showTitle(title);
+ // Update now playing info
+ SongSelectionGUI gui = getSongSelectionGUIForNPC(npcId);
+ if (gui != null) {
+ gui.updateNowPlayingInfo();
}
- currentSong = new Song(selectedSong.getNamespace(), selectedSong.getName(), selectedSong.getDisplayName(), selectedSong.getArtist(), selectedSong.getDuration(), songStarter.getUniqueId());
- songSelectionGUI.updateNowPlayingInfo();
}
- plugin.debugLog("Sound play attempt complete");
+ plugin.debugLog("Sound play attempt completed.");
- int taskId = Bukkit.getScheduler().runTaskTimer(plugin,
- () -> bardNpc.getEntity().getWorld().spawnParticle(Particle.NOTE, bardNpc.getEntity().getLocation().add(0, 2.5, 0), 1),
- 0L, 20L ).getTaskId();
+ int taskId = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
+ Entity bardEntity = plugin.getEntityFromUUID(player.getWorld(), npcId);
+ if (bardEntity != null) {
+ Location particleLocation = bardEntity.getLocation().add(0, 2.5, 0);
+ bardEntity.getWorld().spawnParticle(Particle.NOTE, particleLocation, 1);
+ } else {
+ plugin.debugLog("Entity with UUID " + npcId + " is null when trying to spawn particles.");
+ }
+ }, 0L, 20L).getTaskId();
long songDurationInTicks = selectedSong.getDuration() * 20L;
- currentSongTaskId = taskId;
+ currentSongTaskId.put(npcId, taskId);
Bukkit.getScheduler().runTaskLater(plugin, () -> {
plugin.debugLog("Song ended. Attempting to play next song in the queue.");
Bukkit.getScheduler().cancelTask(taskId);
- setSongPlaying(false);
- playNextSong(player);
+ setSongPlaying(npcId, false);
+ playNextSong(npcId, player);
}, songDurationInTicks);
}
// Stops the current song for nearby players
- public void stopCurrentSong() {
- if (isSongPlaying() && currentSongTaskId != -1) {
- Bukkit.getScheduler().cancelTask(currentSongTaskId);
- setSongPlaying(false);
-
- // Gather players around NPC radius
- for (Player nearbyPlayer : bardNpc.getEntity().getLocation().getWorld().getPlayers()) {
- if (nearbyPlayer.getLocation().distance(bardNpc.getEntity().getLocation()) <= songPlayRadius) {
- // Stop any sounds that are playing nearby
- nearbyPlayer.stopAllSounds();
+ public void stopCurrentSong(UUID npcId) {
+ if (isSongPlaying(npcId) && currentSongTaskId.containsKey(npcId) && currentSongTaskId.get(npcId) != -1) {
+ Bukkit.getScheduler().cancelTask(currentSongTaskId.get(npcId));
+ setSongPlaying(npcId, false);
+
+ UUID bardNpc = bardNpcs.get(npcId);
+ if (bardNpc != null) {
+ World world = Bukkit.getServer().getWorlds().get(0);
+ Entity entity = plugin.getEntityFromUUID(world, bardNpc);
+ if (entity instanceof Player bardPlayer) {
+ for (Player nearbyPlayer : bardPlayer.getWorld().getPlayers()) {
+ if (nearbyPlayer.getLocation().distance(bardPlayer.getLocation()) <= songPlayRadius) {
+ nearbyPlayer.stopAllSounds();
+ }
+ }
+ }
+
+ SongSelectionGUI gui = getSongSelectionGUIForNPC(npcId);
+ if (gui != null) {
+ gui.updateNowPlayingInfo();
}
- songSelectionGUI.updateNowPlayingInfo();
- currentSong = null;
}
- playNextSong(songStarter);
- songStarter = null;
+ currentSong.remove(npcId);
+ playNextSong(npcId, songStarter.get(npcId));
+ songStarter.remove(npcId);
}
}
- public void playNextSong(Player songStarter) {
+ public void playNextSong(UUID npcId, Player songStarter) {
// Attempt to play next song in queue
- Song nextSong = queueManager.getNextSongFromQueue();
- if (nextSong != null) {
- playSongForNearbyPlayers(songStarter, bardNpc, nextSong, false); // Charge player: false
+ Song nextSong = queueManager.getNextSongFromQueue(npcId);
+ if (nextSong != null && npcId != null) {
+ playSongForNearbyPlayers(songStarter, npcId, nextSong, false);
}
}
- public boolean isSongPlaying() {
- return isSongPlaying;
+ public boolean isSongPlaying(UUID npcId) {
+ return isSongPlaying.getOrDefault(npcId, false);
}
- public void setSongPlaying(boolean songPlaying) {
- isSongPlaying = songPlaying;
+ private void setSongPlaying(UUID npcId, boolean status) {
+ isSongPlaying.put(npcId, status);
}
public List getSongs() {
@@ -202,11 +243,54 @@ public Song getSongByName(String actualSongName) {
.orElse(null);
}
- public Player getSongStarter() {
- return songStarter;
+ public Player getSongStarter(UUID playerId) {
+ return songStarter.get(playerId);
}
- public Song getCurrentSong() {
- return currentSong;
+ public Song getCurrentSong(UUID npcId) {
+ return currentSong.get(npcId);
}
+
+
+ public UUID getNearestBard(Player player, double searchRadius) {
+ double closestDistanceSquared = searchRadius * searchRadius;
+ UUID closestBard = null;
+
+ // Get all entities within the search radius
+ List nearbyEntities = player.getNearbyEntities(searchRadius, searchRadius, searchRadius);
+
+ for (Entity entity : nearbyEntities) {
+ // Check for Citizens NPC
+ if (CitizensAPI.getNPCRegistry().isNPC(entity)) {
+ NPC npc = CitizensAPI.getNPCRegistry().getNPC(entity);
+ if (!npc.hasTrait(BardTrait.class)) {
+ continue;
+ }
+ }
+ // Check for MythicMob
+ else if (Bukkit.getPluginManager().isPluginEnabled("MythicMobs")) {
+ if (!MythicBukkit.inst().getAPIHelper().isMythicMob(entity)) {
+ continue;
+ }
+ ActiveMob activeMob = MythicBukkit.inst().getAPIHelper().getMythicMobInstance(entity);
+ if (!activeMob.getType().getConfig().getBoolean("Options.IsBard")) {
+ continue;
+ }
+ }
+ // If not an NPC or MythicMob, skip this entity
+ else continue;
+
+ // Calculate squared distance
+ double distanceSquared = entity.getLocation().distanceSquared(player.getLocation());
+
+ // Update if another entity is closer
+ if (distanceSquared < closestDistanceSquared) {
+ closestDistanceSquared = distanceSquared;
+ closestBard = entity.getUniqueId();
+ }
+ }
+
+ return closestBard;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/me/xidentified/tavernbard/util/MessageUtil.java b/src/main/java/me/xidentified/tavernbard/util/MessageUtil.java
index 0af5911..4721982 100644
--- a/src/main/java/me/xidentified/tavernbard/util/MessageUtil.java
+++ b/src/main/java/me/xidentified/tavernbard/util/MessageUtil.java
@@ -2,27 +2,66 @@
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.entity.Player;
+import org.bukkit.ChatColor;
public class MessageUtil {
private final MiniMessage miniMessage;
private final FileConfiguration config;
+ private final boolean isPaper;
public MessageUtil(FileConfiguration config) {
this.miniMessage = MiniMessage.miniMessage();
this.config = config;
+ this.isPaper = checkIfPaperServer();
+ }
+
+ private boolean checkIfPaperServer() {
+ try {
+ // Attempt to access a Paper-specific class or method
+ Class.forName("com.destroystokyo.paper.PaperConfig");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ public boolean isPaper() {
+ return this.isPaper;
}
public Component parse(String message) {
- return miniMessage.deserialize(message);
+ if (isPaper()) {
+ // For Paper, parse using MiniMessage
+ return miniMessage.deserialize(message);
+ } else {
+ // For Spigot, translate color codes and convert to a Component
+ String translatedMessage = ChatColor.translateAlternateColorCodes('&', message);
+ return Component.text(translatedMessage);
+ }
}
public String getConfigMessage(String path, String defaultValue) {
return config.getString(path, defaultValue);
}
- public void sendParsedMessage(Player player, String message) {
- player.sendMessage(parse(message));
+ // Trying to get messages to work on both Spigot and Paper servers regardless of how you enter color codes
+ public String convertToUniversalFormat(String message) {
+ Component component;
+
+ // First, try parsing as MiniMessage (handles MiniMessage-style tags)
+ try {
+ component = miniMessage.deserialize(message);
+ } catch (Exception e) {
+ // If parsing fails, assume it's traditional color codes and convert
+ String translatedMessage = ChatColor.translateAlternateColorCodes('&', message);
+ component = Component.text(translatedMessage);
+ }
+
+ // Convert the Component to a universally compatible String format
+ String universalString = LegacyComponentSerializer.legacySection().serialize(component);
+ return universalString;
}
+
}
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 1706daa..acf1f64 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -44,5 +44,8 @@ item-cost:
item: GOLD_NUGGET
amount: 3
+mythic-mobs: #Optional - if you want to use certain MythicMobs as bards, set their names below
+ - Mob1
+ - Mob2
debug_mode: false
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 785906e..81ecebc 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -4,8 +4,16 @@ main: me.xidentified.tavernbard.TavernBard
author: xIdentified
api-version: '1.20'
depend: [Citizens]
-softdepend:
- - Vault
+softdepend: [Vault, MythicMobs]
+
+libraries:
+ - net.kyori:adventure-api:4.14.0
+ - net.kyori:adventure-platform-bukkit:4.3.1
+ - net.kyori:adventure-text-minimessage:4.14.0
+ - net.kyori:adventure-text-serializer-legacy:4.14.0
+ - net.kyori:adventure-text-serializer-gson:4.14.0
+ - net.kyori:adventure-text-serializer-plain:4.14.0
+
commands:
bard:
description: Command for TavernBard actions.