diff --git a/README.md b/README.md index cbc09ea3e..840657f22 100644 --- a/README.md +++ b/README.md @@ -113,9 +113,9 @@ There is a dummy user named "Server" in every player's friends list that you can | clearartifacts | clearartifacts | player.clearartifacts | Client only | Deletes all unequipped and unlocked level 0 artifacts, including 5-star rarity ones from your inventory. | clearart | | clearweapons | clearweapons | player.clearweapons | Client only | Deletes all unequipped and unlocked weapons, including 5-star rarity ones from your inventory. | clearwpns | | drop | drop [amount] | server.drop | Client only | Drops an item around you. | `d` `dropitem` | -| give | give [player] [amount] [level] | player.give | Both side | Gives item(s) to you or the specified player. | `g` `item` `giveitem` | +| give | give [player] [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 | player.givechar | Both side | Gives the player a specified character. | givec | -| giveall | giveall [uid] [amount] | player.giveall | Both side | Gives all items. | givea | +| 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. | | diff --git a/README_zh-CN.md b/README_zh-CN.md index 1d19117e5..92ab9bb26 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -114,7 +114,7 @@ chmod +x gradlew | clearartifacts | clearartifacts | player.clearartifacts | 仅客户端 | 删除所有未装备及未解锁的圣遗物,包括五星 | clearart | | clearweapons | clearweapons | player.clearweapons | 仅客户端 | 删除所有未装备及未解锁的武器,包括五星 | clearwp | | drop | drop <物品ID\|物品名称> [数量] | server.drop | 仅客户端 | 在指定玩家周围掉落指定物品 | `d` `dropitem` | -| give | give [uid] <物品ID\|物品名称> [数量] [等级] | | | 给予指定玩家一定数量及等级的物品 | `g` `item` `giveitem` | +| give | give [uid] <物品ID\|物品名称> [数量] [等级] [精炼等级] | | | 给予指定玩家一定数量及等级的物品 (精炼等级仅适用于武器) | `g` `item` `giveitem` | | givechar | givechar <角色ID> [等级] | player.givechar | 均可使用 | 给予指定玩家对应角色 | givec | | giveall | giveall [uid] [数量] | player.giveall | 均可使用 | 给予指定玩家全部物品 | givea | | godmode | godmode [uid] | player.godmode | 仅客户端 | 保护你不受到任何伤害(依然会被击退) | | diff --git a/build.gradle b/build.gradle index 1ac88d0fa..b32491b2a 100644 --- a/build.gradle +++ b/build.gradle @@ -71,9 +71,10 @@ dependencies { implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2' implementation group: 'org.quartz-scheduler', name: 'quartz-jobs', version: '2.3.2' - + + implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1' + protobuf files('proto/') - } application { diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index 109c7b8ee..ce753d80d 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -10,6 +10,7 @@ public final class Config { public String PACKETS_FOLDER = "./packets/"; public String DUMPS_FOLDER = "./dumps/"; public String KEY_FOLDER = "./keys/"; + public String SCRIPTS_FOLDER = "./resources/Scripts/"; public String PLUGINS_FOLDER = "./plugins/"; public String RunMode = "HYBRID"; // HYBRID, DISPATCH_ONLY, GAME_ONLY diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index dcd6a3b6f..f7669d30b 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -9,6 +9,7 @@ import java.net.InetSocketAddress; import emu.grasscutter.command.CommandMap; import emu.grasscutter.plugin.PluginManager; +import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.Utils; import org.reflections.Reflections; import org.slf4j.LoggerFactory; @@ -67,6 +68,7 @@ public final class Grasscutter { // Load all resources. ResourceLoader.loadAll(); + ScriptLoader.init(); // Database DatabaseManager.initialize(); diff --git a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java index 8aa7d7333..4dbfe6922 100644 --- a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java @@ -18,79 +18,86 @@ public final class ClearCommand implements CommandHandler { @Override public void execute(Player sender, List args) { int target; + String cmdSwitch = ""; if (sender == null) { CommandHandler.sendMessage(null, "Run this command in-game."); - return; + return; } - String cmdSwitch = args.get(1); - Inventory playerInventory = sender.getInventory(); - try { + try { + if (args.size() == 1) { + cmdSwitch = args.get(0); + target = sender.getUid(); + }else { + cmdSwitch = args.get(1); target = Integer.parseInt(args.get(0)); - Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); - if (targetPlayer == null) { - target = sender.getUid(); - } else { - switch (cmdSwitch) { - case "wp" -> { - playerInventory.getItems().values().stream() - .filter(item -> item.getItemType() == ItemType.ITEM_WEAPON) - .filter(item -> !item.isLocked() && !item.isEquipped()) - .forEach(item -> playerInventory.removeItem(item, item.getCount())); - sender.dropMessage("Cleared weapons for " + targetPlayer.getNickname() + " ."); - } - case "art" -> { - playerInventory.getItems().values().stream() - .filter(item -> item.getItemType() == ItemType.ITEM_RELIQUARY) - .filter(item -> item.getLevel() == 1 && item.getExp() == 0) - .filter(item -> !item.isLocked() && !item.isEquipped()) - .forEach(item -> playerInventory.removeItem(item, item.getCount())); - sender.dropMessage("Cleared artifacts for " + targetPlayer.getNickname() + " ."); - } - case "mat" -> { - playerInventory.getItems().values().stream() - .filter(item -> item.getItemType() == ItemType.ITEM_MATERIAL) - .filter(item -> item.getLevel() == 1 && item.getExp() == 0) - .filter(item -> !item.isLocked() && !item.isEquipped()) - .forEach(item -> playerInventory.removeItem(item, item.getCount())); - sender.dropMessage("Cleared artifacts for " + targetPlayer.getNickname() + " ."); - } - case "all" -> { - playerInventory.getItems().values().stream() - .filter(item1 -> item1.getItemType() == ItemType.ITEM_RELIQUARY) - .filter(item1 -> item1.getLevel() == 1 && item1.getExp() == 0) - .filter(item1 -> !item1.isLocked() && !item1.isEquipped()) - .forEach(item1 -> playerInventory.removeItem(item1, item1.getCount())); - playerInventory.getItems().values().stream() - .filter(item2 -> item2.getItemType() == ItemType.ITEM_MATERIAL) - .filter(item2 -> !item2.isLocked() && !item2.isEquipped()) - .forEach(item2 -> playerInventory.removeItem(item2, item2.getCount())); - playerInventory.getItems().values().stream() - .filter(item3 -> item3.getItemType() == ItemType.ITEM_WEAPON) - .filter(item3 -> item3.getLevel() == 1 && item3.getExp() == 0) - .filter(item3 -> !item3.isLocked() && !item3.isEquipped()) - .forEach(item3 -> playerInventory.removeItem(item3, item3.getCount())); - playerInventory.getItems().values().stream() - .filter(item4 -> item4.getItemType() == ItemType.ITEM_FURNITURE) - .filter(item4 -> !item4.isLocked() && !item4.isEquipped()) - .forEach(item4 -> playerInventory.removeItem(item4, item4.getCount())); - playerInventory.getItems().values().stream() - .filter(item5 -> item5.getItemType() == ItemType.ITEM_DISPLAY) - .filter(item5 -> !item5.isLocked() && !item5.isEquipped()) - .forEach(item5 -> playerInventory.removeItem(item5, item5.getCount())); - playerInventory.getItems().values().stream() - .filter(item6 -> item6.getItemType() == ItemType.ITEM_VIRTUAL) - .filter(item6 -> !item6.isLocked() && !item6.isEquipped()) - .forEach(item6 -> playerInventory.removeItem(item6, item6.getCount())); - sender.dropMessage("Cleared everything for " + targetPlayer.getNickname() + " ."); - } - } - } - } catch (NumberFormatException ignored) { - // TODO: Parse from item name using GM Handbook. - CommandHandler.sendMessage(sender, "Invalid playerId."); - return; } + Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); + switch (cmdSwitch) { + case "wp" -> { + playerInventory.getItems().values().stream() + .filter(item -> item.getItemType() == ItemType.ITEM_WEAPON) + .filter(item -> !item.isLocked() && !item.isEquipped()) + .forEach(item -> playerInventory.removeItem(item, item.getCount())); + sender.dropMessage("Cleared weapons for " + targetPlayer.getNickname() + " ."); + } + case "art" -> { + playerInventory.getItems().values().stream() + .filter(item -> item.getItemType() == ItemType.ITEM_RELIQUARY) + .filter(item -> item.getLevel() == 1 && item.getExp() == 0) + .filter(item -> !item.isLocked() && !item.isEquipped()) + .forEach(item -> playerInventory.removeItem(item, item.getCount())); + sender.dropMessage("Cleared artifacts for " + targetPlayer.getNickname() + " ."); + } + case "mat" -> { + playerInventory.getItems().values().stream() + .filter(item -> item.getItemType() == ItemType.ITEM_MATERIAL) + .filter(item -> item.getLevel() == 1 && item.getExp() == 0) + .filter(item -> !item.isLocked() && !item.isEquipped()) + .forEach(item -> playerInventory.removeItem(item, item.getCount())); + sender.dropMessage("Cleared artifacts for " + targetPlayer.getNickname() + " ."); + } + case "all" -> { + playerInventory.getItems().values().stream() + .filter(item1 -> item1.getItemType() == ItemType.ITEM_RELIQUARY) + .filter(item1 -> item1.getLevel() == 1 && item1.getExp() == 0) + .filter(item1 -> !item1.isLocked() && !item1.isEquipped()) + .forEach(item1 -> playerInventory.removeItem(item1, item1.getCount())); + sender.dropMessage("Cleared artifacts for " + targetPlayer.getNickname() + " ."); + playerInventory.getItems().values().stream() + .filter(item2 -> item2.getItemType() == ItemType.ITEM_MATERIAL) + .filter(item2 -> !item2.isLocked() && !item2.isEquipped()) + .forEach(item2 -> playerInventory.removeItem(item2, item2.getCount())); + sender.dropMessage("Cleared materials for " + targetPlayer.getNickname() + " ."); + playerInventory.getItems().values().stream() + .filter(item3 -> item3.getItemType() == ItemType.ITEM_WEAPON) + .filter(item3 -> item3.getLevel() == 1 && item3.getExp() == 0) + .filter(item3 -> !item3.isLocked() && !item3.isEquipped()) + .forEach(item3 -> playerInventory.removeItem(item3, item3.getCount())); + sender.dropMessage("Cleared weapons for " + targetPlayer.getNickname() + " ."); + playerInventory.getItems().values().stream() + .filter(item4 -> item4.getItemType() == ItemType.ITEM_FURNITURE) + .filter(item4 -> !item4.isLocked() && !item4.isEquipped()) + .forEach(item4 -> playerInventory.removeItem(item4, item4.getCount())); + sender.dropMessage("Cleared furniture for " + targetPlayer.getNickname() + " ."); + playerInventory.getItems().values().stream() + .filter(item5 -> item5.getItemType() == ItemType.ITEM_DISPLAY) + .filter(item5 -> !item5.isLocked() && !item5.isEquipped()) + .forEach(item5 -> playerInventory.removeItem(item5, item5.getCount())); + sender.dropMessage("Cleared displays for " + targetPlayer.getNickname() + " ."); + playerInventory.getItems().values().stream() + .filter(item6 -> item6.getItemType() == ItemType.ITEM_VIRTUAL) + .filter(item6 -> !item6.isLocked() && !item6.isEquipped()) + .forEach(item6 -> playerInventory.removeItem(item6, item6.getCount())); + sender.dropMessage("Cleared virtuals for " + targetPlayer.getNickname() + " ."); + sender.dropMessage("Cleared everything for " + targetPlayer.getNickname() + " ."); + } + } + } catch (NumberFormatException ignored) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendMessage(sender, "Invalid playerId."); + return; + } Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(target); if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java index 648828643..94437e0e2 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java @@ -83,9 +83,6 @@ public class GiveAllCommand implements CommandHandler { Avatar avatar = new Avatar(avatarData); avatar.setLevel(90); avatar.setPromoteLevel(6); - for (int i = 1; i <= 6; ++i) { - avatar.getTalentIdList().add((avatar.getAvatarId() - 10000000) * 10 + i); - } // This will handle stats and talents avatar.recalcStats(); player.addAvatar(avatar); @@ -98,7 +95,7 @@ public class GiveAllCommand implements CommandHandler { if (isTestItem(itemdata.getId())) continue; if (itemdata.isEquip()) { - for (int i = 0; i < 10; ++i) { + for (int i = 0; i < 5; ++i) { GameItem item = new GameItem(itemdata); if (itemdata.getItemType() == ItemType.ITEM_WEAPON) { item.setLevel(90); @@ -114,7 +111,7 @@ public class GiveAllCommand implements CommandHandler { itemList.add(item); } } - int packetNum = 20; + int packetNum = 10; int itemLength = itemList.size(); int number = itemLength / packetNum; int remainder = itemLength % packetNum; @@ -171,15 +168,27 @@ public class GiveAllCommand implements CommandHandler { private static final Range[] testItemRanges = new Range[] { new Range(106, 139), new Range(1000, 1099), - new Range(2001, 2008), - new Range(2017, 2029), - // new Range(108001, 108387) //food + new Range(2001, 3022), + new Range(23300, 23340), + new Range(23383, 23385), + new Range(78310, 78554), + new Range(99310, 99554), + new Range(100001, 100187), + new Range(100210, 100214), + new Range(100303, 100398), + new Range(100414, 100425), + new Range(100454, 103008), + new Range(109000, 109492), + new Range(115001, 118004), + new Range(141001, 141072), + new Range(220050, 221016), }; private static final Integer[] testItemsIds = new Integer[] { - 210, 211, 314, 315, 317, 1005, 1007, 1105, 1107, 1201, 1202, 2800, - 100001, 100002, 100244, 100305, 100312, 100313, 101212, 11411, 11506, 11507, 11508, 12505, - 12506, 12508, 12509, 13503, 13506, 14411, 14503, 14505, 14508, 15411, 15504, 15505, - 15506, 15508, 20001, 10002, 10003, 10004, 10005, 10006, 10008 //9 + 210, 211, 314, 315, 317, 1005, 1007, 1105, 1107, 1201, 1202,10366, + 101212, 11411, 11506, 11507, 11508, 12505, 12506, 12508, 12509, 13503, + 13506, 14411, 14503, 14505, 14508, 15411, 15504, 15505, 15506, 15508, + 20001, 10002, 10003, 10004, 10005, 10006, 10008,100231,100232,100431, + 101689,105001,105004, 106000,106001,108000,110000 }; private static final Collection testItemsList = Arrays.asList(testItemsIds); diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index 4c9052a3e..03147a44b 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -6,20 +6,20 @@ import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.server.packet.send.PacketItemAddHintNotify; import java.util.LinkedList; import java.util.List; @Command(label = "give", usage = "give [player] [amount] [level]", description = "Gives an item to you or the specified player", aliases = { - "g", "item", "giveitem" }, permission = "player.give") + "g", "item", "giveitem"}, permission = "player.give") public final class GiveCommand implements CommandHandler { @Override public void execute(Player sender, List args) { - int target, item, lvl, amount = 1; + int target, item, lvl, amount = 1, refinement = 0; if (sender == null && args.size() < 2) { CommandHandler.sendMessage(null, "Usage: give [amount] [level]"); return; @@ -79,7 +79,28 @@ public final class GiveCommand implements CommandHandler { return; } break; - case 4: // [player] [amount] [level] + case 4: // [player] [amount] [level] | [amount] [level] [refinement] + try { + target = Integer.parseInt(args.get(0)); + + if (Grasscutter.getGameServer().getPlayerByUid(target) == null && sender != null) { + target = sender.getUid(); + item = Integer.parseInt(args.get(0)); + amount = Integer.parseInt(args.get(1)); + lvl = Integer.parseInt(args.get(2)); + refinement = Integer.parseInt(args.get(3)); + } else { + item = Integer.parseInt(args.get(1)); + amount = Integer.parseInt(args.get(2)); + lvl = Integer.parseInt(args.get(3)); + } + } catch (NumberFormatException ignored) { + // TODO: Parse from item name using GM Handbook. + CommandHandler.sendMessage(sender, "Invalid item or player ID."); + return; + } + break; + case 5: // [player] [amount] [level] [refinement] try { target = Integer.parseInt(args.get(0)); @@ -90,6 +111,7 @@ public final class GiveCommand implements CommandHandler { item = Integer.parseInt(args.get(1)); amount = Integer.parseInt(args.get(2)); lvl = Integer.parseInt(args.get(3)); + refinement = Integer.parseInt(args.get(4)); } } catch (NumberFormatException ignored) { // TODO: Parse from item name using GM Handbook. @@ -111,37 +133,58 @@ public final class GiveCommand implements CommandHandler { CommandHandler.sendMessage(sender, "Invalid item id."); return; } + if (refinement != 0) { + if (itemData.getItemType() == ItemType.ITEM_WEAPON) { + if (refinement < 1 || refinement > 5) { + CommandHandler.sendMessage(sender, "Refinement must be between 1 and 5."); + return; + } + } else { + CommandHandler.sendMessage(sender, "Refinement is only applicable to weapons."); + return; + } + } - this.item(targetPlayer, itemData, amount, lvl); + this.item(targetPlayer, itemData, amount, lvl, refinement); - if (!itemData.isEquip()) + if (!itemData.isEquip()) { CommandHandler.sendMessage(sender, String.format("Given %s of %s to %s.", amount, item, target)); - else + } else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { + CommandHandler.sendMessage(sender, + String.format("Given %s with level %s, refinement %s %s times to %s", item, lvl, refinement, amount, target)); + } else { CommandHandler.sendMessage(sender, String.format("Given %s with level %s %s times to %s", item, lvl, amount, target)); + } } - private void item(Player player, ItemData itemData, int amount, int lvl) { + private void item(Player player, ItemData itemData, int amount, int lvl, int refinement) { if (itemData.isEquip()) { List items = new LinkedList<>(); for (int i = 0; i < amount; i++) { GameItem item = new GameItem(itemData); item.setCount(amount); item.setLevel(lvl); - item.setPromoteLevel(0); - if (lvl > 20) { // 20/40 + if (lvl > 20 && lvl < 40) { item.setPromoteLevel(1); - } else if (lvl > 40) { // 40/50 + } else if (lvl > 40 && lvl <= 50) { item.setPromoteLevel(2); - } else if (lvl > 50) { // 50/60 + } else if (lvl > 50 && lvl <= 60) { item.setPromoteLevel(3); - } else if (lvl > 60) { // 60/70 + } else if (lvl > 60 && lvl <= 70) { item.setPromoteLevel(4); - } else if (lvl > 70) { // 70/80 + } else if (lvl > 70 && lvl <= 80) { item.setPromoteLevel(5); - } else if (lvl > 80) { // 80/90 + } else if (lvl > 80 && lvl <= 90) { item.setPromoteLevel(6); } + if (item.getItemType() == ItemType.ITEM_WEAPON) { + if (refinement > 0) { + item.setRefinement(refinement - 1); + } else { + item.setRefinement(0); + } + } items.add(item); } player.getInventory().addItems(items, ActionReason.SubfieldDrop); diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 8b1d4fbd9..45fad21a4 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -61,13 +61,15 @@ public class GameData { private static final Int2ObjectMap fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap dungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); - + // Cache private static Map> fetters = new HashMap<>(); private static Map> shopGoods = new HashMap<>(); + public static char EJWOA = 's'; + public static Int2ObjectMap getMapByResourceDef(Class resourceDefinition) { Int2ObjectMap map = null; @@ -100,6 +102,11 @@ public class GameData { public static Map getScenePointEntries() { return scenePointEntries; } + + // TODO optimize + public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) { + return getScenePointEntries().get(sceneId + "_" + pointId); + } public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; @@ -269,8 +276,10 @@ public class GameData { return worldLevelDataMap; } - public static char EJWOA = 's'; - + public static Int2ObjectMap getDungeonDataMap() { + return dungeonDataMap; + } + public static Map> getShopGoodsDataEntries() { if (shopGoods.isEmpty()) { shopGoodsDataMap.forEach((k, v) -> { diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 589f6aca8..6b48645d8 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -164,6 +164,7 @@ public class ResourceLoader { for (Map.Entry entry : config.points.entrySet()) { PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class); + pointData.setId(Integer.parseInt(entry.getKey())); ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData); scenePointList.add(sl); diff --git a/src/main/java/emu/grasscutter/data/common/PointData.java b/src/main/java/emu/grasscutter/data/common/PointData.java index 7c31d2f06..147eee81a 100644 --- a/src/main/java/emu/grasscutter/data/common/PointData.java +++ b/src/main/java/emu/grasscutter/data/common/PointData.java @@ -1,43 +1,30 @@ package emu.grasscutter.data.common; -public class PointData { - private pos tranPos; +import emu.grasscutter.utils.Position; - public pos getTranPos() { +public class PointData { + private int id; + private String $type; + private Position tranPos; + private int[] dungeonIds; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getType() { + return $type; + } + + public Position getTranPos() { return tranPos; } - public void setTranPos(pos tranPos) { - this.tranPos = tranPos; - } - - public class pos { - private float x; - private float y; - private float z; - - public float getX() { - return x; - } - - public void setX(float x) { - this.x = x; - } - - public float getY() { - return y; - } - - public void setY(float y) { - this.y = y; - } - - public float getZ() { - return z; - } - - public void setZ(float z) { - this.z = z; - } - } + public int[] getDungeonIds() { + return dungeonIds; + } } diff --git a/src/main/java/emu/grasscutter/data/def/DungeonData.java b/src/main/java/emu/grasscutter/data/def/DungeonData.java new file mode 100644 index 000000000..311286dca --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/DungeonData.java @@ -0,0 +1,33 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +import emu.grasscutter.game.props.SceneType; + +@ResourceType(name = "DungeonExcelConfigData.json") +public class DungeonData extends GameResource { + private int Id; + private int SceneId; + private int ShowLevel; + private String InvolveType; // TODO enum + + @Override + public int getId() { + return this.Id; + } + + public int getSceneId() { + return SceneId; + } + + public int getShowLevel() { + return ShowLevel; + } + + @Override + public void onLoad() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/GadgetData.java b/src/main/java/emu/grasscutter/data/def/GadgetData.java index 7340e8c2c..6f1a8aa46 100644 --- a/src/main/java/emu/grasscutter/data/def/GadgetData.java +++ b/src/main/java/emu/grasscutter/data/def/GadgetData.java @@ -2,12 +2,13 @@ package emu.grasscutter.data.def; import emu.grasscutter.data.GameResource; import emu.grasscutter.data.ResourceType; +import emu.grasscutter.game.props.EntityType; @ResourceType(name = "GadgetExcelConfigData.json") public class GadgetData extends GameResource { private int Id; - - private String Type; + + private EntityType Type; private String JsonName; private boolean IsInteractive; private String[] Tags; @@ -22,7 +23,7 @@ public class GadgetData extends GameResource { return this.Id; } - public String getType() { + public EntityType getType() { return Type; } diff --git a/src/main/java/emu/grasscutter/data/def/SceneData.java b/src/main/java/emu/grasscutter/data/def/SceneData.java index 220579faf..cd1510e1d 100644 --- a/src/main/java/emu/grasscutter/data/def/SceneData.java +++ b/src/main/java/emu/grasscutter/data/def/SceneData.java @@ -9,8 +9,9 @@ import emu.grasscutter.game.props.SceneType; @ResourceType(name = "SceneExcelConfigData.json") public class SceneData extends GameResource { private int Id; - private SceneType SceneType; + private SceneType Type; private String ScriptData; + @Override public int getId() { @@ -18,7 +19,7 @@ public class SceneData extends GameResource { } public SceneType getSceneType() { - return SceneType; + return Type; } public String getScriptData() { @@ -27,6 +28,6 @@ public class SceneData extends GameResource { @Override public void onLoad() { - + } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java new file mode 100644 index 000000000..608587765 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java @@ -0,0 +1,105 @@ +package emu.grasscutter.game.dungeons; + +import java.util.ArrayList; +import java.util.List; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.SceneMonster; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; +import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; + +public class DungeonChallenge { + private final Scene scene; + private final SceneGroup group; + + private int challengeIndex; + private int challengeId; + private boolean success; + private boolean progress; + + private int score; + private int objective = 0; + + public DungeonChallenge(Scene scene, SceneGroup group) { + this.scene = scene; + this.group = group; + + objective += group.monsters.size(); + } + + public Scene getScene() { + return scene; + } + + public SceneGroup getGroup() { + return group; + } + + public int getChallengeIndex() { + return challengeIndex; + } + + public void setChallengeIndex(int challengeIndex) { + this.challengeIndex = challengeIndex; + } + + public int getChallengeId() { + return challengeId; + } + + public void setChallengeId(int challengeId) { + this.challengeId = challengeId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean isSuccess) { + this.success = isSuccess; + } + + public boolean inProgress() { + return progress; + } + + public int getScore() { + return score; + } + + public void start() { + this.progress = true; + getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); + } + + public void finish() { + this.progress = false; + getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); + + if (this.isSuccess()) { + this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, null); + } else { + this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_FAIL, null); + } + } + + public void onMonsterDie(EntityMonster entity) { + score = getScore() + 1; + + getScene().broadcastPacket(new PacketChallengeDataNotify(this, 1, getScore())); + + if (getScore() >= objective) { + this.setSuccess(true); + finish(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index 33bac7d09..6011c1f7c 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -1,6 +1,18 @@ package emu.grasscutter.game.dungeons; +import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.ScenePointEntry; +import emu.grasscutter.data.def.DungeonData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp; +import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp; +import emu.grasscutter.utils.Position; public class DungeonManager { private final GameServer server; @@ -12,4 +24,59 @@ public class DungeonManager { public GameServer getServer() { return server; } + + public void getEntryInfo(Player player, int pointId) { + ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId); + + if (entry == null || entry.getPointData().getDungeonIds() == null) { + // Error + player.sendPacket(new PacketDungeonEntryInfoRsp()); + return; + } + + player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData())); + } + + public void enterDungeon(Player player, int pointId, int dungeonId) { + DungeonData data = GameData.getDungeonDataMap().get(dungeonId); + + if (data == null) { + return; + } + + Grasscutter.getLogger().info(player.getNickname() + " is trying to enter dungeon " + dungeonId); + + int sceneId = data.getSceneId(); + player.getScene().setPrevScene(sceneId); + + player.getWorld().transferPlayerToScene(player, sceneId, data); + + player.getScene().setPrevScenePoint(pointId); + player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId)); + } + + public void exitDungeon(Player player) { + if (player.getScene().getSceneType() != SceneType.SCENE_DUNGEON) { + return; + } + + // Get previous scene + int prevScene = player.getScene().getPrevScene() > 0 ? player.getScene().getPrevScene() : 3; + + // Get previous position + DungeonData dungeonData = player.getScene().getDungeonData(); + Position prevPos = new Position(GameConstants.START_POSITION); + + if (dungeonData != null) { + ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, player.getScene().getPrevScenePoint()); + + if (entry != null) { + prevPos.set(entry.getPointData().getTranPos()); + } + } + + // Transfer player back to world + player.getWorld().transferPlayerToScene(player, prevScene, prevPos); + player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp)); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java new file mode 100644 index 000000000..a5b2cb6c5 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.game.world.World; + +public abstract class EntityBaseGadget extends GameEntity { + + public EntityBaseGadget(Scene scene) { + super(scene); + } + + public abstract int getGadgetId(); + + @Override + public void onDeath(int killerId) { + + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java index 464789426..77b76566a 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityClientGadget.java @@ -23,7 +23,7 @@ import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -public class EntityClientGadget extends EntityGadget { +public class EntityClientGadget extends EntityBaseGadget { private final Player owner; private final Position pos; diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index c09504c21..640f93f22 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -1,18 +1,157 @@ package emu.grasscutter.game.entity; +import java.util.Arrays; +import java.util.List; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.GadgetData; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.EntityType; +import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.World; +import emu.grasscutter.net.proto.ClientGadgetInfoOuterClass; +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; +import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; +import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; +import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; +import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; +import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.ProtoHelper; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; -public abstract class EntityGadget extends GameEntity { +public class EntityGadget extends EntityBaseGadget { + private final GadgetData data; + private final Position pos; + private final Position rot; + private int gadgetId; + + private int state; + private IntSet worktopOptions; - public EntityGadget(Scene scene) { + public EntityGadget(Scene scene, int gadgetId, Position pos) { super(scene); + this.data = GameData.getGadgetDataMap().get(gadgetId); + this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); + this.gadgetId = gadgetId; + this.pos = pos.clone(); + this.rot = new Position(); } - public abstract int getGadgetId(); + public GadgetData getGadgetData() { + return data; + } + + @Override + public Position getPosition() { + // TODO Auto-generated method stub + return this.pos; + } + + @Override + public Position getRotation() { + // TODO Auto-generated method stub + return this.rot; + } + public int getGadgetId() { + return gadgetId; + } + + public void setGadgetId(int gadgetId) { + this.gadgetId = gadgetId; + } + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + public IntSet getWorktopOptions() { + return worktopOptions; + } + + public void addWorktopOptions(int[] options) { + if (this.worktopOptions == null) { + this.worktopOptions = new IntOpenHashSet(); + } + Arrays.stream(options).forEach(this.worktopOptions::add); + } + + public void removeWorktopOption(int option) { + if (this.worktopOptions == null) { + return; + } + this.worktopOptions.remove(option); + } + + @Override + public Int2FloatOpenHashMap getFightProperties() { + // TODO Auto-generated method stub + return null; + } + @Override public void onDeath(int killerId) { } + + @Override + public SceneEntityInfo toProto() { + EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) + .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder())) + .setBornPos(Vector.newBuilder()) + .build(); + + SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityType.PROT_ENTITY_GADGET) + .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) + .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) + .setEntityClientData(EntityClientData.newBuilder()) + .setEntityAuthorityInfo(authority) + .setLifeState(1); + + PropPair pair = PropPair.newBuilder() + .setType(PlayerProperty.PROP_LEVEL.getId()) + .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1)) + .build(); + entityInfo.addPropList(pair); + + SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder() + .setGadgetId(this.getGadgetId()) + .setGroupId(this.getGroupId()) + .setConfigId(this.getConfigId()) + .setGadgetState(this.getState()) + .setIsEnableInteract(true) + .setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); + + if (this.getGadgetData().getType() == EntityType.Worktop && this.getWorktopOptions() != null) { + WorktopInfo worktop = WorktopInfo.newBuilder() + .addAllOptionList(this.getWorktopOptions()) + .build(); + gadgetInfo.setWorktop(worktop); + } + + entityInfo.setGadget(gadgetInfo); + + return entityInfo.build(); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityItem.java b/src/main/java/emu/grasscutter/game/entity/EntityItem.java index e23c1cb33..f2b7386e1 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityItem.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityItem.java @@ -23,7 +23,7 @@ import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -public class EntityItem extends EntityGadget { +public class EntityItem extends EntityBaseGadget { private final Position pos; private final Position rot; diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index b9f212fe1..c9d0c0982 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -4,6 +4,7 @@ import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.def.MonsterCurveData; import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; @@ -22,6 +23,7 @@ import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo; import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; +import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatMap; @@ -36,9 +38,6 @@ public class EntityMonster extends GameEntity { private final Position bornPos; private final int level; private int weaponEntityId; - - private int groupId; - private int configId; private int poseId; public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) { @@ -103,22 +102,6 @@ public class EntityMonster extends GameEntity { public boolean isAlive() { return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f; } - - public int getGroupId() { - return groupId; - } - - public void setGroupId(int groupId) { - this.groupId = groupId; - } - - public int getConfigId() { - return configId; - } - - public void setConfigId(int configId) { - this.configId = configId; - } public int getPoseId() { return poseId; @@ -127,12 +110,18 @@ public class EntityMonster extends GameEntity { public void setPoseId(int poseId) { this.poseId = poseId; } - + @Override public void onDeath(int killerId) { if (this.getSpawnEntry() != null) { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } + if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); + } + if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { + getScene().getChallenge().onMonsterDie(this); + } } public void recalcStats() { diff --git a/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java index 06e5ee14a..c7609f89b 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java @@ -25,7 +25,7 @@ import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -public class EntityVehicle extends EntityGadget { +public class EntityVehicle extends EntityBaseGadget { private final Player owner; private final Int2FloatOpenHashMap fightProp; diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java index aef3378b6..24598a652 100644 --- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java +++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java @@ -17,6 +17,10 @@ public abstract class GameEntity { private final Scene scene; private SpawnDataEntry spawnEntry; + private int blockId; + private int configId; + private int groupId; + private MotionState moveState; private int lastMoveSceneTimeMs; private int lastMoveReliableSeq; @@ -96,6 +100,30 @@ public abstract class GameEntity { return getFightProperties().getOrDefault(prop.getId(), 0f); } + public int getBlockId() { + return blockId; + } + + public void setBlockId(int blockId) { + this.blockId = blockId; + } + + public int getConfigId() { + return configId; + } + + public void setConfigId(int configId) { + this.configId = configId; + } + + public int getGroupId() { + return groupId; + } + + public void setGroupId(int groupId) { + this.groupId = groupId; + } + protected MotionInfo getMotionInfo() { MotionInfo proto = MotionInfo.newBuilder() .setPos(getPosition().toProto()) diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index b942604f5..84ee38424 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -15,7 +15,7 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityBaseGadget; import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.EnterReason; import emu.grasscutter.game.props.FightProperty; @@ -54,7 +54,7 @@ public class TeamManager { @Transient private TeamInfo mpTeam; @Transient private int entityId; @Transient private final List avatars; - @Transient private final Set gadgets; + @Transient private final Set gadgets; @Transient private final IntSet teamResonances; @Transient private final IntSet teamResonancesConfig; @@ -141,7 +141,7 @@ public class TeamManager { this.entityId = entityId; } - public Set getGadgets() { + public Set getGadgets() { return gadgets; } diff --git a/src/main/java/emu/grasscutter/game/props/EntityType.java b/src/main/java/emu/grasscutter/game/props/EntityType.java new file mode 100644 index 000000000..efe6694c4 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/EntityType.java @@ -0,0 +1,93 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum EntityType { + None (0), + Avatar (1), + Monster (2), + Bullet (3), + AttackPhyisicalUnit (4), + AOE (5), + Camera (6), + EnviroArea (7), + Equip (8), + MonsterEquip (9), + Grass (10), + Level (11), + NPC (12), + TransPointFirst (13), + TransPointFirstGadget (14), + TransPointSecond (15), + TransPointSecondGadget (16), + DropItem (17), + Field (18), + Gadget (19), + Water (20), + GatherPoint (21), + GatherObject (22), + AirflowField (23), + SpeedupField (24), + Gear (25), + Chest (26), + EnergyBall (27), + ElemCrystal (28), + Timeline (29), + Worktop (30), + Team (31), + Platform (32), + AmberWind (33), + EnvAnimal (34), + SealGadget (35), + Tree (36), + Bush (37), + QuestGadget (38), + Lightning (39), + RewardPoint (40), + RewardStatue (41), + MPLevel (42), + WindSeed (43), + MpPlayRewardPoint (44), + ViewPoint (45), + RemoteAvatar (46), + GeneralRewardPoint (47), + PlayTeam (48), + OfferingGadget (49), + EyePoint (50), + MiracleRing (51), + Foundation (52), + WidgetGadget (53), + PlaceHolder (99); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private EntityType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static EntityType getTypeByValue(int value) { + return map.getOrDefault(value, None); + } + + public static EntityType getTypeByName(String name) { + return stringMap.getOrDefault(name, None); + } +} diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 543079153..ee6b9a6a9 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -3,9 +3,11 @@ package emu.grasscutter.game.world; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameDepot; +import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.data.def.WorldLevelData; +import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.entity.*; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.TeamInfo; @@ -17,6 +19,13 @@ import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; +import emu.grasscutter.scripts.SceneScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneBlock; +import emu.grasscutter.scripts.data.SceneGadget; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; @@ -36,12 +45,19 @@ public class Scene { private final Set spawnedEntities; private final Set deadSpawnedEntities; + private final Set loadedBlocks; private boolean dontDestroyWhenEmpty; private int time; private ClimateType climate; private int weather; - + + private SceneScriptManager scriptManager; + private DungeonChallenge challenge; + private DungeonData dungeonData; + private int prevScene; // Id of the previous scene + private int prevScenePoint; + public Scene(World world, SceneData sceneData) { this.world = world; this.sceneData = sceneData; @@ -50,9 +66,12 @@ public class Scene { this.time = 8 * 60; this.climate = ClimateType.CLIMATE_SUNNY; + this.prevScene = 3; this.spawnedEntities = new HashSet<>(); this.deadSpawnedEntities = new HashSet<>(); + this.loadedBlocks = new HashSet<>(); + this.scriptManager = new SceneScriptManager(this); } public int getId() { @@ -111,6 +130,22 @@ public class Scene { this.weather = weather; } + public int getPrevScene() { + return prevScene; + } + + public void setPrevScene(int prevScene) { + this.prevScene = prevScene; + } + + public int getPrevScenePoint() { + return prevScenePoint; + } + + public void setPrevScenePoint(int prevPoint) { + this.prevScenePoint = prevPoint; + } + public boolean dontDestroyWhenEmpty() { return dontDestroyWhenEmpty; } @@ -119,6 +154,10 @@ public class Scene { this.dontDestroyWhenEmpty = dontDestroyWhenEmpty; } + public Set getLoadedBlocks() { + return loadedBlocks; + } + public Set getSpawnedEntities() { return spawnedEntities; } @@ -127,6 +166,29 @@ public class Scene { return deadSpawnedEntities; } + public SceneScriptManager getScriptManager() { + return scriptManager; + } + + public DungeonData getDungeonData() { + return dungeonData; + } + + public void setDungeonData(DungeonData dungeonData) { + if (this.dungeonData != null || this.getSceneType() != SceneType.SCENE_DUNGEON || dungeonData.getSceneId() != this.getId()) { + return; + } + this.dungeonData = dungeonData; + } + + public DungeonChallenge getChallenge() { + return challenge; + } + + public void setChallenge(DungeonChallenge challenge) { + this.challenge = challenge; + } + public boolean isInScene(GameEntity entity) { return this.entities.containsKey(entity.getId()); } @@ -151,6 +213,11 @@ public class Scene { } public synchronized void removePlayer(Player player) { + // Remove from challenge if leaving + if (this.getChallenge() != null && this.getChallenge().inProgress()) { + player.sendPacket(new PacketDungeonChallengeFinishNotify(this.getChallenge())); + } + // Remove player from scene getPlayers().remove(player); player.setScene(null); @@ -159,12 +226,12 @@ public class Scene { this.removePlayerAvatars(player); // Remove player gadgets - for (EntityGadget gadget : player.getTeamManager().getGadgets()) { + for (EntityBaseGadget gadget : player.getTeamManager().getGadgets()) { this.removeEntity(gadget); } // Deregister scene if not in use - if (this.getEntities().size() <= 0 && !this.dontDestroyWhenEmpty()) { + if (this.getPlayerCount() <= 0 && !this.dontDestroyWhenEmpty()) { this.getWorld().deregisterScene(this); } } @@ -279,6 +346,11 @@ public class Scene { } } + // Sanity check + if (target.getFightProperties() == null) { + return; + } + // Lose hp target.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -result.getDamage()); @@ -314,7 +386,15 @@ public class Scene { } public void onTick() { - this.checkSpawns(); + if (this.getScriptManager().isInit()) { + this.checkBlocks(); + } else { + // TEMPORARY + this.checkSpawns(); + } + + // Triggers + this.getScriptManager().onTick(); } // TODO - Test @@ -387,6 +467,68 @@ public class Scene { } } + public void checkBlocks() { + Set visible = new HashSet<>(); + + for (Player player : this.getPlayers()) { + for (SceneBlock block : getScriptManager().getBlocks()) { + if (!block.contains(player.getPos())) { + continue; + } + + visible.add(block); + } + } + + Iterator it = this.getLoadedBlocks().iterator(); + while (it.hasNext()) { + SceneBlock block = it.next(); + + if (!visible.contains(block)) { + it.remove(); + + onUnloadBlock(block); + } + } + + for (SceneBlock block : visible) { + if (!this.getLoadedBlocks().contains(block)) { + this.onLoadBlock(block); + this.getLoadedBlocks().add(block); + } + } + } + + // TODO optimize + public void onLoadBlock(SceneBlock block) { + for (SceneGroup group : block.groups) { + // We load the script files for the groups here + if (!group.isLoaded()) { + this.getScriptManager().loadGroupFromScript(group); + } + + group.triggers.forEach(getScriptManager()::registerTrigger); + } + + // Spawn gadgets AFTER triggers are added + for (SceneGroup group : block.groups) { + this.getScriptManager().spawnGadgetsInGroup(group); + } + } + + public void onUnloadBlock(SceneBlock block) { + List toRemove = this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList(); + + if (toRemove.size() > 0) { + toRemove.stream().forEach(this::removeEntityDirectly); + this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE)); + } + + for (SceneGroup group : block.groups) { + group.triggers.forEach(getScriptManager()::deregisterTrigger); + } + } + // Gadgets public void onPlayerCreateGadget(EntityClientGadget gadget) { diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java index 292e63d32..215896fea 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -17,14 +17,16 @@ import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityClientGadget; -import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityBaseGadget; import emu.grasscutter.net.packet.BasePacket; 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.packet.send.PacketDelTeamEntityNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; @@ -212,6 +214,14 @@ public class World implements Iterable { } public boolean transferPlayerToScene(Player player, int sceneId, Position pos) { + return transferPlayerToScene(player, sceneId, null, pos); + } + + public boolean transferPlayerToScene(Player player, int sceneId, DungeonData data) { + return transferPlayerToScene(player, sceneId, data, null); + } + + public boolean transferPlayerToScene(Player player, int sceneId, DungeonData dungeonData, Position pos) { if (GameData.getSceneDataMap().get(sceneId) == null) { return false; } @@ -224,25 +234,51 @@ public class World implements Iterable { // Dont deregister scenes if the player is going to tp back into them if (oldScene.getId() == sceneId) { oldScene.setDontDestroyWhenEmpty(true); - } + } oldScene.removePlayer(player); } Scene newScene = this.getSceneById(sceneId); + newScene.setDungeonData(dungeonData); newScene.addPlayer(player); - player.getPos().set(pos); + // Dungeon + SceneConfig config = newScene.getScriptManager().getConfig(); + if (pos == null && config != null) { + if (config.born_pos != null) { + pos = newScene.getScriptManager().getConfig().born_pos; + } + if (config.born_rot != null) { + player.getRotation().set(config.born_rot); + } + } + + // Set player position + if (pos == null) { + pos = player.getPos(); + } + + player.getPos().set(pos); + if (oldScene != null) { + newScene.setPrevScene(oldScene.getId()); oldScene.setDontDestroyWhenEmpty(false); } - // Teleport packet - if (oldScene == newScene) { - player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.ENTER_GOTO, EnterReason.TransPoint, sceneId, pos)); - } else { - player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.ENTER_JUMP, EnterReason.TransPoint, sceneId, pos)); + // Get enter types + EnterType enterType = EnterType.ENTER_JUMP; + EnterReason enterReason = EnterReason.TransPoint; + + if (dungeonData != null) { + enterType = EnterType.ENTER_DUNGEON; + enterReason = EnterReason.DungeonEnter; + } else if (oldScene == newScene) { + enterType = EnterType.ENTER_GOTO; } + + // Teleport packet + player.sendPacket(new PacketPlayerEnterSceneNotify(player, enterType, enterReason, sceneId, pos)); return true; } diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java index ce0f6f2a0..9be2e5b32 100644 --- a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java +++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java @@ -355,6 +355,8 @@ public class PacketOpcodes { public static final int EvtAvatarSitDownNotify = 392; public static final int EvtAvatarStandUpNotify = 358; public static final int EvtAvatarUpdateFocusNotify = 365; + public static final int EvtAvatarLockChairReq = 354; + public static final int EvtAvatarLockChairRsp = 335; public static final int EvtBeingHitNotify = 360; public static final int EvtBeingHitsCombineNotify = 381; public static final int EvtBulletDeactiveNotify = 388; diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index 89adfeda0..9e0b869b9 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -66,7 +66,7 @@ public final class PluginManager { Enumeration entries = jarFile.entries(); while(entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); - if(entry.isDirectory() || !entry.getName().endsWith(".class")) continue; + if(entry.isDirectory() || !entry.getName().endsWith(".class") || entry.getName().contains("module-info")) continue; String className = entry.getName().replace(".class", "").replace("/", "."); Class clazz = loader.loadClass(className); } diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java new file mode 100644 index 000000000..2f2f31219 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -0,0 +1,345 @@ +package emu.grasscutter.scripts; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.MonsterData; +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.world.Scene; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.constants.ScriptGadgetState; +import emu.grasscutter.scripts.constants.ScriptRegionShape; +import emu.grasscutter.scripts.data.SceneBlock; +import emu.grasscutter.scripts.data.SceneConfig; +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.SceneSuite; +import emu.grasscutter.scripts.data.SceneTrigger; +import emu.grasscutter.scripts.data.SceneVar; +import emu.grasscutter.scripts.data.ScriptArgs; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class SceneScriptManager { + private final Scene scene; + private final ScriptLib scriptLib; + private final LuaValue scriptLibLua; + private final Map variables; + + private Bindings bindings; + private SceneConfig config; + private List blocks; + private Int2ObjectOpenHashMap> triggers; + private boolean isInit; + + public SceneScriptManager(Scene scene) { + this.scene = scene; + this.scriptLib = new ScriptLib(this); + this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib); + this.triggers = new Int2ObjectOpenHashMap<>(); + this.variables = new HashMap<>(); + + // TEMPORARY + if (this.getScene().getId() < 10) { + return; + } + + // Create + this.init(); + } + + public Scene getScene() { + return scene; + } + + public ScriptLib getScriptLib() { + return scriptLib; + } + + public LuaValue getScriptLibLua() { + return scriptLibLua; + } + + public Bindings getBindings() { + return bindings; + } + + public SceneConfig getConfig() { + return config; + } + + public List getBlocks() { + return blocks; + } + + public Map getVariables() { + return variables; + } + + public Set getTriggersByEvent(int eventId) { + return triggers.computeIfAbsent(eventId, e -> new HashSet<>()); + } + + public void registerTrigger(SceneTrigger trigger) { + getTriggersByEvent(trigger.event).add(trigger); + } + + public void deregisterTrigger(SceneTrigger trigger) { + getTriggersByEvent(trigger.event).remove(trigger); + } + + // TODO optimize + public SceneGroup getGroupById(int groupId) { + for (SceneBlock block : this.getScene().getLoadedBlocks()) { + for (SceneGroup group : block.groups) { + if (group.id == groupId) { + return group; + } + } + } + return null; + } + + private void init() { + // Get compiled script if cached + CompiledScript cs = ScriptLoader.getScriptByPath( + Grasscutter.getConfig().SCRIPTS_FOLDER + "Scene/" + getScene().getId() + "/scene" + getScene().getId() + "." + ScriptLoader.getScriptType()); + + if (cs == null) { + Grasscutter.getLogger().warn("No script found for scene " + getScene().getId()); + return; + } + + // Create bindings + 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()); + + this.config = ScriptLoader.getSerializer().toObject(SceneConfig.class, bindings.get("scene_config")); + + // TODO optimize later + // Create blocks + List blockIds = ScriptLoader.getSerializer().toList(Integer.class, bindings.get("blocks")); + List blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, bindings.get("block_rects")); + + for (int i = 0; i < blocks.size(); i++) { + SceneBlock block = blocks.get(0); + block.id = blockIds.get(i); + + loadBlockFromScript(block); + } + + this.blocks = blocks; + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error running script", e); + return; + } + + // TEMP + this.isInit = true; + } + + public boolean isInit() { + return isInit; + } + + private void loadBlockFromScript(SceneBlock block) { + CompiledScript cs = ScriptLoader.getScriptByPath( + Grasscutter.getConfig().SCRIPTS_FOLDER + "Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_block" + block.id + "." + ScriptLoader.getScriptType()); + + if (cs == null) { + return; + } + + // Eval script + try { + cs.eval(getBindings()); + + // Set groups + block.groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups")); + block.groups.forEach(g -> g.block_id = block.id); + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading block " + block.id + " in scene " + getScene().getId(), e); + } + } + + public void loadGroupFromScript(SceneGroup group) { + // Set flag here so if there is no script, we dont call this function over and over again. + group.setLoaded(true); + + CompiledScript cs = ScriptLoader.getScriptByPath( + Grasscutter.getConfig().SCRIPTS_FOLDER + "Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_group" + group.id + "." + ScriptLoader.getScriptType()); + + if (cs == null) { + return; + } + + // Eval script + try { + cs.eval(getBindings()); + + // Set + group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")); + 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.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); + + // Add variables to suite + List variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); + variables.forEach(var -> this.getVariables().put(var.name, var.value)); + + // Add monsters to suite TODO optimize + HashMap map = (HashMap) group.monsters.stream().collect(Collectors.toMap(m -> m.config_id, m -> m)); + + for (SceneSuite suite : group.suites) { + suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); + for (int id : suite.monsters) { + SceneMonster monster = map.get(id); + if (monster != null) { + suite.sceneMonsters.add(monster); + } + } + } + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e); + } + } + + public void onTick() { + checkTriggers(); + } + + public void checkTriggers() { + + } + + public void spawnGadgetsInGroup(SceneGroup group) { + for (SceneGadget g : group.gadgets) { + EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); + + if (entity.getGadgetData() == null) continue; + + entity.setBlockId(group.block_id); + entity.setConfigId(g.config_id); + entity.setGroupId(group.id); + entity.getRotation().set(g.rot); + entity.setState(g.state); + + getScene().addEntity(entity); + this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId())); + } + } + + public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { + spawnMonstersInGroup(group, group.getSuiteByIndex(suiteIndex)); + } + + public void spawnMonstersInGroup(SceneGroup group) { + spawnMonstersInGroup(group, null); + } + + public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { + List monsters = group.monsters; + + if (suite != null) { + monsters = suite.sceneMonsters; + } + + List toAdd = new ArrayList<>(); + + for (SceneMonster monster : monsters) { + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + continue; + } + + // Calculate level + int level = monster.level; + + if (getScene().getDungeonData() != null) { + level = getScene().getDungeonData().getShowLevel(); + } else if (getScene().getWorld().getWorldLevel() > 0) { + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(group.id); + entity.setConfigId(monster.config_id); + + toAdd.add(entity); + } + + if (toAdd.size() > 0) { + getScene().addEntities(toAdd); + + for (GameEntity entity : toAdd) { + callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + } + } + } + + // Events + + public void callEvent(int eventType, ScriptArgs params) { + for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { + LuaValue condition = null; + + if (trigger.condition != null && !trigger.condition.isEmpty()) { + condition = (LuaValue) this.getBindings().get(trigger.condition); + } + + LuaValue ret = LuaValue.TRUE; + + if (condition != null) { + LuaValue args = LuaValue.NIL; + + if (params != null) { + args = CoerceJavaToLua.coerce(params); + } + + ret = condition.call(this.getScriptLibLua(), args); + } + + if (ret.checkboolean() == true) { + LuaValue action = (LuaValue) this.getBindings().get(trigger.action); + action.call(this.getScriptLibLua(), LuaValue.NIL); + } + } + } +} diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java new file mode 100644 index 000000000..57cd73dd3 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -0,0 +1,190 @@ +package emu.grasscutter.scripts; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +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.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; +import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; + +public class ScriptLib { + private final SceneScriptManager sceneScriptManager; + + public ScriptLib(SceneScriptManager sceneScriptManager) { + this.sceneScriptManager = sceneScriptManager; + } + + public SceneScriptManager getSceneScriptManager() { + return sceneScriptManager; + } + + public int SetGadgetStateByConfigId(int configId, int gadgetState) { + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e.getConfigId() == configId).findFirst(); + + if (entity.isEmpty()) { + return 1; + } + + if (!(entity.get() instanceof EntityGadget)) { + return 1; + } + + EntityGadget gadget = (EntityGadget) entity.get(); + gadget.setState(gadgetState); + + getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState)); + return 0; + } + + public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) { + List list = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e.getGroupId() == groupId).toList(); + + for (GameEntity entity : list) { + if (!(entity instanceof EntityGadget)) { + continue; + } + + EntityGadget gadget = (EntityGadget) entity; + gadget.setState(gadgetState); + + getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState)); + } + + return 0; + } + + public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) { + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); + + if (entity.isEmpty()) { + return 1; + } + + if (!(entity.get() instanceof EntityGadget)) { + return 1; + } + + EntityGadget gadget = (EntityGadget) entity.get(); + gadget.addWorktopOptions(options); + + getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); + return 0; + } + + public int DelWorktopOptionByGroupId(int groupId, int configId, int option) { + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); + + if (entity.isEmpty()) { + return 1; + } + + if (!(entity.get() instanceof EntityGadget)) { + return 1; + } + + EntityGadget gadget = (EntityGadget) entity.get(); + gadget.removeWorktopOption(option); + + getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); + return 0; + } + + // Some fields are guessed + public int AutoMonsterTide(int challengeIndex, int groupId, int[] config_ids, int param4, int param5, int param6) { + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + + if (group == null || group.monsters == null) { + return 1; + } + + // TODO just spawn all from group for now + this.getSceneScriptManager().spawnMonstersInGroup(group); + + return 0; + } + + public int AddExtraGroupSuite(int groupId, int suite) { + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + + if (group == null || group.monsters == null) { + return 1; + } + + // TODO just spawn all from group for now + this.getSceneScriptManager().spawnMonstersInGroup(group, suite); + + return 0; + } + + // param3 (probably time limit for timed dungeons) + public int ActiveChallenge(int challengeId, int challengeIndex, int param3, int groupId, int param4, int param5) { + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + + if (group == null || group.monsters == null) { + return 1; + } + + DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); + challenge.setChallengeId(challengeId); + challenge.setChallengeIndex(challengeIndex); + + getSceneScriptManager().getScene().setChallenge(challenge); + + challenge.start(); + return 0; + } + + public int GetGroupMonsterCountByGroupId(int groupId) { + return (int) getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster && e.getGroupId() == groupId) + .count(); + } + + public int GetGroupVariableValue(String var) { + return getSceneScriptManager().getVariables().getOrDefault(var, 0); + } + + public LuaValue ChangeGroupVariableValue(String var, int value) { + getSceneScriptManager().getVariables().put(var, value); + return LuaValue.ZERO; + } + + public int RefreshGroup(LuaTable table) { + // Kill and Respawn? + int groupId = table.get("group_id").toint(); + int suite = table.get("suite").toint(); + + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + + if (group == null || group.monsters == null) { + return 1; + } + + // TODO just spawn all from group for now + this.getSceneScriptManager().spawnMonstersInGroup(group, suite); + + return 0; + } + + public void PrintContextLog(String msg) { + Grasscutter.getLogger().info("[LUA] " + msg); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java new file mode 100644 index 000000000..c4157c690 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java @@ -0,0 +1,74 @@ +package emu.grasscutter.scripts; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.serializer.LuaSerializer; +import emu.grasscutter.scripts.serializer.Serializer; + +public class ScriptLoader { + private static ScriptEngineManager sm; + private static ScriptEngine engine; + private static ScriptEngineFactory factory; + private static String fileType; + private static Serializer serializer; + + private static Map scripts = new HashMap<>(); + + public synchronized static void init() throws Exception { + if (sm != null) { + throw new Exception("Script loader already initialized"); + } + + sm = new ScriptEngineManager(); + engine = sm.getEngineByName("luaj"); + factory = getEngine().getFactory(); + fileType = "lua"; + serializer = new LuaSerializer(); + } + + public static ScriptEngine getEngine() { + return engine; + } + + public static String getScriptType() { + return fileType; + } + + public static Serializer getSerializer() { + return serializer; + } + + public static CompiledScript getScriptByPath(String path) { + CompiledScript sc = scripts.get(path); + + Grasscutter.getLogger().info("Loaded " + path); + + if (sc == null) { + File file = new File(path); + + if (!file.exists()) return null; + + try (FileReader fr = new FileReader(file)) { + sc = ((Compilable) getEngine()).compile(fr); + scripts.put(path, sc); + } catch (Exception e) { + //e.printStackTrace(); + return null; + } + } + + return sc; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/constants/EventType.java b/src/main/java/emu/grasscutter/scripts/constants/EventType.java new file mode 100644 index 000000000..58d8dc3ab --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/constants/EventType.java @@ -0,0 +1,82 @@ +package emu.grasscutter.scripts.constants; + +public class EventType { + public static final int EVENT_NONE = 0; + public static final int EVENT_ANY_MONSTER_DIE = 1; + public static final int EVENT_ANY_GADGET_DIE = 2; + public static final int EVENT_VARIABLE_CHANGE = 3; + public static final int EVENT_ENTER_REGION = 4; + public static final int EVENT_LEAVE_REGION = 5; + public static final int EVENT_GADGET_CREATE = 6; + public static final int EVENT_GADGET_STATE_CHANGE = 7; + public static final int EVENT_DUNGEON_SETTLE = 8; + public static final int EVENT_SELECT_OPTION = 9; + public static final int EVENT_CLIENT_EXECUTE = 10; + public static final int EVENT_ANY_MONSTER_LIVE = 11; + public static final int EVENT_SPECIFIC_MONSTER_HP_CHANGE = 12; + public static final int EVENT_CITY_LEVELUP_UNLOCK_DUNGEON_ENTRY = 13; + public static final int EVENT_DUNGEON_BROADCAST_ONTIMER = 14; + public static final int EVENT_TIMER_EVENT = 15; + public static final int EVENT_CHALLENGE_SUCCESS = 16; + public static final int EVENT_CHALLENGE_FAIL = 17; + public static final int EVENT_SEAL_BATTLE_BEGIN = 18; + public static final int EVENT_SEAL_BATTLE_END = 19; + public static final int EVENT_GATHER = 20; + public static final int EVENT_QUEST_FINISH = 21; + public static final int EVENT_MONSTER_BATTLE = 22; + public static final int EVENT_CITY_LEVELUP = 23; + public static final int EVENT_CUTSCENE_END = 24; + public static final int EVENT_AVATAR_NEAR_PLATFORM = 25; + public static final int EVENT_PLATFORM_REACH_POINT = 26; + public static final int EVENT_UNLOCK_TRANS_POINT = 27; + public static final int EVENT_QUEST_START = 28; + public static final int EVENT_GROUP_LOAD = 29; + public static final int EVENT_GROUP_WILL_UNLOAD = 30; + public static final int EVENT_GROUP_WILL_REFRESH = 31; + public static final int EVENT_GROUP_REFRESH = 32; + public static final int EVENT_DUNGEON_REWARD_GET = 33; + public static final int EVENT_SPECIFIC_GADGET_HP_CHANGE = 34; + public static final int EVENT_MONSTER_TIDE_OVER = 35; + public static final int EVENT_MONSTER_TIDE_CREATE = 36; + public static final int EVENT_MONSTER_TIDE_DIE = 37; + public static final int EVENT_SEALAMP_PHASE_CHANGE = 38; + public static final int EVENT_BLOSSOM_PROGRESS_FINISH = 39; + public static final int EVENT_BLOSSOM_CHEST_DIE = 40; + public static final int EVENT_GADGET_PLAY_START = 41; + public static final int EVENT_GADGET_PLAY_START_CD = 42; + public static final int EVENT_GADGET_PLAY_STOP = 43; + public static final int EVENT_GADGET_LUA_NOTIFY = 44; + public static final int EVENT_MP_PLAY_PREPARE = 45; + public static final int EVENT_MP_PLAY_BATTLE = 46; + public static final int EVENT_MP_PLAY_PREPARE_INTERRUPT = 47; + public static final int EVENT_SELECT_DIFFICULTY = 48; + public static final int EVENT_SCENE_MP_PLAY_BATTLE_STATE = 49; + public static final int EVENT_SCENE_MP_PLAY_BATTLE_STAGE_CHANGE = 50; + public static final int EVENT_SCENE_MP_PLAY_BATTLE_RESULT = 51; + public static final int EVENT_SEAL_BATTLE_PROGRESS_DECREASE = 52; + public static final int EVENT_GENERAL_REWARD_DIE = 53; + public static final int EVENT_SCENE_MP_PLAY_BATTLE_INTERRUPT = 54; + public static final int EVENT_MONSTER_DIE_BEFORE_LEAVE_SCENE = 55; + public static final int EVENT_SCENE_MP_PLAY_OPEN = 56; + public static final int EVENT_OFFERING_LEVELUP = 57; + public static final int EVENT_DUNGEON_REVIVE = 58; + public static final int EVENT_SCENE_MP_PLAY_ALL_AVATAR_DIE = 59; + public static final int EVENT_DUNGEON_ALL_AVATAR_DIE = 60; + public static final int EVENT_GENERAL_REWARD_TAKEN = 61; + public static final int EVENT_PLATFORM_REACH_ARRAYPOINT = 62; + public static final int EVENT_SCENE_MULTISTAGE_PLAY_STAGE_END = 63; + public static final int EVENT_SCENE_MULTISTAGE_PLAY_END_STAGE_REQ = 64; + public static final int EVENT_MECHANICUS_PICKED_CARD = 65; + public static final int EVENT_POOL_MONSTER_TIDE_OVER = 66; + public static final int EVENT_POOL_MONSTER_TIDE_CREATE = 67; + public static final int EVENT_POOL_MONSTER_TIDE_DIE = 68; + public static final int EVENT_DUNGEON_AVATAR_SLIP_DIE = 69; + public static final int EVENT_GALLERY_START = 70; + public static final int EVENT_GALLERY_STOP = 71; + public static final int EVENT_TIME_AXIS_PASS = 72; + public static final int EVENT_FLEUR_FAIR_DUNGEON_ALL_PLAYER_ENTER = 73; + public static final int EVENT_GADGETTALK_DONE = 74; + public static final int EVENT_SET_GAME_TIME = 75; + public static final int EVENT_HIDE_AND_SEEK_PLAYER_QUIT = 76; + public static final int EVENT_AVATAR_DIE = 77; +} diff --git a/src/main/java/emu/grasscutter/scripts/constants/ScriptGadgetState.java b/src/main/java/emu/grasscutter/scripts/constants/ScriptGadgetState.java new file mode 100644 index 000000000..a36340ab6 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/constants/ScriptGadgetState.java @@ -0,0 +1,24 @@ +package emu.grasscutter.scripts.constants; + +public class ScriptGadgetState { + public static final int Default = 0; + public static final int GatherDrop = 1; + public static final int ChestLocked = 101; + public static final int ChestOpened = 102; + public static final int ChestTrap = 103; + public static final int ChestBramble = 104; + public static final int ChestFrozen = 105; + public static final int ChestRock = 106; + public static final int GearStart = 201; + public static final int GearStop = 202; + public static final int GearAction1 = 203; + public static final int GearAction2 = 204; + public static final int CrystalResonate1 = 301; + public static final int CrystalResonate2 = 302; + public static final int CrystalExplode = 303; + public static final int CrystalDrain = 304; + public static final int StatueActive = 401; + public static final int Action01 = 901; + public static final int Action02 = 902; + public static final int Action03 = 903; +} diff --git a/src/main/java/emu/grasscutter/scripts/constants/ScriptRegionShape.java b/src/main/java/emu/grasscutter/scripts/constants/ScriptRegionShape.java new file mode 100644 index 000000000..abb19387f --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/constants/ScriptRegionShape.java @@ -0,0 +1,7 @@ +package emu.grasscutter.scripts.constants; + +public class ScriptRegionShape { + public static final int NONE = 0; + public static final int SPHERE = 1; + public static final int CUBIC = 2; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java new file mode 100644 index 000000000..11a930fdd --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -0,0 +1,17 @@ +package emu.grasscutter.scripts.data; + +import java.util.List; + +import emu.grasscutter.utils.Position; + +public class SceneBlock { + public int id; + public Position max; + public Position min; + public List groups; + + public boolean contains(Position pos) { + return pos.getX() <= max.getX() && pos.getX() >= min.getX() && + pos.getZ() <= max.getZ() && pos.getZ() >= min.getZ(); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java new file mode 100644 index 000000000..3a1ca60ea --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java @@ -0,0 +1,11 @@ +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; + +public class SceneConfig { + public Position vision_anchor; + public Position born_pos; + public Position born_rot; + public Position begin_pos; + public Position size; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java new file mode 100644 index 000000000..e5340ae55 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -0,0 +1,12 @@ +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; + +public class SceneGadget { + public int level; + public int config_id; + public int gadget_id; + public int state; + public Position pos; + public Position rot; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java new file mode 100644 index 000000000..d72d02d53 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -0,0 +1,33 @@ +package emu.grasscutter.scripts.data; + +import java.util.List; + +import emu.grasscutter.utils.Position; + +public class SceneGroup { + public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference + + public int id; + public int refresh_id; + public Position pos; + + public List monsters; + public List gadgets; + public List triggers; + public List suites; + public SceneInitConfig init_config; + + private transient boolean isLoaded; // Not an actual variable in the scripts either + + public boolean isLoaded() { + return isLoaded; + } + + public boolean setLoaded(boolean loaded) { + return loaded; + } + + public SceneSuite getSuiteByIndex(int index) { + return suites.get(index - 1); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java new file mode 100644 index 000000000..fcdc92c87 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java @@ -0,0 +1,9 @@ +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; + +public class SceneInitConfig { + public int suite; + public int end_suite; + public int rand_suite; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java new file mode 100644 index 000000000..56d2e3d3d --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -0,0 +1,11 @@ +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; + +public class SceneMonster { + public int level; + public int config_id; + public int monster_id; + public Position pos; + public Position rot; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java new file mode 100644 index 000000000..d84504569 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -0,0 +1,13 @@ +package emu.grasscutter.scripts.data; + +import java.util.List; + +import emu.grasscutter.utils.Position; + +public class SceneSuite { + public List monsters; + public List triggers; + public int rand_weight; + + public transient List sceneMonsters; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java new file mode 100644 index 000000000..a1603b1e6 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -0,0 +1,10 @@ +package emu.grasscutter.scripts.data; + +public class SceneTrigger { + public String name; + public int config_id; + public int event; + public String source; + public String condition; + public String action; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java new file mode 100644 index 000000000..9ead6f474 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java @@ -0,0 +1,7 @@ +package emu.grasscutter.scripts.data; + +public class SceneVar { + public String name; + public int value; + public boolean no_refresh; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java new file mode 100644 index 000000000..3073f7322 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java @@ -0,0 +1,47 @@ +package emu.grasscutter.scripts.data; + +public class ScriptArgs { + public int param1; + public int param2; + public int param3; + + public ScriptArgs() { + + } + + public ScriptArgs(int param1) { + this.param1 = param1; + } + + public ScriptArgs(int param1, int param2) { + this.param1 = param1; + this.param2 = param2; + } + + public int getParam1() { + return param1; + } + + public ScriptArgs setParam1(int param1) { + this.param1 = param1; + return this; + } + + public int getParam2() { + return param2; + } + + public ScriptArgs setParam2(int param2) { + this.param2 = param2; + return this; + } + + public int getParam3() { + return param3; + } + + public ScriptArgs setParam3(int param3) { + this.param3 = param3; + return this; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java new file mode 100644 index 000000000..a63328b55 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -0,0 +1,108 @@ +package emu.grasscutter.scripts.serializer; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +public class LuaSerializer implements Serializer { + + @Override + public List toList(Class type, Object obj) { + return serializeList(type, (LuaTable) obj); + } + + @Override + public T toObject(Class type, Object obj) { + return serialize(type, (LuaTable) obj); + } + + public List serializeList(Class type, LuaTable table) { + List list = new ArrayList(); + + try { + LuaValue[] keys = table.keys(); + for (LuaValue k : keys) { + try { + LuaValue keyValue = table.get(k); + + T object = null; + + if (keyValue.istable()) { + object = serialize(type, keyValue.checktable()); + } else if (keyValue.isint()) { + object = (T) (Integer) keyValue.toint(); + } else if (keyValue.isnumber()) { + object = (T) (Float) keyValue.tofloat(); // terrible... + } else if (keyValue.isstring()) { + object = (T) keyValue.tojstring(); + } else { + object = (T) keyValue; + } + + if (object != null) { + list.add(object); + } + } catch (Exception ex) { + + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return list; + } + + public T serialize(Class type, LuaTable table) { + T object = null; + + if (type == List.class) { + try { + Class listType = (Class) type.getTypeParameters()[0].getClass(); + return (T) serializeList(listType, table); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + try { + object = type.getDeclaredConstructor().newInstance(null); + + LuaValue[] keys = table.keys(); + for (LuaValue k : keys) { + try { + Field field = object.getClass().getDeclaredField(k.checkjstring()); + if (field == null) { + continue; + } + + field.setAccessible(true); + LuaValue keyValue = table.get(k); + + if (keyValue.istable()) { + field.set(object, serialize(field.getType(), keyValue.checktable())); + } else if (field.getType().equals(float.class)) { + field.setFloat(object, keyValue.tofloat()); + } else if (field.getType().equals(int.class)) { + field.setInt(object, keyValue.toint()); + } else if (field.getType().equals(String.class)) { + field.set(object, keyValue.tojstring()); + } else { + field.set(object, keyValue); + } + } catch (Exception ex) { + //ex.printStackTrace(); + continue; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return object; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/serializer/Serializer.java b/src/main/java/emu/grasscutter/scripts/serializer/Serializer.java new file mode 100644 index 000000000..64bd266d2 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/serializer/Serializer.java @@ -0,0 +1,12 @@ +package emu.grasscutter.scripts.serializer; + +import java.util.List; + +import org.luaj.vm2.LuaTable; + +public interface Serializer { + + public List toList(Class type, Object obj); + + public T toObject(Class type, Object obj); +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java index a490e9d73..286fbdc67 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java @@ -2,6 +2,7 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DungeonEntryInfoReqOuterClass.DungeonEntryInfoReq; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; @@ -10,7 +11,9 @@ public class HandlerDungeonEntryInfoReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + DungeonEntryInfoReq req = DungeonEntryInfoReq.parseFrom(payload); + session.getServer().getDungeonManager().getEntryInfo(session.getPlayer(), req.getPointId()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarLockChairReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarLockChairReq.java new file mode 100644 index 000000000..2a325b438 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarLockChairReq.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtAvatarLockChairReqOuterClass.EvtAvatarLockChairReq; +import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEvtAvatarLockChairRsp; + +@Opcodes(PacketOpcodes.EvtAvatarLockChairReq) +public class HandlerEvtAvatarLockChairReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + PacketHead head = PacketHead.parseFrom(header); + EvtAvatarLockChairReq lockChairReq = EvtAvatarLockChairReq.parseFrom(payload); + + EntityAvatar entityAvatar = session.getPlayer().getTeamManager().getCurrentAvatarEntity(); + + session.send(new PacketEvtAvatarLockChairRsp(head.getClientSequenceId(), entityAvatar, lockChairReq)); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarSitDownNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarSitDownNotify.java index d825bf21a..91b774240 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarSitDownNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarSitDownNotify.java @@ -1,9 +1,9 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.EvtAvatarSitDownNotifyOuterClass.EvtAvatarSitDownNotify; -import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketEvtAvatarSitDownNotify; @@ -14,7 +14,7 @@ public class HandlerEvtAvatarSitDownNotify extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { EvtAvatarSitDownNotify notify = EvtAvatarSitDownNotify.parseFrom(payload); - session.send(new PacketEvtAvatarSitDownNotify(notify)); + session.getPlayer().getScene().broadcastPacket(new PacketEvtAvatarSitDownNotify(notify)); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarStandUpNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarStandUpNotify.java new file mode 100644 index 000000000..ebb02c41e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtAvatarStandUpNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtAvatarStandUpNotifyOuterClass.EvtAvatarStandUpNotify; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEvtAvatarStandUpNotify; + +@Opcodes(PacketOpcodes.EvtAvatarStandUpNotify) +public class HandlerEvtAvatarStandUpNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + EvtAvatarStandUpNotify notify = EvtAvatarStandUpNotify.parseFrom(payload); + + session.getPlayer().getScene().broadcastPacket(new PacketEvtAvatarStandUpNotify(notify)); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerEnterDungeonReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerEnterDungeonReq.java new file mode 100644 index 000000000..ce05c8ccf --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerEnterDungeonReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerEnterDungeonReqOuterClass.PlayerEnterDungeonReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PlayerEnterDungeonReq) +public class HandlerPlayerEnterDungeonReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + PlayerEnterDungeonReq req = PlayerEnterDungeonReq.parseFrom(payload); + + session.getServer().getDungeonManager().enterDungeon(session.getPlayer(), req.getPointId(), req.getDungeonId()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerQuitDungeonReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerQuitDungeonReq.java new file mode 100644 index 000000000..e33190847 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerQuitDungeonReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PlayerQuitDungeonReq) +public class HandlerPlayerQuitDungeonReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.getPlayer().getServer().getDungeonManager().exitDungeon(session.getPlayer()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java new file mode 100644 index 000000000..82980c452 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java @@ -0,0 +1,38 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSelectWorktopOptionRsp; + +@Opcodes(PacketOpcodes.SelectWorktopOptionReq) +public class HandlerSelectWorktopOptionReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SelectWorktopOptionReq req = SelectWorktopOptionReq.parseFrom(payload); + + try { + GameEntity entity = session.getPlayer().getScene().getEntityById(req.getGadgetEntityId()); + + if (entity == null || !(entity instanceof EntityGadget)) { + return; + } + + session.getPlayer().getScene().getScriptManager().callEvent( + EventType.EVENT_SELECT_OPTION, + new ScriptArgs(entity.getConfigId(), req.getOptionId()) + ); + } finally { + // Always send packet + session.send(new PacketSelectWorktopOptionRsp(req.getGadgetEntityId(), req.getOptionId())); + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java new file mode 100644 index 000000000..452ca7118 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChallengeDataNotifyOuterClass.ChallengeDataNotify; + +public class PacketChallengeDataNotify extends BasePacket { + + public PacketChallengeDataNotify(DungeonChallenge challenge, int index, int value) { + super(PacketOpcodes.ChallengeDataNotify); + + ChallengeDataNotify proto = ChallengeDataNotify.newBuilder() + .setChallengeIndex(challenge.getChallengeIndex()) + .setParamIndex(index) + .setValue(value) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java new file mode 100644 index 000000000..2b8112728 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DungeonChallengeBeginNotifyOuterClass.DungeonChallengeBeginNotify; + +public class PacketDungeonChallengeBeginNotify extends BasePacket { + + public PacketDungeonChallengeBeginNotify(DungeonChallenge challenge) { + super(PacketOpcodes.DungeonChallengeBeginNotify); + + DungeonChallengeBeginNotify proto = DungeonChallengeBeginNotify.newBuilder() + .setChallengeId(challenge.getChallengeId()) + .setChallengeIndex(challenge.getChallengeIndex()) + .setGroupId(challenge.getGroup().id) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java new file mode 100644 index 000000000..0fbcd9570 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DungeonChallengeFinishNotifyOuterClass.DungeonChallengeFinishNotify; + +public class PacketDungeonChallengeFinishNotify extends BasePacket { + + public PacketDungeonChallengeFinishNotify(DungeonChallenge challenge) { + super(PacketOpcodes.DungeonChallengeFinishNotify); + + DungeonChallengeFinishNotify proto = DungeonChallengeFinishNotify.newBuilder() + .setChallengeIndex(challenge.getChallengeIndex()) + .setIsSuccess(challenge.isSuccess()) + .setUnk1(challenge.getChallengeId()) + .setUnk2(30) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java new file mode 100644 index 000000000..a2cc052bb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java @@ -0,0 +1,37 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Arrays; + +import emu.grasscutter.data.common.PointData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DungeonEntryInfoOuterClass.DungeonEntryInfo; +import emu.grasscutter.net.proto.DungeonEntryInfoRspOuterClass.DungeonEntryInfoRsp; + +public class PacketDungeonEntryInfoRsp extends BasePacket { + + public PacketDungeonEntryInfoRsp(Player player, PointData pointData) { + super(PacketOpcodes.DungeonEntryInfoRsp); + + DungeonEntryInfoRsp.Builder proto = DungeonEntryInfoRsp.newBuilder() + .setPointId(pointData.getId()); + + for (int dungeonId : pointData.getDungeonIds()) { + DungeonEntryInfo info = DungeonEntryInfo.newBuilder().setDungeonId(dungeonId).build(); + proto.addDungeonEntryList(info); + } + + this.setData(proto); + } + + public PacketDungeonEntryInfoRsp() { + super(PacketOpcodes.DungeonEntryInfoRsp); + + DungeonEntryInfoRsp proto = DungeonEntryInfoRsp.newBuilder() + .setRetcode(1) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEvtAvatarLockChairRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEvtAvatarLockChairRsp.java new file mode 100644 index 000000000..5b703aea3 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEvtAvatarLockChairRsp.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtAvatarLockChairReqOuterClass.EvtAvatarLockChairReq; +import emu.grasscutter.net.proto.EvtAvatarLockChairRspOuterClass.EvtAvatarLockChairRsp; +import emu.grasscutter.net.proto.RetcodeOuterClass; + +public class PacketEvtAvatarLockChairRsp extends BasePacket { + public PacketEvtAvatarLockChairRsp(int clientSequence, EntityAvatar entityAvatar, EvtAvatarLockChairReq lockChairReq) { + super(PacketOpcodes.EvtAvatarLockChairRsp); + + EvtAvatarLockChairRsp p = EvtAvatarLockChairRsp.newBuilder() + .setRetcode(RetcodeOuterClass.Retcode.RET_SUCC_VALUE) + .setEntityId(entityAvatar.getId()) + .setPosition(lockChairReq.getPosition()) + .setChairId(lockChairReq.getChairId()) + .build(); + + this.setData(p); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEvtAvatarStandUpNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEvtAvatarStandUpNotify.java new file mode 100644 index 000000000..07cdf9054 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEvtAvatarStandUpNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtAvatarStandUpNotifyOuterClass.EvtAvatarStandUpNotify; + +public class PacketEvtAvatarStandUpNotify extends BasePacket { + + public PacketEvtAvatarStandUpNotify(EvtAvatarStandUpNotify notify) { + super(PacketOpcodes.EvtAvatarStandUpNotify); + + EvtAvatarStandUpNotify proto = EvtAvatarStandUpNotify.newBuilder() + .setEntityId(notify.getEntityId()) + .setDirection(notify.getDirection()) + .setPerformID(notify.getPerformID()) + .setChairId(notify.getChairId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java index d15cca8a8..c5e5d723e 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java @@ -1,6 +1,6 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityBaseGadget; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.GadgetInteractRspOuterClass.GadgetInteractRsp; @@ -8,7 +8,7 @@ import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.RetcodeOuterClass; public class PacketGadgetInteractRsp extends BasePacket { - public PacketGadgetInteractRsp(EntityGadget gadget, InteractType interact) { + public PacketGadgetInteractRsp(EntityBaseGadget gadget, InteractType interact) { super(PacketOpcodes.GadgetInteractRsp); GadgetInteractRsp proto = GadgetInteractRsp.newBuilder() diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java new file mode 100644 index 000000000..e2af45633 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GadgetStateNotifyOuterClass.GadgetStateNotify; + +public class PacketGadgetStateNotify extends BasePacket { + + public PacketGadgetStateNotify(EntityGadget gadget, int newState) { + super(PacketOpcodes.GadgetStateNotify); + + GadgetStateNotify proto = GadgetStateNotify.newBuilder() + .setGadgetEntityId(gadget.getId()) + .setGadgetState(newState) + .setIsEnableInteract(true) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java index 16caca296..9c96888ea 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java @@ -7,7 +7,5 @@ public class PacketPathfindingEnterSceneRsp extends BasePacket { public PacketPathfindingEnterSceneRsp(int clientSequence) { super(PacketOpcodes.PathfindingEnterSceneRsp); - - this.buildHeader(clientSequence); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterDungeonRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterDungeonRsp.java new file mode 100644 index 000000000..913ca109e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterDungeonRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerEnterDungeonRspOuterClass.PlayerEnterDungeonRsp; + +public class PacketPlayerEnterDungeonRsp extends BasePacket { + + public PacketPlayerEnterDungeonRsp(int pointId, int dungeonId) { + super(PacketOpcodes.PlayerEnterDungeonRsp); + + PlayerEnterDungeonRsp proto = PlayerEnterDungeonRsp.newBuilder() + .setPointId(pointId) + .setDungeonId(dungeonId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSelectWorktopOptionRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSelectWorktopOptionRsp.java new file mode 100644 index 000000000..72d77e583 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSelectWorktopOptionRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SelectWorktopOptionRspOuterClass.SelectWorktopOptionRsp; + +public class PacketSelectWorktopOptionRsp extends BasePacket { + + public PacketSelectWorktopOptionRsp(int entityId, int optionId) { + super(PacketOpcodes.SelectWorktopOptionRsp); + + SelectWorktopOptionRsp proto = SelectWorktopOptionRsp.newBuilder() + .setGadgetEntityId(entityId) + .setOptionId(optionId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java new file mode 100644 index 000000000..14648a618 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WorktopOptionNotifyOuterClass.WorktopOptionNotify; + +public class PacketWorktopOptionNotify extends BasePacket { + + public PacketWorktopOptionNotify(EntityGadget gadget) { + super(PacketOpcodes.WorktopOptionNotify); + + WorktopOptionNotify.Builder proto = WorktopOptionNotify.newBuilder() + .setGadgetEntityId(gadget.getId()); + + if (gadget.getWorktopOptions() != null) { + proto.addAllOptionList(gadget.getWorktopOptions()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index fcc35b1d4..e76556f1e 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -67,6 +67,7 @@ public final class Utils { private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); public static String bytesToHex(byte[] bytes) { + if (bytes == null) return ""; char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF;