ESHQ is a mod for Half-Life with its own storyline (not from Half-Life universe). Unknown government spy must steal secret files from the building called “Evil scientists headquarters”. But his job is going to be a bit more difficult and long...

Report RSS trigger_random: no more lasers needed

This article describes our implementation of entity that can randomly select target to shoot from a specified list

Posted by on

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:

trigger_ramdon setting


  • 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.

multi_manager setting


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.

Post comment Comments
Foxtrop
Foxtrop - - 1,007 comments

mmm arrangement mod rebirth had a similar entity, it give some replayability element too

Reply Good karma Bad karma+4 votes
RD_AAOW_FDL Author
RD_AAOW_FDL - - 287 comments

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

Reply Good karma+3 votes
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: