initial commit -- v1 code
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Timers;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using SendProxyInterop;
|
||||
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
|
||||
|
||||
namespace SendProxyWeaponVisibility;
|
||||
|
||||
public sealed class SendProxyWeaponVisibilityPlugin : BasePlugin
|
||||
{
|
||||
private const int TeamT = 2;
|
||||
private const int TeamCt = 3;
|
||||
private const uint EfNoDraw = 0x20;
|
||||
private const int SpoofedHealth = 42;
|
||||
|
||||
private readonly Dictionary<RuleKey, int> _activeRules = new();
|
||||
private SendProxyNative? _sendProxy;
|
||||
private Timer? _syncTimer;
|
||||
|
||||
public override string ModuleName => "SendProxy Weapon Visibility";
|
||||
public override string ModuleVersion => "0.1.0";
|
||||
public override string ModuleAuthor => "OpenAI Codex";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
_sendProxy = new SendProxyNative(GetSendProxyLibraryPath());
|
||||
_syncTimer = AddTimer(0.25f, SyncRules, TimerFlags.REPEAT);
|
||||
RegisterListener<Listeners.OnEntityCreated>(_ => Server.NextFrame(SyncRules));
|
||||
RegisterListener<Listeners.OnEntityDeleted>(_ => Server.NextFrame(SyncRules));
|
||||
SyncRules();
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_syncTimer?.Kill();
|
||||
ClearNativeRules();
|
||||
_sendProxy?.Dispose();
|
||||
_sendProxy = null;
|
||||
}
|
||||
|
||||
private void SyncRules()
|
||||
{
|
||||
if (_sendProxy == null)
|
||||
return;
|
||||
|
||||
var desired = new Dictionary<RuleKey, RuleValue>();
|
||||
var viewers = Utilities.GetPlayers()
|
||||
.Where(player => player.IsValid && !player.IsBot && !player.IsHLTV)
|
||||
.ToArray();
|
||||
|
||||
AddHealthRules(desired, viewers);
|
||||
|
||||
foreach (var weapon in EnumerateWeapons())
|
||||
{
|
||||
var owner = GetOwner(weapon);
|
||||
bool dropped = owner == null;
|
||||
int weaponIndex = (int)weapon.Index;
|
||||
string designerName = weapon.DesignerName;
|
||||
|
||||
foreach (var viewer in viewers)
|
||||
{
|
||||
int recipientSlot = viewer.Slot;
|
||||
|
||||
if (owner != null && IsOtherPlayerWeapon(viewer, owner))
|
||||
{
|
||||
AddDesiredRule(
|
||||
desired,
|
||||
recipientSlot,
|
||||
weaponIndex,
|
||||
"CBaseEntity",
|
||||
"m_fEffects",
|
||||
RuleValue.UInt32(EfNoDraw));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!dropped)
|
||||
continue;
|
||||
|
||||
int team = (int)viewer.Team;
|
||||
if (team == TeamT && IsAk47(designerName))
|
||||
{
|
||||
AddDesiredRule(
|
||||
desired,
|
||||
recipientSlot,
|
||||
weaponIndex,
|
||||
"CBaseModelEntity",
|
||||
"m_clrRender",
|
||||
RuleValue.Color(255, 0, 0, 255));
|
||||
}
|
||||
else if (team == TeamCt && IsM4A1S(designerName))
|
||||
{
|
||||
AddDesiredRule(
|
||||
desired,
|
||||
recipientSlot,
|
||||
weaponIndex,
|
||||
"CBaseModelEntity",
|
||||
"m_clrRender",
|
||||
RuleValue.Color(0, 96, 255, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = _sendProxy.SetBytes(
|
||||
key.RecipientSlot,
|
||||
key.EntityIndex,
|
||||
key.ClassName,
|
||||
key.FieldPath,
|
||||
value.Bytes);
|
||||
|
||||
if (ruleId != 0)
|
||||
_activeRules[key] = ruleId;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddHealthRules(Dictionary<RuleKey, RuleValue> desired, IReadOnlyCollection<CCSPlayerController> viewers)
|
||||
{
|
||||
foreach (var viewer in viewers)
|
||||
{
|
||||
var viewerPawn = viewer.PlayerPawn.Value;
|
||||
int viewerPawnIndex = viewerPawn != null && viewerPawn.IsValid ? (int)viewerPawn.Index : -1;
|
||||
|
||||
foreach (var target in viewers)
|
||||
{
|
||||
var targetPawn = target.PlayerPawn.Value;
|
||||
if (targetPawn == null || !targetPawn.IsValid)
|
||||
continue;
|
||||
|
||||
int targetPawnIndex = (int)targetPawn.Index;
|
||||
if (targetPawnIndex == viewerPawnIndex)
|
||||
continue;
|
||||
|
||||
AddDesiredRule(
|
||||
desired,
|
||||
viewer.Slot,
|
||||
targetPawnIndex,
|
||||
"CBaseEntity",
|
||||
"m_iHealth",
|
||||
RuleValue.Int32(SpoofedHealth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddDesiredRule(
|
||||
Dictionary<RuleKey, RuleValue> desired,
|
||||
int recipientSlot,
|
||||
int entityIndex,
|
||||
string className,
|
||||
string fieldPath,
|
||||
RuleValue value)
|
||||
{
|
||||
desired[new RuleKey(recipientSlot, entityIndex, className, fieldPath)] = value;
|
||||
}
|
||||
|
||||
private IEnumerable<CBasePlayerWeapon> EnumerateWeapons()
|
||||
{
|
||||
foreach (var entity in Utilities.GetAllEntities())
|
||||
{
|
||||
if (!entity.IsValid || !entity.DesignerName.StartsWith("weapon_", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
CBasePlayerWeapon weapon = entity.As<CBasePlayerWeapon>();
|
||||
if (weapon.IsValid)
|
||||
yield return weapon;
|
||||
}
|
||||
}
|
||||
|
||||
private static CBaseEntity? GetOwner(CBasePlayerWeapon weapon)
|
||||
{
|
||||
var owner = weapon.OwnerEntity.Value;
|
||||
return owner != null && owner.IsValid ? owner : null;
|
||||
}
|
||||
|
||||
private static bool IsOtherPlayerWeapon(CCSPlayerController viewer, CBaseEntity owner)
|
||||
{
|
||||
var ownPawn = viewer.PlayerPawn.Value;
|
||||
if (ownPawn == null || !ownPawn.IsValid)
|
||||
return owner.DesignerName.Contains("player", StringComparison.Ordinal);
|
||||
|
||||
return owner.Index != ownPawn.Index && owner.DesignerName.Contains("player", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static bool IsAk47(string designerName)
|
||||
{
|
||||
return designerName.Equals("weapon_ak47", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static bool IsM4A1S(string designerName)
|
||||
{
|
||||
return designerName.Equals("weapon_m4a1_silencer", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private void ClearNativeRules()
|
||||
{
|
||||
if (_sendProxy == null)
|
||||
return;
|
||||
|
||||
foreach (int ruleId in _activeRules.Values)
|
||||
_sendProxy.RemoveOverride(ruleId);
|
||||
_activeRules.Clear();
|
||||
}
|
||||
|
||||
private string GetSendProxyLibraryPath()
|
||||
{
|
||||
string addonsDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", ".."));
|
||||
string libraryPath = Path.Combine(addonsDirectory, "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so");
|
||||
if (!File.Exists(libraryPath))
|
||||
throw new FileNotFoundException("SendProxy Metamod addon was not found. Install it under game/csgo/addons/sendproxy.", libraryPath);
|
||||
return libraryPath;
|
||||
}
|
||||
|
||||
private readonly record struct RuleKey(int RecipientSlot, int EntityIndex, string ClassName, string FieldPath);
|
||||
|
||||
private readonly record struct RuleValue(byte[] Bytes)
|
||||
{
|
||||
public static RuleValue Int32(int value)
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
BitConverter.TryWriteBytes(bytes, value);
|
||||
return new RuleValue(bytes);
|
||||
}
|
||||
|
||||
public static RuleValue UInt32(uint value)
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
BitConverter.TryWriteBytes(bytes, value);
|
||||
return new RuleValue(bytes);
|
||||
}
|
||||
|
||||
public static RuleValue Color(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
return new RuleValue(new[] { r, g, b, a });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user