From d71b7abfc305fb7ff4b6ccafbc4c3e0c8696b837 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 28 Apr 2022 22:19:14 -0700 Subject: [PATCH] Implement script support needed for dungeons Only a few are supported right now You will need certain script files in ./resources/Scripts --- build.gradle | 5 +- src/main/java/emu/grasscutter/Config.java | 1 + .../java/emu/grasscutter/Grasscutter.java | 2 + .../emu/grasscutter/data/def/GadgetData.java | 5 +- .../game/dungeons/DungeonChallenge.java | 90 ++++++ .../game/dungeons/DungeonManager.java | 4 +- .../game/entity/EntityBaseGadget.java | 18 ++ .../game/entity/EntityClientGadget.java | 2 +- .../grasscutter/game/entity/EntityGadget.java | 145 ++++++++- .../grasscutter/game/entity/EntityItem.java | 2 +- .../game/entity/EntityMonster.java | 25 +- .../grasscutter/game/entity/GameEntity.java | 28 ++ .../grasscutter/game/player/TeamManager.java | 6 +- .../grasscutter/game/props/EntityType.java | 93 ++++++ .../emu/grasscutter/game/world/Scene.java | 118 +++++++- .../emu/grasscutter/game/world/World.java | 19 +- .../scripts/SceneScriptManager.java | 283 ++++++++++++++++++ .../emu/grasscutter/scripts/ScriptLib.java | 165 ++++++++++ .../emu/grasscutter/scripts/ScriptLoader.java | 74 +++++ .../scripts/constants/ScriptEventType.java | 82 +++++ .../scripts/constants/ScriptGadgetState.java | 24 ++ .../scripts/constants/ScriptRegionShape.java | 7 + .../grasscutter/scripts/data/SceneBlock.java | 17 ++ .../grasscutter/scripts/data/SceneConfig.java | 11 + .../grasscutter/scripts/data/SceneGadget.java | 12 + .../grasscutter/scripts/data/SceneGroup.java | 17 ++ .../scripts/data/SceneInitConfig.java | 9 + .../scripts/data/SceneMonster.java | 11 + .../grasscutter/scripts/data/SceneSuite.java | 10 + .../scripts/data/SceneTrigger.java | 10 + .../scripts/serializer/LuaSerializer.java | 108 +++++++ .../scripts/serializer/Serializer.java | 12 + .../recv/HandlerSelectWorktopOptionReq.java | 22 ++ .../PacketDungeonChallengeBeginNotify.java | 20 ++ .../PacketDungeonChallengeFinishNotify.java | 20 ++ .../packet/send/PacketGadgetInteractRsp.java | 4 +- .../packet/send/PacketGadgetStateNotify.java | 21 ++ .../send/PacketSelectWorktopOptionRsp.java | 19 ++ .../send/PacketWorktopOptionNotify.java | 22 ++ 39 files changed, 1499 insertions(+), 44 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java create mode 100644 src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java create mode 100644 src/main/java/emu/grasscutter/game/props/EntityType.java create mode 100644 src/main/java/emu/grasscutter/scripts/SceneScriptManager.java create mode 100644 src/main/java/emu/grasscutter/scripts/ScriptLib.java create mode 100644 src/main/java/emu/grasscutter/scripts/ScriptLoader.java create mode 100644 src/main/java/emu/grasscutter/scripts/constants/ScriptEventType.java create mode 100644 src/main/java/emu/grasscutter/scripts/constants/ScriptGadgetState.java create mode 100644 src/main/java/emu/grasscutter/scripts/constants/ScriptRegionShape.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneBlock.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneConfig.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneGadget.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneGroup.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneMonster.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneSuite.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java create mode 100644 src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java create mode 100644 src/main/java/emu/grasscutter/scripts/serializer/Serializer.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketGadgetStateNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketSelectWorktopOptionRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java 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 3e6e16d20..495035324 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/data/def/GadgetData.java b/src/main/java/emu/grasscutter/data/def/GadgetData.java index 7a071972b..85854eb88 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; @@ -21,7 +22,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/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java new file mode 100644 index 000000000..66d02221a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java @@ -0,0 +1,90 @@ +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.data.SceneGroup; +import emu.grasscutter.scripts.data.SceneMonster; +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 isSuccess; + + 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 isSuccess; + } + + public void setSuccess(boolean isSuccess) { + this.isSuccess = isSuccess; + } + + public void start() { + getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); + } + + public void finish() { + getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); + + if (this.isSuccess()) { + this.getScene().getScriptManager().onChallengeSuccess(); + } else { + this.getScene().getScriptManager().onChallengeFailure(); + } + } + + public void onMonsterDie(EntityMonster entity) { + score++; + + if (score >= 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 3c5b61903..5662abf4d 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -45,9 +45,11 @@ public class DungeonManager { } int sceneId = data.getSceneId(); + player.getScene().setPrevScene(sceneId); player.getWorld().transferPlayerToScene(player, sceneId, data); + player.getScene().setPrevScenePoint(pointId); player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId)); } @@ -64,7 +66,7 @@ public class DungeonManager { Position prevPos = new Position(GameConstants.START_POSITION); if (dungeonData != null) { - ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, dungeonData.getId()); + ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, player.getScene().getPrevScenePoint()); if (entry != null) { prevPos.set(entry.getPointData().getTranPos()); 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..be5812a1d 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; @@ -36,9 +37,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 +101,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 +109,15 @@ 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().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/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 f3ede3e73..44c5be1d2 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -7,6 +7,7 @@ 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; @@ -18,6 +19,10 @@ 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.data.SceneBlock; +import emu.grasscutter.scripts.data.SceneGadget; +import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; @@ -37,15 +42,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; @@ -58,6 +67,8 @@ public class Scene { this.spawnedEntities = new HashSet<>(); this.deadSpawnedEntities = new HashSet<>(); + this.loadedBlocks = new HashSet<>(); + this.scriptManager = new SceneScriptManager(this); } public int getId() { @@ -124,6 +135,14 @@ public class Scene { this.prevScene = prevScene; } + public int getPrevScenePoint() { + return prevScenePoint; + } + + public void setPrevScenePoint(int prevPoint) { + this.prevScenePoint = prevPoint; + } + public boolean dontDestroyWhenEmpty() { return dontDestroyWhenEmpty; } @@ -132,6 +151,10 @@ public class Scene { this.dontDestroyWhenEmpty = dontDestroyWhenEmpty; } + public Set getLoadedBlocks() { + return loadedBlocks; + } + public Set getSpawnedEntities() { return spawnedEntities; } @@ -140,6 +163,10 @@ public class Scene { return deadSpawnedEntities; } + public SceneScriptManager getScriptManager() { + return scriptManager; + } + public DungeonData getDungeonData() { return dungeonData; } @@ -151,6 +178,14 @@ public class Scene { 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()); } @@ -183,7 +218,7 @@ public class Scene { this.removePlayerAvatars(player); // Remove player gadgets - for (EntityGadget gadget : player.getTeamManager().getGadgets()) { + for (EntityBaseGadget gadget : player.getTeamManager().getGadgets()) { this.removeEntity(gadget); } @@ -338,7 +373,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 @@ -411,6 +454,75 @@ 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.getLoadedBlocks().add(block); + this.onLoadBlock(block); + } + } + } + + // TODO optimize + public void onLoadBlock(SceneBlock block) { + for (SceneGroup group : block.groups) { + group.triggers.forEach(getScriptManager()::registerTrigger); + } + + for (SceneGroup group : block.groups) { + for (SceneGadget g : group.gadgets) { + EntityGadget entity = new EntityGadget(this, g.gadget_id, g.pos); + + if (entity.getGadgetData() == null) continue; + + entity.setBlockId(block.id); + entity.setConfigId(g.config_id); + entity.setGroupId(group.id); + entity.getRotation().set(g.rot); + entity.setState(g.state); + + this.addEntity(entity); + this.getScriptManager().onGadgetCreate(entity); + } + } + } + + 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 2a9324e40..215896fea 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -21,11 +21,12 @@ 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; @@ -243,16 +244,22 @@ public class World implements Iterable { newScene.addPlayer(player); // Dungeon - if (dungeonData != null) { - // TODO set position + 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) { - player.getPos().set(pos); - } else { + if (pos == null) { pos = player.getPos(); } + + player.getPos().set(pos); if (oldScene != null) { newScene.setPrevScene(oldScene.getId()); 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..c5de4807f --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -0,0 +1,283 @@ +package emu.grasscutter.scripts; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.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.ScriptEventType; +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 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 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<>(); + + // 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 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 ScriptEventType()); // 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); + + loadBlock(block); + } + + this.blocks = blocks; + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error running script", e); + } + // TEMP + this.isInit = true; + } + + public boolean isInit() { + return isInit; + } + + private void loadBlock(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(this::loadGroup); + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading block " + block.id + " in scene " + getScene().getId(), e); + } + } + + private void loadGroup(SceneGroup group) { + 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")); + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e); + } + } + + public void onTick() { + checkTriggers(); + } + + public void checkTriggers() { + + } + + // Events + + private LuaValue getFunc(String name) { + return (LuaValue) this.getBindings().get(name); + } + + public void onGadgetCreate(EntityGadget gadget) { + LuaTable params = new LuaTable(); + params.set("param1", gadget.getConfigId()); + + for (SceneTrigger trigger : this.getTriggersByEvent(ScriptEventType.EVENT_GADGET_CREATE)) { + LuaValue condition = getFunc(trigger.condition); + + LuaValue ret = condition.call(this.getScriptLibLua(), params); + + if (ret.checkboolean() == true) { + LuaValue action = getFunc(trigger.action); + action.call(this.getScriptLibLua(), LuaValue.NIL); + } + } + } + + public void onOptionSelect(int entityId, int optionId) { + GameEntity entity = this.getScene().getEntityById(entityId); + + if (entity == null || !(entity instanceof EntityGadget)) { + return; + } + + LuaTable params = new LuaTable(); + params.set("param1", entity.getConfigId()); + params.set("param2", optionId); + + for (SceneTrigger trigger : this.getTriggersByEvent(ScriptEventType.EVENT_SELECT_OPTION)) { + LuaValue condition = getFunc(trigger.condition); + + LuaValue ret = condition.call(this.getScriptLibLua(), params); + + if (ret.checkboolean() == true) { + LuaValue action = getFunc(trigger.action); + action.call(this.getScriptLibLua(), LuaValue.NIL); + } + } + } + + public void onMonsterSpawn(EntityMonster entity) { + LuaTable params = new LuaTable(); + params.set("param1", entity.getConfigId()); + + for (SceneTrigger trigger : this.getTriggersByEvent(ScriptEventType.EVENT_ANY_MONSTER_LIVE)) { + LuaValue condition = getFunc(trigger.condition); + + LuaValue ret = condition.call(this.getScriptLibLua(), params); + + if (ret.checkboolean() == true) { + LuaValue action = getFunc(trigger.action); + action.call(this.getScriptLibLua(), LuaValue.NIL); + } + } + } + + public void onChallengeSuccess() { + for (SceneTrigger trigger : this.getTriggersByEvent(ScriptEventType.EVENT_CHALLENGE_SUCCESS)) { + LuaValue action = getFunc(trigger.action); + action.call(this.getScriptLibLua(), LuaValue.NIL); + } + } + + public void onChallengeFailure() { + for (SceneTrigger trigger : this.getTriggersByEvent(ScriptEventType.EVENT_CHALLENGE_FAIL)) { + LuaValue action = getFunc(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..e44367e6b --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -0,0 +1,165 @@ +package emu.grasscutter.scripts; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.luaj.vm2.LuaTable; + +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.data.SceneGroup; +import emu.grasscutter.scripts.data.SceneMonster; +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) { + 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 + List toAdd = new ArrayList<>(); + + for (SceneMonster monster : group.monsters) { + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + continue; + } + + EntityMonster entity = new EntityMonster(getSceneScriptManager().getScene(), data, monster.pos, monster.level); + entity.getRotation().set(monster.rot); + entity.setGroupId(group.id); + entity.setConfigId(monster.config_id); + + toAdd.add(entity); + } + + if (toAdd.size() > 0) { + getSceneScriptManager().getScene().addEntities(toAdd); + + for (GameEntity entity : toAdd) { + this.getSceneScriptManager().onMonsterSpawn((EntityMonster) entity); + } + } + + return 0; + } + + 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 RefreshGroup(LuaTable table) { + // Add group back to 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/ScriptEventType.java b/src/main/java/emu/grasscutter/scripts/constants/ScriptEventType.java new file mode 100644 index 000000000..1ec2b4bf0 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/constants/ScriptEventType.java @@ -0,0 +1,82 @@ +package emu.grasscutter.scripts.constants; + +public class ScriptEventType { + 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..e227b1853 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -0,0 +1,17 @@ +package emu.grasscutter.scripts.data; + +import java.util.List; + +import emu.grasscutter.utils.Position; + +public class SceneGroup { + 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; +} 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..be9bc0f08 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -0,0 +1,10 @@ +package emu.grasscutter.scripts.data; + +import java.util.List; + +import emu.grasscutter.utils.Position; + +public class SceneSuite { + public List triggers; + public int rand_weight; +} 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/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/HandlerSelectWorktopOptionReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java new file mode 100644 index 000000000..23f3af4cc --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq; +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); + + session.getPlayer().getScene().getScriptManager().onOptionSelect(req.getGadgetEntityId(), req.getOptionId()); + + session.send(new PacketSelectWorktopOptionRsp(req.getGadgetEntityId(), req.getOptionId())); + } + +} 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..2dd734cf5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java @@ -0,0 +1,20 @@ +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()) + .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..323528aae --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java @@ -0,0 +1,20 @@ +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.getChallengeId()) + .setIsSuccess(challenge.isSuccess()) + .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/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); + } +}