diff --git a/managed/CounterStrikeSharp.API/Core/BasePlugin.cs b/managed/CounterStrikeSharp.API/Core/BasePlugin.cs
index 6ee542682..c90193071 100644
--- a/managed/CounterStrikeSharp.API/Core/BasePlugin.cs
+++ b/managed/CounterStrikeSharp.API/Core/BasePlugin.cs
@@ -370,6 +370,21 @@ public Timer AddTimer(float interval, Action callback, TimerFlags? flags = null)
return timer;
}
+ ///
+ /// Adds a timer that will call the given callback after the specified amount of ticks.
+ /// By default will only run once unless the flag is set.
+ ///
+ /// Interval/Delay in ticks
+ /// Code to run when timer elapses
+ /// Controls if the timer is a one-off, repeat or stops on map change etc.
+ /// An instance of the
+ public Timer AddTickTimer(int tickInterval, Action callback, TimerFlags? flags = null)
+ {
+ var timer = new Timer(tickInterval * Server.TickInterval, callback, flags ?? 0);
+ Timers.Add(timer);
+ return timer;
+ }
+
///
/// Registers all attribute handlers on the given instance.
/// Can be used to register event handlers, console commands, entity outputs etc. from classes that are not derived from `BasePlugin`.
diff --git a/managed/CounterStrikeSharp.Tests.Native/SchemaTests.cs b/managed/CounterStrikeSharp.Tests.Native/SchemaTests.cs
index 6cf5853f9..dc55a759d 100644
--- a/managed/CounterStrikeSharp.Tests.Native/SchemaTests.cs
+++ b/managed/CounterStrikeSharp.Tests.Native/SchemaTests.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
@@ -14,7 +15,19 @@ public void GetSchemaOffset_ReturnsValidOffset()
{
var offset = NativeAPI.GetSchemaOffset("CBaseEntity", "m_iHealth");
- Assert.True(offset > 0, $"Schema offset for m_iHealth should be positive, got {offset}");
+ Assert.Equal(1464, offset); // Hardcode for now, this may change but I want to know if it changes
+ }
+
+ [Fact]
+ public async Task GetSchemaOffset_CanRunOnAnotherThread()
+ {
+ await Task.Run(async () =>
+ {
+ await Task.Yield();
+ Assert.NotEqual(Thread.CurrentThread.ManagedThreadId, NativeTestsPlugin.gameThreadId);
+ var offset = NativeAPI.GetSchemaOffset("CBaseEntity", "m_iHealth");
+ Assert.True(offset > 0);
+ });
}
[Fact]
diff --git a/managed/CounterStrikeSharp.Tests.Native/TimerTests.cs b/managed/CounterStrikeSharp.Tests.Native/TimerTests.cs
index ca6163621..f20c1ad9e 100644
--- a/managed/CounterStrikeSharp.Tests.Native/TimerTests.cs
+++ b/managed/CounterStrikeSharp.Tests.Native/TimerTests.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.Threading.Tasks;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
@@ -60,6 +61,31 @@ public async Task CreateTimer_RepeatsWithFlag()
}
}
+ [Fact]
+ public async Task CreateTimer_TickBased()
+ {
+ var startTick = Server.TickCount;
+ var tickCounts = new List();
+ int timesTicked = 0;
+
+ // Run every 4 ticks
+ var timer = new Timer(4 * Server.TickInterval, () =>
+ {
+ timesTicked++;
+ tickCounts.Add(Server.TickCount);
+ }, TimerFlags.REPEAT);
+
+ await Server.RunOnTickAsync(startTick + 16, () => { });
+ timer.Kill();
+
+ Assert.Equal(4, timesTicked);
+ for (int i = 0; i < tickCounts.Count; i++)
+ {
+ var expectedTick = startTick + (i + 1) * 4;
+ Assert.Equal(expectedTick, tickCounts[i]);
+ }
+ }
+
[Fact]
public async Task KillTimer_StopsExecution()
{
diff --git a/src/core/timer_system.cpp b/src/core/timer_system.cpp
index 9c7dba017..8e5b44798 100644
--- a/src/core/timer_system.cpp
+++ b/src/core/timer_system.cpp
@@ -119,13 +119,7 @@ void TimerSystem::OnGameFrame(bool simulating)
m_last_ticked_time = globals::getGlobalVars()->curtime;
m_has_map_ticked = true;
- // Handle timer tick
- if (timers::universal_time >= timers::timer_next_think)
- {
- RunFrame();
-
- timers::timer_next_think = CalculateNextThink(timers::timer_next_think, 0.1f);
- }
+ RunFrame();
if (m_on_tick_callback_->GetFunctionCount())
{
@@ -138,14 +132,11 @@ void TimerSystem::OnGameFrame(bool simulating)
double TimerSystem::CalculateNextThink(double last_think_time, float interval)
{
- if (timers::universal_time - last_think_time - interval <= 0.1)
+ if (timers::universal_time - last_think_time - interval <= globals::engine_fixed_tick_interval)
{
return last_think_time + interval;
}
- else
- {
- return timers::universal_time + interval;
- }
+ return timers::universal_time + interval;
}
void TimerSystem::RunFrame()
diff --git a/src/mm_plugin.cpp b/src/mm_plugin.cpp
index 04940f8e4..b1005159c 100644
--- a/src/mm_plugin.cpp
+++ b/src/mm_plugin.cpp
@@ -55,6 +55,7 @@ DLL_EXPORT void InvokeNative(counterstrikesharp::fxNativeContext& context)
if (context.nativeIdentifier == 0) return;
if (context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_FRAME") &&
+ context.nativeIdentifier != counterstrikesharp::hash_string_const("GET_SCHEMA_OFFSET") &&
counterstrikesharp::globals::gameThreadId != std::this_thread::get_id())
{
counterstrikesharp::ScriptContextRaw scriptContext(context);