diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 6dae38820..26e091bb2 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1536,6 +1536,12 @@ public class Player implements PlayerHook, FieldFetch { } } + @Override + public boolean equals(Object obj) { + return obj instanceof Player otherPlayer && + this.id == otherPlayer.getUid(); + } + public enum SceneLoadState { NONE(0), LOADING(1), INIT(2), LOADED(3); @@ -1546,5 +1552,4 @@ public class Player implements PlayerHook, FieldFetch { this.value = value; } } - } diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index cd7c42865..3370db26e 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -22,8 +22,8 @@ import emu.grasscutter.game.props.*; import emu.grasscutter.game.quest.QuestGroupSuite; import emu.grasscutter.game.world.data.TeleportProperties; import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.proto.*; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; +import emu.grasscutter.net.proto.*; import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; import emu.grasscutter.scripts.*; import emu.grasscutter.scripts.constants.EventType; @@ -33,11 +33,12 @@ import emu.grasscutter.server.event.player.PlayerTeleportEvent; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.objects.KahnsSort; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import lombok.*; + +import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; -import javax.annotation.Nullable; -import lombok.*; public final class Scene { @Getter private final World world; @@ -51,7 +52,6 @@ public final class Scene { @Getter private final Set loadedGroups; @Getter private final BlossomManager blossomManager; private final HashSet unlockedForces; - private final List afterLoadedCallbacks = new ArrayList<>(); private final long startWorldTime; @Getter @Setter DungeonManager dungeonManager; @Getter Int2ObjectMap sceneRoutes; @@ -68,6 +68,9 @@ public final class Scene { @Getter private int tickCount = 0; @Getter private boolean isPaused = false; + private final List afterLoadedCallbacks = new ArrayList<>(); + private final List afterHostInitCallbacks = new ArrayList<>(); + @Getter private GameEntity sceneEntity; public Scene(World world, SceneData sceneData) { @@ -106,6 +109,13 @@ public final class Scene { return this.getPlayers().size(); } + /** + * @return The scene's world's host. + */ + public Player getHost() { + return this.getWorld().getHost(); + } + public GameEntity getEntityById(int id) { // Check if the scene's entity ID is referenced. if (id == 0x13800001) return this.sceneEntity; @@ -678,6 +688,29 @@ public final class Scene { this.afterLoadedCallbacks.add(runnable); } + /** + * Invoked when a player initializes loading the scene. + * + * @param player The player that initialized loading the scene. + */ + public void playerSceneInitialized(Player player) { + // Check if the player is the host. + if (!player.equals(this.getHost())) return; + + // Run all callbacks. + this.afterHostInitCallbacks.forEach(Runnable::run); + this.afterHostInitCallbacks.clear(); + } + + /** + * Run a callback when the host initializes loading the scene. + * + * @param runnable The callback to be executed. + */ + public void runWhenHostInitialized(Runnable runnable) { + this.afterHostInitCallbacks.add(runnable); + } + public int getEntityLevel(int baseLevel, int worldLevelOverride) { int level = worldLevelOverride > 0 ? worldLevelOverride + baseLevel - 22 : baseLevel; level = Math.min(level, 100); diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index e37c8be3c..f266104be 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -514,8 +514,8 @@ public class ScriptLib { var entity = scene.getEntityByConfigId(cfgId); if (entity == null) return 2; - scene.broadcastPacket( - new PacketServerGlobalValueChangeNotify(entity, sgvName, value)); + scene.runWhenHostInitialized(() -> scene.broadcastPacket( + new PacketServerGlobalValueChangeNotify(entity, sgvName, value))); return 0; } @@ -528,7 +528,7 @@ public class ScriptLib { var scene = scriptManager.getScene(); - scene.runWhenFinished(() -> scene.broadcastPacket( + scene.runWhenHostInitialized(() -> scene.broadcastPacket( new PacketServerGlobalValueChangeNotify(entityId, sgvName, value))); return 0; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java index 1164dc35e..5e6057c95 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java @@ -13,42 +13,45 @@ public class HandlerEnterSceneDoneReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var player = session.getPlayer(); + // Finished loading - session.getPlayer().setSceneLoadState(SceneLoadState.LOADED); + player.setSceneLoadState(SceneLoadState.LOADED); // Done - session.send(new PacketPlayerTimeNotify(session.getPlayer())); // Probably not the right place + session.send(new PacketPlayerTimeNotify(player)); // Probably not the right place // Spawn player in world - session.getPlayer().getScene().spawnPlayer(session.getPlayer()); + player.getScene().spawnPlayer(player); // Spawn other entites already in world - session.getPlayer().getScene().showOtherEntities(session.getPlayer()); + player.getScene().showOtherEntities(player); // Locations - session.send(new PacketWorldPlayerLocationNotify(session.getPlayer().getWorld())); - session.send(new PacketScenePlayerLocationNotify(session.getPlayer().getScene())); - session.send(new PacketWorldPlayerRTTNotify(session.getPlayer().getWorld())); + session.send(new PacketWorldPlayerLocationNotify(player.getWorld())); + session.send(new PacketScenePlayerLocationNotify(player.getScene())); + session.send(new PacketWorldPlayerRTTNotify(player.getWorld())); // spawn NPC - session.getPlayer().getScene().loadNpcForPlayerEnter(session.getPlayer()); + player.getScene().loadNpcForPlayerEnter(player); // notify client to load the npc for quest var questGroupSuites = - session.getPlayer().getQuestManager().getSceneGroupSuite(session.getPlayer().getSceneId()); + player.getQuestManager().getSceneGroupSuite(player.getSceneId()); - session.getPlayer().getScene().loadGroupForQuest(questGroupSuites); + player.getScene().loadGroupForQuest(questGroupSuites); Grasscutter.getLogger() .trace( "Loaded Scene {} Quest(s) Groupsuite(s): {}", - session.getPlayer().getSceneId(), + player.getSceneId(), questGroupSuites); session.send(new PacketGroupSuiteNotify(questGroupSuites)); // Reset timer for sending player locations - session.getPlayer().resetSendPlayerLocTime(); + player.resetSendPlayerLocTime(); + // Rsp - session.send(new PacketEnterSceneDoneRsp(session.getPlayer())); + session.send(new PacketEnterSceneDoneRsp(player)); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneInitFinishReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneInitFinishReq.java index 20a9137ef..681f7525e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneInitFinishReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneInitFinishReq.java @@ -13,28 +13,33 @@ public class HandlerSceneInitFinishReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var player = session.getPlayer(); + var world = player.getWorld(); + // Info packets session.send(new PacketServerTimeNotify()); - session.send(new PacketWorldPlayerInfoNotify(session.getPlayer().getWorld())); - session.send(new PacketWorldDataNotify(session.getPlayer().getWorld())); + session.send(new PacketWorldPlayerInfoNotify(world)); + session.send(new PacketWorldDataNotify(world)); session.send(new PacketPlayerWorldSceneInfoListNotify()); session.send(new BasePacket(PacketOpcodes.SceneForceUnlockNotify)); - session.send(new PacketHostPlayerNotify(session.getPlayer().getWorld())); + session.send(new PacketHostPlayerNotify(world)); - session.send(new PacketSceneTimeNotify(session.getPlayer())); - session.send(new PacketPlayerGameTimeNotify(session.getPlayer())); - session.send(new PacketPlayerEnterSceneInfoNotify(session.getPlayer())); - session.send(new PacketSceneAreaWeatherNotify(session.getPlayer())); - session.send(new PacketScenePlayerInfoNotify(session.getPlayer().getWorld())); - session.send(new PacketSceneTeamUpdateNotify(session.getPlayer())); + session.send(new PacketSceneTimeNotify(player)); + session.send(new PacketPlayerGameTimeNotify(player)); + session.send(new PacketPlayerEnterSceneInfoNotify(player)); + session.send(new PacketSceneAreaWeatherNotify(player)); + session.send(new PacketScenePlayerInfoNotify(world)); + session.send(new PacketSceneTeamUpdateNotify(player)); - session.send(new PacketSyncTeamEntityNotify(session.getPlayer())); - session.send(new PacketSyncScenePlayTeamEntityNotify(session.getPlayer())); + session.send(new PacketSyncTeamEntityNotify(player)); + session.send(new PacketSyncScenePlayTeamEntityNotify(player)); // Done Packet - session.send(new PacketSceneInitFinishRsp(session.getPlayer())); + session.send(new PacketSceneInitFinishRsp(player)); - // Set state - session.getPlayer().setSceneLoadState(SceneLoadState.INIT); + // Set scene load state. + player.setSceneLoadState(SceneLoadState.INIT); + // Run scene initialization. + player.getScene().playerSceneInitialized(player); } }