Implemented entity transmit feature
With this implementation, developers are able to control the entity transmit per player
Examples
These are simple examples, but it can be used for way more complex things.
// hide every door for everyone as an example
RegisterListener<Listeners.CheckTransmit>((CCheckTransmitInfoList infoList) =>
{
IEnumerable<CPropDoorRotating> doors = Utilities.FindAllEntitiesByDesignerName<CPropDoorRotating>("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);
}
}
});
Overall the PR looks great!😍 I am wondering though if we can possibly improve the DX of iterating over the transmit list. Maybe something like this (pseudo-ish code)?
RegisterListener<Listeners.CheckTransmit>((CCheckTransmitInfoList infoList, int infoCount) =>
{
// 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;
foreach(var (CFixedBitVecBase transmitEntities, CCSPlayerController? player) in infoList)
{
if (player == null || !ShouldSeeDoors.ContainsKey(player.Slot))
continue;
foreach (CPropDoorRotating door in doors)
{
transmitEntities.Remove(door);
}
}
});
The idea being that if we can provide access to an IEnumerable ourselves (or preferably IReadOnlyList<T> so that we can maintan index access), we can handle the bounds checking and prevent issues with that, as well as simplifying the access to the entities list & player.
Example enumerator implementation on CCheckTransmitInfoList. But we have to somehow pass the length of the transmit list through from native to managed, which looks hard with the current implementation simply casting from a pointer:
public sealed class CCheckTransmitInfoList : NativeObject,
IReadOnlyList<(CFixedBitVecBase TransmitEntities, CCSPlayerController? Player)>
{
private int CheckTransmitPlayerSlotOffset = GameData.GetOffset("CheckTransmitPlayerSlot");
private unsafe nint* Inner => (nint*)base.Handle;
public unsafe CCheckTransmitInfoList(IntPtr pointer) : base(pointer)
{
}
public int Count { get; }
private unsafe (CCheckTransmitInfo, int) Get(int index)
{
return (Marshal.PtrToStructure<CCheckTransmitInfo>(this.Inner[index]),
(int)(*(byte*)(this.Inner[index] + CheckTransmitPlayerSlotOffset)));
}
public IEnumerator<(CFixedBitVecBase TransmitEntities, CCSPlayerController? Player)> GetEnumerator()
{
for (int i = 0; i < this.Count; i++)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <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 'infoCount' - 1</param>
/// <returns></returns>
public (CFixedBitVecBase TransmitEntities, CCSPlayerController? Player) this[int index]
{
get
{
// Ideally throw here if out of range
var (transmit, slot) = this.Get(index);
CCSPlayerController? player = Utilities.GetPlayerFromSlot(slot);
return (transmit.TransmitEntities, player);
}
}
}
Overall the PR looks great!😍 I am wondering though if we can possibly improve the DX of iterating over the transmit list. Maybe something like this (pseudo-ish code)?
RegisterListener<Listeners.CheckTransmit>((CCheckTransmitInfoList infoList, int infoCount) => { // 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; foreach(var (CFixedBitVecBase transmitEntities, CCSPlayerController? player) in infoList) { if (player == null || !ShouldSeeDoors.ContainsKey(player.Slot)) continue; foreach (CPropDoorRotating door in doors) { transmitEntities.Remove(door); } } });The idea being that if we can provide access to an
IEnumerableourselves (or preferablyIReadOnlyList<T>so that we can maintan index access), we can handle the bounds checking and prevent issues with that, as well as simplifying the access to the entities list & player.Example enumerator implementation on
CCheckTransmitInfoList. But we have to somehow pass the length of the transmit list through from native to managed, which looks hard with the current implementation simply casting from a pointer:public sealed class CCheckTransmitInfoList : NativeObject, IReadOnlyList<(CFixedBitVecBase TransmitEntities, CCSPlayerController? Player)> { private int CheckTransmitPlayerSlotOffset = GameData.GetOffset("CheckTransmitPlayerSlot"); private unsafe nint* Inner => (nint*)base.Handle; public unsafe CCheckTransmitInfoList(IntPtr pointer) : base(pointer) { } public int Count { get; } private unsafe (CCheckTransmitInfo, int) Get(int index) { return (Marshal.PtrToStructure<CCheckTransmitInfo>(this.Inner[index]), (int)(*(byte*)(this.Inner[index] + CheckTransmitPlayerSlotOffset))); } public IEnumerator<(CFixedBitVecBase TransmitEntities, CCSPlayerController? Player)> GetEnumerator() { for (int i = 0; i < this.Count; i++) { yield return this[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// <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 'infoCount' - 1</param> /// <returns></returns> public (CFixedBitVecBase TransmitEntities, CCSPlayerController? Player) this[int index] { get { // Ideally throw here if out of range var (transmit, slot) = this.Get(index); CCSPlayerController? player = Utilities.GetPlayerFromSlot(slot); return (transmit.TransmitEntities, player); } } }
Great idea! Will take a look on it
Working properly on a test environment! Might be worth adding a warning if hiding a pawn, as killing a hidden pawn crashes the client.
cc @KillStr3aK
Well, currently its only noted in the example plugin but devs should be aware of this issue