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_MULTICAST, SVC_TEMPENTITY);
311	WriteByte (MSG_MULTICAST, TE_TELEPORT);
312	WriteCoord (MSG_MULTICAST, org_x);
313	WriteCoord (MSG_MULTICAST, org_y);
314	WriteCoord (MSG_MULTICAST, org_z);
315	multicast (org, MULTICAST_PHS);
316};
317
318
319void() tdeath_touch =
320{
321	local entity other2;
322
323	if (other == self.owner)
324		return;
325
326// frag anyone who teleports in on top of an invincible player
327	if (other.classname == "player")
328	{
329		if (other.invincible_finished > time &&
330			self.owner.invincible_finished > time) {
331			self.classname = "teledeath3";
332			other.invincible_finished = 0;
333			self.owner.invincible_finished = 0;
334			T_Damage (other, self, self, 50000);
335			other2 = self.owner;
336			self.owner = other;
337			T_Damage (other2, self, self, 50000);
338		}
339
340		if (other.invincible_finished > time)
341		{
342			self.classname = "teledeath2";
343			T_Damage (self.owner, self, self, 50000);
344			return;
345		}
346
347	}
348
349	if (other.health)
350	{
351		T_Damage (other, self, self, 50000);
352	}
353};
354
355
356void(vector org, entity death_owner) spawn_tdeath =
357{
358local entity	death;
359
360	death = spawn();
361	death.classname = "teledeath";
362	death.movetype = MOVETYPE_NONE;
363	death.solid = SOLID_TRIGGER;
364	death.angles = '0 0 0';
365	setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1');
366	setorigin (death, org);
367	death.touch = tdeath_touch;
368	death.nextthink = time + 0.2;
369	death.think = SUB_Remove;
370	death.owner = death_owner;
371
372	force_retouch = 2;		// make sure even still objects get hit
373};
374
375void() teleport_touch =
376{
377local entity	t;
378local vector	org;
379
380	if (self.targetname)
381	{
382		if (self.nextthink < time)
383		{
384			return;		// not fired yet
385		}
386	}
387
388	if (self.spawnflags & PLAYER_ONLY)
389	{
390		if (other.classname != "player")
391			return;
392	}
393
394// only teleport living creatures
395	if (other.health <= 0 || other.solid != SOLID_SLIDEBOX)
396		return;
397
398	SUB_UseTargets ();
399
400// put a tfog where the player was
401	spawn_tfog (other.origin);
402
403	t = find (world, targetname, self.target);
404	if (!t)
405		objerror ("couldn't find target");
406
407// spawn a tfog flash in front of the destination
408	makevectors (t.mangle);
409	org = t.origin + 32 * v_forward;
410
411	spawn_tfog (org);
412	spawn_tdeath(t.origin, other);
413
414// move the player and lock him down for a little while
415	if (!other.health)
416	{
417		other.origin = t.origin;
418		other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y);
419		return;
420	}
421
422	setorigin (other, t.origin);
423	other.angles = t.mangle;
424	if (other.classname == "player")
425	{
426		other.fixangle = 1;		// turn this way immediately
427		other.teleport_time = time + 0.7;
428		if (other.flags & FL_ONGROUND)
429			other.flags = other.flags - FL_ONGROUND;
430		other.velocity = v_forward * 300;
431	}
432	other.flags = other.flags - other.flags & FL_ONGROUND;
433};
434
435/*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32)
436This is the destination marker for a teleporter.  It should have a "targetname" field with the same value as a teleporter's "target" field.
437*/
438void() info_teleport_destination =
439{
440// this does nothing, just serves as a target spot
441	self.mangle = self.angles;
442	self.angles = '0 0 0';
443	self.model = "";
444	self.origin = self.origin + '0 0 27';
445	if (!self.targetname)
446		objerror ("no targetname");
447};
448
449void() teleport_use =
450{
451	self.nextthink = time + 0.2;
452	force_retouch = 2;		// make sure even still objects get hit
453	self.think = SUB_Null;
454};
455
456/*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT
457Any 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.
458
459If the trigger_teleport has a targetname, it will only teleport entities when it has been fired.
460*/
461void() trigger_teleport =
462{
463	local vector o;
464
465	InitTrigger ();
466	self.touch = teleport_touch;
467	// find the destination
468	if (!self.target)
469		objerror ("no target");
470	self.use = teleport_use;
471
472	if (!(self.spawnflags & SILENT))
473	{
474		precache_sound ("ambience/hum1.wav");
475		o = (self.mins + self.maxs)*0.5;
476		ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC);
477	}
478};
479
480/*
481==============================================================================
482
483trigger_setskill
484
485==============================================================================
486*/
487
488/*QUAKED trigger_setskill (.5 .5 .5) ?
489sets skill level to the value of "message".
490Only used on start map.
491*/
492void() trigger_setskill =
493{
494	remove (self);
495};
496
497
498/*
499==============================================================================
500
501ONLY REGISTERED TRIGGERS
502
503==============================================================================
504*/
505
506void() trigger_onlyregistered_touch =
507{
508	if (other.classname != "player")
509		return;
510	if (self.attack_finished > time)
511		return;
512
513	self.attack_finished = time + 2;
514	if (cvar("registered"))
515	{
516		self.message = "";
517		SUB_UseTargets ();
518		remove (self);
519	}
520	else
521	{
522		if (self.message != "")
523		{
524			centerprint (other, self.message);
525			sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
526		}
527	}
528};
529
530/*QUAKED trigger_onlyregistered (.5 .5 .5) ?
531Only fires if playing the registered version, otherwise prints the message
532*/
533void() trigger_onlyregistered =
534{
535	precache_sound ("misc/talk.wav");
536	InitTrigger ();
537	self.touch = trigger_onlyregistered_touch;
538};
539
540//============================================================================
541
542void() hurt_on =
543{
544	self.solid = SOLID_TRIGGER;
545	self.nextthink = -1;
546};
547
548void() hurt_touch =
549{
550	if (other.takedamage)
551	{
552		self.solid = SOLID_NOT;
553		T_Damage (other, self, self, self.dmg);
554		self.think = hurt_on;
555		self.nextthink = time + 1;
556	}
557
558	return;
559};
560
561/*QUAKED trigger_hurt (.5 .5 .5) ?
562Any object touching this will be hurt
563set dmg to damage amount
564defalt dmg = 5
565*/
566void() trigger_hurt =
567{
568	InitTrigger ();
569	self.touch = hurt_touch;
570	if (!self.dmg)
571		self.dmg = 5;
572};
573
574//============================================================================
575
576float PUSH_ONCE = 1;
577
578void() trigger_push_touch =
579{
580	if (other.classname == "grenade")
581		other.velocity = self.speed * self.movedir * 10;
582	else if (other.health > 0)
583	{
584		other.velocity = self.speed * self.movedir * 10;
585		if (other.classname == "player")
586		{
587			if (other.fly_sound < time)
588			{
589				other.fly_sound = time + 1.5;
590				sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM);
591			}
592		}
593	}
594	if (self.spawnflags & PUSH_ONCE)
595		remove(self);
596};
597
598
599/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE
600Pushes the player
601*/
602void() trigger_push =
603{
604	InitTrigger ();
605	precache_sound ("ambience/windfly.wav");
606	self.touch = trigger_push_touch;
607	if (!self.speed)
608		self.speed = 1000;
609};
610
611//============================================================================
612
613void() trigger_monsterjump_touch =
614{
615	if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER )
616		return;
617
618// set XY even if not on ground, so the jump will clear lips
619	other.velocity_x = self.movedir_x * self.speed;
620	other.velocity_y = self.movedir_y * self.speed;
621
622	if ( !(other.flags & FL_ONGROUND) )
623		return;
624
625	other.flags = other.flags - FL_ONGROUND;
626
627	other.velocity_z = self.height;
628};
629
630/*QUAKED trigger_monsterjump (.5 .5 .5) ?
631Walking monsters that touch this will jump in the direction of the trigger's angle
632"speed" default to 200, the speed thrown forward
633"height" default to 200, the speed thrown upwards
634*/
635void() trigger_monsterjump =
636{
637	if (!self.speed)
638		self.speed = 200;
639	if (!self.height)
640		self.height = 200;
641	if (self.angles == '0 0 0')
642		self.angles = '0 360 0';
643	InitTrigger ();
644	self.touch = trigger_monsterjump_touch;
645};
646
647