diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index b469925fc19..e25f847afed 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -252,12 +252,16 @@ public void onDeath(int killerId) { if (scriptManager.isInit() && this.getGroupId() > 0) { Optional.ofNullable(scriptManager.getScriptMonsterSpawnService()).ifPresent(s -> s.onMonsterDead(this)); - // prevent spawn monster after success - /*if (challenge.map(c -> c.inProgress()).orElse(true)) { - scriptManager.callEvent(new ScriptArgs(EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId()).setGroupId(this.getGroupId())); - } else if (getScene().getChallenge() == null) { - }*/ - scriptManager.callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId())); + // Ensure each EVENT_ANY_MONSTER_DIE runs to completion. + // Multiple such events firing at the same time may cause + // the same lua trigger to fire multiple times, when it + // should happen only once. + var future = scriptManager.callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId())); + try { + future.get(); + } catch (Exception e) { + e.printStackTrace(); + } } // Battle Pass trigger scene.getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1)); diff --git a/src/main/java/emu/grasscutter/game/world/GroupReplacementData.java b/src/main/java/emu/grasscutter/game/world/GroupReplacementData.java index 13fd157de33..84add14dc88 100644 --- a/src/main/java/emu/grasscutter/game/world/GroupReplacementData.java +++ b/src/main/java/emu/grasscutter/game/world/GroupReplacementData.java @@ -5,6 +5,13 @@ @Data public class GroupReplacementData { - int id; - List replace_groups; + public int id; + public List replace_groups; + + public GroupReplacementData() {} + + public GroupReplacementData(int id, List replace_groups) { + this.id = id; + this.replace_groups = replace_groups; + } } diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index a6b9bfebca7..9a6252c1572 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -1110,6 +1110,9 @@ public void unloadGroup(SceneBlock block, int group_id) { if (group.regions != null) { group.regions.values().forEach(getScriptManager()::deregisterRegion); } + if (challenge != null && group.id == challenge.getGroup().id) { + challenge.fail(); + } scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group); this.loadedGroups.remove(group); diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 6d6882546f8..be6e2b63b25 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -17,6 +17,7 @@ import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.*; import emu.grasscutter.scripts.service.*; +import emu.grasscutter.server.event.game.SceneMetaLoadEvent; import emu.grasscutter.server.packet.send.PacketGroupSuiteNotify; import emu.grasscutter.utils.*; import io.netty.util.concurrent.FastThreadLocalThread; @@ -38,6 +39,7 @@ public class SceneScriptManager { private final Map variables; private SceneMeta meta; private boolean isInit; + private boolean noCacheGroupGridsToDisk; private final Map timeAxis = new ConcurrentHashMap<>(); @@ -134,7 +136,7 @@ public void registerTrigger(List triggers) { public void registerTrigger(SceneTrigger trigger) { this.triggerInvocations.put(trigger.getName(), new AtomicInteger(0)); this.getTriggersByEvent(trigger.getEvent()).add(trigger); - Grasscutter.getLogger().trace("Registered trigger {}", trigger.getName()); + Grasscutter.getLogger().trace("Registered trigger {} from group {}", trigger.getName(), trigger.getCurrentGroup().id); } public void deregisterTrigger(List triggers) { @@ -143,7 +145,7 @@ public void deregisterTrigger(List triggers) { public void deregisterTrigger(SceneTrigger trigger) { this.getTriggersByEvent(trigger.getEvent()).remove(trigger); - Grasscutter.getLogger().trace("deregistered trigger {}", trigger.getName()); + Grasscutter.getLogger().trace("deregistered trigger {} from group {}", trigger.getName(), trigger.getCurrentGroup().id); } public void resetTriggers(int eventId) { @@ -438,6 +440,17 @@ private static int getGadgetVisionLevel(int gadget_id) { } private void init() { + var event = new SceneMetaLoadEvent(getScene()); + event.call(); + + if (event.isOverride()) { + // Group grids should not be cached to disk when a scene + // group override is in effect. Otherwise, when the server + // next runs without that override, the cached content + // will not make sense. + noCacheGroupGridsToDisk = true; + } + var meta = ScriptLoader.getSceneMeta(getScene().getId()); if (meta == null) { return; @@ -455,7 +468,7 @@ public List getGroupGrids() { return groupGridsCache.get(sceneId); } else { var path = FileUtils.getCachePath("scene" + sceneId + "_grid.json"); - if (path.toFile().isFile() && !Grasscutter.config.server.game.cacheSceneEntitiesEveryRun) { + if (path.toFile().isFile() && !Grasscutter.config.server.game.cacheSceneEntitiesEveryRun && !noCacheGroupGridsToDisk) { try { var groupGrids = JsonUtils.loadToList(path, Grid.class); groupGridsCache.put(sceneId, groupGrids); @@ -585,15 +598,17 @@ public List getGroupGrids() { } groupGridsCache.put(scene.getId(), groupGrids); - try { - Files.createDirectories(path.getParent()); - } catch (IOException ignored) { - } - try (var file = new FileWriter(path.toFile())) { - file.write(JsonUtils.encode(groupGrids)); - Grasscutter.getLogger().info("Scene {} saved grid file.", getScene().getId()); - } catch (Exception e) { - Grasscutter.getLogger().error("Scene {} unable to save grid file.", getScene().getId(), e); + if (!noCacheGroupGridsToDisk) { + try { + Files.createDirectories(path.getParent()); + } catch (IOException ignored) { + } + try (var file = new FileWriter(path.toFile())) { + file.write(JsonUtils.encode(groupGrids)); + Grasscutter.getLogger().info("Scene {} saved grid file.", getScene().getId()); + } catch (Exception e) { + Grasscutter.getLogger().error("Scene {} unable to save grid file.", getScene().getId(), e); + } } return groupGrids; } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java index c741a408bd2..de5e14b5c64 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java @@ -11,7 +11,7 @@ import emu.grasscutter.utils.FileUtils; import java.io.IOException; import java.lang.ref.SoftReference; -import java.nio.file.Files; +import java.nio.file.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; @@ -172,6 +172,17 @@ public LuaValue call(LuaValue arg) { * @return The sources of the script. */ public static String readScript(String path) { + return readScript(path, false); + } + + /** + * Loads the sources of a script. + * + * @param path The path of the script. + * @param useAbsPath Use path as-is; don't look under Scripts resources. + * @return The sources of the script. + */ + public static String readScript(String path, boolean useAbsPath) { // Check if the path is cached. var cached = ScriptLoader.tryGet(ScriptLoader.scriptSources.get(path)); if (cached.isPresent()) { @@ -179,8 +190,11 @@ public static String readScript(String path) { } // Attempt to load the script. - var scriptPath = FileUtils.getScriptPath(path); - if (!Files.exists(scriptPath)) return null; + var scriptPath = useAbsPath ? Paths.get(path) : FileUtils.getScriptPath(path); + if (!Files.exists(scriptPath)) { + Grasscutter.getLogger().error("Could not find script at path {}", path); + return null; + } try { var source = Files.readString(scriptPath); @@ -201,6 +215,17 @@ public static String readScript(String path) { * @return The compiled script. */ public static CompiledScript getScript(String path) { + return getScript(path, false); + } + + /** + * Fetches a script and compiles it, or uses the cached varient. + * + * @param path The path of the script. + * @param useAbsPath Use path as-is; don't look under Scripts resources. + * @return The compiled script. + */ + public static CompiledScript getScript(String path, boolean useAbsPath) { // Check if the script is cached. var sc = ScriptLoader.tryGet(ScriptLoader.scriptsCache.get(path)); if (sc.isPresent()) { @@ -211,15 +236,18 @@ public static CompiledScript getScript(String path) { CompiledScript script; if (Configuration.FAST_REQUIRE) { // Attempt to load the script. - var scriptPath = FileUtils.getScriptPath(path); - if (!Files.exists(scriptPath)) return null; + var scriptPath = useAbsPath ? Paths.get(path) : FileUtils.getScriptPath(path); + if (!Files.exists(scriptPath)) { + Grasscutter.getLogger().error("Could not find script at path {}", path); + return null; + } // Compile the script from the file. var source = Files.newBufferedReader(scriptPath); script = ScriptLoader.getEngine().compile(source); } else { // Load the script sources. - var sources = ScriptLoader.readScript(path); + var sources = ScriptLoader.readScript(path, useAbsPath); if (sources == null) return null; // Check to see if the script references other scripts. @@ -237,7 +265,7 @@ public static CompiledScript getScript(String path) { var scriptName = line.substring(9, line.length() - 1); // Resolve the script path. var scriptPath = "Common/" + scriptName + ".lua"; - var scriptSource = ScriptLoader.readScript(scriptPath); + var scriptSource = ScriptLoader.readScript(scriptPath, useAbsPath); if (scriptSource == null) continue; // Append the script source. diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index c135ef020b1..68e1467d0e2 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -5,6 +5,7 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.game.world.Position; import emu.grasscutter.scripts.*; +import emu.grasscutter.server.event.game.SceneBlockLoadedEvent; import java.util.Map; import java.util.stream.Collectors; import javax.script.*; @@ -64,6 +65,10 @@ public SceneBlock load(int sceneId, Bindings bindings) { .collect(Collectors.toMap(x -> x.id, y -> y, (a, b) -> a)); this.groups.values().forEach(g -> g.block_id = this.id); + + var event = new SceneBlockLoadedEvent(this); + event.call(); + this.sceneGroupIndex = SceneIndexManager.buildIndex(3, this.groups.values(), g -> g.pos.toPoint()); } catch (ScriptException exception) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 9a13a2f5a77..433591da2d7 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -3,6 +3,7 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.game.world.Position; import emu.grasscutter.scripts.ScriptLoader; +import java.io.*; import java.util.*; import java.util.stream.Collectors; import javax.script.*; @@ -39,6 +40,7 @@ public final class SceneGroup { private transient boolean loaded; private transient CompiledScript script; private transient Bindings bindings; + public String overrideScriptPath; public static SceneGroup of(int groupId) { var group = new SceneGroup(); @@ -86,8 +88,12 @@ public synchronized SceneGroup load(int sceneId) { // Create the bindings. this.bindings = ScriptLoader.getEngine().createBindings(); - var cs = - ScriptLoader.getScript("Scene/%s/scene%s_group%s.lua".formatted(sceneId, sceneId, this.id)); + CompiledScript cs; + if (overrideScriptPath != null && !overrideScriptPath.equals("")) { + cs = ScriptLoader.getScript(overrideScriptPath, true); + } else { + cs = ScriptLoader.getScript("Scene/%s/scene%s_group%s.lua".formatted(sceneId, sceneId, this.id)); + } if (cs == null) { return this; diff --git a/src/main/java/emu/grasscutter/server/event/game/SceneBlockLoadedEvent.java b/src/main/java/emu/grasscutter/server/event/game/SceneBlockLoadedEvent.java new file mode 100644 index 00000000000..15b9f23a290 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/event/game/SceneBlockLoadedEvent.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.event.game; + +import emu.grasscutter.server.event.types.ServerEvent; +import emu.grasscutter.scripts.data.SceneBlock; +import lombok.*; + +@Getter +public final class SceneBlockLoadedEvent extends ServerEvent { + private SceneBlock block; + + public SceneBlockLoadedEvent(SceneBlock block) { + super(Type.GAME); + + this.block = block; + } +} diff --git a/src/main/java/emu/grasscutter/server/event/game/SceneMetaLoadEvent.java b/src/main/java/emu/grasscutter/server/event/game/SceneMetaLoadEvent.java new file mode 100644 index 00000000000..0e5768a00fc --- /dev/null +++ b/src/main/java/emu/grasscutter/server/event/game/SceneMetaLoadEvent.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.event.game; + +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.server.event.types.ServerEvent; +import lombok.*; + +@Getter +public final class SceneMetaLoadEvent extends ServerEvent { + private Scene scene; + @Setter private boolean override; + + public SceneMetaLoadEvent(Scene scene) { + super(Type.GAME); + + this.scene = scene; + } +}