From 26971e75bbdf16532d0042ba82e59205f8c358c9 Mon Sep 17 00:00:00 2001 From: hex <hex@iwakura.rip> Date: Sat, 19 Apr 2025 21:46:49 +0200 Subject: [PATCH] feat: discord -> teamchat linking, custom join/leave messages, and /team home command --- bin/main/rip/iwakura/civil/Core.class | Bin 4030 -> 7697 bytes build.gradle.kts | 2 + src/main/java/rip/iwakura/civil/Core.java | 66 +++++- src/main/java/rip/iwakura/civil/Database.java | 37 +++- src/main/java/rip/iwakura/civil/Discord.java | 104 ++++++++++ .../java/rip/iwakura/civil/commands/Team.java | 195 ++++++++++++++---- .../iwakura/civil/discord/LinkCommand.java | 56 +++++ .../rip/iwakura/civil/events/JoinHandler.java | 9 +- .../iwakura/civil/events/LeaveHandler.java | 21 ++ .../iwakura/civil/events/TeamChatHandler.java | 39 ++++ .../exceptions/InvalidChannelException.java | 12 ++ .../rip/iwakura/civil/types/CivilTeam.java | 49 ++++- 12 files changed, 531 insertions(+), 59 deletions(-) create mode 100644 src/main/java/rip/iwakura/civil/Discord.java create mode 100644 src/main/java/rip/iwakura/civil/discord/LinkCommand.java create mode 100644 src/main/java/rip/iwakura/civil/events/LeaveHandler.java create mode 100644 src/main/java/rip/iwakura/civil/events/TeamChatHandler.java create mode 100644 src/main/java/rip/iwakura/civil/exceptions/InvalidChannelException.java diff --git a/bin/main/rip/iwakura/civil/Core.class b/bin/main/rip/iwakura/civil/Core.class index 4c18cb3b66d51639c9896a289ac9ab5b71047265..62d9a58d95981dd832db10e3b8df8da70ac3cfd4 100644 GIT binary patch literal 7697 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 delta 1225 zcmY*ZTT@$A6#jNn&Ph&oLhdAmG?Ysk0xi(071OE|qGAikMOw6459NTBnA9XtwQ5?c zR%_AX@>VbPR;^MWN>z|D)6qA_7iWA|XY}0}|A6D#fHV4X_B!AC*2=fm{!Zq0U5|Tz z{QKH<0G;?!hfN?%10p<^7$PAenoB~5gW_Uuz>&{oa^_gpG9ciW2p~vTTw6t$esv|Z z2sY}7k|AUEYSd6l*2gwU#NiO|70nr|+nO+^v!$+lZsN$Kj?Hx388cL;dWi-!(s|ik zy+uHdn<aD1ELz*93t0nAXx5PuXr;@twL+o=TdBppGggaMC9KBD4ca9tpgFMvH|n@S zpo)HSSNSAv!cGBgC^M27%m|kDy({RpW~^-5KnKz~ZlPH{!n|GBEnrMqrCzI8G$-ko z4G|U=!EF+^qmvw-4y_9r9o>{S!uC6GC-r#JS`YT>xSP&+8tnVfOADShO-H{#oXkMn zZJ-YWO5vbF+Vvovb2{AVrV%yqu*AI>q4COt+5z0BW0d~%Hq$X*P%BE@kAvj$j#WM= zaR?6y*bCD+fk)PQcn@e}7}s%_EB4qeOwbPBy!Nof5oY}1Yt)fdN*|?4f5?VBCHzSb zau{%-2L*{DN_5KKYr_ov>`zKe<8h_&34v<WU+>BnEY?WvfkBUZsAL|V>NSrJt1G9I zfwF;T@tlrRg2(>Lo1`nDI=+$T@dAwo`)zoU=7VjHKAe$w8E5JDV5bwO@ruN&IQLac zXiDffk9T#vLl?q{4R{al(+}a_G`ua4PSQf4iK0GFgn!<P57d@EmMG&yrG}3L`W2xs z<i$mHNKY<j6}qx!v1k=_d`^cVO%<3I*hKABRdg{DEekGZOR%Ty9I=1->EyRoJ_Vl@ zsU;W-9M~ZFsjY$@xH*<<1b9%%Zvx(bi-mHTjj`F(B7FYPBC0rurB+Zo%0Zn!v54dn zwta)P@+H*lQ>?4l-n@vL7jf&i*pphuT{bMNpl>wwHQJVNkKznn#s21NcmP<2X$K>d z%a{^aL2jLPlnWpGUuJrg^&eY<7g(bkoB0#!;9=D?+fi0WfF}-dD#FPq8nKbTk81XM z47*XwcBo^gCD;L5a2Cnc(QCN@kGDR0BUe6&<J`Un-FOO5bMy@7`7eSE*b(s>>UtPd ziyh|8sNDpcmvK@Q@f}X{Brh#v)`tCR(QDF-RZ=}`YxvKygP6l>+=!UBwtj`Y&WIgv dFlNJ>cxyd(fw2$q2|mSVj3}F2!WX!V=s(v?=h6TG diff --git a/build.gradle.kts b/build.gradle.kts index e1607bc..3a1fbe7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,8 @@ tasks { dependencies { compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + implementation("club.minnced:discord-webhooks:0.8.4") + implementation("net.dv8tion:JDA:5.3.2") implementation("com.j256.ormlite", "ormlite-jdbc", "6.1") implementation("org.postgresql", "postgresql", "42.7.5") library("com.google.code.gson", "gson", "2.10.1") // All platform plugins diff --git a/src/main/java/rip/iwakura/civil/Core.java b/src/main/java/rip/iwakura/civil/Core.java index c04addb..1f21b89 100644 --- a/src/main/java/rip/iwakura/civil/Core.java +++ b/src/main/java/rip/iwakura/civil/Core.java @@ -1,18 +1,72 @@ package rip.iwakura.civil; import java.sql.SQLException; +import java.util.ArrayList; import java.util.logging.Level; import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import rip.iwakura.civil.events.ChatHandler; +import rip.iwakura.civil.events.TeamChatHandler; import rip.iwakura.civil.commands.Team; import rip.iwakura.civil.events.JoinHandler; +import rip.iwakura.civil.types.CivilPlayer; public class Core extends JavaPlugin { public Database database; + public Discord discord; + + public java.util.List<Player> teamChatToggled; + + public Component renderTeamChat(CivilPlayer p, Component message) { + return Component.text("TEAM") + .color(NamedTextColor.BLUE) + .decoration(TextDecoration.BOLD, true) + .appendSpace() + .append(Component.text(p.getName()) + .append(Component.text(": ")) + .append(message) + .color(NamedTextColor.WHITE).decoration(TextDecoration.BOLD, false)); + } + + public Component renderTeamChat(User author, String message) { + return Component.text("TEAM") + .color(NamedTextColor.BLUE) + .decoration(TextDecoration.BOLD, true) + .append(Component.text(" Discord", NamedTextColor.BLUE).decoration(TextDecoration.BOLD, false) + .appendSpace() + .append(Component.text(author.getName()) + .append(Component.text(": ")) + .append(Component.text(message)) + .color(NamedTextColor.WHITE).decoration(TextDecoration.BOLD, false))); + } + + + public void sendTeamMessage(CivilPlayer author, Component message) throws SQLException { + Component deserializedMessage = renderTeamChat(author, message); + discord.sendMessage(author, PlainTextComponentSerializer.plainText().serialize(message)); + for (CivilPlayer member : database.getAllPlayers(author.getTeam())) { + Player player = Bukkit.getPlayer(member.getName()); + + if (!player.isOnline()) + continue; + + player.sendMessage(deserializedMessage); + } + } @Override public void onEnable() { @@ -20,16 +74,19 @@ public class Core extends JavaPlugin { saveDefaultConfig(); + teamChatToggled = new ArrayList<>(); + try { - this.database = new Database(getConfig().getString("database.url")); + this.database = new Database(getConfig().getString("database.url"), this); } catch (SQLException e) { getLogger().log(Level.SEVERE, e.getMessage()); Bukkit.getPluginManager().disablePlugin(this); - - return; } - Team teamCommand = new Team(database); + this.discord = new Discord(this); + discord.connect(getConfig().getString("discord.token")); + + Team teamCommand = new Team(this); this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> { commands.registrar().register(teamCommand.getCommand()); @@ -37,5 +94,6 @@ public class Core extends JavaPlugin { getServer().getPluginManager().registerEvents(new JoinHandler(database), this); getServer().getPluginManager().registerEvents(new ChatHandler(this), this); + getServer().getPluginManager().registerEvents(new TeamChatHandler(this), this); } } diff --git a/src/main/java/rip/iwakura/civil/Database.java b/src/main/java/rip/iwakura/civil/Database.java index 6cabcb0..1669d2f 100644 --- a/src/main/java/rip/iwakura/civil/Database.java +++ b/src/main/java/rip/iwakura/civil/Database.java @@ -4,17 +4,17 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; -import org.apache.logging.log4j.LogManager; +import org.bukkit.Location; import org.bukkit.entity.Player; import com.j256.ormlite.dao.Dao; import com.j256.ormlite.dao.DaoManager; import com.j256.ormlite.jdbc.JdbcConnectionSource; -import com.j256.ormlite.logger.LoggerFactory; import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.table.TableUtils; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import rip.iwakura.civil.exceptions.InvalidChannelException; import rip.iwakura.civil.types.CivilPlayer; import rip.iwakura.civil.types.CivilTeam; @@ -22,28 +22,27 @@ public class Database { private final Dao<CivilPlayer, UUID> playerDao; private final Dao<CivilTeam, String> teamDao; private ConnectionSource connectionSource; + private Core core; - public Database(String uri) throws SQLException { + public Database(String uri, Core core) throws SQLException { connectionSource = new JdbcConnectionSource(uri); - TableUtils.createTableIfNotExists(connectionSource, CivilPlayer.class); TableUtils.createTableIfNotExists(connectionSource, CivilTeam.class); playerDao = DaoManager.createDao(connectionSource, CivilPlayer.class); teamDao = DaoManager.createDao(connectionSource, CivilTeam.class); + this.core = core; } public void createPlayer(Player p) throws SQLException { playerDao.create( - new CivilPlayer(p.getUniqueId(), p.getName()) - ); + new CivilPlayer(p.getUniqueId(), p.getName())); } public void createTeam(String name, String prefix) throws SQLException { teamDao.create( - new CivilTeam(name, prefix) - ); + new CivilTeam(name, prefix)); } public void joinTeam(Player p, CivilTeam team) throws SQLException { @@ -65,6 +64,10 @@ public class Database { return teamDao.queryForAll(); } + public List<CivilTeam> getAllTeams(Long channel) throws SQLException { + return teamDao.queryBuilder().where().eq("channel", channel).query(); + } + public CivilPlayer getPlayer(String name) throws SQLException { return playerDao.queryBuilder().where().eq("name", name).queryForFirst(); } @@ -80,4 +83,20 @@ public class Database { public List<CivilPlayer> getAllPlayers() throws SQLException { return playerDao.queryForAll(); } + + public List<CivilPlayer> getAllPlayers(CivilTeam team) throws SQLException { + return playerDao.queryBuilder().where().eq("team_id", team.getName()).query(); + } + + public void setHome(CivilTeam team, Location location) throws SQLException { + team.setHome(location); + teamDao.update(team); + } + + public void linkChannel(CivilTeam team, TextChannel channel) throws SQLException { + team.setChannel(channel.getIdLong()); + + team.setWebhook(channel.createWebhook("CivilCore").complete().getUrl()); + teamDao.update(team); + } } diff --git a/src/main/java/rip/iwakura/civil/Discord.java b/src/main/java/rip/iwakura/civil/Discord.java new file mode 100644 index 0000000..a344563 --- /dev/null +++ b/src/main/java/rip/iwakura/civil/Discord.java @@ -0,0 +1,104 @@ +package rip.iwakura.civil; + +import java.sql.SQLException; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.mojang.brigadier.arguments.StringArgumentType; + +import club.minnced.discord.webhook.WebhookClient; +import club.minnced.discord.webhook.WebhookClientBuilder; +import club.minnced.discord.webhook.send.WebhookMessageBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Webhook; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.requests.GatewayIntent; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import rip.iwakura.civil.discord.LinkCommand; +import rip.iwakura.civil.types.CivilPlayer; +import rip.iwakura.civil.types.CivilTeam; + +/** + * Discord + */ +public class Discord extends ListenerAdapter { + private Core core; + public JDA jda; + + public Discord(Core core) { + this.core = core; + } + + public void sendMessage(CivilPlayer p, String message) { + String webhook = p.getTeam().getWebhook(); + if (webhook != null) { + WebhookClient client = WebhookClient.withUrl(webhook); + client.send(new WebhookMessageBuilder().setUsername(p.getName()) + .setAvatarUrl("https://www.mc-heads.net/avatar/" + p.getName()).setContent(message).build()); + } + } + + public void connect(String token) { + jda = JDABuilder.createDefault(token) + .enableIntents(GatewayIntent.MESSAGE_CONTENT, GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS) + .addEventListeners(this) + .addEventListeners(new LinkCommand(core)) + .build(); + + try { + jda.awaitReady(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + registerCommands(core.getConfig().getLong("discord.guild")); + } + + public void registerCommands(Long guildId) { + Guild guild = jda.getGuildById(guildId); + + if (guild == null) { + core.getLogger().info("Guild ID not found."); + return; + } + + guild.updateCommands().addCommands(Commands.slash("link", "Link in-game team chat to a discord channel.") + .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_CHANNEL)) + .addOption(OptionType.STRING, "team", "Name of the team you want to link to the current channel.", true, + true)) + .queue(); + } + + @Override + public void onMessageReceived(MessageReceivedEvent event) { + if (event.getAuthor().isBot()) return; + + try { + List<CivilTeam> linked_teams = core.database.getAllTeams(event.getChannel().getIdLong()); + + for (CivilTeam team : linked_teams) { + List<CivilPlayer> members = core.database.getAllPlayers(team); + + for (CivilPlayer member : members) { + Player player = Bukkit.getPlayerExact(member.getName()); + + if (player == null || !player.isOnline()) continue; + + player.sendMessage(core.renderTeamChat(event.getAuthor(),event.getMessage().getContentDisplay())); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/rip/iwakura/civil/commands/Team.java b/src/main/java/rip/iwakura/civil/commands/Team.java index 3bfb7c8..8db4450 100644 --- a/src/main/java/rip/iwakura/civil/commands/Team.java +++ b/src/main/java/rip/iwakura/civil/commands/Team.java @@ -1,79 +1,200 @@ package rip.iwakura.civil.commands; import java.sql.SQLException; +import java.text.Format; -import javax.xml.crypto.Data; - +import org.bukkit.Location; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.tree.LiteralCommandNode; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; +import rip.iwakura.civil.Core; import rip.iwakura.civil.Database; +import rip.iwakura.civil.exceptions.InvalidChannelException; import rip.iwakura.civil.types.*; public class Team { private Database database; + private Core core; - public Team(Database database) { - this.database = database; + public Team(Core core) { + this.core = core; + this.database = core.database; } - private LiteralArgumentBuilder<CommandSourceStack> createTeam() { + private LiteralArgumentBuilder<CommandSourceStack> createTeam() { return Commands.literal("create") - .then( - Commands.argument("name", StringArgumentType.string()) - .then( - Commands.argument("prefix", StringArgumentType.string()) + .requires(source -> source.getSender().hasPermission("civil.team.create")) + .then( + Commands.argument("name", StringArgumentType.string()) + .then( + Commands.argument("prefix", StringArgumentType.string()) + .executes(ctx -> { + String name = StringArgumentType.getString(ctx, "name"); + String prefix = StringArgumentType.getString(ctx, "prefix"); + CommandSender sender = ctx.getSource().getSender(); + + try { + database.createTeam(name, prefix); + sender.sendMessage( + Component + .text("Successfully created team ", + NamedTextColor.GREEN) + .append(Component + .text(name, NamedTextColor.GOLD)) + .append(Component.text(" with prefix: \"")) + .append(MiniMessage.miniMessage() + .deserialize(prefix)) + .append(Component.text("\""))); + + return Command.SINGLE_SUCCESS; + } catch (SQLException e) { + ctx.getSource().getSender().sendMessage( + Component.text(e.getMessage(), NamedTextColor.RED)); + e.printStackTrace(); + return 0; + } + }))); + } + + // Send team chat message + private LiteralArgumentBuilder<CommandSourceStack> homeCommand() { + return Commands.literal("home") + .requires(sender -> sender.getSender() instanceof Player) + .executes(ctx -> { + Player p = (Player) ctx.getSource().getSender(); + + try { + CivilPlayer cPlayer = core.database.getPlayer(p); + p.teleport(cPlayer.getTeam().getHome(core)); + p.sendRichMessage("<green>You have been teleported to your team's home."); + return Command.SINGLE_SUCCESS; + } catch (SQLException e) { + e.printStackTrace(); + } + + return 0; + }).then(Commands.literal("set") + .requires(sender -> sender.getSender() instanceof Player) .executes(ctx -> { - String name = StringArgumentType.getString(ctx, "name"); - String prefix = StringArgumentType.getString(ctx, "prefix"); + Player p = (Player) ctx.getSource().getSender(); try { - database.createTeam(name, prefix); + CivilPlayer cPlayer = database.getPlayer(p); + + Location location = p.getLocation(); + + database.setHome(cPlayer.getTeam(), location); + + p.sendRichMessage("<green>Your team's home location has been set to: " + + String.format("<red>%d %d %d", Math.round(location.x()), + Math.round(location.y()), Math.round(location.z()))); return Command.SINGLE_SUCCESS; + } catch (SQLException e) { - ctx.getSource().getSender().sendMessage( - Component.text(e.getMessage(), NamedTextColor.RED)); - return 0; + e.printStackTrace(); } - }))); + + return 1; + })); + + } + + // Send team chat message + private LiteralArgumentBuilder<CommandSourceStack> teamChat() { + return Commands.literal("chat") + .requires(sender -> sender.getSender() instanceof Player) + .executes(ctx -> { + Player p = (Player) ctx.getSource().getSender(); + + if (core.teamChatToggled.contains(p)) { + core.teamChatToggled.remove(p); + p.sendRichMessage("<red>Team chat disabled."); + } else { + core.teamChatToggled.add(p); + p.sendRichMessage("<green>Team chat enabled."); + } + + return Command.SINGLE_SUCCESS; + }).then(Commands.argument("message", StringArgumentType.greedyString()) + .requires(sender -> sender.getSender() instanceof Player) + .executes(ctx -> { + CommandSender sender = ctx.getSource().getSender(); + String message = StringArgumentType.getString(ctx, "message"); + + try { + CivilPlayer p = database.getPlayer((Player) sender); + + core.sendTeamMessage(p, message); + + return Command.SINGLE_SUCCESS; + + } catch (SQLException e) { + sender.sendRichMessage("<red>Uh oh, something went wrong!"); + e.printStackTrace(); + } + + return 1; + })); + } // Add a player to a team. private LiteralArgumentBuilder<CommandSourceStack> joinTeam() { return Commands.literal("add") - .then( - Commands.argument("player", new PlayerArgument(database)) - .then( - Commands.argument("team", new TeamArgument(database)) - .executes(ctx -> { - CivilTeam civilTeam = ctx.getArgument("team", CivilTeam.class); - CivilPlayer civilPlayer = ctx.getArgument("player", - CivilPlayer.class); + .requires(source -> source.getSender().hasPermission("civil.team.add")) + .then( + Commands.argument("player", new PlayerArgument(database)) + .then( + Commands.argument("team", new TeamArgument(database)) + .executes(ctx -> { + CivilTeam civilTeam = ctx.getArgument("team", CivilTeam.class); + CivilPlayer civilPlayer = ctx.getArgument("player", + CivilPlayer.class); + CommandSender sender = ctx.getSource().getSender(); - try { - database.joinTeam(civilPlayer, civilTeam); - return Command.SINGLE_SUCCESS; - } catch (SQLException e) { - ctx.getSource().getSender().sendMessage( - Component.text(e.getMessage(), NamedTextColor.RED)); - return 0; - } - }))); + try { + database.joinTeam(civilPlayer, civilTeam); + + sender.sendMessage( + Component + .text("Successfully added ", + NamedTextColor.GREEN) + .append(Component + .text(civilPlayer.getName(), + NamedTextColor.GOLD)) + .append(Component.text(" to team ")) + .append(Component + .text(civilTeam.getName(), + NamedTextColor.AQUA))); + return Command.SINGLE_SUCCESS; + } catch (SQLException e) { + ctx.getSource().getSender().sendMessage( + Component.text(e.getMessage(), NamedTextColor.RED)); + e.printStackTrace(); + return 0; + } + }))); }; - public LiteralCommandNode<CommandSourceStack> getCommand() { + public LiteralCommandNode<CommandSourceStack> getCommand() { return Commands.literal("team") - .then(createTeam()) - .then(joinTeam()).build(); + .then(createTeam()) + .then(joinTeam()) + .then(teamChat()) + .then(homeCommand()) + .build(); } } diff --git a/src/main/java/rip/iwakura/civil/discord/LinkCommand.java b/src/main/java/rip/iwakura/civil/discord/LinkCommand.java new file mode 100644 index 0000000..c5db068 --- /dev/null +++ b/src/main/java/rip/iwakura/civil/discord/LinkCommand.java @@ -0,0 +1,56 @@ +package rip.iwakura.civil.discord; + +import java.sql.SQLException; +import java.util.List; +import java.util.logging.StreamHandler; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; +import rip.iwakura.civil.Core; +import rip.iwakura.civil.types.CivilTeam; + +/** + * LinkCommand + */ +public class LinkCommand extends ListenerAdapter { + private Core core; + + public LinkCommand(Core core) { + this.core = core; + } + + @Override + public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { + try { + if (event.getName().equals("link")) { + core.database.linkChannel(core.database.getTeam(event.getOption("team").getAsString()),event.getChannel().asTextChannel()); + event.reply("Successfully linked channel to team chat.").queue(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent event) { + try { + if (event.getName().equals("link") && event.getFocusedOption().getName().equals("team")) { + List<String> teamNames = core.database.getAllTeams().stream().map(CivilTeam::getName).collect( + Collectors.toList()); + + List<Command.Choice> options = teamNames.stream() + .filter(name -> name.startsWith(event.getFocusedOption().getValue())) + .map(name -> new Command.Choice(name, name)).collect(Collectors.toList()); + + event.replyChoices(options).queue(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/rip/iwakura/civil/events/JoinHandler.java b/src/main/java/rip/iwakura/civil/events/JoinHandler.java index 77f8082..770a8d4 100644 --- a/src/main/java/rip/iwakura/civil/events/JoinHandler.java +++ b/src/main/java/rip/iwakura/civil/events/JoinHandler.java @@ -7,6 +7,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; +import net.kyori.adventure.text.minimessage.MiniMessage; import rip.iwakura.civil.Database; public class JoinHandler implements Listener { @@ -17,9 +18,13 @@ public class JoinHandler implements Listener { } @EventHandler - public void chatHandler(PlayerJoinEvent event) throws SQLException { + public void joinHandler(PlayerJoinEvent event) throws SQLException { Player p = event.getPlayer(); - if (database.getPlayer(p) == null) database.createPlayer(p); + event.joinMessage(MiniMessage.miniMessage() + .deserialize("<gray>[<green>+<gray>] <green>" + p.getName() + "<white>")); + + if (database.getPlayer(p) == null) + database.createPlayer(p); } } diff --git a/src/main/java/rip/iwakura/civil/events/LeaveHandler.java b/src/main/java/rip/iwakura/civil/events/LeaveHandler.java new file mode 100644 index 0000000..18ce883 --- /dev/null +++ b/src/main/java/rip/iwakura/civil/events/LeaveHandler.java @@ -0,0 +1,21 @@ +package rip.iwakura.civil.events; + +import java.sql.SQLException; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import net.kyori.adventure.text.minimessage.MiniMessage; + +public class LeaveHandler implements Listener { + + @EventHandler + public void leaveHandler(PlayerQuitEvent event) throws SQLException { + Player p = event.getPlayer(); + + event.quitMessage(MiniMessage.miniMessage() + .deserialize("<gray>[<red>-<gray>] <red>" + p.getName() + "<white>")); + } +} diff --git a/src/main/java/rip/iwakura/civil/events/TeamChatHandler.java b/src/main/java/rip/iwakura/civil/events/TeamChatHandler.java new file mode 100644 index 0000000..d097852 --- /dev/null +++ b/src/main/java/rip/iwakura/civil/events/TeamChatHandler.java @@ -0,0 +1,39 @@ +package rip.iwakura.civil.events; + +import java.sql.SQLException; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import io.papermc.paper.chat.ChatRenderer; +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import rip.iwakura.civil.Core; +import rip.iwakura.civil.Database; +import rip.iwakura.civil.types.CivilPlayer; + +public class TeamChatHandler implements Listener { + private Core core; + private Database database; + + public TeamChatHandler(Core core) { + this.core = core; + this.database = core.database; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void chatHandler(AsyncChatEvent event) throws SQLException { + if (core.teamChatToggled.contains(event.getPlayer())) { + CivilPlayer p = database.getPlayer(event.getPlayer()); + + core.sendTeamMessage(p, MiniMessage.miniMessage() + .deserialize(PlainTextComponentSerializer.plainText().serialize(event.message()))); + event.setCancelled(true); + } + } +} diff --git a/src/main/java/rip/iwakura/civil/exceptions/InvalidChannelException.java b/src/main/java/rip/iwakura/civil/exceptions/InvalidChannelException.java new file mode 100644 index 0000000..14d56ec --- /dev/null +++ b/src/main/java/rip/iwakura/civil/exceptions/InvalidChannelException.java @@ -0,0 +1,12 @@ +package rip.iwakura.civil.exceptions; + +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; + +/** + * InvalidChannelException + */ +public class InvalidChannelException extends Exception { + public InvalidChannelException(Long channelId) { + super("Channel with ID: " + channelId.toString() + " could not be found."); + } +} diff --git a/src/main/java/rip/iwakura/civil/types/CivilTeam.java b/src/main/java/rip/iwakura/civil/types/CivilTeam.java index c6fb73e..b6ebf97 100644 --- a/src/main/java/rip/iwakura/civil/types/CivilTeam.java +++ b/src/main/java/rip/iwakura/civil/types/CivilTeam.java @@ -5,6 +5,8 @@ import org.bukkit.Location; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; +import rip.iwakura.civil.Core; + @DatabaseTable(tableName = "teams") public class CivilTeam { @DatabaseField(id = true) @@ -13,8 +15,23 @@ public class CivilTeam { @DatabaseField(canBeNull = false) private String prefix; - /*@DatabaseField(canBeNull = true) - private Location home;*/ + @DatabaseField(canBeNull = true) + private Long channel; + + @DatabaseField(canBeNull = true) + private String webhook; + + @DatabaseField(canBeNull = true) + private double home_x; + + @DatabaseField(canBeNull = true) + private double home_y; + + @DatabaseField(canBeNull = true) + private double home_z; + + @DatabaseField(canBeNull = true) + private String home_world; public CivilTeam() { } @@ -31,13 +48,31 @@ public class CivilTeam { public String getPrefix() { return prefix; } -/* + public void setHome(Location home) { - this.home = home; + this.home_x = home.x(); + this.home_y = home.y(); + this.home_z = home.z(); + this.home_world = home.getWorld().getName(); } - public Location getHome() { - return home; + public Location getHome(Core core) { + return new Location(core.getServer().getWorld(home_world), home_x, home_y, home_z); + } + + public void setChannel(Long channel) { + this.channel = channel; + } + + public Long getChannel() { + return channel; + } + + public void setWebhook(String webhook) { + this.webhook = webhook; + } + + public String getWebhook() { + return webhook; } - */ }