diff --git a/HKMP/Game/Client/Entity/Action/EntityFsmActions.cs b/HKMP/Game/Client/Entity/Action/EntityFsmActions.cs index d77b528..6103487 100644 --- a/HKMP/Game/Client/Entity/Action/EntityFsmActions.cs +++ b/HKMP/Game/Client/Entity/Action/EntityFsmActions.cs @@ -210,8 +210,16 @@ private static void EmitRandomInterceptInstructions(ILCursor c) // Push the current instance of the class onto the stack c.Emit(OpCodes.Ldarg_0); - // Emit a delegate that pops the current int off the stack (our random value) and + // Emit a delegate that pops the current random value off the stack and puts it back after some processing c.EmitDelegate>((value, instance) => { + // We need to check whether the game object that is being spawned with this action is not an object + // managed by the system. Because if so, we do not store the random values because the action for it + // is not being networked. Only the game object spawn is networked with an EntitySpawn packet directly. + var gameObject = ReflectionHelper.GetField(instance, "gameObject"); + if (gameObject != null && IsObjectInRegistry(gameObject)) { + return value; + } + if (!RandomActionValues.TryGetValue(instance, out var queue)) { queue = new Queue(); RandomActionValues[instance] = queue; @@ -238,6 +246,32 @@ private static void FlingObjectsFromGlobalPoolOnEnter(ILContext il) { EmitRandomInterceptInstructions(c); EmitRandomInterceptInstructions(c); EmitRandomInterceptInstructions(c); + + // Reset cursor + c = new ILCursor(il); + + // Goto the next call instruction for ObjectPoolExtensions.Spawn + c.GotoNext(i => i.MatchCall(typeof(ObjectPoolExtensions), "Spawn")); + + // Move the cursor after the call instruction + c.Index++; + + // Push the current instance of the class onto the stack + c.Emit(OpCodes.Ldarg_0); + + // Emit a delegate that pops the spawned game object off the stack and uses it, then puts it back again + c.EmitDelegate>((go, action) => { + Logger.Debug($"Delegate of FlingObjectsFromGlobalPool: {go.name}"); + if (EntitySpawnEvent != null && EntitySpawnEvent.Invoke(new EntitySpawnDetails { + Type = EntitySpawnType.FsmAction, + Action = action, + GameObject = go + })) { + Logger.Debug("FlingObjectsFromGlobalPool IL spawned object is entity"); + } + + return go; + }); } catch (Exception e) { Logger.Error($"Could not change FlingObjectsFromGlobalPool#OnEnter IL:\n{e}"); } @@ -258,6 +292,32 @@ private static void FlingObjectsFromGlobalPoolVelOnEnter(ILContext il) { EmitRandomInterceptInstructions(c); EmitRandomInterceptInstructions(c); EmitRandomInterceptInstructions(c); + + // Reset cursor + c = new ILCursor(il); + + // Goto the next call instruction for ObjectPoolExtensions.Spawn + c.GotoNext(i => i.MatchCall(typeof(ObjectPoolExtensions), "Spawn")); + + // Move the cursor after the call instruction + c.Index++; + + // Push the current instance of the class onto the stack + c.Emit(OpCodes.Ldarg_0); + + // Emit a delegate that pops the spawned game object off the stack and uses it, then puts it back again + c.EmitDelegate>((go, action) => { + Logger.Debug($"Delegate of FlingObjectsFromGlobalPoolVel: {go.name}"); + if (EntitySpawnEvent != null && EntitySpawnEvent.Invoke(new EntitySpawnDetails { + Type = EntitySpawnType.FsmAction, + Action = action, + GameObject = go + })) { + Logger.Debug("FlingObjectsFromGlobalPoolVel IL spawned object is entity"); + } + + return go; + }); } catch (Exception e) { Logger.Error($"Could not change FlingObjectsFromGlobalPoolVel#OnEnter IL:\n{e}"); } @@ -408,6 +468,15 @@ private static void ApplyNetworkDataFromAction(EntityNetworkData data, SpawnObje #region FlingObjectsFromGlobalPool private static bool GetNetworkDataFromAction(EntityNetworkData data, FlingObjectsFromGlobalPool action) { + // We first check whether the game object belonging to the Rigidbody2D in the action is an object that is + // managed by the system. Because if so, it means that we have already caught its spawning in the IL hook + // for the action and sent an EntitySpawn packet instead. So we need not also network this action separately. + var rigidbody = ReflectionHelper.GetField(action, "rb2d"); + if (rigidbody != null && rigidbody.gameObject != null && IsObjectInRegistry(rigidbody.gameObject)) { + Logger.Debug("Skipping getting network data for FlingObjectsFromGlobalPool, because spawned objects are managed by system"); + return false; + } + var position = Vector3.zero; var spawnPoint = action.spawnPoint.Value; @@ -521,6 +590,15 @@ private static void ApplyNetworkDataFromAction(EntityNetworkData data, FlingObje #region FlingObjectsFromGlobalPoolVel private static bool GetNetworkDataFromAction(EntityNetworkData data, FlingObjectsFromGlobalPoolVel action) { + // We first check whether the game object belonging to the Rigidbody2D in the action is an object that is + // managed by the system. Because if so, it means that we have already caught its spawning in the IL hook + // for the action and sent an EntitySpawn packet instead. So we need not also network this action separately. + var rigidbody = ReflectionHelper.GetField(action, "rb2d"); + if (rigidbody != null && rigidbody.gameObject != null && IsObjectInRegistry(rigidbody.gameObject)) { + Logger.Debug("Skipping getting network data for FlingObjectsFromGlobalPool, because spawned objects are managed by system"); + return false; + } + var position = Vector3.zero; var spawnPoint = action.spawnPoint.Value; diff --git a/HKMP/Game/Client/Entity/EntitySpawner.cs b/HKMP/Game/Client/Entity/EntitySpawner.cs index 79fbd15..2cbe600 100644 --- a/HKMP/Game/Client/Entity/EntitySpawner.cs +++ b/HKMP/Game/Client/Entity/EntitySpawner.cs @@ -153,6 +153,22 @@ List clientFsms return SpawnRadianceNailFromComb(clientFsms[0]); } + if (spawningType == EntityType.WingedNosk) { + if (spawnedType == EntityType.InfectedBalloon) { + return SpawnInfectedBalloonWingedNoskObject(clientFsms[0]); + } + + if (spawnedType == EntityType.NoskBlob) { + return SpawnWingedNoskBlobObject(clientFsms[0]); + } + } + + if (spawningType == EntityType.WingedNoskGlobDropper && spawnedType == EntityType.NoskBlob) { + return SpawnWingedNoskGlobDropper(clientFsms[0]); + } + + Logger.Warn($"No implementation for spawning entity game object: {spawningType}, {spawnedType}"); + return null; } @@ -217,6 +233,24 @@ private static GameObject SpawnFromGlobalPool(SpawnObjectFromGlobalPool action, return spawnedObject; } + private static GameObject SpawnFromFlingGlobalPool(FlingObjectsFromGlobalPool action, GameObject gameObject) { + var position = Vector3.zero; + var zero = Vector3.zero; + if (action.spawnPoint.Value != null) { + position = action.spawnPoint.Value.transform.position; + if (!action.position.IsNone) { + position += action.position.Value; + } + } else { + if (!action.position.IsNone) { + position = action.position.Value; + } + } + + var spawnedObject = gameObject.Spawn(position, Quaternion.Euler(zero)); + return spawnedObject; + } + private static GameObject SpawnFromFlingGlobalPoolTime( FlingObjectsFromGlobalPoolTime action, GameObject gameObject @@ -483,4 +517,25 @@ private static GameObject SpawnRadianceNailFromComb(PlayMakerFSM fsm) { return SpawnFromGlobalPool(action, gameObject); } + + private static GameObject SpawnInfectedBalloonWingedNoskObject(PlayMakerFSM fsm) { + var action = fsm.GetFirstAction("Summon"); + var gameObject = action.gameObject.Value; + + return SpawnFromGlobalPool(action, gameObject); + } + + private static GameObject SpawnWingedNoskBlobObject(PlayMakerFSM fsm) { + var action = fsm.GetFirstAction("Spit 1"); + var gameObject = action.gameObject.Value; + + return SpawnFromFlingGlobalPool(action, gameObject); + } + + private static GameObject SpawnWingedNoskGlobDropper(PlayMakerFSM fsm) { + var action = fsm.GetFirstAction("Drop"); + var gameObject = action.gameObject.Value; + + return SpawnFromFlingGlobalPool(action, gameObject); + } } diff --git a/HKMP/Game/Client/Entity/EntityType.cs b/HKMP/Game/Client/Entity/EntityType.cs index 6f4509e..20bfcb1 100644 --- a/HKMP/Game/Client/Entity/EntityType.cs +++ b/HKMP/Game/Client/Entity/EntityType.cs @@ -222,6 +222,7 @@ internal enum EntityType { PureVessel, PureVesselBlast, WingedNosk, + WingedNoskGlobDropper, TurretZoteling, LankyZoteling, HeadOfZote, diff --git a/HKMP/Resource/entity-registry.json b/HKMP/Resource/entity-registry.json index 41125da..5c88ee3 100644 --- a/HKMP/Resource/entity-registry.json +++ b/HKMP/Resource/entity-registry.json @@ -1320,6 +1320,11 @@ "type": "WingedNosk", "fsm_name": "Hornet Nosk" }, + { + "base_object_name": "Glob Dropper", + "type": "WingedNoskGlobDropper", + "fsm_name": "Dropper" + }, { "base_object_name": "Zote Turret", "type": "TurretZoteling",