mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-08 08:56:34 -08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11e5e9972d | ||
|
|
9a221b4ebb | ||
|
|
e270bdfd44 | ||
|
|
f591fe58d0 | ||
|
|
052cb4e14e | ||
|
|
bc3bec4aa8 |
@@ -5,6 +5,8 @@ project(counterstrikesharp C CXX ASM)
|
||||
|
||||
include("makefiles/shared.cmake")
|
||||
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
|
||||
add_subdirectory(libraries/spdlog)
|
||||
add_subdirectory(libraries/dyncall)
|
||||
add_subdirectory(libraries/funchook)
|
||||
@@ -13,7 +15,8 @@ add_subdirectory(libraries/DynoHook)
|
||||
set_property(TARGET dynohook PROPERTY DYNO_ARCH_X86 64)
|
||||
set_property(TARGET funchook-static PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
SET(SOURCE_FILES
|
||||
set(SOURCE_FILES
|
||||
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
|
||||
src/mm_plugin.cpp
|
||||
src/mm_plugin.h
|
||||
libraries/hl2sdk-cs2/tier1/convar.cpp
|
||||
@@ -95,66 +98,55 @@ SET(SOURCE_FILES
|
||||
src/core/game_system.cpp
|
||||
)
|
||||
|
||||
|
||||
if (LINUX)
|
||||
# memoverride.cpp is not usable on CMake Windows, cuz CMake default link libraries (seems) always link ucrt.lib
|
||||
set(SOURCE_FILES
|
||||
${SOURCE_FILES}
|
||||
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(PROTO_DIRS -I${CMAKE_CURRENT_SOURCE_DIR}/libraries/Protobufs/csgo)
|
||||
file(GLOB PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/Protobufs/csgo/*.proto")
|
||||
|
||||
## Generate protobuf source & headers
|
||||
if (LINUX)
|
||||
if(LINUX)
|
||||
set(PROTOC_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2/devtools/bin/linux/protoc)
|
||||
elseif(WIN32)
|
||||
set(PROTOC_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2/devtools/bin/protoc.exe)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT protobuf_output_stamp
|
||||
COMMAND ${PROTOC_EXECUTABLE} --proto_path=thirdparty/protobuf-3.21.8/src --proto_path=common --cpp_out=common common/network_connection.proto
|
||||
COMMENT "Generating protobuf file"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2
|
||||
VERBATIM
|
||||
OUTPUT protobuf_output_stamp
|
||||
COMMAND ${PROTOC_EXECUTABLE} --proto_path=thirdparty/protobuf-3.21.8/src --proto_path=common --cpp_out=common common/network_connection.proto
|
||||
COMMENT "Generating protobuf file"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
SET(SOURCE_FILES ${SOURCE_FILES} protobuf_output_stamp)
|
||||
set(SOURCE_FILES ${SOURCE_FILES} protobuf_output_stamp)
|
||||
|
||||
# Sources
|
||||
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${NATIVES_SOURCES} ${CONVERSIONS_SOURCES} ${CONVERSIONS_HEADERS})
|
||||
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/core/cs2_sdk
|
||||
${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/core/cs2_sdk
|
||||
)
|
||||
|
||||
if (LINUX)
|
||||
if(LINUX)
|
||||
include("makefiles/linux.base.cmake")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/linuxsteamrt64"
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/linuxsteamrt64"
|
||||
)
|
||||
elseif(WIN32)
|
||||
include("makefiles/windows.base.cmake")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/win64"
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/win64"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
# Libraries
|
||||
target_link_libraries(${PROJECT_NAME} ${COUNTER_STRIKE_SHARP_LINK_LIBRARIES})
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME} PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/configs ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
TARGET ${PROJECT_NAME} PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/configs ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
|
||||
@@ -133,6 +133,13 @@
|
||||
"linux": "55 48 89 E5 41 57 49 89 FF 41 56 48 8D 7D C0"
|
||||
}
|
||||
},
|
||||
"CEntitySystem_AddEntityIOEvent": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "48 89 5C 24 ? 48 89 74 24 ? 57 48 ? ? ? 49 ? ? 48 ? ? 48 ? ? 74",
|
||||
"linux": "55 41 BA FF FF FF FF"
|
||||
}
|
||||
},
|
||||
"LegacyGameEventListener": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
|
||||
Submodule libraries/hl2sdk-cs2 updated: aaaaaf040b...3fc8d0faf6
@@ -6,6 +6,7 @@ add_definitions(
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819 /wd4828 /wd5033 /permissive- /utf-8 /wd4005 /MP")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:REF /OPT:ICF")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libcmt")
|
||||
|
||||
set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
|
||||
${SOURCESDK_LIB}/public/win64/tier0.lib
|
||||
@@ -18,4 +19,4 @@ set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
|
||||
distorm
|
||||
funchook-static
|
||||
dynohook
|
||||
)
|
||||
)
|
||||
|
||||
@@ -714,6 +714,37 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void AcceptInput(IntPtr pthis, string inputname, IntPtr activator, IntPtr caller, string value, int outputid){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(pthis);
|
||||
ScriptContext.GlobalScriptContext.Push(inputname);
|
||||
ScriptContext.GlobalScriptContext.Push(activator);
|
||||
ScriptContext.GlobalScriptContext.Push(caller);
|
||||
ScriptContext.GlobalScriptContext.Push(value);
|
||||
ScriptContext.GlobalScriptContext.Push(outputid);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x259E084C);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddEntityIoEvent(IntPtr ptarget, string inputname, IntPtr activator, IntPtr caller, string value, float delay, int outputid){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(ptarget);
|
||||
ScriptContext.GlobalScriptContext.Push(inputname);
|
||||
ScriptContext.GlobalScriptContext.Push(activator);
|
||||
ScriptContext.GlobalScriptContext.Push(caller);
|
||||
ScriptContext.GlobalScriptContext.Push(value);
|
||||
ScriptContext.GlobalScriptContext.Push(delay);
|
||||
ScriptContext.GlobalScriptContext.Push(outputid);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x4CFDE98A);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static void HookEvent(string name, InputArgument callback, bool ispost){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,12 +23,7 @@ public partial class CCSPlayerController
|
||||
{
|
||||
Guard.IsValidEntity(this);
|
||||
|
||||
if (!PlayerPawn.IsValid) return null;
|
||||
if (PlayerPawn.Value == null) return null; ;
|
||||
if (!PlayerPawn.Value.IsValid) return null;
|
||||
if (PlayerPawn.Value.ItemServices == null) return null;
|
||||
|
||||
return PlayerPawn.Value.ItemServices.As<CCSPlayer_ItemServices>().GiveNamedItem<T>(item);
|
||||
return PlayerPawn.Value?.ItemServices?.As<CCSPlayer_ItemServices>().GiveNamedItem<T>(item);
|
||||
}
|
||||
|
||||
public IntPtr GiveNamedItem(CsItem item)
|
||||
@@ -98,17 +93,13 @@ public partial class CCSPlayerController
|
||||
public void DropActiveWeapon()
|
||||
{
|
||||
Guard.IsValidEntity(this);
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (PlayerPawn.Value == null) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
if (PlayerPawn.Value.ItemServices == null) return;
|
||||
if (PlayerPawn.Value.WeaponServices == null) return;
|
||||
if (!PlayerPawn.Value.WeaponServices.ActiveWeapon.IsValid) return;
|
||||
|
||||
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
|
||||
CCSPlayer_WeaponServices weaponServices = new CCSPlayer_WeaponServices(PlayerPawn.Value.WeaponServices.Handle);
|
||||
var itemServices = PlayerPawn.Value?.ItemServices?.As<CCSPlayer_ItemServices>();
|
||||
var activeWeapon = PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;
|
||||
|
||||
itemServices.DropActivePlayerWeapon(weaponServices.ActiveWeapon.Value);
|
||||
if (activeWeapon == null || itemServices == null) return;
|
||||
|
||||
itemServices.DropActivePlayerWeapon(activeWeapon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -118,13 +109,8 @@ public partial class CCSPlayerController
|
||||
public void RemoveWeapons()
|
||||
{
|
||||
Guard.IsValidEntity(this);
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (PlayerPawn.Value == null) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
if (PlayerPawn.Value.ItemServices == null) return;
|
||||
|
||||
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
|
||||
itemServices.RemoveWeapons();
|
||||
PlayerPawn.Value?.ItemServices?.As<CCSPlayer_ItemServices>().RemoveWeapons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,11 +122,8 @@ public partial class CCSPlayerController
|
||||
public void CommitSuicide(bool explode, bool force)
|
||||
{
|
||||
Guard.IsValidEntity(this);
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (PlayerPawn.Value == null) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
|
||||
PlayerPawn.Value.CommitSuicide(explode, force);
|
||||
PlayerPawn.Value?.CommitSuicide(explode, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -150,9 +133,7 @@ public partial class CCSPlayerController
|
||||
public void Respawn()
|
||||
{
|
||||
Guard.IsValidEntity(this);
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (PlayerPawn.Value == null) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
|
||||
// The Call To Arms update appears to have invalidated the need for CCSPlayerPawn_Respawn.
|
||||
SetPawn(PlayerPawn.Value);
|
||||
|
||||
@@ -65,7 +65,7 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a named input method on an entity.
|
||||
/// Calls a named input method on an entity, this will bypass the map IO event queue system.
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// entity.AcceptInput("Break");
|
||||
@@ -82,7 +82,30 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
|
||||
{
|
||||
Guard.IsValidEntity(this);
|
||||
|
||||
VirtualFunctions.AcceptInput(Handle, inputName, activator?.Handle ?? IntPtr.Zero, caller?.Handle ?? IntPtr.Zero, value, 0);
|
||||
NativeAPI.AcceptInput(Handle, inputName, activator?.Handle ?? IntPtr.Zero, caller?.Handle ?? IntPtr.Zero, value, outputId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calls a named input method on an entity, conforming to the map IO event queue system.
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// entity.AddEntityIOEvent("Break");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
/// <param name="inputName">Input action name</param>
|
||||
/// <param name="activator">Entity which initiated the action, <see langword="null"/> for no entity</param>
|
||||
/// <param name="caller">Entity that is sending the event, <see langword="null"/> for no entity</param>
|
||||
/// <param name="value">String variant value to send with the event</param>
|
||||
/// <param name="delay">Delay in seconds before calling the input</param>
|
||||
/// <param name="outputId">Unknown, defaults to 0</param>
|
||||
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
|
||||
public void AddEntityIOEvent(string inputName, CEntityInstance? activator = null, CEntityInstance? caller = null, string value = "", float delay = 0, int outputId = 0)
|
||||
{
|
||||
Guard.IsValidEntity(this);
|
||||
|
||||
NativeAPI.AddEntityIoEvent(Handle, inputName, activator?.Handle ?? IntPtr.Zero, caller?.Handle ?? IntPtr.Zero, value, delay, outputId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +113,4 @@ public partial class CEntityIdentity
|
||||
{
|
||||
public unsafe CEntityInstance EntityInstance => new(Unsafe.Read<IntPtr>((void*)Handle));
|
||||
public unsafe CHandle<CEntityInstance> EntityHandle => new(this.Handle + 0x10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,16 +34,16 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
{
|
||||
// Used to track freeable state for manually created events.
|
||||
private bool _freeable = false;
|
||||
|
||||
|
||||
public GameEvent(IntPtr pointer) : base(pointer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public GameEvent(string name, bool force) : this(NativeAPI.CreateEvent(name, force))
|
||||
{
|
||||
_freeable = true;
|
||||
}
|
||||
|
||||
|
||||
public string EventName => NativeAPI.GetEventName(Handle);
|
||||
|
||||
public T Get<T>(string name)
|
||||
@@ -58,7 +58,9 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
_ when type == typeof(bool) => GetBool(name),
|
||||
_ when type == typeof(ulong) => GetUint64(name),
|
||||
_ when type == typeof(long) => (long)GetUint64(name),
|
||||
_ when type == typeof(CCSPlayerController) => GetPlayer(name),
|
||||
// This is a special case for player controllers as this method previously did not allow for nullable returns.
|
||||
// So we return an invalid player controller if the player is not found.
|
||||
_ when type == typeof(CCSPlayerController) => GetPlayer(name) ?? new CCSPlayerController(0),
|
||||
_ => throw new NotSupportedException(),
|
||||
};
|
||||
|
||||
@@ -80,7 +82,7 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
SetInt(name, i);
|
||||
break;
|
||||
case var _ when value is CCSPlayerController player:
|
||||
NativeAPI.SetEventPlayerController(Handle, name, player.Handle);
|
||||
SetPlayer(name, player);
|
||||
break;
|
||||
case var _ when value is string s:
|
||||
SetString(name, s);
|
||||
@@ -104,9 +106,15 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
protected string GetString(string name) => NativeAPI.GetEventString(Handle, name);
|
||||
protected int GetInt(string name) => NativeAPI.GetEventInt(Handle, name);
|
||||
|
||||
protected CCSPlayerController GetPlayer(string name)
|
||||
protected CCSPlayerController? GetPlayer(string name)
|
||||
{
|
||||
return new CCSPlayerController(NativeAPI.GetEventPlayerController(Handle, name));
|
||||
var ptr = NativeAPI.GetEventPlayerController(Handle, name);
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CCSPlayerController(ptr);
|
||||
}
|
||||
|
||||
protected ulong GetUint64(string name) => NativeAPI.GetEventUint64(Handle, name);
|
||||
@@ -125,6 +133,8 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
|
||||
protected void SetEntityIndex(string name, int value) => NativeAPI.SetEventEntityIndex(Handle, name, value);
|
||||
|
||||
protected void SetPlayer(string name, CCSPlayerController? player) => NativeAPI.SetEventPlayerController(Handle, name, player?.Handle ?? IntPtr.Zero);
|
||||
|
||||
public void FireEvent(bool dontBroadcast)
|
||||
{
|
||||
NativeAPI.FireEvent(Handle, dontBroadcast);
|
||||
@@ -132,7 +142,7 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
}
|
||||
|
||||
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.Index);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to manually free the event.
|
||||
/// <remarks>If <see cref="FireEvent"/> is called, Free will be called automatically.</remarks>
|
||||
@@ -143,10 +153,10 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
{
|
||||
throw new InvalidOperationException("Event is not able to be freed.");
|
||||
}
|
||||
|
||||
|
||||
NativeAPI.FreeEvent(Handle);
|
||||
|
||||
_freeable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,6 @@ public static class VirtualFunctions
|
||||
public static MemoryFunctionVoid<IntPtr, IntPtr> RemovePlayerItemFunc =
|
||||
new(GameData.GetSignature("CBasePlayerPawn_RemovePlayerItem"));
|
||||
public static Action<IntPtr, IntPtr> RemovePlayerItemVirtual = RemovePlayerItemFunc.Invoke;
|
||||
|
||||
public static MemoryFunctionVoid<IntPtr, string, IntPtr, IntPtr, string, int> AcceptInputFunc = new(GameData.GetSignature("CEntityInstance_AcceptInput"));
|
||||
public static Action<IntPtr, string, IntPtr, IntPtr, string, int> AcceptInput = AcceptInputFunc.Invoke;
|
||||
|
||||
public static MemoryFunctionVoid<IntPtr, IntPtr, int, short, short> StateChangedFunc =
|
||||
new(GameData.GetSignature("StateChanged"));
|
||||
@@ -92,4 +89,4 @@ public static class VirtualFunctions
|
||||
public static MemoryFunctionVoid<IntPtr, int, long> NetworkStateChangedFunc = new(GameData.GetSignature("NetworkStateChanged"));
|
||||
public static Action<IntPtr, int, long> NetworkStateChanged = NetworkStateChangedFunc.Invoke;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
@@ -11,6 +11,12 @@ public readonly record struct CEntityIndex(uint Value)
|
||||
public override string ToString() => $"Entity Index {Value}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CHandle is a class that represents a 32-bit ID (entindex + serial number) unique to every past and present entity in a game.
|
||||
/// It is used to refer to entities where pointers and entity indexes are unsafe; mainly across the client/server divide.
|
||||
/// <a href="https://developer.valvesoftware.com/wiki/CHandle">More info</a>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of entity this handle refers to</typeparam>
|
||||
public class CHandle<T> : IEquatable<CHandle<T>> where T : NativeEntity
|
||||
{
|
||||
private uint _raw;
|
||||
@@ -49,15 +55,35 @@ public class CHandle<T> : IEquatable<CHandle<T>> where T : NativeEntity
|
||||
_pointer = raw;
|
||||
}
|
||||
|
||||
public T? Value => (T)Activator.CreateInstance(typeof(T), EntitySystem.GetEntityByHandle(this));
|
||||
|
||||
/// <inheritdoc cref="Get"/>
|
||||
public T? Value => Get();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the instance of the entity this handle refers to.
|
||||
/// </summary>
|
||||
public T? Get()
|
||||
{
|
||||
if (!IsValid)
|
||||
return null;
|
||||
|
||||
var entity = EntitySystem.GetEntityByHandle(this);
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
return (T)Activator.CreateInstance(typeof(T), entity)!;
|
||||
}
|
||||
|
||||
public override string ToString() => IsValid ? $"Index = {Index}, Serial = {SerialNum}" : "<invalid>";
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the handle is valid and points to an entity.
|
||||
/// </summary>
|
||||
public bool IsValid => Index != (Utilities.MaxEdicts - 1);
|
||||
|
||||
public uint Index => (uint)(Raw & (Utilities.MaxEdicts - 1));
|
||||
public uint SerialNum => Raw >> Utilities.MaxEdictBits;
|
||||
|
||||
|
||||
public static implicit operator uint(CHandle<T> handle) => handle.Raw;
|
||||
|
||||
public bool Equals(CHandle<T>? other)
|
||||
@@ -81,8 +107,8 @@ public class CEntityHandle : CHandle<CEntityInstance>
|
||||
public CEntityHandle(uint raw) : base(raw)
|
||||
{
|
||||
}
|
||||
|
||||
public CEntityHandle(IntPtr raw) : base (raw)
|
||||
|
||||
public CEntityHandle(IntPtr raw) : base(raw)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -103,4 +129,4 @@ public class PointerTo<T> : NativeObject where T : NativeObject
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,15 @@ namespace CounterStrikeSharp.API
|
||||
.Where(x => flags.HasFlag(x)).AsEnumerable();
|
||||
}
|
||||
|
||||
public static T GetEntityFromIndex<T>(int index) where T : CEntityInstance
|
||||
public static T? GetEntityFromIndex<T>(int index) where T : CEntityInstance
|
||||
{
|
||||
return (T)Activator.CreateInstance(typeof(T), NativeAPI.GetEntityFromIndex(index))!;
|
||||
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
|
||||
@@ -52,19 +58,25 @@ namespace CounterStrikeSharp.API
|
||||
return (T?)Activator.CreateInstance(typeof(T), VirtualFunctions.UTIL_CreateEntityByName(name, -1));
|
||||
}
|
||||
|
||||
public static CCSPlayerController GetPlayerFromIndex(int index)
|
||||
public static CCSPlayerController? GetPlayerFromIndex(int index)
|
||||
{
|
||||
return Utilities.GetEntityFromIndex<CCSPlayerController>(index);
|
||||
var player = GetEntityFromIndex<CCSPlayerController>(index);
|
||||
if (player == null || player.DesignerName != "cs_player_controller")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
public static CCSPlayerController GetPlayerFromSlot(int slot)
|
||||
public static CCSPlayerController? GetPlayerFromSlot(int slot)
|
||||
{
|
||||
return Utilities.GetEntityFromIndex<CCSPlayerController>(slot + 1);
|
||||
return GetPlayerFromIndex(slot + 1);
|
||||
}
|
||||
|
||||
public static CCSPlayerController GetPlayerFromUserid(int userid)
|
||||
public static CCSPlayerController? GetPlayerFromUserid(int userid)
|
||||
{
|
||||
return Utilities.GetEntityFromIndex<CCSPlayerController>((userid & 0xFF) + 1);
|
||||
return GetPlayerFromIndex((userid & 0xFF) + 1);
|
||||
}
|
||||
|
||||
public static CCSPlayerController? GetPlayerFromSteamId(ulong steamId)
|
||||
@@ -87,16 +99,16 @@ namespace CounterStrikeSharp.API
|
||||
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)
|
||||
foreach (var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
|
||||
{
|
||||
if (weapon is not { IsValid: true, Value.IsValid: true })
|
||||
if (weapon is not { IsValid: true, Value.IsValid: true })
|
||||
continue;
|
||||
if (weapon.Value.DesignerName != designerName)
|
||||
if (weapon.Value.DesignerName != designerName)
|
||||
continue;
|
||||
|
||||
item = weapon;
|
||||
}
|
||||
|
||||
|
||||
if (item != null && item.Value != null)
|
||||
{
|
||||
player.PlayerPawn.Value.RemovePlayerItem(item.Value);
|
||||
@@ -108,7 +120,7 @@ namespace CounterStrikeSharp.API
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -121,7 +133,7 @@ namespace CounterStrikeSharp.API
|
||||
yield return new PointerTo<T>(pEntity.Handle).Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<CEntityInstance> GetAllEntities()
|
||||
{
|
||||
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
|
||||
@@ -130,7 +142,7 @@ namespace CounterStrikeSharp.API
|
||||
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>
|
||||
@@ -142,7 +154,7 @@ namespace CounterStrikeSharp.API
|
||||
{
|
||||
var controller = GetPlayerFromSlot(i);
|
||||
|
||||
if (!controller.IsValid || controller.UserId == -1)
|
||||
if (controller == null || !controller.IsValid || controller.Connected != PlayerConnectedState.PlayerConnected)
|
||||
continue;
|
||||
|
||||
players.Add(controller);
|
||||
@@ -198,7 +210,7 @@ namespace CounterStrikeSharp.API
|
||||
|
||||
return (T)Activator.CreateInstance(typeof(T), pointerTo)!;
|
||||
}
|
||||
|
||||
|
||||
private static int FindSchemaChain(string className) => Schema.GetSchemaOffset(className, "__m_pChainEntity");
|
||||
|
||||
/// <summary>
|
||||
@@ -216,10 +228,11 @@ namespace CounterStrikeSharp.API
|
||||
|
||||
if (!Schema.IsSchemaFieldNetworked(className, fieldName))
|
||||
{
|
||||
Application.Instance.Logger.LogWarning("Field {ClassName}:{FieldName} is not networked, but SetStateChanged was called on it.", 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);
|
||||
|
||||
@@ -235,4 +248,4 @@ namespace CounterStrikeSharp.API
|
||||
entity.IsSteadyState.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,18 +24,14 @@ using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Events;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Menu;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static CounterStrikeSharp.API.Core.Listeners;
|
||||
|
||||
namespace TestPlugin
|
||||
{
|
||||
@@ -56,7 +52,7 @@ namespace TestPlugin
|
||||
|
||||
public override string ModuleDescription => "A playground of features used for testing";
|
||||
|
||||
public SampleConfig Config { get; set; }
|
||||
public SampleConfig Config { get; set; } = null!;
|
||||
|
||||
// This method is called right before `Load` is called
|
||||
public void OnConfigParsed(SampleConfig config)
|
||||
@@ -94,7 +90,6 @@ namespace TestPlugin
|
||||
SetupGameEvents();
|
||||
SetupListeners();
|
||||
SetupCommands();
|
||||
SetupMenus();
|
||||
SetupEntityOutputHooks();
|
||||
|
||||
// ValveInterface provides pointers to loaded modules via Interface Name exposed from the engine (e.g. Source2Server001)
|
||||
@@ -114,69 +109,10 @@ namespace TestPlugin
|
||||
var virtualFunc = VirtualFunction.Create<IntPtr>(server.Pointer, 91);
|
||||
var result = virtualFunc() - 8;
|
||||
Logger.LogInformation("Result of virtual func call is {Pointer:X}", result);
|
||||
|
||||
|
||||
_testInjectedClass.Hello();
|
||||
|
||||
VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(h =>
|
||||
{
|
||||
var trigger = h.GetParam<CBaseTrigger>(0);
|
||||
var entity = h.GetParam<CBaseEntity>(1);
|
||||
|
||||
Logger.LogInformation("Trigger {Trigger} touched by {Entity}", trigger.DesignerName, entity.DesignerName);
|
||||
|
||||
return HookResult.Continue;
|
||||
}, HookMode.Post);
|
||||
|
||||
VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(h =>
|
||||
{
|
||||
var trigger = h.GetParam<CBaseTrigger>(0);
|
||||
var entity = h.GetParam<CBaseEntity>(1);
|
||||
|
||||
Logger.LogInformation("Trigger left {Trigger} by {Entity}", trigger.DesignerName, entity.DesignerName);
|
||||
|
||||
return HookResult.Continue;
|
||||
}, HookMode.Post);
|
||||
|
||||
VirtualFunctions.UTIL_RemoveFunc.Hook(hook =>
|
||||
{
|
||||
var entityInstance = hook.GetParam<CEntityInstance>(0);
|
||||
Logger.LogInformation("Removed entity {EntityIndex}", entityInstance.Index);
|
||||
|
||||
return HookResult.Continue;
|
||||
}, HookMode.Post);
|
||||
|
||||
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook((h =>
|
||||
{
|
||||
var victim = h.GetParam<CEntityInstance>(0);
|
||||
var damageInfo = h.GetParam<CTakeDamageInfo>(1);
|
||||
|
||||
if (damageInfo.Inflictor.Value.DesignerName == "inferno")
|
||||
{
|
||||
var inferno = new CInferno(damageInfo.Inflictor.Value.Handle);
|
||||
Logger.LogInformation("Owner of inferno is {Owner}", inferno.OwnerEntity);
|
||||
|
||||
if (victim == inferno.OwnerEntity.Value)
|
||||
{
|
||||
damageInfo.Damage = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageInfo.Damage = 150;
|
||||
}
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}), HookMode.Pre);
|
||||
|
||||
// Precache resources
|
||||
RegisterListener<Listeners.OnServerPrecacheResources>((manifest) =>
|
||||
{
|
||||
manifest.AddResource("path/to/model");
|
||||
manifest.AddResource("path/to/material");
|
||||
manifest.AddResource("path/to/particle");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
Logger.LogInformation("All plugins loaded!");
|
||||
@@ -186,8 +122,7 @@ namespace TestPlugin
|
||||
{
|
||||
RegisterListener<Listeners.OnMapStart>(name =>
|
||||
{
|
||||
var cheatsCvar = ConVar.Find("sv_cheats");
|
||||
cheatsCvar.SetValue(true);
|
||||
ConVar.Find("sv_cheats")?.SetValue(true);
|
||||
|
||||
var numericCvar = ConVar.Find("mp_warmuptime");
|
||||
Logger.LogInformation("mp_warmuptime = {Value}", numericCvar?.GetPrimitiveValue<float>());
|
||||
@@ -204,67 +139,73 @@ namespace TestPlugin
|
||||
{
|
||||
// Register Game Event Handlers
|
||||
RegisterEventHandler<EventPlayerConnect>(GenericEventHandler, HookMode.Pre);
|
||||
RegisterEventHandler<EventPlayerChat>(((@event, info) =>
|
||||
{
|
||||
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(@event.Userid));
|
||||
if (!entity.IsValid)
|
||||
{
|
||||
Logger.LogInformation("invalid entity");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
RegisterEventHandler<EventPlayerBlind>(GenericEventHandler);
|
||||
|
||||
entity.PrintToChat($"You said {@event.Text}");
|
||||
// Mirrors a chat message back to the player
|
||||
RegisterEventHandler<EventPlayerChat>(((@event, _) =>
|
||||
{
|
||||
var player = Utilities.GetPlayerFromIndex(@event.Userid);
|
||||
if (player == null) return HookResult.Continue;
|
||||
|
||||
player.PrintToChat($"You said {@event.Text}");
|
||||
return HookResult.Continue;
|
||||
}));
|
||||
|
||||
RegisterEventHandler<EventPlayerDeath>((@event, info) =>
|
||||
{
|
||||
// You can use `info.DontBroadcast` to set the dont broadcast flag on the event.
|
||||
// You can use `info.DontBroadcast` to set the don't broadcast flag on the event.
|
||||
if (new Random().NextSingle() > 0.5f)
|
||||
{
|
||||
@event.Attacker.PrintToChat($"Skipping player_death broadcast at {Server.CurrentTime}");
|
||||
@event.Attacker?.PrintToChat($"Skipping player_death broadcast at {Server.CurrentTime}");
|
||||
info.DontBroadcast = true;
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}, HookMode.Pre);
|
||||
|
||||
|
||||
RegisterEventHandler<EventGrenadeBounce>((@event, info) =>
|
||||
{
|
||||
Logger.LogInformation("Player {Player} grenade bounce", @event.Userid.PlayerName);
|
||||
Logger.LogInformation("Player \"{Player}\" grenade bounce", @event.Userid!.PlayerName);
|
||||
|
||||
return HookResult.Continue;
|
||||
}, HookMode.Pre);
|
||||
|
||||
RegisterEventHandler<EventPlayerSpawn>((@event, info) =>
|
||||
{
|
||||
if (!@event.Userid.IsValid) return 0;
|
||||
if (!@event.Userid.PlayerPawn.IsValid) return 0;
|
||||
var player = @event.Userid;
|
||||
var playerPawn = player?.PlayerPawn.Get();
|
||||
if (player == null || playerPawn == null) return HookResult.Continue;
|
||||
|
||||
Logger.LogInformation("Player spawned with entity index: {EntityIndex} & User ID: {UserId}",
|
||||
@event.Userid.Index, @event.Userid.UserId);
|
||||
playerPawn.Index, player.UserId);
|
||||
|
||||
return HookResult.Continue;
|
||||
});
|
||||
RegisterEventHandler<EventPlayerBlind>(GenericEventHandler);
|
||||
|
||||
RegisterEventHandler<EventBulletImpact>((@event, info) =>
|
||||
{
|
||||
var player = @event.Userid;
|
||||
var pawn = player.PlayerPawn.Value;
|
||||
var activeWeapon = @event.Userid.PlayerPawn.Value.WeaponServices?.ActiveWeapon.Value;
|
||||
var weapons = @event.Userid.PlayerPawn.Value.WeaponServices?.MyWeapons;
|
||||
var pawn = player?.PlayerPawn.Get();
|
||||
var activeWeapon = pawn?.WeaponServices?.ActiveWeapon.Get();
|
||||
|
||||
if (pawn == null) return HookResult.Continue;
|
||||
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
var weaponServices = player.PlayerPawn.Value.WeaponServices.As<CCSPlayer_WeaponServices>();
|
||||
player.PrintToChat(weaponServices.ActiveWeapon.Value?.DesignerName);
|
||||
player?.PrintToChat(activeWeapon?.DesignerName ?? "No Active Weapon");
|
||||
});
|
||||
|
||||
// Set player to random colour
|
||||
player.PlayerPawn.Value.Render = Color.FromArgb(Random.Shared.Next(0, 255),
|
||||
Random.Shared.Next(0, 255), Random.Shared.Next(0, 255));
|
||||
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseModelEntity", "m_clrRender");
|
||||
|
||||
activeWeapon.ReserveAmmo[0] = 250;
|
||||
activeWeapon.Clip1 = 250;
|
||||
// Set player to random colour
|
||||
pawn.Render = Color.FromArgb(Random.Shared.Next(0, 255),
|
||||
Random.Shared.Next(0, 255), Random.Shared.Next(0, 255));
|
||||
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
|
||||
|
||||
// Give player 5 health and set their reserve ammo to 250
|
||||
if (activeWeapon != null)
|
||||
{
|
||||
activeWeapon.ReserveAmmo[0] = 250;
|
||||
activeWeapon.Clip1 = 250;
|
||||
}
|
||||
|
||||
pawn.Health += 5;
|
||||
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
||||
@@ -279,20 +220,19 @@ namespace TestPlugin
|
||||
|
||||
foreach (var player in playerEntities)
|
||||
{
|
||||
//var player = new CCSPlayerController(entInst.Handle);
|
||||
if (player.InGameMoneyServices != null) player.InGameMoneyServices.Account = 1337;
|
||||
player.InGameMoneyServices!.Account = 1337;
|
||||
}
|
||||
|
||||
// Grab everything starting with cs_, but we'll only mainpulate cs_gamerules.
|
||||
// Grab everything starting with cs_, but we'll only manipulate cs_gamerules.
|
||||
// Note: this iterates through all entities, so is an expensive operation.
|
||||
var csEntities = Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_");
|
||||
Logger.LogInformation("Amount of cs_* entities: {Count}", csEntities.Count());
|
||||
var csEntities = Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_").ToArray();
|
||||
Logger.LogInformation("Amount of cs_* entities: {Count}", csEntities.Length);
|
||||
|
||||
foreach (var entity in csEntities)
|
||||
foreach (var entity in csEntities.Where(x => x.DesignerName == "cs_gamerules"))
|
||||
{
|
||||
if (entity.DesignerName != "cs_gamerules") continue;
|
||||
var gamerulesEnt = new CCSGameRules(entity.Handle);
|
||||
gamerulesEnt.CTTimeOutActive = true;
|
||||
// It's safe to cast to `CCSGameRules` here as we know the entity is a cs_gamerules entity.
|
||||
var gameRules = entity.As<CCSGameRules>();
|
||||
gameRules.CTTimeOutActive = true;
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
@@ -301,26 +241,32 @@ namespace TestPlugin
|
||||
|
||||
private void SetupListeners()
|
||||
{
|
||||
// Precache resources
|
||||
RegisterListener<Listeners.OnServerPrecacheResources>((manifest) =>
|
||||
{
|
||||
manifest.AddResource("path/to/model");
|
||||
manifest.AddResource("path/to/material");
|
||||
manifest.AddResource("path/to/particle");
|
||||
});
|
||||
|
||||
// Hook global listeners defined by CounterStrikeSharp
|
||||
RegisterListener<Listeners.OnMapStart>(mapName =>
|
||||
{
|
||||
Logger.LogInformation("Map {Map} has started!", mapName);
|
||||
});
|
||||
RegisterListener<Listeners.OnMapEnd>(() => { Logger.LogInformation($"Map has ended."); });
|
||||
RegisterListener<Listeners.OnClientConnect>((index, name, ip) =>
|
||||
RegisterListener<Listeners.OnClientConnect>((playerSlot, name, ip) =>
|
||||
{
|
||||
Logger.LogInformation("Client {Name} from {Ip} has connected!", name, ip);
|
||||
});
|
||||
RegisterListener<Listeners.OnClientAuthorized>((index, id) =>
|
||||
RegisterListener<Listeners.OnClientAuthorized>((playerSlot, steamId) =>
|
||||
{
|
||||
Logger.LogInformation("Client {Index} with address {Id}", index, id);
|
||||
Logger.LogInformation("Client {Index} with address {Id}", playerSlot, steamId);
|
||||
});
|
||||
|
||||
RegisterListener<Listeners.OnEntitySpawned>(entity =>
|
||||
{
|
||||
var designerName = entity.DesignerName;
|
||||
|
||||
switch (designerName)
|
||||
switch (entity.DesignerName)
|
||||
{
|
||||
case "smokegrenade_projectile":
|
||||
var projectile = entity.As<CSmokeGrenadeProjectile>();
|
||||
@@ -328,8 +274,8 @@ namespace TestPlugin
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
|
||||
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
|
||||
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
|
||||
projectile.SmokeColor.Y = Random.Shared.NextSingle() * 255.0f;
|
||||
projectile.SmokeColor.Z = Random.Shared.NextSingle() * 255.0f;
|
||||
Logger.LogInformation("Smoke grenade spawned with color {SmokeColor}",
|
||||
projectile.SmokeColor);
|
||||
});
|
||||
@@ -343,58 +289,6 @@ namespace TestPlugin
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupMenus()
|
||||
{
|
||||
// [Legacy] Chat Menu Example
|
||||
var largeMenu = new ChatMenu("Test Menu");
|
||||
for (int i = 1; i < 26; i++)
|
||||
{
|
||||
var i1 = i;
|
||||
largeMenu.AddMenuOption(new Random().NextSingle().ToString(),
|
||||
(player, option) => player.PrintToChat($"You just selected {option.Text}"), i1 % 5 == 0);
|
||||
}
|
||||
|
||||
var giveItemMenu = new ChatMenu("Small Menu");
|
||||
var handleGive = (CCSPlayerController player, ChatMenuOption option) =>
|
||||
{
|
||||
player.GiveNamedItem(option.Text);
|
||||
};
|
||||
|
||||
giveItemMenu.AddMenuOption("weapon_ak47", handleGive);
|
||||
giveItemMenu.AddMenuOption("weapon_p250", handleGive);
|
||||
|
||||
AddCommand("css_target", "Target Test", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
var targetResult = info.GetArgTargetResult(1);
|
||||
|
||||
if (!targetResult.Any())
|
||||
{
|
||||
player.PrintToChat("No players found.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var result in targetResult.Players)
|
||||
{
|
||||
player.PrintToChat($"Target found: {result?.PlayerName}");
|
||||
}
|
||||
});
|
||||
|
||||
AddCommand("css_menu", "Opens example menu", (player, info) => { ChatMenus.OpenMenu(player, largeMenu); });
|
||||
AddCommand("css_gunmenu", "Gun Menu", (player, info) => { ChatMenus.OpenMenu(player, giveItemMenu); });
|
||||
|
||||
for (int i = 1; i <= 9; i++)
|
||||
{
|
||||
AddCommand("css_" + i, "Command Key Handler", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
|
||||
ChatMenus.OnKeyPress(player, key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupCommands()
|
||||
{
|
||||
// Adds a new server console command
|
||||
@@ -407,24 +301,17 @@ namespace TestPlugin
|
||||
((SteamID)player.SteamID).SteamId2, info.ArgString);
|
||||
});
|
||||
|
||||
AddCommand("css_changeteam", "change team", (player, info) =>
|
||||
AddCommand("css_changeteam", "change team", (player, _) =>
|
||||
{
|
||||
if (player?.IsValid != true) return;
|
||||
if (player == null) return;
|
||||
|
||||
if ((CsTeam)player.TeamNum == CsTeam.Terrorist)
|
||||
{
|
||||
player.ChangeTeam(CsTeam.CounterTerrorist);
|
||||
}
|
||||
else
|
||||
{
|
||||
player.ChangeTeam(CsTeam.Terrorist);
|
||||
}
|
||||
player.ChangeTeam((CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist);
|
||||
});
|
||||
|
||||
// Listens for any client use of the command `jointeam`.
|
||||
AddCommandListener("jointeam", (player, info) =>
|
||||
{
|
||||
Logger.LogInformation("{PlayerName} just did a jointeam (pre) [{ArgString}]", player.PlayerName,
|
||||
Logger.LogInformation("{PlayerName} just did a jointeam (pre) [{ArgString}]", player?.PlayerName,
|
||||
info.ArgString);
|
||||
|
||||
return HookResult.Continue;
|
||||
@@ -433,21 +320,21 @@ namespace TestPlugin
|
||||
|
||||
private void SetupEntityOutputHooks()
|
||||
{
|
||||
HookEntityOutput("weapon_knife", "OnPlayerPickup", (output, name, activator, caller, value, delay) =>
|
||||
HookEntityOutput("weapon_knife", "OnPlayerPickup", (output, _, activator, caller, _, delay) =>
|
||||
{
|
||||
Logger.LogInformation("weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay);
|
||||
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
HookEntityOutput("*", "*", (output, name, activator, caller, value, delay) =>
|
||||
|
||||
HookEntityOutput("*", "*", (output, _, activator, caller, _, delay) =>
|
||||
{
|
||||
Logger.LogInformation("All EntityOutput ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay);
|
||||
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
HookEntityOutput("*", "OnStartTouch", (output, name, activator, caller, value, delay) =>
|
||||
|
||||
HookEntityOutput("*", "OnStartTouch", (_, name, activator, caller, _, delay) =>
|
||||
{
|
||||
Logger.LogInformation("OnStartTouch: ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
|
||||
return HookResult.Continue;
|
||||
@@ -470,49 +357,59 @@ namespace TestPlugin
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_testinput", "Test AcceptInput and AddEntityIOEvent")]
|
||||
public void OnTestInput(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
var pawn = player.PlayerPawn.Get();
|
||||
if (pawn == null) return;
|
||||
|
||||
pawn!.AcceptInput("SetHealth", null, null, "50");
|
||||
pawn!.AddEntityIOEvent("SetHealth", null, null, "75", 5);
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_killmeplease", "Kills the player")]
|
||||
[ConsoleCommand("css_killme", "Kills the player")]
|
||||
public void OnKillme(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
var pawn = player.PlayerPawn.Get();
|
||||
if (pawn == null) return;
|
||||
|
||||
player.PlayerPawn.Value.CommitSuicide(true, true);
|
||||
pawn.CommitSuicide(true, true);
|
||||
}
|
||||
|
||||
|
||||
[CommandHelper(minArgs: 1, usage: "[weaponName]")]
|
||||
[ConsoleCommand("css_strip", "Removes weapon by name")]
|
||||
public void OnStripActiveWeapon(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
|
||||
if (player == null || player.PlayerPawn.Get() == null) return;
|
||||
|
||||
player.RemoveItemByDesignerName(command.GetArg(1));
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_stripweapons", "Removes player weapons")]
|
||||
public void OnStripWeapons(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
if (player == null || player.PlayerPawn.Get() == null) return;
|
||||
|
||||
player.RemoveWeapons();
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_teleportup", "Teleports the player up")]
|
||||
public void OnTeleport(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
var pawn = player.PlayerPawn.Get();
|
||||
if (pawn == null) return;
|
||||
|
||||
player.PlayerPawn.Value.Teleport(player.PlayerPawn.Value.AbsOrigin.With(z: player.PlayerPawn.Value.AbsOrigin.Z + 100), player.PlayerPawn.Value.AbsRotation, new Vector(IntPtr.Zero));
|
||||
pawn.Teleport(pawn.AbsOrigin!.With(z: pawn.AbsOrigin.Z + 100), pawn.AbsRotation, new Vector(IntPtr.Zero));
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_respawn", "Respawns the player")]
|
||||
public void OnRespawn(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
if (player == null || player.PlayerPawn.Get() == null) return;
|
||||
|
||||
player.Respawn();
|
||||
}
|
||||
@@ -527,7 +424,7 @@ namespace TestPlugin
|
||||
entity.AcceptInput("Break");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_fov", "Sets the player's FOV")]
|
||||
[CommandHelper(minArgs: 1, usage: "[fov]")]
|
||||
public void OnFovCommand(CCSPlayerController? player, CommandInfo command)
|
||||
@@ -557,7 +454,7 @@ namespace TestPlugin
|
||||
}
|
||||
else
|
||||
{
|
||||
player.PrintToChat($"Level \"{mapName}\" is invalid.");
|
||||
command.ReplyToCommand($"Level \"{mapName}\" is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,15 +462,19 @@ namespace TestPlugin
|
||||
public void OnCommandGuns(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
if (player == null) return;
|
||||
if (!player.PlayerPawn.IsValid) return;
|
||||
var pawn = player.PlayerPawn.Get();
|
||||
if (pawn == null) return;
|
||||
|
||||
foreach (var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
|
||||
foreach (var weapon in pawn.WeaponServices!.MyWeapons)
|
||||
{
|
||||
var vData = weapon.Value.As<CCSWeaponBase>().VData;
|
||||
command.ReplyToCommand(string.Format("{0}, {1}, {2}, {3}, {4}, {5}", vData.Name, vData.GearSlot, vData.Price, vData.WeaponCategory, vData.WeaponType, vData.KillAward));
|
||||
var vData = weapon.Get()?.As<CCSWeaponBase>().VData;
|
||||
if (vData == null) continue;
|
||||
|
||||
command.ReplyToCommand(
|
||||
$"{vData.Name}, {vData.GearSlot}, {vData.Price}, {vData.WeaponCategory}, {vData.WeaponType}, {vData.KillAward}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_entities", "List entities")]
|
||||
public void OnCommandEntities(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
@@ -581,13 +482,13 @@ namespace TestPlugin
|
||||
{
|
||||
command.ReplyToCommand($"{entity.Index}:{entity.DesignerName}");
|
||||
}
|
||||
|
||||
|
||||
foreach (var entity in Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_"))
|
||||
{
|
||||
command.ReplyToCommand($"{entity.Index}:{entity.DesignerName}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_colors", "List Chat Colors")]
|
||||
public void OnCommandColors(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
@@ -599,14 +500,14 @@ namespace TestPlugin
|
||||
command.ReplyToCommand($" {(char)i}Color 0x{i:x}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_localetest", "Test Translations")]
|
||||
public void OnCommandLocaleTest(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
Logger.LogInformation("Current Culture is {Culture}", CultureInfo.CurrentCulture);
|
||||
command.ReplyToCommand(Localizer["testPlugin.maxPlayersAnnouncement", Server.MaxPlayers]);
|
||||
}
|
||||
|
||||
|
||||
[ConsoleCommand("css_sound", "Play a sound to client")]
|
||||
public void OnCommandSound(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -31,21 +31,20 @@
|
||||
|
||||
#include "core/managers/con_command_manager.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <public/eiface.h>
|
||||
#include <schemasystem.h>
|
||||
#include <schematypes.h>
|
||||
#include <sourcehook/sourcehook.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "scripting/callback_manager.h"
|
||||
#include "core/log.h"
|
||||
#include "core/utils.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/utils.h"
|
||||
#include "interfaces/cs2_interfaces.h"
|
||||
#include <schematypes.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <schemasystem.h>
|
||||
|
||||
#include "metamod_oslink.h"
|
||||
#include "scripting/callback_manager.h"
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace counterstrikesharp {
|
||||
@@ -55,30 +54,37 @@ json WriteTypeJson(json obj, CSchemaType* current_type)
|
||||
obj["name"] = current_type->m_sTypeName.Get();
|
||||
obj["category"] = current_type->m_eTypeCategory;
|
||||
|
||||
if (current_type->m_eTypeCategory == SCHEMA_TYPE_ATOMIC) {
|
||||
if (current_type->m_eTypeCategory == SCHEMA_TYPE_ATOMIC)
|
||||
{
|
||||
obj["atomic"] = current_type->m_eAtomicCategory;
|
||||
|
||||
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T) {
|
||||
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T)
|
||||
{
|
||||
auto atomicTType = static_cast<CSchemaType_Atomic_T*>(current_type);
|
||||
|
||||
if (atomicTType->m_pAtomicInfo != nullptr) {
|
||||
if (atomicTType->m_pAtomicInfo != nullptr)
|
||||
{
|
||||
obj["outer"] = atomicTType->m_pAtomicInfo->m_pszName1;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T ||
|
||||
current_type->m_eAtomicCategory == SCHEMA_ATOMIC_COLLECTION_OF_T) {
|
||||
|
||||
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T || current_type->m_eAtomicCategory == SCHEMA_ATOMIC_COLLECTION_OF_T)
|
||||
{
|
||||
auto atomicType = static_cast<CSchemaType_Atomic_T*>(current_type);
|
||||
|
||||
if (atomicType->GetInnerType().Get() != nullptr) {
|
||||
if (atomicType->GetInnerType().Get() != nullptr)
|
||||
{
|
||||
obj["inner"] = WriteTypeJson(json::object(), atomicType->GetInnerType().Get());
|
||||
}
|
||||
}
|
||||
} else if (current_type->m_eTypeCategory == SCHEMA_TYPE_FIXED_ARRAY) {
|
||||
}
|
||||
else if (current_type->m_eTypeCategory == SCHEMA_TYPE_FIXED_ARRAY)
|
||||
{
|
||||
auto fixedArrayType = static_cast<CSchemaType_FixedArray*>(current_type);
|
||||
obj["inner"] = WriteTypeJson(json::object(), fixedArrayType->m_pElementType);
|
||||
} else if (current_type->m_eTypeCategory == SCHEMA_TYPE_PTR) {
|
||||
}
|
||||
else if (current_type->m_eTypeCategory == SCHEMA_TYPE_PTR)
|
||||
{
|
||||
auto ptrType = static_cast<CSchemaType_Ptr*>(current_type);
|
||||
obj["inner"] = WriteTypeJson(json::object(), ptrType->m_pObjectType);
|
||||
}
|
||||
@@ -97,68 +103,73 @@ CON_COMMAND(dump_schema, "dump schema symbols")
|
||||
std::ofstream output(utils::GamedataDirectory() + "/schema.json");
|
||||
std::string line;
|
||||
|
||||
while (std::getline(inputClasses, line)) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
while (std::getline(inputClasses, line))
|
||||
{
|
||||
if (!line.empty() && line.back() == '\r')
|
||||
{
|
||||
line.pop_back();
|
||||
}
|
||||
classNames.push_back(line);
|
||||
}
|
||||
|
||||
while (std::getline(inputEnums, line)) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
while (std::getline(inputEnums, line))
|
||||
{
|
||||
if (!line.empty() && line.back() == '\r')
|
||||
{
|
||||
line.pop_back();
|
||||
}
|
||||
enumNames.push_back(line);
|
||||
}
|
||||
|
||||
CSchemaSystemTypeScope* pType =
|
||||
globals::schemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT);
|
||||
CSchemaSystemTypeScope* pType = globals::schemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT);
|
||||
|
||||
json j;
|
||||
j["classes"] = json::object();
|
||||
j["enums"] = json::object();
|
||||
|
||||
for (const auto& line : classNames) {
|
||||
for (const auto& line : classNames)
|
||||
{
|
||||
auto* pClassInfo = pType->FindDeclaredClass(line.c_str()).Get();
|
||||
if (!pClassInfo)
|
||||
continue;
|
||||
if (!pClassInfo) continue;
|
||||
|
||||
short fieldsSize = pClassInfo->m_nFieldCount;
|
||||
SchemaClassFieldData_t* pFields = pClassInfo->m_pFields;
|
||||
|
||||
j["classes"][pClassInfo->m_pszName] = json::object();
|
||||
if (pClassInfo->m_pBaseClasses) {
|
||||
j["classes"][pClassInfo->m_pszName]["parent"] =
|
||||
pClassInfo->m_pBaseClasses->m_pClass->m_pszName;
|
||||
if (pClassInfo->m_pBaseClasses)
|
||||
{
|
||||
j["classes"][pClassInfo->m_pszName]["parent"] = pClassInfo->m_pBaseClasses->m_pClass->m_pszName;
|
||||
}
|
||||
|
||||
j["classes"][pClassInfo->m_pszName]["fields"] = json::array();
|
||||
|
||||
for (int i = 0; i < fieldsSize; ++i) {
|
||||
for (int i = 0; i < fieldsSize; ++i)
|
||||
{
|
||||
SchemaClassFieldData_t& field = pFields[i];
|
||||
|
||||
j["classes"][pClassInfo->m_pszName]["fields"].push_back({
|
||||
{"name", field.m_pszName},
|
||||
{"type", WriteTypeJson(json::object(), field.m_pType)},
|
||||
{ "name", field.m_pszName },
|
||||
{ "type", WriteTypeJson(json::object(), field.m_pType) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& line : enumNames) {
|
||||
for (const auto& line : enumNames)
|
||||
{
|
||||
auto* pEnumInfo = pType->FindDeclaredEnum(line.c_str()).Get();
|
||||
if (!pEnumInfo)
|
||||
continue;
|
||||
if (!pEnumInfo) continue;
|
||||
|
||||
j["enums"][pEnumInfo->m_pszName] = json::object();
|
||||
j["enums"][pEnumInfo->m_pszName]["align"] = pEnumInfo->m_nSize;
|
||||
j["enums"][pEnumInfo->m_pszName]["items"] = json::array();
|
||||
|
||||
for (int i = 0; i < pEnumInfo->m_nEnumeratorCount; ++i) {
|
||||
for (int i = 0; i < pEnumInfo->m_nEnumeratorCount; ++i)
|
||||
{
|
||||
auto& field = pEnumInfo->m_pEnumerators[i];
|
||||
|
||||
j["enums"][pEnumInfo->m_pszName]["items"].push_back({
|
||||
{"name", field.m_pszName},
|
||||
{"value", field.m_nValue},
|
||||
{ "name", field.m_pszName },
|
||||
{ "value", field.m_nValue },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -167,8 +178,7 @@ CON_COMMAND(dump_schema, "dump schema symbols")
|
||||
output << std::setw(2) << j << std::endl;
|
||||
}
|
||||
|
||||
SH_DECL_HOOK3_void(ICvar, DispatchConCommand, SH_NOATTRIB, 0, ConCommandHandle,
|
||||
const CCommandContext&, const CCommand&);
|
||||
SH_DECL_HOOK3_void(ICvar, DispatchConCommand, SH_NOATTRIB, 0, ConCommandHandle, const CCommandContext&, const CCommand&);
|
||||
|
||||
ConCommandInfo::ConCommandInfo()
|
||||
{
|
||||
@@ -180,9 +190,7 @@ ConCommandInfo::~ConCommandInfo()
|
||||
globals::callbackManager.ReleaseCallback(callback_pre);
|
||||
globals::callbackManager.ReleaseCallback(callback_post);
|
||||
}
|
||||
ConCommandInfo::ConCommandInfo(bool bNoCallbacks) {
|
||||
|
||||
}
|
||||
ConCommandInfo::ConCommandInfo(bool bNoCallbacks) {}
|
||||
|
||||
ConCommandManager::ConCommandManager() {}
|
||||
|
||||
@@ -190,22 +198,17 @@ ConCommandManager::~ConCommandManager() {}
|
||||
|
||||
void ConCommandManager::OnAllInitialized()
|
||||
{
|
||||
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
|
||||
&ConCommandManager::Hook_DispatchConCommand, false);
|
||||
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
|
||||
&ConCommandManager::Hook_DispatchConCommand_Post, true);
|
||||
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand, false);
|
||||
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand_Post, true);
|
||||
|
||||
m_global_cmd.callback_pre = globals::callbackManager.CreateCallback("OnClientCommandGlobalPre");
|
||||
m_global_cmd.callback_post =
|
||||
globals::callbackManager.CreateCallback("OnClientCommandGlobalPost");
|
||||
m_global_cmd.callback_post = globals::callbackManager.CreateCallback("OnClientCommandGlobalPost");
|
||||
}
|
||||
|
||||
void ConCommandManager::OnShutdown()
|
||||
{
|
||||
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
|
||||
&ConCommandManager::Hook_DispatchConCommand, false);
|
||||
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
|
||||
&ConCommandManager::Hook_DispatchConCommand_Post, true);
|
||||
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand, false);
|
||||
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand_Post, true);
|
||||
|
||||
globals::callbackManager.ReleaseCallback(m_global_cmd.callback_pre);
|
||||
globals::callbackManager.ReleaseCallback(m_global_cmd.callback_post);
|
||||
@@ -219,10 +222,14 @@ void CommandCallback(const CCommandContext& context, const CCommand& command)
|
||||
|
||||
void ConCommandManager::AddCommandListener(const char* name, CallbackT callback, HookMode mode)
|
||||
{
|
||||
if (name == nullptr) {
|
||||
if (mode == HookMode::Pre) {
|
||||
if (name == nullptr)
|
||||
{
|
||||
if (mode == HookMode::Pre)
|
||||
{
|
||||
m_global_cmd.callback_pre->AddListener(callback);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_global_cmd.callback_post->AddListener(callback);
|
||||
}
|
||||
return;
|
||||
@@ -231,29 +238,38 @@ void ConCommandManager::AddCommandListener(const char* name, CallbackT callback,
|
||||
auto strName = std::string(name);
|
||||
ConCommandInfo* pInfo = m_cmd_lookup[strName];
|
||||
|
||||
if (!pInfo) {
|
||||
if (!pInfo)
|
||||
{
|
||||
pInfo = new ConCommandInfo();
|
||||
m_cmd_lookup[strName] = pInfo;
|
||||
|
||||
ConCommandHandle hExistingCommand = globals::cvars->FindCommand(name);
|
||||
if (hExistingCommand.IsValid()) {
|
||||
if (hExistingCommand.IsValid())
|
||||
{
|
||||
pInfo->command = globals::cvars->GetCommand(hExistingCommand);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == HookMode::Pre) {
|
||||
if (mode == HookMode::Pre)
|
||||
{
|
||||
pInfo->callback_pre->AddListener(callback);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
pInfo->callback_post->AddListener(callback);
|
||||
}
|
||||
}
|
||||
|
||||
void ConCommandManager::RemoveCommandListener(const char* name, CallbackT callback, HookMode mode)
|
||||
{
|
||||
if (name == nullptr) {
|
||||
if (mode == HookMode::Pre) {
|
||||
if (name == nullptr)
|
||||
{
|
||||
if (mode == HookMode::Pre)
|
||||
{
|
||||
m_global_cmd.callback_pre->RemoveListener(callback);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_global_cmd.callback_post->RemoveListener(callback);
|
||||
}
|
||||
return;
|
||||
@@ -262,31 +278,33 @@ void ConCommandManager::RemoveCommandListener(const char* name, CallbackT callba
|
||||
auto strName = std::string(name);
|
||||
ConCommandInfo* pInfo = m_cmd_lookup[strName];
|
||||
|
||||
if (!pInfo) {
|
||||
if (!pInfo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == HookMode::Pre) {
|
||||
if (mode == HookMode::Pre)
|
||||
{
|
||||
pInfo->callback_pre->RemoveListener(callback);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
pInfo->callback_post->RemoveListener(callback);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConCommandManager::AddValveCommand(const char* name, const char* description, bool server_only,
|
||||
int flags)
|
||||
bool ConCommandManager::AddValveCommand(const char* name, const char* description, bool server_only, int flags)
|
||||
{
|
||||
ConCommandHandle hExistingCommand = globals::cvars->FindCommand(name);
|
||||
if (hExistingCommand.IsValid())
|
||||
return false;
|
||||
if (hExistingCommand.IsValid()) return false;
|
||||
|
||||
ConCommandRefAbstract conCommandRefAbstract;
|
||||
auto conCommand =
|
||||
new ConCommand(&conCommandRefAbstract, strdup(name), CommandCallback, strdup(description), flags);
|
||||
auto conCommand = new ConCommand(&conCommandRefAbstract, strdup(name), CommandCallback, description ? strdup(description) : "", flags);
|
||||
|
||||
ConCommandInfo* pInfo = m_cmd_lookup[std::string(name)];
|
||||
|
||||
if (!pInfo) {
|
||||
if (!pInfo)
|
||||
{
|
||||
pInfo = new ConCommandInfo();
|
||||
m_cmd_lookup[std::string(name)] = pInfo;
|
||||
}
|
||||
@@ -302,14 +320,16 @@ bool ConCommandManager::RemoveValveCommand(const char* name)
|
||||
{
|
||||
auto hFoundCommand = globals::cvars->FindCommand(name);
|
||||
|
||||
if (!hFoundCommand.IsValid()) {
|
||||
if (!hFoundCommand.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
globals::cvars->UnregisterConCommand(hFoundCommand);
|
||||
|
||||
auto pInfo = m_cmd_lookup[std::string(name)];
|
||||
if (!pInfo) {
|
||||
if (!pInfo)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -318,11 +338,10 @@ bool ConCommandManager::RemoveValveCommand(const char* name)
|
||||
return true;
|
||||
}
|
||||
|
||||
HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CCommandContext& ctx,
|
||||
const CCommand& args, HookMode mode, CommandCallingContext callingContext)
|
||||
HookResult ConCommandManager::ExecuteCommandCallbacks(
|
||||
const char* name, const CCommandContext& ctx, const CCommand& args, HookMode mode, CommandCallingContext callingContext)
|
||||
{
|
||||
CSSHARP_CORE_TRACE("[ConCommandManager::ExecuteCommandCallbacks][{}]: {}",
|
||||
mode == Pre ? "Pre" : "Post", name);
|
||||
CSSHARP_CORE_TRACE("[ConCommandManager::ExecuteCommandCallbacks][{}]: {}", mode == Pre ? "Pre" : "Post", name);
|
||||
ConCommandInfo* pInfo = m_cmd_lookup[std::string(name)];
|
||||
|
||||
HookResult result = HookResult::Continue;
|
||||
@@ -331,20 +350,23 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
|
||||
|
||||
m_cmd_contexts[&args] = callingContext;
|
||||
|
||||
if (globalCallback->GetFunctionCount() > 0) {
|
||||
if (globalCallback->GetFunctionCount() > 0)
|
||||
{
|
||||
globalCallback->ScriptContext().Reset();
|
||||
globalCallback->ScriptContext().Push(ctx.GetPlayerSlot().Get());
|
||||
globalCallback->ScriptContext().Push(&args);
|
||||
|
||||
for (auto fnMethodToCall : globalCallback->GetFunctions()) {
|
||||
if (!fnMethodToCall)
|
||||
continue;
|
||||
for (auto fnMethodToCall : globalCallback->GetFunctions())
|
||||
{
|
||||
if (!fnMethodToCall) continue;
|
||||
fnMethodToCall(&globalCallback->ScriptContextStruct());
|
||||
|
||||
auto hookResult = globalCallback->ScriptContext().GetResult<HookResult>();
|
||||
|
||||
if (hookResult >= HookResult::Stop) {
|
||||
if (mode == HookMode::Pre) {
|
||||
if (hookResult >= HookResult::Stop)
|
||||
{
|
||||
if (mode == HookMode::Pre)
|
||||
{
|
||||
return HookResult::Stop;
|
||||
}
|
||||
|
||||
@@ -352,13 +374,15 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
|
||||
break;
|
||||
}
|
||||
|
||||
if (hookResult >= HookResult::Handled) {
|
||||
if (hookResult >= HookResult::Handled)
|
||||
{
|
||||
result = hookResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pInfo) {
|
||||
if (!pInfo)
|
||||
{
|
||||
m_cmd_contexts.erase(&args);
|
||||
return result;
|
||||
}
|
||||
@@ -369,17 +393,20 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
|
||||
pCallback->ScriptContext().Push(ctx.GetPlayerSlot().Get());
|
||||
pCallback->ScriptContext().Push(&args);
|
||||
|
||||
for (auto fnMethodToCall : pCallback->GetFunctions()) {
|
||||
if (!fnMethodToCall)
|
||||
continue;
|
||||
for (auto fnMethodToCall : pCallback->GetFunctions())
|
||||
{
|
||||
if (!fnMethodToCall) continue;
|
||||
fnMethodToCall(&pCallback->ScriptContextStruct());
|
||||
|
||||
auto thisResult = pCallback->ScriptContext().GetResult<HookResult>();
|
||||
|
||||
if (thisResult >= HookResult::Handled) {
|
||||
if (thisResult >= HookResult::Handled)
|
||||
{
|
||||
m_cmd_contexts.erase(&args);
|
||||
return thisResult;
|
||||
} else if (thisResult > result) {
|
||||
}
|
||||
else if (thisResult > result)
|
||||
{
|
||||
result = thisResult;
|
||||
}
|
||||
}
|
||||
@@ -389,36 +416,34 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
|
||||
return result;
|
||||
}
|
||||
|
||||
void ConCommandManager::Hook_DispatchConCommand(ConCommandHandle cmd, const CCommandContext& ctx,
|
||||
const CCommand& args)
|
||||
void ConCommandManager::Hook_DispatchConCommand(ConCommandHandle cmd, const CCommandContext& ctx, const CCommand& args)
|
||||
{
|
||||
const char* name = args.Arg(0);
|
||||
|
||||
CSSHARP_CORE_TRACE("[ConCommandManager::Hook_DispatchConCommand]: {}", name);
|
||||
|
||||
auto result = ExecuteCommandCallbacks(name, ctx, args, HookMode::Pre, CommandCallingContext::Console);
|
||||
if (result >= HookResult::Handled) {
|
||||
if (result >= HookResult::Handled)
|
||||
{
|
||||
RETURN_META(MRES_SUPERCEDE);
|
||||
}
|
||||
}
|
||||
void ConCommandManager::Hook_DispatchConCommand_Post(ConCommandHandle cmd,
|
||||
const CCommandContext& ctx,
|
||||
const CCommand& args)
|
||||
void ConCommandManager::Hook_DispatchConCommand_Post(ConCommandHandle cmd, const CCommandContext& ctx, const CCommand& args)
|
||||
{
|
||||
const char* name = args.Arg(0);
|
||||
|
||||
auto result = ExecuteCommandCallbacks(name, ctx, args, HookMode::Post, CommandCallingContext::Console);
|
||||
if (result >= HookResult::Handled) {
|
||||
if (result >= HookResult::Handled)
|
||||
{
|
||||
RETURN_META(MRES_SUPERCEDE);
|
||||
}
|
||||
}
|
||||
bool ConCommandManager::IsValidValveCommand(const char* name) {
|
||||
bool ConCommandManager::IsValidValveCommand(const char* name)
|
||||
{
|
||||
ConCommandHandle pCmd = globals::cvars->FindCommand(name);
|
||||
return pCmd.IsValid();
|
||||
}
|
||||
|
||||
CommandCallingContext ConCommandManager::GetCommandCallingContext(CCommand* args) {
|
||||
return m_cmd_contexts[args];
|
||||
}
|
||||
CommandCallingContext ConCommandManager::GetCommandCallingContext(CCommand* args) { return m_cmd_contexts[args]; }
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
} // namespace counterstrikesharp
|
||||
|
||||
@@ -46,6 +46,22 @@ void EntityManager::OnAllInitialized()
|
||||
return;
|
||||
}
|
||||
|
||||
CEntityInstance_AcceptInput = decltype(CEntityInstance_AcceptInput)(
|
||||
modules::server->FindSignature(globals::gameConfig->GetSignature("CEntityInstance_AcceptInput")));
|
||||
|
||||
if (!CEntityInstance_AcceptInput)
|
||||
{
|
||||
CSSHARP_CORE_CRITICAL("Failed to find signature for \'CEntityInstance_AcceptInput\'");
|
||||
}
|
||||
|
||||
CEntitySystem_AddEntityIOEvent = decltype(CEntitySystem_AddEntityIOEvent)(
|
||||
modules::server->FindSignature(globals::gameConfig->GetSignature("CEntitySystem_AddEntityIOEvent")));
|
||||
|
||||
if (!CEntitySystem_AddEntityIOEvent)
|
||||
{
|
||||
CSSHARP_CORE_CRITICAL("Failed to find signature for \'CEntitySystem_AddEntityIOEvent\'");
|
||||
}
|
||||
|
||||
auto m_hook = funchook_create();
|
||||
funchook_prepare(m_hook, (void**)&m_pFireOutputInternal, (void*)&DetourFireOutputInternal);
|
||||
funchook_install(m_hook, 0);
|
||||
@@ -219,4 +235,4 @@ void DetourFireOutputInternal(CEntityIOOutput* const pThis, CEntityInstance* pAc
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
} // namespace counterstrikesharp
|
||||
|
||||
@@ -111,4 +111,15 @@ static void DetourFireOutputInternal(CEntityIOOutput* const pThis, CEntityInstan
|
||||
|
||||
static FireOutputInternal m_pFireOutputInternal = nullptr;
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
// Do it in here because i didn't found a good place to do this
|
||||
inline void (*CEntityInstance_AcceptInput)(CEntityInstance* pThis, const char* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, int nOutputID);
|
||||
|
||||
inline void (*CEntitySystem_AddEntityIOEvent)(CEntitySystem* pEntitySystem,
|
||||
CEntityInstance* pTarget,
|
||||
const char* pInputName,
|
||||
CEntityInstance* pActivator,
|
||||
CEntityInstance* pCaller,
|
||||
variant_t* value,
|
||||
float delay,
|
||||
int nOutputID);
|
||||
} // namespace counterstrikesharp
|
||||
|
||||
@@ -193,6 +193,45 @@ void UnhookEntityOutput(ScriptContext& script_context)
|
||||
globals::entityManager.UnhookEntityOutput(szClassname, szOutput, callback, mode);
|
||||
}
|
||||
|
||||
void AcceptInput(ScriptContext& script_context)
|
||||
{
|
||||
if (!CEntityInstance_AcceptInput)
|
||||
{
|
||||
script_context.ThrowNativeError("Failed to find signature for \'CEntityInstance_AcceptInput\'");
|
||||
return;
|
||||
}
|
||||
|
||||
CEntityInstance* pThis = script_context.GetArgument<CEntityInstance*>(0);
|
||||
const char* pInputName = script_context.GetArgument<const char*>(1);
|
||||
CEntityInstance* pActivator = script_context.GetArgument<CEntityInstance*>(2);
|
||||
CEntityInstance* pCaller = script_context.GetArgument<CEntityInstance*>(3);
|
||||
const char* value = script_context.GetArgument<const char*>(4);
|
||||
int outputID = script_context.GetArgument<int>(5);
|
||||
|
||||
variant_t _value = variant_t(value);
|
||||
CEntityInstance_AcceptInput(pThis, pInputName, pActivator, pCaller, &_value, outputID);
|
||||
}
|
||||
|
||||
void AddEntityIOEvent(ScriptContext& script_context)
|
||||
{
|
||||
if (!CEntitySystem_AddEntityIOEvent)
|
||||
{
|
||||
script_context.ThrowNativeError("Failed to find signature for \'CEntitySystem_AddEntityIOEvent\'");
|
||||
return;
|
||||
}
|
||||
|
||||
CEntityInstance* pTarget = script_context.GetArgument<CEntityInstance*>(0);
|
||||
const char* pInputName = script_context.GetArgument<const char*>(1);
|
||||
CEntityInstance* pActivator = script_context.GetArgument<CEntityInstance*>(2);
|
||||
CEntityInstance* pCaller = script_context.GetArgument<CEntityInstance*>(3);
|
||||
const char* value = script_context.GetArgument<const char*>(4);
|
||||
float delay = script_context.GetArgument<float>(5);
|
||||
int outputID = script_context.GetArgument<int>(6);
|
||||
|
||||
variant_t _value = variant_t(value);
|
||||
CEntitySystem_AddEntityIOEvent(GameEntitySystem(), pTarget, pInputName, pActivator, pCaller, &_value, delay, outputID);
|
||||
}
|
||||
|
||||
REGISTER_NATIVES(entities, {
|
||||
ScriptEngine::RegisterNativeHandler("GET_ENTITY_FROM_INDEX", GetEntityFromIndex);
|
||||
ScriptEngine::RegisterNativeHandler("GET_USERID_FROM_INDEX", GetUserIdFromIndex);
|
||||
@@ -209,5 +248,7 @@ REGISTER_NATIVES(entities, {
|
||||
ScriptEngine::RegisterNativeHandler("GET_PLAYER_IP_ADDRESS", GetPlayerIpAddress);
|
||||
ScriptEngine::RegisterNativeHandler("HOOK_ENTITY_OUTPUT", HookEntityOutput);
|
||||
ScriptEngine::RegisterNativeHandler("UNHOOK_ENTITY_OUTPUT", UnhookEntityOutput);
|
||||
ScriptEngine::RegisterNativeHandler("ACCEPT_INPUT", AcceptInput);
|
||||
ScriptEngine::RegisterNativeHandler("ADD_ENTITY_IO_EVENT", AddEntityIOEvent);
|
||||
})
|
||||
} // namespace counterstrikesharp
|
||||
} // namespace counterstrikesharp
|
||||
|
||||
@@ -11,4 +11,6 @@ GET_FIRST_ACTIVE_ENTITY: -> pointer
|
||||
GET_PLAYER_AUTHORIZED_STEAMID: slot:int -> uint64
|
||||
GET_PLAYER_IP_ADDRESS: slot:int -> string
|
||||
HOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:HookMode -> void
|
||||
UNHOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:HookMode -> void
|
||||
UNHOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:HookMode -> void
|
||||
ACCEPT_INPUT: pThis:pointer, inputName:string, activator:pointer, caller:pointer, value:string, outputID:int -> void
|
||||
ADD_ENTITY_IO_EVENT: pTarget:pointer, inputName:string, activator:pointer, caller:pointer, value:string, delay:float, outputID:int -> void
|
||||
|
||||
@@ -88,7 +88,7 @@ public class Mapping
|
||||
case "player_controller":
|
||||
case "player_pawn":
|
||||
case "player_controller_and_pawn":
|
||||
return "CCSPlayerController";
|
||||
return "CCSPlayerController?";
|
||||
case "ehandle":
|
||||
return "IntPtr";
|
||||
case "uint64":
|
||||
@@ -97,4 +97,4 @@ public class Mapping
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,32 @@ public partial class Generators
|
||||
public string NamePascalCase => Name.ToPascalCase();
|
||||
public string MappedType => Mapping.GetCSharpTypeFromGameEventType(Type);
|
||||
public string? Comment { get; set; }
|
||||
|
||||
public string Getter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MappedType == "CCSPlayerController?")
|
||||
{
|
||||
return $"GetPlayer(\"{Name}\")";
|
||||
}
|
||||
|
||||
return $"Get<{MappedType}>(\"{Name}\")";
|
||||
}
|
||||
}
|
||||
|
||||
public string Setter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MappedType == "CCSPlayerController?")
|
||||
{
|
||||
return $"SetPlayer(\"{Name}\", value)";
|
||||
}
|
||||
|
||||
return $"Set<{MappedType}>(\"{Name}\", value)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClient _httpClient = new HttpClient();
|
||||
@@ -88,6 +114,8 @@ public partial class Generators
|
||||
return allGameEvents.Values.ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static async Task GenerateGameEvents()
|
||||
{
|
||||
var allGameEvents = await GetGameEvents();
|
||||
@@ -102,12 +130,12 @@ public partial class Generators
|
||||
: key.NamePascalCase;
|
||||
|
||||
return $@"
|
||||
|
||||
|
||||
{(!string.IsNullOrEmpty(key.Comment) ? "// " + key.Comment : "")}
|
||||
public {key.MappedType} {propertyName}
|
||||
public {key.MappedType} {propertyName}
|
||||
{{
|
||||
get => Get<{key.MappedType}>(""{key.Name}"");
|
||||
set => Set<{key.MappedType}>(""{key.Name}"", value);
|
||||
get => {key.Getter};
|
||||
set => {key.Setter};
|
||||
}}";
|
||||
});
|
||||
return $@"
|
||||
@@ -120,9 +148,10 @@ public partial class Generators
|
||||
{string.Join("\n", propertyDefinition)}
|
||||
}}";
|
||||
}));
|
||||
|
||||
|
||||
|
||||
var result = $@"
|
||||
#nullable enable
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Modules.Events;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
@@ -132,11 +161,12 @@ namespace CounterStrikeSharp.API.Core
|
||||
{{
|
||||
{gameEventsString}
|
||||
}}
|
||||
#nullable restore
|
||||
";
|
||||
|
||||
|
||||
Console.WriteLine($"Generated C# bindings for {allGameEvents.Count} game events successfully.");
|
||||
|
||||
File.WriteAllText(Path.Join(Helpers.GetRootDirectory(), "managed/CounterStrikeSharp.API/Core/GameEvents.g.cs"),
|
||||
result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user