From 6dc30e4defd133cb71bbf26a8d487beac250b2f3 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sun, 15 May 2022 19:19:24 +0800 Subject: [PATCH] Enable script in big world (#884) * add docs for tower * fix: LEAK: ByteBuf.release() was not called * enableScriptInBigWorld * not print log when loaded scripts from cache * revert the change of server tick * revert the change of server tick * fix * optimize the performance: lazy load & cache * fix the refresh group * fix NPE Co-authored-by: Melledy <52122272+Melledy@users.noreply.github.com> --- build.gradle | 2 + .../emu/grasscutter/game/player/Player.java | 1 - .../emu/grasscutter/game/world/Scene.java | 118 ++++--- .../scripts/SceneIndexManager.java | 41 +++ .../scripts/SceneScriptManager.java | 314 +++++++++--------- .../emu/grasscutter/scripts/ScriptLib.java | 30 ++ .../emu/grasscutter/scripts/ScriptLoader.java | 102 +++--- .../grasscutter/scripts/data/SceneBlock.java | 58 +++- .../grasscutter/scripts/data/SceneGadget.java | 8 +- .../grasscutter/scripts/data/SceneGroup.java | 99 +++++- .../grasscutter/scripts/data/SceneMeta.java | 71 ++++ .../scripts/data/SceneMonster.java | 10 +- .../grasscutter/scripts/data/SceneObject.java | 15 + .../scripts/serializer/LuaSerializer.java | 20 +- .../service/ScriptMonsterSpawnService.java | 39 +-- .../service/ScriptMonsterTideService.java | 4 +- .../grasscutter/server/game/GameServer.java | 13 +- .../grasscutter/server/game/GameSession.java | 10 +- .../send/PacketSceneEntityAppearNotify.java | 2 +- .../grasscutter/utils/ConfigContainer.java | 1 + .../java/emu/grasscutter/utils/Position.java | 6 + 21 files changed, 643 insertions(+), 321 deletions(-) create mode 100644 src/main/java/emu/grasscutter/scripts/SceneIndexManager.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneMeta.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneObject.java diff --git a/build.gradle b/build.gradle index 7beb3a816..b4f66aa5c 100644 --- a/build.gradle +++ b/build.gradle @@ -86,6 +86,8 @@ dependencies { implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1' + implementation group: 'ch.ethz.globis.phtree', name : 'phtree', version: '2.5.0' + protobuf files('proto/') compileOnly 'org.projectlombok:lombok:1.18.24' diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 23b32c70f..aa4a0b59f 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -2,7 +2,6 @@ package emu.grasscutter.game.player; import dev.morphia.annotations.*; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.PlayerLevelData; import emu.grasscutter.database.DatabaseHelper; diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index daed26e3e..e869707bd 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -1,5 +1,6 @@ package emu.grasscutter.game.world; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.def.DungeonData; @@ -19,12 +20,12 @@ 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.SceneIndexManager; import emu.grasscutter.scripts.SceneScriptManager; import emu.grasscutter.scripts.data.SceneBlock; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.server.packet.send.PacketAvatarSkillInfoNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify; @@ -34,6 +35,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.danilopianini.util.SpatialIndex; import java.util.*; +import java.util.stream.Collectors; public class Scene { private final World world; @@ -57,7 +59,6 @@ public class Scene { 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; @@ -323,14 +324,21 @@ public class Scene { public synchronized void addEntityToSingleClient(Player player, GameEntity entity) { this.addEntityDirectly(entity); player.sendPacket(new PacketSceneEntityAppearNotify(entity)); + + } + public void addEntities(Collection entities){ + addEntities(entities, VisionType.VISION_BORN); } - public synchronized void addEntities(Collection entities) { + public synchronized void addEntities(Collection entities, VisionType visionType) { + if(entities == null || entities.isEmpty()){ + return; + } for (GameEntity entity : entities) { this.addEntityDirectly(entity); } - this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_BORN)); + this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, visionType)); } private GameEntity removeEntityDirectly(GameEntity entity) { @@ -354,7 +362,7 @@ public class Scene { this.broadcastPacket(new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VISION_REPLACE)); this.broadcastPacket(new PacketSceneEntityAppearNotify(newEntity, VisionType.VISION_REPLACE, oldEntity.getId())); } - + public void showOtherEntities(Player player) { List entities = new LinkedList<>(); GameEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity(); @@ -402,7 +410,7 @@ public class Scene { // Death event target.onDeath(attackerId); } - + public void onTick() { if (this.getScriptManager().isInit()) { this.checkBlocks(); @@ -410,9 +418,8 @@ public class Scene { // TEMPORARY this.checkSpawns(); } - // Triggers - this.getScriptManager().onTick(); + this.scriptManager.checkRegions(); } // TODO - Test @@ -484,54 +491,75 @@ public class Scene { this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE)); } } - + public Set getPlayerActiveBlocks(Player player){ + // TODO consider the borders of blocks + return getScriptManager().getBlocks().values().stream() + .filter(block -> block.contains(player.getPos())) + .collect(Collectors.toSet()); + } 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); - } + var blocks = getPlayerActiveBlocks(player); + visible.addAll(blocks); } - + Iterator it = this.getLoadedBlocks().iterator(); while (it.hasNext()) { SceneBlock block = it.next(); - + if (!visible.contains(block)) { it.remove(); - + onUnloadBlock(block); } } - - for (SceneBlock block : visible) { + + for(var block : visible){ if (!this.getLoadedBlocks().contains(block)) { - this.onLoadBlock(block); + this.onLoadBlock(block, this.getPlayers()); this.getLoadedBlocks().add(block); } + this.getPlayers().stream() + .filter(p -> block.contains(p.getPos())) + .forEach(p -> playerMeetGroups(p, block)); } + } - - // TODO optimize - public void onLoadBlock(SceneBlock block) { - for (SceneGroup group : block.groups) { + public List playerMeetGroups(Player player, SceneBlock block){ + int RANGE = 100; + + var sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos(), RANGE); + + var groups = new ArrayList<>(sceneGroups.stream() + .filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group)) + .peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group)).toList()); + + if(groups.size() == 0){ + return List.of(); + } + + Grasscutter.getLogger().info("Scene {} Block {} loaded {} group(s)", this.getId(), block.id, groups.size()); + return groups; + } + public void onLoadBlock(SceneBlock block, List players) { + this.getScriptManager().loadBlockFromScript(block); + scriptManager.getLoadedGroupSetPerBlock().put(block.id , new HashSet<>()); + + // the groups form here is not added in current scene + var groups = players.stream() + .map(p -> playerMeetGroups(p, block)) + .flatMap(Collection::stream) + .toList(); + + for (SceneGroup group : groups) { // We load the script files for the groups here - if (!group.isLoaded()) { - this.getScriptManager().loadGroupFromScript(group); - } - - group.triggers.forEach(getScriptManager()::registerTrigger); - group.regions.forEach(getScriptManager()::registerRegion); + this.getScriptManager().loadGroupFromScript(group); } // Spawn gadgets AFTER triggers are added // TODO - for (SceneGroup group : block.groups) { + for (SceneGroup group : groups) { if (group.init_config == null) { continue; } @@ -541,26 +569,36 @@ public class Scene { if (suite == 0) { continue; } - + do { - this.getScriptManager().spawnGadgetsInGroup(group, suite); + var suiteData = group.getSuiteByIndex(suite); + getScriptManager().spawnGadgetsInGroup(group,suiteData); + getScriptManager().spawnMonstersInGroup(group, suiteData); suite++; } while (suite < group.init_config.end_suite); } + Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); } public void onUnloadBlock(SceneBlock block) { - List toRemove = this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList(); + List toRemove = this.getEntities().values().stream() + .filter(e -> e.getBlockId() == block.id).toList(); if (toRemove.size() > 0) { - toRemove.stream().forEach(this::removeEntityDirectly); + toRemove.forEach(this::removeEntityDirectly); this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE)); } for (SceneGroup group : block.groups) { - group.triggers.forEach(getScriptManager()::deregisterTrigger); - group.regions.forEach(getScriptManager()::deregisterRegion); + if(group.triggers != null){ + group.triggers.forEach(getScriptManager()::deregisterTrigger); + } + if(group.regions != null){ + group.regions.forEach(getScriptManager()::deregisterRegion); + } } + scriptManager.getLoadedGroupSetPerBlock().remove(block.id); + Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); } // Gadgets diff --git a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java new file mode 100644 index 000000000..3b48ed279 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java @@ -0,0 +1,41 @@ +package emu.grasscutter.scripts; + +import ch.ethz.globis.phtree.PhTree; +import emu.grasscutter.utils.Position; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class SceneIndexManager { + + public static void buildIndex(PhTree tree, List elements, Function extractor){ + elements.forEach(e -> tree.put(extractor.apply(e), e)); + } + public static List queryNeighbors(PhTree tree, Position position, int range){ + var result = new ArrayList(); + var arrPos = position.toLongArray(); + var query = tree.query(calRange(arrPos, -range), calRange(arrPos, range)); + while(query.hasNext()){ + var element = query.next(); + result.add(element); + } + return result; + } + public static List queryNeighbors(PhTree tree, long[] position, int range){ + var result = new ArrayList(); + var query = tree.query(calRange(position, -range), calRange(position, range)); + while(query.hasNext()){ + var element = query.next(); + result.add(element); + } + return result; + } + private static long[] calRange(long[] position, int range){ + var newPos = position.clone(); + for(int i=0;i variables; private Bindings bindings; - private SceneConfig config; - private List blocks; + private SceneMeta meta; private boolean isInit; /** * SceneTrigger Set @@ -55,7 +45,10 @@ public class SceneScriptManager { private SceneGroup currentGroup; private ScriptMonsterTideService scriptMonsterTideService; private ScriptMonsterSpawnService scriptMonsterSpawnService; - + /** + * blockid - loaded groupSet + */ + private Int2ObjectMap> loadedGroupSetPerBlock; public SceneScriptManager(Scene scene) { this.scene = scene; this.scriptLib = new ScriptLib(this); @@ -67,9 +60,10 @@ public class SceneScriptManager { this.variables = new HashMap<>(); this.sceneGroups = new HashMap<>(); this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this); + this.loadedGroupSetPerBlock = new Int2ObjectOpenHashMap<>(); // TEMPORARY - if (this.getScene().getId() < 10) { + if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) { return; } @@ -94,15 +88,18 @@ public class SceneScriptManager { } public SceneConfig getConfig() { - return config; + if(!isInit){ + return null; + } + return meta.config; } public SceneGroup getCurrentGroup() { return currentGroup; } - public List getBlocks() { - return blocks; + public Map getBlocks() { + return meta.blocks; } public Map getVariables() { @@ -150,12 +147,19 @@ public class SceneScriptManager { public void deregisterRegion(SceneRegion region) { regions.remove(region.config_id); } - + + public Int2ObjectMap> getLoadedGroupSetPerBlock() { + return loadedGroupSetPerBlock; + } + // TODO optimize public SceneGroup getGroupById(int groupId) { for (SceneBlock block : this.getScene().getLoadedBlocks()) { for (SceneGroup group : block.groups) { if (group.id == groupId) { + if(!group.isLoaded()){ + loadGroupFromScript(group); + } return group; } } @@ -164,45 +168,17 @@ public class SceneScriptManager { } private void init() { - // Get compiled script if cached - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("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("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(i); - block.id = blockIds.get(i); - - loadBlockFromScript(block); - } - - this.blocks = blocks; - } catch (ScriptException e) { - Grasscutter.getLogger().error("Error running script", e); + var meta = ScriptLoader.getSceneMeta(getScene().getId()); + if (meta == null){ return; } - + this.meta = meta; + // TEMP this.isInit = true; } @@ -211,89 +187,29 @@ public class SceneScriptManager { return isInit; } - private void loadBlockFromScript(SceneBlock block) { - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("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 loadBlockFromScript(SceneBlock block) { + block.load(scene.getId(), meta.context); } 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( - SCRIPTS_FOLDER + "Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_group" + group.id + "." + ScriptLoader.getScriptType()); - - if (cs == null) { - return; - } - - // Eval script + group.load(getScene().getId(), meta.context); + try { - cs.eval(getBindings()); - - // Set - group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() - .collect(Collectors.toMap(x -> x.config_id, y -> y)); - group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")); - group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); - group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); - group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); - group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); - - // Add variables to suite - List variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); - variables.forEach(var -> this.getVariables().put(var.name, var.value)); - - // Add monsters to suite TODO optimize - Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); - group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m)); - group.gadgets.forEach(m -> map.put(m.config_id, m)); - - for (SceneSuite suite : group.suites) { - suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); - suite.monsters.forEach(id -> { - Object objEntry = map.get(id.intValue()); - if (objEntry instanceof Map.Entry monsterEntry) { - Object monster = monsterEntry.getValue(); - if(monster instanceof SceneMonster sceneMonster){ - suite.sceneMonsters.add(sceneMonster); - } - } - }); - - suite.sceneGadgets = new ArrayList<>(suite.gadgets.size()); - for (int id : suite.gadgets) { - try { - SceneGadget gadget = (SceneGadget) map.get(id); - if (gadget != null) { - suite.sceneGadgets.add(gadget); - } - } catch (Exception ignored) { } - } - } - this.sceneGroups.put(group.id, group); + // build the trigger for this scene + group.getScript().eval(getBindings()); } catch (ScriptException e) { - Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e); + Grasscutter.getLogger().error("Could not build the trigger for this scene", e); } - } - public void onTick() { - checkRegions(); + group.variables.forEach(var -> this.getVariables().put(var.name, var.value)); + this.sceneGroups.put(group.id, group); + + if(group.triggers != null){ + group.triggers.forEach(this::registerTrigger); + } + if(group.regions != null){ + group.regions.forEach(this::registerRegion); + } } public void checkRegions() { @@ -330,21 +246,12 @@ public class SceneScriptManager { if (suite != null) { gadgets = suite.sceneGadgets; } - - for (SceneGadget g : 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())); - } + + var toCreate = gadgets.stream() + .map(g -> createGadgets(g.groupId, group.block_id, g)) + .filter(Objects::nonNull) + .toList(); + this.addEntities(toCreate); } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { @@ -359,12 +266,15 @@ public class SceneScriptManager { return; } this.currentGroup = group; - suite.sceneMonsters.forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); + this.addEntities(suite.sceneMonsters.stream() + .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); + } public void spawnMonstersInGroup(SceneGroup group) { this.currentGroup = group; - group.monsters.values().forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); + this.addEntities(group.monsters.values().stream() + .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); } public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) { @@ -381,7 +291,8 @@ public class SceneScriptManager { } public void spawnMonstersByConfigId(int configId, int delayTime) { // TODO delay - this.scriptMonsterSpawnService.spawnMonster(this.currentGroup.id, this.currentGroup.monsters.get(configId)); + getScene().addEntity( + createMonster(this.currentGroup.id, this.currentGroup.block_id, this.currentGroup.monsters.get(configId))); } // Events @@ -407,6 +318,9 @@ public class SceneScriptManager { } if (ret.isboolean() && ret.checkboolean()) { + if(trigger.action == null || trigger.action.isEmpty()){ + return; + } ScriptLib.logger.trace("Call Action Trigger {}", trigger); LuaValue action = (LuaValue) this.getBindings().get(trigger.action); // TODO impl the param of SetGroupVariableValueByGroup @@ -436,4 +350,84 @@ public class SceneScriptManager { return scriptMonsterSpawnService; } + public EntityGadget createGadgets(int groupId, int blockId, SceneGadget g) { + EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); + + if (entity.getGadgetData() == null){ + return null; + } + + entity.setBlockId(blockId); + entity.setConfigId(g.config_id); + entity.setGroupId(groupId); + entity.getRotation().set(g.rot); + entity.setState(g.state); + + return entity; + } + + public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) { + if(monster == null){ + return null; + } + + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return null; + } + + // 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(groupId); + entity.setBlockId(blockId); + entity.setConfigId(monster.config_id); + + this.getScriptMonsterSpawnService() + .onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); + + return entity; + } + + public void addEntity(GameEntity gameEntity){ + getScene().addEntity(gameEntity); + callCreateEvent(gameEntity); + } + public void meetEntities(List gameEntity){ + getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_MEET); + gameEntity.forEach(this::callCreateEvent); + } + public void addEntities(List gameEntity){ + getScene().addEntities(gameEntity); + gameEntity.forEach(this::callCreateEvent); + } + public void callCreateEvent(GameEntity gameEntity){ + if(!isInit){ + return; + } + if(gameEntity instanceof EntityMonster entityMonster){ + callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entityMonster.getConfigId())); + } + if(gameEntity instanceof EntityGadget entityGadget){ + this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entityGadget.getConfigId())); + } + } + + public PhTree getBlocksIndex() { + return meta.sceneBlockIndex; + } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index b7fb5939f..e82df384d 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -355,6 +355,36 @@ public class ScriptLib { //TODO + return 0; + } + public int CheckRemainGadgetCountByGroupId(LuaTable table){ + logger.debug("[LUA] Call CheckRemainGadgetCountByGroupId with {}", + printTable(table)); + var groupId = table.get("group_id").toint(); + + var count = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == groupId) + .count(); + return (int)count; + } + + public int GetGadgetStateByConfigId(int groupId, int configId){ + logger.debug("[LUA] Call GetGadgetStateByConfigId with {},{}", + groupId, configId); + var gadget = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == groupId) + .filter(g -> g.getConfigId() == configId) + .findFirst(); + if(gadget.isEmpty()){ + return 0; + } + var stat = ((EntityGadget)gadget.get()).getState(); + return stat; + } + public int SetGadgetStateByConfigId(int configId, LuaTable gadgetState){ + logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", + configId, printTable(gadgetState)); + return 0; } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java index 0cbd6a191..049dbce32 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java @@ -1,32 +1,27 @@ package emu.grasscutter.scripts; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import javax.script.Compilable; -import javax.script.CompiledScript; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; - -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; -import org.luaj.vm2.lib.OneArgFunction; -import org.luaj.vm2.lib.jse.CoerceJavaToLua; -import org.luaj.vm2.script.LuajContext; - import emu.grasscutter.Grasscutter; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.ScriptGadgetState; import emu.grasscutter.scripts.constants.ScriptRegionShape; +import emu.grasscutter.scripts.data.SceneMeta; import emu.grasscutter.scripts.serializer.LuaSerializer; import emu.grasscutter.scripts.serializer.Serializer; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; +import org.luaj.vm2.script.LuajContext; + +import javax.script.*; +import java.io.File; +import java.io.FileReader; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; public class ScriptLoader { private static ScriptEngineManager sm; @@ -34,9 +29,15 @@ public class ScriptLoader { private static ScriptEngineFactory factory; private static String fileType; private static Serializer serializer; - - private static Map scripts = new HashMap<>(); - + /** + * suggest GC to remove it if the memory is less + */ + private static Map> scriptsCache = new ConcurrentHashMap<>(); + /** + * sceneId - SceneMeta + */ + private static Map> sceneMetaCache = new ConcurrentHashMap<>(); + public synchronized static void init() throws Exception { if (sm != null) { throw new Exception("Script loader already initialized"); @@ -81,25 +82,42 @@ public class ScriptLoader { 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; - } + public static Optional tryGet(SoftReference softReference){ + try{ + return Optional.ofNullable(softReference.get()); + }catch (NullPointerException npe){ + return Optional.empty(); } - - return sc; } + public static CompiledScript getScriptByPath(String path) { + var sc = tryGet(scriptsCache.get(path)); + if (sc.isPresent()) { + return sc.get(); + } + + Grasscutter.getLogger().info("Loaded Script" + path); + + File file = new File(path); + + if (!file.exists()) return null; + + try (FileReader fr = new FileReader(file)) { + var script = ((Compilable) getEngine()).compile(fr); + scriptsCache.put(path, new SoftReference<>(script)); + return script; + } catch (Exception e) { + Grasscutter.getLogger().error("Loaded Script {} failed!", path, e); + return null; + } + + } + + public static SceneMeta getSceneMeta(int sceneId) { + return tryGet(sceneMetaCache.get(sceneId)).orElseGet(() -> { + var instance = SceneMeta.of(sceneId); + sceneMetaCache.put(sceneId, new SoftReference<>(instance)); + return instance; + }); + } + } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index 11a930fdd..37d03e88b 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -1,17 +1,69 @@ package emu.grasscutter.scripts.data; +import ch.ethz.globis.phtree.PhTree; +import ch.ethz.globis.phtree.v16.PhTree16; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.SceneIndexManager; +import emu.grasscutter.scripts.ScriptLoader; +import emu.grasscutter.utils.Position; + +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; import java.util.List; -import emu.grasscutter.utils.Position; +import static emu.grasscutter.Configuration.SCRIPT; public class SceneBlock { public int id; public Position max; public Position min; + + public int sceneId; public List groups; - + public PhTree sceneGroupIndex = new PhTree16<>(3); + + private transient boolean loaded; // Not an actual variable in the scripts either + + public boolean isLoaded() { + return loaded; + } + + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + public boolean contains(Position pos) { return pos.getX() <= max.getX() && pos.getX() >= min.getX() && pos.getZ() <= max.getZ() && pos.getZ() >= min.getZ(); } -} + + public SceneBlock load(int sceneId, Bindings bindings){ + if(loaded){ + return this; + } + this.sceneId = sceneId; + setLoaded(true); + + CompiledScript cs = ScriptLoader.getScriptByPath( + SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_block" + id + "." + ScriptLoader.getScriptType())); + + if (cs == null) { + return null; + } + + // Eval script + try { + cs.eval(bindings); + + // Set groups + groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups")); + groups.forEach(g -> g.block_id = id); + SceneIndexManager.buildIndex(this.sceneGroupIndex, groups, g -> g.pos.toLongArray()); + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading block " + id + " in scene " + sceneId, e); + } + Grasscutter.getLogger().info("scene {} block {} is loaded successfully.", sceneId, id); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index e5340ae55..e93f93c3c 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -1,12 +1,6 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.utils.Position; - -public class SceneGadget { - public int level; - public int config_id; +public class SceneGadget extends SceneObject{ 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 index 690cd3d0d..3ec446465 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -1,9 +1,20 @@ package emu.grasscutter.scripts.data; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + +import static emu.grasscutter.Configuration.SCRIPTS_FOLDER; public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference @@ -21,18 +32,94 @@ public class SceneGroup { public List regions; public List suites; public SceneInitConfig init_config; - - private transient boolean isLoaded; // Not an actual variable in the scripts either + + public List variables; + private transient boolean loaded; // Not an actual variable in the scripts either public boolean isLoaded() { - return isLoaded; - } - - public boolean setLoaded(boolean loaded) { return loaded; } + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + + private transient CompiledScript script; // Not an actual variable in the scripts either + + public CompiledScript getScript() { + return script; + } + public SceneSuite getSuiteByIndex(int index) { return suites.get(index - 1); } + + public SceneGroup load(int sceneId, Bindings bindings){ + if(loaded){ + return this; + } + // Set flag here so if there is no script, we dont call this function over and over again. + setLoaded(true); + + CompiledScript cs = ScriptLoader.getScriptByPath( + SCRIPTS_FOLDER + "Scene/" + sceneId + "/scene" + sceneId + "_group" + id + "." + ScriptLoader.getScriptType()); + + if (cs == null) { + return this; + } + + this.script = cs; + // Eval script + try { + cs.eval(bindings); + + // Set + monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); + gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")); + triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); + suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); + regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); + init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); + + // Add variables to suite + variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); + + + // Add monsters to suite TODO optimize + Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m)); + monsters.values().forEach(m -> m.groupId = id); + gadgets.forEach(m -> map.put(m.config_id, m)); + gadgets.forEach(m -> m.groupId = id); + + for (SceneSuite suite : suites) { + suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); + suite.monsters.forEach(id -> { + Object objEntry = map.get(id.intValue()); + if (objEntry instanceof Map.Entry monsterEntry) { + Object monster = monsterEntry.getValue(); + if(monster instanceof SceneMonster sceneMonster){ + suite.sceneMonsters.add(sceneMonster); + } + } + }); + + suite.sceneGadgets = new ArrayList<>(suite.gadgets.size()); + for (int id : suite.gadgets) { + try { + SceneGadget gadget = (SceneGadget) map.get(id); + if (gadget != null) { + suite.sceneGadgets.add(gadget); + } + } catch (Exception ignored) { } + } + } + + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading group " + id + " in scene " + sceneId, e); + } + Grasscutter.getLogger().info("group {} in scene {} is loaded successfully.", id, sceneId); + return this; + } } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java new file mode 100644 index 000000000..0a2cf9582 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java @@ -0,0 +1,71 @@ +package emu.grasscutter.scripts.data; + +import ch.ethz.globis.phtree.PhTree; +import ch.ethz.globis.phtree.v16.PhTree16; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.SceneIndexManager; +import emu.grasscutter.scripts.ScriptLoader; + +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static emu.grasscutter.Configuration.SCRIPT; + +public class SceneMeta { + + public SceneConfig config; + public Map blocks; + + public Bindings context; + + public PhTree sceneBlockIndex = new PhTree16<>(2); + + public static SceneMeta of(int sceneId) { + return new SceneMeta().load(sceneId); + } + + public SceneMeta load(int sceneId){ + // Get compiled script if cached + CompiledScript cs = ScriptLoader.getScriptByPath( + SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "." + ScriptLoader.getScriptType())); + + if (cs == null) { + Grasscutter.getLogger().warn("No script found for scene " + sceneId); + return null; + } + + // Create bindings + context = ScriptLoader.getEngine().createBindings(); + + // Eval script + try { + cs.eval(context); + + this.config = ScriptLoader.getSerializer().toObject(SceneConfig.class, context.get("scene_config")); + + // TODO optimize later + // Create blocks + List blockIds = ScriptLoader.getSerializer().toList(Integer.class, context.get("blocks")); + List blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, context.get("block_rects")); + + for (int i = 0; i < blocks.size(); i++) { + SceneBlock block = blocks.get(i); + block.id = blockIds.get(i); + + } + + this.blocks = blocks.stream().collect(Collectors.toMap(b -> b.id, b -> b)); + SceneIndexManager.buildIndex(this.sceneBlockIndex, blocks, g -> g.min.toXZLongArray()); + SceneIndexManager.buildIndex(this.sceneBlockIndex, blocks, g -> g.max.toXZLongArray()); + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error running script", e); + return null; + } + Grasscutter.getLogger().info("scene {} metadata is loaded successfully.", sceneId); + return this; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index 56d2e3d3d..9c508d147 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -1,11 +1,5 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.utils.Position; - -public class SceneMonster { - public int level; - public int config_id; +public class SceneMonster extends SceneObject{ public int monster_id; - public Position pos; - public Position rot; -} +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java new file mode 100644 index 000000000..7f9f1f709 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java @@ -0,0 +1,15 @@ +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; + +public class SceneObject { + public int level; + public int config_id; + + public Position pos; + public Position rot; + /** + * not set by lua + */ + public int groupId; +} diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index 8924a33ae..c31c13b81 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -76,8 +76,11 @@ public class LuaSerializer implements Serializer { LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { - Field field = object.getClass().getDeclaredField(k.checkjstring()); - + Field field = getField(object.getClass(), k.checkjstring()); + if (field == null) { + continue; + } + field.setAccessible(true); LuaValue keyValue = table.get(k); @@ -103,4 +106,17 @@ public class LuaSerializer implements Serializer { return object; } + + public Field getField(Class clazz, String name){ + try{ + return clazz.getField(name); + } catch (NoSuchFieldException ex) { + try { + return clazz.getDeclaredField(name); + } catch (NoSuchFieldException e) { + // ignore + return null; + } + } + } } diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java index fdc4941f2..90deb33fb 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java @@ -16,9 +16,9 @@ import java.util.List; public class ScriptMonsterSpawnService { private final SceneScriptManager sceneScriptManager; - private final List onMonsterCreatedListener = new ArrayList<>(); + public final List onMonsterCreatedListener = new ArrayList<>(); - private final List onMonsterDeadListener = new ArrayList<>(); + public final List onMonsterDeadListener = new ArrayList<>(); public ScriptMonsterSpawnService(SceneScriptManager sceneScriptManager){ this.sceneScriptManager = sceneScriptManager; @@ -39,40 +39,5 @@ public class ScriptMonsterSpawnService { public void onMonsterDead(EntityMonster entityMonster){ onMonsterDeadListener.forEach(l -> l.onNotify(entityMonster)); } - public void spawnMonster(int groupId, SceneMonster monster) { - if(monster == null){ - return; - } - MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - return; - } - - // Calculate level - int level = monster.level; - - if (sceneScriptManager.getScene().getDungeonData() != null) { - level = sceneScriptManager.getScene().getDungeonData().getShowLevel(); - } else if (sceneScriptManager.getScene().getWorld().getWorldLevel() > 0) { - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(sceneScriptManager.getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(sceneScriptManager.getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(groupId); - entity.setConfigId(monster.config_id); - - onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); - - sceneScriptManager.getScene().addEntity(entity); - - sceneScriptManager.callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); - } } diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java index 57d4735ba..831548716 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -37,7 +37,7 @@ public class ScriptMonsterTideService { this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead); // spawn the first turn for (int i = 0; i < this.monsterSceneLimit; i++) { - this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(group.id, getNextMonster()); + sceneScriptManager.addEntity(this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster())); } } @@ -73,7 +73,7 @@ public class ScriptMonsterTideService { monsterKillCount.incrementAndGet(); if (monsterTideCount.get() > 0) { // add more - sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(currentGroup.id, getNextMonster()); + sceneScriptManager.addEntity(sceneScriptManager.createMonster(currentGroup.id, currentGroup.block_id, getNextMonster())); } // spawn the last turn of monsters // fix the 5-2 diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index a54d3900d..3ebe83997 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -57,7 +57,6 @@ public final class GameServer extends KcpServer { private final CombineManger combineManger; private final TowerScheduleManager towerScheduleManager; - public GameServer() { this(new InetSocketAddress( GAME_INFO.bindAddress, @@ -205,23 +204,23 @@ public final class GameServer extends KcpServer { } return DatabaseHelper.getAccountByName(username); } - - public void onTick() throws Exception { + + public synchronized void onTick(){ Iterator it = this.getWorlds().iterator(); while (it.hasNext()) { World world = it.next(); - + if (world.getPlayerCount() == 0) { it.remove(); } - + world.onTick(); } - + for (Player player : this.getPlayers().values()) { player.onTick(); } - + ServerTickEvent event = new ServerTickEvent(); event.call(); } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index cf6386770..b981455d4 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -1,10 +1,5 @@ package emu.grasscutter.server.game; -import java.io.File; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.Set; - import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.game.Account; @@ -21,6 +16,11 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; +import java.io.File; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.Set; + import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.Configuration.*; diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java index 78c17a5d5..98bdc6326 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java @@ -36,7 +36,7 @@ public class PacketSceneEntityAppearNotify extends BasePacket { this(player.getTeamManager().getCurrentAvatarEntity()); } - public PacketSceneEntityAppearNotify(Collection entities, VisionType visionType) { + public PacketSceneEntityAppearNotify(Collection entities, VisionType visionType) { super(PacketOpcodes.SceneEntityAppearNotify, true); SceneEntityAppearNotify.Builder proto = SceneEntityAppearNotify.newBuilder() diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index b65fb10db..9b5711a8d 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -136,6 +136,7 @@ public class ConfigContainer { public int bindPort = 22102; /* This is the port used in the default region. */ public int accessPort = 0; + public boolean enableScriptInBigWorld = false; public GameOptions gameOptions = new GameOptions(); public JoinOptions joinOptions = new JoinOptions(); diff --git a/src/main/java/emu/grasscutter/utils/Position.java b/src/main/java/emu/grasscutter/utils/Position.java index f2ecb6915..4442e8f98 100644 --- a/src/main/java/emu/grasscutter/utils/Position.java +++ b/src/main/java/emu/grasscutter/utils/Position.java @@ -155,4 +155,10 @@ public class Position implements Serializable { .setZ(this.getZ()) .build(); } + public long[] toLongArray(){ + return new long[]{(long) x, (long) y, (long) z}; + } + public long[] toXZLongArray(){ + return new long[]{(long) x, (long) z}; + } }