diff --git a/regamedll/dlls/addons/trigger_random.cpp b/regamedll/dlls/addons/trigger_random.cpp
new file mode 100644
index 00000000..44b22f4d
--- /dev/null
+++ b/regamedll/dlls/addons/trigger_random.cpp
@@ -0,0 +1,229 @@
+/*
+*
+* This program 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 2 of the License, or (at
+* your option) any later version.
+*
+* This program 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 this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*/
+
+#include "precompiled.h"
+
+TYPEDESCRIPTION CTriggerRandom::m_SaveData[] =
+{
+ DEFINE_FIELD(CTriggerRandom, m_uiTargetsUse, FIELD_INTEGER),
+ DEFINE_ARRAY(CTriggerRandom, m_iszTargets, FIELD_STRING, MAX_TR_TARGETS),
+ DEFINE_FIELD(CTriggerRandom, m_bActive, FIELD_CHARACTER),
+ DEFINE_FIELD(CTriggerRandom, m_flMinDelay, FIELD_FLOAT),
+ DEFINE_FIELD(CTriggerRandom, m_flMaxDelay, FIELD_FLOAT)
+};
+
+LINK_ENTITY_TO_CLASS(trigger_random, CTriggerRandom, CCSTriggerRandom)
+LINK_ENTITY_TO_CLASS(trigger_random_time, CTriggerRandom, CCSTriggerRandom) // Obsolete: use trigger_random with Timed flag
+LINK_ENTITY_TO_CLASS(trigger_random_unique, CTriggerRandom, CCSTriggerRandom) // Obsolete: use trigger_ranom with Random flag. Unique Trigger Random. Randomly selects an unused trigger.
+
+IMPLEMENT_SAVERESTORE(CTriggerRandom, CBaseDelay)
+
+void CTriggerRandom::Spawn()
+{
+ m_bActive = (pev->spawnflags & SF_RANDOM_STARTON) == SF_RANDOM_STARTON;
+
+ if (FClassnameIs(pev, "trigger_random_time"))
+ {
+ pev->spawnflags |= SF_RANDOM_TIMED;
+ }
+ else if (FClassnameIs(pev, "trigger_random_unique"))
+ {
+ pev->spawnflags |= SF_RANDOM_UNIQUE;
+
+ if (pev->spawnflags & SF_RANDOM_STARTON)
+ {
+ pev->spawnflags &= ~SF_RANDOM_STARTON;
+ pev->spawnflags |= SF_RANDOM_REUSABLE;
+ }
+ }
+
+ if (pev->spawnflags & SF_RANDOM_TIMED)
+ {
+ pev->nextthink = gpGlobals->time + RandomDelay();
+ SetThink(&CTriggerRandom::RandomThink);
+ }
+
+ if (pev->spawnflags & SF_RANDOM_UNIQUE)
+ {
+ InitUnique();
+ }
+}
+
+void CTriggerRandom::KeyValue(KeyValueData *pkvd)
+{
+ if (FStrEq(pkvd->szKeyName, "target_count"))
+ {
+ m_uiTargetsUse = Q_atoi(pkvd->szValue);
+
+ if (m_uiTargetsUse >= MAX_TR_TARGETS)
+ {
+ m_uiTargetsUse = MAX_TR_TARGETS;
+ }
+
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "min_delay"))
+ {
+ m_flMinDelay = Q_atof(pkvd->szValue);
+
+ if (m_flMaxDelay > 0 && m_flMinDelay > m_flMaxDelay)
+ {
+ SWAP(m_flMinDelay, m_flMaxDelay);
+ }
+
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "max_delay"))
+ {
+ m_flMaxDelay = Q_atof(pkvd->szValue);
+
+ if (m_flMinDelay > 0 && m_flMaxDelay < m_flMinDelay)
+ {
+ SWAP(m_flMinDelay, m_flMaxDelay);
+ }
+
+ pkvd->fHandled = TRUE;
+ }
+ else
+ {
+ if (FStrnEq(pkvd->szKeyName, "target", sizeof("target") - 1))
+ {
+ char *pszTargetName = nullptr;
+ int iTargetsCount = strtoul(&pkvd->szKeyName[sizeof("target") - 1], &pszTargetName, 10);
+ if (iTargetsCount < MAX_TR_TARGETS && pszTargetName && pszTargetName[0] == '\0')
+ {
+ m_iszTargets[iTargetsCount] = ALLOC_STRING(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ return;
+ }
+ }
+
+ CBaseDelay::KeyValue(pkvd);
+ }
+}
+
+void CTriggerRandom::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
+{
+ if (pev->spawnflags & SF_RANDOM_TIMED)
+ {
+ m_bActive ^= true;
+
+ pev->nextthink = gpGlobals->time + RandomDelay();
+ SetThink(&CTriggerRandom::RandomThink);
+
+ return;
+ }
+
+ Fire(pActivator);
+}
+
+void CTriggerRandom::RandomThink()
+{
+ if (m_bActive)
+ {
+ Fire(this);
+
+ if (pev->spawnflags & SF_RANDOM_ONCE)
+ {
+ m_bActive = false;
+ }
+ }
+
+ if (m_flMinDelay <= 0 || m_flMaxDelay <= 0)
+ {
+ m_bActive = false;
+ return;
+ }
+
+ pev->nextthink = RandomDelay() + gpGlobals->time;
+}
+
+void CTriggerRandom::InitUnique()
+{
+ m_uiTargetsFired = 0;
+ Q_memset(m_bActiveTargets, 0, MAX_TR_TARGETS);
+}
+
+void CTriggerRandom::Fire(CBaseEntity *pActivator)
+{
+ string_t iszSelectTarget = iStringNull;
+ if (pev->spawnflags & SF_RANDOM_UNIQUE)
+ {
+ if (m_uiTargetsFired >= m_uiTargetsUse)
+ {
+ if (pev->spawnflags & SF_RANDOM_REUSABLE)
+ {
+ InitUnique();
+ ALERT(at_aiconsole, "%s(%s): all targets fired; reusable mode on; resetting state\n", pev->classname.str(), pev->targetname.str());
+ }
+ else
+ {
+ // no re-usable
+ return;
+ }
+ }
+
+ unsigned int iRandomTarget = 0;
+ const int MAX_SELECT_ATTEMPT = 256;
+
+ for (int iSelect = 0; iSelect < MAX_SELECT_ATTEMPT; iSelect++)
+ {
+ iRandomTarget = RANDOM_LONG(0, m_uiTargetsUse - 1);
+
+ if (!m_bActiveTargets[iRandomTarget])
+ break;
+ }
+
+ // if queue is busy, try select first free target
+ if (m_bActiveTargets[iRandomTarget])
+ {
+ ALERT(at_aiconsole, "%s(%s): random selection failed, selecting first free target\n", pev->classname.str(), pev->targetname.str());
+
+ for (iRandomTarget = 0; iRandomTarget < m_uiTargetsUse; iRandomTarget++)
+ {
+ if (!m_bActiveTargets[iRandomTarget])
+ break;
+ }
+ }
+
+ iszSelectTarget = m_iszTargets[iRandomTarget];
+ m_bActiveTargets[iRandomTarget] = true;
+ m_uiTargetsFired++;
+ }
+ else
+ {
+ const int MAX_SELECT_ATTEMPT = 10;
+ for (int iSelect = 0; iSelect < MAX_SELECT_ATTEMPT; iSelect++)
+ {
+ iszSelectTarget = m_iszTargets[RANDOM_LONG(0, m_uiTargetsUse - 1)];
+
+ // free target
+ if (!iszSelectTarget.IsNull())
+ {
+ break;
+ }
+ }
+ }
+
+ FireTargets(iszSelectTarget, pActivator, this, USE_TOGGLE, 0);
+}
+
+float CTriggerRandom::RandomDelay()
+{
+ return RANDOM_FLOAT(m_flMinDelay, m_flMaxDelay);
+}
diff --git a/regamedll/dlls/addons/trigger_random.h b/regamedll/dlls/addons/trigger_random.h
new file mode 100644
index 00000000..cc9b12ab
--- /dev/null
+++ b/regamedll/dlls/addons/trigger_random.h
@@ -0,0 +1,73 @@
+/*
+*
+* This program 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 2 of the License, or (at
+* your option) any later version.
+*
+* This program 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 this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*/
+
+#pragma once
+
+// Makes trigger_random (with 'Timed only' flag) enabled at map start, so it will start it's timer and trigger random target on game start.
+// If 'Trigger Once' flag isn't selected, it will continue until deactivated by trigger.
+#define SF_RANDOM_STARTON BIT(0)
+
+// When using random delays (with 'Timed only' flag), this tells to trigger a random target once, instead
+// of continuously triggering random targets until deactivation (in that case, disable timer by triggering this entity again).
+#define SF_RANDOM_ONCE BIT(1)
+
+// If set, the trigger_random with 'Unique only' flag can be used again after having fired its targets, handling
+// all of them as if not triggered before again.
+#define SF_RANDOM_REUSABLE BIT(2)
+
+// Enables 'Minimum/Maximum delay' keyvalues so you can specify to wait a random amount of time before triggering random targets.
+// When 'Trigger Once' and 'Start On' flags are NOT selected, triggering this trigger_random starts the timer,
+// and it will fire it's targets with random delays repeatedly until triggered again, what pauses it.
+#define SF_RANDOM_TIMED BIT(3)
+
+// Trigger will pick target (each time it's triggered), that haven't been triggered yet, randomly. So if four targets are specified,
+// the combination in which they can be picked may be: 3th, 1th, 2th, 4th. It never repeats the same target unless 'Re-usable'
+// flag is selected- the list will be "shuffled", and targets can be picked all over again.
+#define SF_RANDOM_UNIQUE BIT(4)
+
+const int MAX_TR_TARGETS = 16; // maximum number of targets a single trigger_random entity may be assigned.
+
+class CTriggerRandom: public CBaseDelay {
+public:
+ void Spawn();
+ void KeyValue(KeyValueData *pkvd);
+ int Save(CSave &save);
+ int Restore(CRestore &restore);
+ int ObjectCaps() { return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); }
+ void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value);
+
+protected:
+ void InitUnique();
+ float RandomDelay();
+ void Fire(CBaseEntity *pActivator);
+ void EXPORT RandomThink();
+
+ static TYPEDESCRIPTION m_SaveData[];
+
+private:
+ unsigned int m_uiTargetsUse;
+ string_t m_iszTargets[MAX_TR_TARGETS];
+
+ bool m_bActive;
+
+ float m_flMinDelay;
+ float m_flMaxDelay;
+
+ unsigned int m_uiTargetsFired;
+ bool m_bActiveTargets[MAX_TR_TARGETS];
+};
diff --git a/regamedll/dlls/util.h b/regamedll/dlls/util.h
index 90465823..e45b8112 100644
--- a/regamedll/dlls/util.h
+++ b/regamedll/dlls/util.h
@@ -152,6 +152,8 @@ inline bool FNullEnt(entvars_t *pev) { return (pev == nullptr || FNullEnt(OFFSET
inline bool FNullEnt(const edict_t *pent) { return (pent == nullptr || pent->free || FNullEnt(OFFSET(pent))); }
inline bool FStringNull(string_t iString) { return (iString == iStringNull); }
inline bool FStrEq(const char *sz1, const char *sz2) { return (Q_strcmp(sz1, sz2) == 0); }
+inline bool FStrnEq(const char *sz1, const char *sz2, size_t elem) { return (Q_strncmp(sz1, sz2, elem) == 0); }
+
inline bool FClassnameIs(entvars_t *pev, const char *szClassname) { return FStrEq(STRING(pev->classname), szClassname); }
inline bool FClassnameIs(edict_t *pent, const char *szClassname) { return FStrEq(STRING(VARS(pent)->classname), szClassname); }
inline void UTIL_MakeVectorsPrivate(Vector vecAngles, float *p_vForward, float *p_vRight, float *p_vUp) { g_engfuncs.pfnAngleVectors(vecAngles, p_vForward, p_vRight, p_vUp); }
diff --git a/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd b/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd
index 22580717..42b6a181 100644
--- a/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd
+++ b/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd
@@ -2157,6 +2157,57 @@
speed(integer) : "Speed of push" : 40
]
+@BaseClass base(Targetname) = BaseRandom
+[
+ target_count(integer) : "Target Count" : 4
+ target1(target_destination) : "Target 1"
+ target2(target_destination) : "Target 2"
+ target3(target_destination) : "Target 3"
+ target4(target_destination) : "Target 4"
+ target5(target_destination) : "Target 5"
+ target6(target_destination) : "Target 6"
+ target7(target_destination) : "Target 7"
+ target8(target_destination) : "Target 8"
+ target9(target_destination) : "Target 9"
+ target10(target_destination) : "Target 10"
+ target11(target_destination) : "Target 11"
+ target12(target_destination) : "Target 12"
+ target13(target_destination) : "Target 13"
+ target14(target_destination) : "Target 14"
+ target15(target_destination) : "Target 15"
+ target16(target_destination) : "Target 16"
+]
+
+@PointClass base(BaseRandom) size(-8 -8 -8, 8 8 8) iconsprite("sprites/CS/trigger_random.spr") = trigger_random : "Trigger Random"
+[
+ spawnflags(Flags) =
+ [
+ 1 : "Start On (Timed only)" : 0
+ 2 : "Trigger Once (Timed only)" : 0
+ 4 : "Reusable (Unique only)" : 0
+ 8 : "Timed" : 0
+ 16 : "Unique" : 0
+ ]
+
+ min_delay(string) : "Minimum Delay (0 = off)" : "3.0"
+ max_delay(string) : "Maximum Delay (0 = off)" : "7.0"
+]
+
+// Obsolete: use trigger_random with Timed flag
+@PointClass base(trigger_random) size(-8 -8 -8, 8 8 8) iconsprite("sprites/CS/trigger_random.spr") = trigger_random_time : "Trigger Random Time"
+[
+]
+
+// Obsolete: use trigger_ranom with Random flag
+// Unique Trigger Random. Randomly selects an unused trigger.
+@PointClass base(BaseRandom) size(-8 -8 -8, 8 8 8) iconsprite("sprites/CS/trigger_random.spr") = trigger_random_unique : "Trigger Random Unique"
+[
+ spawnflags(Flags) =
+ [
+ 1 : "Re-usable" : 0
+ ]
+]
+
@PointClass base(Targetname, Targetx) iconsprite("sprites/CS/trigger_relay.spr") = trigger_relay : "Trigger Relay"
[
spawnflags(flags) =
diff --git a/regamedll/extra/Toolkit/GameDefinitionFile/sprites/CS/trigger_random.spr b/regamedll/extra/Toolkit/GameDefinitionFile/sprites/CS/trigger_random.spr
new file mode 100644
index 00000000..840cfc28
Binary files /dev/null and b/regamedll/extra/Toolkit/GameDefinitionFile/sprites/CS/trigger_random.spr differ
diff --git a/regamedll/msvc/ReGameDLL.vcxproj b/regamedll/msvc/ReGameDLL.vcxproj
index 6e1541e9..776cd00e 100644
--- a/regamedll/msvc/ReGameDLL.vcxproj
+++ b/regamedll/msvc/ReGameDLL.vcxproj
@@ -24,6 +24,7 @@
+
@@ -601,6 +602,7 @@
+
diff --git a/regamedll/msvc/ReGameDLL.vcxproj.filters b/regamedll/msvc/ReGameDLL.vcxproj.filters
index 20ec22a6..59396e9c 100644
--- a/regamedll/msvc/ReGameDLL.vcxproj.filters
+++ b/regamedll/msvc/ReGameDLL.vcxproj.filters
@@ -550,6 +550,9 @@
regamedll
+
+ dlls\addons
+
@@ -1041,6 +1044,9 @@
public\regamedll\API
+
+ dlls\addons
+
diff --git a/regamedll/public/regamedll/API/CSInterfaces.h b/regamedll/public/regamedll/API/CSInterfaces.h
index 74c541e4..bf6c6c7b 100644
--- a/regamedll/public/regamedll/API/CSInterfaces.h
+++ b/regamedll/public/regamedll/API/CSInterfaces.h
@@ -226,4 +226,5 @@ class CCSTriggerCamera: public CCSDelay {};
class CCSWeather: public CCSTrigger {};
class CCSClientFog: public CCSEntity {};
class CCSTriggerSetOrigin: public CCSDelay {};
+class CCSTriggerRandom: public CCSDelay {};
class CCSItemAirBox: public CCSArmoury {};
diff --git a/regamedll/public/strtools.h b/regamedll/public/strtools.h
index 8baec82b..5cdf8c41 100644
--- a/regamedll/public/strtools.h
+++ b/regamedll/public/strtools.h
@@ -80,6 +80,7 @@ inline char *_strlwr(char *start)
#define Q_strstr A_strstr
#define Q_strchr strchr
#define Q_strrchr strrchr
+ #define Q_strtok strtok
#define Q_strlwr A_strtolower
#define Q_strupr A_strtoupper
#define Q_sprintf sprintf
@@ -120,6 +121,7 @@ inline char *_strlwr(char *start)
#define Q_strstr strstr
#define Q_strchr strchr
#define Q_strrchr strrchr
+ #define Q_strtok strtok
#define Q_strlwr _strlwr
#define Q_strupr _strupr
#define Q_sprintf sprintf
@@ -144,30 +146,24 @@ inline char *_strlwr(char *start)
#define Q_fmod fmod
#endif // #if defined(ASMLIB_H) && defined(HAVE_OPT_STRTOOLS)
-// a safe variant of strcpy that truncates the result to fit in the destination buffer
-template
-T *Q_strlcpy(T (&dest)[size], const char *src)
-{
- static_assert(sizeof(T) == sizeof(char), "invalid size of type != sizeof(char)");
-
- Q_strncpy((char *)dest, src, size - 1);
+// size - sizeof(buffer)
+inline char *Q_strlcpy(char *dest, const char *src, size_t size) {
+ Q_strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
-inline char *Q_strnlcpy(char *dest, const char *src, size_t n) {
- Q_strncpy(dest, src, n - 1);
- dest[n - 1] = '\0';
- return dest;
+// a safe variant of strcpy that truncates the result to fit in the destination buffer
+template
+char *Q_strlcpy(char (&dest)[size], const char *src) {
+ return Q_strlcpy(dest, src, size);
}
// safely concatenate two strings.
// a variant of strcat that truncates the result to fit in the destination buffer
-template
-size_t Q_strlcat(T (&dest)[size], const char *src)
+template
+size_t Q_strlcat(char (&dest)[size], const char *src)
{
- static_assert(sizeof(T) == sizeof(char), "invalid size of type != sizeof(char)");
-
size_t srclen; // Length of source string
size_t dstlen; // Length of destination string
diff --git a/regamedll/regamedll/dlls.h b/regamedll/regamedll/dlls.h
index 136336b0..60e0974d 100644
--- a/regamedll/regamedll/dlls.h
+++ b/regamedll/regamedll/dlls.h
@@ -132,6 +132,7 @@ using FloatRef = float;
// Addons
#include "addons/item_airbox.h"
#include "addons/trigger_setorigin.h"
+#include "addons/trigger_random.h"
// Tutor
#include "tutor.h"