diff --git a/configs/addons/counterstrikesharp/gamedata/gamedata.json b/configs/addons/counterstrikesharp/gamedata/gamedata.json
index 2cd4fd0e7..0d634c5ff 100644
--- a/configs/addons/counterstrikesharp/gamedata/gamedata.json
+++ b/configs/addons/counterstrikesharp/gamedata/gamedata.json
@@ -253,5 +253,11 @@
"windows": 2,
"linux": 0
}
+ },
+ "CheckTransmitPlayerSlot": {
+ "offsets": {
+ "windows": 584,
+ "linux": 584
+ }
}
}
diff --git a/examples/WithCheckTransmit/README.md b/examples/WithCheckTransmit/README.md
new file mode 100644
index 000000000..9030eb5c8
--- /dev/null
+++ b/examples/WithCheckTransmit/README.md
@@ -0,0 +1,2 @@
+# With CheckTransmit
+This example shows how to work with the `CheckTransmit` listener.
diff --git a/examples/WithCheckTransmit/WithCheckTransmit.csproj b/examples/WithCheckTransmit/WithCheckTransmit.csproj
new file mode 100644
index 000000000..32c0ee55a
--- /dev/null
+++ b/examples/WithCheckTransmit/WithCheckTransmit.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/examples/WithCheckTransmit/WithCheckTransmitPlugin.cs b/examples/WithCheckTransmit/WithCheckTransmitPlugin.cs
new file mode 100644
index 000000000..9a88b1389
--- /dev/null
+++ b/examples/WithCheckTransmit/WithCheckTransmitPlugin.cs
@@ -0,0 +1,115 @@
+using CounterStrikeSharp.API;
+using CounterStrikeSharp.API.Core;
+using CounterStrikeSharp.API.Core.Attributes;
+
+namespace WithCheckTransmit;
+
+[MinimumApiVersion(276)]
+public class WithCheckTransmitPlugin : BasePlugin
+{
+ public override string ModuleName => "Example: With CheckTransmit";
+ public override string ModuleVersion => "1.0.0";
+ public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
+ public override string ModuleDescription => "A simple plugin that uses the CheckTransmit listener!";
+
+ private Dictionary ShouldSeeDoors = new Dictionary();
+
+ public override void Load(bool hotReload)
+ {
+ // This command is related to the following example.
+ AddCommand("nodoors", "Toggle door transmit", (player, info) =>
+ {
+ if (player == null)
+ return;
+
+ if (ShouldSeeDoors.ContainsKey(player.Slot))
+ {
+ ShouldSeeDoors[player.Slot] = !ShouldSeeDoors[player.Slot];
+ } else
+ {
+ ShouldSeeDoors.Add(player.Slot, false);
+ }
+
+ info.ReplyToCommand($"You should {(ShouldSeeDoors[player.Slot] ? "see" : "not see")} doors");
+ });
+
+ // In this example, we will hide every door for players that have enabled the option with the command 'nodoors'
+ RegisterListener((CCheckTransmitInfoList infoList) =>
+ {
+ // Get the list of the currently available doors (prop_door_rotating)
+ IEnumerable doors = Utilities.FindAllEntitiesByDesignerName("prop_door_rotating");
+
+ // Do nothing if there is none.
+ if (!doors.Any())
+ return;
+
+ // Go through every received info
+ foreach ((CCheckTransmitInfo info, CCSPlayerController? player) in infoList)
+ {
+ // If no player is found, we can continue
+ if (player == null)
+ continue;
+
+ // Otherwise, lets do the work:
+
+ // Check if we should clear or not:
+
+ // If we have no data saved for this player, then we should not continue
+ if (!ShouldSeeDoors.ContainsKey(player.Slot))
+ continue;
+
+ // If this value is true, then this player should see doors
+ if (ShouldSeeDoors[player.Slot])
+ continue;
+
+ // Otherwise, lets remove the door entity indexes from the info list so they won't be transmitted
+ foreach (CPropDoorRotating door in doors)
+ {
+ info.TransmitEntities.Remove(door);
+ }
+
+ // NOTE: this is a barebone example, saving data and doing sanity checks is up to you.
+ }
+ });
+
+ // In this example, we will hide other players in the same team as the player.
+ // NOTE: 'Hiding' players requires extra work to do, killing non-transmitted players results in crash.
+ RegisterListener((CCheckTransmitInfoList infoList) =>
+ {
+ // Get the list of the current players, we only work with this value later on
+ List players = Utilities.GetPlayers();
+
+ // Go through every received info
+ foreach ((CCheckTransmitInfo info, CCSPlayerController? player) in infoList)
+ {
+ // If no player is found, we can continue
+ if (player == null)
+ continue;
+
+ // Otherwise, lets do the work:
+
+ // as an example, lets hide everyone for this player who is in the same team.
+ IEnumerable targetPlayers = players.Where(p =>
+ // is the player and its pawn valid
+ p.IsValid && p.Pawn.IsValid &&
+
+ // we shouldn't hide ourselves
+ p.Slot != player.Slot &&
+
+ // is the player is in the same team
+ p.Team == player.Team &&
+
+ // is alive
+ p.PlayerPawn.Value?.LifeState == (byte)LifeState_t.LIFE_ALIVE
+ );
+
+ foreach (CCSPlayerController targetPlayer in targetPlayers)
+ {
+ // Calling 'Remove' will clear the entity index of the target player pawn from the transmission list
+ // so it won't be transmitted for the 'player'.
+ info.TransmitEntities.Remove(targetPlayer.Pawn);
+ }
+ }
+ });
+ }
+}
diff --git a/managed/CounterStrikeSharp.API/Core/Listeners.g.cs b/managed/CounterStrikeSharp.API/Core/Listeners.g.cs
index 6e2d24e4a..b362834ab 100644
--- a/managed/CounterStrikeSharp.API/Core/Listeners.g.cs
+++ b/managed/CounterStrikeSharp.API/Core/Listeners.g.cs
@@ -163,5 +163,12 @@ public class Listeners {
/// Resource Manifest
[ListenerName("OnServerPrecacheResources")]
public delegate void OnServerPrecacheResources(ResourceManifest manifest);
+
+ ///
+ /// Called when checking transmit on entities.
+ ///
+ /// Transmit info list
+ [ListenerName("CheckTransmit")]
+ public delegate void CheckTransmit([CastFrom(typeof(nint))]CCheckTransmitInfoList infoList);
}
-}
\ No newline at end of file
+}
diff --git a/managed/CounterStrikeSharp.API/Core/Model/CCheckTransmitInfo.cs b/managed/CounterStrikeSharp.API/Core/Model/CCheckTransmitInfo.cs
new file mode 100644
index 000000000..04a9e80b6
--- /dev/null
+++ b/managed/CounterStrikeSharp.API/Core/Model/CCheckTransmitInfo.cs
@@ -0,0 +1,106 @@
+/*
+ * This file is part of CounterStrikeSharp.
+ * CounterStrikeSharp is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CounterStrikeSharp is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CounterStrikeSharp. If not, see . *
+ */
+
+using System.Collections;
+using System.Runtime.InteropServices;
+
+namespace CounterStrikeSharp.API.Core
+{
+ [StructLayout(LayoutKind.Explicit)]
+ public struct CCheckTransmitInfo
+ {
+ ///
+ /// Entity n is already marked for transmission
+ ///
+ [FieldOffset(0x0)]
+ public CFixedBitVecBase TransmitEntities;
+
+ ///
+ /// Entity n is always send even if not in PVS (HLTV and Replay only)
+ ///
+ [FieldOffset(0x8)]
+ public CFixedBitVecBase TransmitAlways;
+ };
+
+ public sealed class CCheckTransmitInfoList : NativeObject, IReadOnlyList<(CCheckTransmitInfo, CCSPlayerController?)>
+ {
+ private int CheckTransmitPlayerSlotOffset = GameData.GetOffset("CheckTransmitPlayerSlot");
+
+ private unsafe nint* Inner => (nint*)base.Handle;
+
+ public unsafe int Count { get => (int)(*(this.Inner + 1)); }
+
+ public unsafe CCheckTransmitInfoList(IntPtr pointer) : base(pointer)
+ { }
+
+ ///
+ /// Get transmit info for the given index.
+ ///
+ /// Index of the info you want to retrieve from the list, should be between 0 and '' - 1
+ ///
+ public (CCheckTransmitInfo, CCSPlayerController?) this[int index]
+ {
+ get
+ {
+ var (transmit, slot) = this.Get(index);
+ CCSPlayerController? player = Utilities.GetPlayerFromSlot(slot);
+ return (transmit, player);
+ }
+ }
+
+ ///
+ /// Get transmit info for the given index.
+ ///
+ /// Index of the info you want to retrieve from the list, should be between 0 and '' - 1
+ ///
+ private unsafe (CCheckTransmitInfo, int) Get(int index)
+ {
+ if (index < 0 || index >= this.Count)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+
+ // 'base.Handle' holds the pointer for our 'CCheckTransmitInfoList' wrapper class
+
+ // Get the pointer to the array of 'CCheckTransmitInfo'
+ nint* infoListPtr = *(nint**)this.Inner; // Dereference 'Inner' to get the pointer to the array
+
+ // Access the specific 'CCheckTransmitInfo*'
+ nint infoPtr = *(infoListPtr + index);
+
+ // Retrieve the 'CCheckTransmitInfo' from the pointer
+ CCheckTransmitInfo info = Marshal.PtrToStructure(infoPtr);
+
+ // Get player slot from the 'infoPtr' using the 'CheckTransmitPlayerSlotOffset' offset
+ int playerSlot = *(int*)((byte*)infoPtr + CheckTransmitPlayerSlotOffset);
+
+ return (info, playerSlot);
+ }
+
+ public IEnumerator<(CCheckTransmitInfo, CCSPlayerController?)> GetEnumerator()
+ {
+ for (int i = 0; i < this.Count; i++)
+ {
+ yield return this[i];
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+ }
+}
diff --git a/managed/CounterStrikeSharp.API/Core/Model/CFixedBitVecBase.cs b/managed/CounterStrikeSharp.API/Core/Model/CFixedBitVecBase.cs
new file mode 100644
index 000000000..55ef6ec0e
--- /dev/null
+++ b/managed/CounterStrikeSharp.API/Core/Model/CFixedBitVecBase.cs
@@ -0,0 +1,81 @@
+/*
+ * This file is part of CounterStrikeSharp.
+ * CounterStrikeSharp is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CounterStrikeSharp is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CounterStrikeSharp. If not, see . *
+ */
+
+using System.Runtime.InteropServices;
+
+namespace CounterStrikeSharp.API.Core
+{
+ // credits: qstage
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct CFixedBitVecBase
+ {
+ private const int LOG2_BITS_PER_INT = 5;
+ private const int BITS_PER_INT = 32;
+
+ private readonly uint* m_Ints;
+
+ public void Add(CEntityInstance entityInstance) => Write(entityInstance.Index);
+
+ public void Add(int bitNum) => Write(bitNum);
+
+ public void Add(uint bitNum) => Write(bitNum);
+
+ public void Remove(CEntityInstance entityInstance) => Clear(entityInstance.Index);
+
+ public void Remove(int bitNum) => Clear(bitNum);
+
+ public void Remove(uint bitNum) => Clear(bitNum);
+
+ public bool Contains(CEntityInstance entityInstance) => Contains(entityInstance.Index);
+
+ public bool Contains(uint bitNum) => Contains((int)bitNum);
+
+ private void Clear(uint bitNum) => Clear((int)bitNum);
+
+ private void Write(uint bitNum) => Write((int)bitNum);
+
+ private void Clear(int bitNum)
+ {
+ if (!(bitNum >= 0 && bitNum < Utilities.MaxEdicts))
+ return;
+
+ uint* pInt = m_Ints + BitVec_Int(bitNum);
+ *pInt &= ~(uint)BitVec_Bit(bitNum);
+ }
+
+ private void Write(int bitNum)
+ {
+ if (!(bitNum >= 0 && bitNum < Utilities.MaxEdicts))
+ return;
+
+ uint* pInt = m_Ints + BitVec_Int(bitNum);
+ *pInt |= (uint)BitVec_Bit(bitNum);
+ }
+
+ public bool Contains(int bitNum)
+ {
+ if (!(bitNum >= 0 && bitNum < Utilities.MaxEdicts))
+ return false;
+
+ uint* pInt = m_Ints + BitVec_Int(bitNum);
+ return (*pInt & (uint)BitVec_Bit(bitNum)) != 0;
+ }
+
+ private int BitVec_Int(int bitNum) => bitNum >> LOG2_BITS_PER_INT;
+
+ private int BitVec_Bit(int bitNum) => 1 << (bitNum & (BITS_PER_INT - 1));
+ }
+}
diff --git a/managed/CounterStrikeSharp.sln b/managed/CounterStrikeSharp.sln
index e0d6f5040..82ccb2078 100644
--- a/managed/CounterStrikeSharp.sln
+++ b/managed/CounterStrikeSharp.sln
@@ -1,5 +1,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35309.182
+MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPlugin", "TestPlugin\TestPlugin.csproj", "{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CounterStrikeSharp.API", "CounterStrikeSharp.API\CounterStrikeSharp.API.csproj", "{55B47E41-61AA-4D75-9069-CB14328107B7}"
@@ -42,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithSharedTypesConsumer", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithUserMessages", "..\examples\WithUserMessages\WithUserMessages.csproj", "{A14029BA-CADE-4F25-ADC5-48CF14332F61}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithCheckTransmit", "..\examples\WithCheckTransmit\WithCheckTransmit.csproj", "{854B06B5-0E7B-438A-BA51-3F299557F884}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -108,14 +113,14 @@ Global
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.Build.0 = Release|Any CPU
- {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Release|Any CPU.Build.0 = Release|Any CPU
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E5289B5-E81D-421C-B340-B98B6FFE09D1}.Release|Any CPU.Build.0 = Release|Any CPU
{A37676EA-CF2F-424D-85A1-C359D07A679D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A37676EA-CF2F-424D-85A1-C359D07A679D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A37676EA-CF2F-424D-85A1-C359D07A679D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -128,6 +133,13 @@ Global
{A14029BA-CADE-4F25-ADC5-48CF14332F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A14029BA-CADE-4F25-ADC5-48CF14332F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A14029BA-CADE-4F25-ADC5-48CF14332F61}.Release|Any CPU.Build.0 = Release|Any CPU
+ {854B06B5-0E7B-438A-BA51-3F299557F884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {854B06B5-0E7B-438A-BA51-3F299557F884}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {854B06B5-0E7B-438A-BA51-3F299557F884}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {854B06B5-0E7B-438A-BA51-3F299557F884}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
@@ -141,10 +153,14 @@ Global
{31EABE0B-871F-497B-BF36-37FFC6FAD15F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{BB44E08E-CCA8-4E22-A132-11B2F69D1890} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{6FA3107D-42AF-42A0-BF51-2230D13268B5} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
- {4E5289B5-E81D-421C-B340-B98B6FFE09D1} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{1309954E-FAF7-47A5-9FF9-C7263B33E4E3} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
+ {4E5289B5-E81D-421C-B340-B98B6FFE09D1} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{A37676EA-CF2F-424D-85A1-C359D07A679D} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{76AD7BB0-A096-4336-83E2-B32CAE4E9933} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{A14029BA-CADE-4F25-ADC5-48CF14332F61} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
+ {854B06B5-0E7B-438A-BA51-3F299557F884} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {069C4CD4-BACA-446A-A6B8-0194E4F75355}
EndGlobalSection
EndGlobal
diff --git a/managed/TestPlugin/TestPlugin.cs b/managed/TestPlugin/TestPlugin.cs
index a4640c586..66982f9aa 100644
--- a/managed/TestPlugin/TestPlugin.cs
+++ b/managed/TestPlugin/TestPlugin.cs
@@ -15,6 +15,7 @@
*/
using System;
+using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
@@ -301,6 +302,26 @@ private void SetupListeners()
return;
}
});
+
+ // Hide every door (prop_door_rotating) for everyone as a test
+ RegisterListener((CCheckTransmitInfoList infoList) =>
+ {
+ IEnumerable doors = Utilities.FindAllEntitiesByDesignerName("prop_door_rotating");
+
+ if (!doors.Any())
+ return;
+
+ foreach ((CCheckTransmitInfo info, CCSPlayerController? player) in infoList)
+ {
+ if (player == null)
+ continue;
+
+ foreach (CPropDoorRotating door in doors)
+ {
+ info.TransmitEntities.Remove(door);
+ }
+ }
+ });
}
private void SetupCommands()
diff --git a/src/core/globals.cpp b/src/core/globals.cpp
index bd0cecfff..941752c06 100644
--- a/src/core/globals.cpp
+++ b/src/core/globals.cpp
@@ -78,6 +78,7 @@ ISmmAPI* ismm = nullptr;
CGameEntitySystem* entitySystem = nullptr;
CCoreConfig* coreConfig = nullptr;
CGameConfig* gameConfig = nullptr;
+ISource2GameEntities* gameEntities = nullptr;
// Custom Managers
CallbackManager callbackManager;
diff --git a/src/core/globals.h b/src/core/globals.h
index 531a340da..b834d9770 100644
--- a/src/core/globals.h
+++ b/src/core/globals.h
@@ -94,6 +94,7 @@ extern ISource2Server* server;
extern CGlobalEntityList* globalEntityList;
extern EntityListener entityListener;
extern CGameEntitySystem* entitySystem;
+extern ISource2GameEntities* gameEntities;
extern EventManager eventManager;
extern UserMessageManager userMessageManager;
diff --git a/src/core/managers/entity_manager.cpp b/src/core/managers/entity_manager.cpp
index d27ada326..8f5d4252a 100644
--- a/src/core/managers/entity_manager.cpp
+++ b/src/core/managers/entity_manager.cpp
@@ -24,14 +24,21 @@
#include
#include "scripting/callback_manager.h"
+SH_DECL_HOOK7_void(ISource2GameEntities, CheckTransmit, SH_NOATTRIB, 0, CCheckTransmitInfo**, int, CBitVec<16384>&, const Entity2Networkable_t**, const uint16*, int, bool);
+
namespace counterstrikesharp {
EntityManager::EntityManager() {}
EntityManager::~EntityManager() {}
+CCheckTransmitInfoList::CCheckTransmitInfoList(CCheckTransmitInfo** pInfoInfoList, int nInfoCount) : infoList(pInfoInfoList), infoCount(nInfoCount) {}
+
void EntityManager::OnAllInitialized()
{
+ SH_ADD_HOOK_MEMFUNC(ISource2GameEntities, CheckTransmit, globals::gameEntities, this, &EntityManager::CheckTransmit, true);
+
+ check_transmit = globals::callbackManager.CreateCallback("CheckTransmit");
on_entity_spawned_callback = globals::callbackManager.CreateCallback("OnEntitySpawned");
on_entity_created_callback = globals::callbackManager.CreateCallback("OnEntityCreated");
on_entity_deleted_callback = globals::callbackManager.CreateCallback("OnEntityDeleted");
@@ -75,7 +82,10 @@ void EntityManager::OnShutdown()
globals::callbackManager.ReleaseCallback(on_entity_created_callback);
globals::callbackManager.ReleaseCallback(on_entity_deleted_callback);
globals::callbackManager.ReleaseCallback(on_entity_parent_changed_callback);
+ globals::callbackManager.ReleaseCallback(check_transmit);
globals::entitySystem->RemoveListenerEntity(&entityListener);
+
+ SH_REMOVE_HOOK_MEMFUNC(ISource2GameEntities, CheckTransmit, globals::gameEntities, this, &EntityManager::CheckTransmit, true);
}
void CEntityListener::OnEntitySpawned(CEntityInstance* pEntity)
@@ -156,6 +166,21 @@ void EntityManager::UnhookEntityOutput(const char* szClassname, const char* szOu
}
}
+void EntityManager::CheckTransmit(CCheckTransmitInfo** pInfoInfoList, int nInfoCount, CBitVec<16384>& unionTransmitEdicts, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntityIndices, bool bEnablePVSBits)
+{
+ auto callback = globals::entityManager.check_transmit;
+
+ if (callback && callback->GetFunctionCount()) {
+ CCheckTransmitInfoList* infoList = new CCheckTransmitInfoList(pInfoInfoList, nInfoCount);
+
+ callback->ScriptContext().Reset();
+ callback->ScriptContext().Push(infoList);
+ callback->Execute();
+
+ delete infoList;
+ }
+}
+
void DetourFireOutputInternal(CEntityIOOutput* const pThis, CEntityInstance* pActivator,
CEntityInstance* pCaller, const CVariant* const value, float flDelay)
{
diff --git a/src/core/managers/entity_manager.h b/src/core/managers/entity_manager.h
index 4aaeebc20..793ac895a 100644
--- a/src/core/managers/entity_manager.h
+++ b/src/core/managers/entity_manager.h
@@ -39,6 +39,14 @@ class CEntityListener : public IEntityListener {
void OnEntityParentChanged(CEntityInstance *pEntity, CEntityInstance *pNewParent) override;
};
+class CCheckTransmitInfoList {
+public:
+ CCheckTransmitInfoList(CCheckTransmitInfo** pInfoInfoList, int nInfoCount);
+private:
+ CCheckTransmitInfo** infoList;
+ int infoCount;
+};
+
class EntityManager : public GlobalClass {
friend CEntityListener;
public:
@@ -51,10 +59,13 @@ class EntityManager : public GlobalClass {
CEntityListener entityListener;
std::map m_pHookMap;
private:
+ void CheckTransmit(CCheckTransmitInfo** pInfoInfoList, int nInfoCount, CBitVec<16384>& unionTransmitEdicts, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntityIndices, bool bEnablePVSBits);
+
ScriptCallback *on_entity_spawned_callback;
ScriptCallback *on_entity_created_callback;
ScriptCallback *on_entity_deleted_callback;
ScriptCallback *on_entity_parent_changed_callback;
+ ScriptCallback *check_transmit;
};
diff --git a/src/mm_plugin.cpp b/src/mm_plugin.cpp
index f7ae54ea0..ca8bbdb44 100644
--- a/src/mm_plugin.cpp
+++ b/src/mm_plugin.cpp
@@ -102,6 +102,7 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
GET_V_IFACE_ANY(GetEngineFactory, globals::gameEventSystem, IGameEventSystem, GAMEEVENTSYSTEM_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, globals::engineServiceManager, IEngineServiceMgr, ENGINESERVICEMGR_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, globals::networkMessages, INetworkMessages, NETWORKMESSAGES_INTERFACE_VERSION);
+ GET_V_IFACE_ANY(GetServerFactory, globals::gameEntities, ISource2GameEntities, SOURCE2GAMEENTITIES_INTERFACE_VERSION);
auto coreconfig_path = std::string(utils::ConfigsDirectory() + "/core");
globals::coreConfig = new CCoreConfig(coreconfig_path);
diff --git a/src/scripting/listeners/entities.yaml b/src/scripting/listeners/entities.yaml
index e31122698..94907b103 100644
--- a/src/scripting/listeners/entities.yaml
+++ b/src/scripting/listeners/entities.yaml
@@ -1,4 +1,5 @@
OnEntitySpawned: entity:pointer
OnEntityCreated: entity:pointer
OnEntityDeleted: entity:pointer
-OnEntityParentChanged: entity:pointer, newParent:pointer
\ No newline at end of file
+OnEntityParentChanged: entity:pointer, newParent:pointer
+CheckTransmit: infoList:pointer