From ae190f3fa085146eb5b77f23e5fbd7c73bc93ee8 Mon Sep 17 00:00:00 2001 From: Benjamin Elsdon Date: Mon, 25 Apr 2022 21:53:10 +0800 Subject: [PATCH] Send Mail Command! Almost done! Wooooo! --- .../command/commands/SendMailCommand.java | 194 +++++++++++++++++- .../grasscutter/database/DatabaseHelper.java | 4 + .../emu/grasscutter/game/GenshinPlayer.java | 8 +- src/main/java/emu/grasscutter/game/Mail.java | 14 +- .../packet/recv/HandlerGetMailItemReq.java | 1 - .../packet/send/PacketGetAllMailRsp.java | 6 +- 6 files changed, 201 insertions(+), 26 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java index 036a261b5..452ac475e 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java @@ -3,30 +3,202 @@ package emu.grasscutter.command.commands; import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.GenshinPlayer; import emu.grasscutter.game.Mail; import emu.grasscutter.server.packet.send.PacketMailChangeNotify; import java.time.Instant; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Locale; -@Command(label = "sendmail", usage = "sendmail") +@Command(label = "sendmail", usage = "sendmail [templateId]", + description = "Sends mail to the specified user. The usage of this command changes based on it's composition state.", permission = "server.sendmail") public class SendMailCommand implements CommandHandler { + // TODO: You should be able to do /sendmail and then just send subsequent messages until you finish + // However, due to the current nature of the command system, I don't think this is possible without rewriting + // the command system (again). For now this will do + + // Key = User that is constructing the mail. + private static HashMap mailBeingConstructed = new HashMap(); + + // Yes this is awful and I hate it. @Override public void execute(GenshinPlayer sender, List args) { - // This is literally so I can receive mail for some reason. - if(sender == null) { - // This is my uuid in my test server. This is just for testing. - // If someone pulled this please put your uuid to receive mail using /sendmail - // until I actually make a proper /sendmail command. - sender = Grasscutter.getGameServer().getPlayerByUid(7006); + int senderId; + if(sender != null) { + senderId = sender.getUid(); + } else { + senderId = -1; } - sender.sendMail(new Mail(new Mail.MailContent("Test", "This is a test"), - new ArrayList(){{add(new Mail.MailItem(23411 ));}}, - Instant.now().getEpochSecond() + 4000)); - sender.dropMessage("Check your inbox"); + if (!mailBeingConstructed.containsKey(senderId)) { + switch (args.size()) { + case 1: + MailBuilder mailBuilder; + switch (args.get(0).toLowerCase()) { + case "help": + CommandHandler.sendMessage(sender, this.getClass().getAnnotation(Command.class).description() + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); + return; + case "all": + mailBuilder = new MailBuilder(true, new Mail()); + break; + default: + if (DatabaseHelper.getPlayerById(Integer.parseInt(args.get(0))) != null) { + mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail()); + break; + } else { + CommandHandler.sendMessage(sender, "The user with an id of '" + args.get(0) + "' does not exist"); + return; + } + } + mailBeingConstructed.put(senderId, mailBuilder); + CommandHandler.sendMessage(sender, "Starting composition of message.\nPlease use `/sendmail ` to continue.\nYou can use `/sendmail stop` at any time"); + break; + case 2: + CommandHandler.sendMessage(sender, "Mail templates coming soon implemented..."); + return; + default: + CommandHandler.sendMessage(sender, "Invalid arguments.\nUsage `/sendmail <userId|all|help> [templateId]`"); + return; + } + } else { + MailBuilder mailBuilder = mailBeingConstructed.get(senderId); + + if (args.size() >= 1) { + switch (args.get(0).toLowerCase()) { + case "stop": + mailBeingConstructed.remove(senderId); + CommandHandler.sendMessage(sender, "Message sending cancelled"); + return; + case "finish": + if (mailBuilder.constructionStage == 3) { + if(mailBuilder.sendToAll == false) { + Grasscutter.getGameServer().getPlayerByUid(mailBuilder.recipient, true).sendMail(mailBuilder.mail); + CommandHandler.sendMessage(sender, "Message sent to user " + mailBuilder.recipient + "!"); + } else { + // TODO: More testing required. This probably won't work for online players if DatabaseHelper.getPlayerById(string) didn't work. + for (GenshinPlayer player : DatabaseHelper.getAllPlayers()) { + player.sendMail(mailBuilder.mail); + } + CommandHandler.sendMessage(sender, "Message sent to all users!"); + } + mailBeingConstructed.remove(senderId); + } else { + CommandHandler.sendMessage(sender, "Message composition not at final stage.\nPlease use `/sendmail " + getConstructionArgs(mailBuilder.constructionStage) + "` or `/sendmail stop` to cancel"); + } + return; + case "help": + CommandHandler.sendMessage(sender, "Please use `/sendmail " + getConstructionArgs(mailBuilder.constructionStage) + "`"); + return; + default: + switch (mailBuilder.constructionStage) { + case 0: + String title = String.join(" ", args.subList(0, args.size())); + mailBuilder.mail.mailContent.title = title; + CommandHandler.sendMessage(sender, "Message title set as '" + title + "'.\nUse '/sendmail <content>' to continue."); + mailBuilder.constructionStage++; + break; + case 1: + String contents = String.join(" ", args.subList(0, args.size())); + mailBuilder.mail.mailContent.content = contents; + CommandHandler.sendMessage(sender, "Message contents set as '" + contents + "'.\nUse '/sendmail <sender>' to continue."); + mailBuilder.constructionStage++; + break; + case 2: + String msgSender = String.join(" ", args.subList(0, args.size())); + mailBuilder.mail.mailContent.sender = msgSender; + CommandHandler.sendMessage(sender, "Message sender set as '" + msgSender + "'.\nUse '/sendmail <itemId|itemName|finish> [amount] [level]' to continue."); + mailBuilder.constructionStage++; + break; + case 3: + // Literally just copy-pasted from the give command lol. + int item, lvl, amount = 1; + switch (args.size()) { + default: // *No args* + CommandHandler.sendMessage(sender, "Usage: give [player] <itemId|itemName> [amount]"); + return; + case 1: // <itemId|itemName> + try { + item = Integer.parseInt(args.get(0)); + lvl = 1; + } catch (NumberFormatException ignored) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendMessage(sender, "Invalid item id."); + return; + } + break; + case 2: // <itemId|itemName> [amount] + lvl = 1; + item = Integer.parseInt(args.get(0)); + amount = Integer.parseInt(args.get(1)); + break; + case 3: // <itemId|itemName> [amount] [level] + try { + item = Integer.parseInt(args.get(0)); + amount = Integer.parseInt(args.get(1)); + lvl = Integer.parseInt(args.get(2)); + + } catch (NumberFormatException ignored) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendMessage(sender, "Invalid item or player ID."); + return; + } + break; + } + mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl)); + CommandHandler.sendMessage(sender, String.format("Attached %s of %s (level %s) to the message.\nContinue adding more items or use `/sendmail finish` to send the message.", amount, item, lvl)); + } + break; + } + } else { + CommandHandler.sendMessage(sender, "Invalid arguments \n Please use `/sendmail " + getConstructionArgs(mailBuilder.constructionStage)); + } + } + } + + public String getConstructionArgs(int stage) { + switch (stage) { + case 0: + return "<title>"; + case 1: + return "<message>"; + case 2: + return "<sender>"; + case 3: + return "<itemId|itemName|finish> [amount] [level]"; + default: + Thread.dumpStack(); + return "ERROR: invalid construction stage " + stage + ". Check console for stacktrace."; + } + } + + public static class MailBuilder { + public int recipient; + public boolean sendToAll; + public int constructionStage; + public Mail mail; + + public MailBuilder(int recipient, Mail mail) { + this.recipient = recipient; + this.sendToAll = false; + this.constructionStage = 0; + this.mail = mail; + } + + public MailBuilder(boolean sendToAll, Mail mail) { + if (sendToAll) { + this.recipient = 0; + this.sendToAll = true; + this.constructionStage = 0; + this.mail = mail; + } else { + Grasscutter.getLogger().error("Please use MailBuilder(int, mail) when not sending to all"); + Thread.dumpStack(); + } + } } } diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index fc24b70e6..aa2fbe251 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -90,6 +90,10 @@ public final class DatabaseHelper { return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("username", username)).delete().getDeletedCount() > 0; } + public static List<GenshinPlayer> getAllPlayers() { + return DatabaseManager.getDatastore().find(GenshinPlayer.class).stream().toList(); + } + public static GenshinPlayer getPlayerById(int id) { return DatabaseManager.getDatastore().find(GenshinPlayer.class).filter(Filters.eq("_id", id)).first(); } diff --git a/src/main/java/emu/grasscutter/game/GenshinPlayer.java b/src/main/java/emu/grasscutter/game/GenshinPlayer.java index 0fafeb3cd..e6474990f 100644 --- a/src/main/java/emu/grasscutter/game/GenshinPlayer.java +++ b/src/main/java/emu/grasscutter/game/GenshinPlayer.java @@ -6,6 +6,7 @@ import java.util.*; import dev.morphia.annotations.*; import emu.grasscutter.GenshinConstants; import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GenshinData; import emu.grasscutter.data.def.PlayerLevelData; import emu.grasscutter.database.DatabaseHelper; @@ -603,10 +604,13 @@ public class GenshinPlayer { public List<Mail> getAllMail() { return this.mail; } public void sendMail(Mail message) { - this.mail.add(message); message._id = this.mail.size() + 1; + this.mail.add(message); this.save(); - this.sendPacket(new PacketMailChangeNotify(this, message)); + Grasscutter.getLogger().info("Message sent to user [" + this.getUid() + ":" + this.getNickname() + "]!"); + if(this.getSession() != null) { + this.sendPacket(new PacketMailChangeNotify(this, message)); + } // TODO: setup a way for the mail notification to show up when someone receives mail when they were offline } public boolean deleteMail(int mailId) { diff --git a/src/main/java/emu/grasscutter/game/Mail.java b/src/main/java/emu/grasscutter/game/Mail.java index 67a592a33..4d1f79d9f 100644 --- a/src/main/java/emu/grasscutter/game/Mail.java +++ b/src/main/java/emu/grasscutter/game/Mail.java @@ -20,15 +20,7 @@ public class Mail { public int stateValue; public Mail() { - _id = 1; - mailContent = new MailContent("No title set...", "No content set..."); - itemList = new ArrayList<>(); - sendTime = 0; - expireTime = 0; - importance = 0; // Starred mail, 0 = No star, 1 = Star. - isRead = true; - isAttachmentGot = true; - stateValue = 1; // Different mailboxes, 1 = Default, 3 = Gift-box. + this(new MailContent(), new ArrayList<MailItem>(), (int) Instant.now().getEpochSecond() + 604800); // TODO: add expire time to send mail command } public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime) { @@ -49,10 +41,10 @@ public class Mail { this.itemList = itemList; this.sendTime = (int) Instant.now().getEpochSecond(); this.expireTime = expireTime; - this.importance = importance; + this.importance = importance; // Starred mail, 0 = No star, 1 = Star. this.isRead = false; this.isAttachmentGot = false; - this.stateValue = state; + this.stateValue = state; // Different mailboxes, 1 = Default, 3 = Gift-box. } public int getId() { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetMailItemReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetMailItemReq.java index 59195a4b3..f00bf911e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetMailItemReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetMailItemReq.java @@ -13,7 +13,6 @@ public class HandlerGetMailItemReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - Grasscutter.getLogger().info("Mail Item Req"); GetMailItemReqOuterClass.GetMailItemReq req = GetMailItemReqOuterClass.GetMailItemReq.parseFrom(payload); session.send(new PacketGetMailItemRsp(session.getPlayer(), req.getMailIdListList())); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailRsp.java index 756b6f347..4632054b7 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailRsp.java @@ -39,9 +39,13 @@ public class PacketGetAllMailRsp extends GenshinPacket { List<MailData> mailDataList = new ArrayList<MailData>(); for (Mail message : player.getAllMail()) { + if(message.stateValue == 1) { // Make sure it isn't a gift - if (message.expireTime < Instant.now().getEpochSecond()) { // Make sure the message isn't expired (The game won't show expired mail, but I don't want to send unnecessary information). + Grasscutter.getLogger().info("a"); + if (message.expireTime > (int) Instant.now().getEpochSecond()) { // Make sure the message isn't expired (The game won't show expired mail, but I don't want to send unnecessary information). + Grasscutter.getLogger().info("b"); if(mailDataList.size() <= 1000) { // Make sure that there isn't over 1000 messages in the mailbox. (idk what will happen if there is but the game probably won't like it.) + Grasscutter.getLogger().info("c"); MailTextContent.Builder mailTextContent = MailTextContent.newBuilder(); mailTextContent.setTitle(message.mailContent.title); mailTextContent.setContent(message.mailContent.content);