using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Modules.Timers; using SendProxyInterop; using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; namespace SendProxyConcealedCarry; public sealed class SendProxyConcealedCarryPlugin : BasePlugin { private const string WeaponListField = "m_pWeaponServices.m_hMyWeapons"; private const string ActiveWeaponField = "m_pWeaponServices.m_hActiveWeapon"; private const string EntityClass = "CBaseEntity"; private static readonly byte[] InvalidHandle = UInt32Bytes(0xffffffff); private static readonly byte[] EmptyToken = UInt32Bytes(0); private static readonly byte[] NoAttachment = Int16Bytes(-1); private static readonly byte[] FalseByte = [0]; private readonly Dictionary _activeRules = new(); private SendProxyNative? _sendProxy; private Timer? _syncTimer; public override string ModuleName => "SendProxy Concealed Carry"; public override string ModuleVersion => "0.1.0"; public override string ModuleAuthor => "OpenAI Codex"; public override void Load(bool hotReload) { _sendProxy = SendProxyNative.FromGameDirectory(Server.GameDirectory); _syncTimer = AddTimer(0.25f, SyncRules, TimerFlags.REPEAT); RegisterListener(_ => Server.NextFrame(SyncRules)); RegisterListener(_ => Server.NextFrame(SyncRules)); Server.NextFrame(SyncRules); } public override void Unload(bool hotReload) { _syncTimer?.Kill(); ClearRules(); _sendProxy?.Dispose(); _sendProxy = null; } private void SyncRules() { if (_sendProxy == null) return; Dictionary desired; try { var viewers = Utilities.GetPlayers() .Where(player => player.IsValid && !player.IsBot && !player.IsHLTV) .ToArray(); var targets = Utilities.GetPlayers() .Where(player => player.IsValid && !player.IsHLTV) .ToArray(); var targetStatesByPawnIndex = targets .Select(player => new { player.Slot, Pawn = player.PlayerPawn.Value, }) .Where(entry => entry.Pawn != null && entry.Pawn.IsValid) .GroupBy(entry => (int)entry.Pawn!.Index) .ToDictionary( group => group.Key, group => { var first = group.First(); var activeWeapon = first.Pawn!.WeaponServices?.ActiveWeapon.Value; int activeWeaponIndex = activeWeapon != null && activeWeapon.IsValid ? (int)activeWeapon.Index : -1; return new TargetState(first.Slot, activeWeaponIndex); }); desired = BuildDesiredRules(viewers, targetStatesByPawnIndex); } catch (NativeException) { return; } ReconcileRules(desired); } private Dictionary BuildDesiredRules( IReadOnlyCollection viewers, IReadOnlyDictionary targetStatesByPawnIndex) { var desired = new Dictionary(); foreach (var (pawnIndex, target) in targetStatesByPawnIndex) { foreach (var viewer in viewers) { if (viewer.Slot == target.OwnerSlot) continue; desired[new RuleKey( ToEngineSlot(viewer), pawnIndex, "CBasePlayerPawn", WeaponListField)] = Array.Empty(); } } foreach (var weapon in EnumerateConcealableCarriedWeapons(targetStatesByPawnIndex)) { if (weapon.EntityIndex == weapon.ActiveWeaponIndex) continue; foreach (var viewer in viewers) { if (viewer.Slot == weapon.OwnerSlot) continue; int recipient = ToEngineSlot(viewer); AddAssociationDisconnectRules(desired, recipient, weapon.EntityIndex); } } return desired; } private void ReconcileRules(Dictionary desired) { if (_sendProxy == null) return; foreach (var key in _activeRules.Keys.Except(desired.Keys).ToArray()) { _sendProxy.RemoveOverride(_activeRules[key]); _activeRules.Remove(key); } foreach (var (key, value) in desired) { if (_activeRules.ContainsKey(key)) continue; int ruleId = value.Length == 0 ? _sendProxy.SetVectorFirstFromField( key.RecipientSlot, key.EntityIndex, key.ClassName, key.FieldPath, ActiveWeaponField) : _sendProxy.SetBytes( key.RecipientSlot, key.EntityIndex, key.ClassName, key.FieldPath, value); if (ruleId != 0) _activeRules[key] = ruleId; } } private IEnumerable EnumerateConcealableCarriedWeapons(IReadOnlyDictionary targetStatesByPawnIndex) { foreach (var entity in Utilities.GetAllEntities()) { if (!entity.IsValid || !entity.DesignerName.StartsWith("weapon_", StringComparison.Ordinal)) continue; CBasePlayerWeapon weapon = entity.As(); if (!weapon.IsValid) continue; var owner = weapon.OwnerEntity.Value; if (owner == null || !owner.IsValid) continue; if (!targetStatesByPawnIndex.TryGetValue((int)owner.Index, out var target)) continue; yield return new ConcealableWeapon( (int)weapon.Index, target.OwnerSlot, target.ActiveWeaponIndex); } } private static byte[] Int16Bytes(short value) { byte[] bytes = new byte[2]; BitConverter.TryWriteBytes(bytes, value); return bytes; } private static byte[] UInt32Bytes(uint value) { byte[] bytes = new byte[4]; BitConverter.TryWriteBytes(bytes, value); return bytes; } private static void AddAssociationDisconnectRules(Dictionary desired, int recipientSlot, int weaponEntityIndex) { desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_hOwnerEntity")] = InvalidHandle; desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_hParent.m_hOwner")] = InvalidHandle; desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_hParent.m_name")] = EmptyToken; desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_nParentAttachmentOrBone")] = NoAttachment; desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_hierarchyAttachName")] = EmptyToken; desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_bForceParentToBeNetworked")] = FalseByte; } private static int ToEngineSlot(CCSPlayerController player) { return Math.Max(0, player.Slot - 1); } private void ClearRules() { if (_sendProxy == null) return; foreach (int ruleId in _activeRules.Values) _sendProxy.RemoveOverride(ruleId); _activeRules.Clear(); } private readonly record struct TargetState(int OwnerSlot, int ActiveWeaponIndex); private readonly record struct ConcealableWeapon(int EntityIndex, int OwnerSlot, int ActiveWeaponIndex); private readonly record struct RuleKey(int RecipientSlot, int EntityIndex, string ClassName, string FieldPath); }