Merge pull request #355 from Grasscutters/dungeon-scripts

Implemented Dungeon Support
This commit is contained in:
memetrollsXD 2022-04-29 12:58:01 +02:00 committed by GitHub
commit 2f39aff4a6
55 changed files with 2016 additions and 93 deletions

View File

@ -71,9 +71,10 @@ dependencies {
implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2' 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.quartz-scheduler', name: 'quartz-jobs', version: '2.3.2'
implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1'
protobuf files('proto/') protobuf files('proto/')
} }
application { application {

View File

@ -10,6 +10,7 @@ public final class Config {
public String PACKETS_FOLDER = "./packets/"; public String PACKETS_FOLDER = "./packets/";
public String DUMPS_FOLDER = "./dumps/"; public String DUMPS_FOLDER = "./dumps/";
public String KEY_FOLDER = "./keys/"; public String KEY_FOLDER = "./keys/";
public String SCRIPTS_FOLDER = "./resources/Scripts/";
public String PLUGINS_FOLDER = "./plugins/"; public String PLUGINS_FOLDER = "./plugins/";
public String RunMode = "HYBRID"; // HYBRID, DISPATCH_ONLY, GAME_ONLY public String RunMode = "HYBRID"; // HYBRID, DISPATCH_ONLY, GAME_ONLY

View File

@ -9,6 +9,7 @@ import java.net.InetSocketAddress;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.PluginManager;
import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import org.reflections.Reflections; import org.reflections.Reflections;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -67,6 +68,7 @@ public final class Grasscutter {
// Load all resources. // Load all resources.
ResourceLoader.loadAll(); ResourceLoader.loadAll();
ScriptLoader.init();
// Database // Database
DatabaseManager.initialize(); DatabaseManager.initialize();

View File

@ -61,13 +61,15 @@ public class GameData {
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap = new Int2ObjectOpenHashMap<>();
// Cache // Cache
private static Map<Integer, List<Integer>> fetters = new HashMap<>(); private static Map<Integer, List<Integer>> fetters = new HashMap<>();
private static Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>(); private static Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>();
public static char EJWOA = 's';
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) { public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
Int2ObjectMap<?> map = null; Int2ObjectMap<?> map = null;
@ -100,6 +102,11 @@ public class GameData {
public static Map<String, ScenePointEntry> getScenePointEntries() { public static Map<String, ScenePointEntry> getScenePointEntries() {
return scenePointEntries; return scenePointEntries;
} }
// TODO optimize
public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) {
return getScenePointEntries().get(sceneId + "_" + pointId);
}
public static Int2ObjectMap<AvatarData> getAvatarDataMap() { public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
return avatarDataMap; return avatarDataMap;
@ -269,8 +276,10 @@ public class GameData {
return worldLevelDataMap; return worldLevelDataMap;
} }
public static char EJWOA = 's'; public static Int2ObjectMap<DungeonData> getDungeonDataMap() {
return dungeonDataMap;
}
public static Map<Integer, List<ShopGoodsData>> getShopGoodsDataEntries() { public static Map<Integer, List<ShopGoodsData>> getShopGoodsDataEntries() {
if (shopGoods.isEmpty()) { if (shopGoods.isEmpty()) {
shopGoodsDataMap.forEach((k, v) -> { shopGoodsDataMap.forEach((k, v) -> {

View File

@ -164,6 +164,7 @@ public class ResourceLoader {
for (Map.Entry<String, JsonElement> entry : config.points.entrySet()) { for (Map.Entry<String, JsonElement> entry : config.points.entrySet()) {
PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class); PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class);
pointData.setId(Integer.parseInt(entry.getKey()));
ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData); ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData);
scenePointList.add(sl); scenePointList.add(sl);

View File

@ -1,43 +1,30 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
public class PointData { import emu.grasscutter.utils.Position;
private pos tranPos;
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; return tranPos;
} }
public void setTranPos(pos tranPos) { public int[] getDungeonIds() {
this.tranPos = tranPos; return dungeonIds;
} }
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;
}
}
} }

View File

@ -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() {
}
}

View File

@ -2,12 +2,13 @@ package emu.grasscutter.data.def;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.EntityType;
@ResourceType(name = "GadgetExcelConfigData.json") @ResourceType(name = "GadgetExcelConfigData.json")
public class GadgetData extends GameResource { public class GadgetData extends GameResource {
private int Id; private int Id;
private String Type; private EntityType Type;
private String JsonName; private String JsonName;
private boolean IsInteractive; private boolean IsInteractive;
private String[] Tags; private String[] Tags;
@ -22,7 +23,7 @@ public class GadgetData extends GameResource {
return this.Id; return this.Id;
} }
public String getType() { public EntityType getType() {
return Type; return Type;
} }

View File

@ -9,8 +9,9 @@ import emu.grasscutter.game.props.SceneType;
@ResourceType(name = "SceneExcelConfigData.json") @ResourceType(name = "SceneExcelConfigData.json")
public class SceneData extends GameResource { public class SceneData extends GameResource {
private int Id; private int Id;
private SceneType SceneType; private SceneType Type;
private String ScriptData; private String ScriptData;
@Override @Override
public int getId() { public int getId() {
@ -18,7 +19,7 @@ public class SceneData extends GameResource {
} }
public SceneType getSceneType() { public SceneType getSceneType() {
return SceneType; return Type;
} }
public String getScriptData() { public String getScriptData() {
@ -27,6 +28,6 @@ public class SceneData extends GameResource {
@Override @Override
public void onLoad() { public void onLoad() {
} }
} }

View File

@ -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();
}
}
}

View File

@ -1,6 +1,18 @@
package emu.grasscutter.game.dungeons; 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.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
import emu.grasscutter.utils.Position;
public class DungeonManager { public class DungeonManager {
private final GameServer server; private final GameServer server;
@ -12,4 +24,59 @@ public class DungeonManager {
public GameServer getServer() { public GameServer getServer() {
return server; 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));
}
} }

View File

@ -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) {
}
}

View File

@ -23,7 +23,7 @@ import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityClientGadget extends EntityGadget { public class EntityClientGadget extends EntityBaseGadget {
private final Player owner; private final Player owner;
private final Position pos; private final Position pos;

View File

@ -1,18 +1,157 @@
package emu.grasscutter.game.entity; 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.Scene;
import emu.grasscutter.game.world.World; 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); 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 @Override
public void onDeath(int killerId) { 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();
}
} }

View File

@ -23,7 +23,7 @@ import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityItem extends EntityGadget { public class EntityItem extends EntityBaseGadget {
private final Position pos; private final Position pos;
private final Position rot; private final Position rot;

View File

@ -4,6 +4,7 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.def.MonsterCurveData; import emu.grasscutter.data.def.MonsterCurveData;
import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty; 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.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo; import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatMap;
@ -36,9 +38,6 @@ public class EntityMonster extends GameEntity {
private final Position bornPos; private final Position bornPos;
private final int level; private final int level;
private int weaponEntityId; private int weaponEntityId;
private int groupId;
private int configId;
private int poseId; private int poseId;
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) { public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
@ -103,22 +102,6 @@ public class EntityMonster extends GameEntity {
public boolean isAlive() { public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f; 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() { public int getPoseId() {
return poseId; return poseId;
@ -127,12 +110,18 @@ public class EntityMonster extends GameEntity {
public void setPoseId(int poseId) { public void setPoseId(int poseId) {
this.poseId = poseId; this.poseId = poseId;
} }
@Override @Override
public void onDeath(int killerId) { public void onDeath(int killerId) {
if (this.getSpawnEntry() != null) { if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); 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() { public void recalcStats() {

View File

@ -25,7 +25,7 @@ import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityVehicle extends EntityGadget { public class EntityVehicle extends EntityBaseGadget {
private final Player owner; private final Player owner;
private final Int2FloatOpenHashMap fightProp; private final Int2FloatOpenHashMap fightProp;

View File

@ -17,6 +17,10 @@ public abstract class GameEntity {
private final Scene scene; private final Scene scene;
private SpawnDataEntry spawnEntry; private SpawnDataEntry spawnEntry;
private int blockId;
private int configId;
private int groupId;
private MotionState moveState; private MotionState moveState;
private int lastMoveSceneTimeMs; private int lastMoveSceneTimeMs;
private int lastMoveReliableSeq; private int lastMoveReliableSeq;
@ -96,6 +100,30 @@ public abstract class GameEntity {
return getFightProperties().getOrDefault(prop.getId(), 0f); 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() { protected MotionInfo getMotionInfo() {
MotionInfo proto = MotionInfo.newBuilder() MotionInfo proto = MotionInfo.newBuilder()
.setPos(getPosition().toProto()) .setPos(getPosition().toProto())

View File

@ -15,7 +15,7 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar; 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.ElementType;
import emu.grasscutter.game.props.EnterReason; import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
@ -54,7 +54,7 @@ public class TeamManager {
@Transient private TeamInfo mpTeam; @Transient private TeamInfo mpTeam;
@Transient private int entityId; @Transient private int entityId;
@Transient private final List<EntityAvatar> avatars; @Transient private final List<EntityAvatar> avatars;
@Transient private final Set<EntityGadget> gadgets; @Transient private final Set<EntityBaseGadget> gadgets;
@Transient private final IntSet teamResonances; @Transient private final IntSet teamResonances;
@Transient private final IntSet teamResonancesConfig; @Transient private final IntSet teamResonancesConfig;
@ -141,7 +141,7 @@ public class TeamManager {
this.entityId = entityId; this.entityId = entityId;
} }
public Set<EntityGadget> getGadgets() { public Set<EntityBaseGadget> getGadgets() {
return gadgets; return gadgets;
} }

View File

@ -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<EntityType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, EntityType> 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);
}
}

View File

@ -3,9 +3,11 @@ package emu.grasscutter.game.world;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.def.DungeonData;
import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.SceneData; import emu.grasscutter.data.def.SceneData;
import emu.grasscutter.data.def.WorldLevelData; import emu.grasscutter.data.def.WorldLevelData;
import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.entity.*; import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.TeamInfo; 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.packet.BasePacket;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; 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.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
@ -36,12 +45,19 @@ public class Scene {
private final Set<SpawnDataEntry> spawnedEntities; private final Set<SpawnDataEntry> spawnedEntities;
private final Set<SpawnDataEntry> deadSpawnedEntities; private final Set<SpawnDataEntry> deadSpawnedEntities;
private final Set<SceneBlock> loadedBlocks;
private boolean dontDestroyWhenEmpty; private boolean dontDestroyWhenEmpty;
private int time; private int time;
private ClimateType climate; private ClimateType climate;
private int weather; 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) { public Scene(World world, SceneData sceneData) {
this.world = world; this.world = world;
this.sceneData = sceneData; this.sceneData = sceneData;
@ -50,9 +66,12 @@ public class Scene {
this.time = 8 * 60; this.time = 8 * 60;
this.climate = ClimateType.CLIMATE_SUNNY; this.climate = ClimateType.CLIMATE_SUNNY;
this.prevScene = 3;
this.spawnedEntities = new HashSet<>(); this.spawnedEntities = new HashSet<>();
this.deadSpawnedEntities = new HashSet<>(); this.deadSpawnedEntities = new HashSet<>();
this.loadedBlocks = new HashSet<>();
this.scriptManager = new SceneScriptManager(this);
} }
public int getId() { public int getId() {
@ -111,6 +130,22 @@ public class Scene {
this.weather = weather; 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() { public boolean dontDestroyWhenEmpty() {
return dontDestroyWhenEmpty; return dontDestroyWhenEmpty;
} }
@ -119,6 +154,10 @@ public class Scene {
this.dontDestroyWhenEmpty = dontDestroyWhenEmpty; this.dontDestroyWhenEmpty = dontDestroyWhenEmpty;
} }
public Set<SceneBlock> getLoadedBlocks() {
return loadedBlocks;
}
public Set<SpawnDataEntry> getSpawnedEntities() { public Set<SpawnDataEntry> getSpawnedEntities() {
return spawnedEntities; return spawnedEntities;
} }
@ -127,6 +166,29 @@ public class Scene {
return deadSpawnedEntities; 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) { public boolean isInScene(GameEntity entity) {
return this.entities.containsKey(entity.getId()); return this.entities.containsKey(entity.getId());
} }
@ -151,6 +213,11 @@ public class Scene {
} }
public synchronized void removePlayer(Player player) { 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 // Remove player from scene
getPlayers().remove(player); getPlayers().remove(player);
player.setScene(null); player.setScene(null);
@ -159,12 +226,12 @@ public class Scene {
this.removePlayerAvatars(player); this.removePlayerAvatars(player);
// Remove player gadgets // Remove player gadgets
for (EntityGadget gadget : player.getTeamManager().getGadgets()) { for (EntityBaseGadget gadget : player.getTeamManager().getGadgets()) {
this.removeEntity(gadget); this.removeEntity(gadget);
} }
// Deregister scene if not in use // Deregister scene if not in use
if (this.getEntities().size() <= 0 && !this.dontDestroyWhenEmpty()) { if (this.getPlayerCount() <= 0 && !this.dontDestroyWhenEmpty()) {
this.getWorld().deregisterScene(this); this.getWorld().deregisterScene(this);
} }
} }
@ -279,6 +346,11 @@ public class Scene {
} }
} }
// Sanity check
if (target.getFightProperties() == null) {
return;
}
// Lose hp // Lose hp
target.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -result.getDamage()); target.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -result.getDamage());
@ -314,7 +386,15 @@ public class Scene {
} }
public void onTick() { public void onTick() {
this.checkSpawns(); if (this.getScriptManager().isInit()) {
this.checkBlocks();
} else {
// TEMPORARY
this.checkSpawns();
}
// Triggers
this.getScriptManager().onTick();
} }
// TODO - Test // TODO - Test
@ -387,6 +467,68 @@ public class Scene {
} }
} }
public void checkBlocks() {
Set<SceneBlock> visible = new HashSet<>();
for (Player player : this.getPlayers()) {
for (SceneBlock block : getScriptManager().getBlocks()) {
if (!block.contains(player.getPos())) {
continue;
}
visible.add(block);
}
}
Iterator<SceneBlock> 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<GameEntity> 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 // Gadgets
public void onPlayerCreateGadget(EntityClientGadget gadget) { public void onPlayerCreateGadget(EntityClientGadget gadget) {

View File

@ -17,14 +17,16 @@ import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.DungeonData;
import emu.grasscutter.data.def.SceneData; import emu.grasscutter.data.def.SceneData;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityClientGadget; 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.packet.BasePacket;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; 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.PacketDelTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
@ -212,6 +214,14 @@ public class World implements Iterable<Player> {
} }
public boolean transferPlayerToScene(Player player, int sceneId, Position pos) { 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) { if (GameData.getSceneDataMap().get(sceneId) == null) {
return false; return false;
} }
@ -224,25 +234,51 @@ public class World implements Iterable<Player> {
// Dont deregister scenes if the player is going to tp back into them // Dont deregister scenes if the player is going to tp back into them
if (oldScene.getId() == sceneId) { if (oldScene.getId() == sceneId) {
oldScene.setDontDestroyWhenEmpty(true); oldScene.setDontDestroyWhenEmpty(true);
} }
oldScene.removePlayer(player); oldScene.removePlayer(player);
} }
Scene newScene = this.getSceneById(sceneId); Scene newScene = this.getSceneById(sceneId);
newScene.setDungeonData(dungeonData);
newScene.addPlayer(player); 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) { if (oldScene != null) {
newScene.setPrevScene(oldScene.getId());
oldScene.setDontDestroyWhenEmpty(false); oldScene.setDontDestroyWhenEmpty(false);
} }
// Teleport packet // Get enter types
if (oldScene == newScene) { EnterType enterType = EnterType.ENTER_JUMP;
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.ENTER_GOTO, EnterReason.TransPoint, sceneId, pos)); EnterReason enterReason = EnterReason.TransPoint;
} else {
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.ENTER_JUMP, EnterReason.TransPoint, sceneId, pos)); 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; return true;
} }

View File

@ -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<String, Integer> variables;
private Bindings bindings;
private SceneConfig config;
private List<SceneBlock> blocks;
private Int2ObjectOpenHashMap<Set<SceneTrigger>> 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<SceneBlock> getBlocks() {
return blocks;
}
public Map<String, Integer> getVariables() {
return variables;
}
public Set<SceneTrigger> 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<Integer> blockIds = ScriptLoader.getSerializer().toList(Integer.class, bindings.get("blocks"));
List<SceneBlock> 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<SceneVar> 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<Integer, SceneMonster> map = (HashMap<Integer, SceneMonster>) 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<SceneMonster> monsters = group.monsters;
if (suite != null) {
monsters = suite.sceneMonsters;
}
List<GameEntity> 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);
}
}
}
}

View File

@ -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<GameEntity> 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<GameEntity> 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<GameEntity> 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<GameEntity> 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);
}
}

