mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-06 08:03:12 -08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d295589c44 | ||
|
|
16767fd494 | ||
|
|
36a97bfffd | ||
|
|
178f7472c6 | ||
|
|
cba5144bbf | ||
|
|
0de951cb6f | ||
|
|
c3d44a87bc | ||
|
|
bd3c0c76e3 | ||
|
|
656c0e3a84 | ||
|
|
40c842149c | ||
|
|
64d1c0a9f4 |
@@ -49,6 +49,8 @@ SET(SOURCE_FILES
|
||||
src/core/managers/event_manager.cpp
|
||||
src/core/timer_system.h
|
||||
src/core/timer_system.cpp
|
||||
src/core/tick_scheduler.h
|
||||
src/core/tick_scheduler.cpp
|
||||
src/scripting/autonative.h
|
||||
src/scripting/natives/natives_engine.cpp
|
||||
src/core/engine_trace.h
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"SilentChatTrigger": [ "/" ],
|
||||
"FollowCS2ServerGuidelines": true,
|
||||
"PluginHotReloadEnabled": true,
|
||||
"PluginAutoLoadEnabled": true,
|
||||
"ServerLanguage": "en"
|
||||
}
|
||||
@@ -33,6 +33,10 @@ receive a ban.
|
||||
|
||||
When enabled, plugins are automatically reloaded when their .dll file is updated.
|
||||
|
||||
## PluginAutoLoadEnabled
|
||||
|
||||
When enabled, plugins are automatically loaded from the plugins directory on server start.
|
||||
|
||||
## ServerLanguage
|
||||
|
||||
Configures the default language to use for server commands & messages. The format for the culture name based on RFC 4646 is `languagecode2-country`/`regioncode2`, where `languagecode2` is the two-letter language code and `country/regioncode2` is the two-letter subculture code. Examples include `ja-JP` for Japanese (Japan) and `en-US` for English (United States). Defaults to "en".
|
||||
@@ -1,5 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
@@ -5,7 +5,7 @@ using MySharedTypes.Contracts;
|
||||
|
||||
namespace WithSharedTypes;
|
||||
|
||||
[MinimumApiVersion(143)]
|
||||
[MinimumApiVersion(184)]
|
||||
public class WithSharedTypesPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: Shared Types";
|
||||
@@ -54,4 +54,4 @@ public class WithSharedTypesPlugin : BasePlugin
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using MySharedTypes.Contracts;
|
||||
|
||||
namespace WithSharedTypesConsumer;
|
||||
|
||||
[MinimumApiVersion(142)]
|
||||
[MinimumApiVersion(184)]
|
||||
public class WithSharedTypesConsumerPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: Shared Types (Consumer)";
|
||||
@@ -53,4 +53,4 @@ public class WithSharedTypesConsumerPlugin : BasePlugin
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3747
libraries/moodycamel/concurrentqueue.h
Normal file
3747
libraries/moodycamel/concurrentqueue.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,7 @@ include_directories(
|
||||
libraries/tl
|
||||
libraries/funchook/include
|
||||
libraries/DynoHook/src
|
||||
libraries/moodycamel
|
||||
libraries
|
||||
)
|
||||
|
||||
|
||||
@@ -427,6 +427,24 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.FunctionReference.Create(System.Delegate)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.FunctionReference.Get(System.Int32)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.NativeAPI.GetGameFrameTime</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.NativeAPI.QueueTaskForNextFrame(System.IntPtr)</Target>
|
||||
@@ -451,6 +469,24 @@
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.Translations.JsonStringLocalizerFactory.#ctor(CounterStrikeSharp.API.Core.Plugin.IPluginContext)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Modules.Memory.DynamicFunctions.DynamicHook.GetReturn``1(System.Int32)</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Server.get_GameFrameTime</Target>
|
||||
<Left>.\ApiCompat\v151.dll</Left>
|
||||
<Right>obj\Debug\net7.0\CounterStrikeSharp.API.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:CounterStrikeSharp.API.Core.IPlugin.OnAllPluginsLoaded(System.Boolean)</Target>
|
||||
|
||||
@@ -315,16 +315,6 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetGameFrameTime(){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x97E331CA);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (float)ScriptContext.GlobalScriptContext.GetResult(typeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetEngineTime(){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -512,6 +502,17 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void QueueTaskForFrame(int tick, InputArgument callback){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(tick);
|
||||
ScriptContext.GlobalScriptContext.Push((InputArgument)callback);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x2F92C340);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static void QueueTaskForNextWorldUpdate(InputArgument callback){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -749,6 +750,16 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void FreeEvent(IntPtr gameevent){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(gameevent);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x7E8B60C2);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static void FireEvent(IntPtr gameevent, bool dontbroadcast){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace CounterStrikeSharp.API.Core
|
||||
[JsonPropertyName("PluginHotReloadEnabled")]
|
||||
public bool PluginHotReloadEnabled { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("PluginAutoLoadEnabled")]
|
||||
public bool PluginAutoLoadEnabled { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("ServerLanguage")]
|
||||
public string ServerLanguage { get; set; } = "en";
|
||||
}
|
||||
@@ -95,7 +98,13 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// </summary>
|
||||
public static bool PluginHotReloadEnabled => _coreConfig.PluginHotReloadEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// When enabled, plugins are automatically loaded from the plugins directory on server start.
|
||||
/// </summary>
|
||||
public static bool PluginAutoLoadEnabled => _coreConfig.PluginAutoLoadEnabled;
|
||||
|
||||
public static string ServerLanguage => _coreConfig.ServerLanguage;
|
||||
|
||||
}
|
||||
|
||||
public partial class CoreConfig : IStartupService
|
||||
|
||||
@@ -14,11 +14,9 @@
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
@@ -30,140 +28,149 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
/// <summary>Delegate will be removed after the first invocation.</summary>
|
||||
SingleUse,
|
||||
/// <summary>Delegate will remain in memory for the lifetime of the application.</summary>
|
||||
|
||||
/// <summary>Delegate will remain in memory for the lifetime of the application (or until <see cref="FunctionReference.Remove"/> is called).</summary>
|
||||
Permanent
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a reference to a function that can be called from native code.
|
||||
/// </summary>
|
||||
public class FunctionReference
|
||||
{
|
||||
private readonly Delegate m_method;
|
||||
|
||||
public FunctionLifetime Lifetime { get; set; } = FunctionLifetime.Permanent;
|
||||
|
||||
public unsafe delegate void CallbackDelegate(fxScriptContext* context);
|
||||
private CallbackDelegate s_callback;
|
||||
|
||||
private FunctionReference(Delegate method)
|
||||
private static readonly ConcurrentDictionary<int, FunctionReference> IdToFunctionReferencesMap = new();
|
||||
private static readonly ConcurrentDictionary<Delegate, FunctionReference> TargetMethodToFunctionReferencesMap = new();
|
||||
|
||||
private static readonly object ReferenceCounterLock = new();
|
||||
private static int _referenceCounter;
|
||||
|
||||
private readonly Delegate _targetMethod;
|
||||
private readonly CallbackDelegate _nativeCallback;
|
||||
|
||||
private readonly TaskCompletionSource _taskCompletionSource = new();
|
||||
|
||||
private FunctionReference(Delegate method, FunctionLifetime lifetime)
|
||||
{
|
||||
m_method = method;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var dg = new CallbackDelegate((fxScriptContext* context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var scriptContext = new ScriptContext(context);
|
||||
|
||||
if (method.Method.GetParameters().FirstOrDefault()?.ParameterType == typeof(ScriptContext))
|
||||
{
|
||||
var returnO = m_method.DynamicInvoke(scriptContext);
|
||||
if (returnO != null)
|
||||
{
|
||||
scriptContext.SetResult(returnO, context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var paramsList = method.Method.GetParameters().Select((x, i) =>
|
||||
{
|
||||
var param = method.Method.GetParameters()[i];
|
||||
object obj = null;
|
||||
if (typeof(NativeObject).IsAssignableFrom(param.ParameterType))
|
||||
{
|
||||
obj = Activator.CreateInstance(param.ParameterType,
|
||||
new[] { scriptContext.GetArgument(typeof(IntPtr), i) });
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = scriptContext.GetArgument(param.ParameterType, i);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}).ToArray();
|
||||
|
||||
var returnObj = m_method.DynamicInvoke(paramsList);
|
||||
|
||||
if (returnObj != null)
|
||||
{
|
||||
scriptContext.SetResult(returnObj, context);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.Instance.Logger.LogError(e, "Error invoking callback");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Lifetime == FunctionLifetime.SingleUse)
|
||||
{
|
||||
Remove(Identifier);
|
||||
if (references.ContainsKey(m_method))
|
||||
references.Remove(m_method, out _);
|
||||
}
|
||||
}
|
||||
});
|
||||
s_callback = dg;
|
||||
}
|
||||
|
||||
Lifetime = lifetime;
|
||||
_targetMethod = method;
|
||||
_nativeCallback = CreateWrappedCallback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="FunctionLifetime"/>
|
||||
/// </summary>
|
||||
public FunctionLifetime Lifetime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// For <see cref="FunctionLifetime.SingleUse"/> function references, this task will complete when
|
||||
/// the function has finished invoking.
|
||||
/// </summary>
|
||||
public Task CompletionTask => _taskCompletionSource.Task;
|
||||
|
||||
public int Identifier { get; private set; }
|
||||
|
||||
public static FunctionReference Create(Delegate method)
|
||||
private unsafe CallbackDelegate CreateWrappedCallback()
|
||||
{
|
||||
if (references.TryGetValue(method, out var existingReference))
|
||||
return context =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var scriptContext = new ScriptContext(context);
|
||||
|
||||
// Allow for manual handling of the script context
|
||||
if (_targetMethod.Method.GetParameters().FirstOrDefault()?.ParameterType == typeof(ScriptContext))
|
||||
{
|
||||
var returnValue = _targetMethod.DynamicInvoke(scriptContext);
|
||||
if (returnValue != null)
|
||||
{
|
||||
scriptContext.SetResult(returnValue, context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var parameterList = _targetMethod.Method.GetParameters().Select((_, i) =>
|
||||
{
|
||||
var parameter = _targetMethod.Method.GetParameters()[i];
|
||||
return scriptContext.GetArgument(parameter.ParameterType, i);
|
||||
}).ToArray();
|
||||
|
||||
var returnObj = _targetMethod.DynamicInvoke(parameterList);
|
||||
|
||||
if (returnObj != null)
|
||||
{
|
||||
scriptContext.SetResult(returnObj, context);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.Instance.Logger.LogError(e, "Error invoking callback");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Lifetime == FunctionLifetime.SingleUse)
|
||||
{
|
||||
RemoveSelf();
|
||||
}
|
||||
|
||||
_taskCompletionSource.TrySetResult();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static FunctionReference Create(Delegate method, FunctionLifetime lifetime = FunctionLifetime.Permanent)
|
||||
{
|
||||
// We always want to create a new reference if the lifetime is single use.
|
||||
if (lifetime == FunctionLifetime.Permanent && TargetMethodToFunctionReferencesMap.TryGetValue(method, out var existingReference))
|
||||
{
|
||||
return existingReference;
|
||||
}
|
||||
|
||||
var reference = new FunctionReference(method);
|
||||
var reference = new FunctionReference(method, lifetime);
|
||||
var referenceId = Register(reference);
|
||||
|
||||
reference.Identifier = referenceId;
|
||||
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<int, FunctionReference> ms_references = new ConcurrentDictionary<int, FunctionReference>();
|
||||
private static int ms_referenceId;
|
||||
|
||||
private static ConcurrentDictionary<Delegate, FunctionReference> references =
|
||||
new ConcurrentDictionary<Delegate, FunctionReference>();
|
||||
|
||||
private static int Register(FunctionReference reference)
|
||||
{
|
||||
var thisRefId = ms_referenceId;
|
||||
ms_references[thisRefId] = reference;
|
||||
references[reference.m_method] = reference;
|
||||
|
||||
unchecked { ms_referenceId++; }
|
||||
|
||||
return thisRefId;
|
||||
}
|
||||
|
||||
public static FunctionReference Get(int reference)
|
||||
{
|
||||
if (ms_references.ContainsKey(reference))
|
||||
lock (ReferenceCounterLock)
|
||||
{
|
||||
return ms_references[reference];
|
||||
}
|
||||
var thisRefId = _referenceCounter;
|
||||
IdToFunctionReferencesMap[thisRefId] = reference;
|
||||
TargetMethodToFunctionReferencesMap[reference._targetMethod] = reference;
|
||||
|
||||
return null;
|
||||
unchecked
|
||||
{
|
||||
_referenceCounter++;
|
||||
}
|
||||
|
||||
return thisRefId;
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr GetFunctionPointer()
|
||||
|
||||
public IntPtr GetFunctionPointer() => Marshal.GetFunctionPointerForDelegate(_nativeCallback);
|
||||
|
||||
private void RemoveSelf()
|
||||
{
|
||||
IntPtr cb = Marshal.GetFunctionPointerForDelegate(s_callback);
|
||||
return cb;
|
||||
Remove(Identifier);
|
||||
}
|
||||
|
||||
public static void Remove(int reference)
|
||||
{
|
||||
if (ms_references.TryGetValue(reference, out var funcRef))
|
||||
if (IdToFunctionReferencesMap.TryGetValue(reference, out var functionReference))
|
||||
{
|
||||
ms_references.Remove(reference, out _);
|
||||
if (TargetMethodToFunctionReferencesMap.ContainsKey(functionReference._targetMethod))
|
||||
{
|
||||
TargetMethodToFunctionReferencesMap.Remove(functionReference._targetMethod, out _);
|
||||
}
|
||||
|
||||
IdToFunctionReferencesMap.Remove(reference, out _);
|
||||
|
||||
Application.Instance.Logger.LogDebug("Removing function/callback reference: {Reference}", reference);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ public partial class CCSGameRules
|
||||
/// </summary>
|
||||
public void TerminateRound(float delay, RoundEndReason roundEndReason)
|
||||
{
|
||||
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay);
|
||||
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay, 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ public partial class CCSPlayerController
|
||||
Userid = this
|
||||
};
|
||||
@event.FireEventToClient(this);
|
||||
@event.Free();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -78,29 +78,38 @@ public class PluginManager : IPluginManager
|
||||
|
||||
if (!_sharedAssemblies.TryGetValue(name.FullName, out var assembly))
|
||||
{
|
||||
_logger.LogError("Failed to use existing shared assembly: {Name}", name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return assembly;
|
||||
};
|
||||
|
||||
foreach (var path in pluginAssemblyPaths)
|
||||
if (CoreConfig.PluginAutoLoadEnabled)
|
||||
{
|
||||
foreach (var path in pluginAssemblyPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadPlugin(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to load plugin from {Path}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var plugin in _loadedPluginContexts)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadPlugin(path);
|
||||
plugin.Plugin?.OnAllPluginsLoaded(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to load plugin from {Path}", path);
|
||||
_logger.LogError(e, "OnAllPluginsLoaded failed");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var plugin in _loadedPluginContexts)
|
||||
{
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<PluginContext> GetLoadedPlugins()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Global using directives
|
||||
|
||||
global using System;
|
||||
global using System.Linq;
|
||||
global using System.IO;
|
||||
global using System.Collections.Generic;
|
||||
global using CounterStrikeSharp.API.Core;
|
||||
@@ -18,34 +18,37 @@ namespace CounterStrikeSharp.API.Modules.Entities.Constants
|
||||
{
|
||||
public enum CollisionGroup
|
||||
{
|
||||
COLLISION_GROUP_NONE = 0,
|
||||
COLLISION_GROUP_DEBRIS, // Collides with nothing but world and static stuff
|
||||
COLLISION_GROUP_DEBRIS_TRIGGER, // Same as debris, but hits triggers
|
||||
COLLISION_GROUP_INTERACTIVE_DEBRIS, // Collides with everything except other interactive debris or debris
|
||||
COLLISION_GROUP_INTERACTIVE, // Collides with everything except interactive debris or debris
|
||||
COLLISION_GROUP_NONE = 0,
|
||||
COLLISION_GROUP_NEVER,
|
||||
COLLISION_GROUP_TRIGGER,
|
||||
COLLISION_GROUP_CONDITIONALLY_SOLID,
|
||||
COLLISION_GROUP_DEFAULT,
|
||||
COLLISION_GROUP_DEBRIS, // Collides with nothing but world and static stuff
|
||||
COLLISION_GROUP_INTERACTIVE_DEBRIS, // Collides with everything except other interactive debris or debris
|
||||
COLLISION_GROUP_INTERACTIVE, // Collides with everything except interactive debris or debris
|
||||
COLLISION_GROUP_PLAYER,
|
||||
COLLISION_GROUP_BREAKABLE_GLASS,
|
||||
COLLISION_GROUP_VEHICLE,
|
||||
COLLISION_GROUP_PLAYER_MOVEMENT, // For HL2, same as Collision_Group_Player, for
|
||||
// TF2, this filters out other players and CBaseObjects
|
||||
COLLISION_GROUP_NPC, // Generic NPC group
|
||||
COLLISION_GROUP_IN_VEHICLE, // for any entity inside a vehicle
|
||||
COLLISION_GROUP_WEAPON, // for any weapons that need collision detection
|
||||
COLLISION_GROUP_VEHICLE_CLIP, // vehicle clip brush to restrict vehicle movement
|
||||
COLLISION_GROUP_PROJECTILE, // Projectiles!
|
||||
COLLISION_GROUP_DOOR_BLOCKER, // Blocks entities not permitted to get near moving doors
|
||||
COLLISION_GROUP_PASSABLE_DOOR, // Doors that the player shouldn't collide with
|
||||
COLLISION_GROUP_DISSOLVING, // Things that are dissolving are in this group
|
||||
COLLISION_GROUP_PUSHAWAY, // Nonsolid on client and server, pushaway in player code
|
||||
COLLISION_GROUP_PLAYER_MOVEMENT, // For HL2, same as Collision_Group_Player, for
|
||||
|
||||
COLLISION_GROUP_NPC_ACTOR, // Used so NPCs in scripts ignore the player.
|
||||
COLLISION_GROUP_NPC_SCRIPTED, // USed for NPCs in scripts that should not collide with each other
|
||||
// TF2, this filters out other players and CBaseObjects
|
||||
COLLISION_GROUP_NPC, // Generic NPC group
|
||||
COLLISION_GROUP_IN_VEHICLE, // for any entity inside a vehicle
|
||||
COLLISION_GROUP_WEAPON, // for any weapons that need collision detection
|
||||
COLLISION_GROUP_VEHICLE_CLIP, // vehicle clip brush to restrict vehicle movement
|
||||
COLLISION_GROUP_PROJECTILE, // Projectiles!
|
||||
COLLISION_GROUP_DOOR_BLOCKER, // Blocks entities not permitted to get near moving doors
|
||||
COLLISION_GROUP_PASSABLE_DOOR, // Doors that the player shouldn't collide with
|
||||
COLLISION_GROUP_DISSOLVING, // Things that are dissolving are in this group
|
||||
COLLISION_GROUP_PUSHAWAY, // Nonsolid on client and server, pushaway in player code
|
||||
|
||||
COLLISION_GROUP_NPC_ACTOR, // Used so NPCs in scripts ignore the player.
|
||||
COLLISION_GROUP_NPC_SCRIPTED, // USed for NPCs in scripts that should not collide with each other
|
||||
COLLISION_GROUP_PZ_CLIP,
|
||||
|
||||
|
||||
|
||||
COLLISION_GROUP_DEBRIS_BLOCK_PROJECTILE, // Only collides with bullets
|
||||
COLLISION_GROUP_PROPS,
|
||||
|
||||
LAST_SHARED_COLLISION_GROUP
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,16 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
|
||||
public class GameEvent : NativeObject
|
||||
{
|
||||
// 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);
|
||||
@@ -121,8 +125,28 @@ namespace CounterStrikeSharp.API.Modules.Events
|
||||
|
||||
protected void SetEntityIndex(string name, int value) => NativeAPI.SetEventEntityIndex(Handle, name, value);
|
||||
|
||||
public void FireEvent(bool dontBroadcast) => NativeAPI.FireEvent(Handle, dontBroadcast);
|
||||
|
||||
public void FireEvent(bool dontBroadcast)
|
||||
{
|
||||
NativeAPI.FireEvent(Handle, dontBroadcast);
|
||||
_freeable = false;
|
||||
}
|
||||
|
||||
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>
|
||||
/// </summary>
|
||||
public void Free()
|
||||
{
|
||||
if (!_freeable)
|
||||
{
|
||||
throw new InvalidOperationException("Event is not able to be freed.");
|
||||
}
|
||||
|
||||
NativeAPI.FreeEvent(Handle);
|
||||
|
||||
_freeable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class DynamicHook : NativeObject
|
||||
return NativeAPI.DynamicHookGetParam<T>(Handle, (int)typeof(T).ToValidDataType(), index);
|
||||
}
|
||||
|
||||
public T GetReturn<T>(int index)
|
||||
public T GetReturn<T>()
|
||||
{
|
||||
return NativeAPI.DynamicHookGetReturn<T>(Handle, (int)typeof(T).ToValidDataType());
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ public static class VirtualFunctions
|
||||
|
||||
public static Action<IntPtr, string> SetModel = SetModelFunc.Invoke;
|
||||
|
||||
public static MemoryFunctionVoid<nint, RoundEndReason, float> TerminateRoundFunc =
|
||||
public static MemoryFunctionVoid<nint, RoundEndReason, float, nint, byte> TerminateRoundFunc =
|
||||
new(GameData.GetSignature("CCSGameRules_TerminateRound"));
|
||||
|
||||
public static Action<IntPtr, RoundEndReason, float> TerminateRound = TerminateRoundFunc.Invoke;
|
||||
public static Action<IntPtr, RoundEndReason, float, nint, byte> TerminateRound = TerminateRoundFunc.Invoke;
|
||||
|
||||
public static MemoryFunctionWithReturn<string, int, IntPtr> UTIL_CreateEntityByNameFunc =
|
||||
new(GameData.GetSignature("UTIL_CreateEntityByName"));
|
||||
|
||||
@@ -19,6 +19,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
@@ -27,19 +28,77 @@ namespace CounterStrikeSharp.API
|
||||
{
|
||||
public class Server
|
||||
{
|
||||
public static float TickInterval => NativeAPI.GetTickInterval();
|
||||
/// <summary>
|
||||
/// Duration of a single game tick in seconds, based on a 64 tick server (hard coded in CS2).
|
||||
/// </summary>
|
||||
public static float TickInterval => 0.015625f;
|
||||
|
||||
/// <summary>
|
||||
/// Executes a command on the server, as if it was entered from the console.
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
public static void ExecuteCommand(string command) => NativeAPI.IssueServerCommand(command);
|
||||
|
||||
public static string MapName => NativeAPI.GetMapName();
|
||||
// public static void PrintToConsole(string message) => NativeAPI.PrintToConsole(message);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total time the server has been running in seconds.
|
||||
/// </summary>
|
||||
/// <remarks>Does not increment when server is hibernating</remarks>
|
||||
public static double TickedTime => NativeAPI.GetTickedTime();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current map time in seconds, as an interval of the server's tick interval.
|
||||
/// e.g. 70.046875 would represent 70 seconds of map time and the 4483rd tick of the server (70.046875 / 0.015625).
|
||||
/// </summary>
|
||||
/// <remarks>Increments even when server is hibernating</remarks>
|
||||
public static float CurrentTime => NativeAPI.GetCurrentTime();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current map tick count.
|
||||
/// CS2 is a 64 tick server, so the value will increment by 64 every second.
|
||||
/// </summary>
|
||||
public static int TickCount => NativeAPI.GetTickCount();
|
||||
public static float GameFrameTime => NativeAPI.GetGameFrameTime();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total time the server has been running in seconds.
|
||||
/// </summary>
|
||||
/// <remarks>Increments even when server is hibernating</remarks>
|
||||
public static double EngineTime => NativeAPI.GetEngineTime();
|
||||
|
||||
public static void PrecacheModel(string name) => NativeAPI.PrecacheModel(name);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="RunOnTick"/>
|
||||
/// Returns Task that completes once the synchronous task has been completed.
|
||||
/// </summary>
|
||||
public static Task RunOnTickAsync(int tick, Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task, FunctionLifetime.SingleUse);
|
||||
NativeAPI.QueueTaskForFrame(tick, functionReference);
|
||||
return functionReference.CompletionTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a task to be executed on the specified tick.
|
||||
/// See <see cref="TickCount"/> to retrieve the current tick.
|
||||
/// <remarks>Does not execute if the server is hibernating.</remarks>
|
||||
/// </summary>
|
||||
public static void RunOnTick(int tick, Action task)
|
||||
{
|
||||
RunOnTickAsync(tick, task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="NextFrame"/>
|
||||
/// Returns Task that completes once the synchronous task has been completed.
|
||||
/// </summary>
|
||||
public static Task NextFrameAsync(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task, FunctionLifetime.SingleUse);
|
||||
NativeAPI.QueueTaskForNextFrame(functionReference);
|
||||
return functionReference.CompletionTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a task to be executed on the next game frame.
|
||||
@@ -47,9 +106,18 @@ namespace CounterStrikeSharp.API
|
||||
/// </summary>
|
||||
public static void NextFrame(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task);
|
||||
functionReference.Lifetime = FunctionLifetime.SingleUse;
|
||||
NativeAPI.QueueTaskForNextFrame(functionReference);
|
||||
NextFrameAsync(task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="NextWorldUpdate"/>
|
||||
/// Returns Task that completes once the synchronous task has been completed.
|
||||
/// </summary>
|
||||
public static Task NextWorldUpdateAsync(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task, FunctionLifetime.SingleUse);
|
||||
NativeAPI.QueueTaskForNextWorldUpdate(functionReference);
|
||||
return functionReference.CompletionTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,9 +127,7 @@ namespace CounterStrikeSharp.API
|
||||
/// <param name="task"></param>
|
||||
public static void NextWorldUpdate(Action task)
|
||||
{
|
||||
var functionReference = FunctionReference.Create(task);
|
||||
functionReference.Lifetime = FunctionLifetime.SingleUse;
|
||||
NativeAPI.QueueTaskForNextWorldUpdate(functionReference);
|
||||
NextWorldUpdateAsync(task);
|
||||
}
|
||||
|
||||
public static void PrintToChatAll(string message)
|
||||
|
||||
@@ -51,6 +51,7 @@ bool CCoreConfig::Init(char* conf_error, int conf_error_size)
|
||||
SilentChatTrigger = m_json.value("SilentChatTrigger", SilentChatTrigger);
|
||||
FollowCS2ServerGuidelines = m_json.value("FollowCS2ServerGuidelines", FollowCS2ServerGuidelines);
|
||||
PluginHotReloadEnabled = m_json.value("PluginHotReloadEnabled", PluginHotReloadEnabled);
|
||||
PluginAutoLoadEnabled = m_json.value("PluginAutoLoadEnabled", PluginAutoLoadEnabled);
|
||||
ServerLanguage = m_json.value("ServerLanguage", ServerLanguage);
|
||||
} catch (const std::exception& ex) {
|
||||
V_snprintf(conf_error, conf_error_size, "Failed to parse CoreConfig file: %s", ex.what());
|
||||
|
||||
@@ -29,6 +29,7 @@ class CCoreConfig
|
||||
std::vector<std::string> SilentChatTrigger = { std::string("/") };
|
||||
bool FollowCS2ServerGuidelines = true;
|
||||
bool PluginHotReloadEnabled = true;
|
||||
bool PluginAutoLoadEnabled = true;
|
||||
std::string ServerLanguage = "en";
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "mm_plugin.h"
|
||||
#include "core/globals.h"
|
||||
#include "core/managers/player_manager.h"
|
||||
#include "core/tick_scheduler.h"
|
||||
#include "iserver.h"
|
||||
#include "managers/event_manager.h"
|
||||
#include "scripting/callback_manager.h"
|
||||
@@ -79,6 +80,7 @@ EntityManager entityManager;
|
||||
ChatManager chatManager;
|
||||
ServerManager serverManager;
|
||||
VoiceManager voiceManager;
|
||||
TickScheduler tickScheduler;
|
||||
|
||||
bool gameLoopInitialized = false;
|
||||
GetLegacyGameEventListener_t* GetLegacyGameEventListener = nullptr;
|
||||
|
||||
@@ -47,6 +47,7 @@ class CallbackManager;
|
||||
class ConVarManager;
|
||||
class PlayerManager;
|
||||
class MenuManager;
|
||||
class TickScheduler;
|
||||
class TimerSystem;
|
||||
class ChatCommands;
|
||||
class HookManager;
|
||||
@@ -100,6 +101,7 @@ extern ChatCommands chatCommands;
|
||||
extern ChatManager chatManager;
|
||||
extern ServerManager serverManager;
|
||||
extern VoiceManager voiceManager;
|
||||
extern TickScheduler tickScheduler;
|
||||
|
||||
extern HookManager hookManager;
|
||||
extern SourceHook::ISourceHook *source_hook;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "scripting/callback_manager.h"
|
||||
|
||||
#include "core/game_system.h"
|
||||
#include <concurrentqueue.h>
|
||||
|
||||
SH_DECL_HOOK1_void(ISource2Server, ServerHibernationUpdate, SH_NOATTRIB, 0, bool);
|
||||
SH_DECL_HOOK0_void(ISource2Server, GameServerSteamAPIActivated, SH_NOATTRIB, 0);
|
||||
@@ -176,17 +177,17 @@ void ServerManager::UpdateWhenNotInGame(float flFrameTime)
|
||||
|
||||
void ServerManager::PreWorldUpdate(bool bSimulating)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_nextWorldUpdateTasksLock);
|
||||
std::vector<std::function<void()>> out_list(1024);
|
||||
|
||||
if (!m_nextWorldUpdateTasks.empty()) {
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} at time {1}", m_nextWorldUpdateTasks.size(),
|
||||
globals::getGlobalVars()->curtime);
|
||||
auto size = m_nextWorldUpdateTasks.try_dequeue_bulk(out_list.begin(), 1024);
|
||||
|
||||
for (size_t i = 0; i < m_nextWorldUpdateTasks.size(); i++) {
|
||||
m_nextWorldUpdateTasks[i]();
|
||||
if (size > 0) {
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} at time {1}", size,
|
||||
globals::getGlobalVars()->curtime);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
out_list[i]();
|
||||
}
|
||||
|
||||
m_nextWorldUpdateTasks.clear();
|
||||
}
|
||||
|
||||
auto callback = globals::serverManager.on_server_pre_world_update;
|
||||
@@ -200,8 +201,7 @@ void ServerManager::PreWorldUpdate(bool bSimulating)
|
||||
|
||||
void ServerManager::AddTaskForNextWorldUpdate(std::function<void()>&& task)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_nextWorldUpdateTasksLock);
|
||||
m_nextWorldUpdateTasks.push_back(std::forward<decltype(task)>(task));
|
||||
m_nextWorldUpdateTasks.enqueue(std::forward<decltype(task)>(task));
|
||||
}
|
||||
|
||||
void ServerManager::OnPrecacheResources(IEntityResourceManifest* pResourceManifest)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "core/globals.h"
|
||||
#include "core/global_listener.h"
|
||||
#include "scripting/script_engine.h"
|
||||
#include <concurrentqueue.h>
|
||||
|
||||
#include "core/game_system.h"
|
||||
|
||||
@@ -56,8 +57,7 @@ private:
|
||||
|
||||
ScriptCallback *on_server_precache_resources;
|
||||
|
||||
std::vector<std::function<void()>> m_nextWorldUpdateTasks;
|
||||
std::mutex m_nextWorldUpdateTasksLock;
|
||||
moodycamel::ConcurrentQueue<std::function<void()>> m_nextWorldUpdateTasks;
|
||||
};
|
||||
|
||||
} // namespace counterstrikesharp
|
||||
45
src/core/tick_scheduler.cpp
Normal file
45
src/core/tick_scheduler.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
#include "tick_scheduler.h"
|
||||
|
||||
namespace counterstrikesharp {
|
||||
|
||||
void TickScheduler::schedule(int tick, std::function<void()> callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(taskMutex);
|
||||
scheduledTasks.push(std::make_pair(tick, callback));
|
||||
}
|
||||
|
||||
std::vector<std::function<void()>> TickScheduler::getCallbacks(int currentTick)
|
||||
{
|
||||
std::vector<std::function<void()>> callbacksToRun;
|
||||
|
||||
std::lock_guard<std::mutex> lock(taskMutex);
|
||||
|
||||
if (scheduledTasks.empty()) {
|
||||
return callbacksToRun;
|
||||
}
|
||||
|
||||
// Process tasks due for the current tick
|
||||
while (!scheduledTasks.empty() && scheduledTasks.top().first <= currentTick) {
|
||||
callbacksToRun.push_back(scheduledTasks.top().second);
|
||||
scheduledTasks.pop();
|
||||
}
|
||||
|
||||
return callbacksToRun;
|
||||
}
|
||||
} // namespace counterstrikesharp
|
||||
44
src/core/tick_scheduler.h
Normal file
44
src/core/tick_scheduler.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace counterstrikesharp {
|
||||
class TickScheduler
|
||||
{
|
||||
public:
|
||||
struct TaskComparator
|
||||
{
|
||||
bool operator()(const std::pair<int, std::function<void()>>& a,
|
||||
const std::pair<int, std::function<void()>>& b) const
|
||||
{
|
||||
return a.first > b.first;
|
||||
}
|
||||
};
|
||||
|
||||
void schedule(int tick, std::function<void()> callback);
|
||||
std::vector<std::function<void()>> getCallbacks(int currentTick);
|
||||
private:
|
||||
std::priority_queue<std::pair<int, std::function<void()>>,
|
||||
std::vector<std::pair<int, std::function<void()>>>,
|
||||
TaskComparator>
|
||||
scheduledTasks;
|
||||
std::mutex taskMutex;
|
||||
};
|
||||
} // namespace counterstrikesharp
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "core/gameconfig.h"
|
||||
#include "core/game_system.h"
|
||||
#include "core/timer_system.h"
|
||||
#include "core/tick_scheduler.h"
|
||||
#include "core/utils.h"
|
||||
#include "core/managers/entity_manager.h"
|
||||
#include "igameeventsystem.h"
|
||||
@@ -48,6 +49,7 @@ DLL_EXPORT void InvokeNative(counterstrikesharp::fxNativeContext& context)
|
||||
|
||||
if (context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_NEXT_FRAME") &&
|
||||
context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_NEXT_WORLD_UPDATE") &&
|
||||
context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_FRAME") &&
|
||||
counterstrikesharp::globals::gameThreadId != std::this_thread::get_id())
|
||||
{
|
||||
counterstrikesharp::ScriptContextRaw scriptContext(context);
|
||||
@@ -193,9 +195,7 @@ void CounterStrikeSharpMMPlugin::AllPluginsLoaded()
|
||||
|
||||
void CounterStrikeSharpMMPlugin::AddTaskForNextFrame(std::function<void()>&& task)
|
||||
{
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_nextTasksLock);
|
||||
m_nextTasks.push_back(std::forward<decltype(task)>(task));
|
||||
m_nextTasks.try_enqueue(std::forward<decltype(task)>(task));
|
||||
}
|
||||
|
||||
void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick)
|
||||
@@ -208,19 +208,28 @@ void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick
|
||||
*/
|
||||
globals::timerSystem.OnGameFrame(simulating);
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_nextTasksLock);
|
||||
std::vector<std::function<void()>> out_list(1024);
|
||||
|
||||
if (m_nextTasks.empty())
|
||||
return;
|
||||
auto size = m_nextTasks.try_dequeue_bulk(out_list.begin(), 1024);
|
||||
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} on tick number {1}", m_nextTasks.size(),
|
||||
globals::getGlobalVars()->tickcount);
|
||||
if (size > 0) {
|
||||
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} on tick number {1}", size,
|
||||
globals::getGlobalVars()->tickcount);
|
||||
|
||||
for (size_t i = 0; i < m_nextTasks.size(); i++) {
|
||||
m_nextTasks[i]();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
out_list[i]();
|
||||
}
|
||||
}
|
||||
|
||||
m_nextTasks.clear();
|
||||
auto callbacks = globals::tickScheduler.getCallbacks(globals::getGlobalVars()->tickcount);
|
||||
if (callbacks.size() > 0) {
|
||||
CSSHARP_CORE_TRACE("Executing frame specific tasks of size: {0} on tick number {1}", callbacks.size(),
|
||||
globals::getGlobalVars()->tickcount);
|
||||
|
||||
for (auto& callback : callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Potentially might not work
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <sh_vector.h>
|
||||
#include <vector>
|
||||
#include "entitysystem.h"
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
namespace counterstrikesharp {
|
||||
class ScriptCallback;
|
||||
@@ -63,8 +64,7 @@ public:
|
||||
const char *GetLogTag() override;
|
||||
|
||||
private:
|
||||
std::vector<std::function<void()>> m_nextTasks;
|
||||
std::mutex m_nextTasksLock;
|
||||
moodycamel::ConcurrentQueue<std::function<void()>> m_nextTasks;
|
||||
};
|
||||
|
||||
static ScriptCallback *on_activate_callback;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "core/function.h"
|
||||
#include "core/managers/player_manager.h"
|
||||
#include "core/managers/server_manager.h"
|
||||
#include "core/tick_scheduler.h"
|
||||
// clang-format on
|
||||
|
||||
#if _WIN32
|
||||
@@ -238,6 +239,15 @@ void QueueTaskForNextWorldUpdate(ScriptContext& script_context)
|
||||
globals::serverManager.AddTaskForNextWorldUpdate([func]() { reinterpret_cast<voidfunc*>(func)(); });
|
||||
}
|
||||
|
||||
void QueueTaskForFrame(ScriptContext& script_context)
|
||||
{
|
||||
auto tick = script_context.GetArgument<int>(0);
|
||||
auto func = script_context.GetArgument<void*>(1);
|
||||
|
||||
typedef void(voidfunc)(void);
|
||||
globals::tickScheduler.schedule(tick, reinterpret_cast<voidfunc*>(func));
|
||||
}
|
||||
|
||||
enum InterfaceType
|
||||
{
|
||||
Engine,
|
||||
@@ -331,6 +341,7 @@ REGISTER_NATIVES(engine, {
|
||||
ScriptEngine::RegisterNativeHandler("GET_TICKED_TIME", GetTickedTime);
|
||||
ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_NEXT_FRAME", QueueTaskForNextFrame);
|
||||
ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_NEXT_WORLD_UPDATE", QueueTaskForNextWorldUpdate);
|
||||
ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_FRAME", QueueTaskForFrame);
|
||||
ScriptEngine::RegisterNativeHandler("GET_VALVE_INTERFACE", GetValveInterface);
|
||||
ScriptEngine::RegisterNativeHandler("GET_COMMAND_PARAM_VALUE", GetCommandParamValue);
|
||||
ScriptEngine::RegisterNativeHandler("PRINT_TO_SERVER_CONSOLE", PrintToServerConsole);
|
||||
|
||||
@@ -4,7 +4,6 @@ IS_MAP_VALID: mapname:string -> bool
|
||||
GET_TICK_INTERVAL: -> float
|
||||
GET_CURRENT_TIME: -> float
|
||||
GET_TICK_COUNT: -> int
|
||||
GET_GAME_FRAME_TIME: -> float
|
||||
GET_ENGINE_TIME: -> double
|
||||
GET_MAX_CLIENTS: -> int
|
||||
ISSUE_SERVER_COMMAND: command:string -> void
|
||||
@@ -22,6 +21,7 @@ TRACE_FILTER_PROXY_SET_SHOULD_HIT_ENTITY_CALLBACK: trace_filter:pointer, callbac
|
||||
NEW_TRACE_RESULT: -> pointer
|
||||
GET_TICKED_TIME: -> double
|
||||
QUEUE_TASK_FOR_NEXT_FRAME: callback:func -> void
|
||||
QUEUE_TASK_FOR_FRAME: tick:int, callback:func -> void
|
||||
QUEUE_TASK_FOR_NEXT_WORLD_UPDATE: callback:func -> void
|
||||
GET_VALVE_INTERFACE: interfaceType:int, interfaceName:string -> pointer
|
||||
GET_COMMAND_PARAM_VALUE: param:string, dataType:DataType_t, defaultValue:any -> any
|
||||
|
||||
@@ -63,7 +63,7 @@ static void FireEvent(ScriptContext &script_context) {
|
||||
}
|
||||
|
||||
|
||||
static void FireEventToClient(ScriptContext& script_context) {
|
||||
static void FireEventToClient(ScriptContext& script_context) {
|
||||
auto game_event = script_context.GetArgument<IGameEvent*>(0);
|
||||
int entityIndex = script_context.GetArgument<int>(1);
|
||||
if (!game_event) {
|
||||
@@ -76,7 +76,17 @@ static void FireEvent(ScriptContext &script_context) {
|
||||
}
|
||||
|
||||
pListener->FireGameEvent(game_event);
|
||||
}
|
||||
}
|
||||
|
||||
static void FreeEvent(ScriptContext& script_context) {
|
||||
auto game_event = script_context.GetArgument<IGameEvent*>(0);
|
||||
if (!game_event) {
|
||||
script_context.ThrowNativeError("Invalid game event");
|
||||
}
|
||||
|
||||
globals::gameEventManager->FreeEvent(game_event);
|
||||
managed_game_events.erase(std::remove(managed_game_events.begin(), managed_game_events.end(), game_event), managed_game_events.end());
|
||||
}
|
||||
|
||||
static const char *GetEventName(ScriptContext &script_context) {
|
||||
IGameEvent *game_event = script_context.GetArgument<IGameEvent *>(0);
|
||||
@@ -263,6 +273,7 @@ REGISTER_NATIVES(events, {
|
||||
ScriptEngine::RegisterNativeHandler("HOOK_EVENT", HookEvent);
|
||||
ScriptEngine::RegisterNativeHandler("UNHOOK_EVENT", UnhookEvent);
|
||||
ScriptEngine::RegisterNativeHandler("CREATE_EVENT", CreateEvent);
|
||||
ScriptEngine::RegisterNativeHandler("FREE_EVENT", FreeEvent);
|
||||
ScriptEngine::RegisterNativeHandler("FIRE_EVENT", FireEvent);
|
||||
ScriptEngine::RegisterNativeHandler("FIRE_EVENT_TO_CLIENT", FireEventToClient);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
HOOK_EVENT: name:string, callback:func, isPost:bool -> void
|
||||
UNHOOK_EVENT: name:string, callback:func, isPost:bool -> void
|
||||
CREATE_EVENT: name:string, force:bool -> pointer
|
||||
FREE_EVENT: gameEvent:pointer -> void
|
||||
FIRE_EVENT: gameEvent:pointer, dontBroadcast:bool -> void
|
||||
FIRE_EVENT_TO_CLIENT: gameEvent:pointer, clientIndex:int -> void
|
||||
GET_EVENT_NAME: gameEvent:pointer -> string
|
||||
|
||||
Reference in New Issue
Block a user