Subject: Xash FWGS engine
Purpose: this article describes our implementation of entity that can randomly select target to shoot from a specified list.
Main features: list of possible targets, ability to setting a probability of triggering for every target, single entity for random triggering functionality.
We think you know that the random triggering function in significant portion of actual HL1 engines haven’t even presented. You need to perform some manipulations with “env_laser” and “func_button” to do that (one of popular methods). Also you need to hide this construction.
We want to present you our simple solution for creating randomly generated events. It allows you to:
- specify list of possible targets; up to 16 targets can be specified; if you set probability for all targets to “1”, you can add up to 160 targets;
- set a probability of triggering for every target; numeric value from range [1; 10] represents a proportion with that the current target will be shooted;
- use single entity for random triggering functionality; if you need to loop triggering just add a multi_manager (see below).
Code implementation
Code for this entity called trigger_random added to triggers.cpp. As you can see, his entity largely based on “multi_manager”.
//**********************************************************
// Trigger random - when fired, will randomly fire one of up to 16 targets
// FLAG: THREAD (create clones when triggered)
// FLAG: CLONE (this is a clone for a threaded execution)
#define MAX_RANDOM_RATE 10
// Generic implementation
class CTriggerRandom : public CBaseToggle
{
public:
void KeyValue (KeyValueData *pkvd);
void Spawn (void);
void EXPORT ManagerThink (void);
void EXPORT ManagerUse (CBaseEntity *pActivator, CBaseEntity *pCaller,
USE_TYPE useType, float value);
BOOL HasTarget (string_t targetname);
int ObjectCaps (void)
{
return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION;
}
virtual int Save (CSave &save);
virtual int Restore (CRestore &restore);
static TYPEDESCRIPTION m_SaveData[];
int m_cTargets; // Targets in list
float m_startTime; // (probably, not needed)
// List of targets; for more or less probable events
// same targets will be added one or more times (up to MAX_RANDOM_RATE)
int m_iTargetName [MAX_MULTI_TARGETS * MAX_RANDOM_RATE];
private:
inline BOOL IsClone ( void )
{
return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE;
}
inline BOOL ShouldClone ( void )
{
if (IsClone())
return FALSE;
return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE;
}
CTriggerRandom *Clone (void);
};
LINK_ENTITY_TO_CLASS (trigger_random, CTriggerRandom);
TYPEDESCRIPTION CTriggerRandom::m_SaveData[] =
{
DEFINE_FIELD (CTriggerRandom, m_cTargets, FIELD_INTEGER),
DEFINE_FIELD (CTriggerRandom, m_startTime, FIELD_TIME),
DEFINE_ARRAY (CTriggerRandom, m_iTargetName, FIELD_STRING,
MAX_MULTI_TARGETS * MAX_RANDOM_RATE),
};
IMPLEMENT_SAVERESTORE (CTriggerRandom, CBaseToggle);
// Creating a list of targets taking into account the probabilities
void CTriggerRandom :: KeyValue (KeyValueData *pkvd)
{
if (m_cTargets < MAX_MULTI_TARGETS * MAX_RANDOM_RATE)
{
char tmp[128];
// Rendering a proportion value
int rate = atoi (pkvd->szValue);
if ((rate < 1) || (rate > MAX_RANDOM_RATE))
rate = 1;
// Adding a target
UTIL_StripToken (pkvd->szKeyName, tmp);
for (int i = 0; i < rate; i++)
{
m_iTargetName[m_cTargets] = ALLOC_STRING (tmp);
m_cTargets++;
}
pkvd->fHandled = TRUE;
}
}
// Initializing an entity
void CTriggerRandom :: Spawn( void )
{
pev->solid = SOLID_NOT;
SetUse (&CTriggerRandom::ManagerUse);
SetThink (&CTriggerRandom::ManagerThink);
}
BOOL CTriggerRandom :: HasTarget (string_t targetname)
{
for (int i = 0; i < m_cTargets; i++)
if (FStrEq (STRING(targetname), STRING(m_iTargetName[i])))
return TRUE;
return FALSE;
}
// Shooting target
void CTriggerRandom :: ManagerThink (void)
{
float time;
time = gpGlobals->time - m_startTime; // probably, not needed
// Shooting
// Due to different counts of same targets you will have different
// probabilities for them. Highest count - highest probability
FireTargets (STRING(m_iTargetName[RANDOM_LONG (0, m_cTargets - 1)]),
m_hActivator, this, USE_TOGGLE, 0);
// Stand-by
SetThink (NULL);
if (IsClone ())
{
UTIL_Remove (this);
return;
}
SetUse (&CTriggerRandom::ManagerUse); // Allow to re-use trigger
}
// Cloning for multiplayer mode
CTriggerRandom *CTriggerRandom::Clone (void)
{
CTriggerRandom *pMulti = GetClassPtr ((CTriggerRandom *)NULL);
edict_t *pEdict = pMulti->pev->pContainingEntity;
memcpy (pMulti->pev, pev, sizeof (*pev));
pMulti->pev->pContainingEntity = pEdict;
pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE;
pMulti->m_cTargets = m_cTargets;
memcpy (pMulti->m_iTargetName, m_iTargetName, sizeof (m_iTargetName));
return pMulti;
}
// Activating a trigger
void CTriggerRandom :: ManagerUse (CBaseEntity *pActivator, CBaseEntity *pCaller,
USE_TYPE useType, float value)
{
// In multiplayer games, clone the MM and execute in the clone (like a thread)
// to allow multiple players to trigger the same multimanager
if (ShouldClone ())
{
CTriggerRandom *pClone = Clone ();
pClone->ManagerUse (pActivator, pCaller, useType, value);
return;
}
m_hActivator = pActivator;
m_startTime = gpGlobals->time;
SetUse (NULL); // Disable use until target have fired
SetThink (&CTriggerRandom::ManagerThink);
pev->nextthink = gpGlobals->time;
}
We think that this code may and should be optimized.
How to use (for Hammer 3.5.3)
- Add all targets that you need to randomly trigger. Name them all; f.e., “Lamp1” and “Lamp2”.
- Add a “trigger_random” entity; open settings window. Name the entity.
- Turn off “SmartEdit”. Use “Add” button to fill the list with targets like that:
- Set probabilities. Probability of triggering target = numeric value / summa of numeric values. F.e., probability of triggering target “Lamp1” = 1 / (1 + 2) = 1 / 3 = 33%. Note that, because of division, values (1; 2), (2; 4) and (4; 8) will have the same effect. Accordingly, to make equal probability for all targets just set equal values (not only “1” allowed).
- If you want to loop triggering we recommend you to add “multi_manager” with this settings (see below). Note that in this case multi_manager must trigger himself after some pause (line “TM1 0.5”). Also remember that both of “trigger_random” and “multi_manager” entinies must have “allow repeats” flags set.
Looping method with “multi_manager” is more usable because of ability of interrupting. Just replace the last line with calling a “trigger_relay” entity that will call this “multi_manager”. In needed moment use “trigger_changetarget” for “trigger_relay” to interrupt a loop. I'm not talking about that “multi_manager” can trigger something else at the same time.
mmm arrangement mod rebirth had a similar entity, it give some replayability element too
Oh, thanks for information. We didn't know about this feature. We know that we, probably, 'invented the bicycle'. Anyway, it is another one solution for this old question (for our mod too). Just because manuals with lasers continue arising