diff --git a/.gitignore b/.gitignore index 6fd78ed3b..9a298a89a 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ BuildConfig.java # macOS .DS_Store +data/hk4e/announcement/ diff --git a/data/GameAnnouncementList.json b/data/GameAnnouncementList.json index f6566960a..7464b3b0f 100644 --- a/data/GameAnnouncementList.json +++ b/data/GameAnnouncementList.json @@ -57,5 +57,13 @@ "mi18n_name": "Activity" } ], - "timezone": -5 + "timezone": -5, + "alert": false, + "alert_id": 0, + "pic_list": [], + "pic_total": 0, + "pic_type_list": [], + "pic_alert": false, + "pic_alert_id": 0, + "static_sign": "" } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/command/commands/JoinCommand.java b/src/main/java/emu/grasscutter/command/commands/JoinCommand.java new file mode 100644 index 000000000..4dac15dd7 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/JoinCommand.java @@ -0,0 +1,46 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp; + +import java.util.ArrayList; +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "join", usage = "join [AvatarIDs] such as\"join 10000038 10000039\"", + description = "commands.join.description", permission = "player.join") +public class JoinCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + List avatarIds = new ArrayList<>(); + for (String arg : args) { + try { + int avatarId = Integer.parseInt(arg); + avatarIds.add(avatarId); + } catch (Exception ignored) { + ignored.printStackTrace(); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.avatarId")); + return; + } + } + + + for (int i = 0; i < args.size(); i++) { + Avatar avatar = sender.getAvatars().getAvatarById(avatarIds.get(i)); + if (avatar == null || sender.getTeamManager().getCurrentTeamInfo().contains(avatar)) { + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.avatarId")); + return; + } + sender.getTeamManager().getCurrentTeamInfo().addAvatar(avatar); + } + + // Packet + sender.getTeamManager().updateTeamEntities(new PacketChangeMpTeamAvatarRsp(sender, sender.getTeamManager().getCurrentTeamInfo())); + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java index 70fae0120..affbfa769 100644 --- a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java @@ -37,7 +37,7 @@ public final class QuestCommand implements CommandHandler { switch (cmd) { case "add" -> { - GameQuest quest = sender.getQuestManager().addQuest(questId); + GameQuest quest = targetPlayer.getQuestManager().addQuest(questId); if (quest != null) { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId)); @@ -47,7 +47,7 @@ public final class QuestCommand implements CommandHandler { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); } case "finish" -> { - GameQuest quest = sender.getQuestManager().getQuestById(questId); + GameQuest quest = targetPlayer.getQuestManager().getQuestById(questId); if (quest == null) { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); diff --git a/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java b/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java new file mode 100644 index 000000000..a40b42698 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java @@ -0,0 +1,43 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp; + +import java.util.ArrayList; +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "remove", usage = "remove [indexOfYourTeams] index start from 1", + description = "commands.remove.description", permission = "player.remove") +public class RemoveCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + List avatarIds = new ArrayList<>(); + for (String arg : args) { + try { + int avatarId = Integer.parseInt(arg); + avatarIds.add(avatarId); + } catch (Exception ignored) { + ignored.printStackTrace(); + CommandHandler.sendMessage(sender, translate("commands.remove.invalid_index")); + return; + } + } + + for (int i = 0; i < avatarIds.size(); i++) { + if (avatarIds.get(i) > sender.getTeamManager().getCurrentTeamInfo().getAvatars().size() || avatarIds.get(i) <= 0) { + CommandHandler.sendMessage(sender, translate("commands.remove.invalid_index")); + return; + } + sender.getTeamManager().getCurrentTeamInfo().removeAvatar(avatarIds.get(i) - 1); + } + + // Packet + sender.getTeamManager().updateTeamEntities(new PacketChangeMpTeamAvatarRsp(sender, sender.getTeamManager().getCurrentTeamInfo())); + } +} diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 6c3daf61a..84873ec61 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -144,16 +144,17 @@ public class Account { } public boolean hasPermission(String permission) { - if (this.permissions.contains(permission) || this.permissions.contains("*")) { - return true; - } + + if (this.permissions.contains(permission)) return true; + if(this.permissions.contains("*") && this.permissions.size() == 1) return true; + String[] permissionParts = permission.split("\\."); for (String p : this.permissions) { - if (permissionMatchesWildcard(p, permissionParts)) { - return true; - } + if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false; + if (permissionMatchesWildcard(p, permissionParts)) return true; } - return false; + + return this.permissions.contains("*"); } public boolean removePermission(String permission) { diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 7e439a1a4..7153542a4 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -16,6 +16,7 @@ import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; +import emu.grasscutter.server.dispatch.http.AnnouncementIndexHandler; import emu.grasscutter.server.dispatch.http.GachaDetailsHandler; import emu.grasscutter.server.dispatch.http.GachaRecordHandler; import emu.grasscutter.server.dispatch.json.*; @@ -443,6 +444,11 @@ public final class DispatchServer { // gacha details httpServer.get("/gacha/details", new GachaDetailsHandler()); + // announcement index + httpServer.get("/hk4e/announcement/*", new AnnouncementIndexHandler()); + httpServer.get("/sw.js", new AnnouncementIndexHandler()); + httpServer.get("/dora/lib/vue/2.6.11/vue.min.js", new AnnouncementIndexHandler()); + // static file support for plugins httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java new file mode 100644 index 000000000..7e55eac7e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java @@ -0,0 +1,61 @@ +package emu.grasscutter.server.dispatch.http; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.http.HttpContextHandler; +import express.http.Request; +import express.http.Response; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +import static emu.grasscutter.Configuration.DATA; + +public class AnnouncementIndexHandler implements HttpContextHandler { + private final String render_template; + private final String render_swjs; + private final String render_vueminjs; + + public AnnouncementIndexHandler() { + File template = new File(Utils.toFilePath(DATA("/hk4e/announcement/index.html"))); + File swjs = new File(Utils.toFilePath(DATA("/hk4e/announcement/sw.js"))); + File vueminjs = new File(Utils.toFilePath(DATA("/hk4e/announcement/vue.min.js"))); + this.render_template = template.exists() ? new String(FileUtils.read(template)) : null; + this.render_swjs = swjs.exists() ? new String(FileUtils.read(swjs)) : null; + this.render_vueminjs = vueminjs.exists() ? new String(FileUtils.read(vueminjs)) : null; + } + + @Override + public void handle(Request req, Response res) throws IOException { + if (Objects.equals(req.path(), "/sw.js")) { + res.send(render_swjs); + }else if(Objects.equals(req.path(), "/hk4e/announcement/index.html")) { + res.send(render_template); + }else if(Objects.equals(req.path(), "/dora/lib/vue/2.6.11/vue.min.js")){ + res.send(render_vueminjs); + }else{ + File renderFile = new File(Utils.toFilePath(DATA(req.path()))); + if(renderFile.exists()){ + String ext = req.path().substring(req.path().lastIndexOf(".") + 1); + switch(ext){ + case "css": + res.type("text/css"); + res.send(FileUtils.read(renderFile)); + break; + case "js": + default: + res.send(FileUtils.read(renderFile)); + break; + } + }else{ + Grasscutter.getLogger().info( "File not exist: " + req.path()); + } + } + + + } +} diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 02945e6d4..32631db1f 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -176,6 +176,10 @@ "success": "All characters have been healed.", "description": "Heal all characters in your current team." }, + "join": { + "usage": "Usage: join [AvatarIDs] such as\"join 10000038 10000039\"", + "description": "force join avatar into your team" + }, "kick": { "player_kick_player": "Player [%s:%s] has kicked player [%s:%s]", "server_kick_player": "Kicking player [%s:%s]", @@ -228,6 +232,11 @@ "reload_done": "Reload complete.", "description": "Reload server config" }, + "remove": { + "usage": "Usage: remove [indexOfYourTeams] index start from 1", + "invalid_index": "index start from 1", + "description": "force remove avatar into your team" + }, "resetConst": { "reset_all": "Reset all avatars' constellations.", "success": "Constellations for %s have been reset. Please relog to see changes.", diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index aaeac5a86..5f379540b 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -176,6 +176,10 @@ "success": "已治疗所有角色。", "description": "治疗当前队伍的角色" }, + "join": { + "usage": "用法:join <角色IDs> 例如\"join 10000038 10000039\"空格分开", + "description": "强制将角色加入到当前队伍中" + }, "kick": { "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出。", "server_kick_player": "正在踢出玩家 [%s:%s]...", @@ -228,6 +232,11 @@ "reload_done": "重载完成。", "description": "重载配置文件和数据" }, + "remove": { + "usage": "用法: remove [indexOfYourTeams] 从1开始", + "invalid_index": "下标从1开始", + "description": "强制移除队内角色" + }, "resetConst": { "reset_all": "重置所有角色的命座。", "success": "已重置 %s 的命座,重新登录后生效。", diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 2b6fe34ff..25f92f869 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -215,11 +215,24 @@ "success": "座標:%s, %s, %s\n場景ID:%s", "description": "獲取目前所在位置的座標。" }, + "quest": { + "description": "添加或完成任務", + "usage": "quest [任務ID]", + "added": "已添加任務 %s", + "finished": "已完成任務 %s", + "not_found": "未找到任務", + "invalid_id": "無效的任務ID" + }, "reload": { "reload_start": "正在重新加載設定檔。", "reload_done": "重新加載已完成。", "description": "重新加載設定檔和數據。" }, + "remove": { + "usage": "用法: remove [indexOfYourTeams] 从1开始", + "invalid_index": "下標從1開始", + "description": "强制移除對内角色" + }, "resetConst": { "reset_all": "重設所有角色的命座。", "success": "已重設 %s 的命座,重新登入後將會生效。",