From d261931ff793cb7b1da74089b80717a2b194f128 Mon Sep 17 00:00:00 2001
From: memetrollsXD <memetrollsxd@gmail.com>
Date: Wed, 27 Apr 2022 11:37:50 +0200
Subject: [PATCH 01/21] Update README.md

---
 README.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index d32e1ca9e..690b7cd5d 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ EN | [中文](README_zh-CN.md)
 * Friends list
 * Teleportation
 * Gacha system
-* Co-op *partially* work
+* Co-op *partially* works
 * Spawning monsters via console
 * Inventory features (recieving items/characters, upgrading items/characters, etc)
 
@@ -140,9 +140,11 @@ There is a dummy user named "Server" in every player's friends list that you can
 When you want to teleport to somewhere, use the ingame marking function on Map, click Confirm. You will see your
 character falling from a very high destination, exact location that you marked.
 
+You can also specify a set Y coordinate by renaming the map marker.
+
 # Quick Troubleshooting
 
 * If compiling wasn't successful, please check your JDK installation (JDK 17 and validated JDK's bin PATH variable)
 * My client doesn't connect, doesn't login, 4206, etc... - Mostly your proxy daemon setup is *the issue*, if using
   Fiddler make sure it running on another port except 8888
-* Startup sequence: Mongodb > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Client
+* Startup sequence: Mongodb > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Genshin Impact Client

From efe694fb06e3a08b509bad2c204d27a5e1eece58 Mon Sep 17 00:00:00 2001
From: memetrollsXD <memetrollsxd@gmail.com>
Date: Wed, 27 Apr 2022 13:20:57 +0200
Subject: [PATCH 02/21] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 690b7cd5d..16d5fc8f8 100644
--- a/README.md
+++ b/README.md
@@ -147,4 +147,4 @@ You can also specify a set Y coordinate by renaming the map marker.
 * If compiling wasn't successful, please check your JDK installation (JDK 17 and validated JDK's bin PATH variable)
 * My client doesn't connect, doesn't login, 4206, etc... - Mostly your proxy daemon setup is *the issue*, if using
   Fiddler make sure it running on another port except 8888
-* Startup sequence: Mongodb > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Genshin Impact Client
+* Startup sequence: Mongodb > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Game

From 4339810ca379d2bd32acb938f64b36aa94010f3b Mon Sep 17 00:00:00 2001
From: xtaodada <xtao@xtaolink.cn>
Date: Fri, 29 Apr 2022 17:53:42 +0800
Subject: [PATCH 03/21] Support Teleport from console

---
 README.md                                     |  6 +--
 README_zh-CN.md                               |  4 +-
 .../command/commands/TeleportCommand.java     | 49 +++++++++++++------
 3 files changed, 40 insertions(+), 19 deletions(-)

diff --git a/README.md b/README.md
index 909aa6cc5..cbc09ea3e 100644
--- a/README.md
+++ b/README.md
@@ -114,8 +114,8 @@ There is a dummy user named "Server" in every player's friends list that you can
 | clearweapons   | clearweapons                                      | player.clearweapons       | Client only  | Deletes all unequipped and unlocked weapons, including 5-star rarity ones from your inventory. | clearwpns                                       |
 | drop           | drop <itemID\|itemName> [amount]                  | server.drop               | Client only  | Drops an item around you.                                    | `d` `dropitem`                                  |
 | give           | give [player] <itemId\|itemName> [amount] [level] | player.give               | Both side    | Gives item(s) to you or the specified player.                | `g` `item` `giveitem`                           |
-| givechar       | givechar <uid> <avatarId>                 | player.givechar           | Both side    | Gives the player a specified character.                      | givec                                           |
-| giveall       | giveall [uid] [amount]             | player.giveall      | Both side    | Gives all items.      | givea                                         |
+| givechar       | givechar <uid> <avatarId>                         | player.givechar           | Both side    | Gives the player a specified character.                      | givec                                           |
+| giveall        | giveall [uid] [amount]                            | player.giveall            | Both side    | Gives all items.      | givea                                         |
 | godmode        | godmode [uid]                                     | player.godmode            | Client only  | Prevents you from taking damage.                             |                                                 |
 | heal           | heal                                              | player.heal               | Client only  | Heals all characters in your current team.                    | h                                               |
 | help           | help [command]                                    |                           | Both side    | Sends the help message or shows information about a specified command. |                                                 |
@@ -134,7 +134,7 @@ There is a dummy user named "Server" in every player's friends list that you can
 | spawn          | spanw <entityID\|entityName> [level] [amount]     | server.spawn              | Client only  | Spawns an entity near you                                    |                                                 |
 | stop           | stop                                              | server.stop               | Both side    | Stops the server                                             |                                                 |
 | talent         | talent <talentID> <value>                         | player.settalent          | Client only  | Sets talent level for your currently selected character           |                                                 |
-| teleport       | teleport <x> <y> <z>                              | player.teleport           | Client only  | Change the player's position.                                | tp                                              |
+| teleport       | teleport [@player id] <x> <y> <z> [scene id]      | player.teleport           | Both side    | Change the player's position.                                | tp                                              |
 | tpall          |                                                   | player.tpall              | Client only  | Teleports all players in your world to your position         |                                                 |
 | weather        | weather <weatherID> <climateID>                   | player.weather            | Client only  | Changes the weather                                          | w                                               |
 
diff --git a/README_zh-CN.md b/README_zh-CN.md
index 97f332f3a..1d19117e5 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -105,7 +105,7 @@ chmod +x gradlew
 在每个玩家的朋友列表中都有一个名为“服务器”的虚拟用户,你可以通过发送消息来使用命令。命令也适用于其他聊天室,例如私人/团队聊天。
 要在游戏中使用命令,需要添加 `/` 或 `!` 前缀,如 `/pos`
 
-| 命令           | 用法                                         | 权限节点                  | 可用性   | 注释                                       | 别名                                            |
+| 命令            | 用法                                         | 权限节点                  | 可用性   | 注释                                       | 别名                                            |
 | -------------- | -------------------------------------------- | ------------------------- | -------- | ------------------------------------------ | ----------------------------------------------- |
 | account        | account <create\|delete> <用户名> [uid]      |                           | 仅服务端 | 通过指定用户名和uid增删账户                |                                                 |
 | broadcast      | broadcast <消息内容>                         | server.broadcast          | 均可使用 | 给所有玩家发送公告                         | b                                               |
@@ -135,7 +135,7 @@ chmod +x gradlew
 | spawn          | spanw <实体ID\|实体名称> [等级] [数量]       | server.spawn              | 仅客户端 | 在你周围生成实体                           |                                                 |
 | stop           | stop                                         | server.stop               | 均可使用 | 停止服务器                                 |                                                 |
 | talent         | talent <天赋ID> <等级>                       | player.settalent          | 仅客户端 | 设置当前角色的天赋等级                     |                                                 |
-| teleport       | teleport <x> <y> <z>                         | player.teleport           | 仅客户端 | 传送玩家到指定坐标                         | tp                                              |
+| teleport       | teleport [@player id] <x> <y> <z> [scene id]   | player.teleport           | 均可使用 | 传送玩家到指定坐标                         | tp                                              |
 | tpall          |                                                   | player.tpall              | 仅客户端  | 传送多人世界中所有的玩家到自身地点         |                                                 |
 | weather        | weather <天气ID> <气候ID>                    | player.weather            | 仅客户端 | 改变天气                                   | w                                               |
 
diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java
index c478118e3..238dccfa1 100644
--- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java
+++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java
@@ -1,5 +1,6 @@
 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;
@@ -7,21 +8,39 @@ import emu.grasscutter.utils.Position;
 
 import java.util.List;
 
