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