From f61ddb0c87bc44103c897dbb1f368911096f3250 Mon Sep 17 00:00:00 2001 From: Yui <50331474+SirYodaJedi@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:50:49 -0400 Subject: [PATCH 1/2] add `trigger_random` --- fgd_def/pd_300.def | 6 +++ fgd_def/pd_300.fgd | 16 ++++++ fgd_def/pd_300_JACK.fgd | 16 ++++++ qc/triggers.qc | 113 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) diff --git a/fgd_def/pd_300.def b/fgd_def/pd_300.def index 5d00061..3839ba3 100644 --- a/fgd_def/pd_300.def +++ b/fgd_def/pd_300.def @@ -778,6 +778,12 @@ set "message" to text string /*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) X X X X X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages. */ +/*QUAKED trigger_random (.5 .5 .5) (-8 -8 -8) (8 8 8) X X X X X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY +Similar to trigger_relay, but randomly uses one of its targets when used, instead of all of them. Targets must be defined seqentially. Does not support killtargets, message, or delay. + +"target" through "target4" are the random target options +"noise" through "noise4" are played when the respective target is chosen +*/ /*QUAKED trigger_secret (.5 .5 .5) ? X X X X X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY secret counter trigger sounds diff --git a/fgd_def/pd_300.fgd b/fgd_def/pd_300.fgd index 1403863..bb52634 100644 --- a/fgd_def/pd_300.fgd +++ b/fgd_def/pd_300.fgd @@ -2410,6 +2410,22 @@ MONSTER_ONLY(16) will only teleport monsters" @PointClass base(Trigger) = trigger_relay : "Trigger: Relay" [ ] + +@PointClass base(Appearflags,Targetname) = trigger_random : "Similar to trigger_relay, but randomly fires one of its targets when used, instead of all of them. Targets must be defined seqentially. Does not support killtargets, message, or delay." +[ + target(target_destination) : "Option 1 Target" : : "Target to fire when option 1 is chosen." + noise(sound) : "Option 1 Sound" : "misc/null.wav" : "Sound to play when option 1 is chosen." + + target2(target_destination) : "Option 2 Target" : : "Target to fire when option 2 is chosen." + noise2(sound) : "Option 2 Sound" : "misc/null.wav" : "Sound to play when option 2 is chosen." + + target3(target_destination) : "Option 3 Target" : : "Target to fire when option 3 is chosen." + noise3(sound) : "Option 3 Sound" : "misc/null.wav" : "Sound to play when option 3 is chosen." + + target4(target_destination) : "Option 4 Target" : : "Target to fire when option 4 is chosen." + noise4(sound) : "Option 4 Sound" : "misc/null.wav" : "Sound to play when option 4 is chosen." +] + @SolidClass base(Appearflags, TriggerWait) = trigger_take_weapon : "Trigger: Remove shotgun on touch. Place at info_player_start for an axe only spawn. Make sure and add a weapon_shotgun to your map!" diff --git a/fgd_def/pd_300_JACK.fgd b/fgd_def/pd_300_JACK.fgd index 210bc84..378cad2 100644 --- a/fgd_def/pd_300_JACK.fgd +++ b/fgd_def/pd_300_JACK.fgd @@ -2167,6 +2167,22 @@ spawnflags(Flags) = @SolidClass base(Appearflags, TriggerWait) = trigger_take_weapon : "Trigger: Remove shotgun on touch. Place at 'info_player_start' for an axe only spawn. Make sure and add a 'weapon_shotgun' to your map!" [] @PointClass base(Targetname, Trigger) = trigger_relay : "Trigger relay" [] + +@PointClass base(Appearflags,Targetname) = trigger_random : "Similar to trigger_relay, but randomly fires one of its targets when used, instead of all of them. Targets must be defined seqentially. Does not support killtargets, message, or delay." +[ + target(target_destination) : "Option 1 Target" : : "Target to fire when option 1 is chosen." + noise(sound) : "Option 1 Sound" : "misc/null.wav" : "Sound to play when option 1 is chosen." + + target2(target_destination) : "Option 2 Target" : : "Target to fire when option 2 is chosen." + noise2(sound) : "Option 2 Sound" : "misc/null.wav" : "Sound to play when option 2 is chosen." + + target3(target_destination) : "Option 3 Target" : : "Target to fire when option 3 is chosen." + noise3(sound) : "Option 3 Sound" : "misc/null.wav" : "Sound to play when option 3 is chosen." + + target4(target_destination) : "Option 4 Target" : : "Target to fire when option 4 is chosen." + noise4(sound) : "Option 4 Sound" : "misc/null.wav" : "Sound to play when option 4 is chosen." +] + @SolidClass base(Angle, Appearflags, OneTargetname, TriggerWait) = trigger_monsterjump : "Trigger: Monster jump" [ speed(integer) : "Jump Speed" : 200 diff --git a/qc/triggers.qc b/qc/triggers.qc index 96c3b01..81488b7 100644 --- a/qc/triggers.qc +++ b/qc/triggers.qc @@ -225,6 +225,119 @@ void() trigger_relay = self.use = SUB_UseTargets; }; +//============================================================================= + +/*QUAKED trigger_random (.5 .5 .5) (-8 -8 -8) (8 8 8) X X X X X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY +Similar to trigger_relay, but randomly uses one of its targets when used, instead of all of them. Targets must be defined seqentially. Does not support killtargets, message, or delay. + +"target" through "target4" are the random target options +"noise" through "noise4" are played when the respective target is chosen +*/ + +void() trigger_random_use = +{ + local float rand; + if (self.health == 1) + { + // only one target; forgo calculating random + rand = 1; + } + else + { + // remember, health is how many target KVs there are + rand = ( random() * self.health) + 1; + } + + if (rand >= 4) + { + SUB_UseSpecificTarget(self.target4, targetname); + SUB_UseSpecificTarget(self.target4, targetname2); + SUB_UseSpecificTarget(self.target4, targetname3); + SUB_UseSpecificTarget(self.target4, targetname4); + sound (self, CHAN_AUTO, self.noise4, 1, ATTN_NORM); + } + else if (rand >= 3) + { + SUB_UseSpecificTarget(self.target3, targetname); + SUB_UseSpecificTarget(self.target3, targetname2); + SUB_UseSpecificTarget(self.target3, targetname3); + SUB_UseSpecificTarget(self.target3, targetname4); + sound (self, CHAN_AUTO, self.noise3, 1, ATTN_NORM); + } + else if (rand >= 2) + { + SUB_UseSpecificTarget(self.target2, targetname); + SUB_UseSpecificTarget(self.target2, targetname2); + SUB_UseSpecificTarget(self.target2, targetname3); + SUB_UseSpecificTarget(self.target2, targetname4); + sound (self, CHAN_AUTO, self.noise2, 1, ATTN_NORM); + } + else + { + SUB_UseSpecificTarget(self.target, targetname); + SUB_UseSpecificTarget(self.target, targetname2); + SUB_UseSpecificTarget(self.target, targetname3); + SUB_UseSpecificTarget(self.target, targetname4); + sound (self, CHAN_AUTO, self.noise, 1, ATTN_NORM); + } +} + +void() trigger_random = +{ + if (SUB_Inhibit ()) // new spawnflags for all entities -- iw + return; + + // check target KVs in sequence; if any are null, assume end of sequence. + if (self.target != "") + { + // using health KV to tell rand func how many choices there are. + self.health = 1; + + if ( self.target2 != "" ) + { + self.health = 2; + + if ( self.target3 != "" ) + { + self.health = 3; + + if ( self.target4 != "" ) + { + self.health = 4; + } + } + } + } + else + { + // no target, so remove entity + remove(self); + } + + if (self.noise == "") + { + self.noise = "misc/null.wav"; + } + if (self.noise2 == "") + { + self.noise2 = "misc/null.wav"; + } + if (self.noise3 == "") + { + self.noise3 = "misc/null.wav"; + } + if (self.noise4 == "") + { + self.noise4 = "misc/null.wav"; + } + + precache_sound (self.noise); + precache_sound (self.noise2); + precache_sound (self.noise3); + precache_sound (self.noise4); + + self.use = trigger_random_use; +}; //============================================================================= From 27415abce50e5d74c0265307ea77a7255607dd19 Mon Sep 17 00:00:00 2001 From: Yui <50331474+SirYodaJedi@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:26:30 -0400 Subject: [PATCH 2/2] `trigger_random` improvements and optimzations * Only play sounds if sounds are defined * Log failed spawns and RNG results --- qc/triggers.qc | 67 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/qc/triggers.qc b/qc/triggers.qc index 81488b7..408e9e3 100644 --- a/qc/triggers.qc +++ b/qc/triggers.qc @@ -234,6 +234,12 @@ Similar to trigger_relay, but randomly uses one of its targets when used, instea "noise" through "noise4" are played when the respective target is chosen */ +// these spawnflags are cleared upon entity spawn, and are assigned at runtime based upon whether noise# KVs are empty. +float SF_HAS_NOISE1 = 2; +float SF_HAS_NOISE2 = 4; +float SF_HAS_NOISE3 = 8; +float SF_HAS_NOISE4 = 16; + void() trigger_random_use = { local float rand; @@ -248,13 +254,22 @@ void() trigger_random_use = rand = ( random() * self.health) + 1; } + dprint("RNG result for trigger_random "); + dprint(self.classname); + dprint(": "); + dprint(ftos(rand)); + dprint("\n"); + if (rand >= 4) { SUB_UseSpecificTarget(self.target4, targetname); SUB_UseSpecificTarget(self.target4, targetname2); SUB_UseSpecificTarget(self.target4, targetname3); SUB_UseSpecificTarget(self.target4, targetname4); - sound (self, CHAN_AUTO, self.noise4, 1, ATTN_NORM); + if (self.spawnflags & SF_HAS_NOISE4) + { + sound (self, CHAN_AUTO, self.noise4, 1, ATTN_NORM); + } } else if (rand >= 3) { @@ -262,7 +277,10 @@ void() trigger_random_use = SUB_UseSpecificTarget(self.target3, targetname2); SUB_UseSpecificTarget(self.target3, targetname3); SUB_UseSpecificTarget(self.target3, targetname4); - sound (self, CHAN_AUTO, self.noise3, 1, ATTN_NORM); + if (self.spawnflags & SF_HAS_NOISE3) + { + sound (self, CHAN_AUTO, self.noise3, 1, ATTN_NORM); + } } else if (rand >= 2) { @@ -270,7 +288,10 @@ void() trigger_random_use = SUB_UseSpecificTarget(self.target2, targetname2); SUB_UseSpecificTarget(self.target2, targetname3); SUB_UseSpecificTarget(self.target2, targetname4); - sound (self, CHAN_AUTO, self.noise2, 1, ATTN_NORM); + if (self.spawnflags & SF_HAS_NOISE2) + { + sound (self, CHAN_AUTO, self.noise2, 1, ATTN_NORM); + } } else { @@ -278,7 +299,10 @@ void() trigger_random_use = SUB_UseSpecificTarget(self.target, targetname2); SUB_UseSpecificTarget(self.target, targetname3); SUB_UseSpecificTarget(self.target, targetname4); - sound (self, CHAN_AUTO, self.noise, 1, ATTN_NORM); + if (self.spawnflags & SF_HAS_NOISE1) + { + sound (self, CHAN_AUTO, self.noise, 1, ATTN_NORM); + } } } @@ -311,31 +335,38 @@ void() trigger_random = else { // no target, so remove entity + dprint("WARNING: trigger_random"); + dprint(self.targetname); + dprint(" at "); + dprint(vtos(self.origin)); + dprint(" with no target; removing.\n"); remove(self); } - - if (self.noise == "") + + // clear spawnflags if set by mapper + self.spawnflags &= ~(SF_HAS_NOISE1|SF_HAS_NOISE2|SF_HAS_NOISE3|SF_HAS_NOISE4); + + if (self.noise != "") { - self.noise = "misc/null.wav"; + precache_sound (self.noise); + self.spawnflags |= SF_HAS_NOISE1; } - if (self.noise2 == "") + if (self.noise2 != "") { - self.noise2 = "misc/null.wav"; + precache_sound (self.noise2); + self.spawnflags |= SF_HAS_NOISE2; } - if (self.noise3 == "") + if (self.noise3 != "") { - self.noise3 = "misc/null.wav"; + precache_sound (self.noise3); + self.spawnflags |= SF_HAS_NOISE3; } - if (self.noise4 == "") + if (self.noise4 != "") { - self.noise4 = "misc/null.wav"; + precache_sound (self.noise4); + self.spawnflags |= SF_HAS_NOISE4; } - precache_sound (self.noise); - precache_sound (self.noise2); - precache_sound (self.noise3); - precache_sound (self.noise4); - self.use = trigger_random_use; };