-@Command(label = "teleport", usage = "teleport <x> <y> <z>", aliases = {"tp"},
+@Command(label = "teleport", usage = "teleport [@player id] <x> <y> <z> [scene id]", aliases = {"tp"},
         description = "Change the player's position.", permission = "player.teleport")
 public final class TeleportCommand implements CommandHandler {
 
     @Override
     public void execute(Player sender, List<String> args) {
-        if (sender == null) {
-            CommandHandler.sendMessage(null, "Run this command in-game.");
+        int target;
+        if (args.size() < (sender == null ? 4 : 3)) {
+            CommandHandler.sendMessage(sender, sender == null ? "Usage: /tp @<player id> <x> <y> <z> [scene id]" :
+                    "Usage: /tp [@<player id>] <x> <y> <z> [scene id]");
             return;
         }
+        if (args.get(0).startsWith("@")) {
+            try {
+                target = Integer.parseInt(args.get(0).substring(1));
+            } catch (NumberFormatException e) {
+                CommandHandler.sendMessage(sender, "Invalid player id.");
+                return;
+            }
+        } else {
+            if (sender == null) {
+                CommandHandler.sendMessage(null, "You must specify a player id.");
+                return;
+            }
+            target = sender.getUid();
+        }
 
-        if (args.size() < 3){
-            CommandHandler.sendMessage(sender, "Usage: /tp <x> <y> <z> [scene id]");
+        Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target);
+        if (targetPlayer == null) {
+            CommandHandler.sendMessage(sender, "Player not found or offline.");
             return;
         }
+        args = args.subList(args.get(0).startsWith("@") ? 1 : 0, args.size());
 
         try {
             float x = 0f;
@@ -29,39 +48,41 @@ public final class TeleportCommand implements CommandHandler {
             float z = 0f;
             if (args.get(0).contains("~")) {
                 if (args.get(0).equals("~")) {
-                    x = sender.getPos().getX();
+                    x = targetPlayer.getPos().getX();
                 } else {
-                    x = Float.parseFloat(args.get(0).replace("~", "")) + sender.getPos().getX();
+                    x = Float.parseFloat(args.get(0).replace("~", "")) + targetPlayer.getPos().getX();
                 }
             } else {
                 x = Float.parseFloat(args.get(0));
             }
             if (args.get(1).contains("~")) {
                 if (args.get(1).equals("~")) {
-                    y = sender.getPos().getY();
+                    y = targetPlayer.getPos().getY();
                 } else {
-                    y = Float.parseFloat(args.get(1).replace("~", "")) + sender.getPos().getY();
+                    y = Float.parseFloat(args.get(1).replace("~", "")) + targetPlayer.getPos().getY();
                 }
             } else {
                 y = Float.parseFloat(args.get(1));
             }
             if (args.get(2).contains("~")) {
                 if (args.get(2).equals("~")) {
-                    z = sender.getPos().getZ();
+                    z = targetPlayer.getPos().getZ();
                 } else {
-                    z = Float.parseFloat(args.get(2).replace("~", "")) + sender.getPos().getZ();
+                    z = Float.parseFloat(args.get(2).replace("~", "")) + targetPlayer.getPos().getZ();
                 }
             } else {
                 z = Float.parseFloat(args.get(2));
             }
-            int sceneId = sender.getSceneId();
+            int sceneId = targetPlayer.getSceneId();
             if (args.size() == 4){
                 sceneId = Integer.parseInt(args.get(3));
             }
-            Position target = new Position(x, y, z);
-            boolean result = sender.getWorld().transferPlayerToScene(sender, sceneId, target);
+            Position target_pos = new Position(x, y, z);
+            boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, target_pos);
             if (!result) {
                 CommandHandler.sendMessage(sender, "Invalid position.");
+            } else {
+                CommandHandler.sendMessage(sender, "Teleported " + targetPlayer.getNickname() + " to " + x + "," + y + "," + z + " in scene " + sceneId);
             }
         } catch (NumberFormatException ignored) {
             CommandHandler.sendMessage(sender, "Invalid position.");

From 2487e2af752275ac90bf8e8764adbbe285a0af98 Mon Sep 17 00:00:00 2001
From: xtaodada <xtao@xtaolink.cn>
Date: Fri, 29 Apr 2022 18:04:20 +0800
Subject: [PATCH 04/21] Support custom server avatar

---
 src/main/java/emu/grasscutter/Config.java                       | 1 +
 src/main/java/emu/grasscutter/GameConstants.java                | 1 +
 .../server/packet/send/PacketGetPlayerFriendListRsp.java        | 2 +-
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java
index 238ddb31c..56a1a0352 100644
--- a/src/main/java/emu/grasscutter/Config.java
+++ b/src/main/java/emu/grasscutter/Config.java
@@ -70,6 +70,7 @@ public final class Config {
 		public int MaxAvatarsInTeamMultiplayer = 4;
 		public int MaxEntityLimit = 1000; // Max entity limit per world. // TODO: Enforce later.
 		public boolean WatchGacha = false;
+		public int ServerAvatarId = 10000007;
 		public int[] WelcomeEmotes = {2007, 1002, 4010};
 		public String WelcomeMotd = "Welcome to Grasscutter emu";
 		public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n<type=\"browser\" text=\"Discord\" href=\"https://discord.gg/T5vZU6UyeG\"/> <type=\"browser\" text=\"GitHub\" href=\"https://github.com/Melledy/Grasscutter\"/>";
diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java
index dc07c32e1..ab48c01c7 100644
--- a/src/main/java/emu/grasscutter/GameConstants.java
+++ b/src/main/java/emu/grasscutter/GameConstants.java
@@ -11,6 +11,7 @@ public final class GameConstants {
 	public static final int MAX_TEAMS = 4;
 	public static final int MAIN_CHARACTER_MALE = 10000005;
 	public static final int MAIN_CHARACTER_FEMALE = 10000007;
+	public static final int SERVER_AVATAR_ID = Grasscutter.getConfig().getGameServerOptions().ServerAvatarId;
 	public static final Position START_POSITION = new Position(2747, 194, -1719);
 	
 	public static final int MAX_FRIENDS = 45;
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java
index dbaa4b316..e085e6f6e 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java
@@ -20,7 +20,7 @@ public class PacketGetPlayerFriendListRsp extends BasePacket {
 				.setUid(GameConstants.SERVER_CONSOLE_UID)
 				.setNickname("Server")
 				.setLevel(1)
-				.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(GameConstants.MAIN_CHARACTER_FEMALE))
+				.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(GameConstants.SERVER_AVATAR_ID))
 				.setWorldLevel(0)
 				.setSignature("")
 				.setLastActiveTime((int) (System.currentTimeMillis() / 1000f))

From c4bdfc26eb74fe43c31bcbf9c539cf474219b779 Mon Sep 17 00:00:00 2001
From: xtaodada <xtao@xtaolink.cn>
Date: Fri, 29 Apr 2022 18:59:24 +0800
Subject: [PATCH 05/21] Support custom server in-game nickname

---
 src/main/java/emu/grasscutter/Config.java                       | 1 +
 src/main/java/emu/grasscutter/GameConstants.java                | 1 +
 .../server/packet/send/PacketGetPlayerFriendListRsp.java        | 2 +-
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java
index 56a1a0352..c2d7850fd 100644
--- a/src/main/java/emu/grasscutter/Config.java
+++ b/src/main/java/emu/grasscutter/Config.java
@@ -51,6 +51,7 @@ public final class Config {
 	
 	public static class GameServerOptions {
 		public String Name = "Test";
+		public String NickName = "Server";
 		public String Ip = "0.0.0.0";
 		public String PublicIp = "127.0.0.1";
 		public int Port = 22102;
diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java
index ab48c01c7..317221ec6 100644
--- a/src/main/java/emu/grasscutter/GameConstants.java
+++ b/src/main/java/emu/grasscutter/GameConstants.java
@@ -12,6 +12,7 @@ public final class GameConstants {
 	public static final int MAIN_CHARACTER_MALE = 10000005;
 	public static final int MAIN_CHARACTER_FEMALE = 10000007;
 	public static final int SERVER_AVATAR_ID = Grasscutter.getConfig().getGameServerOptions().ServerAvatarId;
+	public static final String SERVER_AVATAR_NAME = Grasscutter.getConfig().getGameServerOptions().NickName;
 	public static final Position START_POSITION = new Position(2747, 194, -1719);
 	
 	public static final int MAX_FRIENDS = 45;
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java
index e085e6f6e..d7a9427b8 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java
@@ -18,7 +18,7 @@ public class PacketGetPlayerFriendListRsp extends BasePacket {
 		
 		FriendBrief serverFriend = FriendBrief.newBuilder()
 				.setUid(GameConstants.SERVER_CONSOLE_UID)
-				.setNickname("Server")
+				.setNickname(GameConstants.SERVER_AVATAR_NAME)
 				.setLevel(1)
 				.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(GameConstants.SERVER_AVATAR_ID))
 				.setWorldLevel(0)

From 8c3333dbea5089ac4149140330c218c2f8f7c179 Mon Sep 17 00:00:00 2001
From: xtaodada <xtao@xtaolink.cn>
Date: Fri, 29 Apr 2022 19:37:51 +0800
Subject: [PATCH 06/21] Change NickName to ServerNickname

---
 src/main/java/emu/grasscutter/Config.java        | 2 +-
 src/main/java/emu/grasscutter/GameConstants.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java
index c2d7850fd..109c7b8ee 100644
--- a/src/main/java/emu/grasscutter/Config.java
+++ b/src/main/java/emu/grasscutter/Config.java
@@ -51,7 +51,6 @@ public final class Config {
 	
 	public static class GameServerOptions {
 		public String Name = "Test";
-		public String NickName = "Server";
 		public String Ip = "0.0.0.0";
 		public String PublicIp = "127.0.0.1";
 		public int Port = 22102;
@@ -71,6 +70,7 @@ public final class Config {
 		public int MaxAvatarsInTeamMultiplayer = 4;
 		public int MaxEntityLimit = 1000; // Max entity limit per world. // TODO: Enforce later.
 		public boolean WatchGacha = false;
+		public String ServerNickname = "Server";
 		public int ServerAvatarId = 10000007;
 		public int[] WelcomeEmotes = {2007, 1002, 4010};
 		public String WelcomeMotd = "Welcome to Grasscutter emu";
diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java
index 317221ec6..39b28b736 100644
--- a/src/main/java/emu/grasscutter/GameConstants.java
+++ b/src/main/java/emu/grasscutter/GameConstants.java
@@ -11,8 +11,8 @@ public final class GameConstants {
 	public static final int MAX_TEAMS = 4;
 	public static final int MAIN_CHARACTER_MALE = 10000005;
 	public static final int MAIN_CHARACTER_FEMALE = 10000007;
+	public static final String SERVER_AVATAR_NAME = Grasscutter.getConfig().getGameServerOptions().ServerNickname;
 	public static final int SERVER_AVATAR_ID = Grasscutter.getConfig().getGameServerOptions().ServerAvatarId;
-	public static final String SERVER_AVATAR_NAME = Grasscutter.getConfig().getGameServerOptions().NickName;
 	public static final Position START_POSITION = new Position(2747, 194, -1719);
 	
 	public static final int MAX_FRIENDS = 45;

From d37a326e9ac6c89c976813bd37687b720c3a7b47 Mon Sep 17 00:00:00 2001
From: muhammadeko <muhammadekoprasetyo29@gmail.com>
Date: Sat, 30 Apr 2022 06:49:49 +0700
Subject: [PATCH 07/21] Give Artifact: Clear random props first before adding
 all picked props

---
 .../emu/grasscutter/command/commands/GiveArtifactCommand.java    | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java
index e33a287e3..299a14f40 100644
--- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java
+++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java
@@ -79,6 +79,7 @@ public final class GiveArtifactCommand implements CommandHandler {
 		GameItem item = new GameItem(itemData);
 		item.setLevel(level);
 		item.setMainPropId(mainPropId);
+		item.getAppendPropIdList().clear();//Clear default random props first
 		item.getAppendPropIdList().addAll(appendPropIdList);
 		targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop);
 

From b5582099f9389450a4e92840199acc76a04065c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=AD=B1=E5=82=91?= <jie65535@qq.com>
Date: Sat, 30 Apr 2022 15:40:53 +0800
Subject: [PATCH 08/21] Fix Give Command Promote Level Bug

Should be <=40, just change the order
---
 .../command/commands/GiveCommand.java         | 30 ++++++++++++-------
 1 file changed, 19 insertions(+), 11 deletions(-)

diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java
index 03147a44b..52670efa7 100644
--- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java
+++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java
@@ -163,20 +163,28 @@ public final class GiveCommand implements CommandHandler {
             List<GameItem> items = new LinkedList<>();
             for (int i = 0; i < amount; i++) {
                 GameItem item = new GameItem(itemData);
+                if (item.isEquipped()) {
+                    // check item max level
+                    if (item.getItemType() == ItemType.ITEM_WEAPON) {
+                        if (lvl > 90) lvl = 90;
+                    } else {
+                        if (lvl > 21) lvl = 21;
+                    }
+                }
                 item.setCount(amount);
                 item.setLevel(lvl);
-                if (lvl > 20 && lvl < 40) {
-                    item.setPromoteLevel(1);
-                } else if (lvl > 40 && lvl <= 50) {
-                    item.setPromoteLevel(2);
-                } else if (lvl > 50 && lvl <= 60) {
-                    item.setPromoteLevel(3);
-                } else if (lvl > 60 && lvl <= 70) {
-                    item.setPromoteLevel(4);
-                } else if (lvl > 70 && lvl <= 80) {
-                    item.setPromoteLevel(5);
-                } else if (lvl > 80 && lvl <= 90) {
+                if (lvl > 80) {
                     item.setPromoteLevel(6);
+                } else if (lvl > 70) {
+                    item.setPromoteLevel(5);
+                } else if (lvl > 60) {
+                    item.setPromoteLevel(4);
+                } else if (lvl > 50) {
+                    item.setPromoteLevel(3);
+                } else if (lvl > 40) {
+                    item.setPromoteLevel(2);
+                } else if (lvl > 20) {
+                    item.setPromoteLevel(1);
                 }
                 if (item.getItemType() == ItemType.ITEM_WEAPON) {
                     if (refinement > 0) {

From c2b45a7a0d8cf98aff584d656d6258b1c8a4006d Mon Sep 17 00:00:00 2001
From: lhhxxxxx <91231470+lhhxxxxx@users.noreply.github.com>
Date: Sat, 30 Apr 2022 15:28:49 +0800
Subject: [PATCH 09/21] Update GiveAllCommand.java

giveall command nomore give arts
---
 .../emu/grasscutter/command/commands/GiveAllCommand.java  | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java
index cdf2adbc1..cb6d1e93e 100644
--- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java
+++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java
@@ -98,14 +98,14 @@ public class GiveAllCommand implements CommandHandler {
             if (isTestItem(itemdata.getId())) continue;
 
             if (itemdata.isEquip()) {
-                for (int i = 0; i < 5; ++i) {
-                    GameItem item = new GameItem(itemdata);
-                    if (itemdata.getItemType() == ItemType.ITEM_WEAPON) {
+                if (itemdata.getItemType() == ItemType.ITEM_WEAPON) {
+                    for (int i = 0; i < 5; ++i) {
+                        GameItem item = new GameItem(itemdata);
                         item.setLevel(90);
                         item.setPromoteLevel(6);
                         item.setRefinement(4);
+                        itemList.add(item);
                     }
-                    itemList.add(item);
                 }
             }
             else {

From d64c26d4f56282ff289c8aba3d4b969a0c15428b Mon Sep 17 00:00:00 2001
From: Melledy <52122272+Melledy@users.noreply.github.com>
Date: Fri, 29 Apr 2022 14:29:34 -0700
Subject: [PATCH 10/21] Update how scene/dungeon map points are handled

---
 .../java/emu/grasscutter/Grasscutter.java     | 14 ++++++
 .../java/emu/grasscutter/data/GameData.java   | 12 +++++
 .../emu/grasscutter/data/ResourceLoader.java  |  6 ++-
 .../grasscutter/data/common/PointData.java    | 33 ++++++++++++
 .../data/def/DailyDungeonData.java            | 50 +++++++++++++++++++
 .../game/dungeons/DungeonManager.java         |  8 ++-
 .../send/PacketDungeonEntryInfoRsp.java       | 10 ++--
 .../packet/send/PacketGetScenePointRsp.java   |  9 +++-
 8 files changed, 134 insertions(+), 8 deletions(-)
 create mode 100644 src/main/java/emu/grasscutter/data/def/DailyDungeonData.java

diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java
index f7669d30b..60d042b11 100644
--- a/src/main/java/emu/grasscutter/Grasscutter.java
+++ b/src/main/java/emu/grasscutter/Grasscutter.java
@@ -6,6 +6,7 @@ import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.InputStreamReader;
 import java.net.InetSocketAddress;
+import java.util.Calendar;
 
 import emu.grasscutter.command.CommandMap;
 import emu.grasscutter.plugin.PluginManager;
@@ -32,6 +33,8 @@ public final class Grasscutter {
 	private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
 	private static final File configFile = new File("./config.json");
 	
+	private static int day; // Current day of week
+	
 	public static RunMode MODE = RunMode.BOTH;
 	private static DispatchServer dispatchServer;
 	private static GameServer gameServer;
@@ -67,8 +70,10 @@ public final class Grasscutter {
 		Grasscutter.getLogger().info("Starting Grasscutter...");
 		
 		// Load all resources.
+		Grasscutter.updateDayOfWeek();
 		ResourceLoader.loadAll();
 		ScriptLoader.init();
+		
 		// Database
 		DatabaseManager.initialize();
 
@@ -179,4 +184,13 @@ public final class Grasscutter {
 	public static PluginManager getPluginManager() {
 		return pluginManager;
 	}
+	
+	public static void updateDayOfWeek() {
+		Calendar calendar = Calendar.getInstance();
+		day = calendar.get(Calendar.DAY_OF_WEEK); 
+	}
+
+	public static int getCurrentDayOfWeek() {
+		return day;
+	}
 }
diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java
index 45fad21a4..c187a1819 100644
--- a/src/main/java/emu/grasscutter/data/GameData.java
+++ b/src/main/java/emu/grasscutter/data/GameData.java
@@ -15,6 +15,8 @@ import emu.grasscutter.data.def.*;
 import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 public class GameData {
 	// BinOutputs
@@ -61,12 +63,14 @@ public class GameData {
 	private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
 	private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
 	private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
+	private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
 	private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
 	private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap = new Int2ObjectOpenHashMap<>();
 
 	// Cache
 	private static Map<Integer, List<Integer>> fetters = new HashMap<>();
 	private static Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>();
+	private static final IntList scenePointIdList = new IntArrayList();
 	
 	public static char EJWOA = 's';
 	
@@ -280,6 +284,10 @@ public class GameData {
 		return dungeonDataMap;
 	}
 	  
+	public static Int2ObjectMap<DailyDungeonData> getDailyDungeonDataMap() {
+		return dailyDungeonDataMap;
+	}
+
 	public static Map<Integer, List<ShopGoodsData>> getShopGoodsDataEntries() {
 		if (shopGoods.isEmpty()) {
 			shopGoodsDataMap.forEach((k, v) -> {
@@ -291,4 +299,8 @@ public class GameData {
 
 		return shopGoods;
 	}
+
+	public static IntList getScenePointIdList() {
+		return scenePointIdList;
+	}
 }
diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java
index 6b48645d8..180080e51 100644
--- a/src/main/java/emu/grasscutter/data/ResourceLoader.java
+++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java
@@ -48,11 +48,12 @@ public class ResourceLoader {
 		loadOpenConfig();
 		// Load resources
 		loadResources();
-		loadScenePoints();
 		// Process into depots
 		GameDepot.load();
 		// Load spawn data
 		loadSpawnData();
+		// Load scene points - must be done AFTER resources are loaded
+		loadScenePoints();
 		// Custom - TODO move this somewhere else
 		try {
 			GameData.getAvatarSkillDepotDataMap().get(504).setAbilities(
@@ -168,6 +169,9 @@ public class ResourceLoader {
 
 				ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData);
 				scenePointList.add(sl);
+				GameData.getScenePointIdList().add(pointData.getId());
+				
+				pointData.updateDailyDungeon();
 			}
 
 			for (ScenePointEntry entry : scenePointList) {
diff --git a/src/main/java/emu/grasscutter/data/common/PointData.java b/src/main/java/emu/grasscutter/data/common/PointData.java
index 147eee81a..fa3891d7c 100644
--- a/src/main/java/emu/grasscutter/data/common/PointData.java
+++ b/src/main/java/emu/grasscutter/data/common/PointData.java
@@ -1,12 +1,18 @@
 package emu.grasscutter.data.common;
 
+import emu.grasscutter.Grasscutter;
+import emu.grasscutter.data.GameData;
+import emu.grasscutter.data.def.DailyDungeonData;
 import emu.grasscutter.utils.Position;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 public class PointData {
 	private int id;
 	private String $type;
     private Position tranPos;
     private int[] dungeonIds;
+    private int[] dungeonRandomList;
     
     public int getId() {
 		return id;
@@ -27,4 +33,31 @@ public class PointData {
 	public int[] getDungeonIds() {
 		return dungeonIds;
 	}
+
+	public int[] getDungeonRandomList() {
+		return dungeonRandomList;
+	}
+
+	public void updateDailyDungeon() {
+		if (getDungeonRandomList() == null) {
+			return;
+		}
+		
+		IntList newDungeons = new IntArrayList();
+		int day = Grasscutter.getCurrentDayOfWeek();
+		
+		for (int randomId : getDungeonRandomList()) {
+			DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId);
+			
+			if (data != null) {
+				int[] addDungeons = data.getDungeonsByDay(day);
+				
+				for (int d : addDungeons) {
+					newDungeons.add(d);
+				}
+			}
+		}
+		
+		this.dungeonIds = newDungeons.toIntArray();
+	}
 }
diff --git a/src/main/java/emu/grasscutter/data/def/DailyDungeonData.java b/src/main/java/emu/grasscutter/data/def/DailyDungeonData.java
new file mode 100644
index 000000000..8cd878125
--- /dev/null
+++ b/src/main/java/emu/grasscutter/data/def/DailyDungeonData.java
@@ -0,0 +1,50 @@
+package emu.grasscutter.data.def;
+
+import java.util.Calendar;
+
+import emu.grasscutter.data.GameData;
+import emu.grasscutter.data.GameResource;
+import emu.grasscutter.data.ResourceType;
+
+import emu.grasscutter.game.props.SceneType;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+@ResourceType(name = "DailyDungeonConfigData.json")
+public class DailyDungeonData extends GameResource {
+	private int Id;
+	private int[] Monday;
+	private int[] Tuesday;
+	private int[] Wednesday;
+	private int[] Thursday;
+	private int[] Friday;
+	private int[] Saturday;
+	private int[] Sunday;
+	
+	private static final int[] empty = new int[0];
+	private final Int2ObjectMap<int[]> map;
+	
+	public DailyDungeonData() {
+		this.map = new Int2ObjectOpenHashMap<>();
+	}
+	    
+	@Override
+	public int getId() {
+		return this.Id;
+	}
+	
+	public int[] getDungeonsByDay(int day) {
+		return map.getOrDefault(day, empty);
+	}
+
+	@Override
+	public void onLoad() {
+		map.put(Calendar.MONDAY, Monday);
+		map.put(Calendar.TUESDAY, Tuesday);
+		map.put(Calendar.WEDNESDAY, Wednesday);
+		map.put(Calendar.THURSDAY, Thursday);
+		map.put(Calendar.FRIDAY, Friday);
+		map.put(Calendar.SATURDAY, Saturday);
+		map.put(Calendar.SUNDAY, Sunday);
+	}
+}
diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java
index 6011c1f7c..4c04f86f7 100644
--- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java
+++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java
@@ -28,7 +28,7 @@ public class DungeonManager {
 	public void getEntryInfo(Player player, int pointId) {
 		ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
 		
-		if (entry == null || entry.getPointData().getDungeonIds() == null) {
+		if (entry == null) {
 			// Error
 			player.sendPacket(new PacketDungeonEntryInfoRsp());
 			return;
@@ -79,4 +79,10 @@ public class DungeonManager {
 		player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
 		player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
 	}
+	
+	public void updateDailyDungeons() {
+		for (ScenePointEntry entry : GameData.getScenePointEntries().values()) {
+			entry.getPointData().updateDailyDungeon();
+		}
+	}
 }
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java
index a2cc052bb..5d7842fe1 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java
@@ -17,11 +17,13 @@ public class PacketDungeonEntryInfoRsp extends BasePacket {
 		DungeonEntryInfoRsp.Builder proto = DungeonEntryInfoRsp.newBuilder()
 				.setPointId(pointData.getId());
 		
-		for (int dungeonId : pointData.getDungeonIds()) {
-			DungeonEntryInfo info = DungeonEntryInfo.newBuilder().setDungeonId(dungeonId).build();
-			proto.addDungeonEntryList(info);
+		if (pointData.getDungeonIds() != null) {
+			for (int dungeonId : pointData.getDungeonIds()) {
+				DungeonEntryInfo info = DungeonEntryInfo.newBuilder().setDungeonId(dungeonId).build();
+				proto.addDungeonEntryList(info);
+			}
 		}
-		
+
 		this.setData(proto);
 	}
 	
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetScenePointRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetScenePointRsp.java
index 85b7ab02f..b4c001831 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetScenePointRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetScenePointRsp.java
@@ -1,5 +1,6 @@
 package emu.grasscutter.server.packet.send;
 
+import emu.grasscutter.data.GameData;
 import emu.grasscutter.net.packet.BasePacket;
 import emu.grasscutter.net.packet.PacketOpcodes;
 import emu.grasscutter.net.proto.GetScenePointRspOuterClass.GetScenePointRsp;
@@ -12,8 +13,12 @@ public class PacketGetScenePointRsp extends BasePacket {
 		GetScenePointRsp.Builder p = GetScenePointRsp.newBuilder()
 				.setSceneId(sceneId);
 		
-		for (int i = 1; i < 1000; i++) {
-			p.addUnlockedPointList(i);
+		if (GameData.getScenePointIdList().size() == 0) {
+			for (int i = 1; i < 1000; i++) {
+				p.addUnlockedPointList(i);
+			}
+		} else {
+			p.addAllUnlockedPointList(GameData.getScenePointIdList());
 		}
 		
 		for (int i = 1; i < 9; i++) {

From bea9a76895bbd72c6848c9e18e21fdd0a5e4ab63 Mon Sep 17 00:00:00 2001
From: Melledy <52122272+Melledy@users.noreply.github.com>
Date: Fri, 29 Apr 2022 14:36:02 -0700
Subject: [PATCH 11/21] Optimize GetOnlinePlayerListRsp

---
 .../emu/grasscutter/game/player/Player.java   |  2 +-
 .../send/PacketGetOnlinePlayerListRsp.java    | 37 +++++--------------
 2 files changed, 11 insertions(+), 28 deletions(-)

diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java
index f440fcb1b..0637e3d43 100644
--- a/src/main/java/emu/grasscutter/game/player/Player.java
+++ b/src/main/java/emu/grasscutter/game/player/Player.java
@@ -822,7 +822,7 @@ public class Player {
 				.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()));
 
 		if (this.getWorld() != null) {
-			onlineInfo.setCurPlayerNumInWorld(this.getWorld().getPlayers().indexOf(this) + 1);
+			onlineInfo.setCurPlayerNumInWorld(getWorld().getPlayerCount());
 		} else {
 			onlineInfo.setCurPlayerNumInWorld(1);
 		}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetOnlinePlayerListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetOnlinePlayerListRsp.java
index 85cf5429e..4ea8a02a5 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetOnlinePlayerListRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetOnlinePlayerListRsp.java
@@ -18,36 +18,19 @@ import java.util.Objects;
 public class PacketGetOnlinePlayerListRsp extends BasePacket {
     public PacketGetOnlinePlayerListRsp(Player session){
         super(PacketOpcodes.GetOnlinePlayerListRsp);
-        Map<Integer, Player> playersMap = Grasscutter.getGameServer().getPlayers();
+        
+        List<Player> players = Grasscutter.getGameServer().getPlayers().values().stream().limit(50).toList();
+        
         GetOnlinePlayerListRsp.Builder proto = GetOnlinePlayerListRsp.newBuilder();
-        if(playersMap.size() != 0){
-            List<OnlinePlayerInfo> playerInfoList = new ArrayList<>();
-
-            for(Player player:playersMap.values()){
-                ProfilePicture.Builder picture = ProfilePicture.newBuilder();
-                OnlinePlayerInfo.Builder playerInfo = OnlinePlayerInfo.newBuilder();
-
-                if(player.getUid() == session.getUid())continue;
-                picture.setAvatarId(player.getProfile().getAvatarId())
-                        .build();
-                System.out.println(player.getHeadImage());
-                playerInfo.setUid(player.getUid())
-                        .setNickname(player.getNickname())
-                        .setPlayerLevel(player.getLevel())
-                        .setMpSettingType(MpSettingTypeOuterClass.MpSettingType.MP_SETTING_ENTER_AFTER_APPLY)
-                        .setCurPlayerNumInWorld(player.getWorld().getPlayerCount())
-                        .setWorldLevel(player.getWorldLevel())
-                        .setNameCardId(player.getNameCardId())
-                        .setProfilePicture(picture);
-                if(!Objects.equals(player.getSignature(), "")){
-                    playerInfo.setSignature(player.getSignature());
-                }
-                playerInfoList.add(playerInfo.build());
-            }
-            for (OnlinePlayerInfo onlinePlayerInfo : playerInfoList) {
-                proto.addPlayerInfoList(onlinePlayerInfo).build();
+        
+        if (players.size() != 0) {
+            for(Player player : players) {
+                if (player.getUid() == session.getUid()) continue;
+                
+                proto.addPlayerInfoList(player.getOnlinePlayerInfo());
             }
         }
+        
         this.setData(proto);
     }
 }

From a61e1916a0942a4911fb8e20a04849d0c2155aaf Mon Sep 17 00:00:00 2001
From: Melledy <52122272+Melledy@users.noreply.github.com>
Date: Fri, 29 Apr 2022 15:59:44 -0700
Subject: [PATCH 12/21] Temporary fix for scripts that call require

---
 .../emu/grasscutter/scripts/ScriptLoader.java    | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java
index c4157c690..f6ac0eb53 100644
--- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java
+++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java
@@ -13,6 +13,10 @@ import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 import javax.script.ScriptEngineManager;
 
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.OneArgFunction;
+import org.luaj.vm2.script.LuajContext;
+
 import emu.grasscutter.Grasscutter;
 import emu.grasscutter.scripts.serializer.LuaSerializer;
 import emu.grasscutter.scripts.serializer.Serializer;
@@ -31,11 +35,23 @@ public class ScriptLoader {
 			throw new Exception("Script loader already initialized");
 		}
 		
+		// Create script engine
 		sm = new ScriptEngineManager();
         engine = sm.getEngineByName("luaj");
         factory = getEngine().getFactory();
+        
+        // Lua stuff
         fileType = "lua";
         serializer = new LuaSerializer();
+        
+        // Set engine to replace require as a temporary fix to missing scripts
+        LuajContext ctx = (LuajContext) engine.getContext();
+		ctx.globals.set("require", new OneArgFunction() {
+		    @Override
+		    public LuaValue call(LuaValue arg0) {
+		        return LuaValue.ZERO;
+		    }
+		});
 	}
 	
 	public static ScriptEngine getEngine() {

From 202361f2bf3f18207b9fc2a8ad2632246bfb6a0c Mon Sep 17 00:00:00 2001
From: Melledy <52122272+Melledy@users.noreply.github.com>
Date: Fri, 29 Apr 2022 20:45:36 -0700
Subject: [PATCH 13/21] Move script constants to globals

---
 .../grasscutter/scripts/SceneScriptManager.java   |  7 +++----
 .../emu/grasscutter/scripts/ScriptLoader.java     | 15 +++++++++++++++
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java
index 2f2f31219..58e3b3b8a 100644
--- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java
+++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java
@@ -1,6 +1,7 @@
 package emu.grasscutter.scripts;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -23,6 +24,7 @@ import emu.grasscutter.data.def.WorldLevelData;
 import emu.grasscutter.game.entity.EntityGadget;
 import emu.grasscutter.game.entity.EntityMonster;
 import emu.grasscutter.game.entity.GameEntity;
+import emu.grasscutter.game.props.EntityType;
 import emu.grasscutter.game.world.Scene;
 import emu.grasscutter.scripts.constants.EventType;
 import emu.grasscutter.scripts.constants.ScriptGadgetState;
@@ -134,11 +136,8 @@ public class SceneScriptManager {
 		bindings = ScriptLoader.getEngine().createBindings();
 		
 		// Set variables
-		bindings.put("EventType", new EventType()); // TODO - make static class to avoid instantiating a new class every scene
-		bindings.put("GadgetState", new ScriptGadgetState());
-		bindings.put("RegionShape", new ScriptRegionShape());
 		bindings.put("ScriptLib", getScriptLib());
-		
+
 		// Eval script
 		try {
 			cs.eval(getBindings());
diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java
index f6ac0eb53..0cbd6a191 100644
--- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java
+++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java
@@ -4,6 +4,7 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -13,11 +14,17 @@ import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 import javax.script.ScriptEngineManager;
 
+import org.luaj.vm2.LuaTable;
 import org.luaj.vm2.LuaValue;
 import org.luaj.vm2.lib.OneArgFunction;
+import org.luaj.vm2.lib.jse.CoerceJavaToLua;
 import org.luaj.vm2.script.LuajContext;
 
 import emu.grasscutter.Grasscutter;
+import emu.grasscutter.game.props.EntityType;
+import emu.grasscutter.scripts.constants.EventType;
+import emu.grasscutter.scripts.constants.ScriptGadgetState;
+import emu.grasscutter.scripts.constants.ScriptRegionShape;
 import emu.grasscutter.scripts.serializer.LuaSerializer;
 import emu.grasscutter.scripts.serializer.Serializer;
 
@@ -52,6 +59,14 @@ public class ScriptLoader {
 		        return LuaValue.ZERO;
 		    }
 		});
+		
+		LuaTable table = new LuaTable();
+		Arrays.stream(EntityType.values()).forEach(e -> table.set(e.name().toUpperCase(), e.getValue()));
+		ctx.globals.set("EntityType", table);
+		
+		ctx.globals.set("EventType", CoerceJavaToLua.coerce(new EventType())); // TODO - make static class to avoid instantiating a new class every scene
+		ctx.globals.set("GadgetState", CoerceJavaToLua.coerce(new ScriptGadgetState()));
+		ctx.globals.set("RegionShape", CoerceJavaToLua.coerce(new ScriptRegionShape()));
 	}
 	
 	public static ScriptEngine getEngine() {

From b140c7f5bf3eba3fde5ce9ba68dd5dd1edffff45 Mon Sep 17 00:00:00 2001
From: Melledy <52122272+Melledy@users.noreply.github.com>
Date: Sat, 30 Apr 2022 01:08:38 -0700
Subject: [PATCH 14/21] Implement script region check

---
 .../grasscutter/game/entity/GameEntity.java   |  4 ++
 .../emu/grasscutter/game/world/Scene.java     |  2 +
 .../scripts/SceneScriptManager.java           | 39 ++++++++++++-
 .../emu/grasscutter/scripts/ScriptLib.java    | 14 +++++
 .../grasscutter/scripts/data/SceneGroup.java  |  1 +
 .../grasscutter/scripts/data/SceneRegion.java | 57 +++++++++++++++++++
 .../grasscutter/scripts/data/ScriptArgs.java  | 10 ++++
 7 files changed, 124 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneRegion.java

diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java
index 24598a652..627b41103 100644
--- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java
+++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java
@@ -34,6 +34,10 @@ public abstract class GameEntity {
 		return this.id;
 	}
 	
+	public int getEntityType() {
+		return getId() >> 24;
+	}
+	
 	public World getWorld() {
 		return this.getScene().getWorld();
 	}
diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java
index ee6b9a6a9..6ef810fbf 100644
--- a/src/main/java/emu/grasscutter/game/world/Scene.java
+++ b/src/main/java/emu/grasscutter/game/world/Scene.java
@@ -508,6 +508,7 @@ public class Scene {
 			}
 			
 			group.triggers.forEach(getScriptManager()::registerTrigger);
+			group.regions.forEach(getScriptManager()::registerRegion);
 		}
 		
 		// Spawn gadgets AFTER triggers are added
@@ -526,6 +527,7 @@ public class Scene {
 		
 		for (SceneGroup group : block.groups) {
 			group.triggers.forEach(getScriptManager()::deregisterTrigger);
+			group.regions.forEach(getScriptManager()::deregisterRegion);
 		}
 	}
 	
diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java
index 58e3b3b8a..f2f892de3 100644
--- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java
+++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java
@@ -35,6 +35,7 @@ import emu.grasscutter.scripts.data.SceneGadget;
 import emu.grasscutter.scripts.data.SceneGroup;
 import emu.grasscutter.scripts.data.SceneInitConfig;
 import emu.grasscutter.scripts.data.SceneMonster;
+import emu.grasscutter.scripts.data.SceneRegion;
 import emu.grasscutter.scripts.data.SceneSuite;
 import emu.grasscutter.scripts.data.SceneTrigger;
 import emu.grasscutter.scripts.data.SceneVar;
@@ -51,14 +52,17 @@ public class SceneScriptManager {
 	private Bindings bindings;
 	private SceneConfig config;
 	private List<SceneBlock> blocks;
-	private Int2ObjectOpenHashMap<Set<SceneTrigger>> triggers;
 	private boolean isInit;
 	
+	private final Int2ObjectOpenHashMap<Set<SceneTrigger>> triggers;
+	private final Int2ObjectOpenHashMap<SceneRegion> regions;
+	
 	public SceneScriptManager(Scene scene) {
 		this.scene = scene;
 		this.scriptLib = new ScriptLib(this);
 		this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib);
 		this.triggers = new Int2ObjectOpenHashMap<>();
+		this.regions = new Int2ObjectOpenHashMap<>();
 		this.variables = new HashMap<>();
 		
 		// TEMPORARY
@@ -110,6 +114,18 @@ public class SceneScriptManager {
 		getTriggersByEvent(trigger.event).remove(trigger);
 	}
 	
+	public SceneRegion getRegionById(int id) {
+		return regions.get(id);
+	}
+	
+	public void registerRegion(SceneRegion region) {
+		regions.put(region.config_id, region);
+	}
+	
+	public void deregisterRegion(SceneRegion region) {
+		regions.remove(region.config_id);
+	}
+	
 	// TODO optimize
 	public SceneGroup getGroupById(int groupId) {
 		for (SceneBlock block : this.getScene().getLoadedBlocks()) {
@@ -210,6 +226,7 @@ public class SceneScriptManager {
 			group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets"));
 			group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers"));
 			group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites"));
+			group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions"));
 			group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config"));
 			
 			// Add variables to suite
@@ -234,11 +251,27 @@ public class SceneScriptManager {
 	}
 
 	public void onTick() {
-		checkTriggers();
+		checkRegions();
 	}
 	
-	public void checkTriggers() {
+	public void checkRegions() {
+		if (this.regions.size() == 0) {
+			return;
+		}
+		
+		for (SceneRegion region : this.regions.values()) {
+			getScene().getEntities().values()
+				.stream()
+				.filter(e -> e.getEntityType() <= 2 && region.contains(e.getPosition()))
+				.forEach(region::addEntity);
 
+			if (region.hasNewEntities()) {
+				// This is not how it works, source_eid should be region entity id, but we dont have an entity for regions yet
+				callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.config_id).setSourceEntityId(region.config_id));
+				
+				region.resetNewEntities();
+			}
+		}
 	}
 	
 	public void spawnGadgetsInGroup(SceneGroup group) {
diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java
index 57cd73dd3..5dfe644be 100644
--- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java
+++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java
@@ -17,6 +17,7 @@ import emu.grasscutter.game.entity.GameEntity;
 import emu.grasscutter.scripts.constants.EventType;
 import emu.grasscutter.scripts.data.SceneGroup;
 import emu.grasscutter.scripts.data.SceneMonster;
+import emu.grasscutter.scripts.data.SceneRegion;
 import emu.grasscutter.scripts.data.ScriptArgs;
 import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
 import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify;
@@ -184,6 +185,19 @@ public class ScriptLib {
 		return 0;
 	}
 	
+	public int GetRegionEntityCount(LuaTable table) {
+		int regionId = table.get("region_eid").toint();
+		int entityType = table.get("entity_type").toint();
+
+		SceneRegion region = this.getSceneScriptManager().getRegionById(regionId);
+		
+		if (region == null) {
+			return 0;
+		}
+
+		return (int) region.getEntities().intStream().filter(e -> e >> 24 == entityType).count();
+	}
+	
 	public void PrintContextLog(String msg) {
 		Grasscutter.getLogger().info("[LUA] " + msg);
 	}
diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java
index d72d02d53..a13db7b68 100644
--- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java
+++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java
@@ -14,6 +14,7 @@ public class SceneGroup {
 	public List<SceneMonster> monsters;
 	public List<SceneGadget> gadgets;
 	public List<SceneTrigger> triggers;
+	public List<SceneRegion> regions;
 	public List<SceneSuite> suites;
 	public SceneInitConfig init_config;
 	
diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java
new file mode 100644
index 000000000..dac164d0e
--- /dev/null
+++ b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java
@@ -0,0 +1,57 @@
+package emu.grasscutter.scripts.data;
+
+import emu.grasscutter.game.entity.GameEntity;
+import emu.grasscutter.scripts.constants.ScriptRegionShape;
+import emu.grasscutter.utils.Position;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+
+public class SceneRegion {
+	public int config_id;
+	public int shape;
+	public Position pos;
+	public Position size;
+	
+	private boolean hasNewEntities;
+	private final IntSet entities; // Ids of entities inside this region
+	
+	public SceneRegion() {
+		this.entities = new IntOpenHashSet();
+	}
+	
+	public IntSet getEntities() {
+		return entities;
+	}
+
+	public void addEntity(GameEntity entity) {
+		if (this.getEntities().contains(entity.getId())) {
+			return;
+		}
+		this.getEntities().add(entity.getId());
+		this.hasNewEntities = true;
+	}
+	
+	public void removeEntity(GameEntity entity) {
+		this.getEntities().remove(entity.getId());
+	}
+	
+	public boolean contains(Position p) {
+		switch (shape) {
+			case ScriptRegionShape.CUBIC:
+				return (Math.abs(pos.getX() - p.getX()) <= size.getX()) &&
+				       (Math.abs(pos.getZ() - p.getZ()) <= size.getZ());
+			case ScriptRegionShape.SPHERE:
+				return false;
+		}
+		
+		return false;
+	}
+
+	public boolean hasNewEntities() {
+		return hasNewEntities;
+	}
+	
+	public void resetNewEntities() {
+		hasNewEntities = false;
+	}
+}
diff --git a/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java
index 3073f7322..a1f183b63 100644
--- a/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java
+++ b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java
@@ -4,6 +4,7 @@ public class ScriptArgs {
 	public int param1;
 	public int param2;
 	public int param3;
+	public int source_eid; // Source entity
 	
 	public ScriptArgs() {
 		
@@ -44,4 +45,13 @@ public class ScriptArgs {
 		this.param3 = param3;
 		return this;
 	}
+
+	public int getSourceEntityId() {
+		return source_eid;
+	}
+
+	public ScriptArgs setSourceEntityId(int source_eid) {
+		this.source_eid = source_eid;
+		return this;
+	}
 }

From 3bdf93f5039e6751162abe5d82578bade2c22442 Mon Sep 17 00:00:00 2001
From: Melledy <52122272+Melledy@users.noreply.github.com>
Date: Sat, 30 Apr 2022 01:18:50 -0700
Subject: [PATCH 15/21] Monsters should not drop items inside dungeons

---
 src/main/java/emu/grasscutter/game/world/Scene.java | 4 ++--
 src/main/java/emu/grasscutter/game/world/World.java | 7 +++++++
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java
index 6ef810fbf..365b55982 100644
--- a/src/main/java/emu/grasscutter/game/world/Scene.java
+++ b/src/main/java/emu/grasscutter/game/world/Scene.java
@@ -375,8 +375,8 @@ public class Scene {
 		this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD));
 
 		// Reward drop
-		if (target instanceof EntityMonster) {
-			Grasscutter.getGameServer().getDropManager().callDrop((EntityMonster) target);
+		if (target instanceof EntityMonster && this.getSceneType() != SceneType.SCENE_WORLD) {
+			getWorld().getServer().getDropManager().callDrop((EntityMonster) target);
 		}
 
 		this.removeEntity(target);
diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java
index 215896fea..46c80cd25 100644
--- a/src/main/java/emu/grasscutter/game/world/World.java
+++ b/src/main/java/emu/grasscutter/game/world/World.java
@@ -27,6 +27,7 @@ import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
 import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
 import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
 import emu.grasscutter.scripts.data.SceneConfig;
+import emu.grasscutter.server.game.GameServer;
 import emu.grasscutter.server.packet.send.PacketDelTeamEntityNotify;
 import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
 import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
@@ -44,6 +45,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 
 public class World implements Iterable<Player> {
+	private final GameServer server;
 	private final Player owner;
 	private final List<Player> players;
 	private final Int2ObjectMap<Scene> scenes;
@@ -61,6 +63,7 @@ public class World implements Iterable<Player> {
 	
 	public World(Player player, boolean isMultiplayer) {
 		this.owner = player;
+		this.server = player.getServer();
 		this.players = Collections.synchronizedList(new ArrayList<>());
 		this.scenes = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
 		
@@ -75,6 +78,10 @@ public class World implements Iterable<Player> {
 		return owner;
 	}
 
+	public GameServer getServer() {
+		return server;
+	}
+
 	public int getLevelEntityId() {
 		return levelEntityId;
 	}

From f31fda2e16c4e9f03ce2ed160d0f46fcf5e50e8f Mon Sep 17 00:00:00 2001
From: lhhxxxxx <91231470+lhhxxxxx@users.noreply.github.com>
Date: Wed, 27 Apr 2022 14:45:53 +0800
Subject: [PATCH 16/21] Update Account.hasPermission()

Add wildcard characters to permission nodes under the same namespace. (simple implementation)
---
 src/main/java/emu/grasscutter/game/Account.java | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java
index dbd6ef854..7ccaf7e7e 100644
--- a/src/main/java/emu/grasscutter/game/Account.java
+++ b/src/main/java/emu/grasscutter/game/Account.java
@@ -104,7 +104,10 @@ public class Account {
 	}
 
 	public boolean hasPermission(String permission) {
-		return this.permissions.contains(permission) || this.permissions.contains("*") ? true : false;
+		return this.permissions.contains(permission) ||
+                this.permissions.contains("*") ||
+                (this.permissions.contains("player") || this.permissions.contains("player.*")) && permission.startsWith("player.") ||
+                (this.permissions.contains("server") || this.permissions.contains("server.*")) && permission.startsWith("server.");
 	}
 	
 	public boolean removePermission(String permission) {

From 13aaebd9e8b463c7d8c5ec2e0d11121db431eec4 Mon Sep 17 00:00:00 2001
From: Scirese <62688390+Scirese@users.noreply.github.com>
Date: Sat, 30 Apr 2022 17:45:24 +0800
Subject: [PATCH 17/21] Add Android Client fix by @BaiSugar (#105)

---
 .../emu/grasscutter/server/dispatch/DispatchServer.java   | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
index 99c5e4f8d..6997d0449 100644
--- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
+++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
@@ -490,10 +490,10 @@ public final class DispatchServer {
 						"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}"));
 		// Captcha
 		server.createContext( // api-account-os.hoyoverse.com
-				"/account/risky/api/check",
-				new DispatchHttpJsonHandler(
-						"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"c8820f246a5241ab9973f71df3ddd791\",\"action\":\"\",\"geetest\":{\"challenge\":\"\",\"gt\":\"\",\"new_captcha\":0,\"success\":1}}}"));
-		// Config
+				"/account/risky/api/check", 
+				new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")
+		);
+		// Config	
 		server.createContext( // sdk-os-static.hoyoverse.com
 				"/combo/box/api/config/sdk/combo",
 				new DispatchHttpJsonHandler(

From 86f72a5f7a66ea23882cc72a037c2fea5b672d42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=AD=B1=E5=82=91?= <jie65535@qq.com>
Date: Sat, 30 Apr 2022 17:46:23 +0800
Subject: [PATCH 18/21] Update Welcome Mail Items Type (#369)

---
 src/main/java/emu/grasscutter/Config.java                  | 7 ++++++-
 .../server/packet/recv/HandlerSetPlayerBornDataReq.java    | 6 +++---
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java
index ce753d80d..03c6fb1f8 100644
--- a/src/main/java/emu/grasscutter/Config.java
+++ b/src/main/java/emu/grasscutter/Config.java
@@ -1,5 +1,7 @@
 package emu.grasscutter;
 
+import emu.grasscutter.game.mail.Mail;
+
 public final class Config {
 
 	public String DatabaseUrl = "mongodb://localhost:27017";
@@ -76,7 +78,10 @@ public final class Config {
 		public int[] WelcomeEmotes = {2007, 1002, 4010};
 		public String WelcomeMotd = "Welcome to Grasscutter emu";
 		public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n<type=\"browser\" text=\"Discord\" href=\"https://discord.gg/T5vZU6UyeG\"/> <type=\"browser\" text=\"GitHub\" href=\"https://github.com/Melledy/Grasscutter\"/>";
-		public int[] WelcomeMailItems = {13509};
+		public Mail.MailItem[] WelcomeMailItems = {
+				new Mail.MailItem(13509, 1, 1),
+				new Mail.MailItem(201, 10000, 1),
+		};
 
 		public boolean EnableOfficialShop = true;
 
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java
index 52c52c280..9acd4f21b 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java
@@ -16,6 +16,8 @@ import emu.grasscutter.net.packet.PacketHandler;
 import emu.grasscutter.server.game.GameSession;
 import emu.grasscutter.server.game.GameSession.SessionState;
 
+import java.util.Arrays;
+
 @Opcodes(PacketOpcodes.SetPlayerBornDataReq)
 public class HandlerSetPlayerBornDataReq extends PacketHandler {
 	
@@ -82,9 +84,7 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler {
 			mailBuilder.mail.mailContent.title = String.format("W%sl%som%s to %s%s%s%s%s%s%s%s%s%s%s!", DatabaseHelper.AWJVN, u, DatabaseHelper.AWJVN, d, e, z, GameData.EJWOA, GameData.EJWOA, u, PacketOpcodes.ONLWE, s, s, DatabaseHelper.AWJVN, e);
 			mailBuilder.mail.mailContent.sender = String.format("L%swnmow%s%s @ Gi%sH%sb", z, DatabaseHelper.AWJVN, e, s, PacketOpcodes.ONLWE);
 			mailBuilder.mail.mailContent.content = Grasscutter.getConfig().GameServer.WelcomeMailContent;
-			for (int itemId : Grasscutter.getConfig().GameServer.WelcomeMailItems) {
-				mailBuilder.mail.itemList.add(new Mail.MailItem(itemId, 1, 1));
-			}
+			mailBuilder.mail.itemList.addAll(Arrays.asList(Grasscutter.getConfig().GameServer.WelcomeMailItems));
 			mailBuilder.mail.importance = 1;
 			player.sendMail(mailBuilder.mail);
 		} catch (Exception e) {

From dd388142093919b3a94820f3ba645882050297db Mon Sep 17 00:00:00 2001
From: Aru <mraru@outlook.com>
Date: Sat, 30 Apr 2022 19:18:09 +0800
Subject: [PATCH 19/21] Monsters should drop items in world (#373)

* Monsters should drop items in world

fix the typo introduced in https://github.com/Grasscutters/Grasscutter/pull/366/commits/f1934c3a47d6c7ef888581bcf7f361d063154215

* Determine whether in dungeon scene
---
 src/main/java/emu/grasscutter/game/world/Scene.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java
index 365b55982..440ec7316 100644
--- a/src/main/java/emu/grasscutter/game/world/Scene.java
+++ b/src/main/java/emu/grasscutter/game/world/Scene.java
@@ -375,7 +375,7 @@ public class Scene {
 		this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD));
 
 		// Reward drop
-		if (target instanceof EntityMonster && this.getSceneType() != SceneType.SCENE_WORLD) {
+		if (target instanceof EntityMonster && this.getSceneType() != SceneType.SCENE_DUNGEON) {
 			getWorld().getServer().getDropManager().callDrop((EntityMonster) target);
 		}
 

From 2fd73e043a1055f3c580bf829b51a2347455afd9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B5=A4=E5=BA=A7=E3=81=82=E3=81=8B=E3=82=8A?=
 <sev0@qq.com>
Date: Sat, 30 Apr 2022 19:19:22 +0800
Subject: [PATCH 20/21] Give Artifact: Fix error & Rename alias (#370)

* Give Artifact: Fix error & Rename alias

* Update README.md
---
 README.md                                     |  2 +-
 README_zh-CN.md                               |  2 +-
 .../command/commands/GiveArtifactCommand.java | 19 ++++++++++++-------
 3 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index 9d5595853..623baf68b 100644
--- a/README.md
+++ b/README.md
@@ -115,7 +115,7 @@ There is a dummy user named "Server" in every player's friends list that you can
 | drop           | drop <itemID\|itemName> [amount]                  | server.drop               | Client only  | Drops an item around you.                                    | `d` `dropitem`                                  |
 | give           | give [player] <itemId\|itemName> [amount] [level] [finement] | player.give               | Both side    | Gives item(s) to you or the specified player. (finement option only weapon.)               | `g` `item` `giveitem`                           |
 | givechar       | givechar \<uid> \<avatarId>                 | player.givechar           | Both side    | Gives the player a specified character.                      | givec                                           |
-| giveart        | giveart [player] \<artifactId> \<mainPropId> [\<appendPropId>[,\<times>]]... [level] | player.giveart            | Both side    | Gives the player a specified reliquary.                      | givea                                           |
+| giveart        | giveart [player] \<artifactId> \<mainPropId> [\<appendPropId>[,\<times>]]... [level] | player.giveart            | Both side    | Gives the player a specified artifact.                      | gart                                           |
 | giveall       | giveall [uid] [amount]             | player.giveall      | Both side    | Gives all items.      | givea                                         |
 | godmode        | godmode [uid]                                     | player.godmode            | Client only  | Prevents you from taking damage.                             |                                                 |
 | heal           | heal                                              | player.heal               | Client only  | Heals all characters in your current team.                    | h                                               |
diff --git a/README_zh-CN.md b/README_zh-CN.md
index a386f0d23..6216cf3eb 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -116,7 +116,7 @@ chmod +x gradlew
 | drop           | drop <物品ID\|物品名称> [数量]               | server.drop               | 仅客户端 | 在指定玩家周围掉落指定物品                 | `d` `dropitem`                                  |
 | give           | give [uid] <物品ID\|物品名称> [数量] [等级] [精炼等级]  |                           |          | 给予指定玩家一定数量及等级的物品 (精炼等级仅适用于武器)        | `g` `item` `giveitem`                           |
 | givechar       | givechar \<uid> <角色ID> [等级]               | player.givechar           | 均可使用 | 给予指定玩家对应角色                       | givec                                           |
-| giveart        | giveart [uid] \<圣遗物ID> \<主属性ID> [\<副属性ID>[,<次数>]]... [等级] | player.giveart            | 均可使用 | 给予玩家指定属性的圣遗物                   | givea                                           |
+| giveart        | giveart [uid] \<圣遗物ID> \<主属性ID> [\<副属性ID>[,<次数>]]... [等级] | player.giveart            | 均可使用 | 给予玩家指定属性的圣遗物                   | gart                                           |
 | giveall        | giveall [uid] [数量]                         | player.giveall            | 均可使用 | 给予指定玩家全部物品                       | givea                                           |
 | godmode        | godmode [uid]                                | player.godmode            | 仅客户端 | 保护你不受到任何伤害(依然会被击退)         |                                                 |
 | heal           | heal                                         | player.heal               | 仅客户端 | 治疗队伍中所有角色                         | h                                               |
diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java
index 299a14f40..382c55ec2 100644
--- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java
+++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java
@@ -14,13 +14,13 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-@Command(label = "giveart", usage = "giveart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", description = "Gives the player a specified reliquary", aliases = {"givea"}, permission = "player.giveart")
+@Command(label = "giveart", usage = "giveart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart")
 public final class GiveArtifactCommand implements CommandHandler {
 	@Override
 	public void execute(Player sender, List<String> args) {
-		int size = args.size(), target, itemId, mainPropId, level;
+		int size = args.size(), target, itemId, mainPropId, level = 1;
 		ArrayList<Integer> appendPropIdList = new ArrayList<>();
-		String msg = "Usage: giveart|givea [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]";
+		String msg = "Usage: giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]";
 
 		if (sender == null && size < 2) {
 			CommandHandler.sendMessage(null, msg);
@@ -29,9 +29,14 @@ public final class GiveArtifactCommand implements CommandHandler {
 
 		if (size >= 2) {
 			try {
-				level = Integer.parseInt(args.get(size - 1));
-				if (level <= 21) size--;
-				else level = 1;
+				try {
+					int last = Integer.parseInt(args.get(size - 1));
+					if (last >= 1 && last <= 21) {
+						level = last;
+						size--;
+					}
+				} catch (NumberFormatException ignored) {
+				}
 				target = Integer.parseInt(args.get(0));
 				int fromIdx;
 				if (Grasscutter.getGameServer().getPlayerByUid(target) == null && sender != null) {
@@ -79,7 +84,7 @@ public final class GiveArtifactCommand implements CommandHandler {
 		GameItem item = new GameItem(itemData);
 		item.setLevel(level);
 		item.setMainPropId(mainPropId);
-		item.getAppendPropIdList().clear();//Clear default random props first
+		item.getAppendPropIdList().clear();
 		item.getAppendPropIdList().addAll(appendPropIdList);
 		targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop);
 

From 64930b9ba961c0a4db5bd6c903c9e0187a888312 Mon Sep 17 00:00:00 2001
From: LDA <lda@ldasuku.net>
Date: Sat, 30 Apr 2022 10:04:36 -0700
Subject: [PATCH 21/21] Implement viewing character details in character
 showcase

---
 proto/GetFriendShowAvatarInfoReq.proto        | 16 ++++++
 proto/GetFriendShowAvatarInfoRsp.proto        | 19 +++++++
 proto/ShowAvatarInfo.proto                    | 24 +++++++++
 proto/ShowEquip.proto                         | 14 +++++
 .../emu/grasscutter/game/avatar/Avatar.java   | 51 +++++++++++++++++--
 .../grasscutter/game/inventory/GameItem.java  | 46 ++++++++++-------
 .../emu/grasscutter/game/player/Player.java   | 31 ++++++++++-
 .../HandlerGetFriendShowAvatarInfoReq.java    | 26 ++++++++++
 .../PacketGetFriendShowAvatarInfoRsp.java     | 24 +++++++++
 9 files changed, 228 insertions(+), 23 deletions(-)
 create mode 100644 proto/GetFriendShowAvatarInfoReq.proto
 create mode 100644 proto/GetFriendShowAvatarInfoRsp.proto
 create mode 100644 proto/ShowAvatarInfo.proto
 create mode 100644 proto/ShowEquip.proto
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerGetFriendShowAvatarInfoReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketGetFriendShowAvatarInfoRsp.java

diff --git a/proto/GetFriendShowAvatarInfoReq.proto b/proto/GetFriendShowAvatarInfoReq.proto
new file mode 100644
index 000000000..33c888d42
--- /dev/null
+++ b/proto/GetFriendShowAvatarInfoReq.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+option java_package = "emu.grasscutter.net.proto";
+
+message GetFriendShowAvatarInfoReq {
+	enum CmdId {
+		option allow_alias = true;
+		ENET_CHANNEL_ID = 0;
+		NONE = 0;
+		ENET_IS_RELIABLE = 1;
+		IS_ALLOW_CLIENT = 1;
+		CMD_ID = 4007;
+	}
+
+	uint32 uid = 1;
+}
diff --git a/proto/GetFriendShowAvatarInfoRsp.proto b/proto/GetFriendShowAvatarInfoRsp.proto
new file mode 100644
index 000000000..abd7d2aae
--- /dev/null
+++ b/proto/GetFriendShowAvatarInfoRsp.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+option java_package = "emu.grasscutter.net.proto";
+
+import "ShowAvatarInfo.proto";
+
+message GetFriendShowAvatarInfoRsp {
+	enum CmdId {
+		option allow_alias = true;
+		NONE = 0;
+		ENET_CHANNEL_ID = 0;
+		ENET_IS_RELIABLE = 1;
+		CMD_ID = 4008;
+	}
+
+	int32 retcode = 1;
+	uint32 uid = 2;
+	repeated ShowAvatarInfo show_avatar_info_list = 3;
+}
diff --git a/proto/ShowAvatarInfo.proto b/proto/ShowAvatarInfo.proto
new file mode 100644
index 000000000..61be51d80
--- /dev/null
+++ b/proto/ShowAvatarInfo.proto
@@ -0,0 +1,24 @@
+syntax = "proto3";
+
+option java_package = "emu.grasscutter.net.proto";
+
+import "AvatarFetterInfo.proto";
+import "AvatarExcelInfo.proto";
+import "PropValue.proto";
+import "ShowEquip.proto";
+
+message ShowAvatarInfo {
+	uint32 avatar_id = 1;
+	map<uint32, PropValue> prop_map = 2;
+	repeated uint32 talent_id_list = 3;
+	map<uint32, float> fight_prop_map = 4;
+	uint32 skill_depot_id = 5;
+	uint32 core_proud_skill_level = 6;
+	repeated uint32 inherent_proud_skill_list = 7;
+	map<uint32, uint32> skill_level_map = 8;
+	map<uint32, uint32> proud_skill_extra_level_map = 9;
+	repeated ShowEquip equip_list = 10;
+	AvatarFetterInfo fetter_info = 11;
+	uint32 costume_id = 12;
+	AvatarExcelInfo excel_info = 13;
+}
diff --git a/proto/ShowEquip.proto b/proto/ShowEquip.proto
new file mode 100644
index 000000000..a35882de1
--- /dev/null
+++ b/proto/ShowEquip.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+option java_package = "emu.grasscutter.net.proto";
+
+import "Reliquary.proto";
+import "Weapon.proto";
+
+message ShowEquip {
+	oneof Detail {
+		Reliquary reliquary = 2;
+		Weapon weapon = 3;
+	}
+	uint32 item_id = 1;
+}
diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java
index d16b9a2fa..cf5446ab7 100644
--- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java
+++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java
@@ -15,7 +15,6 @@ import dev.morphia.annotations.Indexed;
 import dev.morphia.annotations.PostLoad;
 import dev.morphia.annotations.PrePersist;
 import dev.morphia.annotations.Transient;
-import emu.grasscutter.Grasscutter;
 import emu.grasscutter.data.GameData;
 import emu.grasscutter.data.common.FightPropData;
 import emu.grasscutter.data.custom.OpenConfigEntry;
@@ -26,18 +25,19 @@ import emu.grasscutter.data.def.AvatarSkillDepotData;
 import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens;
 import emu.grasscutter.data.def.AvatarTalentData;
 import emu.grasscutter.data.def.EquipAffixData;
+import emu.grasscutter.data.def.ItemData.WeaponProperty;
+import emu.grasscutter.data.def.ProudSkillData;
 import emu.grasscutter.data.def.ReliquaryAffixData;
 import emu.grasscutter.data.def.ReliquaryLevelData;
 import emu.grasscutter.data.def.ReliquaryMainPropData;
 import emu.grasscutter.data.def.ReliquarySetData;
 import emu.grasscutter.data.def.WeaponCurveData;
 import emu.grasscutter.data.def.WeaponPromoteData;
-import emu.grasscutter.data.def.ItemData.WeaponProperty;
-import emu.grasscutter.data.def.ProudSkillData;
 import emu.grasscutter.database.DatabaseHelper;
 import emu.grasscutter.game.entity.EntityAvatar;
 import emu.grasscutter.game.inventory.EquipType;
 import emu.grasscutter.game.inventory.GameItem;
+import emu.grasscutter.game.inventory.ItemType;
 import emu.grasscutter.game.player.Player;
 import emu.grasscutter.game.props.ElementType;
 import emu.grasscutter.game.props.EntityIdType;
@@ -45,8 +45,11 @@ import emu.grasscutter.game.props.FetterState;
 import emu.grasscutter.game.props.FightProperty;
 import emu.grasscutter.game.props.PlayerProperty;
 import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo;
-import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
 import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo;
+import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
+import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
+import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo;
+import emu.grasscutter.net.proto.ShowEquipOuterClass.ShowEquip;
 import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify;
 import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
 import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
@@ -797,6 +800,46 @@ public class Avatar {
 		
 		return avatarInfo.build();
 	}
+
+	// used only in character showcase
+	public ShowAvatarInfo toShowAvatarInfoProto() {
+		AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder()
+				.setExpLevel(this.getFetterLevel());
+
+		ShowAvatarInfo.Builder showAvatarInfo = ShowAvatarInfoOuterClass.ShowAvatarInfo.newBuilder()
+				.setAvatarId(avatarId)
+				.addAllTalentIdList(this.getTalentIdList())
+				.putAllFightPropMap(this.getFightProperties())
+				.setSkillDepotId(this.getSkillDepotId())
+				.setCoreProudSkillLevel(this.getCoreProudSkillLevel())
+				.addAllInherentProudSkillList(this.getProudSkillList())
+				.putAllSkillLevelMap(this.getSkillLevelMap())
+				.putAllProudSkillExtraLevelMap(this.getProudSkillBonusMap())
+				.setFetterInfo(avatarFetter)
+				.setCostumeId(this.getCostume());
+
+		showAvatarInfo.putPropMap(PlayerProperty.PROP_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel()));
+		showAvatarInfo.putPropMap(PlayerProperty.PROP_EXP.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp()));
+		showAvatarInfo.putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel()));
+		showAvatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation()));
+		showAvatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiationPenalty()));
+		int maxStamina = this.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA);
+		showAvatarInfo.putPropMap(PlayerProperty.PROP_MAX_STAMINA.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_MAX_STAMINA, maxStamina));
+
+		for (GameItem item : this.getEquips().values()) {
+			if (item.getItemType() == ItemType.ITEM_RELIQUARY) {
+				showAvatarInfo.addEquipList(ShowEquip.newBuilder()
+						.setItemId(item.getItemId())
+						.setReliquary(item.toReliquaryProto()));
+			} else if (item.getItemType() == ItemType.ITEM_WEAPON) {
+				showAvatarInfo.addEquipList(ShowEquip.newBuilder()
+						.setItemId(item.getItemId())
+						.setWeapon(item.toWeaponProto()));
+			}
+		}
+
+		return showAvatarInfo.build();
+	}
 	
 	@PostLoad
 	private void onLoad() {
diff --git a/src/main/java/emu/grasscutter/game/inventory/GameItem.java b/src/main/java/emu/grasscutter/game/inventory/GameItem.java
index 252c0a01c..7293b75c0 100644
--- a/src/main/java/emu/grasscutter/game/inventory/GameItem.java
+++ b/src/main/java/emu/grasscutter/game/inventory/GameItem.java
@@ -375,6 +375,32 @@ public class GameItem {
 		return relicInfo;
 	}
 
+	public Weapon toWeaponProto() {
+		Weapon.Builder weapon = Weapon.newBuilder()
+			.setLevel(this.getLevel())
+			.setExp(this.getExp())
+			.setPromoteLevel(this.getPromoteLevel());
+
+		if (this.getAffixes() != null && this.getAffixes().size() > 0) {
+			for (int affix : this.getAffixes()) {
+				weapon.putAffixMap(affix, this.getRefinement());
+			}
+		}
+
+		return weapon.build();
+	}
+
+	public Reliquary toReliquaryProto() {
+		Reliquary.Builder relic = Reliquary.newBuilder()
+			.setLevel(this.getLevel())
+			.setExp(this.getExp())
+			.setPromoteLevel(this.getPromoteLevel())
+			.setMainPropId(this.getMainPropId())
+			.addAllAppendPropIdList(this.getAppendPropIdList());
+
+		return relic.build();
+	}
+
 	public Item toProto() {
 		Item.Builder proto = Item.newBuilder()
 				.setGuid(this.getGuid())
@@ -382,27 +408,11 @@ public class GameItem {
 				
 		switch (getItemType()) {
 			case ITEM_WEAPON:
-				Weapon.Builder weapon = Weapon.newBuilder()
-					.setLevel(this.getLevel())
-					.setExp(this.getExp())
-					.setPromoteLevel(this.getPromoteLevel());
-				
-				if (this.getAffixes() != null && this.getAffixes().size() > 0) {
-					for (int affix : this.getAffixes()) {
-						weapon.putAffixMap(affix, this.getRefinement());
-					}
-				}
-					
+				Weapon weapon = this.toWeaponProto();
 				proto.setEquip(Equip.newBuilder().setWeapon(weapon).setIsLocked(this.isLocked()).build());
 				break;
 			case ITEM_RELIQUARY:
-				Reliquary relic = Reliquary.newBuilder()
-					.setLevel(this.getLevel())
-					.setExp(this.getExp())
-					.setPromoteLevel(this.getPromoteLevel())
-					.setMainPropId(this.getMainPropId())
-					.addAllAppendPropIdList(this.getAppendPropIdList())
-					.build();
+				Reliquary relic = this.toReliquaryProto();
 				proto.setEquip(Equip.newBuilder().setReliquary(relic).setIsLocked(this.isLocked()).build());
 				break;
 			case ITEM_MATERIAL:
diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java
index 0637e3d43..2d3442bbe 100644
--- a/src/main/java/emu/grasscutter/game/player/Player.java
+++ b/src/main/java/emu/grasscutter/game/player/Player.java
@@ -21,7 +21,6 @@ import emu.grasscutter.game.inventory.Inventory;
 import emu.grasscutter.game.mail.Mail;
 import emu.grasscutter.game.props.ActionReason;
 import emu.grasscutter.game.props.PlayerProperty;
-import emu.grasscutter.game.shop.ShopInfo;
 import emu.grasscutter.game.shop.ShopLimit;
 import emu.grasscutter.game.world.Scene;
 import emu.grasscutter.game.world.World;
@@ -34,6 +33,7 @@ import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
 import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass;
 import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
 import emu.grasscutter.net.proto.PlayerWorldLocationInfoOuterClass;
+import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
 import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
 import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
 import emu.grasscutter.net.proto.SocialShowAvatarInfoOuterClass;
@@ -898,6 +898,35 @@ public class Player {
 				.setFinishAchievementNum(0);
 		return social;
 	}
+
+	public List<ShowAvatarInfoOuterClass.ShowAvatarInfo> getShowAvatarInfoList() {
+		List<ShowAvatarInfoOuterClass.ShowAvatarInfo> showAvatarInfoList = new ArrayList<>();
+
+		Player player;
+		boolean shouldRecalc;
+		if (this.isOnline()) {
+			player = this;
+			shouldRecalc = false;
+		} else {
+			player = DatabaseHelper.getPlayerById(id);
+			player.getAvatars().loadFromDatabase();
+			player.getInventory().loadFromDatabase();
+			shouldRecalc = true;
+		}
+
+		List<Integer> showAvatarList = player.getShowAvatarList();
+		AvatarStorage avatars = player.getAvatars();
+		if (showAvatarList != null) {
+			for (int avatarId : showAvatarList) {
+				Avatar avatar = avatars.getAvatarById(avatarId);
+				if (shouldRecalc) {
+					avatar.recalcStats();
+				}
+				showAvatarInfoList.add(avatar.toShowAvatarInfoProto());
+			}
+		}
+		return showAvatarInfoList;
+	}
 	
 	public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() {
 		return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder()
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetFriendShowAvatarInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetFriendShowAvatarInfoReq.java
new file mode 100644
index 000000000..61fd31640
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetFriendShowAvatarInfoReq.java
@@ -0,0 +1,26 @@
+package emu.grasscutter.server.packet.recv;
+
+import emu.grasscutter.game.player.Player;
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.GetFriendShowAvatarInfoReqOuterClass.GetFriendShowAvatarInfoReq;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketGetFriendShowAvatarInfoRsp;
+
+@Opcodes(PacketOpcodes.GetFriendShowAvatarInfoReq)
+public class HandlerGetFriendShowAvatarInfoReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+		GetFriendShowAvatarInfoReq req = GetFriendShowAvatarInfoReq.parseFrom(payload);
+
+		int targetUid = req.getUid();
+		Player targetPlayer = session.getServer().getPlayerByUid(targetUid, true);
+
+		if (targetPlayer.isShowAvatars()) {
+			session.send(new PacketGetFriendShowAvatarInfoRsp(targetUid, targetPlayer.getShowAvatarInfoList()));
+		}
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetFriendShowAvatarInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetFriendShowAvatarInfoRsp.java
new file mode 100644
index 000000000..f9b640659
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetFriendShowAvatarInfoRsp.java
@@ -0,0 +1,24 @@
+package emu.grasscutter.server.packet.send;
+
+import java.util.List;
+
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.GetFriendShowAvatarInfoRspOuterClass.GetFriendShowAvatarInfoRsp;
+import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo;
+
+@Opcodes(PacketOpcodes.GetFriendShowAvatarInfoRsp)
+public class PacketGetFriendShowAvatarInfoRsp extends BasePacket {
+
+	public PacketGetFriendShowAvatarInfoRsp(int uid, List<ShowAvatarInfo> showAvatarInfoList) {
+		super(PacketOpcodes.GetFriendShowAvatarInfoRsp);
+
+		GetFriendShowAvatarInfoRsp.Builder p = GetFriendShowAvatarInfoRsp.newBuilder()
+				.setUid(uid)
+				.addAllShowAvatarInfoList(showAvatarInfoList);
+
+		this.setData(p.build());
+	}
+
+}