View File

@ -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<String, CompiledScript> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<SceneGroup> groups;
public boolean contains(Position pos) {
return pos.getX() <= max.getX() && pos.getX() >= min.getX() &&
pos.getZ() <= max.getZ() && pos.getZ() >= min.getZ();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<SceneMonster> monsters;
public List<SceneGadget> gadgets;
public List<SceneTrigger> triggers;
public List<SceneSuite> 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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,13 @@
package emu.grasscutter.scripts.data;
import java.util.List;
import emu.grasscutter.utils.Position;
public class SceneSuite {
public List<Integer> monsters;
public List<String> triggers;
public int rand_weight;
public transient List<SceneMonster> sceneMonsters;
}

View File

@ -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;
}

View File

@ -0,0 +1,7 @@
package emu.grasscutter.scripts.data;
public class SceneVar {
public String name;
public int value;
public boolean no_refresh;
}

View File

@ -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;
}
}

View File

@ -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 <T> List<T> toList(Class<T> type, Object obj) {
return serializeList(type, (LuaTable) obj);
}
@Override
public <T> T toObject(Class<T> type, Object obj) {
return serialize(type, (LuaTable) obj);
}
public <T> List<T> serializeList(Class<T> type, LuaTable table) {
List<T> 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> T serialize(Class<T> type, LuaTable table) {
T object = null;
if (type == List.class) {
try {
Class<T> listType = (Class<T>) 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;
}
}

View File

@ -0,0 +1,12 @@
package emu.grasscutter.scripts.serializer;
import java.util.List;
import org.luaj.vm2.LuaTable;
public interface Serializer {
public <T> List<T> toList(Class<T> type, Object obj);
public <T> T toObject(Class<T> type, Object obj);
}

View File

@ -2,6 +2,7 @@ package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.DungeonEntryInfoReqOuterClass.DungeonEntryInfoReq;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
@ -10,7 +11,9 @@ public class HandlerDungeonEntryInfoReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
DungeonEntryInfoReq req = DungeonEntryInfoReq.parseFrom(payload);
session.getServer().getDungeonManager().getEntryInfo(session.getPlayer(), req.getPointId());
} }
} }

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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()));
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,6 +1,6 @@
package emu.grasscutter.server.packet.send; 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.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GadgetInteractRspOuterClass.GadgetInteractRsp; import emu.grasscutter.net.proto.GadgetInteractRspOuterClass.GadgetInteractRsp;
@ -8,7 +8,7 @@ import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.RetcodeOuterClass; import emu.grasscutter.net.proto.RetcodeOuterClass;
public class PacketGadgetInteractRsp extends BasePacket { public class PacketGadgetInteractRsp extends BasePacket {
public PacketGadgetInteractRsp(EntityGadget gadget, InteractType interact) { public PacketGadgetInteractRsp(EntityBaseGadget gadget, InteractType interact) {
super(PacketOpcodes.GadgetInteractRsp); super(PacketOpcodes.GadgetInteractRsp);
GadgetInteractRsp proto = GadgetInteractRsp.newBuilder() GadgetInteractRsp proto = GadgetInteractRsp.newBuilder()

View File

@ -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);
}
}

View File

@ -7,7 +7,5 @@ public class PacketPathfindingEnterSceneRsp extends BasePacket {
public PacketPathfindingEnterSceneRsp(int clientSequence) { public PacketPathfindingEnterSceneRsp(int clientSequence) {
super(PacketOpcodes.PathfindingEnterSceneRsp); super(PacketOpcodes.PathfindingEnterSceneRsp);
this.buildHeader(clientSequence);
} }
} }

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -67,6 +67,7 @@ public final class Utils {
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
public static String bytesToHex(byte[] bytes) { public static String bytesToHex(byte[] bytes) {
if (bytes == null) return "";
char[] hexChars = new char[bytes.length * 2]; char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) { for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF; int v = bytes[j] & 0xFF;