Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented entity transmit feature #608

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c7c0e8c
chore: bump to latest hl2sdk
KillStr3aK Oct 6, 2024
35fca3f
feat: get interface `ISource2GameEntities`
KillStr3aK Oct 6, 2024
850a389
feat: Add `CheckTransmit`
KillStr3aK Oct 6, 2024
148fbf8
feat: Add `CheckTransmitPlayerSlot`
KillStr3aK Oct 6, 2024
2e2771e
feat: Add hook and listener for `CheckTransmit`
KillStr3aK Oct 6, 2024
01479af
feat: added `CheckTransmit` listener
KillStr3aK Oct 6, 2024
265cfb8
feat: Added `CFixedBitVecBase` (thanks: qstage)
KillStr3aK Oct 6, 2024
ce19fe8
feat: implemented `CCheckTransmitInfoList` and added `CCheckTransmitI…
KillStr3aK Oct 6, 2024
9d03cb4
feat: added example for `CheckTransmit` listener
KillStr3aK Oct 6, 2024
82a2346
Merge branch 'roflmuffin:main' into feature/entity-transmit
KillStr3aK Oct 6, 2024
23f625b
feat: added `WithCheckTransmit` example
KillStr3aK Oct 6, 2024
3519a95
Merge branch 'feature/entity-transmit' of https://github.com/KillStr3…
KillStr3aK Oct 6, 2024
0cdbc1d
fix: added check for self
KillStr3aK Oct 6, 2024
46c323f
chore: update and extended examples
KillStr3aK Oct 6, 2024
c12ed80
fix: continue instead of return
KillStr3aK Oct 6, 2024
d479cac
tweak: reuse code from utils
KillStr3aK Oct 7, 2024
c507f6f
tweak: renamed to `TransmitEntities`
KillStr3aK Oct 9, 2024
6c1125f
feat: `CFixedBitVecBase::Write`
KillStr3aK Oct 9, 2024
e6a1248
tweak: adjusted naming
KillStr3aK Oct 9, 2024
b20aaa5
feat: methods with `CEntityInstance` params
KillStr3aK Oct 9, 2024
9a69a63
tweak: adjusted changes
KillStr3aK Oct 9, 2024
0eac0a9
tweak: adjusted
KillStr3aK Oct 9, 2024
17170b5
Merge branch 'roflmuffin:main' into feature/entity-transmit
KillStr3aK Oct 10, 2024
a3d5e14
feat: added `TransmitAlways` to `CCheckTransmitInfo`
KillStr3aK Oct 10, 2024
6fd68b5
feat: added wrapper `CCheckTransmitInfoList`
KillStr3aK Oct 10, 2024
2c9db33
tweak: removed `infoCount`
KillStr3aK Oct 10, 2024
a99cd58
tweak: adjusted changes and supports `Count`
KillStr3aK Oct 10, 2024
663f07e
tweak: adjusted changes
KillStr3aK Oct 10, 2024
6f9e465
feat: throw exception when oob
KillStr3aK Oct 10, 2024
3ed0c0d
tweak: break down code
KillStr3aK Oct 10, 2024
6782b93
style: adjusted code
KillStr3aK Oct 10, 2024
700550d
style: adjusted code
KillStr3aK Oct 10, 2024
c516cce
fix: adjusted code to return full info
KillStr3aK Oct 10, 2024
daf35f5
tweak: `CCheckTransmitInfoList.Get` is now private
KillStr3aK Oct 10, 2024
441485a
style: fix indentitation
KillStr3aK Oct 10, 2024
c161dcc
Merge branch 'main' into feature/entity-transmit
KillStr3aK Oct 11, 2024
f887fed
Merge branch 'main' into feature/entity-transmit
KillStr3aK Oct 13, 2024
dd24d8c
Merge branch 'main' into feature/entity-transmit
KillStr3aK Oct 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions configs/addons/counterstrikesharp/gamedata/gamedata.json
Original file line number Diff line number Diff line change
Expand Up @@ -253,5 +253,11 @@
"windows": 2,
"linux": 0
}
},
"CheckTransmitPlayerSlot": {
"offsets": {
"windows": 584,
"linux": 584
}
}
}
2 changes: 2 additions & 0 deletions examples/WithCheckTransmit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# With CheckTransmit
This example shows how to work with the `CheckTransmit` listener.
13 changes: 13 additions & 0 deletions examples/WithCheckTransmit/WithCheckTransmit.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>

</Project>
115 changes: 115 additions & 0 deletions examples/WithCheckTransmit/WithCheckTransmitPlugin.cs
Original file line number Diff line number Diff line change
@@ -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<int, bool> ShouldSeeDoors = new Dictionary<int, bool>();

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<Listeners.CheckTransmit>((CCheckTransmitInfoList infoList) =>
{
// Get the list of the currently available doors (prop_door_rotating)
IEnumerable<CPropDoorRotating> doors = Utilities.FindAllEntitiesByDesignerName<CPropDoorRotating>("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<Listeners.CheckTransmit>((CCheckTransmitInfoList infoList) =>
{
// Get the list of the current players, we only work with this value later on
List<CCSPlayerController> 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<CCSPlayerController> 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);
}
}
});
}
}
9 changes: 8 additions & 1 deletion managed/CounterStrikeSharp.API/Core/Listeners.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,12 @@ public class Listeners {
/// <param name="manifest">Resource Manifest</param>
[ListenerName("OnServerPrecacheResources")]
public delegate void OnServerPrecacheResources(ResourceManifest manifest);

/// <summary>
/// Called when checking transmit on entities.
/// </summary>
/// <param name="infoList">Transmit info list</param>
[ListenerName("CheckTransmit")]
public delegate void CheckTransmit([CastFrom(typeof(nint))]CCheckTransmitInfoList infoList);
}
}
}
106 changes: 106 additions & 0 deletions managed/CounterStrikeSharp.API/Core/Model/CCheckTransmitInfo.cs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>. *
*/

using System.Collections;
using System.Runtime.InteropServices;

namespace CounterStrikeSharp.API.Core
{
[StructLayout(LayoutKind.Explicit)]
public struct CCheckTransmitInfo
{
/// <summary>
/// Entity n is already marked for transmission
/// </summary>
[FieldOffset(0x0)]
public CFixedBitVecBase TransmitEntities;

/// <summary>
/// Entity n is always send even if not in PVS (HLTV and Replay only)
/// </summary>
[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)
{ }

/// <summary>
/// Get transmit info for the given index.
/// </summary>
/// <param name="index">Index of the info you want to retrieve from the list, should be between 0 and '<see cref="Count"/>' - 1</param>
/// <returns></returns>
public (CCheckTransmitInfo, CCSPlayerController?) this[int index]
{
get
{
var (transmit, slot) = this.Get(index);
CCSPlayerController? player = Utilities.GetPlayerFromSlot(slot);
return (transmit, player);
}
}

/// <summary>
/// Get transmit info for the given index.
/// </summary>
/// <param name="index">Index of the info you want to retrieve from the list, should be between 0 and '<see cref="Count"/>' - 1</param>
/// <returns></returns>
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<CCheckTransmitInfo>(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();
}
}
}
81 changes: 81 additions & 0 deletions managed/CounterStrikeSharp.API/Core/Model/CFixedBitVecBase.cs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>. *
*/

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));
}
}
Loading
Loading