commit b0669c4f4d75d70489e1e2639222d94b3af87155 Author: AINDUSTRIES Date: Tue Jul 7 16:36:30 2026 +0200 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d4a6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +obj \ No newline at end of file diff --git a/_old_plugin.cs b/_old_plugin.cs new file mode 100644 index 0000000..467589d --- /dev/null +++ b/_old_plugin.cs @@ -0,0 +1,199 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API.Modules.Utils; +using System.Runtime.InteropServices; +using System.Net.Http.Json; +using System.Text.Json.Serialization; + +namespace OPorning; + +public class OPorning : BasePlugin +{ + public override string ModuleName => "p0rning"; + public override string ModuleVersion => "1.0.0"; + + private static readonly ulong WeaponId = 16384; + + private static readonly HttpClient Client = new HttpClient(); + + public class LegacyClass + { + [JsonPropertyName("legacy")] + public bool Legacy { get; set; } + } + + private static readonly MemoryFunctionVoid CAttributeListSetOrAddAttributeValueByName = new(GameData.GetSignature("CAttributeList_SetOrAddAttributeValueByName")); + + private void Info(string message) => Console.WriteLine("\x1b[38;5;129m[AINDUSTRIES PRODUCTIONS] \x1b[38;5;82m[INFO] \x1b[38;5;255m{0}\x1b[0m", message); + private void Debug(string message) => Console.WriteLine("\x1b[38;5;129m[AINDUSTRIES PRODUCTIONS] \x1b[38;5;214m[DEBUG] \x1b[38;5;255m{0}\x1b[0m", message); + + public override void Load(bool hotReload) + { + RegisterListener(OnEntityCreated); + RegisterListener((entity, parent) => + { + var designerName = entity.DesignerName; + + if (!designerName.Contains("weapon_")) + { + return; + } + + Debug("Parent changed for weapon"); + + var weapon = new CBasePlayerWeapon(entity.Handle); + if (weapon.AttributeManager.Item.ItemID != WeaponId) + { + Debug("Weapon is not a spawned weapon"); + return; + } + + DebugWeapon(weapon); + + bool legacy = IsWeaponLegacy(weapon); + + //TODO: Check if this is necessary + // Seems not necessary, will keep it for now. + + //CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture prefab", weapon.FallbackPaintKit); + //CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture seed", weapon.FallbackSeed); + //CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture prefab", weapon.FallbackPaintKit); + //CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture seed", weapon.FallbackSeed); + + UpdateWeaponMeshGroupMask(weapon, legacy); + }); + Info("Plugin Loaded"); + } + + [ConsoleCommand("css_give", "Give an item to yourself")] + [CommandHelper(minArgs: 1, usage: "[name] [paint] [seed]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void Give(CCSPlayerController? player, CommandInfo commandInfo) + { + string weaponName = commandInfo.GetArg(1); + string paint = commandInfo.GetArg(2); + int paintId = 0; + if (paint != "") + { + paintId = Int32.Parse(paint); + } + + string pattern = commandInfo.GetArg(3); + int patternId = 1; + if (pattern != "") + { + patternId = Int32.Parse(pattern); + } + + if (player == null) return; + + var entity = player.GiveNamedItem(weaponName); + + var weaponEntity = new CCSWeaponBase(entity); + var baseWeapon = new CBasePlayerWeapon(weaponEntity.Handle); + + commandInfo.ReplyToCommand("Weapon given"); + Server.NextFrame(() => + { + GivePlayerWeaponSkin(player, baseWeapon, paintId, patternId); + Info("Weapon given and skin applied."); + }); + } + + private static void UpdateWeaponMeshGroupMask(CBaseEntity weapon, bool isLegacy = false) + { + if (weapon.CBodyComponent?.SceneNode == null) return; + var skeleton = weapon.CBodyComponent.SceneNode.GetSkeletonInstance(); + var value = (ulong)(isLegacy ? 2 : 1); + + if (skeleton.ModelState.MeshGroupMask != value) + { + skeleton.ModelState.MeshGroupMask = value; + } + } + + private static void UpdatePlayerWeaponMeshGroupMask(CCSPlayerController player, CBasePlayerWeapon weapon, bool isLegacy) + { + UpdateWeaponMeshGroupMask(weapon, isLegacy); + + var viewModel = GetPlayerViewModel(player); + if (viewModel == null || viewModel.Weapon.Value == null || + viewModel.Weapon.Value.Index != weapon.Index) return; + + UpdateWeaponMeshGroupMask(viewModel, isLegacy); + Utilities.SetStateChanged(viewModel, "CBaseEntity", "m_CBodyComponent"); + } + + private static CBaseViewModel? GetPlayerViewModel(CCSPlayerController player) + { + if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.ViewModelServices == null) return null; + CCSPlayer_ViewModelServices viewModelServices = new(player.PlayerPawn.Value.ViewModelServices!.Handle); + var ptr = viewModelServices.Handle + Schema.GetSchemaOffset("CCSPlayer_ViewModelServices", "m_hViewModel"); + var references = MemoryMarshal.CreateSpan(ref ptr, 3); + var viewModel = (CHandle)Activator.CreateInstance(typeof(CHandle), references[0])!; + return viewModel.Value == null ? null : viewModel.Value; + } + + private void GivePlayerWeaponSkin(CCSPlayerController? player, CBasePlayerWeapon baseWeapon, int paint, int pattern) + { + var weapon = new CEconEntity(baseWeapon.Handle); + baseWeapon.AttributeManager.Item.AttributeList.Attributes.RemoveAll(); + baseWeapon.AttributeManager.Item.NetworkedDynamicAttributes.Attributes.RemoveAll(); + + weapon.FallbackPaintKit = paint; + weapon.FallbackSeed = pattern; + weapon.FallbackWear = 0.01f; + weapon.AttributeManager.Item.AccountID = (uint)player.SteamID; + + /* + CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture prefab", weapon.FallbackPaintKit); + CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture seed", weapon.FallbackSeed); + + CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture prefab", weapon.FallbackPaintKit); + CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture seed", weapon.FallbackSeed); + */ + + weapon.AttributeManager.Item.ItemID = WeaponId; + weapon.AttributeManager.Item.ItemIDLow = (uint)WeaponId & 0xFFFFFFFF; + weapon.AttributeManager.Item.ItemIDHigh = (uint)WeaponId >> 32; + + bool legacy = IsWeaponLegacy(baseWeapon); + + UpdatePlayerWeaponMeshGroupMask(player, baseWeapon, legacy); + Info($"Gave weapon to {player.PlayerName}"); + DebugWeapon(baseWeapon); + } + + private void OnEntityCreated(CEntityInstance entity) + { + // TODO: Check if user has a loadout set, if so change the skins. + } + + private void DebugWeapon(CBasePlayerWeapon weapon) + { + Debug("[WEAPON DEBUG]"); + Console.WriteLine("[Entity ID]: {0}", weapon.AttributeManager.Item.ItemID); + Console.WriteLine("[Owner ID]: {0}", weapon.AttributeManager.Item.AccountID); + Console.WriteLine("[Owner ID Low]: {0}", weapon.AttributeManager.Item.ItemIDLow); + Console.WriteLine("[Owner ID High]: {0}", weapon.AttributeManager.Item.ItemIDHigh); + Console.WriteLine("[Paint]: {0}", weapon.FallbackPaintKit); + Console.WriteLine("[Pattern]: {0}", weapon.FallbackSeed); + Console.WriteLine("[DefIndex]: {0}", weapon.AttributeManager.Item.ItemDefinitionIndex); + } + + private bool IsWeaponLegacy(CBasePlayerWeapon weapon) + { + var designerName = weapon.DesignerName; + var paint = weapon.FallbackPaintKit; + HttpResponseMessage response = Client.GetAsync($"http://127.0.0.1:8900/api/legacy/{designerName}/{paint}").GetAwaiter().GetResult(); + LegacyClass? value = null; + if (response.IsSuccessStatusCode) + { + value = response.Content.ReadFromJsonAsync().GetAwaiter().GetResult(); + } + return value?.Legacy ?? false; + } +} \ No newline at end of file diff --git a/commands/admin.cs b/commands/admin.cs new file mode 100644 index 0000000..f91fd3d --- /dev/null +++ b/commands/admin.cs @@ -0,0 +1,60 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Cvars; + +namespace Porning; + +class AdminCommands +{ + private readonly BasePlugin plugin; + + public AdminCommands(BasePlugin pluginInstance) + { + plugin = pluginInstance; + } + private ConVar autoBhopCvar = ConVar.Find("sv_autobunnyhopping")!; + + [CommandHelper(minArgs: 1, usage: "[map_name]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@porning/admin")] + public void onMap(CCSPlayerController? player, CommandInfo commandInfo) + { + string map = commandInfo.GetArg(1); + if (!Server.IsMapValid(map)) + { + Info info = new Info(plugin, "Map not recognised."); + info.Show(player); + return; + } + Server.ExecuteCommand($"map {map}"); + } + + [CommandHelper(minArgs: 0, usage: "[0/1]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@porning/admin")] + public void onBhop(CCSPlayerController? player, CommandInfo commandInfo) + { + Info info; + if (commandInfo.ArgCount == 1) + { + bool autoBhop = autoBhopCvar.GetPrimitiveValue(); + Server.ExecuteCommand($"sv_autobunnyhopping {!autoBhop}"); + info = new Info(plugin, $"Toggled bhop: {!autoBhop}"); + info.Show(player); + return; + } + string bhop = commandInfo.GetArg(1); + Server.ExecuteCommand($"sv_autobunnyhopping {bhop}"); + info = new Info(plugin, $"Toggled bhop: {bhop}"); + info.Show(player); + } + + [CommandHelper(minArgs: 1, usage: "[message]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@porning/admin")] + public void onInfo(CCSPlayerController? player, CommandInfo commandInfo) + { + string message = commandInfo.GetCommandString.Substring(9); + Info info = new Info(plugin, message); + info.Show(); + } +} \ No newline at end of file diff --git a/commands/player.cs b/commands/player.cs new file mode 100644 index 0000000..c20eea1 --- /dev/null +++ b/commands/player.cs @@ -0,0 +1,54 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Commands; + +namespace Porning; + +class PlayerCommands +{ + private readonly BasePlugin plugin; + + public PlayerCommands(BasePlugin pluginInstance) + { + plugin = pluginInstance; + } + + [CommandHelper(minArgs: 0, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void onHelp(CCSPlayerController? player, CommandInfo commandInfo) + { + commandInfo.ReplyToCommand("/help: Show help"); + commandInfo.ReplyToCommand("/map: Change map"); + commandInfo.ReplyToCommand("/bhop: Toggle bhop"); + } + + [CommandHelper(minArgs:0, usage: "[map]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + public void onMapVote(CCSPlayerController? player, CommandInfo commandInfo) + { + string map = commandInfo.GetArg(1); + if (!Server.IsMapValid(map)) + { + Info info = new Info(plugin, "Map not recognised."); + info.Show(player); + return; + } + + Vote vote = new Vote(plugin, $"Map vote: {map}", ["Yes", "No"]); + vote.BeginVote(); + vote.OnEnd((best) => + { + if (best == 0) + { + Info info = new Info(plugin, "Changing map!"); + info.Show(); + info.OnClose(() => + { + Server.ExecuteCommand($"map {map}"); + }); + } else + { + Info info = new Info(plugin, "Not changing map!"); + info.Show(); + } + }); + } +} \ No newline at end of file diff --git a/plugin.cs b/plugin.cs new file mode 100644 index 0000000..0c1062f --- /dev/null +++ b/plugin.cs @@ -0,0 +1,38 @@ +using CounterStrikeSharp.API.Core; + +namespace Porning; + +public class Porning : BasePlugin +{ + public override string ModuleName => "p0rning"; + public override string ModuleVersion => "0.0.1"; + private static readonly ulong SpawnedWeaponId = 16384; // Randomly chosen + private readonly AdminCommands adminCommands; + private readonly PlayerCommands playerCommands; + + public Porning() + { + adminCommands = new AdminCommands(this); + playerCommands = new PlayerCommands(this); + } + + public override void Load(bool hotReload) + { + new Logger().Info("Loaded!"); + registerAdminCommands(); + registerPlayerCommands(); + } + + private void registerAdminCommands() + { + AddCommand("css_amap", "Change map", adminCommands.onMap); + AddCommand("css_abhop", "Toggle bhop", adminCommands.onBhop); + AddCommand("css_ainfo", "Broadcast message", adminCommands.onInfo); + } + + private void registerPlayerCommands() + { + AddCommand("css_help", "Help", playerCommands.onHelp); + AddCommand("css_map", "Vote Map", playerCommands.onMapVote); + } +} \ No newline at end of file diff --git a/plugin.csproj b/plugin.csproj new file mode 100644 index 0000000..22fef0b --- /dev/null +++ b/plugin.csproj @@ -0,0 +1,30 @@ + + + + net10.0 + enable + enable + + + + + /games/cs2-server/game/csgo/addons/counterstrikesharp/api/CounterStrikeSharp.API.dll + + + + + + + + + + + + + + + + + + + diff --git a/utils/info.cs b/utils/info.cs new file mode 100644 index 0000000..aef9fe5 --- /dev/null +++ b/utils/info.cs @@ -0,0 +1,53 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Menu; + +namespace Porning; + +class Info +{ + private string message = ""; + private BasePlugin plugin = null!; + private CenterHtmlMenu? menu = null; + private Action? callback = null; + + public Info(BasePlugin plugin, string message) + { + this.plugin = plugin; + this.message = $"Porning
{message}"; + } + + public void Show(CCSPlayerController? player=null) + { + menu = new CenterHtmlMenu(message, plugin) + { + ExitButton = false + }; + + if (player !=null) + { + MenuManager.OpenCenterHtmlMenu(plugin, player, menu); + } else + { + foreach (CCSPlayerController p in Utilities.GetPlayers()) + { + MenuManager.OpenCenterHtmlMenu(plugin, p, menu); + } + } + plugin.AddTimer(5.0f, Close); + } + + public void Close() + { + foreach (CCSPlayerController player in Utilities.GetPlayers()) + { + MenuManager.CloseActiveMenu(player); + } + callback?.Invoke(); + } + + public void OnClose(Action callback) + { + this.callback = callback; + } +} \ No newline at end of file diff --git a/utils/logger.cs b/utils/logger.cs new file mode 100644 index 0000000..4af7273 --- /dev/null +++ b/utils/logger.cs @@ -0,0 +1,14 @@ +namespace Porning; + +public class Logger +{ + public void Info(string message) + { + Console.WriteLine("\x1b[38;5;129m[p0rning] \x1b[38;5;82m[INFO] \x1b[38;5;255m{0}\x1b[0m", message); + } + + public void Debug(string message) + { + Console.WriteLine("\x1b[38;5;129m[p0rning] \x1b[38;5;214m[DEBUG] \x1b[38;5;255m{0}\x1b[0m", message); + } +} \ No newline at end of file diff --git a/utils/vote.cs b/utils/vote.cs new file mode 100644 index 0000000..5ed09fb --- /dev/null +++ b/utils/vote.cs @@ -0,0 +1,95 @@ +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Menu; + +namespace Porning; + +public class Vote +{ + private List counters = new List(); + private int best = -1; + private Action? callback = null; + private int time = 0; + private string title = ""; + private BasePlugin plugin = null!; + private CenterHtmlMenu menu = null!; + private List options = null!; + + public Vote(BasePlugin plugin, string message, List options) + { + this.plugin = plugin; + this.options = options; + title = $"Porning
{message}"; + } + + public void BeginVote() + { + menu = new CenterHtmlMenu($"{title} (10 sec)", plugin){ + ExitButton = false + }; + for (int i = 0; i < options.Count; i++) + { + counters.Add(0); + int current = i; + menu.AddMenuOption($"-> {options[i]} [0]", (player, action) => {counters[current]++;}); + } + + foreach (CCSPlayerController player in Utilities.GetPlayers()) + { + MenuManager.OpenCenterHtmlMenu(plugin, player, menu); + } + + plugin.AddTimer(1.0f, Tick); + } + + public void Tick() + { + new Logger().Info($"TICK: {time}"); + if (time >= 10) + { + EndVote(); + return; + } + + time++; + + menu.Title = $"{title} ({10-time} sec)"; + + for (int i = 0; i < options.Count; i++) + { + menu.MenuOptions[i].Text = $"-> {options[i]} [{counters[i]}]"; + } + + foreach(CCSPlayerController player in Utilities.GetPlayers()) + { + MenuManager.OpenCenterHtmlMenu(plugin, player, menu); + } + plugin.AddTimer(1.0f, Tick); + } + + public void EndVote() + { + foreach (CCSPlayerController player in Utilities.GetPlayers()) + { + MenuManager.CloseActiveMenu(player); + } + + int maxVotes = 0; + for (int i = 0; i < counters.Count; i++) + { + new Logger().Info($"Counter: {counters[i]}"); + if (counters[i] > maxVotes) + { + maxVotes = counters[i]; + best = i; + } + } + + callback?.Invoke(best); + } + + public void OnEnd(Action function) + { + callback = function; + } +} \ No newline at end of file