From 79a09651ab05dd2acb3b0e0ca8edf1655cd0d882 Mon Sep 17 00:00:00 2001 From: PocketMars <51007356+PocketMars@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:40:28 -0500 Subject: [PATCH] Preparations for public host This commit adds a lot of new features, optimizations and bugfixes to prepare the bot to be hosted publicly. Updates include a prefix changing command, fixes with box generation to prevent softlocks in later levels, and making edits to game messages instead of sending a new message after each update. Smaller optimizations were made as well, such as removing games from the HashMap when a user quits to save memory. --- src/main/java/Bot.java | 20 +++++++- src/main/java/Commands.java | 91 ++++++++++++++++++++++++++++++------- src/main/java/Game.java | 26 ++++++++--- src/main/java/Grid.java | 2 +- 4 files changed, 113 insertions(+), 26 deletions(-) diff --git a/src/main/java/Bot.java b/src/main/java/Bot.java index 2587883..7164a16 100644 --- a/src/main/java/Bot.java +++ b/src/main/java/Bot.java @@ -2,22 +2,38 @@ import net.dv8tion.jda.api.AccountType; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; +import net.dv8tion.jda.api.entities.Guild; import javax.security.auth.login.LoginException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.HashMap; public class Bot { - static String prefix = "!"; + static HashMap prefixes = new HashMap(); public static void main(String[] args) throws LoginException, IOException { JDABuilder builder = new JDABuilder(AccountType.BOT); String token = new String(Files.readAllBytes(Paths.get("token.txt"))); builder.setToken(token); builder.setStatus(OnlineStatus.ONLINE); - builder.setActivity(Activity.playing("!play to play Sokoban!")); + builder.setActivity(Activity.playing("@Sokobot for info!")); builder.addEventListeners(new Commands()); builder.build(); } + + static void setPrefix(Guild guild, String prefix) + { + prefixes.put(guild, prefix); + } + + static String getPrefix(Guild guild) + { + if (!prefixes.containsKey(guild)) + { + return "!"; + } + return prefixes.get(guild); + } } \ No newline at end of file diff --git a/src/main/java/Commands.java b/src/main/java/Commands.java index 3a38474..534dd55 100644 --- a/src/main/java/Commands.java +++ b/src/main/java/Commands.java @@ -1,6 +1,10 @@ import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.guild.GuildLeaveEvent; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -11,7 +15,15 @@ import java.util.HashMap; public class Commands extends ListenerAdapter { HashMap games = new HashMap(); - ArrayList commands = new ArrayList(Arrays.asList("w", "a", "s", "d", "up", "left", "down", "right", "r", Bot.prefix + "play", Bot.prefix + "continue", Bot.prefix + "stop")); + ArrayList commandsPrefix = new ArrayList(Arrays.asList("play", "continue", "stop")); + ArrayList commandsNoPrefix = new ArrayList(Arrays.asList("w", "a", "s", "d", "up", "left", "down", "right", "r")); + public void onGuildLeave(GuildLeaveEvent event) //removes bot's stored prefix for a server if removed from that server + { + if (Bot.prefixes.containsKey(event.getGuild())) + { + Bot.prefixes.remove(event.getGuild()); + } + } public void onGuildMessageReceived(GuildMessageReceivedEvent event) { if (event.getAuthor().isBot() && event.getMessage().getEmbeds().get(0).getTitle().charAt(0) == 'L') @@ -21,26 +33,55 @@ public class Commands extends ListenerAdapter { event.getMessage().addReaction("U+2B06").queue(); event.getMessage().addReaction("U+2B07").queue(); event.getMessage().addReaction("U+1F504").queue(); + if (games.containsKey(event.getJDA().getUserById(event.getMessage().getEmbeds().get(0).getFields().get(0).getValue().substring(10, event.getMessage().getEmbeds().get(0).getFields().get(0).getValue().length() - 1)))) + { + games.get(event.getJDA().getUserById(event.getMessage().getEmbeds().get(0).getFields().get(0).getValue().substring(10, event.getMessage().getEmbeds().get(0).getFields().get(0).getValue().length() - 1))).setGameMessage(event.getMessage()); + } return; } - String[] args = event.getMessage().getContentRaw().split("\\s+"); - if (args[0].toLowerCase().equals(Bot.prefix + "info")) + String[] args = event.getMessage().getContentRaw().split("\\s+"); + if (args[0].toLowerCase().equals(Bot.getPrefix(event.getGuild()) + "prefix")) { - event.getChannel().sendMessage(info().build()).queue(); + if (event.getMember().hasPermission(Permission.ADMINISTRATOR)) { + if (args.length == 2 && args[1].length() == 1) { + Bot.setPrefix(event.getGuild(), args[1].toLowerCase()); + event.getChannel().sendMessage("Prefix successfully changed to ``" + Bot.getPrefix(event.getGuild()) + "``.").queue(); + } else { + event.getChannel().sendMessage("``" + args[1] + "`` is not a valid prefix!").queue(); + } + event.getMessage().delete().queue(); + } + else + { + event.getChannel().sendMessage(event.getAuthor().getAsMention() + ", you do not have permission to use this command.").queue(); + } } - else if (commands.contains(args[0].toLowerCase())) + else if ((commandsNoPrefix.contains(args[0].toLowerCase())) || (Character.toString(args[0].toLowerCase().charAt(0)).equals(Bot.getPrefix(event.getGuild())) && commandsPrefix.contains(args[0].toLowerCase().substring(1)))) { if (!games.containsKey(event.getAuthor())) { - games.put(event.getAuthor(), new Game()); + games.put(event.getAuthor(), new Game(event.getAuthor())); } String userInput = args[0].toLowerCase(); - if (Character.toString(userInput.charAt(0)).equals(Bot.prefix)) + if (Character.toString(userInput.charAt(0)).equals(Bot.getPrefix(event.getGuild()))) { userInput = userInput.substring(1, userInput.length()); } - games.get(event.getAuthor()).run(event.getChannel(), userInput); + games.get(event.getAuthor()).run(event.getGuild(), event.getChannel(), userInput); + if (userInput.equals("stop")) //remove game from hashmap when player quits + { + if (games.containsKey(event.getAuthor())) + { + games.remove(event.getAuthor()); + } + } + event.getMessage().delete().queue(); + } + else if (args[0].toLowerCase().equals(Bot.getPrefix(event.getGuild()) + "info") || event.getMessage().getMentionedUsers().get(0).equals(event.getJDA().getSelfUser())) + { + event.getChannel().sendMessage(info(event.getGuild()).build()).queue(); + event.getMessage().delete().queue(); } } public void onGuildMessageReactionAdd(GuildMessageReactionAddEvent event) { @@ -50,8 +91,9 @@ public class Commands extends ListenerAdapter { } if (!games.containsKey(event.getMember().getUser())) { - games.put(event.getMember().getUser(), new Game()); + games.put(event.getMember().getUser(), new Game(event.getMember().getUser())); } + boolean reactionCommand = true; String userInput = ""; switch (event.getReactionEmote().toString()) { @@ -70,10 +112,16 @@ public class Commands extends ListenerAdapter { case "RE:U+1f504": userInput = "r"; break; + default: + reactionCommand = false; } - games.get(event.getMember().getUser()).run(event.getChannel(), userInput); + if (reactionCommand) + { + games.get(event.getMember().getUser()).run(event.getGuild(), event.getChannel(), userInput); + } + event.getReaction().removeReaction(event.getUser()).queue(); } - EmbedBuilder info() + EmbedBuilder info(Guild guild) { EmbedBuilder info = new EmbedBuilder(); info.setTitle("Sokobot"); @@ -82,25 +130,34 @@ public class Commands extends ListenerAdapter { info.setColor(0xdd2e53); info.addField("How to Play", "You are a **Sokoban** :flushed:.\nYour job is to push **boxes** :brown_square: on top of their **destinations** :negative_squared_cross_mark:.", false); info.addField("Features", ":white_small_square:**Infinite levels**\nThe maps in Sokobot are randomly generated, increasing in difficulty as you progress.\n:white_small_square:**Varied controls**\nSokobot has multiple control options to improve the player's experience, including reactions and wasd commands!\n:white_small_square:**Simultaneous games**\nThanks to the power of Java HashMaps:tm:, multiple users can use the bot at the same time without interfering with one another.", false); - info.addField("Commands", ("``" + Bot.prefix + "play`` can be used to start a game if you are not currently in one.\n``" + Bot.prefix + "stop`` can be used to stop your active game at any time.\n``" + Bot.prefix + "info`` provides some useful details about the bot and rules of the game."), false); + info.addField("Commands", ("``" + Bot.getPrefix(guild) + "play`` can be used to start a game if you are not currently in one.\n``" + Bot.getPrefix(guild) + "stop`` can be used to stop your active game at any time.\n``" + Bot.getPrefix(guild) + "info`` provides some useful details about the bot and rules of the game."), false); info.addField("Source code", "https://github.com/PolyMarsDev/Sokobot", false); info.addField("Latest build", "https://github.com/PolyMarsDev/Sokobot/releases", false); info.setFooter("created by PolyMars", "https://avatars0.githubusercontent.com/u/51007356?s=460&u=4eb8fd498421a2eee9781edfbadf654386cf06c7&v=4"); return info; } - public static void sendGameEmbed(MessageChannel channel, String level, String game) + public static void sendGameEmbed(MessageChannel channel, String level, String game, User user) { EmbedBuilder embed = new EmbedBuilder(); embed.setTitle("Level " + level); embed.setDescription(game); - embed.setFooter("Enter direction (up, down, left, right, or wasd) or r to reset"); + embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "Player: " + user.getAsMention(), false); channel.sendMessage(embed.build()).queue(); } - public static void sendWinEmbed(MessageChannel channel, String level) + public static void updateGameEmbed(Message message, String level, String game, User user) + { + EmbedBuilder embed = new EmbedBuilder(); + embed.setTitle("Level " + level); + embed.setDescription(game); + embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "Player: " + user.getAsMention(), false); + message.editMessage(embed.build()).queue(); + } + public static void sendWinEmbed(Guild guild, Message message, String level) { EmbedBuilder embed = new EmbedBuilder(); embed.setTitle("You win!"); - embed.setDescription("Type ``" + Bot.prefix + "continue`` to continue to Level " + level + " or ``" + Bot.prefix + "stop`` to quit "); - channel.sendMessage(embed.build()).queue(); + embed.setDescription("Type ``" + Bot.getPrefix(guild) + "continue`` to continue to Level " + level + " or ``" + Bot.getPrefix(guild) + "stop`` to quit "); + embed.setFooter("You can also press any reaction to continue."); + message.editMessage(embed.build()).queue(); } } diff --git a/src/main/java/Game.java b/src/main/java/Game.java index 11ed665..acf6642 100644 --- a/src/main/java/Game.java +++ b/src/main/java/Game.java @@ -1,11 +1,24 @@ +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.User; public class Game { + public Message gameMessage; + public User user; boolean gameActive = false; public int level = 1; int width = 9; int height = 6; Grid grid = new Grid(width, height, level); + public Game(User user) + { + this.user = user; + } + public void setGameMessage(Message message) + { + gameMessage = message; + } public void newGame(MessageChannel channel) { if (!gameActive) @@ -16,15 +29,16 @@ public class Game { grid = new Grid(width, height, level); gameActive = true; - Commands.sendGameEmbed(channel, String.valueOf(level), grid.toString()); + Commands.sendGameEmbed(channel, String.valueOf(level), grid.toString(), user); + } } - public void run(MessageChannel channel, String userInput) + public void run(Guild guild, MessageChannel channel, String userInput) { if (userInput.equals("stop") && gameActive) { - channel.sendMessage("Thanks for playing!").queue(); - gameActive = false; + channel.sendMessage("Thanks for playing, " + user.getAsMention() + "!").queue(); + gameActive = false; } if (userInput.equals("play") && !gameActive) @@ -53,7 +67,7 @@ public class Game { grid.reset(); } if (!grid.hasWon()) { //need to check again - Commands.sendGameEmbed(channel, String.valueOf(level), grid.toString()); + Commands.updateGameEmbed(gameMessage, String.valueOf(level), grid.toString(), user); } } if (grid.hasWon()) { @@ -65,7 +79,7 @@ public class Game { { height += 1; } - Commands.sendWinEmbed(channel, String.valueOf(level)); + Commands.sendWinEmbed(guild, gameMessage, String.valueOf(level)); grid = new Grid(width, height, level); } } diff --git a/src/main/java/Grid.java b/src/main/java/Grid.java index 91b5128..a913d48 100644 --- a/src/main/java/Grid.java +++ b/src/main/java/Grid.java @@ -97,7 +97,7 @@ public class Grid int y = rand.nextInt(height - 4) + 2; for (int j = 0; j < i; j++) { - while ((x == boxes[j].getX() && y == boxes[j].getY()) || (x == 2 && y == 2)) + while ((x == boxes[j].getX() && y == boxes[j].getY()) || (x == 2 && y == 2) || (x - 1 == boxes[j].getX() && y == boxes[j].getY()) || (x + 1 == boxes[j].getX() && y == boxes[j].getY())) { x = rand.nextInt(width - 4) + 2; y = rand.nextInt(height - 4) + 2;