diff --git a/src/main/java/Commands.java b/src/main/java/Commands.java deleted file mode 100644 index ac28e11..0000000 --- a/src/main/java/Commands.java +++ /dev/null @@ -1,221 +0,0 @@ -import com.vdurmont.emoji.EmojiManager; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; -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; - -import java.util.*; - -public class Commands extends ListenerAdapter { - HashMap games = new HashMap<>(); - ArrayList commandsPrefix = new ArrayList<>(Arrays.asList("play", "continue", "stop")); - ArrayList commandsNoPrefix = new ArrayList<>(Arrays.asList("w", "a", "s", "d", "up", "left", "down", - "right", "r")); - - @Override - public void onGuildLeave(GuildLeaveEvent event) { - Guild guild = event.getGuild(); - Bot.removePrefix(guild.getIdLong()); - } - - @Override - public void onGuildMessageReceived(GuildMessageReceivedEvent event) { - User user = event.getAuthor(); - Member member = event.getMember(); - Message message = event.getMessage(); - TextChannel channel = event.getChannel(); - Guild guild = event.getGuild(); - String prefix = Bot.getPrefix(guild); - if (user.getId().equals(event.getJDA().getSelfUser().getId())) { - List embeds = message.getEmbeds(); - if (embeds.size() > 0) { - MessageEmbed embed = embeds.get(0); - if (embed.getTitle() != null && embed.getTitle().length() > 0) { - if (embed.getTitle().startsWith("Sokobot | Level ")) { - message.addReaction("U+2B05").queue(); - message.addReaction("U+27A1").queue(); - message.addReaction("U+2B06").queue(); - message.addReaction("U+2B07").queue(); - message.addReaction("U+1F504").queue(); - MessageEmbed.Footer footerObject = embed.getFooter(); - if (footerObject != null) { - String footer = footerObject.getText(); - if (footer != null) { - long playerId = Long.parseLong(footer.substring(10, footer.length() - 1)); - if (games.containsKey(playerId)) { - Game game = games.get(playerId); - game.setGameMessage(message); - } - } - } - } - } - } - return; - } - String[] args = message.getContentRaw().split("\\s+"); - if (args.length > 0) { - String arg = args[0].toLowerCase(); - if (arg.equals(prefix + "prefix")) { - if (!hasPermissions(guild, channel)) { - sendInvalidPermissionsMessage(user, channel); - return; - } - if (member.hasPermission(Permission.ADMINISTRATOR)) { - if (args.length == 2 && args[1].length() == 1) { - String newPrefix = args[1].toLowerCase(); - Bot.setPrefix(event.getGuild(), newPrefix); - channel.sendMessage("Prefix successfully changed to ``" + newPrefix + "``.").queue(); - } else channel.sendMessage("The prefix must be one character long!").queue(); - } else - channel.sendMessage(user.getAsMention() + ", you do not have permission to use this " + "command" + ".").queue(); - // No need to delete prefix-set command - // message.delete().queue(); - } else if (((commandsNoPrefix.contains(arg)) || (arg.length() > 0 && Character.toString(arg.charAt(0)).equals(prefix) && commandsPrefix.contains(arg.substring(1))))) { - if (!hasPermissions(guild, channel)) { - sendInvalidPermissionsMessage(user, channel); - return; - } - Game game; - if (!games.containsKey(user.getIdLong())) { - game = new Game(user); - games.put(user.getIdLong(), game); - } else game = games.get(user.getIdLong()); - String userInput = arg; - if (userInput.substring(0, 1).equals(prefix)) userInput = userInput.substring(1); - if (!game.gameActive && userInput.equals("play") && args.length == 2 && EmojiManager.isEmoji(args[1])) { - game.setPlayerEmote(args[1]); - } - game.run(event.getGuild(), channel, userInput); - if (userInput.equals("stop")) games.remove(user.getIdLong()); - if (guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE)) message.delete().queue(); - } else if ((arg.equals(prefix + "info")) || (message.getMentionedUsers().size() > 0 && message.getMentionedUsers().get(0).equals(event.getJDA().getSelfUser()))) { - if (!hasPermissions(guild, channel)) { - sendInvalidPermissionsMessage(user, channel); - return; - } - channel.sendMessage(info(event.getGuild()).build()).queue(); - if (guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE)) message.delete().queue(); - } - } - } - - private static final Collection requiredPermissions = Arrays.asList(Permission.MESSAGE_ADD_REACTION, - Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_MANAGE, Permission.MESSAGE_WRITE); - - private boolean hasPermissions(Guild guild, TextChannel channel) { - Member self = guild.getSelfMember(); - if (self.hasPermission(Permission.ADMINISTRATOR)) return true; - return self.hasPermission(channel, requiredPermissions); - } - - private void sendInvalidPermissionsMessage(User user, TextChannel channel) { - if (channel.canTalk()) { - StringBuilder requiredPermissionsDisplay = new StringBuilder(); - for (Permission requiredPermission : requiredPermissions) { - requiredPermissionsDisplay.append("`").append(requiredPermission.getName()).append("`, "); - } - if (requiredPermissionsDisplay.toString().endsWith(", ")) - requiredPermissionsDisplay = new StringBuilder(requiredPermissionsDisplay.substring(0, - requiredPermissionsDisplay.length() - 2)); - channel.sendMessage(user.getAsMention() + ", I don't have enough permissions to work properly.\nMake " + - "sure I have the following permissions: " + requiredPermissionsDisplay + "\nIf you think this is " - + "an error, please contact a server administrator.").queue(); - } - } - - @Override - public void onGuildMessageReactionAdd(GuildMessageReactionAddEvent event) { - User user = event.getUser(); - if (user.isBot()) { - return; - } - Guild guild = event.getGuild(); - MessageReaction reaction = event.getReaction(); - TextChannel channel = event.getChannel(); - channel.retrieveMessageById(event.getMessageId()).queue(message -> { - if (message.getAuthor().getId().equals(event.getJDA().getSelfUser().getId())) { - Game game; - if (!games.containsKey(user.getIdLong())) { - game = new Game(user); - games.put(user.getIdLong(), game); - } else game = games.get(user.getIdLong()); - boolean reactionCommand = true; - String userInput = ""; - switch (event.getReactionEmote().toString()) { - case "RE:U+2b05": - userInput = "left"; - break; - case "RE:U+27a1": - userInput = "right"; - break; - case "RE:U+2b06": - userInput = "up"; - break; - case "RE:U+2b07": - userInput = "down"; - break; - case "RE:U+1f504": - userInput = "r"; - break; - default: - reactionCommand = false; - break; - } - if (reactionCommand) game.run(guild, channel, userInput); - reaction.removeReaction(user).queue(); - } - }); - } - - EmbedBuilder info(Guild guild) { - EmbedBuilder info = new EmbedBuilder(); - info.setTitle("Sokobot"); - info.setThumbnail(guild.getSelfMember().getUser().getAvatarUrl()); - info.setDescription("Sokobot is a bot that lets you play Sokoban, the classic box-pushing puzzle game."); - 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.\n:white_small_square:**Custom prefixes**\nTo prevent Sokobot from conflicting with other " + "bots, admins can choose any single-character prefix to preface Sokobot's commands.", 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.\n``" + Bot.getPrefix(guild) + "prefix [character]`` can be used to " + "change the prefix the " + "bot responds to."), false); - info.addField("Add to your server", - "https://top.gg/bot/713635251703906336\nSokobot is currently in " + Bot.getShardManager().getGuilds().size() + " servers.", false); - info.addField("Source code", "https://github.com/PolyMarsDev/Sokobot", 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, User user) { - EmbedBuilder embed = new EmbedBuilder(); - embed.setTitle("Sokobot | Level " + level); - embed.setDescription(game); - embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "", false); - embed.setFooter("Game of " + user.getAsMention(), user.getAvatarUrl()); - channel.sendMessage(embed.build()).queue(); - } - - public static void updateGameEmbed(Message message, String level, String game, User user) { - EmbedBuilder embed = new EmbedBuilder(); - embed.setTitle("Sokobot | Level " + level); - embed.setDescription(game); - embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "", false); - embed.setFooter("Game of " + user.getAsMention(), user.getAvatarUrl()); - message.editMessage(embed.build()).queue(); - } - - public static void sendWinEmbed(Guild guild, Message message, String level) { - EmbedBuilder embed = new EmbedBuilder(); - embed.setTitle("Sokobot | You win!"); - 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/Bot.java b/src/main/java/me/polymarsdev/sokobot/Bot.java similarity index 73% rename from src/main/java/Bot.java rename to src/main/java/me/polymarsdev/sokobot/Bot.java index b5f2480..37f0c32 100644 --- a/src/main/java/Bot.java +++ b/src/main/java/me/polymarsdev/sokobot/Bot.java @@ -1,3 +1,8 @@ +package me.polymarsdev.sokobot; + +import me.polymarsdev.sokobot.database.Database; +import me.polymarsdev.sokobot.listener.CommandListener; +import me.polymarsdev.sokobot.listener.GameListener; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Guild; @@ -16,6 +21,15 @@ import java.util.Scanner; public class Bot { static HashMap prefixes = new HashMap<>(); + /** + * You can enable the database here. + * Set the DB Type to MySQL or SQLite, which you want to use. + * - + * You can configure login data in the Database class. + */ + private static final boolean enableDatabase = false; + private static final Database.DBType dbType = Database.DBType.SQLite; + private static ShardManager shardManager; private static Database database = null; @@ -25,16 +39,27 @@ public class Bot { File tokenFile = Paths.get("token.txt").toFile(); if (!tokenFile.exists()) { System.out.println("[ERROR] Could not find token.txt file"); - System.out.println("[ERROR] Please create a file called \"token.txt\" in the same folder as the jar " - + "file and paste in your bot token."); - return; + System.out.print("Please paste in your bot token: "); + Scanner s = new Scanner(System.in); + token = s.nextLine(); + System.out.println(); + System.out.println("[INFO] Creating token.txt - please wait"); + if (!tokenFile.createNewFile()) { + System.out.println( + "[ERROR] Could not create token.txt - please create this file and paste in your token" + + "."); + s.close(); + return; + } + Files.write(tokenFile.toPath(), token.getBytes()); + s.close(); } token = new String(Files.readAllBytes(tokenFile.toPath())); } catch (Exception ex) { ex.printStackTrace(); } if (token == null) return; - // database = new Database(Database.DBType.SQLite); + if (enableDatabase) database = new Database(dbType); if (database != null) { if (!database.isConnected()) { database = null; @@ -48,13 +73,14 @@ public class Bot { DefaultShardManagerBuilder builder = new DefaultShardManagerBuilder(token); builder.setStatus(OnlineStatus.ONLINE); builder.setActivity(Activity.playing("@Sokobot for info!")); - builder.addEventListeners(new Commands()); + builder.addEventListeners(new GameListener(), new CommandListener()); shardManager = builder.build(); Thread consoleThread = new Thread(() -> { Scanner s = new Scanner(System.in); while (s.hasNextLine()) { processCommand(s.nextLine()); } + s.close(); }); consoleThread.setDaemon(true); consoleThread.setName("Console Thread"); @@ -84,14 +110,14 @@ public class Bot { return shardManager; } - static void removePrefix(long guildId) { + public static void removePrefix(long guildId) { prefixes.remove(guildId); if (database != null) { database.update("DELETE FROM guildprefix WHERE guildId=?;", String.valueOf(guildId)); } } - static void setPrefix(Guild guild, String prefix) { + public static void setPrefix(Guild guild, String prefix) { prefixes.put(guild.getIdLong(), prefix); if (database != null) { database.update("DELETE FROM guildprefix WHERE guildId=?;", guild.getId()); @@ -99,7 +125,7 @@ public class Bot { } } - static String getPrefix(Guild guild) { + public static String getPrefix(Guild guild) { if (prefixes.containsKey(guild.getIdLong())) return prefixes.get(guild.getIdLong()); if (database != null) { try (ResultSet rs = database.query("SELECT prefix FROM guildprefix WHERE guildId=?;", guild.getId())) { diff --git a/src/main/java/Game.java b/src/main/java/me/polymarsdev/sokobot/Game.java similarity index 66% rename from src/main/java/Game.java rename to src/main/java/me/polymarsdev/sokobot/Game.java index c2d30f2..c06d1ce 100644 --- a/src/main/java/Game.java +++ b/src/main/java/me/polymarsdev/sokobot/Game.java @@ -1,3 +1,7 @@ +package me.polymarsdev.sokobot; + +import me.polymarsdev.sokobot.objects.Grid; +import me.polymarsdev.sokobot.util.GameUtil; import net.dv8tion.jda.api.entities.*; public class Game { @@ -32,7 +36,7 @@ public class Game { height = 6; grid = new Grid(width, height, level, playerEmote); gameActive = true; - Commands.sendGameEmbed(channel, String.valueOf(level), grid.toString(), user); + GameUtil.sendGameEmbed(channel, String.valueOf(level), grid.toString(), user); } } @@ -41,27 +45,37 @@ public class Game { channel.sendMessage("Thanks for playing, " + user.getAsMention() + "!").queue(); gameActive = false; } - if (userInput.equals("play") && !gameActive) { newGame(channel); } else if (gameActive) { if (!grid.hasWon()) { String direction = userInput; - if (direction.equals("up") || direction.equals("w")) { - grid.getPlayer().moveUp(); - } else if (direction.equals("down") || direction.equals("s")) { - grid.getPlayer().moveDown(); - } else if (direction.equals("left") || direction.equals("a")) { - grid.getPlayer().moveLeft(); - } else if (direction.equals("right") || direction.equals("d")) { - grid.getPlayer().moveRight(); - } else if (direction.equals("r")) { - grid.reset(); + switch (direction) { + case "up": + case "w": + grid.getPlayer().moveUp(); + break; + case "down": + case "s": + grid.getPlayer().moveDown(); + break; + case "left": + case "a": + grid.getPlayer().moveLeft(); + break; + case "right": + case "d": + grid.getPlayer().moveRight(); + break; + case "r": + grid.reset(); + break; } if (!grid.hasWon()) { TextChannel textChannel = Bot.getShardManager().getTextChannelById(channelID); if (textChannel != null) { - textChannel.retrieveMessageById(gameMessageID).queue(gameMessage -> Commands.updateGameEmbed(gameMessage, String.valueOf(level), grid.toString(), user)); + textChannel.retrieveMessageById(gameMessageID).queue(gameMessage -> GameUtil + .updateGameEmbed(gameMessage, String.valueOf(level), grid.toString(), user)); } } } @@ -75,8 +89,8 @@ public class Game { } TextChannel textChannel = Bot.getShardManager().getTextChannelById(channelID); if (textChannel != null) { - textChannel.retrieveMessageById(gameMessageID).queue(gameMessage -> Commands.sendWinEmbed(guild, - gameMessage, String.valueOf(level))); + textChannel.retrieveMessageById(gameMessageID) + .queue(gameMessage -> GameUtil.sendWinEmbed(guild, gameMessage, String.valueOf(level))); } grid = new Grid(width, height, level, playerEmote); } diff --git a/src/main/java/me/polymarsdev/sokobot/commands/GameInputCommand.java b/src/main/java/me/polymarsdev/sokobot/commands/GameInputCommand.java new file mode 100644 index 0000000..aab59a6 --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/commands/GameInputCommand.java @@ -0,0 +1,47 @@ +package me.polymarsdev.sokobot.commands; + +import com.vdurmont.emoji.EmojiManager; +import me.polymarsdev.sokobot.Bot; +import me.polymarsdev.sokobot.Game; +import me.polymarsdev.sokobot.entity.Command; +import me.polymarsdev.sokobot.event.CommandEvent; +import me.polymarsdev.sokobot.util.GameUtil; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.entities.User; + +public class GameInputCommand extends Command { + + public GameInputCommand(String name) { + super(name); + } + + @Override + public void execute(CommandEvent event) { + User user = event.getAuthor(); + String[] args = event.getArgs(); + String prefix = Bot.getPrefix(event.getGuild()); + Game game; + if (!GameUtil.hasGame(user.getIdLong())) { + game = new Game(user); + GameUtil.setGame(user.getIdLong(), game); + } else game = GameUtil.getGame(user.getIdLong()); + // + String userInput = this.getName().toLowerCase(); + if (userInput.equals("play")) { + if (!game.gameActive) { + if (args.length > 0 && EmojiManager.isEmoji(args[0])) game.setPlayerEmote(args[0]); + } else { + event.reply(user.getAsMention() + ", you already have an active game.\nUse `" + prefix + + "stop` to stop your current game first."); + } + } + Guild guild = event.getGuild(); + TextChannel channel = event.getTextChannel(); + game.run(event.getGuild(), channel, userInput); + if (userInput.equals("stop")) GameUtil.removeGame(user.getIdLong()); + if (game.gameActive && guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE)) + event.getMessage().delete().queue(); + } +} diff --git a/src/main/java/me/polymarsdev/sokobot/commands/InfoCommand.java b/src/main/java/me/polymarsdev/sokobot/commands/InfoCommand.java new file mode 100644 index 0000000..df47eb2 --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/commands/InfoCommand.java @@ -0,0 +1,53 @@ +package me.polymarsdev.sokobot.commands; + +import me.polymarsdev.sokobot.Bot; +import me.polymarsdev.sokobot.entity.Command; +import me.polymarsdev.sokobot.event.CommandEvent; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Guild; + +public class InfoCommand extends Command { + + public InfoCommand() { + super("info"); + } + + @Override + public void execute(CommandEvent event) { + Guild guild = event.getGuild(); + EmbedBuilder info = new EmbedBuilder(); + final String prefix = Bot.getPrefix(guild); + info.setTitle("Sokobot"); + info.setThumbnail(guild.getSelfMember().getUser().getAvatarUrl()); + info.setDescription("Sokobot is a bot that lets you play Sokoban, the classic box-pushing puzzle game."); + 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.\n:white_small_square:**Custom prefixes**\nTo prevent Sokobot from conflicting with other " + + "bots, admins can choose any single-character prefix to preface Sokobot's commands.", false); + info.addField("Commands", + ("``" + prefix + "play`` can be used to start a game if you are not " + "currently in " + + "one.\n``" + prefix + "stop`` can be used to stop your active game at any " + + "time.\n``" + prefix + "info`` provides some useful details about the bot and " + + "rules of " + "the game.\n``" + Bot.getPrefix(guild) + + "prefix [character]`` can be used to " + "change the prefix the " + "bot responds to."), + false); + info.addField("Add to your server", + "https://top.gg/bot/713635251703906336\nSokobot is currently in " + Bot.getShardManager() + .getGuilds().size() + " servers.", false); + /* + // Official Support Server + info.addField("Support / Feedback", + "Official Support Server: https://invite.affluentproductions.org/apserver", false); + */ + info.addField("Source code", "https://github.com/PolyMarsDev/Sokobot", false); + info.setFooter("created by PolyMars", "https://avatars0.githubusercontent" + ".com/u/51007356?s=460&u" + + "=4eb8fd498421a2eee9781edfbadf654386cf06c7&v=4"); + event.reply(info.build()); + } +} diff --git a/src/main/java/me/polymarsdev/sokobot/commands/PrefixCommand.java b/src/main/java/me/polymarsdev/sokobot/commands/PrefixCommand.java new file mode 100644 index 0000000..7224d40 --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/commands/PrefixCommand.java @@ -0,0 +1,37 @@ +package me.polymarsdev.sokobot.commands; + +import me.polymarsdev.sokobot.Bot; +import me.polymarsdev.sokobot.entity.Command; +import me.polymarsdev.sokobot.event.CommandEvent; +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.Member; +import net.dv8tion.jda.api.entities.User; + +public class PrefixCommand extends Command { + + public PrefixCommand() { + super("prefix"); + } + + @Override + public void execute(CommandEvent event) { + User user = event.getAuthor(); + Member member = event.getMember(); + String[] args = event.getArgs(); + if (args.length > 0) { + if (!member.hasPermission(Permission.ADMINISTRATOR)) { + event.reply(user.getAsMention() + ", you do not have permission to use this command."); + return; + } + String newPrefix = args[0].toLowerCase(); + if (newPrefix.length() > 1) { + event.reply(user.getAsMention() + ", the prefix must be one character long!"); + return; + } + Bot.setPrefix(event.getGuild(), newPrefix); + event.reply("Prefix successfully changed to ``" + newPrefix + "``."); + } + } +} diff --git a/src/main/java/Database.java b/src/main/java/me/polymarsdev/sokobot/database/Database.java similarity index 95% rename from src/main/java/Database.java rename to src/main/java/me/polymarsdev/sokobot/database/Database.java index 8a904ef..97162d5 100644 --- a/src/main/java/Database.java +++ b/src/main/java/me/polymarsdev/sokobot/database/Database.java @@ -1,15 +1,17 @@ +package me.polymarsdev.sokobot.database; + import java.io.File; import java.sql.*; public class Database { - enum DBType {MySQL, SQLite} + public enum DBType {MySQL, SQLite} /** * SQLite Data * Set this data if you use DBType#SQLite * - * @param filePath This can either be a relative or absolute path. + * field filePath - This can either be a relative or absolute path. * ex: sokobot.db * or: C:/sqlite/db/sokobot.db */ diff --git a/src/main/java/me/polymarsdev/sokobot/entity/Command.java b/src/main/java/me/polymarsdev/sokobot/entity/Command.java new file mode 100644 index 0000000..8d51a6d --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/entity/Command.java @@ -0,0 +1,18 @@ +package me.polymarsdev.sokobot.entity; + +import me.polymarsdev.sokobot.event.CommandEvent; + +public abstract class Command { + + private final String name; + + public Command(String name){ + this.name = name; + } + + public String getName() { + return name; + } + + public abstract void execute(CommandEvent commandEvent); +} diff --git a/src/main/java/Player.java b/src/main/java/me/polymarsdev/sokobot/entity/Player.java similarity index 96% rename from src/main/java/Player.java rename to src/main/java/me/polymarsdev/sokobot/entity/Player.java index aea126d..fead62e 100644 --- a/src/main/java/Player.java +++ b/src/main/java/me/polymarsdev/sokobot/entity/Player.java @@ -1,3 +1,7 @@ +package me.polymarsdev.sokobot.entity; + +import me.polymarsdev.sokobot.objects.Grid; + public class Player { int x = 0; diff --git a/src/main/java/me/polymarsdev/sokobot/event/CommandEvent.java b/src/main/java/me/polymarsdev/sokobot/event/CommandEvent.java new file mode 100644 index 0000000..fa3b7f7 --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/event/CommandEvent.java @@ -0,0 +1,159 @@ +package me.polymarsdev.sokobot.event; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class CommandEvent { + private static final int MAX_MESSAGES = 2; + + private final GuildMessageReceivedEvent event; + private String[] args; + + public CommandEvent(GuildMessageReceivedEvent event, String[] args) { + this.event = event; + this.args = args; + } + + public String[] getArgs() { + return args; + } + + public GuildMessageReceivedEvent getEvent() { + return event; + } + + public List getMentionedMembers() { + List mentionedMembers = new ArrayList<>(event.getMessage().getMentionedMembers()); + if (event.getMessage().getContentRaw().startsWith("<@!" + event.getJDA().getSelfUser().getId() + ">")) + mentionedMembers.remove(0); + return mentionedMembers; + } + + public void reply(String message) { + sendMessage(event.getChannel(), message); + } + + public void reply(String message, Consumer success) { + sendMessage(event.getChannel(), message, success); + } + + public void reply(String message, Consumer success, Consumer failure) { + sendMessage(event.getChannel(), message, success, failure); + } + + public void reply(MessageEmbed embed) { + event.getChannel().sendMessage(embed).queue(); + } + + public void reply(MessageEmbed embed, Consumer success) { + event.getChannel().sendMessage(embed).queue(success); + } + + public void reply(MessageEmbed embed, Consumer success, Consumer failure) { + event.getChannel().sendMessage(embed).queue(success, failure); + } + + public void reply(Message message) { + event.getChannel().sendMessage(message).queue(); + } + + public void reply(Message message, Consumer success) { + event.getChannel().sendMessage(message).queue(success); + } + + public void reply(Message message, Consumer success, Consumer failure) { + event.getChannel().sendMessage(message).queue(success, failure); + } + + public void reply(File file, String filename) { + event.getChannel().sendFile(file, filename).queue(); + } + + public void reply(String message, File file, String filename) { + String msg = message == null ? null : splitMessage(message).get(0); + if (msg == null) event.getChannel().sendFile(file, filename).queue(); + else event.getChannel().sendMessage(msg).addFile(file, filename).queue(); + } + + private void sendMessage(MessageChannel chan, String message) { + ArrayList messages = splitMessage(message); + for (int i = 0; i < MAX_MESSAGES && i < messages.size(); i++) { + chan.sendMessage(messages.get(i)).queue(); + } + } + + private void sendMessage(MessageChannel chan, String message, Consumer success) { + ArrayList messages = splitMessage(message); + for (int i = 0; i < MAX_MESSAGES && i < messages.size(); i++) { + if (i + 1 == MAX_MESSAGES || i + 1 == messages.size()) { + chan.sendMessage(messages.get(i)).queue(success); + } else { + chan.sendMessage(messages.get(i)).queue(); + } + } + } + + private void sendMessage(MessageChannel chan, String message, Consumer success, + Consumer failure) { + ArrayList messages = splitMessage(message); + for (int i = 0; i < MAX_MESSAGES && i < messages.size(); i++) { + if (i + 1 == MAX_MESSAGES || i + 1 == messages.size()) { + chan.sendMessage(messages.get(i)).queue(success, failure); + } else { + chan.sendMessage(messages.get(i)).queue(); + } + } + } + + private static ArrayList splitMessage(String stringtoSend) { + ArrayList msgs = new ArrayList<>(); + if (stringtoSend != null) { + stringtoSend = stringtoSend.replace("@everyone", "@\u0435veryone").replace("@here", "@h\u0435re").trim(); + while (stringtoSend.length() > 2000) { + int leeway = 2000 - (stringtoSend.length() % 2000); + int index = stringtoSend.lastIndexOf("\n", 2000); + if (index < leeway) index = stringtoSend.lastIndexOf(" ", 2000); + if (index < leeway) index = 2000; + String temp = stringtoSend.substring(0, index).trim(); + if (!temp.equals("")) msgs.add(temp); + stringtoSend = stringtoSend.substring(index).trim(); + } + if (!stringtoSend.equals("")) msgs.add(stringtoSend); + } + return msgs; + } + + SelfUser getSelfUser() { + return event.getJDA().getSelfUser(); + } + + public User getAuthor() { + return event.getAuthor(); + } + + public Guild getGuild() { + return event.getGuild(); + } + + public JDA getJDA() { + return event.getJDA(); + } + + public Member getMember() { + return event.getMember(); + } + + public Message getMessage() { + return event.getMessage(); + } + + public TextChannel getTextChannel() { + return event.getChannel(); + } +} \ No newline at end of file diff --git a/src/main/java/me/polymarsdev/sokobot/listener/CommandListener.java b/src/main/java/me/polymarsdev/sokobot/listener/CommandListener.java new file mode 100644 index 0000000..8759eab --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/listener/CommandListener.java @@ -0,0 +1,122 @@ +package me.polymarsdev.sokobot.listener; + +import me.polymarsdev.sokobot.Bot; +import me.polymarsdev.sokobot.Game; +import me.polymarsdev.sokobot.commands.GameInputCommand; +import me.polymarsdev.sokobot.commands.InfoCommand; +import me.polymarsdev.sokobot.commands.PrefixCommand; +import me.polymarsdev.sokobot.entity.Command; +import me.polymarsdev.sokobot.event.CommandEvent; +import me.polymarsdev.sokobot.util.GameUtil; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; + +import java.util.*; + +public class CommandListener extends ListenerAdapter { + private static final ArrayList commandsNoPrefix = new ArrayList<>( + Arrays.asList("w", "a", "s", "d", "up", "left", "down", "right", "r")); + private static final HashMap commands = new HashMap<>(); + + public CommandListener() { + List botCommands = new ArrayList<>(Arrays.asList(new InfoCommand(), new PrefixCommand())); + botCommands.addAll(Arrays.asList(new GameInputCommand("play"), new GameInputCommand("continue"), + new GameInputCommand("stop"))); + for (String cnp : commandsNoPrefix) botCommands.add(new GameInputCommand(cnp)); + for (Command command : botCommands) commands.put(command.getName().toLowerCase(), command); + System.out.println("[INFO] Loaded " + commands.size() + " commands"); + } + + + @Override + public void onGuildMessageReceived(GuildMessageReceivedEvent event) { + User user = event.getAuthor(); + Message message = event.getMessage(); + TextChannel channel = event.getChannel(); + Guild guild = event.getGuild(); + if (user.getId().equals(event.getJDA().getSelfUser().getId())) { + List embeds = message.getEmbeds(); + if (embeds.size() > 0) { + MessageEmbed embed = embeds.get(0); + if (embed.getTitle() != null && embed.getTitle().length() > 0) { + if (embed.getTitle().startsWith("Sokobot | Level ")) { + message.addReaction("U+2B05").queue(); + message.addReaction("U+27A1").queue(); + message.addReaction("U+2B06").queue(); + message.addReaction("U+2B07").queue(); + message.addReaction("U+1F504").queue(); + MessageEmbed.Footer footerObject = embed.getFooter(); + if (footerObject != null) { + String footer = footerObject.getText(); + if (footer != null) { + long playerId = Long.parseLong(footer.substring(10, footer.length() - 1)); + if (GameUtil.hasGame(playerId)) { + Game game = GameUtil.getGame(playerId); + game.setGameMessage(message); + } + } + } + } + } + } + return; + } + String msgRaw = message.getContentRaw(); + String[] args = msgRaw.split("\\s+"); + if (args.length > 0) { + boolean isMention = msgRaw.equals("<@" + event.getJDA().getSelfUser().getId() + ">") || msgRaw + .equals("<@!" + event.getJDA().getSelfUser().getId() + ">"); + String prefix = Bot.getPrefix(guild); + String arg = args[0].toLowerCase(); + boolean isCommand; + if (arg.startsWith(prefix)) { + if (commandsNoPrefix.contains(arg)) { + isCommand = true; + } else { + String commandName = arg.substring(prefix.length()).toLowerCase(); + isCommand = commands.containsKey(commandName); + if (isCommand) arg = commandName; + } + } else { + isCommand = commandsNoPrefix.contains(arg); + } + if (isCommand) { + if (!hasPermissions(guild, channel)) { + sendInvalidPermissionsMessage(user, channel); + return; + } + Command command = commands.get(arg); + if (isMention) command = commands.get("info"); + if (command == null) return; + command.execute(new CommandEvent(event, Arrays.copyOfRange(msgRaw.split("\\s+"), 1, args.length))); + } + } + } + + private static final Collection requiredPermissions = Arrays + .asList(Permission.MESSAGE_ADD_REACTION, Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_MANAGE, + Permission.MESSAGE_WRITE); + + private boolean hasPermissions(Guild guild, TextChannel channel) { + Member self = guild.getSelfMember(); + if (self.hasPermission(Permission.ADMINISTRATOR)) return true; + return self.hasPermission(channel, requiredPermissions); + } + + private void sendInvalidPermissionsMessage(User user, TextChannel channel) { + if (channel.canTalk()) { + StringBuilder requiredPermissionsDisplay = new StringBuilder(); + for (Permission requiredPermission : requiredPermissions) { + requiredPermissionsDisplay.append("`").append(requiredPermission.getName()).append("`, "); + } + if (requiredPermissionsDisplay.toString().endsWith(", ")) requiredPermissionsDisplay = new StringBuilder( + requiredPermissionsDisplay.substring(0, requiredPermissionsDisplay.length() - 2)); + channel.sendMessage(user.getAsMention() + ", I don't have enough permissions to work properly.\nMake " + + "sure I have the following permissions: " + requiredPermissionsDisplay + + "\nIf you think this is " + + "an error, please contact a server administrator.").queue(); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/polymarsdev/sokobot/listener/GameListener.java b/src/main/java/me/polymarsdev/sokobot/listener/GameListener.java new file mode 100644 index 0000000..c98577d --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/listener/GameListener.java @@ -0,0 +1,68 @@ +package me.polymarsdev.sokobot.listener; + +import me.polymarsdev.sokobot.Bot; +import me.polymarsdev.sokobot.Game; +import me.polymarsdev.sokobot.util.GameUtil; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.MessageReaction; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.guild.GuildLeaveEvent; +import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; + +public class GameListener extends ListenerAdapter { + + @Override + public void onGuildLeave(GuildLeaveEvent event) { + Guild guild = event.getGuild(); + Bot.removePrefix(guild.getIdLong()); + } + + @Override + public void onGuildMessageReactionAdd(GuildMessageReactionAddEvent event) { + User user = event.getUser(); + if (user.isBot()) { + return; + } + Guild guild = event.getGuild(); + MessageReaction reaction = event.getReaction(); + TextChannel channel = event.getChannel(); + channel.retrieveMessageById(event.getMessageId()).queue(message -> { + if (message.getAuthor().getId().equals(event.getJDA().getSelfUser().getId())) { + Game game; + if (!GameUtil.hasGame(user.getIdLong())) { + game = new Game(user); + GameUtil.setGame(user.getIdLong(), game); + } else game = GameUtil.getGame(user.getIdLong()); + boolean reactionCommand = true; + String userInput = ""; + switch (event.getReactionEmote().toString()) { + case "RE:U+2b05": + userInput = "left"; + break; + case "RE:U+27a1": + userInput = "right"; + break; + case "RE:U+2b06": + userInput = "up"; + break; + case "RE:U+2b07": + userInput = "down"; + break; + case "RE:U+1f504": + userInput = "r"; + break; + default: + reactionCommand = false; + break; + } + if (reactionCommand) game.run(guild, channel, userInput); + if (guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE)) + reaction.removeReaction(user).queue(); + } + }); + } + +} diff --git a/src/main/java/Box.java b/src/main/java/me/polymarsdev/sokobot/objects/Box.java similarity index 97% rename from src/main/java/Box.java rename to src/main/java/me/polymarsdev/sokobot/objects/Box.java index c39dfaa..6432c0b 100644 --- a/src/main/java/Box.java +++ b/src/main/java/me/polymarsdev/sokobot/objects/Box.java @@ -1,3 +1,5 @@ +package me.polymarsdev.sokobot.objects; + public class Box { int x = 0; diff --git a/src/main/java/Destination.java b/src/main/java/me/polymarsdev/sokobot/objects/Destination.java similarity index 51% rename from src/main/java/Destination.java rename to src/main/java/me/polymarsdev/sokobot/objects/Destination.java index fd9f93d..128282f 100644 --- a/src/main/java/Destination.java +++ b/src/main/java/me/polymarsdev/sokobot/objects/Destination.java @@ -1,24 +1,25 @@ -public class Destination -{ +package me.polymarsdev.sokobot.objects; + +public class Destination { int x = 0; int y = 0; Grid currentGrid; - public Destination(int x, int y, Grid currentGrid) - { + + public Destination(int x, int y, Grid currentGrid) { this.x = x; this.y = y; this.currentGrid = currentGrid; } - public boolean hasBox(Grid currentGrid) - { + + public boolean hasBox(Grid currentGrid) { return currentGrid.isWall(x, y); } - public int getX() - { + + public int getX() { return x; } - public int getY() - { + + public int getY() { return y; } } diff --git a/src/main/java/Grid.java b/src/main/java/me/polymarsdev/sokobot/objects/Grid.java similarity index 97% rename from src/main/java/Grid.java rename to src/main/java/me/polymarsdev/sokobot/objects/Grid.java index d352b12..2f8d32a 100644 --- a/src/main/java/Grid.java +++ b/src/main/java/me/polymarsdev/sokobot/objects/Grid.java @@ -1,3 +1,8 @@ +package me.polymarsdev.sokobot.objects; + +import me.polymarsdev.sokobot.entity.Player; +import me.polymarsdev.sokobot.util.Randomizer; + public class Grid { final int GROUND = 0; diff --git a/src/main/java/Tile.java b/src/main/java/me/polymarsdev/sokobot/objects/Tile.java similarity index 97% rename from src/main/java/Tile.java rename to src/main/java/me/polymarsdev/sokobot/objects/Tile.java index 6c88636..81f2235 100644 --- a/src/main/java/Tile.java +++ b/src/main/java/me/polymarsdev/sokobot/objects/Tile.java @@ -1,3 +1,5 @@ +package me.polymarsdev.sokobot.objects; + public class Tile { final int GROUND = 0; diff --git a/src/main/java/me/polymarsdev/sokobot/util/GameUtil.java b/src/main/java/me/polymarsdev/sokobot/util/GameUtil.java new file mode 100644 index 0000000..f3cc4ed --- /dev/null +++ b/src/main/java/me/polymarsdev/sokobot/util/GameUtil.java @@ -0,0 +1,61 @@ +package me.polymarsdev.sokobot.util; + +import me.polymarsdev.sokobot.Bot; +import me.polymarsdev.sokobot.Game; +import net.dv8tion.jda.api.EmbedBuilder; +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 java.util.HashMap; + +public class GameUtil { + + private static final HashMap games = new HashMap<>(); + + public static void setGame(long userId, Game game) { + games.put(userId, game); + } + + public static boolean hasGame(long userId) { + return games.containsKey(userId); + } + + public static Game getGame(long userId) { + return games.get(userId); + } + + public static void removeGame(long userId) { + games.remove(userId); + } + + public static void sendGameEmbed(MessageChannel channel, String level, String game, User user) { + EmbedBuilder embed = new EmbedBuilder(); + embed.setTitle("Sokobot | Level " + level); + embed.setDescription(game); + embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "", false); + embed.addField("Player", user.getAsMention(), false); + embed.setFooter("Game of " + user.getAsMention(), user.getAvatarUrl()); + channel.sendMessage(embed.build()).queue(); + } + + public static void updateGameEmbed(Message message, String level, String game, User user) { + EmbedBuilder embed = new EmbedBuilder(); + embed.setTitle("Sokobot | Level " + level); + embed.setDescription(game); + embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "", false); + embed.setFooter("Game of " + user.getAsMention(), user.getAvatarUrl()); + message.editMessage(embed.build()).queue(); + } + + public static void sendWinEmbed(Guild guild, Message message, String level) { + EmbedBuilder embed = new EmbedBuilder(); + embed.setTitle("Sokobot | You win!"); + 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/Randomizer.java b/src/main/java/me/polymarsdev/sokobot/util/Randomizer.java similarity index 98% rename from src/main/java/Randomizer.java rename to src/main/java/me/polymarsdev/sokobot/util/Randomizer.java index ad762ad..6ffb385 100644 --- a/src/main/java/Randomizer.java +++ b/src/main/java/me/polymarsdev/sokobot/util/Randomizer.java @@ -1,3 +1,5 @@ +package me.polymarsdev.sokobot.util; + import java.util.*; public class Randomizer{ diff --git a/src/main/resources/token.txt b/src/main/resources/token.txt index 2755fc0..ae0e28a 100644 --- a/src/main/resources/token.txt +++ b/src/main/resources/token.txt @@ -1 +1 @@ -replace this with your Discord bot token (https://discord.com/developers/applications) \ No newline at end of file +NzQwMzAzNzMzODM3MDA0OTEw.XynDlA.YutDr_eKx12ohzNurCm4ZqLMJDY \ No newline at end of file