using System.Drawing; using System.Numerics; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Translations; using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API.Modules.Entities; using CounterStrikeSharp.API.Modules.Memory; using Microsoft.Extensions.Localization; using System.Text; using CounterStrikeSharp.API.Modules.UserMessages; using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; namespace CS2_SimpleAdmin; public static class PlayerExtensions { /// /// Slaps the player pawn by applying optional damage and adding a random velocity knockback. /// /// The player pawn to slap. /// The amount of damage to apply (default is 0). public static void Slap(this CBasePlayerPawn pawn, int damage = 0) { PerformSlap(pawn, damage); } /// /// Prints a localized chat message to the player with a prefix. /// /// The player controller to send the message to. /// The message string. public static void Print(this CCSPlayerController controller, string message = "") { StringBuilder _message = new(CS2_SimpleAdmin._localizer!["sa_prefix"]); _message.Append(message); controller.PrintToChat(_message.ToString()); } /// /// Determines if the player controller can target another player controller, respecting admin permissions and immunity. /// /// The player controller who wants to target. /// The player controller being targeted. /// True if targeting is allowed, false otherwise. public static bool CanTarget(this CCSPlayerController? controller, CCSPlayerController? target) { if (controller is null || target is null) return true; if (target.IsBot) return true; return AdminManager.CanPlayerTarget(controller, target) || AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), new SteamID(target.SteamID)) || AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(target); } /// /// Checks if the controller can target a player by SteamID, considering targeting permissions and immunities. /// /// The attacker player controller. /// The SteamID of the target player. /// True if targeting is permitted, false otherwise. public static bool CanTarget(this CCSPlayerController? controller, SteamID steamId) { if (controller is null) return true; return AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), steamId) || AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(steamId); } /// /// Sets the movement speed modifier of the player controller. /// /// The player controller. /// The speed modifier value. public static void SetSpeed(this CCSPlayerController? controller, float speed) { var playerPawnValue = controller?.PlayerPawn.Value; if (playerPawnValue == null) return; playerPawnValue.VelocityModifier = speed; } /// /// Sets the gravity scale for the player controller. /// /// The player controller. /// The gravity scale. public static void SetGravity(this CCSPlayerController? controller, float gravity) { var playerPawnValue = controller?.PlayerPawn.Value; if (playerPawnValue == null) return; playerPawnValue.ActualGravityScale = gravity; } /// /// Sets the player's in-game money amount. /// /// The player controller. /// The amount of money to set. public static void SetMoney(this CCSPlayerController? controller, int money) { var moneyServices = controller?.InGameMoneyServices; if (moneyServices == null) return; moneyServices.Account = money; if (controller != null) Utilities.SetStateChanged(controller, "CCSPlayerController", "m_pInGameMoneyServices"); } /// /// Sets the player's health points. /// /// The player controller. /// The health value, default is 100. public static void SetHp(this CCSPlayerController? controller, int health = 100) { if (controller == null) return; if (health <= 0 || controller.PlayerPawn.Value == null || controller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return; controller.PlayerPawn.Value.Health = health; if (health > 100) { controller.PlayerPawn.Value.MaxHealth = health; } Utilities.SetStateChanged(controller.PlayerPawn.Value, "CBaseEntity", "m_iHealth"); } /// /// Buries the player pawn by moving it down by a depth offset. /// /// The player pawn to bury. /// The depth offset (default 10 units). public static void Bury(this CBasePlayerPawn pawn, float depth = 10f) { var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y, pawn.AbsOrigin!.Z - depth); var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z); var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z); pawn.Teleport(newPos, newRotation, newVelocity); } /// /// Unburies the player pawn by moving it up by a depth offset. /// /// The player pawn to unbury. /// The depth offset (default 15 units). public static void Unbury(this CBasePlayerPawn pawn, float depth = 15f) { var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y, pawn.AbsOrigin!.Z + depth); var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z); var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z); pawn.Teleport(newPos, newRotation, newVelocity); } /// /// Freezes the player pawn, disabling movement. /// /// The player pawn to freeze. public static void Freeze(this CBasePlayerPawn pawn) { pawn.MoveType = MoveType_t.MOVETYPE_INVALID; Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 11); // invalid Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); } /// /// Unfreezes the player pawn, enabling movement. /// /// The player pawn to unfreeze. public static void Unfreeze(this CBasePlayerPawn pawn) { pawn.MoveType = MoveType_t.MOVETYPE_WALK; Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); } /// /// Changes the player's color tint to specified RGBA values. /// /// The pawn to colorize. /// Red component (0-255). /// Green component (0-255). /// Blue component (0-255). /// Alpha (transparency) component (0-255). public static void Colorize(this CBasePlayerPawn pawn, int r = 255, int g = 255, int b = 255, int a = 255) { pawn.Render = Color.FromArgb(a, r, g, b); Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender"); } /// /// Toggles noclip mode for the player pawn. /// /// The player pawn. public static void ToggleNoclip(this CBasePlayerPawn pawn) { if (pawn.MoveType == MoveType_t.MOVETYPE_NOCLIP) { pawn.MoveType = MoveType_t.MOVETYPE_WALK; Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); } else { pawn.MoveType = MoveType_t.MOVETYPE_NOCLIP; Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 8); // noclip Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType"); } } /// /// Renames the player controller to a new name, with fallback to a localized "Unknown". /// /// The player controller to rename. /// The new name to assign. public static void Rename(this CCSPlayerController? controller, string newName = "Unknown") { newName ??= CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; if (controller != null) { var playerName = new SchemaString(controller, "m_iszPlayerName"); playerName.Set(newName + " "); CS2_SimpleAdmin.Instance.AddTimer(0.25f, () => { Utilities.SetStateChanged(controller, "CCSPlayerController", "m_szClan"); Utilities.SetStateChanged(controller, "CBasePlayerController", "m_iszPlayerName"); }); CS2_SimpleAdmin.Instance.AddTimer(0.3f, () => { playerName.Set(newName); }); } CS2_SimpleAdmin.Instance.AddTimer(0.4f, () => { if (controller != null) Utilities.SetStateChanged(controller, "CBasePlayerController", "m_iszPlayerName"); }); } /// /// Teleports a player controller to the position, rotation, and velocity of another player controller. /// /// The controller to teleport. /// The target controller whose position to copy. public static void TeleportPlayer(this CCSPlayerController? controller, CCSPlayerController? target) { if (controller?.PlayerPawn.Value == null && target?.PlayerPawn.Value == null) return; if ( controller?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null } && target?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null } ) { controller.PlayerPawn.Value.Teleport( target.PlayerPawn.Value.AbsOrigin, target.PlayerPawn.Value.AbsRotation, target.PlayerPawn.Value.AbsVelocity ); } } /// /// Applies a slap effect to the given player pawn, optionally inflicting damage and adding velocity knockback. /// /// The player pawn to slap. /// The amount of damage to deal (default is 0). private static void PerformSlap(CBasePlayerPawn pawn, int damage = 0) { if (pawn.LifeState != (int)LifeState_t.LIFE_ALIVE) return; var controller = pawn.Controller.Value?.As(); /* Teleport in a random direction - thank you, Mani!*/ /* Thank you AM & al!*/ var random = new Random(); var vel = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z); vel.X += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1); vel.Y += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1); vel.Z += random.Next(200) + 100; pawn.AbsVelocity.X = vel.X; pawn.AbsVelocity.Y = vel.Y; pawn.AbsVelocity.Z = vel.Z; if (controller != null && controller.IsValid) { var shakeMessage = UserMessage.FromPartialName("Shake"); shakeMessage.SetFloat("duration", 1); shakeMessage.SetFloat("amplitude", 10); shakeMessage.SetFloat("frequency", 1f); shakeMessage.SetInt("command", 0); shakeMessage.Recipients.Add(controller); shakeMessage.Send(); } if (damage <= 0) return; pawn.Health -= damage; Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth"); if (pawn.Health <= 0) pawn.CommitSuicide(true, true); } /// /// Sends a localized chat message to the player controller. /// The message is retrieved from the specified localizer using the given message key and optional formatting arguments. /// Each line of the message is prefixed with a localized prefix string. /// The message respects the player's configured language for proper localization. /// /// The target player controller to receive the message. /// The string localizer used for localization. /// The key identifying the localized message. /// Optional arguments to format the localized message. public static void SendLocalizedMessage(this CCSPlayerController? controller, IStringLocalizer? localizer, string messageKey, params object[] messageArgs) { if (controller == null || localizer == null) return; using (new WithTemporaryCulture(controller.GetLanguage())) { StringBuilder sb = new(); sb.Append(localizer[messageKey, messageArgs]); foreach (var part in Helper.SeparateLines(sb.ToString())) { var lineWithPrefix = (CS2_SimpleAdmin._localizer?["sa_prefix"] ?? "") + part.Trim(); controller.PrintToChat(lineWithPrefix); } } } /// /// Sends a localized chat message to the player controller, centered horizontally on the player's screen. /// The message is retrieved from the specified localizer using the given message key and optional formatting arguments. /// Each line of the message is centered and prefixed with a localized prefix string. /// The message respects the player's configured language for localization. /// /// The target player controller to receive the message. /// The string localizer used for localization. /// The key identifying the localized message. /// Optional arguments to format the localized message. public static void SendLocalizedMessageCenter(this CCSPlayerController? controller, IStringLocalizer? localizer, string messageKey, params object[] messageArgs) { if (controller == null || localizer == null) return; using (new WithTemporaryCulture(controller.GetLanguage())) { StringBuilder sb = new(); sb.Append(localizer[messageKey, messageArgs]); foreach (var part in Helper.SeparateLines(sb.ToString())) { string _part; _part = Helper.CenterMessage(part); var lineWithPrefix = localizer["sa_prefix"] + _part; controller.PrintToChat(lineWithPrefix); } } } }