1
2entity stemp, otemp, s, old;
3
4
5void() trigger_reactivate =
6{
7	self.solid = SOLID_TRIGGER;
8};
9
10//=============================================================================
11
12float	SPAWNFLAG_NOMESSAGE = 1;
13float	SPAWNFLAG_NOTOUCH = 1;
14
15// the wait time has passed, so set back up for another activation
16void() multi_wait =
17{
18	if (self.max_health)
19	{
20		self.health = self.max_health;
21		self.takedamage = DAMAGE_YES;
22		self.solid = SOLID_BBOX;
23	}
24};
25
26
27// the trigger was just touched/killed/used
28// self.enemy should be set to the activator so it can be held through a delay
29// so wait for the delay time before firing
30void() multi_trigger =
31{
32	if (self.nextthink > time)
33	{
34		return;		// allready been triggered
35	}
36
37	if (self.classname == "trigger_secret")
38	{
39		if (self.enemy.classname != "player")
40			return;
41		found_secrets = found_secrets + 1;
42		WriteByte (MSG_ALL, SVC_FOUNDSECRET);
43	}
44
45	if (self.noise)
46		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
47
48// don't trigger again until reset
49	self.takedamage = DAMAGE_NO;
50
51	activator = self.enemy;
52
53	SUB_UseTargets();
54
55	if (self.wait > 0)
56	{
57		self.think = multi_wait;
58		self.nextthink = time + self.wait;
59	}
60	else
61	{	// we can't just remove (self) here, because this is a touch function
62		// called wheil C code is looping through area links...
63		self.touch = SUB_Null;
64		self.nextthink = time + 0.1;
65		self.think = SUB_Remove;
66	}
67};
68
69void() multi_killed =
70{
71	self.enemy = damage_attacker;
72	multi_trigger();
73};
74
75void() multi_use =
76{
77	self.enemy = activator;
78	multi_trigger();
79};
80
81void() multi_touch =
82{
83	if (other.classname != "player")
84		return;
85
86// if the trigger has an angles field, check player's facing direction
87	if (self.movedir != '0 0 0')
88	{
89		makevectors (other.angles);
90		if (v_forward * self.movedir < 0)
91			return;		// not facing the right way
92	}
93
94	self.enemy = other;
95	multi_trigger ();
96};
97
98/*QUAKED trigger_multiple (.5 .5 .5) ? notouch
99Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
100If "delay" is set, the trigger waits some time after activating before firing.
101"wait" : Seconds between triggerings. (.2 default)
102If notouch is set, the trigger is only fired by other entities, not by touching.
103NOTOUCH has been obsoleted by trigger_relay!
104sounds
1051)	secret
1062)	beep beep
1073)	large switch
1084)
109set "message" to text string
110*/
111void() trigger_multiple =
112{
113	if (self.sounds == 1)
114	{
115		precache_sound ("misc/secret.wav");
116		self.noise = "misc/secret.wav";
117	}
118	else if (self.sounds == 2)
119	{
120		precache_sound ("misc/talk.wav");
121		self.noise = "misc/talk.wav";
122	}
123	else if (self.sounds == 3)
124	{
125		precache_sound ("misc/trigger1.wav");
126		self.noise = "misc/trigger1.wav";
127	}
128
129	if (!self.wait)
130		self.wait = 0.2;
131	self.use = multi_use;
132
133	InitTrigger ();
134
135	if (self.health)
136	{
137		if (self.spawnflags & SPAWNFLAG_NOTOUCH)
138			objerror ("health and notouch don't make sense\n");
139		self.max_health = self.health;
140		self.th_die = multi_killed;
141		self.takedamage = DAMAGE_YES;
142		self.solid = SOLID_BBOX;
143		setorigin (self, self.origin);	// make sure it links into the world
144	}
145	else
146	{
147		if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
148		{
149			self.touch = multi_touch;
150		}
151	}
152};
153
154
155/*QUAKED trigger_once (.5 .5 .5) ? notouch
156Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
157"targetname".  If "health" is set, the trigger must be killed to activate.
158If notouch is set, the trigger is only fired by other entities, not by touching.
159if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
160if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
161sounds
1621)	secret
1632)	beep beep
1643)	large switch
1654)
166set "message" to text string
167*/
168void() trigger_once =
169{
170	self.wait = -1;
171	trigger_multiple();
172};
173
174//=============================================================================
175
176/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
177This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
178*/
179void() trigger_relay =
180{
181	self.use = SUB_UseTargets;
182};
183
184
185//=============================================================================
186
187/*QUAKED trigger_secret (.5 .5 .5) ?
188secret counter trigger
189sounds
1901)	secret
1912)	beep beep
1923)
1934)
194set "message" to text string
195*/
196void() trigger_secret =
197{
198	total_secrets = total_secrets + 1;
199	self.wait = -1;
200	if (!self.message)
201		self.message = "You found a secret area!";
202	if (!self.sounds)
203		self.sounds = 1;
204
205	if (self.sounds == 1)
206	{
207		precache_sound ("misc/secret.wav");
208		self.noise = "misc/secret.wav";
209	}
210	else if (self.sounds == 2)
211	{
212		precache_sound ("misc/talk.wav");
213		self.noise = "misc/talk.wav";
214	}
215
216	trigger_multiple ();
217};
218
219//=============================================================================
220
221
222void() counter_use =
223{
224	local string junk;
225
226	self.count = self.count - 1;
227	if (self.count < 0)
228		return;
229
230	if (self.count != 0)
231	{
232		if (activator.classname == "player"
233		&& (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
234		{
235			if (self.count >= 4)
236				centerprint (activator, "There are more to go...");
237			else if (self.count == 3)
238				centerprint (activator, "Only 3 more to go...");
239			else if (self.count == 2)
240				centerprint (activator, "Only 2 more to go...");
241			else
242				centerprint (activator, "Only 1 more to go...");
243		}
244		return;
245	}
246
247	if (activator.classname == "player"
248	&& (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
249		centerprint(activator, "Sequence completed!");
250	self.enemy = activator;
251	multi_trigger ();
252};
253
254/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
255Acts as an intermediary for an action that takes multiple inputs.
256
257If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
258
259After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
260*/
261void() trigger_counter =
262{
263	self.wait = -1;
264	if (!self.count)
265		self.count = 2;
266
267	self.use = counter_use;
268};
269
270
271/*
272==============================================================================
273
274TELEPORT TRIGGERS
275
276==============================================================================
277*/
278
279float	PLAYER_ONLY	= 1;
280float	SILENT = 2;
281
282void() play_teleport =
283{
284	local	float v;
285	local	string tmpstr;
286
287	v = random() * 5;
288	if (v < 1)
289		tmpstr = "misc/r_tele1.wav";
290	else if (v < 2)
291		tmpstr = "misc/r_tele2.wav";
292	else if (v < 3)
293		tmpstr = "misc/r_tele3.wav";
294	else if (v < 4)
295		tmpstr = "misc/r_tele4.wav";
296	else
297		tmpstr = "misc/r_tele5.wav";
298
299	sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM);
300	remove (self);
301};
302
303void(vector org) spawn_tfog =
304{
305	s = spawn ();
306	s.origin = org;
307	s.nextthink = time + 0.2;
308	s.think = play_teleport;
309
310	WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
311	WriteByte (MSG_BROADCAST, TE_TELEPORT);
312	WriteCoord (MSG_BROADCAST, org_x);
313	WriteCoord (MSG_BROADCAST, org_y);
314	WriteCoord (MSG_BROADCAST, org_z);
315};
316
317
318void() tdeath_touch =
319{
320	if (other == self.owner)
321		return;
322
323// frag anyone who teleports in on top of an invincible player
324	if (other.classname == "player")
325	{
326		if (other.invincible_finished > time)
327			self.classname = "teledeath2";
328		if (self.owner.classname != "player")
329		{	// other monsters explode themselves
330			T_Damage (self.owner, self, self, 50000);
331			return;
332		}
333
334	}
335
336	if (other.health)
337	{
338		T_Damage (other, self, self, 50000);
339	}
340};
341
342
343void(vector org, entity death_owner) spawn_tdeath =
344{
345local entity	death;
346
347	death = spawn();
348	death.classname = "teledeath";
349	death.movetype = MOVETYPE_NONE;
350	death.solid = SOLID_TRIGGER;
351	death.angles = '0 0 0';
352	setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1');
353	setorigin (death, org);
354	death.touch = tdeath_touch;
355	death.nextthink = time + 0.2;
356	death.think = SUB_Remove;
357	death.owner = death_owner;
358
359	force_retouch = 2;		// make sure even still objects get hit
360};
361
362void() teleport_touch =
363{
364local entity	t;
365local vector	org;
366
367	if (self.targetname)
368	{
369		if (self.nextthink < time)
370		{
371			return;		// not fired yet
372		}
373	}
374
375	if (self.spawnflags & PLAYER_ONLY)
376	{
377		if (other.classname != "player")
378			return;
379	}
380
381// only teleport living creatures
382	if (other.health <= 0 || other.solid != SOLID_SLIDEBOX)
383		return;
384
385	SUB_UseTargets ();
386
387// put a tfog where the player was
388	spawn_tfog (other.origin);
389
390	t = find (world, targetname, self.target);
391	if (!t)
392		objerror ("couldn't find target");
393
394// spawn a tfog flash in front of the destination
395	makevectors (t.mangle);
396	org = t.origin + 32 * v_forward;
397
398	spawn_tfog (org);
399	spawn_tdeath(t.origin, other);
400
401// move the player and lock him down for a little while
402	if (!other.health)
403	{
404		other.origin = t.origin;
405		other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y);
406		return;
407	}
408
409	setorigin (other, t.origin);
410	other.angles = t.mangle;
411	if (other.classname == "player")
412	{
413		other.fixangle = 1;		// turn this way immediately
414		other.teleport_time = time + 0.7;
415		if (other.flags & FL_ONGROUND)
416			other.flags = other.flags - FL_ONGROUND;
417		other.velocity = v_forward * 300;
418	}
419	other.flags = other.flags - other.flags & FL_ONGROUND;
420};
421
422/*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32)
423This is the destination marker for a teleporter.  It should have a "targetname" field with the same value as a teleporter's "target" field.
424*/
425void() info_teleport_destination =
426{
427// this does nothing, just serves as a target spot
428	self.mangle = self.angles;
429	self.angles = '0 0 0';
430	self.model = "";
431	self.origin = self.origin + '0 0 27';
432	if (!self.targetname)
433		objerror ("no targetname");
434};
435
436void() teleport_use =
437{
438	self.nextthink = time + 0.2;
439	force_retouch = 2;		// make sure even still objects get hit
440	self.think = SUB_Null;
441};
442
443/*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT
444Any object touching this will be transported to the corresponding info_teleport_destination entity. You must set the "target" field, and create an object with a "targetname" field that matches.
445
446If the trigger_teleport has a targetname, it will only teleport entities when it has been fired.
447*/
448void() trigger_teleport =
449{
450	local vector o;
451
452	InitTrigger ();
453	self.touch = teleport_touch;
454	// find the destination
455	if (!self.target)
456		objerror ("no target");
457	self.use = teleport_use;
458
459	if (!(self.spawnflags & SILENT))
460	{
461		precache_sound ("ambience/hum1.wav");
462		o = (self.mins + self.maxs)*0.5;
463		ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC);
464	}
465};
466
467/*
468==============================================================================
469
470trigger_setskill
471
472==============================================================================
473*/
474
475void() trigger_skill_touch =
476{
477	if (other.classname != "player")
478		return;
479
480	cvar_set ("skill", self.message);
481};
482
483/*QUAKED trigger_setskill (.5 .5 .5) ?
484sets skill level to the value of "message".
485Only used on start map.
486*/
487void() trigger_setskill =
488{
489	InitTrigger ();
490	self.touch = trigger_skill_touch;
491};
492
493
494/*
495==============================================================================
496
497ONLY REGISTERED TRIGGERS
498
499==============================================================================
500*/
501
502void() trigger_onlyregistered_touch =
503{
504	if (other.classname != "player")
505		return;
506	if (self.attack_finished > time)
507		return;
508
509	self.attack_finished = time + 2;
510	if (cvar("registered"))
511	{
512		self.message = "";
513		SUB_UseTargets ();
514		remove (self);
515	}
516	else
517	{
518		if (self.message != "")
519		{
520			centerprint (other, self.message);
521			sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
522		}
523	}
524};
525
526/*QUAKED trigger_onlyregistered (.5 .5 .5) ?
527Only fires if playing the registered version, otherwise prints the message
528*/
529void() trigger_onlyregistered =
530{
531	precache_sound ("misc/talk.wav");
532	InitTrigger ();
533	self.touch = trigger_onlyregistered_touch;
534};
535
536//============================================================================
537
538void() hurt_on =
539{
540	self.solid = SOLID_TRIGGER;
541	self.nextthink = -1;
542};
543
544void() hurt_touch =
545{
546	if (other.takedamage)
547	{
548		self.solid = SOLID_NOT;
549		T_Damage (other, self, self, self.dmg);
550		self.think = hurt_on;
551		self.nextthink = time + 1;
552	}
553
554	return;
555};
556
557/*QUAKED trigger_hurt (.5 .5 .5) ?
558Any object touching this will be hurt
559set dmg to damage amount
560defalt dmg = 5
561*/
562void() trigger_hurt =
563{
564	InitTrigger ();
565	self.touch = hurt_touch;
566	if (!self.dmg)
567		self.dmg = 5;
568};
569
570//============================================================================
571
572float PUSH_ONCE = 1;
573
574void() trigger_push_touch =
575{
576	if (other.classname == "grenade")
577		other.velocity = self.speed * self.movedir * 10;
578	else if (other.health > 0)
579	{
580		other.velocity = self.speed * self.movedir * 10;
581		if (other.classname == "player")
582		{
583			if (other.fly_sound < time)
584			{
585				other.fly_sound = time + 1.5;
586				sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM);
587			}
588		}
589	}
590	if (self.spawnflags & PUSH_ONCE)
591		remove(self);
592};
593
594
595/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE
596Pushes the player
597*/
598void() trigger_push =
599{
600	InitTrigger ();
601	precache_sound ("ambience/windfly.wav");
602	self.touch = trigger_push_touch;
603	if (!self.speed)
604		self.speed = 1000;
605};
606
607//============================================================================
608
609void() trigger_monsterjump_touch =
610{
611	if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER )
612		return;
613
614// set XY even if not on ground, so the jump will clear lips
615	other.velocity_x = self.movedir_x * self.speed;
616	other.velocity_y = self.movedir_y * self.speed;
617
618	if ( !(other.flags & FL_ONGROUND) )
619		return;
620
621	other.flags = other.flags - FL_ONGROUND;
622
623	other.velocity_z = self.height;
624};
625
626/*QUAKED trigger_monsterjump (.5 .5 .5) ?
627Walking monsters that touch this will jump in the direction of the trigger's angle
628"speed" default to 200, the speed thrown forward
629"height" default to 200, the speed thrown upwards
630*/
631void() trigger_monsterjump =
632{
633	if (!self.speed)
634		self.speed = 200;
635	if (!self.height)
636		self.height = 200;
637	if (self.angles == '0 0 0')
638		self.angles = '0 360 0';
639	InitTrigger ();
640	self.touch = trigger_monsterjump_touch;
641};
642
643