-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPlugin.h
More file actions
502 lines (480 loc) · 22.3 KB
/
Copy pathPlugin.h
File metadata and controls
502 lines (480 loc) · 22.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#pragma once
#include <Global.h>
// --- STRUCTS ---
// Structure to hold all actor states
struct ActorStateData
{
std::uint32_t lifeState; // LIFE_STATE (0-8)
std::uint32_t weaponState; // WEAPON_STATE (0-5)
std::uint32_t gunState; // GUN_STATE (0-8)
std::uint32_t interactingState; // INTERACTING_STATE (0-3)
};
// ========================================================================================
// ACTOR STATE CHECKING FUNCTIONS
// ========================================================================================
//
// LIFE_STATE enum values (from Actor.h):
// - kAlive (0) : Actor is alive and functioning normally
// - kDying (1) : Actor is in the process of dying (death animation playing)
// - kDead (2) : Actor is fully dead
// - kUnconscious (3) : Actor is knocked out/unconscious
// - kReanimate (4) : Actor has been reanimated (necromancy)
// - kRecycle (5) : Actor is being recycled by the engine
// - kRestrained (6) : Actor is restrained/captured
// - kEssentialDown (7) : Essential actor is downed (protected, can't die)
// - kBleedout (8) : Actor is in bleedout state (dying but can be saved)
//
// GUN_STATE enum values (from Actor.h):
// - kDrawn (0) : Weapon is drawn and ready
// - kRelaxed (1) : Weapon drawn but in relaxed pose
// - kBlocked (2) : Weapon blocked/unavailable
// - kAlert (3) : Weapon drawn and on alert
// - kReloading (4) : Currently reloading weapon
// - kThrowing (5) : Throwing grenade/throwable
// - kSighted (6) : Aiming down sights (iron sights/scope)
// - kFire (7) : Currently firing weapon
// - kFireSighted (8) : Firing while aiming down sights
//
// WEAPON_STATE enum values (from Actor.h):
// - kSheathed (0) : Weapon is put away
// - kWantToDraw (1) : Beginning to draw weapon
// - kDrawing (2) : Animation of drawing weapon
// - kDrawn (3) : Weapon is fully drawn
// - kWantToSheathe (4) : Beginning to sheathe weapon
// - kSheathing (5) : Animation of sheathing weapon
//
// INTERACTING_STATE enum values (from Actor.h):
// - kNotInteracting (0) : Not interacting with any object
// - kWaitingToInteract (1) : Queued to interact with object
// - kInteracting (2) : Currently interacting with object
// - kWaitingToStopInteracting (3): Finishing interaction
//
// ========================================================================================
namespace ACTOR_STATE
{
// Life States
constexpr std::uint32_t ALIVE = 0;
constexpr std::uint32_t DYING = 1;
constexpr std::uint32_t DEAD = 2;
constexpr std::uint32_t UNCONSCIOUS = 3;
constexpr std::uint32_t REANIMATE = 4;
constexpr std::uint32_t RECYCLE = 5;
constexpr std::uint32_t RESTRAINED = 6;
constexpr std::uint32_t ESSENTIAL_DOWN = 7;
constexpr std::uint32_t BLEEDOUT = 8;
// Weapon States
constexpr std::uint32_t WEAPON_SHEATHED = 0;
constexpr std::uint32_t WEAPON_WANT_TO_DRAW = 1;
constexpr std::uint32_t WEAPON_DRAWING = 2;
constexpr std::uint32_t WEAPON_DRAWN = 3;
constexpr std::uint32_t WEAPON_WANT_TO_SHEATHE= 4;
constexpr std::uint32_t WEAPON_SHEATHING = 5;
// Gun States
constexpr std::uint32_t GUN_DRAWN = 0;
constexpr std::uint32_t GUN_RELAXED = 1;
constexpr std::uint32_t GUN_BLOCKED = 2;
constexpr std::uint32_t GUN_ALERT = 3;
constexpr std::uint32_t GUN_RELOADING = 4;
constexpr std::uint32_t GUN_THROWING = 5;
constexpr std::uint32_t GUN_SIGHTED = 6;
constexpr std::uint32_t GUN_FIRE = 7;
constexpr std::uint32_t GUN_FIRE_SIGHTED = 8;
// Interacting States
constexpr std::uint32_t NOT_INTERACTING = 0;
constexpr std::uint32_t WAITING_TO_INTERACT = 1;
constexpr std::uint32_t INTERACTING = 2;
constexpr std::uint32_t WAITING_TO_STOP_INTERACT = 3;
// Filter wildcard (any state)
constexpr std::uint32_t ANY = 0xFF;
}
// Enemy Tier Classification System
enum class ENEMY_TIER : std::uint8_t
{
LOW = 0, // Basic enemies (low health, simple behavior)
MEDIUM = 1, // Standard enemies (moderate threat)
HIGH = 2 // Dangerous enemies (bosses, legendaries, high health)
};
// Enemy combat style flags
struct EnemyAnalysis
{
ENEMY_TIER tier;
float healthPercentOfMax; // Relative to highest enemy in cell
bool isRanged;
bool isMelee;
bool hasGrenades;
bool isUnique;
bool isAlerted;
bool isLegendary;
};
// Follower ENUMs
enum FOLLOWER_STATE : std::uint8_t
{
FOLLOWER_STATE_FOLLOW = 1, // iFollower_Com_Follow (1.0)
FOLLOWER_STATE_WAIT = 2, // iFollower_Com_Wait (2.0)
FOLLOWER_STATE_GO_HOME= 4 // iFollower_Com_GoHome (4.0)
};
enum FOLLOWER_DISTANCE : std::uint8_t
{
FOLLOWER_DISTANCE_NEAR = 0, // iFollower_Dist_Near (0.0)
FOLLOWER_DISTANCE_MEDIUM = 1, // iFollower_Dist_Medium (1.0)
FOLLOWER_DISTANCE_FAR = 2 // iFollower_Dist_Far (2.0)
};
enum FOLLOWER_STANCE : std::uint8_t
{
FOLLOWER_STANCE_DEFENSIVE = 0,
FOLLOWER_STANCE_AGGRESSIVE= 1
};
// Actor tracking data structure
struct TrackedActorData
{
RE::Actor* actor; // Pointer to the actor
ENEMY_TIER tier; // Threat level (for enemies)
float distanceToPlayer; // Distance in units
RE::NiPoint3 position; // Current position
std::uint32_t lifeState; // Current LIFE_STATE
std::uint32_t weaponState; // Current WEAPON_STATE
std::uint32_t gunState; // Current GUN_STATE
std::uint32_t interactingState; // Current INTERACTING_STATE
float healthPercent; // Current health / max health
float maxHealth; // Maximum health pool
bool isAlerted; // In combat/alert state
bool isRanged; // Uses ranged weapons
bool isMelee; // Uses melee weapons
bool hasGrenades; // Can throw grenades
bool isUnique; // Unique NPC flag
bool isLegendary; // Legendary enemy flag
bool usesStimpak; // true = stimpak, false = repair kit
std::chrono::steady_clock::time_point lastUpdate; // Last scan time
float velocity; // Current velocity
int stuckCounter; // Consecutive stuck updates
bool lost; // Whether actor is lost
bool overshoot; // Whether actor is overshooting target
// 0 follow, 1 wait, 2 go home
int followerState; // Follower state actor value
// 0 near, 1 medium, 2 far
int followerDistance; // Follower distance actor value
// 0 defensive, 1 aggressive
int followerStance; // Follower stance actor value
};
// Companion Movement task
struct CompanionTask {
RE::Actor* companion;
float timeRemaining;
float convexRadius;
};
// Companion Movement task management
namespace MovementSystem
{
extern std::mutex g_companionTasksMutex;
extern std::vector<CompanionTask> g_companionTasks;
// Add a companion task
void AddCompanionTask(RE::Actor* companion, float duration);
// Process all Companion tasks (called per-frame)
void ProcessCompanionTasks(float deltaTime);
// Remove a specific Companion task
void RemoveCompanionTask(RE::Actor* companion);
// Apply movement settings to a companion
void ApplyStuckMeasures1(RE::Actor* companion);
// Apply movement settings to a companion
void ApplyStuckMeasures2(RE::Actor* companion);
// Remove movement settings from a companion
void RemoveStuckMeasures(RE::Actor* companion);
}
// Slower loop to update companion data
namespace ActorTracking
{
// Faster loop to update companion flags
struct CompanionFlags {
float lastPosX = 0.0f;
float lastPosY = 0.0f;
float lastPosZ = 0.0f;
float velocity = 0.0f;
int stuckCounter = 0;
bool stuck = false;
bool lost = false;
bool overshoot = false;
};
// Fast per-companion flags (mutex only for map structural changes)
extern std::mutex g_companionFlagsMutex;
extern std::unordered_map<RE::Actor*, std::unique_ptr<CompanionFlags>> g_companionFlags;
void EnsureCompanionFlagEntry(RE::Actor* actor);
void SyncCompanionFlagsWithSnapshot(const std::vector<TrackedActorData>& snapshot);
bool GetActorStuckStatusFast(RE::Actor* actor);
void SetActorStuckStatusFast(RE::Actor* actor, bool stuck);
int GetActorStuckCounterFast(RE::Actor* actor);
void IncrementActorStuckCounterFast(RE::Actor* actor);
void SetActorStuckCounterFast(RE::Actor* actor, int counter);
void SetActorVelocityFast(RE::Actor* actor, float vel);
float GetActorVelocityFast(RE::Actor* actor);
void SetActorLastPositionFast(RE::Actor* actor, const RE::NiPoint3& pos);
void GetActorLastPositionFast(RE::Actor* actor, RE::NiPoint3& outPos);
bool GetActorLostStatusFast(RE::Actor* actor);
void SetActorLostStatusFast(RE::Actor* actor, bool lost);
bool GetActorOvershootStatusFast(RE::Actor* actor);
void SetActorOvershootStatusFast(RE::Actor* actor, bool overshoot);
// normal data tracking (mutex for entire data set)
extern std::mutex g_actorDataMutex;
extern std::vector<TrackedActorData> g_enemies;
extern std::vector<TrackedActorData> g_companions;
extern std::vector<TrackedActorData> g_neutralNPCs;
// CACHE: Previous frame data for comparison
extern std::vector<TrackedActorData> g_enemies_prev;
extern std::vector<TrackedActorData> g_companions_prev;
extern std::vector<TrackedActorData> g_neutralNPCs_prev;
// Helper to cache current state before update
inline void CacheCurrentState() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
g_enemies_prev = g_enemies;
g_companions_prev = g_companions;
g_neutralNPCs_prev = g_neutralNPCs;
}
// Replace companion data (thread-safe)
inline void ReplaceCompanionData(const std::vector<TrackedActorData>& newData) {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
g_companions = newData;
}
// Replace enemy data (thread-safe)
inline void ReplaceEnemyData(const std::vector<TrackedActorData>& newData) {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
g_enemies = newData;
}
// Replace neutral NPC data (thread-safe)
inline void ReplaceNeutralNPCData(const std::vector<TrackedActorData>& newData) {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
g_neutralNPCs = newData;
}
// Get all companion data (thread-safe)
inline std::vector<TrackedActorData> GetCompanionData() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
return g_companions; // Returns a copy
}
// Get a COPY of companion data (thread-safe)
inline std::optional<TrackedActorData> GetCompanionData(RE::Actor* actor) {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
for (const auto& data : g_companions) {
if (data.actor == actor) {
return data; // Returns a copy
}
}
return std::nullopt;
}
// Get previous companion data
inline std::optional<TrackedActorData> GetPreviousCompanionData(RE::Actor* actor) {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
for (const auto& data : g_companions_prev) {
if (data.actor == actor) {
return data;
}
}
return std::nullopt;
}
// Helper to get enemy data (thread-safe)
inline std::vector<TrackedActorData> GetEnemyData() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
return g_enemies; // Returns a copy
}
// Helper to get enemy data (thread-safe)
inline std::optional<TrackedActorData> GetEnemyData(RE::Actor* actor) {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
for (const auto& data : g_enemies) {
if (data.actor == actor) {
return data; // Returns a copy
}
}
return std::nullopt;
}
// Helper to get neutral NPC data (thread-safe)
inline std::vector<TrackedActorData> GetNeutralNPCData() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
return g_neutralNPCs; // Returns a copy
}
// Helper get enemies actors
inline std::vector<RE::Actor*> GetEnemyActors() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
std::vector<RE::Actor*> actors;
for (const auto& data : g_enemies) {
if (data.actor) {
actors.push_back(data.actor);
}
}
return actors;
}
// Helper get companion actors
inline std::vector<RE::Actor*> GetCompanionActors() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
std::vector<RE::Actor*> actors;
for (const auto& data : g_companions) {
if (data.actor) {
actors.push_back(data.actor);
}
}
return actors;
}
// Helper get neutral NPC actors
inline std::vector<RE::Actor*> GetNeutralNPCActors() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
std::vector<RE::Actor*> actors;
for (const auto& data : g_neutralNPCs) {
if (data.actor) {
actors.push_back(data.actor);
}
}
return actors;
}
// Helper to clear stale data
inline void ClearAll() {
std::lock_guard<std::mutex> lock(g_actorDataMutex);
g_enemies.clear();
g_companions.clear();
g_neutralNPCs.clear();
g_enemies_prev.clear();
g_companions_prev.clear();
g_neutralNPCs_prev.clear();
}
}
// -- EVENTS ---
// Event handler for companion kill enemy events
class CompanionKillEventSink : public RE::BSTEventSink<RE::TESDeathEvent>
{
public:
virtual RE::BSEventNotifyControl ProcessEvent(
const RE::TESDeathEvent& a_event,
RE::BSTEventSource<RE::TESDeathEvent>* a_eventSource) override;
static CompanionKillEventSink* GetSingleton()
{
static CompanionKillEventSink singleton;
return &singleton;
}
private:
CompanionKillEventSink() = default;
~CompanionKillEventSink() = default;
CompanionKillEventSink(const CompanionKillEventSink&) = delete;
CompanionKillEventSink(CompanionKillEventSink&&) = delete;
CompanionKillEventSink& operator=(const CompanionKillEventSink&) = delete;
CompanionKillEventSink& operator=(CompanionKillEventSink&&) = delete;
};
// --- FUNCTIONS ---
void ActionCompanions_Internal(std::vector<TrackedActorData> companionData);
RE::BGSInventoryItem* ActorAddInventoryItem_Internal(RE::Actor *actor, RE::TESForm *itemForm, std::int32_t count);
void ActorRemoveInventoryItem_Internal(RE::Actor* actor, RE::TESForm* itemForm, std::int32_t count);
void AdjustChatterFrequency_Internal(std::vector<TrackedActorData> companionData);
void AdjustCombatStyle_Internal(std::vector<TrackedActorData> companionData);
void AdjustGlobalFollowDistances_Internal();
bool AIAccessTest_Internal(RE::TESNPC* npc);
void ApplyAIAggression_Internal(std::vector<TrackedActorData> companionData);
void ApplyPerksToCompanions_Internal(std::vector<TrackedActorData> companionData);
void ApplyKeywordsToCompanions_Internal(std::vector<TrackedActorData> companionData);
void BuffCompanionsMinValues_Internal(std::vector<TrackedActorData> companionData);
void BuffCompanionsSetValues_Internal(std::vector<TrackedActorData> companionData);
bool CheckActorHasItem_Internal(RE::Actor* actor, RE::TESForm* itemForm);
ActorStateData CheckActorStates_Internal(RE::Actor* actor);
bool CheckActorStatesMatch_Internal(RE::Actor* actor, std::uint32_t lifeStateFilter = 0xFF, std::uint32_t weaponStateFilter = 0xFF, std::uint32_t gunStateFilter = 0xFF, std::uint32_t interactingStateFilter = 0xFF);
bool CheckIsCurrentCellEncounterZone_Internal();
bool CheckIsCurrentCellInterior_Internal();
bool CheckIsCurrentCellSettlement_Internal();
TrackedActorData CreateTrackedData_Internal(RE::Actor* actor, ENEMY_TIER tier = ENEMY_TIER::LOW);
EnemyAnalysis EnemyActorAnalyze_Internal(RE::Actor* actor);
std::map<ENEMY_TIER, int> EnemyActorAnalyzeThreatLevel_Internal(std::vector<TrackedActorData> enemyData);
void EquipCompanions_Internal(std::vector<TrackedActorData> companionData);
void EquipAmmunition_Internal(std::vector<TrackedActorData> companionData);
void EquipInventoryItem_Internal(RE::Actor* aNPC, RE::BGSInventoryItem* aInvItem);
bool EquipSlotCheck_Internal(RE::Actor* actor, RE::TESObjectARMO* armor);
float GetActorAngleToActor(const RE::Actor* src, const RE::Actor* dst);
float GetActorDistanceToObject_Internal(RE::Actor* actor, RE::TESObjectREFR* object);
float GetActorDistanceToPlayer_Internal(RE::Actor* actor);
std::vector<RE::TESObjectREFR*> GetAllLootReferencesInCurrentCell_Internal();
std::vector<RE::Actor*> GetAllActors_Internal();
RE::BGSInventoryItem::Stack* GetInventoryItemStackData_Internal(RE::BGSInventoryItem* invItem);
RE::NiPoint3 GetPointXY_Internal(RE::NiPoint3 a_pos, RE::CFilter a_filter, float a_stepRadians, float a_scanDistance, float a_moveDistance);
float GetPointZ_Internal(RE::NiPoint3 a_pos, RE::CFilter a_filter, float a_scanDistanceUp, float a_scanDistanceDown);
std::uint32_t GetSlotMaskFromIndex_Internal(std::int32_t aiSlotIndex);
void HealActorDowned_Internal(RE::Actor *actor);
void HealActorLimbs_Internal(RE::Actor* actor);
void HealActorHealth_Internal(RE::Actor* actor, float healthPercent);
void HealActorPA_Internal(std::vector<TrackedActorData> companionData);
std::size_t InitializeScrapItemComponents_Internal();
void InitializeVariables_Internal();
bool IsActorActiveCompanion_Internal(RE::Actor* actor);
bool IsActorCommanded_Internal(RE::Actor* actor);
bool IsActorEnemy_Internal(RE::Actor* actor);
bool IsActorExcluded_Internal(RE::Actor* actor);
bool IsActorInScene_Internal(RE::Actor* actor);
bool IsActorItemEquipped_Internal(RE::Actor *actor, RE::BGSInventoryItem *invItem);
bool IsActorPlayerOrCompanion_Internal(RE::Actor* actor);
bool IsActorRaceHumanoid_Internal(RE::Actor* actor);
bool IsActorRaceSynth_Internal(RE::Actor* actor);
bool IsActorVendor_Internal(RE::Actor* actor);
bool IsActorWeightLimit_Internal(RE::Actor* actor);
bool IsArmorItem_Internal(RE::BGSInventoryItem invArmor);
bool IsArmorItem_Internal(RE::TESObjectREFR* itemRef);
bool IsArmorItem_Internal(RE::TESForm* itemForm);
bool IsArmorPower_Internal(RE::TESObjectREFR* armor);
bool IsArmorPower_Internal(RE::BGSInventoryItem* invArmor);
bool IsArmorPowerFrame_Internal(RE::TESObjectREFR* armor);
bool IsItemOwnedByPlayer_Internal(RE::TESObjectREFR* source);
bool IsLootableFormType_Internal(RE::TESObjectREFR* source);
bool IsLootAlways_Internal(RE::TESForm* itemForm);
bool IsLootKeywordExcluded_Internal(RE::TESForm* itemForm);
bool IsMenuOpen_Internal();
bool IsWeaponItem_Internal(RE::BGSInventoryItem invItem);
bool IsWeaponItem_Internal(RE::TESObjectREFR* itemRef);
bool IsWeaponItem_Internal(RE::TESForm* itemForm);
RE::TESObjectREFR::RemoveItemData LootBuildRemoveItemData_Internal(RE::BGSInventoryItem *aInventoryItem, RE::TESObjectREFR *aContainer, std::int32_t aCount);
std::int32_t LootItems_Internal(std::vector<TrackedActorData> companionData);
void LootItemsBreakdown_Internal(std::vector<TrackedActorData> companionData);
void LootItemsBreakdown_Internal(RE::Actor* companion);
bool LootItemsFromReference_Internal(RE::TESObjectREFR* source, RE::Actor* companion, std::vector<RE::TESObjectREFR*> objectReferences);
bool LootItemFilter_Internal(RE::TESForm* aForm);
bool LootItemsWeaponLooseNearCorpse_Internal(RE::TESObjectREFR* source, RE::Actor* companion, std::vector<RE::TESObjectREFR*> objectReferences);
void RemoveAIAggression_Internal(std::vector<TrackedActorData> companionData);
void Update_Internal();
std::int32_t UpdateGlobalActorArrays_Internal();
// --- HOOKS ---
// This is our simple repeating timer to run periodic updates
class CCB_RepeatingTimer {
public:
CCB_RepeatingTimer() : running(false) {}
// Start the timer with interval in seconds
void Start(int intervalSeconds, std::function<void()> callback) {
running = true;
std::thread([=]() mutable {
while (running) {
std::this_thread::sleep_for(std::chrono::seconds(intervalSeconds));
if (running) callback();
}
}).detach();
}
void Stop() { running = false; }
bool IsRunning() const { return running.load(); }
private:
std::atomic<bool> running;
};
// One-shot / delayed timer that schedules a task to run on the main thread
namespace F4SE { class TaskInterface; }
extern const F4SE::TaskInterface* g_taskInterface;
class CCB_OneShotTimer {
public:
CCB_OneShotTimer() : cancelled(false) {}
// Start the timer: delaySeconds before scheduling 'task' on the main thread.
void Start(int delaySeconds, std::function<void()> task) {
cancelled.store(false);
std::thread([this, delaySeconds, task = std::move(task)]() mutable {
std::this_thread::sleep_for(std::chrono::seconds(delaySeconds));
if (cancelled.load()) return;
if (g_taskInterface) {
// Enqueue on main-thread task queue
g_taskInterface->AddTask([task = std::move(task)]() {
try { task(); } catch (...) {}
});
}
}).detach();
}
// Cancel the pending task before it fires
void Cancel() { cancelled.store(true); }
// Convenience to check if cancelled (optional)
bool IsCancelled() const { return cancelled.load(); }
private:
std::atomic<bool> cancelled;
};
// --- PAPYRUS ---
bool RegisterPapyrusFunctions(RE::BSScript::IVirtualMachine* vm);