First commit

This commit is contained in:
2026-07-07 16:36:30 +02:00
commit b0669c4f4d
9 changed files with 545 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
bin
obj

199
_old_plugin.cs Normal file
View File

@@ -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<nint, string, float> 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<Listeners.OnEntitySpawned>(OnEntityCreated);
RegisterListener<Listeners.OnEntityParentChanged>((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<CBaseViewModel>)Activator.CreateInstance(typeof(CHandle<CBaseViewModel>), 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<LegacyClass>().GetAwaiter().GetResult();
}
return value?.Legacy ?? false;
}
}

60
commands/admin.cs Normal file
View File

@@ -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<bool>();
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();
}
}

54
commands/player.cs Normal file
View File

@@ -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();
}
});
}
}

38
plugin.cs Normal file
View File

@@ -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);
}
}

30
plugin.csproj Normal file
View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="CounterStrikeSharp.API">
<HintPath>/games/cs2-server/game/csgo/addons/counterstrikesharp/api/CounterStrikeSharp.API.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Remove="_old_plugin.cs" />
</ItemGroup>
<ItemGroup>
<None Include="_old_plugin.cs" />
</ItemGroup>
<Target Name="CopyAllOutputAfterBuild" AfterTargets="AfterBuild">
<ItemGroup>
<MyOutputFiles Include="$(TargetDir)**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(MyOutputFiles)" DestinationFolder="/games/cs2-server//game/csgo/addons/counterstrikesharp/plugins/plugin/%(RecursiveDir)" />
</Target>
</Project>

53
utils/info.cs Normal file
View File

@@ -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 = $"<font color=\"red\">Porning</font><br>{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;
}
}

14
utils/logger.cs Normal file
View File

@@ -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);
}
}

95
utils/vote.cs Normal file
View File

@@ -0,0 +1,95 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Menu;
namespace Porning;
public class Vote
{
private List<int> counters = new List<int>();
private int best = -1;
private Action<int>? callback = null;
private int time = 0;
private string title = "";
private BasePlugin plugin = null!;
private CenterHtmlMenu menu = null!;
private List<string> options = null!;
public Vote(BasePlugin plugin, string message, List<string> options)
{
this.plugin = plugin;
this.options = options;
title = $"<font color=\"red\">Porning</font><br>{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<int> function)
{
callback = function;
}
}