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 01/24] 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}; + } } From 18ef5ee7de12a0a02818ab0621f6328aad49df5c Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sun, 15 May 2022 23:14:29 +0800 Subject: [PATCH 02/24] fix the dynamic group loading --- .../emu/grasscutter/game/world/Scene.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index e869707bd..1c382fc74 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -519,10 +519,15 @@ public class Scene { if (!this.getLoadedBlocks().contains(block)) { this.onLoadBlock(block, this.getPlayers()); this.getLoadedBlocks().add(block); + }else{ + // dynamic load the groups for players in a loaded block + var toLoad = this.getPlayers().stream() + .filter(p -> block.contains(p.getPos())) + .map(p -> playerMeetGroups(p, block)) + .flatMap(Collection::stream) + .toList(); + onLoadGroup(toLoad); } - this.getPlayers().stream() - .filter(p -> block.contains(p.getPos())) - .forEach(p -> playerMeetGroups(p, block)); } } @@ -539,7 +544,6 @@ public class Scene { 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) { @@ -548,36 +552,50 @@ public class Scene { // the groups form here is not added in current scene var groups = players.stream() + .filter(player -> block.contains(player.getPos())) .map(p -> playerMeetGroups(p, block)) .flatMap(Collection::stream) .toList(); + onLoadGroup(groups); + Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); + } + + public void onLoadGroup(List groups){ + if(groups == null || groups.isEmpty()){ + return; + } for (SceneGroup group : groups) { // We load the script files for the groups here this.getScriptManager().loadGroupFromScript(group); } - + // Spawn gadgets AFTER triggers are added // TODO + var entities = new ArrayList(); for (SceneGroup group : groups) { if (group.init_config == null) { continue; } - + int suite = group.init_config.suite; - + if (suite == 0) { continue; } do { var suiteData = group.getSuiteByIndex(suite); - getScriptManager().spawnGadgetsInGroup(group,suiteData); - getScriptManager().spawnMonstersInGroup(group, suiteData); + entities.addAll(suiteData.sceneGadgets.stream() + .map(g -> scriptManager.createGadgets(group.id, group.block_id, g)).toList()); + entities.addAll(suiteData.sceneMonsters.stream() + .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)).toList()); suite++; } while (suite < group.init_config.end_suite); } - Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); + + scriptManager.meetEntities(entities); + Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size()); } public void onUnloadBlock(SceneBlock block) { From 1925bf64d899daa96d27e7b973147b782dc17e04 Mon Sep 17 00:00:00 2001 From: TangHuLuTaiTian <40757889+TangHuLuTaiTian@users.noreply.github.com> Date: Mon, 16 May 2022 13:07:23 +0800 Subject: [PATCH 03/24] Some clients Code 4206 Error --- .../emu/grasscutter/server/http/handlers/GenericHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java index 2de8969d7..52415b265 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java @@ -42,6 +42,8 @@ public final class GenericHandler implements Router { // webstatic-sea.hoyoverse.com express.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new HttpJsonResponse("{\"version\":51}")); + express.get("/admin/mi18n/plat_oversea/m2020030410/m2020030410-version.json", new HttpJsonResponse("{\"version\":51}")); + express.get("/admin/mi18n/plat_oversea/m2020030410/m2020030410-zh-cn.json", new HttpJsonResponse("{\"version\":51}")); express.get("/status/server", GenericHandler::serverStatus); } From 593de83847e7d335366adfa154146f6f39de795d Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Tue, 17 May 2022 11:17:38 +0800 Subject: [PATCH 04/24] optimized the lua serializer --- build.gradle | 1 + .../scripts/SceneScriptManager.java | 2 +- .../grasscutter/scripts/data/SceneBlock.java | 4 + .../grasscutter/scripts/data/SceneConfig.java | 4 + .../grasscutter/scripts/data/SceneGadget.java | 5 + .../grasscutter/scripts/data/SceneGroup.java | 61 +++++---- .../scripts/data/SceneInitConfig.java | 5 +- .../grasscutter/scripts/data/SceneMeta.java | 5 + .../scripts/data/SceneMonster.java | 5 + .../grasscutter/scripts/data/SceneObject.java | 6 +- .../grasscutter/scripts/data/SceneRegion.java | 5 + .../grasscutter/scripts/data/SceneSuite.java | 5 +- .../scripts/data/SceneTrigger.java | 4 + .../grasscutter/scripts/data/SceneVar.java | 5 + .../scripts/serializer/LuaSerializer.java | 118 +++++++++++++----- 15 files changed, 168 insertions(+), 67 deletions(-) diff --git a/build.gradle b/build.gradle index b4f66aa5c..8801b3da0 100644 --- a/build.gradle +++ b/build.gradle @@ -87,6 +87,7 @@ dependencies { implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1' implementation group: 'ch.ethz.globis.phtree', name : 'phtree', version: '2.5.0' + implementation group: 'com.esotericsoftware', name : 'reflectasm', version: '1.11.9' protobuf files('proto/') diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 0b4b0c001..d57cb9ab5 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -241,7 +241,7 @@ public class SceneScriptManager { } public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) { - List gadgets = group.gadgets; + var gadgets = group.gadgets.values(); if (suite != null) { gadgets = suite.sceneGadgets; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index 37d03e88b..0f4400a50 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -6,6 +6,8 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; @@ -14,6 +16,8 @@ import java.util.List; import static emu.grasscutter.Configuration.SCRIPT; +@ToString +@Setter public class SceneBlock { public int id; public Position max; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java index 3a1ca60ea..cba2be8bd 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java @@ -1,7 +1,11 @@ package emu.grasscutter.scripts.data; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneConfig { public Position vision_anchor; public Position born_pos; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index e93f93c3c..8b3e452fd 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -1,5 +1,10 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter public class SceneGadget extends SceneObject{ public int gadget_id; public int state; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 3ec446465..82efb1969 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -3,19 +3,22 @@ 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 lombok.Setter; +import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static emu.grasscutter.Configuration.SCRIPTS_FOLDER; +@ToString +@Setter public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference @@ -27,7 +30,10 @@ public class SceneGroup { * ConfigId - Monster */ public Map monsters; - public List gadgets; + /** + * ConfigId - Gadget + */ + public Map gadgets; public List triggers; public List regions; public List suites; @@ -76,8 +82,15 @@ public class SceneGroup { // 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")); + monsters.values().forEach(m -> m.groupId = id); + + gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); + gadgets.values().forEach(m -> m.groupId = id); + triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); + triggers.forEach(t -> t.currentGroup = this); + 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")); @@ -85,35 +98,21 @@ public class SceneGroup { // 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); - + // Add monsters to suite 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.sceneMonsters = new ArrayList<>( + suite.monsters.stream() + .filter(monsters::containsKey) + .map(monsters::get) + .toList() + ); - 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) { } - } + suite.sceneGadgets = new ArrayList<>( + suite.gadgets.stream() + .filter(gadgets::containsKey) + .map(gadgets::get) + .toList() + ); } } catch (ScriptException e) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java index fcdc92c87..a9bc586c9 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java @@ -1,7 +1,10 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneInitConfig { public int suite; public int end_suite; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java index 0a2cf9582..4014b2e1e 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java @@ -5,6 +5,9 @@ import ch.ethz.globis.phtree.v16.PhTree16; import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; +import lombok.Data; +import lombok.Setter; +import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; @@ -15,6 +18,8 @@ import java.util.stream.Collectors; import static emu.grasscutter.Configuration.SCRIPT; +@ToString +@Setter public class SceneMeta { public SceneConfig config; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index 9c508d147..02435d800 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -1,5 +1,10 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter public class SceneMonster extends SceneObject{ public int monster_id; } \ 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 index 7f9f1f709..ecf089a5e 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java @@ -1,7 +1,11 @@ package emu.grasscutter.scripts.data; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneObject { public int level; public int config_id; @@ -11,5 +15,5 @@ public class SceneObject { /** * not set by lua */ - public int groupId; + public transient int groupId; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java index dac164d0e..edc04be15 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java @@ -5,7 +5,12 @@ import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Data; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneRegion { public int config_id; public int shape; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java index 90433b3d4..2afa9cf72 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -2,8 +2,11 @@ package emu.grasscutter.scripts.data; import java.util.List; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneSuite { public List monsters; public List gadgets; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index 301fdb8e0..6f5d761ef 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -1,5 +1,8 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; + +@Setter public class SceneTrigger { public String name; public int config_id; @@ -8,6 +11,7 @@ public class SceneTrigger { public String condition; public String action; + public SceneGroup currentGroup; @Override public boolean equals(Object obj) { if(obj instanceof SceneTrigger sceneTrigger){ diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java index 9ead6f474..41ebda1b1 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java @@ -1,5 +1,10 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter public class SceneVar { public String name; public int value; diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index c31c13b81..b262079ee 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -1,14 +1,23 @@ package emu.grasscutter.scripts.serializer; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - + import com.esotericsoftware.reflectasm.ConstructorAccess; +import com.esotericsoftware.reflectasm.MethodAccess; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.experimental.FieldDefaults; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + public class LuaSerializer implements Serializer { - + + private final static Map, MethodAccess> methodAccessCache = new ConcurrentHashMap<>(); + private final static Map, ConstructorAccess> constructorCache = new ConcurrentHashMap<>(); + private final static Map, Map> fieldMetaCache = new ConcurrentHashMap<>(); + @Override public List toList(Class type, Object obj) { return serializeList(type, (LuaTable) obj); @@ -20,7 +29,7 @@ public class LuaSerializer implements Serializer { } public List serializeList(Class type, LuaTable table) { - List list = new ArrayList(); + List list = new ArrayList<>(); try { LuaValue[] keys = table.keys(); @@ -55,7 +64,7 @@ public class LuaSerializer implements Serializer { return list; } - + public T serialize(Class type, LuaTable table) { T object = null; @@ -70,30 +79,34 @@ public class LuaSerializer implements Serializer { } try { - //noinspection ConfusingArgumentToVarargsMethod - object = type.getDeclaredConstructor().newInstance(null); - + if(!methodAccessCache.containsKey(type)){ + cacheType(type); + } + var methodAccess = methodAccessCache.get(type); + var fieldMetaMap = fieldMetaCache.get(type); + + object = (T) constructorCache.get(type).newInstance(); + LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { - Field field = getField(object.getClass(), k.checkjstring()); - if (field == null) { + var keyName = k.checkjstring(); + if(!fieldMetaMap.containsKey(keyName)){ continue; } - - field.setAccessible(true); + var fieldMeta = fieldMetaMap.get(keyName); 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()); + methodAccess.invoke(object, fieldMeta.index, serialize(fieldMeta.getType(), keyValue.checktable())); + } else if (fieldMeta.getType().equals(float.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.tofloat()); + } else if (fieldMeta.getType().equals(int.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.toint()); + } else if (fieldMeta.getType().equals(String.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); } else { - field.set(object, keyValue); + methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); } } catch (Exception ex) { //ex.printStackTrace(); @@ -107,16 +120,57 @@ 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; - } + public Map cacheType(Class type){ + if(fieldMetaCache.containsKey(type)) { + return fieldMetaCache.get(type); } + if(!constructorCache.containsKey(type)){ + constructorCache.putIfAbsent(type, ConstructorAccess.get(type)); + } + var methodAccess = Optional.ofNullable(methodAccessCache.get(type)).orElse(MethodAccess.get(type)); + methodAccessCache.putIfAbsent(type, methodAccess); + + var fieldMetaMap = new HashMap(); + var methodNameSet = new HashSet<>(Arrays.stream(methodAccess.getMethodNames()).toList()); + + Arrays.stream(type.getDeclaredFields()) + .filter(field -> methodNameSet.contains(getSetterName(field.getName()))) + .forEach(field -> { + var setter = getSetterName(field.getName()); + var index = methodAccess.getIndex(setter); + fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType())); + }); + + Arrays.stream(type.getFields()) + .filter(field -> !fieldMetaMap.containsKey(field.getName())) + .filter(field -> methodNameSet.contains(getSetterName(field.getName()))) + .forEach(field -> { + var setter = getSetterName(field.getName()); + var index = methodAccess.getIndex(setter); + fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType())); + }); + + fieldMetaCache.put(type, fieldMetaMap); + return fieldMetaMap; + } + + public String getSetterName(String fieldName){ + if(fieldName == null || fieldName.length() == 0){ + return null; + } + if(fieldName.length() == 1){ + return "set" + fieldName.toUpperCase(); + } + return "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + } + + @Data + @AllArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE) + static class FieldMeta{ + String name; + String setter; + int index; + Class type; } } From df580bf51908103c18e2e2a92ad33e3322ef10ca Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Mon, 16 May 2022 22:09:10 -0700 Subject: [PATCH 05/24] Fixed issue with scene groups after merge --- src/main/java/emu/grasscutter/scripts/data/SceneGroup.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 82efb1969..08766b763 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import static emu.grasscutter.Configuration.SCRIPTS_FOLDER; +import static emu.grasscutter.Configuration.*; @ToString @Setter @@ -68,7 +68,7 @@ public class SceneGroup { setLoaded(true); CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPTS_FOLDER + "Scene/" + sceneId + "/scene" + sceneId + "_group" + id + "." + ScriptLoader.getScriptType()); + SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_group" + id + "." + ScriptLoader.getScriptType())); if (cs == null) { return this; From 5f13a51668454d48197a94243aea15a6cb087458 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Wed, 18 May 2022 15:13:31 +0800 Subject: [PATCH 06/24] Support Open Chest --- data/ChestReward.json | 135 ++++++++++++++++++ .../grasscutter/game/entity/EntityGadget.java | 41 +++++- .../game/entity/EntityMonster.java | 7 +- .../emu/grasscutter/game/player/Player.java | 8 ++ .../grasscutter/game/world/ChestReward.java | 22 +++ .../emu/grasscutter/game/world/Scene.java | 43 ++++-- .../game/world/WorldDataManager.java | 41 ++++++ .../scripts/SceneScriptManager.java | 41 ++---- .../emu/grasscutter/scripts/ScriptLib.java | 77 ++++++++-- .../scripts/constants/EventType.java | 3 + .../grasscutter/scripts/data/SceneGroup.java | 14 +- .../grasscutter/scripts/data/SceneSuite.java | 1 + .../scripts/data/SceneTrigger.java | 2 +- .../grasscutter/server/game/GameServer.java | 7 + 14 files changed, 380 insertions(+), 62 deletions(-) create mode 100644 data/ChestReward.json create mode 100644 src/main/java/emu/grasscutter/game/world/ChestReward.java create mode 100644 src/main/java/emu/grasscutter/game/world/WorldDataManager.java diff --git a/data/ChestReward.json b/data/ChestReward.json new file mode 100644 index 000000000..a9c847b16 --- /dev/null +++ b/data/ChestReward.json @@ -0,0 +1,135 @@ +[ + { + "objNames" : [ + "SceneObj_Chest_Default_Lv1", + "SceneObj_Chest_Locked_Lv1", + "SceneObj_Chest_Bramble_Lv1", + "SceneObj_Chest_Frozen_Lv1", + "SceneObj_Chest_Rock_Lv1", + "SceneObj_EssenceChest_Default_Lv1", + "SceneObj_EssenceChest_Locked_Lv1" + ], + "advExp" : 10, + "resin" : 0, + "mora" : 257, + "sigil" : 1, + "content" : [ + { + "itemId" : 104011, + "count": 3 + }, + { + "itemId" : 104001, + "count": 1 + } + ], + "randomCount": 4, + "randomContent": [ + { + "itemId" : 11101, + "count": 1 + }, + { + "itemId" : 11201, + "count": 1 + }, + { + "itemId" : 12101, + "count": 1 + }, + { + "itemId" : 12201, + "count": 1 + }, + { + "itemId" : 13101, + "count": 1 + }, + { + "itemId" : 13201, + "count": 1 + }, + { + "itemId" : 14101, + "count": 1 + }, + { + "itemId" : 14201, + "count": 1 + }, + { + "itemId" : 15101, + "count": 1 + }, + { + "itemId" : 15201, + "count": 1 + } + ] + }, + { + "objNames" : [ + "SceneObj_Chest_Default_Lv2", + "SceneObj_Chest_Locked_Lv2", + "SceneObj_Chest_Bramble_Lv2", + "SceneObj_Chest_Frozen_Lv2" + ], + "advExp" : 20, + "resin" : 2, + "mora" : 756, + "sigil" : 2, + "content" : [ + { + "itemId" : 104012, + "count": 3 + }, + { + "itemId" : 104002, + "count": 1 + } + ], + "randomCount": 4, + "randomContent": [ + { + "itemId" : 11201, + "count": 1 + }, + { + "itemId" : 11301, + "count": 1 + }, + { + "itemId" : 12201, + "count": 1 + }, + { + "itemId" : 12301, + "count": 1 + }, + { + "itemId" : 13201, + "count": 1 + }, + { + "itemId" : 13301, + "count": 1 + }, + { + "itemId" : 14201, + "count": 1 + }, + { + "itemId" : 14301, + "count": 1 + }, + { + "itemId" : 15201, + "count": 1 + }, + { + "itemId" : 15301, + "count": 1 + } + ] + } +] \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index 640f93f22..eecc2a07c 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -1,18 +1,16 @@ package emu.grasscutter.game.entity; -import java.util.Arrays; -import java.util.List; - +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.GadgetData; +import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.world.Scene; -import emu.grasscutter.game.world.World; -import emu.grasscutter.net.proto.ClientGadgetInfoOuterClass; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; +import emu.grasscutter.net.proto.BossChestInfoOuterClass; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; @@ -27,11 +25,14 @@ 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; +import lombok.ToString; +import java.util.Arrays; +import java.util.Random; + +@ToString(callSuper = true) public class EntityGadget extends EntityBaseGadget { private final GadgetData data; private final Position pos; @@ -154,4 +155,30 @@ public class EntityGadget extends EntityBaseGadget { return entityInfo.build(); } + + public void openChest(Player player) { + var chestRewardMap = getScene().getWorld().getServer().getWorldDataManager().getChestRewardMap(); + var chestReward = chestRewardMap.get(this.getGadgetData().getJsonName()); + if(chestReward == null){ + Grasscutter.getLogger().warn("Could not found the config of this type of Chests {}", this.getGadgetData().getJsonName()); + return; + } + + player.earnExp(chestReward.getAdvExp()); + player.getInventory().addItem(201, chestReward.getResin()); + + var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5); + player.getInventory().addItem(202, (int)mora); + + for(int i=0;i objNames; + int advExp; + int resin; + int mora; + int sigil; + List content; + int randomCount; + List randomContent; + +} diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 1c382fc74..93c56ae3f 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -3,10 +3,7 @@ package emu.grasscutter.game.world; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameDepot; -import emu.grasscutter.data.def.DungeonData; -import emu.grasscutter.data.def.MonsterData; -import emu.grasscutter.data.def.SceneData; -import emu.grasscutter.data.def.WorldLevelData; +import emu.grasscutter.data.def.*; import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.entity.*; @@ -24,11 +21,8 @@ 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.PacketLifeStateChangeNotify; -import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; -import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -37,6 +31,8 @@ import org.danilopianini.util.SpatialIndex; import java.util.*; import java.util.stream.Collectors; +import static emu.grasscutter.utils.Language.translate; + public class Scene { private final World world; private final SceneData sceneData; @@ -586,8 +582,10 @@ public class Scene { do { var suiteData = group.getSuiteByIndex(suite); + suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger); + entities.addAll(suiteData.sceneGadgets.stream() - .map(g -> scriptManager.createGadgets(group.id, group.block_id, g)).toList()); + .map(g -> scriptManager.createGadget(group.id, group.block_id, g)).toList()); entities.addAll(suiteData.sceneMonsters.stream() .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)).toList()); suite++; @@ -609,7 +607,7 @@ public class Scene { for (SceneGroup group : block.groups) { if(group.triggers != null){ - group.triggers.forEach(getScriptManager()::deregisterTrigger); + group.triggers.values().forEach(getScriptManager()::deregisterTrigger); } if(group.regions != null){ group.regions.forEach(getScriptManager()::deregisterRegion); @@ -681,4 +679,27 @@ public class Scene { player.getSession().send(packet); } } + + public void addItemEntity(int itemId, int amount, GameEntity bornForm){ + ItemData itemData = GameData.getItemDataMap().get(itemId); + if (itemData == null) { + return; + } + if (itemData.isEquip()) { + float range = (3f + (.1f * amount)); + for (int i = 0; i < amount; i++) { + Position pos = bornForm.getPosition().clone().addX((float) (Math.random() * range) - (range / 2)).addZ((float) (Math.random() * range) - (range / 2)).addZ(.9f); + EntityItem entity = new EntityItem(this, null, itemData, pos, 1); + addEntity(entity); + } + } else { + EntityItem entity = new EntityItem(this, null, itemData, bornForm.getPosition().clone().addZ(.9f), amount); + addEntity(entity); + } + } + + public void updateGadgetState(EntityGadget gadget, int state){ + gadget.setState(state); + broadcastPacket(new PacketGadgetStateNotify(gadget, state)); + } } diff --git a/src/main/java/emu/grasscutter/game/world/WorldDataManager.java b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java new file mode 100644 index 000000000..c7466880b --- /dev/null +++ b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java @@ -0,0 +1,41 @@ +package emu.grasscutter.game.world; + +import com.google.gson.reflect.TypeToken; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.server.game.GameServer; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static emu.grasscutter.Configuration.DATA; + +public class WorldDataManager { + private final GameServer gameServer; + private final Map chestRewardMap; + + public WorldDataManager(GameServer gameServer){ + this.gameServer = gameServer; + this.chestRewardMap = new HashMap<>(); + load(); + } + + public synchronized void load(){ + try { + List chestReward = Grasscutter.getGsonFactory().fromJson( + Files.readString(Path.of(DATA("ChestReward.json"))), + TypeToken.getParameterized(List.class, ChestReward.class).getType()); + chestReward.forEach(reward -> + reward.getObjNames().forEach(name -> chestRewardMap.put(name, reward))); + + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to load chest reward config.", e); + } + } + + public Map getChestRewardMap() { + return chestRewardMap; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index d57cb9ab5..923d45a55 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -42,7 +42,6 @@ public class SceneScriptManager { private final Int2ObjectOpenHashMap> currentTriggers; private final Int2ObjectOpenHashMap regions; private Map sceneGroups; - private SceneGroup currentGroup; private ScriptMonsterTideService scriptMonsterTideService; private ScriptMonsterSpawnService scriptMonsterSpawnService; /** @@ -94,10 +93,6 @@ public class SceneScriptManager { return meta.config; } - public SceneGroup getCurrentGroup() { - return currentGroup; - } - public Map getBlocks() { return meta.blocks; } @@ -118,20 +113,19 @@ public class SceneScriptManager { this.triggers.remove(trigger.name); getTriggersByEvent(trigger.event).remove(trigger); } - public void resetTriggers(List triggerNames) { - for(var name : triggerNames){ - var instance = triggers.get(name); - this.currentTriggers.get(instance.event).clear(); - this.currentTriggers.get(instance.event).add(instance); - } + public void resetTriggers(int eventId) { + currentTriggers.put(eventId, new HashSet<>()); } public void refreshGroup(SceneGroup group, int suiteIndex){ var suite = group.getSuiteByIndex(suiteIndex); if(suite == null){ return; } - if(suite.triggers.size() > 0){ - resetTriggers(suite.triggers); + if(suite.sceneTriggers.size() > 0){ + for(var trigger : suite.sceneTriggers){ + resetTriggers(trigger.event); + this.currentTriggers.get(trigger.event).add(trigger); + } } spawnMonstersInGroup(group, suite); spawnGadgetsInGroup(group, suite); @@ -158,7 +152,7 @@ public class SceneScriptManager { for (SceneGroup group : block.groups) { if (group.id == groupId) { if(!group.isLoaded()){ - loadGroupFromScript(group); + getScene().onLoadGroup(List.of(group)); } return group; } @@ -204,9 +198,6 @@ public class SceneScriptManager { 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); } @@ -248,7 +239,7 @@ public class SceneScriptManager { } var toCreate = gadgets.stream() - .map(g -> createGadgets(g.groupId, group.block_id, g)) + .map(g -> createGadget(g.groupId, group.block_id, g)) .filter(Objects::nonNull) .toList(); this.addEntities(toCreate); @@ -265,20 +256,17 @@ public class SceneScriptManager { if(suite == null || suite.sceneMonsters.size() <= 0){ return; } - this.currentGroup = group; this.addEntities(suite.sceneMonsters.stream() .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); } public void spawnMonstersInGroup(SceneGroup group) { - this.currentGroup = group; 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) { - this.currentGroup = group; this.scriptMonsterTideService = new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId); @@ -289,15 +277,15 @@ public class SceneScriptManager { } this.getScriptMonsterTideService().unload(); } - public void spawnMonstersByConfigId(int configId, int delayTime) { + public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) { // TODO delay - getScene().addEntity( - createMonster(this.currentGroup.id, this.currentGroup.block_id, this.currentGroup.monsters.get(configId))); + getScene().addEntity(createMonster(group.id, group.block_id, group.monsters.get(configId))); } // Events public void callEvent(int eventType, ScriptArgs params) { for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { + scriptLib.setCurrentGroup(trigger.currentGroup); LuaValue condition = null; if (trigger.condition != null && !trigger.condition.isEmpty()) { @@ -330,6 +318,7 @@ public class SceneScriptManager { safetyCall(trigger.action, action, args); } //TODO some ret may not bool + scriptLib.removeCurrentGroup(); } } @@ -337,7 +326,7 @@ public class SceneScriptManager { try{ return func.call(this.getScriptLibLua(), args); }catch (LuaError error){ - ScriptLib.logger.error("[LUA] call trigger failed {},{}",name,args,error); + ScriptLib.logger.error("[LUA] call trigger failed {},{},{}",name,args,error.getMessage()); return LuaValue.valueOf(-1); } } @@ -350,7 +339,7 @@ public class SceneScriptManager { return scriptMonsterSpawnService; } - public EntityGadget createGadgets(int groupId, int blockId, SceneGadget g) { + public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) { EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); if (entity.getGadgetData() == null){ diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index e82df384d..44a8888df 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -23,6 +23,7 @@ public class ScriptLib { public ScriptLib(SceneScriptManager sceneScriptManager) { this.sceneScriptManager = sceneScriptManager; + this.currentGroup = new ThreadLocal<>(); } public SceneScriptManager getSceneScriptManager() { @@ -38,7 +39,17 @@ public class ScriptLib { sb.append("}"); return sb.toString(); } - + private final ThreadLocal currentGroup; + public void setCurrentGroup(SceneGroup currentGroup){ + logger.debug("current {}", currentGroup); + this.currentGroup.set(currentGroup); + } + public Optional getCurrentGroup(){ + return Optional.ofNullable(this.currentGroup.get()); + } + public void removeCurrentGroup(){ + this.currentGroup.remove(); + } public int SetGadgetStateByConfigId(int configId, int gadgetState) { logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", configId,gadgetState); @@ -100,7 +111,12 @@ public class ScriptLib { getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); return 0; } - + + public int SetWorktopOptions(LuaTable table){ + logger.debug("[LUA] Call SetWorktopOptions with {}", printTable(table)); + // TODO + return 0; + } public int DelWorktopOptionByGroupId(int groupId, int configId, int option) { logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option); @@ -272,7 +288,8 @@ public class ScriptLib { var1); return (int) getSceneScriptManager().getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id) + .filter(e -> e instanceof EntityMonster && + e.getGroupId() == getCurrentGroup().map(sceneGroup -> sceneGroup.id).orElse(-1)) .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ @@ -334,7 +351,11 @@ public class ScriptLib { var configId = table.get("config_id").toint(); var delayTime = table.get("delay_time").toint(); - getSceneScriptManager().spawnMonstersByConfigId(configId, delayTime); + if(getCurrentGroup().isEmpty()){ + return 1; + } + + getSceneScriptManager().spawnMonstersByConfigId(getCurrentGroup().get(), configId, delayTime); return 0; } @@ -353,7 +374,13 @@ public class ScriptLib { printTable(table)); var configId = table.get("config_id").toint(); - //TODO + var group = getCurrentGroup(); + if(group.isEmpty()){ + return 1; + } + var gadget = group.get().gadgets.get(configId); + var entity = getSceneScriptManager().createGadget(group.get().id, group.get().block_id, gadget); + getSceneScriptManager().addEntity(entity); return 0; } @@ -376,14 +403,42 @@ public class ScriptLib { .filter(g -> g.getConfigId() == configId) .findFirst(); if(gadget.isEmpty()){ - return 0; + return 1; } - var stat = ((EntityGadget)gadget.get()).getState(); - return stat; + return ((EntityGadget)gadget.get()).getState(); } - public int SetGadgetStateByConfigId(int configId, LuaTable gadgetState){ - logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", - configId, printTable(gadgetState)); + + public int MarkPlayerAction(int var1, int var2, int var3, int var4){ + logger.debug("[LUA] Call MarkPlayerAction with {},{}", + var1, var2); + + return 0; + } + + public int AddQuestProgress(String var1){ + logger.debug("[LUA] Call AddQuestProgress with {}", + var1); + + return 0; + } + + /** + * change the state of gadget + */ + public int ChangeGroupGadget(LuaTable table){ + logger.debug("[LUA] Call ChangeGroupGadget with {}", + printTable(table)); + var configId = table.get("config_id").toint(); + var state = table.get("state").toint(); + + var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId); + if(entity == null){ + return 1; + } + + if(entity instanceof EntityGadget entityGadget){ + getSceneScriptManager().getScene().updateGadgetState(entityGadget, state); + } return 0; } diff --git a/src/main/java/emu/grasscutter/scripts/constants/EventType.java b/src/main/java/emu/grasscutter/scripts/constants/EventType.java index 58d8dc3ab..b7236b85c 100644 --- a/src/main/java/emu/grasscutter/scripts/constants/EventType.java +++ b/src/main/java/emu/grasscutter/scripts/constants/EventType.java @@ -2,6 +2,9 @@ package emu.grasscutter.scripts.constants; public class EventType { public static final int EVENT_NONE = 0; + /** + * param1: monster.configId + */ 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; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 08766b763..ee7efd1a7 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -34,7 +34,7 @@ public class SceneGroup { * ConfigId - Gadget */ public Map gadgets; - public List triggers; + public Map triggers; public List regions; public List suites; public SceneInitConfig init_config; @@ -88,8 +88,9 @@ public class SceneGroup { .collect(Collectors.toMap(x -> x.config_id, y -> y)); gadgets.values().forEach(m -> m.groupId = id); - triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); - triggers.forEach(t -> t.currentGroup = this); + triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")).stream() + .collect(Collectors.toMap(x -> x.name, y -> y)); + triggers.values().forEach(t -> t.currentGroup = this); suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); @@ -113,6 +114,13 @@ public class SceneGroup { .map(gadgets::get) .toList() ); + + suite.sceneTriggers = new ArrayList<>( + suite.triggers.stream() + .filter(triggers::containsKey) + .map(triggers::get) + .toList() + ); } } catch (ScriptException e) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java index 2afa9cf72..7555601ca 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -15,4 +15,5 @@ public class SceneSuite { public transient List sceneMonsters; public transient List sceneGadgets; + public transient List sceneTriggers; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index 6f5d761ef..34e4df75e 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -11,7 +11,7 @@ public class SceneTrigger { public String condition; public String action; - public SceneGroup currentGroup; + public transient SceneGroup currentGroup; @Override public boolean equals(Object obj) { if(obj instanceof SceneTrigger sceneTrigger){ diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 3ebe83997..3159de1aa 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -19,6 +19,7 @@ import emu.grasscutter.game.quest.ServerQuestHandler; import emu.grasscutter.game.shop.ShopManager; import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.game.world.World; +import emu.grasscutter.game.world.WorldDataManager; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.netty.KcpServer; @@ -57,6 +58,7 @@ public final class GameServer extends KcpServer { private final CombineManger combineManger; private final TowerScheduleManager towerScheduleManager; + private final WorldDataManager worldDataManager; public GameServer() { this(new InetSocketAddress( GAME_INFO.bindAddress, @@ -86,6 +88,7 @@ public final class GameServer extends KcpServer { this.expeditionManager = new ExpeditionManager(this); this.combineManger = new CombineManger(this); this.towerScheduleManager = new TowerScheduleManager(this); + this.worldDataManager = new WorldDataManager(this); // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); } @@ -153,6 +156,10 @@ public final class GameServer extends KcpServer { return towerScheduleManager; } + public WorldDataManager getWorldDataManager() { + return worldDataManager; + } + public TaskMap getTaskMap() { return this.taskMap; } From a48585d1241bc7f8828fdf50c3b3c3ccbfdd4c49 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 18 May 2022 02:21:34 -0700 Subject: [PATCH 07/24] Implement local specialty spawning --- .../java/emu/grasscutter/data/GameData.java | 5 ++ .../emu/grasscutter/data/def/GatherData.java | 49 +++++++++++++++ .../emu/grasscutter/data/def/SceneData.java | 1 - .../grasscutter/game/entity/EntityGadget.java | 63 ++++++++++++------- .../game/entity/gadget/GadgetContent.java | 21 +++++++ .../game/entity/gadget/GadgetGatherPoint.java | 44 +++++++++++++ .../entity/gadget/GadgetRewardStatue.java | 28 +++++++++ .../game/entity/gadget/GadgetWorktop.java | 52 +++++++++++++++ .../emu/grasscutter/game/player/Player.java | 18 +++--- .../emu/grasscutter/game/world/Scene.java | 9 +++ .../scripts/SceneScriptManager.java | 2 + .../emu/grasscutter/scripts/ScriptLib.java | 37 +++++------ .../emu/grasscutter/scripts/ScriptLoader.java | 4 +- .../scripts/data/SceneBusiness.java | 10 +++ .../grasscutter/scripts/data/SceneGadget.java | 1 + .../scripts/data/SceneGarbage.java | 12 ++++ .../grasscutter/scripts/data/SceneGroup.java | 34 ++++++---- .../send/PacketWorktopOptionNotify.java | 5 +- 18 files changed, 328 insertions(+), 67 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/def/GatherData.java create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/GadgetContent.java create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/GadgetGatherPoint.java create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/GadgetWorktop.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneBusiness.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index ed5c469bb..a87e0e168 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -75,6 +75,7 @@ public class GameData { private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap gatherDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerScheduleDataMap = new Int2ObjectOpenHashMap<>(); @@ -347,4 +348,8 @@ public class GameData { public static Int2ObjectMap getQuestDataMap() { return questDataMap; } + + public static Int2ObjectMap getGatherDataMap() { + return gatherDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/def/GatherData.java b/src/main/java/emu/grasscutter/data/def/GatherData.java new file mode 100644 index 000000000..c80442378 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/GatherData.java @@ -0,0 +1,49 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = "GatherExcelConfigData.json") +public class GatherData extends GameResource { + private int PointType; + private int Id; + private int GadgetId; + private int ItemId; + private int Cd; // Probably hours + private boolean IsForbidGuest; + private boolean InitDisableInteract; + + @Override + public int getId() { + return this.PointType; + } + + public int getGatherId() { + return Id; + } + + public int getGadgetId() { + return GadgetId; + } + + public int getItemId() { + return ItemId; + } + + public int getCd() { + return Cd; + } + + public boolean isForbidGuest() { + return IsForbidGuest; + } + + public boolean initDisableInteract() { + return InitDisableInteract; + } + + @Override + public void onLoad() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/SceneData.java b/src/main/java/emu/grasscutter/data/def/SceneData.java index cd1510e1d..955291cff 100644 --- a/src/main/java/emu/grasscutter/data/def/SceneData.java +++ b/src/main/java/emu/grasscutter/data/def/SceneData.java @@ -12,7 +12,6 @@ public class SceneData extends GameResource { private SceneType Type; private String ScriptData; - @Override public int getId() { return this.Id; diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index 640f93f22..af1d2c68c 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -5,8 +5,13 @@ import java.util.List; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.GadgetData; +import emu.grasscutter.game.entity.gadget.GadgetContent; +import emu.grasscutter.game.entity.gadget.GadgetGatherPoint; +import emu.grasscutter.game.entity.gadget.GadgetRewardStatue; +import emu.grasscutter.game.entity.gadget.GadgetWorktop; import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityType; +import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.World; @@ -39,7 +44,8 @@ public class EntityGadget extends EntityBaseGadget { private int gadgetId; private int state; - private IntSet worktopOptions; + private int pointType; + private GadgetContent content; public EntityGadget(Scene scene, int gadgetId, Position pos) { super(scene); @@ -50,19 +56,22 @@ public class EntityGadget extends EntityBaseGadget { this.rot = new Position(); } + public EntityGadget(Scene scene, int gadgetId, Position pos, GadgetContent content) { + this(scene, gadgetId, pos); + this.content = content; + } + 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; } @@ -82,27 +91,42 @@ public class EntityGadget extends EntityBaseGadget { this.state = state; } - public IntSet getWorktopOptions() { - return worktopOptions; + public int getPointType() { + return pointType; + } + + public void setPointType(int pointType) { + this.pointType = pointType; + } + + public GadgetContent getContent() { + return content; + } + + @Deprecated // Dont use! + public void setContent(GadgetContent content) { + this.content = this.content == null ? content : this.content; } - 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) { + // TODO refactor + public void buildContent() { + if (getContent() != null || getGadgetData() == null || getGadgetData().getType() == null) { return; } - this.worktopOptions.remove(option); + + EntityType type = getGadgetData().getType(); + GadgetContent content = switch (type) { + case GatherPoint -> new GadgetGatherPoint(this); + case Worktop -> new GadgetWorktop(this); + case RewardStatue -> new GadgetRewardStatue(this); + default -> null; + }; + + this.content = content; } @Override public Int2FloatOpenHashMap getFightProperties() { - // TODO Auto-generated method stub return null; } @@ -143,11 +167,8 @@ public class EntityGadget extends EntityBaseGadget { .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); + if (this.getContent() != null) { + this.getContent().onBuildProto(gadgetInfo); } entityInfo.setGadget(gadgetInfo); diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetContent.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetContent.java new file mode 100644 index 000000000..d35663f60 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetContent.java @@ -0,0 +1,21 @@ +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; + +public abstract class GadgetContent { + private final EntityGadget gadget; + + public GadgetContent(EntityGadget gadget) { + this.gadget = gadget; + } + + public EntityGadget getGadget() { + return gadget; + } + + public abstract boolean onInteract(Player player); + + public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo); +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetGatherPoint.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetGatherPoint.java new file mode 100644 index 000000000..0b3f3b6f9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetGatherPoint.java @@ -0,0 +1,44 @@ +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.GatherData; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; + +public class GadgetGatherPoint extends GadgetContent { + private GatherData gatherData; + + public GadgetGatherPoint(EntityGadget gadget) { + super(gadget); + this.gatherData = GameData.getGatherDataMap().get(gadget.getPointType()); + } + + public GatherData getGatherData() { + return gatherData; + } + + public int getItemId() { + return getGatherData().getItemId(); + } + + public boolean onInteract(Player player) { + GameItem item = new GameItem(gatherData.getItemId(), 1); + + player.getInventory().addItem(item, ActionReason.Gather); + + return true; + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { + GatherGadgetInfo gatherGadgetInfo = GatherGadgetInfo.newBuilder() + .setItemId(this.getItemId()) + .setIsForbidGuest(this.getGatherData().isForbidGuest()) + .build(); + + gadgetInfo.setGatherGadget(gatherGadgetInfo); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java new file mode 100644 index 000000000..8412c8b9f --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java @@ -0,0 +1,28 @@ +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; + +public class GadgetRewardStatue extends GadgetContent { + + public GadgetRewardStatue(EntityGadget gadget) { + super(gadget); + } + + public boolean onInteract(Player player) { + if (player.getScene().getChallenge() != null) { + player.getScene().getChallenge().getStatueDrops(player); + } + + player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_OPEN_STATUE)); + + return false; + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { + + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetWorktop.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetWorktop.java new file mode 100644 index 000000000..fa7966189 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetWorktop.java @@ -0,0 +1,52 @@ +package emu.grasscutter.game.entity.gadget; + +import java.util.Arrays; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +public class GadgetWorktop extends GadgetContent { + private IntSet worktopOptions; + + public GadgetWorktop(EntityGadget gadget) { + super(gadget); + } + + 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); + } + + public boolean onInteract(Player player) { + return false; + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { + if (this.worktopOptions == null) { + return; + } + + WorktopInfo worktop = WorktopInfo.newBuilder() + .addAllOptionList(this.getWorktopOptions()) + .build(); + + gadgetInfo.setWorktop(worktop); + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index aa4a0b59f..0ec0f3d1d 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -917,22 +917,20 @@ public class Player { else this.getScene().broadcastPacket(new PacketGadgetInteractRsp(drop, InteractType.INTERACT_PICK_ITEM)); } - } else if (entity instanceof EntityGadget) { - EntityGadget gadget = (EntityGadget) entity; + } else if (entity instanceof EntityGadget gadget) { + if (gadget.getContent() == null) { + return; + } - if (gadget.getGadgetData().getType() == EntityType.RewardStatue) { - if (scene.getChallenge() != null) { - scene.getChallenge().getStatueDrops(this); - } - - this.sendPacket(new PacketGadgetInteractRsp(gadget, InteractType.INTERACT_OPEN_STATUE)); + boolean shouldDelete = gadget.getContent().onInteract(this); + + if (shouldDelete) { + entity.getScene().removeEntity(entity); } } else { // Delete directly entity.getScene().removeEntity(entity); } - - return; } public void onPause() { diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 1c382fc74..c53ad0b21 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -23,6 +23,7 @@ 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.SceneGadget; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.server.packet.send.PacketAvatarSkillInfoNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; @@ -577,7 +578,15 @@ public class Scene { if (group.init_config == null) { continue; } + + // Load garbages + List garbageGadgets = group.getGarbageGadgets(); + + if (garbageGadgets != null) { + garbageGadgets.forEach(g -> scriptManager.createGadgets(group.id, group.block_id, g)); + } + // Load suites int suite = group.init_config.suite; if (suite == 0) { diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index d57cb9ab5..99755a18f 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -362,6 +362,8 @@ public class SceneScriptManager { entity.setGroupId(groupId); entity.getRotation().set(g.rot); entity.setState(g.state); + entity.setPointType(g.point_type); + entity.buildContent(); return entity; } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index e82df384d..f436df321 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -4,6 +4,7 @@ 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.game.entity.gadget.GadgetWorktop; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; @@ -83,21 +84,22 @@ public class ScriptLib { public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) { logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}", groupId,configId,options); + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); - if (entity.isEmpty()) { - return 1; - } - - if (!(entity.get() instanceof EntityGadget)) { - return 1; - } - - EntityGadget gadget = (EntityGadget) entity.get(); - gadget.addWorktopOptions(options); + if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) { + return 1; + } + + if (!(gadget.getContent() instanceof GadgetWorktop worktop)) { + return 1; + } + + worktop.addWorktopOptions(options); getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); + return 0; } @@ -107,18 +109,17 @@ public class ScriptLib { Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); - if (entity.isEmpty()) { + if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) { + return 1; + } + + if (!(gadget.getContent() instanceof GadgetWorktop worktop)) { return 1; } - if (!(entity.get() instanceof EntityGadget)) { - return 1; - } - - EntityGadget gadget = (EntityGadget) entity.get(); - gadget.removeWorktopOption(option); - + worktop.removeWorktopOption(option); getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); + return 0; } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java index 049dbce32..6795de575 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java @@ -95,7 +95,7 @@ public class ScriptLoader { return sc.get(); } - Grasscutter.getLogger().info("Loaded Script" + path); + Grasscutter.getLogger().info("Loading script " + path); File file = new File(path); @@ -106,7 +106,7 @@ public class ScriptLoader { scriptsCache.put(path, new SoftReference<>(script)); return script; } catch (Exception e) { - Grasscutter.getLogger().error("Loaded Script {} failed!", path, e); + Grasscutter.getLogger().error("Loading script {} failed!", path, e); return null; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBusiness.java b/src/main/java/emu/grasscutter/scripts/data/SceneBusiness.java new file mode 100644 index 000000000..f03011e09 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBusiness.java @@ -0,0 +1,10 @@ +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneBusiness { + public int type; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index 8b3e452fd..bdf3d55ba 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -8,4 +8,5 @@ import lombok.ToString; public class SceneGadget extends SceneObject{ public int gadget_id; public int state; + public int point_type; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java b/src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java new file mode 100644 index 000000000..19f98984e --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java @@ -0,0 +1,12 @@ +package emu.grasscutter.scripts.data; + +import java.util.List; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneGarbage { + public List gadgets; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 08766b763..0cef08627 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -26,21 +26,20 @@ public class SceneGroup { public int refresh_id; public Position pos; - /** - * ConfigId - Monster - */ - public Map monsters; - /** - * ConfigId - Gadget - */ - public Map gadgets; + public Map monsters; // + public Map gadgets; // + public List triggers; public List regions; public List suites; + public List variables; + + public SceneBusiness business; + public SceneGarbage garbages; public SceneInitConfig init_config; - public List variables; private transient boolean loaded; // Not an actual variable in the scripts either + private transient CompiledScript script; public boolean isLoaded() { return loaded; @@ -49,8 +48,14 @@ public class SceneGroup { public void setLoaded(boolean loaded) { this.loaded = loaded; } - - private transient CompiledScript script; // Not an actual variable in the scripts either + + public int getBusinessType() { + return this.business == null ? 0 : this.business.type; + } + + public List getGarbageGadgets() { + return this.garbages == null ? null : this.garbages.gadgets; + } public CompiledScript getScript() { return script; @@ -75,6 +80,7 @@ public class SceneGroup { } this.script = cs; + // Eval script try { cs.eval(bindings); @@ -90,15 +96,16 @@ public class SceneGroup { triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); triggers.forEach(t -> t.currentGroup = this); - + suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); + garbages = ScriptLoader.getSerializer().toObject(SceneGarbage.class, bindings.get("garbages")); 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 + // Add monsters and gadgets to suite for (SceneSuite suite : suites) { suite.sceneMonsters = new ArrayList<>( suite.monsters.stream() @@ -118,6 +125,7 @@ public class SceneGroup { } 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/server/packet/send/PacketWorktopOptionNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java index 14648a618..ade64cf09 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java @@ -1,6 +1,7 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.gadget.GadgetWorktop; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.WorktopOptionNotifyOuterClass.WorktopOptionNotify; @@ -13,8 +14,8 @@ public class PacketWorktopOptionNotify extends BasePacket { WorktopOptionNotify.Builder proto = WorktopOptionNotify.newBuilder() .setGadgetEntityId(gadget.getId()); - if (gadget.getWorktopOptions() != null) { - proto.addAllOptionList(gadget.getWorktopOptions()); + if (gadget.getContent() instanceof GadgetWorktop worktop) { + proto.addAllOptionList(worktop.getWorktopOptions()); } this.setData(proto); From b375881a3e388d3f9515066ad51ca9ecaa88c18e Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 18 May 2022 02:36:50 -0700 Subject: [PATCH 08/24] Fix errors caused by merge + Refactor chests into GadgetChest --- .../grasscutter/game/entity/EntityGadget.java | 34 +++--------- .../game/entity/gadget/GadgetChest.java | 53 +++++++++++++++++++ .../emu/grasscutter/game/player/Player.java | 7 --- .../emu/grasscutter/game/world/Scene.java | 7 +-- .../emu/grasscutter/scripts/ScriptLib.java | 4 +- 5 files changed, 64 insertions(+), 41 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index 6b4c677b2..db7e4b80e 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.entity; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.GadgetData; +import emu.grasscutter.game.entity.gadget.GadgetChest; import emu.grasscutter.game.entity.gadget.GadgetContent; import emu.grasscutter.game.entity.gadget.GadgetGatherPoint; import emu.grasscutter.game.entity.gadget.GadgetRewardStatue; @@ -27,6 +28,7 @@ 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.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; @@ -91,6 +93,11 @@ public class EntityGadget extends EntityBaseGadget { public void setState(int state) { this.state = state; } + + public void updateState(int state){ + this.setState(state); + this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state)); + } public int getPointType() { return pointType; @@ -120,6 +127,7 @@ public class EntityGadget extends EntityBaseGadget { case GatherPoint -> new GadgetGatherPoint(this); case Worktop -> new GadgetWorktop(this); case RewardStatue -> new GadgetRewardStatue(this); + case Chest -> new GadgetChest(this); default -> null; }; @@ -176,30 +184,4 @@ public class EntityGadget extends EntityBaseGadget { return entityInfo.build(); } - - public void openChest(Player player) { - var chestRewardMap = getScene().getWorld().getServer().getWorldDataManager().getChestRewardMap(); - var chestReward = chestRewardMap.get(this.getGadgetData().getJsonName()); - if(chestReward == null){ - Grasscutter.getLogger().warn("Could not found the config of this type of Chests {}", this.getGadgetData().getJsonName()); - return; - } - - player.earnExp(chestReward.getAdvExp()); - player.getInventory().addItem(201, chestReward.getResin()); - - var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5); - player.getInventory().addItem(202, (int)mora); - - for(int i=0;i garbageGadgets = group.getGarbageGadgets(); if (garbageGadgets != null) { - garbageGadgets.forEach(g -> scriptManager.createGadgets(group.id, group.block_id, g)); + garbageGadgets.forEach(g -> scriptManager.createGadget(group.id, group.block_id, g)); } // Load suites @@ -706,9 +706,4 @@ public class Scene { addEntity(entity); } } - - public void updateGadgetState(EntityGadget gadget, int state){ - gadget.setState(state); - broadcastPacket(new PacketGadgetStateNotify(gadget, state)); - } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 74aa8b6b6..7b2aefdc7 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -437,8 +437,8 @@ public class ScriptLib { return 1; } - if(entity instanceof EntityGadget entityGadget){ - getSceneScriptManager().getScene().updateGadgetState(entityGadget, state); + if (entity instanceof EntityGadget entityGadget) { + entityGadget.updateState(state); } return 0; From 4b5f5b3db562fd4ca30631206d3e0fb6431c7516 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 18 May 2022 05:10:45 -0700 Subject: [PATCH 09/24] Only load groups that have a business type of 0 --- src/main/java/emu/grasscutter/game/world/Scene.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 647750643..821506723 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -534,8 +534,9 @@ public class Scene { 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()); + .filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group) && group.getBusinessType() == 0) + .peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group)) + .toList()); if(groups.size() == 0){ return List.of(); From 3cffdd9773d5b0db33f49b4e091653ce6cadc027 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 18 May 2022 05:33:00 -0700 Subject: [PATCH 10/24] Cleanup and remove business_type filter --- .../emu/grasscutter/game/world/Scene.java | 10 ++++----- .../scripts/SceneScriptManager.java | 22 +++++++------------ .../emu/grasscutter/scripts/ScriptLib.java | 5 ++++- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 821506723..2e2b34c7d 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -531,14 +531,14 @@ public class Scene { public List playerMeetGroups(Player player, SceneBlock block){ int RANGE = 100; - var sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos(), RANGE); + List sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos(), RANGE); - var groups = new ArrayList<>(sceneGroups.stream() - .filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group) && group.getBusinessType() == 0) + List groups = sceneGroups.stream() + .filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group)) .peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group)) - .toList()); + .toList(); - if(groups.size() == 0){ + if (groups.size() == 0) { return List.of(); } diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 539fef5b2..2bc42bace 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -353,6 +353,9 @@ public class SceneScriptManager { entity.setState(g.state); entity.setPointType(g.point_type); entity.buildContent(); + + // Lua event + this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId())); return entity; } @@ -390,32 +393,23 @@ public class SceneScriptManager { this.getScriptMonsterSpawnService() .onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); + + // Lua event + callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); 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() { diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 7b2aefdc7..bea84d8b3 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -376,11 +376,14 @@ public class ScriptLib { var configId = table.get("config_id").toint(); var group = getCurrentGroup(); - if(group.isEmpty()){ + + if (group.isEmpty()) { return 1; } + var gadget = group.get().gadgets.get(configId); var entity = getSceneScriptManager().createGadget(group.get().id, group.get().block_id, gadget); + getSceneScriptManager().addEntity(entity); return 0; From a8f38ad9951914de574b334615e0a43809e81dca Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 18 May 2022 15:54:55 -0700 Subject: [PATCH 11/24] Move chest rewards to new data format --- .../emu/grasscutter/game/world/WorldDataManager.java | 9 ++++++--- .../main/resources/defaults/data}/ChestReward.json | 0 2 files changed, 6 insertions(+), 3 deletions(-) rename {data => src/main/resources/defaults/data}/ChestReward.json (100%) diff --git a/src/main/java/emu/grasscutter/game/world/WorldDataManager.java b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java index c7466880b..3809dce6d 100644 --- a/src/main/java/emu/grasscutter/game/world/WorldDataManager.java +++ b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java @@ -2,8 +2,11 @@ package emu.grasscutter.game.world; import com.google.gson.reflect.TypeToken; import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.DataLoader; import emu.grasscutter.server.game.GameServer; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; @@ -23,13 +26,13 @@ public class WorldDataManager { } public synchronized void load(){ - try { + try(InputStream is = DataLoader.load("ChestReward.json", false); InputStreamReader isr = new InputStreamReader(is)) { List chestReward = Grasscutter.getGsonFactory().fromJson( - Files.readString(Path.of(DATA("ChestReward.json"))), + isr, TypeToken.getParameterized(List.class, ChestReward.class).getType()); + chestReward.forEach(reward -> reward.getObjNames().forEach(name -> chestRewardMap.put(name, reward))); - } catch (Exception e) { Grasscutter.getLogger().error("Unable to load chest reward config.", e); } diff --git a/data/ChestReward.json b/src/main/resources/defaults/data/ChestReward.json similarity index 100% rename from data/ChestReward.json rename to src/main/resources/defaults/data/ChestReward.json From bad853573c248695d5a7fe30eedea674299ce557 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Thu, 19 May 2022 11:15:40 +0800 Subject: [PATCH 12/24] optimized the Lua func binding so that the script will not eval again --- .../game/dungeons/DungeonChallenge.java | 4 +- .../game/world/WorldDataManager.java | 2 +- .../scripts/SceneScriptManager.java | 107 +++++++----------- .../emu/grasscutter/scripts/ScriptLib.java | 26 +++-- .../emu/grasscutter/scripts/ScriptLoader.java | 14 +++ .../grasscutter/scripts/data/SceneGroup.java | 12 +- .../scripts/serializer/LuaSerializer.java | 3 + 7 files changed, 89 insertions(+), 79 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java index a13f1a611..2b1e992f4 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java @@ -138,7 +138,9 @@ public class DungeonChallenge { getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); if(!stage){ - getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0)); + getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, + // TODO record the time in PARAM2 and used in action + new ScriptArgs(this.isSuccess() ? 1 : 0, 100)); } } diff --git a/src/main/java/emu/grasscutter/game/world/WorldDataManager.java b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java index 3809dce6d..ab3ea40c1 100644 --- a/src/main/java/emu/grasscutter/game/world/WorldDataManager.java +++ b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java @@ -26,7 +26,7 @@ public class WorldDataManager { } public synchronized void load(){ - try(InputStream is = DataLoader.load("ChestReward.json", false); InputStreamReader isr = new InputStreamReader(is)) { + try(InputStream is = DataLoader.load("ChestReward.json"); InputStreamReader isr = new InputStreamReader(is)) { List chestReward = Grasscutter.getGsonFactory().fromJson( isr, TypeToken.getParameterized(List.class, ChestReward.class).getType()); diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 2bc42bace..bbb475717 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -20,16 +20,11 @@ import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.CoerceJavaToLua; -import javax.script.Bindings; -import javax.script.ScriptException; import java.util.*; public class SceneScriptManager { private final Scene scene; - private final ScriptLib scriptLib; - private final LuaValue scriptLibLua; private final Map variables; - private Bindings bindings; private SceneMeta meta; private boolean isInit; /** @@ -50,8 +45,6 @@ public class SceneScriptManager { private Int2ObjectMap> loadedGroupSetPerBlock; public SceneScriptManager(Scene scene) { this.scene = scene; - this.scriptLib = new ScriptLib(this); - this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib); this.triggers = new HashMap<>(); this.currentTriggers = new Int2ObjectOpenHashMap<>(); @@ -74,18 +67,6 @@ public class SceneScriptManager { return scene; } - public ScriptLib getScriptLib() { - return scriptLib; - } - - public LuaValue getScriptLibLua() { - return scriptLibLua; - } - - public Bindings getBindings() { - return bindings; - } - public SceneConfig getConfig() { if(!isInit){ return null; @@ -162,11 +143,6 @@ public class SceneScriptManager { } private void init() { - // Create bindings - bindings = ScriptLoader.getEngine().createBindings(); - // Set variables - bindings.put("ScriptLib", getScriptLib()); - var meta = ScriptLoader.getSceneMeta(getScene().getId()); if (meta == null){ return; @@ -186,14 +162,7 @@ public class SceneScriptManager { } public void loadGroupFromScript(SceneGroup group) { - group.load(getScene().getId(), meta.context); - - try { - // build the trigger for this scene - group.getScript().eval(getBindings()); - } catch (ScriptException e) { - Grasscutter.getLogger().error("Could not build the trigger for this scene", e); - } + group.load(getScene().getId()); group.variables.forEach(var -> this.getVariables().put(var.name, var.value)); this.sceneGroups.put(group.id, group); @@ -284,47 +253,55 @@ public class SceneScriptManager { // Events public void callEvent(int eventType, ScriptArgs params) { - for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { - scriptLib.setCurrentGroup(trigger.currentGroup); - 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); - } + try{ + ScriptLoader.getScriptLib().setSceneScriptManager(this); + for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { + try{ + ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup); - ScriptLib.logger.trace("Call Condition Trigger {}", trigger); - ret = safetyCall(trigger.condition, condition, args); - } - - if (ret.isboolean() && ret.checkboolean()) { - if(trigger.action == null || trigger.action.isEmpty()){ - return; + LuaValue ret = callScriptFunc(trigger.condition, trigger.currentGroup, params); + Grasscutter.getLogger().trace("Call Condition Trigger {}", trigger.condition); + + if (ret.isboolean() && ret.checkboolean()) { + // the SetGroupVariableValueByGroup in tower need the param to record the first stage time + callScriptFunc(trigger.action, trigger.currentGroup, params); + Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.action); + } + //TODO some ret may not bool + + }finally { + ScriptLoader.getScriptLib().removeCurrentGroup(); } - ScriptLib.logger.trace("Call Action Trigger {}", trigger); - LuaValue action = (LuaValue) this.getBindings().get(trigger.action); - // TODO impl the param of SetGroupVariableValueByGroup - var arg = new ScriptArgs(); - arg.param2 = 100; - var args = CoerceJavaToLua.coerce(arg); - safetyCall(trigger.action, action, args); } - //TODO some ret may not bool - scriptLib.removeCurrentGroup(); + }finally { + // make sure it is removed + ScriptLoader.getScriptLib().removeSceneScriptManager(); } } + public LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params){ + LuaValue funcLua = null; + if (funcName != null && !funcName.isEmpty()) { + funcLua = (LuaValue) group.getBindings().get(funcName); + } + + LuaValue ret = LuaValue.TRUE; + + if (funcLua != null) { + LuaValue args = LuaValue.NIL; + + if (params != null) { + args = CoerceJavaToLua.coerce(params); + } + + ret = safetyCall(funcName, funcLua, args); + } + return ret; + } + public LuaValue safetyCall(String name, LuaValue func, LuaValue args){ try{ - return func.call(this.getScriptLibLua(), args); + return func.call(ScriptLoader.getScriptLibLua(), args); }catch (LuaError error){ ScriptLib.logger.error("[LUA] call trigger failed {},{},{}",name,args,error.getMessage()); return LuaValue.valueOf(-1); diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index bea84d8b3..e33deb860 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -10,6 +10,7 @@ import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; +import io.netty.util.concurrent.FastThreadLocal; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.slf4j.Logger; @@ -20,15 +21,24 @@ import java.util.Optional; public class ScriptLib { public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class); - private final SceneScriptManager sceneScriptManager; - - public ScriptLib(SceneScriptManager sceneScriptManager) { - this.sceneScriptManager = sceneScriptManager; - this.currentGroup = new ThreadLocal<>(); + private final FastThreadLocal sceneScriptManager; + private final FastThreadLocal currentGroup; + public ScriptLib() { + this.sceneScriptManager = new FastThreadLocal<>(); + this.currentGroup = new FastThreadLocal<>(); + } + + public void setSceneScriptManager(SceneScriptManager sceneScriptManager){ + this.sceneScriptManager.set(sceneScriptManager); + } + + public void removeSceneScriptManager(){ + this.sceneScriptManager.remove(); } public SceneScriptManager getSceneScriptManager() { - return sceneScriptManager; + // normally not null + return Optional.of(sceneScriptManager.get()).get(); } private String printTable(LuaTable table){ @@ -40,13 +50,11 @@ public class ScriptLib { sb.append("}"); return sb.toString(); } - private final ThreadLocal currentGroup; public void setCurrentGroup(SceneGroup currentGroup){ - logger.debug("current {}", currentGroup); this.currentGroup.set(currentGroup); } public Optional getCurrentGroup(){ - return Optional.ofNullable(this.currentGroup.get()); + return Optional.of(this.currentGroup.get()); } public void removeCurrentGroup(){ this.currentGroup.remove(); diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java index 6795de575..c55a76251 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java @@ -29,6 +29,8 @@ public class ScriptLoader { private static ScriptEngineFactory factory; private static String fileType; private static Serializer serializer; + private static ScriptLib scriptLib; + private static LuaValue scriptLibLua; /** * suggest GC to remove it if the memory is less */ @@ -68,6 +70,10 @@ public class ScriptLoader { ctx.globals.set("EventType", CoerceJavaToLua.coerce(new EventType())); // TODO - make static class to avoid instantiating a new class every scene ctx.globals.set("GadgetState", CoerceJavaToLua.coerce(new ScriptGadgetState())); ctx.globals.set("RegionShape", CoerceJavaToLua.coerce(new ScriptRegionShape())); + + scriptLib = new ScriptLib(); + scriptLibLua = CoerceJavaToLua.coerce(scriptLib); + ctx.globals.set("ScriptLib", scriptLibLua); } public static ScriptEngine getEngine() { @@ -82,6 +88,14 @@ public class ScriptLoader { return serializer; } + public static ScriptLib getScriptLib() { + return scriptLib; + } + + public static LuaValue getScriptLibLua() { + return scriptLibLua; + } + public static Optional tryGet(SoftReference softReference){ try{ return Optional.ofNullable(softReference.get()); diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index d43961f7b..fb67c518f 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -10,12 +10,11 @@ import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import static emu.grasscutter.Configuration.*; +import static emu.grasscutter.Configuration.SCRIPT; @ToString @Setter @@ -40,6 +39,7 @@ public class SceneGroup { private transient boolean loaded; // Not an actual variable in the scripts either private transient CompiledScript script; + private transient Bindings bindings; public boolean isLoaded() { return loaded; @@ -65,13 +65,19 @@ public class SceneGroup { return suites.get(index - 1); } - public SceneGroup load(int sceneId, Bindings bindings){ + public Bindings getBindings() { + return bindings; + } + + public SceneGroup load(int sceneId){ if(loaded){ return this; } // Set flag here so if there is no script, we dont call this function over and over again. setLoaded(true); + this.bindings = ScriptLoader.getEngine().createBindings(); + CompiledScript cs = ScriptLoader.getScriptByPath( SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_group" + id + "." + ScriptLoader.getScriptType())); diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index b262079ee..948be1739 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -87,6 +87,9 @@ public class LuaSerializer implements Serializer { object = (T) constructorCache.get(type).newInstance(); + if(table == null){ + return object; + } LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { From 512021b23477f71bcae0737183d7d3a3d222928f Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 19 May 2022 00:10:02 -0700 Subject: [PATCH 13/24] Fix dataloader with eclipse --- .../java/emu/grasscutter/data/DataLoader.java | 9 ++++--- .../java/emu/grasscutter/utils/FileUtils.java | 27 ++++++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java index dc7e67281..30b6f711b 100644 --- a/src/main/java/emu/grasscutter/data/DataLoader.java +++ b/src/main/java/emu/grasscutter/data/DataLoader.java @@ -7,8 +7,10 @@ import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; import java.io.*; +import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.List; +import java.util.regex.Pattern; import static emu.grasscutter.Configuration.DATA; @@ -46,17 +48,18 @@ public class DataLoader { } public static void CheckAllFiles() { - + String pathSplitter = "defaults" + Pattern.quote(FileSystems.getDefault().getSeparator()) + "data" + Pattern.quote(FileSystems.getDefault().getSeparator()); + try { List filenames = FileUtils.getPathsFromResource("/defaults/data/"); for (Path file : filenames) { - String relativePath = String.valueOf(file).split("/defaults/data/")[1]; + String relativePath = String.valueOf(file).split(pathSplitter)[1]; CheckAndCopyData(relativePath); } } catch (Exception e) { - Grasscutter.getLogger().error("An error occurred while trying to check the data folder. \n" + e); + Grasscutter.getLogger().error("An error occurred while trying to check the data folder. \n", e); } GenerateGachaMappings(); diff --git a/src/main/java/emu/grasscutter/utils/FileUtils.java b/src/main/java/emu/grasscutter/utils/FileUtils.java index 17e680576..5d42dd88f 100644 --- a/src/main/java/emu/grasscutter/utils/FileUtils.java +++ b/src/main/java/emu/grasscutter/utils/FileUtils.java @@ -9,6 +9,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.*; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -76,23 +77,29 @@ public final class FileUtils { // From https://mkyong.com/java/java-read-a-file-from-resources-folder/ public static List getPathsFromResource(String folder) throws URISyntaxException, IOException { - List result; - - // get path of the current running JAR + List result = null; + + // Get path of the current running JAR String jarPath = Grasscutter.class.getProtectionDomain() .getCodeSource() .getLocation() .toURI() .getPath(); - // file walks JAR - URI uri = URI.create("jar:file:" + jarPath); - try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { - result = Files.walk(fs.getPath(folder)) - .filter(Files::isRegularFile) - .collect(Collectors.toList()); + try { + // file walks JAR + URI uri = URI.create("jar:file:" + jarPath); + try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { + result = Files.walk(fs.getPath(folder)) + .filter(Files::isRegularFile) + .collect(Collectors.toList()); + } + } catch (Exception e) { + // Eclipse puts resources in its bin folder + File f = new File(jarPath + "defaults/data/"); + result = Arrays.stream(f.listFiles()).map(File::toPath).toList(); } - + return result; } From cd77fb90a3449f9ccdd8902c93dfa686f4b5347b Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 19 May 2022 00:36:38 -0700 Subject: [PATCH 14/24] Move gadget/monster creation events to after they are spawned in scene --- .../grasscutter/game/entity/EntityGadget.java | 8 +++++++ .../game/entity/EntityMonster.java | 6 ++++++ .../grasscutter/game/entity/GameEntity.java | 21 +++++++++++++++---- .../emu/grasscutter/game/world/Scene.java | 1 + .../scripts/SceneScriptManager.java | 3 --- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index db7e4b80e..099950226 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -28,6 +28,8 @@ 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.scripts.constants.EventType; +import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; @@ -138,6 +140,12 @@ public class EntityGadget extends EntityBaseGadget { public Int2FloatOpenHashMap getFightProperties() { return null; } + + @Override + public void onCreate() { + // Lua event + getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId())); + } @Override public void onDeath(int killerId) { diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index cc1ac1d22..0ccdfef2e 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -109,6 +109,12 @@ public class EntityMonster extends GameEntity { public void setPoseId(int poseId) { this.poseId = poseId; } + + @Override + public void onCreate() { + // Lua event + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId())); + } @Override public void onDeath(int killerId) { diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java index 0cdcc3ebd..515a31cb5 100644 --- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java +++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java @@ -107,10 +107,6 @@ public abstract class GameEntity { public void setLastMoveReliableSeq(int lastMoveReliableSeq) { this.lastMoveReliableSeq = lastMoveReliableSeq; } - - public abstract SceneEntityInfo toProto(); - - public abstract void onDeath(int killerId); public void setFightProperty(FightProperty prop, float value) { this.getFightProperties().put(prop.getId(), value); @@ -219,4 +215,21 @@ public abstract class GameEntity { getScene().killEntity(this, 0); } } + + /** + * Called when this entity is added to the world + */ + public void onCreate() { + + } + + /** + * Called when this entity dies + * @param killerId Entity id of the entity that killed this entity + */ + public void onDeath(int killerId) { + + } + + public abstract SceneEntityInfo toProto(); } diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 2e2b34c7d..0852ffa6c 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -311,6 +311,7 @@ public class Scene { private void addEntityDirectly(GameEntity entity) { getEntities().put(entity.getId(), entity); + entity.onCreate(); // Call entity create event } public synchronized void addEntity(GameEntity entity) { diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index bbb475717..11041dee8 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -371,9 +371,6 @@ public class SceneScriptManager { this.getScriptMonsterSpawnService() .onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); - // Lua event - callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); - return entity; } From 0dac404f0dfeddb71e169c3a080f8857260fc1aa Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 19 May 2022 02:19:18 -0700 Subject: [PATCH 15/24] Fix issue with groups that dont have any suites --- src/main/java/emu/grasscutter/game/world/Scene.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 0852ffa6c..57f11fa69 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -587,10 +587,10 @@ public class Scene { // Load suites int suite = group.init_config.suite; - if (suite == 0) { + if (suite == 0 || group.suites == null || group.suites.size() == 0) { continue; } - + do { var suiteData = group.getSuiteByIndex(suite); suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger); From 8c860308ba0c13b8140ebef13c92bf8905e567e9 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 19 May 2022 02:28:25 -0700 Subject: [PATCH 16/24] Fix bad casting exceptions with scene garbages objects --- .../emu/grasscutter/game/world/Scene.java | 1 + .../scripts/SceneScriptManager.java | 5 +++- .../emu/grasscutter/scripts/ScriptUtils.java | 28 +++++++++++++++++++ .../grasscutter/scripts/data/SceneGroup.java | 16 +++++++++-- .../scripts/serializer/LuaSerializer.java | 15 ++++++++-- 5 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/main/java/emu/grasscutter/scripts/ScriptUtils.java diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 57f11fa69..c1b6a2208 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -599,6 +599,7 @@ public class Scene { .map(g -> scriptManager.createGadget(group.id, group.block_id, g)).toList()); entities.addAll(suiteData.sceneMonsters.stream() .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)).toList()); + suite++; } while (suite < group.init_config.end_suite); } diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 11041dee8..5dd25a1df 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -164,7 +164,10 @@ public class SceneScriptManager { public void loadGroupFromScript(SceneGroup group) { group.load(getScene().getId()); - group.variables.forEach(var -> this.getVariables().put(var.name, var.value)); + if (group.variables != null) { + group.variables.forEach(var -> this.getVariables().put(var.name, var.value)); + } + this.sceneGroups.put(group.id, group); if(group.regions != null){ diff --git a/src/main/java/emu/grasscutter/scripts/ScriptUtils.java b/src/main/java/emu/grasscutter/scripts/ScriptUtils.java new file mode 100644 index 000000000..b79fef9d6 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/ScriptUtils.java @@ -0,0 +1,28 @@ +package emu.grasscutter.scripts; + +import java.util.HashMap; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +import emu.grasscutter.Grasscutter; + +public class ScriptUtils { + + public static HashMap toMap(LuaTable table) { + HashMap map = new HashMap<>(); + LuaValue[] rootKeys = table.keys(); + for (LuaValue k : rootKeys) { + if (table.get(k).istable()) { + map.put(k, toMap(table.get(k).checktable())); + } else { + map.put(k, table.get(k)); + } + } + return map; + } + + public static void print(LuaTable table) { + Grasscutter.getLogger().info(toMap(table).toString()); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index fb67c518f..c0fb9ac78 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -9,6 +9,10 @@ import lombok.ToString; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -106,9 +110,17 @@ public class SceneGroup { suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); - garbages = ScriptLoader.getSerializer().toObject(SceneGarbage.class, bindings.get("garbages")); init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); - + + // Garbages TODO fix properly later + Object garbagesValue = bindings.get("garbages"); + if (garbagesValue != null && garbagesValue instanceof LuaValue garbagesTable) { + garbages = new SceneGarbage(); + if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) { + garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable()); + } + } + // Add variables to suite variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index 948be1739..8e668c48a 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -2,6 +2,9 @@ package emu.grasscutter.scripts.serializer; import com.esotericsoftware.reflectasm.ConstructorAccess; import com.esotericsoftware.reflectasm.MethodAccess; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.ScriptUtils; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Data; @@ -9,6 +12,8 @@ import lombok.experimental.FieldDefaults; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -31,6 +36,10 @@ public class LuaSerializer implements Serializer { public List serializeList(Class type, LuaTable table) { List list = new ArrayList<>(); + if (table == null) { + return list; + } + try { LuaValue[] keys = table.keys(); for (LuaValue k : keys) { @@ -79,7 +88,7 @@ public class LuaSerializer implements Serializer { } try { - if(!methodAccessCache.containsKey(type)){ + if (!methodAccessCache.containsKey(type)) { cacheType(type); } var methodAccess = methodAccessCache.get(type); @@ -87,9 +96,10 @@ public class LuaSerializer implements Serializer { object = (T) constructorCache.get(type).newInstance(); - if(table == null){ + if (table == null) { return object; } + LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { @@ -117,6 +127,7 @@ public class LuaSerializer implements Serializer { } } } catch (Exception e) { + Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString()); e.printStackTrace(); } From 28dda4c30711f288e621564c56b1d8f4c956c56b Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 19 May 2022 02:28:46 -0700 Subject: [PATCH 17/24] Add area_id to SceneObject --- src/main/java/emu/grasscutter/scripts/data/SceneObject.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java index ecf089a5e..f22f530ed 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java @@ -9,6 +9,7 @@ import lombok.ToString; public class SceneObject { public int level; public int config_id; + public int area_id; public Position pos; public Position rot; From 01987279b5da5012d4b2cf46be69ec1a90a989fd Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 19 May 2022 03:00:59 -0700 Subject: [PATCH 18/24] Fix dataloader not getting path correctly --- src/main/java/emu/grasscutter/data/DataLoader.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java index 30b6f711b..b5ed90745 100644 --- a/src/main/java/emu/grasscutter/data/DataLoader.java +++ b/src/main/java/emu/grasscutter/data/DataLoader.java @@ -48,13 +48,11 @@ public class DataLoader { } public static void CheckAllFiles() { - String pathSplitter = "defaults" + Pattern.quote(FileSystems.getDefault().getSeparator()) + "data" + Pattern.quote(FileSystems.getDefault().getSeparator()); - try { List filenames = FileUtils.getPathsFromResource("/defaults/data/"); for (Path file : filenames) { - String relativePath = String.valueOf(file).split(pathSplitter)[1]; + String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; CheckAndCopyData(relativePath); } From 54294698523a8deb7f220331bc5b1dc0433f09ba Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 19 May 2022 03:05:57 -0700 Subject: [PATCH 19/24] Add error message in case data files in resources could not be found --- src/main/java/emu/grasscutter/data/DataLoader.java | 6 +++++- src/main/java/emu/grasscutter/utils/FileUtils.java | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java index b5ed90745..69a17491c 100644 --- a/src/main/java/emu/grasscutter/data/DataLoader.java +++ b/src/main/java/emu/grasscutter/data/DataLoader.java @@ -50,6 +50,10 @@ public class DataLoader { public static void CheckAllFiles() { try { List filenames = FileUtils.getPathsFromResource("/defaults/data/"); + + if (filenames == null) { + Grasscutter.getLogger().error("We were unable to locate your default data files."); + } for (Path file : filenames) { String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; @@ -57,7 +61,7 @@ public class DataLoader { CheckAndCopyData(relativePath); } } catch (Exception e) { - Grasscutter.getLogger().error("An error occurred while trying to check the data folder. \n", e); + Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e); } GenerateGachaMappings(); diff --git a/src/main/java/emu/grasscutter/utils/FileUtils.java b/src/main/java/emu/grasscutter/utils/FileUtils.java index 5d42dd88f..181168aea 100644 --- a/src/main/java/emu/grasscutter/utils/FileUtils.java +++ b/src/main/java/emu/grasscutter/utils/FileUtils.java @@ -9,6 +9,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -97,6 +98,11 @@ public final class FileUtils { } catch (Exception e) { // Eclipse puts resources in its bin folder File f = new File(jarPath + "defaults/data/"); + + if (!f.exists() || f.listFiles().length == 0) { + return null; + } + result = Arrays.stream(f.listFiles()).map(File::toPath).toList(); } From 717c2d1dd7da636cf8e0c29038fd1cccdd58a7f1 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Fri, 20 May 2022 13:46:00 +0800 Subject: [PATCH 20/24] Support Boss Chest --- .../java/emu/grasscutter/data/GameData.java | 5 ++ .../data/def/InvestigationMonsterData.java | 29 ++++++++ .../game/dungeons/DungeonChallenge.java | 7 +- .../grasscutter/game/entity/EntityGadget.java | 13 +++- .../game/entity/gadget/GadgetChest.java | 68 +++++++++++-------- .../game/entity/gadget/GadgetContent.java | 3 +- .../game/entity/gadget/GadgetGatherPoint.java | 3 +- .../entity/gadget/GadgetRewardStatue.java | 3 +- .../game/entity/gadget/GadgetWorktop.java | 3 +- .../chest/BossChestInteractHandler.java | 35 ++++++++++ .../gadget/chest/ChestInteractHandler.java | 11 +++ .../chest/NormalChestInteractHandler.java | 42 ++++++++++++ .../emu/grasscutter/game/player/Player.java | 8 +-- .../game/world/WorldDataManager.java | 40 ++++++++--- .../scripts/SceneScriptManager.java | 32 +++++++-- .../emu/grasscutter/scripts/ScriptLib.java | 39 ++++------- .../scripts/data/SceneBossChest.java | 13 ++++ .../grasscutter/scripts/data/SceneGadget.java | 1 + .../grasscutter/scripts/data/SceneGroup.java | 4 +- .../grasscutter/scripts/data/SceneObject.java | 2 +- .../packet/recv/HandlerGadgetInteractReq.java | 2 +- .../HandlerGetInvestigationMonsterReq.java | 20 ++++++ .../packet/send/PacketGadgetInteractRsp.java | 15 ++-- .../PacketGetInvestigationMonsterRsp.java | 20 ++++++ 24 files changed, 324 insertions(+), 94 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/def/InvestigationMonsterData.java create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/chest/ChestInteractHandler.java create mode 100644 src/main/java/emu/grasscutter/game/entity/gadget/chest/NormalChestInteractHandler.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneBossChest.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerGetInvestigationMonsterReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketGetInvestigationMonsterRsp.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index a87e0e168..e23bb0f32 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -79,6 +79,7 @@ public class GameData { private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerScheduleDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap investigationMonsterDataMap = new Int2ObjectOpenHashMap<>(); // Cache private static Map> fetters = new HashMap<>(); @@ -352,4 +353,8 @@ public class GameData { public static Int2ObjectMap getGatherDataMap() { return gatherDataMap; } + public static Int2ObjectMap getInvestigationMonsterDataMap() { + return investigationMonsterDataMap; + } + } diff --git a/src/main/java/emu/grasscutter/data/def/InvestigationMonsterData.java b/src/main/java/emu/grasscutter/data/def/InvestigationMonsterData.java new file mode 100644 index 000000000..bdfa720fb --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/InvestigationMonsterData.java @@ -0,0 +1,29 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import lombok.Data; + +import java.util.List; + +@ResourceType(name = "InvestigationMonsterConfigData.json") +@Data +public class InvestigationMonsterData extends GameResource { + private int Id; + private int CityId; + private List MonsterIdList; + private List GroupIdList; + private int RewardPreviewId; + private String MapMarkCreateType; + private String MonsterCategory; + + @Override + public int getId() { + return this.Id; + } + + @Override + public void onLoad() { + super.onLoad(); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java index 2b1e992f4..35e1407a8 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java @@ -125,7 +125,9 @@ public class DungeonChallenge { if (this.isSuccess()) { // Call success script event - this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, null); + this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, + // TODO record the time in PARAM2 and used in action + new ScriptArgs().setParam2(100)); // Settle settle(); @@ -139,8 +141,7 @@ public class DungeonChallenge { if(!stage){ getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, - // TODO record the time in PARAM2 and used in action - new ScriptArgs(this.isSuccess() ? 1 : 0, 100)); + new ScriptArgs(this.isSuccess() ? 1 : 0)); } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index 099950226..efd3289ba 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -29,6 +29,7 @@ import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo; import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.utils.Position; @@ -51,6 +52,7 @@ public class EntityGadget extends EntityBaseGadget { private int state; private int pointType; private GadgetContent content; + private SceneGadget metaGadget; public EntityGadget(Scene scene, int gadgetId, Position pos) { super(scene); @@ -99,6 +101,7 @@ public class EntityGadget extends EntityBaseGadget { public void updateState(int state){ this.setState(state); this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state)); + getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId())); } public int getPointType() { @@ -117,7 +120,15 @@ public class EntityGadget extends EntityBaseGadget { public void setContent(GadgetContent content) { this.content = this.content == null ? content : this.content; } - + + public SceneGadget getMetaGadget() { + return metaGadget; + } + + public void setMetaGadget(SceneGadget metaGadget) { + this.metaGadget = metaGadget; + } + // TODO refactor public void buildContent() { if (getContent() != null || getGadgetData() == null || getGadgetData().getType() == null) { diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java index 51dca293f..038b7d6dd 100644 --- a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java @@ -1,53 +1,63 @@ package emu.grasscutter.game.entity.gadget; -import java.util.Random; - import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; +import emu.grasscutter.net.proto.InteractTypeOuterClass; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.scripts.constants.ScriptGadgetState; import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; +import static emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType.INTER_OP_START; + public class GadgetChest extends GadgetContent { public GadgetChest(EntityGadget gadget) { super(gadget); } - public boolean onInteract(Player player) { - var chestRewardMap = getGadget().getScene().getWorld().getServer().getWorldDataManager().getChestRewardMap(); - var chestReward = chestRewardMap.get(getGadget().getGadgetData().getJsonName()); - if (chestReward == null) { - Grasscutter.getLogger().warn("Could not found the config of this type of Chests {}", getGadget().getGadgetData().getJsonName()); + public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) { + var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataManager().getChestInteractHandlerMap(); + var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName()); + if(handler == null){ + Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName()); + return false; + } + + if(opType == INTER_OP_START && handler.isTwoStep()){ + player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_OPEN_CHEST, INTER_OP_START)); + return false; + }else{ + var success = handler.onInteract(this, player); + if (!success){ + return false; + } + + getGadget().updateState(ScriptGadgetState.ChestOpened); + player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_OPEN_CHEST)); return true; } - - player.earnExp(chestReward.getAdvExp()); - player.getInventory().addItem(201, chestReward.getResin()); - - var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5); - player.getInventory().addItem(202, (int)mora); - - for(int i=0;i rewards = new ArrayList<>(); + for (ItemParamData param : reward.getPreviewItems()) { + rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + } + + player.getInventory().addItems(rewards, ActionReason.OpenWorldBossChest); + player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); + + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/chest/ChestInteractHandler.java b/src/main/java/emu/grasscutter/game/entity/gadget/chest/ChestInteractHandler.java new file mode 100644 index 000000000..4dfea36fb --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/chest/ChestInteractHandler.java @@ -0,0 +1,11 @@ +package emu.grasscutter.game.entity.gadget.chest; + +import emu.grasscutter.game.entity.gadget.GadgetChest; +import emu.grasscutter.game.player.Player; + +public interface ChestInteractHandler { + + boolean isTwoStep(); + + boolean onInteract(GadgetChest chest, Player player); +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/chest/NormalChestInteractHandler.java b/src/main/java/emu/grasscutter/game/entity/gadget/chest/NormalChestInteractHandler.java new file mode 100644 index 000000000..32c50691a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/chest/NormalChestInteractHandler.java @@ -0,0 +1,42 @@ +package emu.grasscutter.game.entity.gadget.chest; + +import emu.grasscutter.game.entity.gadget.GadgetChest; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.world.ChestReward; + +import java.util.Random; + +public class NormalChestInteractHandler implements ChestInteractHandler { + private final ChestReward chestReward; + + public NormalChestInteractHandler(ChestReward rewardData){ + this.chestReward = rewardData; + } + + @Override + public boolean isTwoStep() { + return false; + } + + @Override + public boolean onInteract(GadgetChest chest, Player player) { + player.earnExp(chestReward.getAdvExp()); + player.getInventory().addItem(201, chestReward.getResin()); + + var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5); + player.getInventory().addItem(202, (int)mora); + + for(int i=0;i chestRewardMap; + private final Map chestInteractHandlerMap; // chestType-Handler public WorldDataManager(GameServer gameServer){ this.gameServer = gameServer; - this.chestRewardMap = new HashMap<>(); - load(); + this.chestInteractHandlerMap = new HashMap<>(); + loadChestConfig(); } - public synchronized void load(){ + public synchronized void loadChestConfig(){ + // set the special chest first + chestInteractHandlerMap.put("SceneObj_Chest_Flora", new BossChestInteractHandler()); + try(InputStream is = DataLoader.load("ChestReward.json"); InputStreamReader isr = new InputStreamReader(is)) { List chestReward = Grasscutter.getGsonFactory().fromJson( isr, TypeToken.getParameterized(List.class, ChestReward.class).getType()); chestReward.forEach(reward -> - reward.getObjNames().forEach(name -> chestRewardMap.put(name, reward))); + reward.getObjNames().forEach( + name -> chestInteractHandlerMap.putIfAbsent(name, new NormalChestInteractHandler(reward)))); + } catch (Exception e) { Grasscutter.getLogger().error("Unable to load chest reward config.", e); } } - public Map getChestRewardMap() { - return chestRewardMap; + public Map getChestInteractHandlerMap() { + return chestInteractHandlerMap; + } + + public RewardPreviewData getRewardByBossId(int monsterId){ + var investigationMonsterData = GameData.getInvestigationMonsterDataMap().values().parallelStream() + .filter(imd -> imd.getMonsterIdList() != null && !imd.getMonsterIdList().isEmpty()) + .filter(imd -> imd.getMonsterIdList().get(0) == monsterId) + .findFirst(); + + if(investigationMonsterData.isEmpty()){ + return null; + } + return GameData.getRewardPreviewDataMap().get(investigationMonsterData.get().getRewardPreviewId()); } } diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 5dd25a1df..055bffe33 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -14,6 +14,7 @@ import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.*; import emu.grasscutter.scripts.service.ScriptMonsterSpawnService; import emu.grasscutter.scripts.service.ScriptMonsterTideService; +import io.netty.util.concurrent.FastThreadLocalThread; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.luaj.vm2.LuaError; @@ -21,6 +22,10 @@ import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.CoerceJavaToLua; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class SceneScriptManager { private final Scene scene; @@ -43,6 +48,12 @@ public class SceneScriptManager { * blockid - loaded groupSet */ private Int2ObjectMap> loadedGroupSetPerBlock; + public static final ExecutorService eventExecutor; + static { + eventExecutor = new ThreadPoolExecutor(4, 4, + 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100), + FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy()); + } public SceneScriptManager(Scene scene) { this.scene = scene; this.triggers = new HashMap<>(); @@ -211,7 +222,7 @@ public class SceneScriptManager { } var toCreate = gadgets.stream() - .map(g -> createGadget(g.groupId, group.block_id, g)) + .map(g -> createGadget(g.group.id, group.block_id, g)) .filter(Objects::nonNull) .toList(); this.addEntities(toCreate); @@ -254,8 +265,17 @@ public class SceneScriptManager { getScene().addEntity(createMonster(group.id, group.block_id, group.monsters.get(configId))); } // Events - - public void callEvent(int eventType, ScriptArgs params) { + public void callEvent(int eventType, ScriptArgs params){ + /** + * We use ThreadLocal to trans SceneScriptManager context to ScriptLib, to avoid eval script for every groups' trigger in every scene instances. + * But when callEvent is called in a ScriptLib func, it may cause NPE because the inner call cleans the ThreadLocal so that outer call could not get it. + * e.g. CallEvent -> set -> ScriptLib.xxx -> CallEvent -> set -> remove -> NPE -> (remove) + * So we use thread pool to clean the stack to avoid this new issue. + */ + eventExecutor.submit(() -> this.realCallEvent(eventType, params)); + } + + private void realCallEvent(int eventType, ScriptArgs params) { try{ ScriptLoader.getScriptLib().setSceneScriptManager(this); for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { @@ -282,7 +302,7 @@ public class SceneScriptManager { } } - public LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params){ + private LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params){ LuaValue funcLua = null; if (funcName != null && !funcName.isEmpty()) { funcLua = (LuaValue) group.getBindings().get(funcName); @@ -332,10 +352,8 @@ public class SceneScriptManager { entity.getRotation().set(g.rot); entity.setState(g.state); entity.setPointType(g.point_type); + entity.setMetaGadget(g); entity.buildContent(); - - // Lua event - this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId())); return entity; } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index e33deb860..7e67d8862 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -8,7 +8,6 @@ import emu.grasscutter.game.entity.gadget.GadgetWorktop; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; -import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; import io.netty.util.concurrent.FastThreadLocal; import org.luaj.vm2.LuaTable; @@ -16,7 +15,6 @@ import org.luaj.vm2.LuaValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; import java.util.Optional; public class ScriptLib { @@ -68,34 +66,24 @@ public class ScriptLib { if (entity.isEmpty()) { return 1; } - - if (!(entity.get() instanceof EntityGadget)) { - return 1; + + if (entity.get() instanceof EntityGadget entityGadget) { + entityGadget.updateState(gadgetState); + return 0; } - - EntityGadget gadget = (EntityGadget) entity.get(); - gadget.setState(gadgetState); - - getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState)); - return 0; + + return 1; } public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) { logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}", groupId,configId,gadgetState); - List list = getSceneScriptManager().getScene().getEntities().values().stream() - .filter(e -> e.getGroupId() == groupId).toList(); - - for (GameEntity entity : list) { - if (!(entity instanceof EntityGadget)) { - continue; - } - - EntityGadget gadget = (EntityGadget) entity; - gadget.setState(gadgetState); - - getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState)); - } + + getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e.getGroupId() == groupId) + .filter(e -> e instanceof EntityGadget) + .map(e -> (EntityGadget)e) + .forEach(e -> e.updateState(gadgetState)); return 0; } @@ -450,8 +438,9 @@ public class ScriptLib { if (entity instanceof EntityGadget entityGadget) { entityGadget.updateState(state); + return 0; } - return 0; + return 1; } } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBossChest.java b/src/main/java/emu/grasscutter/scripts/data/SceneBossChest.java new file mode 100644 index 000000000..f0009eb05 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBossChest.java @@ -0,0 +1,13 @@ +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@Setter +@ToString +public class SceneBossChest { + public int life_time; + public int monster_config_id; + public int resin; + public int take_num; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index bdf3d55ba..ca37ac953 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -9,4 +9,5 @@ public class SceneGadget extends SceneObject{ public int gadget_id; public int state; public int point_type; + public SceneBossChest boss_chest; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index c0fb9ac78..197b80848 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -98,11 +98,11 @@ public class SceneGroup { // Set monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() .collect(Collectors.toMap(x -> x.config_id, y -> y)); - monsters.values().forEach(m -> m.groupId = id); + monsters.values().forEach(m -> m.group = this); gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")).stream() .collect(Collectors.toMap(x -> x.config_id, y -> y)); - gadgets.values().forEach(m -> m.groupId = id); + gadgets.values().forEach(m -> m.group = this); triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")).stream() .collect(Collectors.toMap(x -> x.name, y -> y)); diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java index f22f530ed..eb1f42d8c 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java @@ -16,5 +16,5 @@ public class SceneObject { /** * not set by lua */ - public transient int groupId; + public transient SceneGroup group; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java index 6c8b6515a..1fb7a641b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java @@ -13,7 +13,7 @@ public class HandlerGadgetInteractReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { GadgetInteractReq req = GadgetInteractReq.parseFrom(payload); - session.getPlayer().interactWith(req.getGadgetEntityId()); + session.getPlayer().interactWith(req.getGadgetEntityId(), req.getOpType()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetInvestigationMonsterReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetInvestigationMonsterReq.java new file mode 100644 index 000000000..e3689a094 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetInvestigationMonsterReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetInvestigationMonsterReqOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetInvestigationMonsterRsp; + +@Opcodes(PacketOpcodes.GetInvestigationMonsterReq) +public class HandlerGetInvestigationMonsterReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = GetInvestigationMonsterReqOuterClass.GetInvestigationMonsterReq.parseFrom(payload); + + session.send(new PacketGetInvestigationMonsterRsp(req.getCityIdListList())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java index c5e5d723e..57e5a8412 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java @@ -4,20 +4,27 @@ import emu.grasscutter.game.entity.EntityBaseGadget; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.GadgetInteractRspOuterClass.GadgetInteractRsp; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.RetcodeOuterClass; public class PacketGadgetInteractRsp extends BasePacket { public PacketGadgetInteractRsp(EntityBaseGadget gadget, InteractType interact) { + this(gadget, interact, null); + } + public PacketGadgetInteractRsp(EntityBaseGadget gadget, InteractType interact, InterOpTypeOuterClass.InterOpType opType) { super(PacketOpcodes.GadgetInteractRsp); - GadgetInteractRsp proto = GadgetInteractRsp.newBuilder() + var proto = GadgetInteractRsp.newBuilder() .setGadgetEntityId(gadget.getId()) .setInteractType(interact) - .setGadgetId(gadget.getGadgetId()) - .build(); + .setGadgetId(gadget.getGadgetId()); + + if(opType != null){ + proto.setOpType(opType); + } - this.setData(proto); + this.setData(proto.build()); } public PacketGadgetInteractRsp() { diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetInvestigationMonsterRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetInvestigationMonsterRsp.java new file mode 100644 index 000000000..d6e921711 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetInvestigationMonsterRsp.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetActivityInfoRspOuterClass; + +import java.util.List; + +public class PacketGetInvestigationMonsterRsp extends BasePacket { + + public PacketGetInvestigationMonsterRsp(List cityIdListList) { + super(PacketOpcodes.GetInvestigationMonsterRsp); + + var resp = GetActivityInfoRspOuterClass.GetActivityInfoRsp.newBuilder(); + + + + this.setData(resp.build()); + } +} From 791b9534b7548800150710d1470c85bc7dd8b712 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Mon, 23 May 2022 20:55:22 +0800 Subject: [PATCH 21/24] refactor the challenge --- .../game/dungeons/DungeonChallenge.java | 180 ------------------ .../dungeons/challenge/DungeonChallenge.java | 91 +++++++++ .../dungeons/challenge/WorldChallenge.java | 132 +++++++++++++ .../challenge/factory/ChallengeFactory.java | 30 +++ .../factory/ChallengeFactoryHandler.java | 10 + .../DungeonChallengeFactoryHandler.java | 33 ++++ .../DungeonGuardChallengeFactoryHandler.java | 34 ++++ .../KillGadgetChallengeFactoryHandler.java | 34 ++++ .../KillMonsterChallengeFactoryHandler.java | 31 +++ .../challenge/trigger/ChallengeTrigger.java | 14 ++ .../challenge/trigger/GuardTrigger.java | 27 +++ .../challenge/trigger/InTimeTrigger.java | 13 ++ .../challenge/trigger/KillGadgetTrigger.java | 23 +++ .../challenge/trigger/KillMonsterTrigger.java | 23 +++ .../grasscutter/game/entity/EntityGadget.java | 24 +-- .../game/entity/EntityMonster.java | 4 +- .../game/entity/gadget/GadgetChest.java | 2 + .../entity/gadget/GadgetRewardStatue.java | 5 +- .../chest/BossChestInteractHandler.java | 5 + .../emu/grasscutter/game/world/Scene.java | 45 +++-- .../scripts/SceneScriptManager.java | 57 ++++-- .../emu/grasscutter/scripts/ScriptLib.java | 121 +++++++++--- .../grasscutter/scripts/data/SceneGadget.java | 1 + .../grasscutter/scripts/data/SceneGroup.java | 3 +- .../scripts/data/SceneMonster.java | 2 + .../send/PacketChallengeDataNotify.java | 6 +- .../PacketDungeonChallengeBeginNotify.java | 9 +- .../PacketDungeonChallengeFinishNotify.java | 8 +- .../send/PacketDungeonSettleNotify.java | 12 +- .../send/PacketLifeStateChangeNotify.java | 14 +- 30 files changed, 713 insertions(+), 280 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonChallengeFactoryHandler.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonGuardChallengeFactoryHandler.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillGadgetChallengeFactoryHandler.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterChallengeFactoryHandler.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillGadgetTrigger.java create mode 100644 src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java deleted file mode 100644 index 35e1407a8..000000000 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java +++ /dev/null @@ -1,180 +0,0 @@ -package emu.grasscutter.game.dungeons; - -import emu.grasscutter.data.common.ItemParamData; -import emu.grasscutter.data.def.DungeonData; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.ScriptArgs; -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.PacketGadgetAutoPickDropInfoNotify; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; - -import java.util.ArrayList; -import java.util.List; - -public class DungeonChallenge { - private final Scene scene; - private final SceneGroup group; - - private int challengeIndex; - private int challengeId; - private boolean success; - private boolean progress; - /** - * has more challenge - */ - private boolean stage; - private int score; - private int objective = 0; - private IntSet rewardedPlayers; - - public DungeonChallenge(Scene scene, SceneGroup group, int challengeId, int challengeIndex, int objective) { - this.scene = scene; - this.group = group; - this.challengeId = challengeId; - this.challengeIndex = challengeIndex; - this.objective = objective; - this.setRewardedPlayers(new IntOpenHashSet()); - } - - 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 int getObjective() { - return objective; - } - - public void setObjective(int objective) { - this.objective = objective; - } - - public boolean isSuccess() { - return success; - } - - public void setSuccess(boolean isSuccess) { - this.success = isSuccess; - } - - public boolean inProgress() { - return progress; - } - - public int getScore() { - return score; - } - - public boolean isStage() { - return stage; - } - - public void setStage(boolean stage) { - this.stage = stage; - } - - public int getTimeLimit() { - return 600; - } - - public IntSet getRewardedPlayers() { - return rewardedPlayers; - } - - public void setRewardedPlayers(IntSet rewardedPlayers) { - this.rewardedPlayers = rewardedPlayers; - } - - 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()) { - // Call success script event - this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, - // TODO record the time in PARAM2 and used in action - new ScriptArgs().setParam2(100)); - - // Settle - settle(); - } else { - this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_FAIL, null); - } - } - - private void settle() { - getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); - - if(!stage){ - getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, - new ScriptArgs(this.isSuccess() ? 1 : 0)); - } - } - - public void onMonsterDie(EntityMonster entity) { - score = getScore() + 1; - - getScene().broadcastPacket(new PacketChallengeDataNotify(this, 1, getScore())); - - if (getScore() >= getObjective() && this.progress) { - this.setSuccess(true); - finish(); - } - } - - public void getStatueDrops(Player player) { - DungeonData dungeonData = getScene().getDungeonData(); - if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) { - return; - } - - // Already rewarded - if (getRewardedPlayers().contains(player.getUid())) { - return; - } - - List rewards = new ArrayList<>(); - for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) { - rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); - } - - player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); - player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); - - getRewardedPlayers().add(player.getUid()); - } -} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java new file mode 100644 index 000000000..f2a485f1d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java @@ -0,0 +1,91 @@ +package emu.grasscutter.game.dungeons.challenge; + +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.def.DungeonData; +import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +import java.util.ArrayList; +import java.util.List; + +public class DungeonChallenge extends WorldChallenge { + + /** + * has more challenge + */ + private boolean stage; + private IntSet rewardedPlayers; + + public DungeonChallenge(Scene scene, SceneGroup group, + int challengeId, int challengeIndex, + List paramList, + int timeLimit, int goal, + List challengeTriggers) { + super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers); + this.setRewardedPlayers(new IntOpenHashSet()); + } + + public boolean isStage() { + return stage; + } + + public void setStage(boolean stage) { + this.stage = stage; + } + + public IntSet getRewardedPlayers() { + return rewardedPlayers; + } + + public void setRewardedPlayers(IntSet rewardedPlayers) { + this.rewardedPlayers = rewardedPlayers; + } + + @Override + public void done() { + super.done(); + if (this.isSuccess()) { + // Settle + settle(); + } + } + + private void settle() { + if(!stage){ + getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); + getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, + new ScriptArgs(this.isSuccess() ? 1 : 0)); + } + } + + public void getStatueDrops(Player player) { + DungeonData dungeonData = getScene().getDungeonData(); + if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) { + return; + } + + // Already rewarded + if (getRewardedPlayers().contains(player.getUid())) { + return; + } + + List rewards = new ArrayList<>(); + for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) { + rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + } + + player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); + player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); + + getRewardedPlayers().add(player.getUid()); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java new file mode 100644 index 000000000..106bd13e8 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java @@ -0,0 +1,132 @@ +package emu.grasscutter.game.dungeons.challenge; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +@Getter +@Setter +public class WorldChallenge { + private final Scene scene; + private final SceneGroup group; + private final int challengeId; + private final int challengeIndex; + private final List paramList; + private final int timeLimit; + private final List challengeTriggers; + private boolean progress; + private boolean success; + private long startedAt; + private int finishedTime; + private final int goal; + private final AtomicInteger score; + + public WorldChallenge(Scene scene, SceneGroup group, + int challengeId, int challengeIndex, List paramList, + int timeLimit, int goal, + List challengeTriggers){ + this.scene = scene; + this.group = group; + this.challengeId = challengeId; + this.challengeIndex = challengeIndex; + this.paramList = paramList; + this.timeLimit = timeLimit; + this.challengeTriggers = challengeTriggers; + this.goal = goal; + this.score = new AtomicInteger(0); + } + public boolean inProgress(){ + return this.progress; + } + public void onCheckTimeOut(){ + if(!inProgress()){ + return; + } + if(timeLimit <= 0){ + return; + } + challengeTriggers.forEach(t -> t.onCheckTimeout(this)); + } + public void start(){ + if(inProgress()){ + Grasscutter.getLogger().info("Could not start a in progress challenge."); + return; + } + this.progress = true; + this.startedAt = System.currentTimeMillis(); + getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); + challengeTriggers.forEach(t -> t.onBegin(this)); + } + + public void done(){ + if(!inProgress()){ + return; + } + finish(true); + this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, + // TODO record the time in PARAM2 and used in action + new ScriptArgs().setParam2(finishedTime)); + + challengeTriggers.forEach(t -> t.onFinish(this)); + } + + public void fail(){ + if(!inProgress()){ + return; + } + finish(false); + this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_FAIL, null); + challengeTriggers.forEach(t -> t.onFinish(this)); + } + + private void finish(boolean success){ + this.progress = false; + this.success = success; + this.finishedTime = (int)((System.currentTimeMillis() - this.startedAt) / 1000L); + getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); + } + + public int increaseScore(){ + return score.incrementAndGet(); + } + public void onMonsterDeath(EntityMonster monster){ + if(!inProgress()){ + return; + } + if(monster.getGroupId() != getGroup().id){ + return; + } + this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster)); + } + public void onGadgetDeath(EntityGadget gadget){ + if(!inProgress()){ + return; + } + if(gadget.getGroupId() != getGroup().id){ + return; + } + this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget)); + } + + public void onGadgetDamage(EntityGadget gadget){ + if(!inProgress()){ + return; + } + if(gadget.getGroupId() != getGroup().id){ + return; + } + this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget)); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java new file mode 100644 index 000000000..fd1662de6 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java @@ -0,0 +1,30 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.ArrayList; +import java.util.List; + +public class ChallengeFactory { + + private static final List challengeFactoryHandlers = new ArrayList<>(); + + static { + challengeFactoryHandlers.add(new DungeonChallengeFactoryHandler()); + challengeFactoryHandlers.add(new DungeonGuardChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillGadgetChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillMonsterChallengeFactoryHandler()); + } + + public static WorldChallenge getChallenge(int param1, int param2, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group){ + for(var handler : challengeFactoryHandlers){ + if(!handler.isThisType(param1, param2, param3, param4, param5, param6, scene, group)){ + continue; + } + return handler.build(param1, param2, param3, param4, param5, param6, scene, group); + } + return null; + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java new file mode 100644 index 000000000..a91d55a28 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java @@ -0,0 +1,10 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +public interface ChallengeFactoryHandler { + boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group); + WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group); +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonChallengeFactoryHandler.java new file mode 100644 index 000000000..8357c9642 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonChallengeFactoryHandler.java @@ -0,0 +1,33 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class DungeonChallengeFactoryHandler implements ChallengeFactoryHandler{ + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // ActiveChallenge with 1,1000,300,233101003,15,0 + return scene.getSceneType() == SceneType.SCENE_DUNGEON + && param4 == group.id; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + var realGroup = scene.getScriptManager().getGroupById(param4); + return new DungeonChallenge( + scene, realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(param5, param3), + param3, // Limit + param5, // Goal + List.of(new InTimeTrigger(), new KillMonsterTrigger())); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonGuardChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonGuardChallengeFactoryHandler.java new file mode 100644 index 000000000..3ea68f114 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonGuardChallengeFactoryHandler.java @@ -0,0 +1,34 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class DungeonGuardChallengeFactoryHandler implements ChallengeFactoryHandler{ + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // ActiveChallenge with 1,188,234101003,12,3030,0 + return scene.getSceneType() == SceneType.SCENE_DUNGEON + && param3 == group.id; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + var realGroup = scene.getScriptManager().getGroupById(param3); + return new DungeonChallenge( + scene, realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(param4, 0), + 0, // Limit + param5, // Goal + List.of(new GuardTrigger())); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillGadgetChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillGadgetChallengeFactoryHandler.java new file mode 100644 index 000000000..4e075f2d3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillGadgetChallengeFactoryHandler.java @@ -0,0 +1,34 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.factory.ChallengeFactoryHandler; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillGadgetTrigger; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class KillGadgetChallengeFactoryHandler implements ChallengeFactoryHandler { + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // kill gadgets(explosive barrel) in time + // ActiveChallenge with 56,201,20,2,201,4 + // open chest in time + // ActiveChallenge with 666,202,30,7,202,1 + return challengeId == 201 || challengeId == 202; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + return new WorldChallenge( + scene, group, + challengeId, // Id + challengeIndex, // Index + List.of(param3, param6, 0), + param3, // Limit + param6, // Goal + List.of(new InTimeTrigger(), new KillGadgetTrigger()) + ); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterChallengeFactoryHandler.java new file mode 100644 index 000000000..b2285ee3e --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterChallengeFactoryHandler.java @@ -0,0 +1,31 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class KillMonsterChallengeFactoryHandler implements ChallengeFactoryHandler{ + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // ActiveChallenge with 180,180,45,133108061,1,0 + return challengeId == 180; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + var realGroup = scene.getScriptManager().getGroupById(param4); + return new WorldChallenge( + scene, realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(param5, param3), + param3, // Limit + param5, // Goal + List.of(new KillMonsterTrigger(), new InTimeTrigger()) + ); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java new file mode 100644 index 000000000..b0b174f8d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java @@ -0,0 +1,14 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; + +public abstract class ChallengeTrigger { + public void onBegin(WorldChallenge challenge){} + public void onFinish(WorldChallenge challenge){} + public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster){} + public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget){} + public void onCheckTimeout(WorldChallenge challenge){} + public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget){} +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java new file mode 100644 index 000000000..4b9bc9e43 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java @@ -0,0 +1,27 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class GuardTrigger extends KillMonsterTrigger{ + @Override + public void onBegin(WorldChallenge challenge) { + super.onBegin(challenge); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100)); + } + + @Override + public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { + var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId()); + var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId()); + int percent = (int) (curHp / maxHp); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent)); + + if(percent <= 0){ + challenge.fail(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java new file mode 100644 index 000000000..abb51c512 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java @@ -0,0 +1,13 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; + +public class InTimeTrigger extends ChallengeTrigger{ + @Override + public void onCheckTimeout(WorldChallenge challenge) { + var current = System.currentTimeMillis(); + if(current - challenge.getStartedAt() > challenge.getTimeLimit() * 1000L){ + challenge.fail(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillGadgetTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillGadgetTrigger.java new file mode 100644 index 000000000..87a1997d4 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillGadgetTrigger.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class KillGadgetTrigger extends ChallengeTrigger{ + @Override + public void onBegin(WorldChallenge challenge) { + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, challenge.getScore().get())); + } + + @Override + public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) { + var newScore = challenge.increaseScore(); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, newScore)); + + if(newScore >= challenge.getGoal()){ + challenge.done(); + } + + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java new file mode 100644 index 000000000..dcde43049 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class KillMonsterTrigger extends ChallengeTrigger{ + @Override + public void onBegin(WorldChallenge challenge) { + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get())); + } + + @Override + public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { + var newScore = challenge.increaseScore(); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, newScore)); + + if(newScore >= challenge.getGoal()){ + challenge.done(); + } + + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index efd3289ba..22d3759cc 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -1,18 +1,10 @@ package emu.grasscutter.game.entity; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.GadgetData; -import emu.grasscutter.game.entity.gadget.GadgetChest; -import emu.grasscutter.game.entity.gadget.GadgetContent; -import emu.grasscutter.game.entity.gadget.GadgetGatherPoint; -import emu.grasscutter.game.entity.gadget.GadgetRewardStatue; -import emu.grasscutter.game.entity.gadget.GadgetWorktop; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.EntityIdType; -import emu.grasscutter.game.props.EntityType; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.data.def.GadgetPropData; +import emu.grasscutter.game.entity.gadget.*; +import emu.grasscutter.game.props.*; import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; @@ -32,6 +24,7 @@ import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; +import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; @@ -160,7 +153,10 @@ public class EntityGadget extends EntityBaseGadget { @Override public void onDeath(int killerId) { - + if(getScene().getChallenge() != null){ + getScene().getChallenge().onGadgetDeath(this); + } + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId())); } @Override @@ -203,4 +199,8 @@ public class EntityGadget extends EntityBaseGadget { return entityInfo.build(); } + public void die() { + getScene().broadcastPacket(new PacketLifeStateChangeNotify(this, LifeState.LIFE_DEAD)); + this.onDeath(0); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index 0ccdfef2e..851f0e2ad 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -122,8 +122,8 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } // first set the challenge data - if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { - getScene().getChallenge().onMonsterDie(this); + if (getScene().getChallenge() != null) { + getScene().getChallenge().onMonsterDeath(this); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){ diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java index 038b7d6dd..6e2cdf72b 100644 --- a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java @@ -38,6 +38,8 @@ public class GadgetChest extends GadgetContent { getGadget().updateState(ScriptGadgetState.ChestOpened); player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_OPEN_CHEST)); + // let the chest disappear + getGadget().die(); return true; } } diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java index 7e606d4ca..69a25b0a5 100644 --- a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java @@ -1,5 +1,6 @@ package emu.grasscutter.game.entity.gadget; +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.proto.InterOpTypeOuterClass; @@ -14,8 +15,8 @@ public class GadgetRewardStatue extends GadgetContent { } public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) { - if (player.getScene().getChallenge() != null) { - player.getScene().getChallenge().getStatueDrops(player); + if (player.getScene().getChallenge() != null && player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) { + dungeonChallenge.getStatueDrops(player); } player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_OPEN_STATUE)); diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java b/src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java index 39f0a88fd..491bfd9bd 100644 --- a/src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java +++ b/src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java @@ -1,5 +1,6 @@ package emu.grasscutter.game.entity.gadget.chest; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.game.entity.gadget.GadgetChest; import emu.grasscutter.game.inventory.GameItem; @@ -22,6 +23,10 @@ public class BossChestInteractHandler implements ChestInteractHandler{ var monster = chest.getGadget().getMetaGadget().group.monsters.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id); var reward = worldDataManager.getRewardByBossId(monster.monster_id); + if(reward == null){ + Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.monster_id); + return false; + } List rewards = new ArrayList<>(); for (ItemParamData param : reward.getPreviewItems()) { rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index c1b6a2208..3d5c85a00 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -4,7 +4,6 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.def.*; -import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.entity.*; import emu.grasscutter.game.player.Player; @@ -14,6 +13,7 @@ import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; @@ -32,8 +32,6 @@ import org.danilopianini.util.SpatialIndex; import java.util.*; import java.util.stream.Collectors; -import static emu.grasscutter.utils.Language.translate; - public class Scene { private final World world; private final SceneData sceneData; @@ -51,7 +49,7 @@ public class Scene { private int weather; private SceneScriptManager scriptManager; - private DungeonChallenge challenge; + private WorldChallenge challenge; private List dungeonSettleListeners; private DungeonData dungeonData; private int prevScene; // Id of the previous scene @@ -199,11 +197,11 @@ public class Scene { this.dungeonData = dungeonData; } - public DungeonChallenge getChallenge() { + public WorldChallenge getChallenge() { return challenge; } - public void setChallenge(DungeonChallenge challenge) { + public void setChallenge(WorldChallenge challenge) { this.challenge = challenge; } @@ -353,7 +351,14 @@ public class Scene { this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType)); } } - + public synchronized void removeEntities(List entity, VisionType visionType) { + var toRemove = entity.stream() + .map(this::removeEntityDirectly) + .toList(); + if (toRemove.size() > 0) { + this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, visionType)); + } + } public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) { this.removeEntityDirectly(oldEntity); this.addEntityDirectly(newEntity); @@ -418,6 +423,10 @@ public class Scene { } // Triggers this.scriptManager.checkRegions(); + + if(challenge != null){ + challenge.onCheckTimeOut(); + } } // TODO - Test @@ -590,21 +599,21 @@ public class Scene { if (suite == 0 || group.suites == null || group.suites.size() == 0) { continue; } - - do { - var suiteData = group.getSuiteByIndex(suite); - suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger); - entities.addAll(suiteData.sceneGadgets.stream() - .map(g -> scriptManager.createGadget(group.id, group.block_id, g)).toList()); - entities.addAll(suiteData.sceneMonsters.stream() - .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)).toList()); - - suite++; - } while (suite < group.init_config.end_suite); + // just load the 'init' suite, avoid spawn the suite added by AddExtraGroupSuite etc. + var suiteData = group.getSuiteByIndex(suite); + suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger); + + entities.addAll(suiteData.sceneGadgets.stream() + .map(g -> scriptManager.createGadget(group.id, group.block_id, g)).toList()); + entities.addAll(suiteData.sceneMonsters.stream() + .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)).toList()); + } scriptManager.meetEntities(entities); + //scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null); + //groups.forEach(g -> scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null)); Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size()); } diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 055bffe33..ef802dde9 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -26,16 +26,13 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; public class SceneScriptManager { private final Scene scene; private final Map variables; private SceneMeta meta; private boolean isInit; - /** - * SceneTrigger Set - */ - private final Map triggers; /** * current triggers controlled by RefreshGroup */ @@ -51,12 +48,11 @@ public class SceneScriptManager { public static final ExecutorService eventExecutor; static { eventExecutor = new ThreadPoolExecutor(4, 4, - 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100), + 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy()); } public SceneScriptManager(Scene scene) { this.scene = scene; - this.triggers = new HashMap<>(); this.currentTriggers = new Int2ObjectOpenHashMap<>(); this.regions = new Int2ObjectOpenHashMap<>(); @@ -96,13 +92,16 @@ public class SceneScriptManager { public Set getTriggersByEvent(int eventId) { return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>()); } + public void registerTrigger(List triggers) { + triggers.forEach(this::registerTrigger); + } public void registerTrigger(SceneTrigger trigger) { - this.triggers.put(trigger.name, trigger); getTriggersByEvent(trigger.event).add(trigger); } - + public void deregisterTrigger(List triggers) { + triggers.forEach(this::deregisterTrigger); + } public void deregisterTrigger(SceneTrigger trigger) { - this.triggers.remove(trigger.name); getTriggersByEvent(trigger.event).remove(trigger); } public void resetTriggers(int eventId) { @@ -205,7 +204,17 @@ public class SceneScriptManager { } } } - + + public void addGroupSuite(SceneGroup group, SceneSuite suite){ + spawnMonstersInGroup(group, suite); + spawnGadgetsInGroup(group, suite); + registerTrigger(suite.sceneTriggers); + } + public void removeGroupSuite(SceneGroup group, SceneSuite suite){ + removeMonstersInGroup(group, suite); + removeGadgetsInGroup(group, suite); + deregisterTrigger(suite.sceneTriggers); + } public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) { spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex)); } @@ -241,7 +250,6 @@ public class SceneScriptManager { } this.addEntities(suite.sceneMonsters.stream() .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); - } public void spawnMonstersInGroup(SceneGroup group) { @@ -326,7 +334,7 @@ public class SceneScriptManager { try{ return func.call(ScriptLoader.getScriptLibLua(), args); }catch (LuaError error){ - ScriptLib.logger.error("[LUA] call trigger failed {},{},{}",name,args,error.getMessage()); + ScriptLib.logger.error("[LUA] call trigger failed {},{}",name,args,error); return LuaValue.valueOf(-1); } } @@ -388,6 +396,7 @@ public class SceneScriptManager { entity.setGroupId(groupId); entity.setBlockId(blockId); entity.setConfigId(monster.config_id); + entity.setPoseId(monster.pose_id); this.getScriptMonsterSpawnService() .onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); @@ -410,4 +419,28 @@ public class SceneScriptManager { public PhTree getBlocksIndex() { return meta.sceneBlockIndex; } + public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneMonsters.stream() + .map(m -> m.config_id) + .collect(Collectors.toSet()); + var toRemove = getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_MISS); + } + public void removeGadgetsInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneGadgets.stream() + .map(m -> m.config_id) + .collect(Collectors.toSet()); + var toRemove = getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityGadget) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_MISS); + } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 7e67d8862..631250c90 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -1,10 +1,11 @@ package emu.grasscutter.scripts; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.gadget.GadgetWorktop; +import emu.grasscutter.game.dungeons.challenge.factory.ChallengeFactory; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; @@ -159,48 +160,102 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } // avoid spawn wrong monster if(getSceneScriptManager().getScene().getChallenge() != null) if(!getSceneScriptManager().getScene().getChallenge().inProgress() || getSceneScriptManager().getScene().getChallenge().getGroup().id != groupId){ return 0; } - this.getSceneScriptManager().spawnMonstersInGroup(group, suite); - + this.getSceneScriptManager().addGroupSuite(group, suiteData); + + return 0; + } + public int GoToGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call GoToGroupSuite with {},{}", + groupId,suite); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + if (group == null || group.monsters == null) { + return 1; + } + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } + + for(var suiteItem : group.suites){ + if(suiteData == suiteItem){ + continue; + } + this.getSceneScriptManager().removeGroupSuite(group, suiteItem); + } + this.getSceneScriptManager().addGroupSuite(group, suiteData); + + return 0; + } + public int RemoveExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call RemoveExtraGroupSuite with {},{}", + groupId,suite); + + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + if (group == null || group.monsters == null) { + return 1; + } + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } + + this.getSceneScriptManager().removeGroupSuite(group, suiteData); + + return 0; + } + public int KillExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call KillExtraGroupSuite with {},{}", + groupId,suite); + + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + if (group == null || group.monsters == null) { + return 1; + } + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } + + this.getSceneScriptManager().removeGroupSuite(group, suiteData); + return 0; } - // param3 (probably time limit for timed dungeons) public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) { logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}", challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5); - SceneGroup group = getSceneScriptManager().getGroupById(groupId); - var objective = objectiveKills; + var challenge = ChallengeFactory.getChallenge( + challengeId, + challengeIndex, + timeLimitOrGroupId, + groupId, + objectiveKills, + param5, + getSceneScriptManager().getScene(), + getCurrentGroup().get() + ); - if(group == null){ - group = getSceneScriptManager().getGroupById(timeLimitOrGroupId); - objective = groupId; - } - - if (group == null || group.monsters == null) { + if(challenge == null){ return 1; } - if(getSceneScriptManager().getScene().getChallenge() != null && - getSceneScriptManager().getScene().getChallenge().inProgress()) - { - return 0; + if(challenge instanceof DungeonChallenge dungeonChallenge){ + // set if tower first stage (6-1) + dungeonChallenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0); } - DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), - group, challengeId, challengeIndex, objective); - // set if tower first stage (6-1) - challenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0); - getSceneScriptManager().getScene().setChallenge(challenge); - challenge.start(); return 0; } @@ -257,7 +312,7 @@ public class ScriptLib { public int GetRegionEntityCount(LuaTable table) { logger.debug("[LUA] Call GetRegionEntityCount with {}", - table); + printTable(table)); int regionId = table.get("region_eid").toint(); int entityType = table.get("entity_type").toint(); @@ -280,9 +335,8 @@ public class ScriptLib { // TODO record time return 0; } - public int GetGroupMonsterCount(int var1){ - logger.debug("[LUA] Call GetGroupMonsterCount with {}", - var1); + public int GetGroupMonsterCount(){ + logger.debug("[LUA] Call GetGroupMonsterCount "); return (int) getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e instanceof EntityMonster && @@ -328,7 +382,7 @@ public class ScriptLib { var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint()); if(entity == null){ - return 1; + return 0; } getSceneScriptManager().getScene().killEntity(entity, 0); return 0; @@ -398,8 +452,13 @@ public class ScriptLib { public int GetGadgetStateByConfigId(int groupId, int configId){ logger.debug("[LUA] Call GetGadgetStateByConfigId with {},{}", groupId, configId); + + if(groupId == 0){ + groupId = getCurrentGroup().get().id; + } + final int realGroupId = groupId; var gadget = getSceneScriptManager().getScene().getEntities().values().stream() - .filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == groupId) + .filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == realGroupId) .filter(g -> g.getConfigId() == configId) .findFirst(); if(gadget.isEmpty()){ @@ -409,8 +468,8 @@ public class ScriptLib { } public int MarkPlayerAction(int var1, int var2, int var3, int var4){ - logger.debug("[LUA] Call MarkPlayerAction with {},{}", - var1, var2); + logger.debug("[LUA] Call MarkPlayerAction with {},{},{},{}", + var1, var2,var3,var4); return 0; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index ca37ac953..3b9f2bf7c 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -10,4 +10,5 @@ public class SceneGadget extends SceneObject{ public int state; public int point_type; public SceneBossChest boss_chest; + public int interact_id; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 197b80848..9275706a4 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -73,7 +73,7 @@ public class SceneGroup { return bindings; } - public SceneGroup load(int sceneId){ + public synchronized SceneGroup load(int sceneId){ if(loaded){ return this; } @@ -118,6 +118,7 @@ public class SceneGroup { garbages = new SceneGarbage(); if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) { garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable()); + garbages.gadgets.forEach(m -> m.group = this); } } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index 02435d800..070a87fee 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -7,4 +7,6 @@ import lombok.ToString; @Setter public class SceneMonster extends SceneObject{ public int monster_id; + public int pose_id; + public int drop_id; } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java index 452ca7118..2a133149d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java @@ -1,13 +1,13 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; 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) { + + public PacketChallengeDataNotify(WorldChallenge challenge, int index, int value) { super(PacketOpcodes.ChallengeDataNotify); ChallengeDataNotify proto = ChallengeDataNotify.newBuilder() diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java index 0c52c9d04..c02f41119 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java @@ -1,23 +1,22 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; 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) { + public PacketDungeonChallengeBeginNotify(WorldChallenge challenge) { super(PacketOpcodes.DungeonChallengeBeginNotify, true); DungeonChallengeBeginNotify proto = DungeonChallengeBeginNotify.newBuilder() .setChallengeId(challenge.getChallengeId()) .setChallengeIndex(challenge.getChallengeIndex()) .setGroupId(challenge.getGroup().id) - .addParamList(challenge.getObjective()) - .addParamList(challenge.getTimeLimit()) + .addAllParamList(challenge.getParamList()) .build(); - + this.setData(proto); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java index a44c16778..22cd862cf 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java @@ -1,13 +1,13 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; 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) { + + public PacketDungeonChallengeFinishNotify(WorldChallenge challenge) { super(PacketOpcodes.DungeonChallengeFinishNotify, true); DungeonChallengeFinishNotify proto = DungeonChallengeFinishNotify.newBuilder() @@ -15,7 +15,7 @@ public class PacketDungeonChallengeFinishNotify extends BasePacket { .setIsSuccess(challenge.isSuccess()) .setUnk1(2) .build(); - + this.setData(proto); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java index 56d844d8d..5e214b219 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java @@ -1,6 +1,6 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.DungeonSettleNotifyOuterClass.DungeonSettleNotify; @@ -9,7 +9,7 @@ import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNoti public class PacketDungeonSettleNotify extends BasePacket { - public PacketDungeonSettleNotify(DungeonChallenge challenge) { + public PacketDungeonSettleNotify(WorldChallenge challenge) { super(PacketOpcodes.DungeonSettleNotify); DungeonSettleNotify proto = DungeonSettleNotify.newBuilder() @@ -22,10 +22,10 @@ public class PacketDungeonSettleNotify extends BasePacket { this.setData(proto); } - public PacketDungeonSettleNotify(DungeonChallenge challenge, - boolean canJump, - boolean hasNextLevel, - int nextFloorId + public PacketDungeonSettleNotify(WorldChallenge challenge, + boolean canJump, + boolean hasNextLevel, + int nextFloorId ) { super(PacketOpcodes.DungeonSettleNotify); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java index 75685a27e..37545d8ea 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java @@ -1,17 +1,23 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.LifeStateChangeNotifyOuterClass.LifeStateChangeNotify; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; -import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff; - -import java.util.ArrayList; public class PacketLifeStateChangeNotify extends BasePacket { + public PacketLifeStateChangeNotify(GameEntity target, LifeState lifeState) { + super(PacketOpcodes.LifeStateChangeNotify); + + LifeStateChangeNotify proto = LifeStateChangeNotify.newBuilder() + .setEntityId(target.getId()) + .setLifeState(lifeState.getValue()) + .build(); + + this.setData(proto); + } public PacketLifeStateChangeNotify(GameEntity attacker, GameEntity target, LifeState lifeState) { super(PacketOpcodes.LifeStateChangeNotify); From 5a4a708931f7f973beeff4c1e51f1375f58c0164 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Mon, 23 May 2022 21:00:41 +0800 Subject: [PATCH 22/24] remove unused imports --- .../emu/grasscutter/game/entity/EntityGadget.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index 22d3759cc..d4ddf5fd4 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -2,13 +2,14 @@ package emu.grasscutter.game.entity; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.GadgetData; -import emu.grasscutter.data.def.GadgetPropData; import emu.grasscutter.game.entity.gadget.*; -import emu.grasscutter.game.props.*; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.EntityType; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; -import emu.grasscutter.net.proto.BossChestInfoOuterClass; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; @@ -19,7 +20,6 @@ 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.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.scripts.data.ScriptArgs; @@ -28,13 +28,8 @@ import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; import lombok.ToString; -import java.util.Arrays; -import java.util.Random; - @ToString(callSuper = true) public class EntityGadget extends EntityBaseGadget { private final GadgetData data; From d95708ec032fe9e45be4e718861b3316710cd3f2 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Wed, 25 May 2022 10:44:46 +0800 Subject: [PATCH 23/24] Support spawn NPC --- .../java/emu/grasscutter/data/GameData.java | 13 ++- .../emu/grasscutter/data/ResourceLoader.java | 36 +++++++-- .../data/custom/SceneNpcBornData.java | 28 +++++++ .../data/custom/SceneNpcBornEntry.java | 19 +++++ .../grasscutter/game/entity/EntityNPC.java | 81 +++++++++++++++++++ .../emu/grasscutter/game/world/Scene.java | 62 ++++++++++++-- .../scripts/SceneIndexManager.java | 3 +- .../scripts/SceneScriptManager.java | 20 +++-- .../grasscutter/scripts/data/SceneBlock.java | 12 ++- .../grasscutter/scripts/data/SceneGroup.java | 11 ++- .../grasscutter/scripts/data/SceneNPC.java | 10 +++ .../packet/send/PacketGroupSuiteNotify.java | 25 ++++++ 12 files changed, 288 insertions(+), 32 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java create mode 100644 src/main/java/emu/grasscutter/data/custom/SceneNpcBornEntry.java create mode 100644 src/main/java/emu/grasscutter/game/entity/EntityNPC.java create mode 100644 src/main/java/emu/grasscutter/scripts/data/SceneNPC.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index e23bb0f32..21246c25a 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -7,12 +7,8 @@ import java.util.List; import java.util.Map; import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.custom.*; import emu.grasscutter.utils.Utils; -import emu.grasscutter.data.custom.AbilityEmbryoEntry; -import emu.grasscutter.data.custom.AbilityModifierEntry; -import emu.grasscutter.data.custom.OpenConfigEntry; -import emu.grasscutter.data.custom.MainQuestData; -import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.*; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -28,7 +24,8 @@ public class GameData { private static final Map openConfigEntries = new HashMap<>(); private static final Map scenePointEntries = new HashMap<>(); private static final Int2ObjectMap mainQuestData = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap npcBornData = new Int2ObjectOpenHashMap<>(); + // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -131,7 +128,9 @@ public class GameData { public static Int2ObjectMap getMainQuestDataMap() { return mainQuestData; } - + public static Int2ObjectMap getSceneNpcBornData() { + return npcBornData; + } public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 6fe9b19fb..9d15fc61b 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -1,13 +1,19 @@ package emu.grasscutter.data; import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; +import ch.ethz.globis.phtree.PhTree; +import ch.ethz.globis.phtree.v16.PhTree16; import com.google.gson.Gson; +import emu.grasscutter.data.custom.*; import emu.grasscutter.utils.Utils; +import lombok.SneakyThrows; import org.reflections.Reflections; import com.google.gson.JsonElement; @@ -16,15 +22,9 @@ import com.google.gson.reflect.TypeToken; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.common.PointData; import emu.grasscutter.data.common.ScenePointConfig; -import emu.grasscutter.data.custom.AbilityEmbryoEntry; -import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifier.AbilityConfigData; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; -import emu.grasscutter.data.custom.AbilityModifierEntry; -import emu.grasscutter.data.custom.OpenConfigEntry; -import emu.grasscutter.data.custom.MainQuestData; -import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -65,6 +65,7 @@ public class ResourceLoader { loadQuests(); // Load scene points - must be done AFTER resources are loaded loadScenePoints(); + loadNpcBornData(); // Custom - TODO move this somewhere else try { GameData.getAvatarSkillDepotDataMap().get(504).setAbilities( @@ -418,6 +419,29 @@ public class ResourceLoader { Grasscutter.getLogger().info("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); } + @SneakyThrows + private static void loadNpcBornData(){ + var folder = Files.list(Path.of(RESOURCE("BinOutput/Scene/SceneNpcBorn"))).toList(); + + for(var file : folder){ + if(file.toFile().isDirectory()){ + continue; + } + + PhTree index = new PhTree16<>(3); + + var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class); + if(data.getBornPosList() == null || data.getBornPosList().size() == 0){ + continue; + } + data.getBornPosList().forEach(item -> index.put(item.getPos().toLongArray(), item)); + + data.setIndex(index); + GameData.getSceneNpcBornData().put(data.getSceneId(), data); + } + + Grasscutter.getLogger().info("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas."); + } // BinOutput configs private static class AvatarConfig { diff --git a/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java new file mode 100644 index 000000000..7aab358f2 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java @@ -0,0 +1,28 @@ +package emu.grasscutter.data.custom; + +import ch.ethz.globis.phtree.PhTree; +import emu.grasscutter.scripts.data.SceneGroup; +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class SceneNpcBornData { + int sceneId; + List bornPosList; + + /** + * Spatial Index For NPC + */ + transient PhTree index; + + /** + * npc groups + */ + transient Map groups = new ConcurrentHashMap<>(); +} diff --git a/src/main/java/emu/grasscutter/data/custom/SceneNpcBornEntry.java b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornEntry.java new file mode 100644 index 000000000..1810c94bf --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornEntry.java @@ -0,0 +1,19 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.utils.Position; +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.util.List; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class SceneNpcBornEntry { + int id; + int configId; + Position pos; + Position rot; + int groupId; + List suiteIdList; +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityNPC.java b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java new file mode 100644 index 000000000..213951ed3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java @@ -0,0 +1,81 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.*; +import emu.grasscutter.scripts.data.SceneNPC; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; + +public class EntityNPC extends GameEntity{ + + private final Position position; + private final Position rotation; + private final SceneNPC metaNpc; + private final int suiteId; + + public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) { + super(scene); + this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC); + setConfigId(metaNPC.config_id); + setGroupId(metaNPC.group.id); + setBlockId(blockId); + this.suiteId = suiteId; + this.position = metaNPC.pos.clone(); + this.rotation = metaNPC.rot.clone(); + this.metaNpc = metaNPC; + + } + + @Override + public Int2FloatOpenHashMap getFightProperties() { + return null; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public Position getRotation() { + return rotation; + } + + public int getSuiteId() { + return suiteId; + } + + @Override + public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { + + EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority = EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo(EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder()) + .setAiInfo(SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder() + .setIsAiOpen(true) + .setBornPos(getPosition().toProto())) + .setBornPos(getPosition().toProto()) + .build(); + + SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo = SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_NPC) + .setMotionInfo(MotionInfoOuterClass.MotionInfo.newBuilder() + .setPos(getPosition().toProto()) + .setRot(getRotation().toProto()) + .setSpeed(VectorOuterClass.Vector.newBuilder())) + .addAnimatorParaList(AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair.newBuilder()) + .setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder()) + .setEntityAuthorityInfo(authority) + .setLifeState(1); + + + entityInfo.setNpc(SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder() + .setNpcId(metaNpc.npc_id) + .setBlockId(getBlockId()) + .build()); + + return entityInfo.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 3d5c85a00..44c10b10c 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -376,7 +376,7 @@ public class Scene { } entities.add(entity); } - + player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_MEET)); } @@ -535,6 +535,9 @@ public class Scene { .toList(); onLoadGroup(toLoad); } + for (Player player : this.getPlayers()) { + getScriptManager().meetEntities(loadNpcForPlayer(player, block)); + } } } @@ -590,7 +593,9 @@ public class Scene { List garbageGadgets = group.getGarbageGadgets(); if (garbageGadgets != null) { - garbageGadgets.forEach(g -> scriptManager.createGadget(group.id, group.block_id, g)); + entities.addAll(garbageGadgets.stream().map(g -> scriptManager.createGadget(group.id, group.block_id, g)) + .filter(Objects::nonNull) + .toList()); } // Load suites @@ -605,9 +610,13 @@ public class Scene { suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger); entities.addAll(suiteData.sceneGadgets.stream() - .map(g -> scriptManager.createGadget(group.id, group.block_id, g)).toList()); + .map(g -> scriptManager.createGadget(group.id, group.block_id, g)) + .filter(Objects::nonNull) + .toList()); entities.addAll(suiteData.sceneMonsters.stream() - .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)).toList()); + .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)) + .filter(Objects::nonNull) + .toList()); } @@ -626,7 +635,7 @@ public class Scene { this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_REMOVE)); } - for (SceneGroup group : block.groups) { + for (SceneGroup group : block.groups.values()) { if(group.triggers != null){ group.triggers.values().forEach(getScriptManager()::deregisterTrigger); } @@ -718,4 +727,47 @@ public class Scene { addEntity(entity); } } + public List loadNpcForPlayer(Player player, SceneBlock block){ + if(!block.contains(player.getPos())){ + return List.of(); + } + + int RANGE = 100; + var pos = player.getPos(); + var data = GameData.getSceneNpcBornData().get(getId()); + if(data == null){ + return List.of(); + } + + var npcs = SceneIndexManager.queryNeighbors(data.getIndex(), pos.toLongArray(), RANGE); + var entityNPCS = npcs.stream().map(item -> { + var group = data.getGroups().get(item.getGroupId()); + if(group == null){ + group = SceneGroup.of(item.getGroupId()); + data.getGroups().putIfAbsent(item.getGroupId(), group); + group.load(getId()); + } + + if(group.npc == null){ + return null; + } + var npc = group.npc.get(item.getConfigId()); + if(npc == null){ + return null; + } + + return getScriptManager().createNPC(npc, block.id, item.getSuiteIdList().get(0)); + }) + .filter(Objects::nonNull) + .filter(item -> getEntities().values().stream() + .filter(e -> e instanceof EntityNPC) + .noneMatch(e -> e.getConfigId() == item.getConfigId())) + .toList(); + + if(entityNPCS.size() > 0){ + broadcastPacket(new PacketGroupSuiteNotify(entityNPCS)); + } + + return entityNPCS; + } } diff --git a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java index 3b48ed279..504884427 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java @@ -4,12 +4,13 @@ import ch.ethz.globis.phtree.PhTree; import emu.grasscutter.utils.Position; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.function.Function; public class SceneIndexManager { - public static void buildIndex(PhTree tree, List elements, Function extractor){ + public static void buildIndex(PhTree tree, Collection elements, Function extractor){ elements.forEach(e -> tree.put(extractor.apply(e), e)); } public static List queryNeighbors(PhTree tree, Position position, int range){ diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index ef802dde9..9fd329838 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -7,6 +7,7 @@ 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.EntityNPC; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.proto.VisionTypeOuterClass; @@ -140,14 +141,15 @@ public class SceneScriptManager { // 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()){ - getScene().onLoadGroup(List.of(group)); - } - return group; - } + var group = block.groups.get(groupId); + if(group == null){ + continue; } + + if(!group.isLoaded()){ + getScene().onLoadGroup(List.of(group)); + } + return group; } return null; } @@ -365,7 +367,9 @@ public class SceneScriptManager { return entity; } - + public EntityNPC createNPC(SceneNPC npc, int blockId, int suiteId) { + return new EntityNPC(getScene(), npc, blockId, suiteId); + } public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) { if(monster == null){ return null; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index 0f4400a50..b42852d8f 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -13,6 +13,8 @@ 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; @@ -24,7 +26,7 @@ public class SceneBlock { public Position min; public int sceneId; - public List groups; + public Map groups; public PhTree sceneGroupIndex = new PhTree16<>(3); private transient boolean loaded; // Not an actual variable in the scripts either @@ -61,9 +63,11 @@ public class SceneBlock { 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()); + groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups")).stream() + .collect(Collectors.toMap(x -> x.id, y -> y)); + + groups.values().forEach(g -> g.block_id = id); + SceneIndexManager.buildIndex(this.sceneGroupIndex, groups.values(), g -> g.pos.toLongArray()); } catch (ScriptException e) { Grasscutter.getLogger().error("Error loading block " + id + " in scene " + sceneId, e); } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 9275706a4..0be30342d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -32,7 +32,7 @@ public class SceneGroup { public Map monsters; // public Map gadgets; // public Map triggers; - + public Map npc; // public List regions; public List suites; public List variables; @@ -44,6 +44,11 @@ public class SceneGroup { private transient boolean loaded; // Not an actual variable in the scripts either private transient CompiledScript script; private transient Bindings bindings; + public static SceneGroup of(int groupId) { + var group = new SceneGroup(); + group.id = groupId; + return group; + } public boolean isLoaded() { return loaded; @@ -124,6 +129,10 @@ public class SceneGroup { // Add variables to suite variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); + // NPC in groups + npc = ScriptLoader.getSerializer().toList(SceneNPC.class, bindings.get("npcs")).stream() + .collect(Collectors.toMap(x -> x.npc_id, y -> y)); + npc.values().forEach(n -> n.group = this); // Add monsters and gadgets to suite for (SceneSuite suite : suites) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneNPC.java b/src/main/java/emu/grasscutter/scripts/data/SceneNPC.java new file mode 100644 index 000000000..97f3f79dd --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneNPC.java @@ -0,0 +1,10 @@ +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneNPC extends SceneObject{ + public int npc_id; +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java new file mode 100644 index 000000000..9abcfc049 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityNPC; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GroupSuiteNotifyOuterClass; + +import java.util.List; + +public class PacketGroupSuiteNotify extends BasePacket { + + /** + * control which npc suite is loaded + */ + public PacketGroupSuiteNotify(List list) { + super(PacketOpcodes.GroupSuiteNotify); + + var proto = GroupSuiteNotifyOuterClass.GroupSuiteNotify.newBuilder(); + + list.forEach(item -> proto.putGroupMap(item.getGroupId(), item.getSuiteId())); + + this.setData(proto); + + } +} From 5a3e9bc34e5d5d7a3e8fc3a2ce215864b6310c1f Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Wed, 25 May 2022 14:12:36 +0800 Subject: [PATCH 24/24] Fixed excessive memory usage of Spatial Index --- build.gradle | 2 +- .../emu/grasscutter/data/ResourceLoader.java | 8 ++--- .../data/custom/SceneNpcBornData.java | 5 +-- .../emu/grasscutter/game/world/Scene.java | 19 +++++----- .../scripts/SceneIndexManager.java | 35 +++++++------------ .../scripts/SceneScriptManager.java | 5 +-- .../grasscutter/scripts/data/SceneBlock.java | 14 +++++--- .../grasscutter/scripts/data/SceneMeta.java | 11 +++--- .../grasscutter/utils/ConfigContainer.java | 3 +- .../java/emu/grasscutter/utils/Position.java | 19 +++++++--- 10 files changed, 62 insertions(+), 59 deletions(-) diff --git a/build.gradle b/build.gradle index 8801b3da0..f47a771d0 100644 --- a/build.gradle +++ b/build.gradle @@ -86,8 +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' implementation group: 'com.esotericsoftware', name : 'reflectasm', version: '1.11.9' + implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1' protobuf files('proto/') diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 9d15fc61b..ed7d3ff2a 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -8,10 +8,9 @@ import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; -import ch.ethz.globis.phtree.PhTree; -import ch.ethz.globis.phtree.v16.PhTree16; import com.google.gson.Gson; import emu.grasscutter.data.custom.*; +import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.utils.Utils; import lombok.SneakyThrows; import org.reflections.Reflections; @@ -428,15 +427,12 @@ public class ResourceLoader { continue; } - PhTree index = new PhTree16<>(3); - var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class); if(data.getBornPosList() == null || data.getBornPosList().size() == 0){ continue; } - data.getBornPosList().forEach(item -> index.put(item.getPos().toLongArray(), item)); - data.setIndex(index); + data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint())); GameData.getSceneNpcBornData().put(data.getSceneId(), data); } diff --git a/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java index 7aab358f2..968ef9a05 100644 --- a/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java +++ b/src/main/java/emu/grasscutter/data/custom/SceneNpcBornData.java @@ -1,6 +1,7 @@ package emu.grasscutter.data.custom; -import ch.ethz.globis.phtree.PhTree; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; import emu.grasscutter.scripts.data.SceneGroup; import lombok.AccessLevel; import lombok.Data; @@ -19,7 +20,7 @@ public class SceneNpcBornData { /** * Spatial Index For NPC */ - transient PhTree index; + transient RTree index; /** * npc groups diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 44c10b10c..2fa9b3cb0 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -498,11 +498,11 @@ 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 List getPlayerActiveBlocks(Player player){ + // consider the borders' entities of blocks, so we check if contains by index + return SceneIndexManager.queryNeighbors(getScriptManager().getBlocksIndex(), + player.getPos().toXZDoubleArray(), Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); } public void checkBlocks() { Set visible = new HashSet<>(); @@ -542,9 +542,8 @@ public class Scene { } public List playerMeetGroups(Player player, SceneBlock block){ - int RANGE = 100; - - List sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos(), RANGE); + List sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos().toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); List groups = sceneGroups.stream() .filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group)) @@ -732,14 +731,14 @@ public class Scene { return List.of(); } - int RANGE = 100; var pos = player.getPos(); var data = GameData.getSceneNpcBornData().get(getId()); if(data == null){ return List.of(); } - var npcs = SceneIndexManager.queryNeighbors(data.getIndex(), pos.toLongArray(), RANGE); + var npcs = SceneIndexManager.queryNeighbors(data.getIndex(), pos.toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); var entityNPCS = npcs.stream().map(item -> { var group = data.getGroups().get(item.getGroupId()); if(group == null){ diff --git a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java index 504884427..597661226 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java @@ -1,7 +1,9 @@ package emu.grasscutter.scripts; -import ch.ethz.globis.phtree.PhTree; -import emu.grasscutter.utils.Position; +import com.github.davidmoten.rtreemulti.Entry; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import com.github.davidmoten.rtreemulti.geometry.Rectangle; import java.util.ArrayList; import java.util.Collection; @@ -10,31 +12,20 @@ import java.util.function.Function; public class SceneIndexManager { - public static void buildIndex(PhTree tree, Collection elements, Function extractor){ - elements.forEach(e -> tree.put(extractor.apply(e), e)); + public static RTree buildIndex(int dimensions, Collection elements, Function extractor){ + RTree rtree = RTree.dimensions(dimensions).create(); + return rtree.add(elements.stream().map(e -> Entry.entry(e, extractor.apply(e))).toList()); } - public static List queryNeighbors(PhTree tree, Position position, int range){ + public static List queryNeighbors(RTree tree, double[] 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); - } + Rectangle rectangle = Rectangle.create(calRange(position, -range), calRange(position, range)); + var queryResult = tree.search(rectangle); + queryResult.forEach(q -> result.add(q.value())); 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){ + private static double[] calRange(double[] position, int range){ var newPos = position.clone(); - for(int i=0;i getBlocksIndex() { + public RTree getBlocksIndex() { return meta.sceneBlockIndex; } public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index b42852d8f..3584f3fd5 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -1,7 +1,8 @@ package emu.grasscutter.scripts.data; -import ch.ethz.globis.phtree.PhTree; -import ch.ethz.globis.phtree.v16.PhTree16; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import com.github.davidmoten.rtreemulti.geometry.Rectangle; import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; @@ -12,7 +13,6 @@ import lombok.ToString; 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; @@ -27,7 +27,7 @@ public class SceneBlock { public int sceneId; public Map groups; - public PhTree sceneGroupIndex = new PhTree16<>(3); + public RTree sceneGroupIndex; private transient boolean loaded; // Not an actual variable in the scripts either @@ -67,11 +67,15 @@ public class SceneBlock { .collect(Collectors.toMap(x -> x.id, y -> y)); groups.values().forEach(g -> g.block_id = id); - SceneIndexManager.buildIndex(this.sceneGroupIndex, groups.values(), g -> g.pos.toLongArray()); + this.sceneGroupIndex = SceneIndexManager.buildIndex(3, groups.values(), g -> g.pos.toPoint()); } 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; } + + public Rectangle toRectangle() { + return Rectangle.create(min.toXZDoubleArray(), max.toXZDoubleArray()); + } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java index 4014b2e1e..212c2c85d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java @@ -1,11 +1,10 @@ package emu.grasscutter.scripts.data; -import ch.ethz.globis.phtree.PhTree; -import ch.ethz.globis.phtree.v16.PhTree16; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; -import lombok.Data; import lombok.Setter; import lombok.ToString; @@ -27,7 +26,7 @@ public class SceneMeta { public Bindings context; - public PhTree sceneBlockIndex = new PhTree16<>(2); + public RTree sceneBlockIndex; public static SceneMeta of(int sceneId) { return new SceneMeta().load(sceneId); @@ -64,8 +63,8 @@ public class SceneMeta { } 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()); + this.sceneBlockIndex = SceneIndexManager.buildIndex(2, blocks, SceneBlock::toRectangle); + } catch (ScriptException e) { Grasscutter.getLogger().error("Error running script", e); return null; diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index 9fd5eebae..9961f1323 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -135,7 +135,8 @@ public class ConfigContainer { public int bindPort = 22102; /* This is the port used in the default region. */ public int accessPort = 0; - + /* Entities within a certain range will be loaded for the player */ + public int loadEntitiesForPlayerRange = 100; public boolean enableScriptInBigWorld = false; public boolean enableConsole = true; public GameOptions gameOptions = new GameOptions(); diff --git a/src/main/java/emu/grasscutter/utils/Position.java b/src/main/java/emu/grasscutter/utils/Position.java index 4442e8f98..a323fe919 100644 --- a/src/main/java/emu/grasscutter/utils/Position.java +++ b/src/main/java/emu/grasscutter/utils/Position.java @@ -2,6 +2,7 @@ package emu.grasscutter.utils; import java.io.Serializable; +import com.github.davidmoten.rtreemulti.geometry.Point; import dev.morphia.annotations.Entity; import emu.grasscutter.net.proto.VectorOuterClass.Vector; @@ -155,10 +156,20 @@ public class Position implements Serializable { .setZ(this.getZ()) .build(); } - public long[] toLongArray(){ - return new long[]{(long) x, (long) y, (long) z}; + public Point toPoint(){ + return Point.create(x,y,z); } - public long[] toXZLongArray(){ - return new long[]{(long) x, (long) z}; + + /** + * To XYZ array for Spatial Index + */ + public double[] toDoubleArray(){ + return new double[]{ x, y, z}; + } + /** + * To XZ array for Spatial Index (Blocks) + */ + public double[] toXZDoubleArray(){ + return new double[]{x, z}; } }