From a658fd6c8f977e04313d6a25b9cc278b8ad98517 Mon Sep 17 00:00:00 2001 From: s1lent Date: Tue, 22 May 2018 17:51:24 +0700 Subject: [PATCH] Add new point-entity trigger_random --- regamedll/dlls/addons/trigger_random.cpp | 229 ++++++++++++++++++ regamedll/dlls/addons/trigger_random.h | 73 ++++++ regamedll/dlls/util.h | 2 + .../GameDefinitionFile/regamedll-cs.fgd | 51 ++++ regamedll/msvc/ReGameDLL.vcxproj | 2 + regamedll/msvc/ReGameDLL.vcxproj.filters | 6 + regamedll/public/regamedll/API/CSInterfaces.h | 1 + regamedll/public/strtools.h | 26 +- regamedll/regamedll/dlls.h | 1 + 9 files changed, 376 insertions(+), 15 deletions(-) create mode 100644 regamedll/dlls/addons/trigger_random.cpp create mode 100644 regamedll/dlls/addons/trigger_random.h diff --git a/regamedll/dlls/addons/trigger_random.cpp b/regamedll/dlls/addons/trigger_random.cpp new file mode 100644 index 000000000..44b22f4dc --- /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 000000000..cc9b12abf --- /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 90465823d..e45b81125 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 225807176..798947aab 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) color(255 230 150) = 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) color(135 205 255) = trigger_random_time : "Trigger Random Time" +[ +] + +// Obsolete: use trigger_ranom with Random flag +// Unique Trigger Random. Randomly selects an unused trigger. +@PointClass base(BaseRandom) color(225 170 70) = 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/msvc/ReGameDLL.vcxproj b/regamedll/msvc/ReGameDLL.vcxproj index 6e1541e96..776cd00e3 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 20ec22a62..59396e9cb 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 74c541e45..bf6c6c7be 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 8baec82b5..5cdf8c41d 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 136336b05..60e0974d0 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"