Files
CounterStrikeSharp/managed/CounterStrikeSharp.API/Utilities.cs
2025-08-04 09:28:05 +10:00

268 lines
9.6 KiB
C#

/*
* 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 CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using CounterStrikeSharp.API.Core.Logging;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API
{
public static class Utilities
{
// https://github.com/dotabuff/manta/blob/master/entity.go#L186-L190
public const int MaxEdictBits = 15;
public const int MaxEdicts = 1 << MaxEdictBits;
public const int NumEHandleSerialNumberBits = 17;
public const uint InvalidEHandleIndex = 0xFFFFFFFF;
public static IEnumerable<T> FlagsToList<T>(this T flags) where T : Enum
{
return Enum.GetValues(typeof(T)).Cast<T>()
.Where(x => flags.HasFlag(x)).AsEnumerable();
}
public static T? GetEntityFromIndex<T>(int index) where T : CEntityInstance
{
var entityPtr = EntitySystem.GetEntityByIndex((uint)index);
if (entityPtr is null || entityPtr == IntPtr.Zero)
{
return null;
}
return (T)Activator.CreateInstance(typeof(T), entityPtr)!;
}
public static T? CreateEntityByName<T>(string name) where T : CBaseEntity
{
return (T?)Activator.CreateInstance(typeof(T), VirtualFunctions.UTIL_CreateEntityByName(name, -1));
}
public static CCSPlayerController? GetPlayerFromIndex(int index)
{
var player = GetEntityFromIndex<CCSPlayerController>(index);
if (player == null || player.DesignerName != "cs_player_controller")
{
return null;
}
return player;
}
public static CCSPlayerController? GetPlayerFromSlot(int slot)
{
return GetPlayerFromIndex(slot + 1);
}
public static CCSPlayerController? GetPlayerFromUserid(int userid)
{
return GetPlayerFromIndex((userid & 0xFF) + 1);
}
public static CCSPlayerController? GetPlayerFromSteamId(ulong steamId)
{
return Utilities.GetPlayers().FirstOrDefault(player => player.AuthorizedSteamID == (SteamID)steamId);
}
public static TargetResult ProcessTargetString(string pattern, CCSPlayerController player)
{
return new Target(pattern).GetTarget(player);
}
public static bool RemoveItemByDesignerName(this CCSPlayerController player, string designerName)
{
return RemoveItemByDesignerName(player, designerName, false);
}
public static bool RemoveItemByDesignerName(this CCSPlayerController player, string designerName, bool shouldRemoveEntity)
{
CHandle<CBasePlayerWeapon>? item = null;
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.WeaponServices == null) return false;
foreach (var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
{
if (weapon is not { IsValid: true, Value.IsValid: true })
continue;
if (weapon.Value.DesignerName != designerName)
continue;
item = weapon;
}
if (item != null && item.Value != null)
{
player.PlayerPawn.Value.RemovePlayerItem(item.Value);
if (shouldRemoveEntity)
{
item.Value.Remove();
}
return true;
}
return false;
}
public static IEnumerable<T> FindAllEntitiesByDesignerName<T>(string designerName) where T : CEntityInstance
{
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.Handle != IntPtr.Zero; pEntity = pEntity.Next)
{
if (pEntity.DesignerName == null || !pEntity.DesignerName.Contains(designerName)) continue;
yield return new PointerTo<T>(pEntity.Handle).Value;
}
}
public static IEnumerable<CEntityInstance> GetAllEntities()
{
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.Handle != IntPtr.Zero; pEntity = pEntity.Next)
{
yield return new PointerTo<CEntityInstance>(pEntity.Handle).Value;
}
}
/// <summary>
/// Returns a list of <see cref="CCSPlayerController"/> that are valid and have a valid <see cref="CCSPlayerController.UserId"/> >= 0
/// </summary>
public static List<CCSPlayerController> GetPlayers()
{
List<CCSPlayerController> players = new();
for (int i = 0; i < Server.MaxPlayers; i++)
{
var controller = GetPlayerFromSlot(i);
if (controller == null || !controller.IsValid || controller.Connected != PlayerConnectedState.PlayerConnected)
continue;
players.Add(controller);
}
return players;
}
[Obsolete]
public static void ReplyToCommand(CCSPlayerController? player, string msg, bool console = false)
{
if (player != null)
{
if (console) player.PrintToConsole(msg);
else player.PrintToChat(msg);
}
else
{
Server.PrintToConsole(msg);
}
}
public static unsafe string ReadStringUtf8(IntPtr ptr)
{
unsafe
{
var nativeUtf8 = Unsafe.Read<IntPtr>((void*)ptr);
if (nativeUtf8 == IntPtr.Zero)
{
return null;
}
var len = 0;
while (Marshal.ReadByte(nativeUtf8, len) != 0)
{
++len;
}
var buffer = new byte[len];
Marshal.Copy(nativeUtf8, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
}
}
public static T? GetPointer<T>(IntPtr pointer) where T : NativeObject
{
var pointerTo = Marshal.ReadIntPtr(pointer);
if (pointerTo == IntPtr.Zero)
{
return null;
}
return (T)Activator.CreateInstance(typeof(T), pointerTo)!;
}
private static int FindSchemaChain(string className) => Schema.GetSchemaOffset(className, "__m_pChainEntity");
/// <summary>
/// Marks a field as changed for network transmission.
/// Not all schema fields are network enabled, so please check the schema before using this.
/// </summary>
/// <param name="entity">Entity to update</param>
/// <param name="className" example="CBaseEntity">Schema field class name</param>
/// <param name="fieldName" example="m_iHealth">Schema field name</param>
/// <param name="extraOffset">Any additional offset to the schema field</param>
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
public static void SetStateChanged(CBaseEntity entity, string className, string fieldName, int extraOffset = 0)
{
Guard.IsValidEntity(entity);
if (!Schema.IsSchemaFieldNetworked(className, fieldName))
{
Application.Instance.Logger.LogWarning(
"Field {ClassName}:{FieldName} is not networked, but SetStateChanged was called on it.", className, fieldName);
return;
}
int offset = Schema.GetSchemaOffset(className, fieldName);
int chainOffset = FindSchemaChain(className);
if (chainOffset != 0)
{
NativeAPI.SchemaNetworkStateChanged(entity.Handle + chainOffset, (uint)(offset + extraOffset), 0xFFFFFFFF, 0xFFFFFFFF);
return;
}
NativeAPI.SchemaSetStateChanged(entity.Handle, (uint)(offset + extraOffset), 0xFFFFFFFF, 0xFFFFFFFF);
entity.LastNetworkChange = Server.CurrentTime;
entity.IsSteadyState.Clear();
}
/// <summary>
/// metamod method 'MetaFactory' to get the pointer of api interface exposed by metamod plugins.
/// Returns null when the interface cannot be found.
/// </summary>
/// <param name="interfaceName">The interface name of metamod api, can be found in their api header file</param>
public static IntPtr? MetaFactory(string interfaceName)
{
IntPtr ptr = NativeAPI.MetaFactory(interfaceName);
if (ptr == 0)
{
return null;
}
return ptr;
}
}
}