1
2void() trigger_reactivate =
3{
4	self.solid = SOLID_TRIGGER;
5};
6
7//=============================================================================
8
9float SPAWNFLAG_NOMESSAGE = 1;
10float SPAWNFLAG_NOTOUCH = 1;
11
12// the wait time has passed, so set back up for another activation
13void() multi_wait =
14{
15	if (self.max_health)
16	{
17		self.health = self.max_health;
18		self.takedamage = DAMAGE_YES;
19		self.solid = SOLID_BBOX;
20	}
21};
22
23
24// the trigger was just touched/killed/used
25// self.enemy should be set to the activator so it can be held through a delay
26// so wait for the delay time before firing
27void() multi_trigger =
28{
29	if (self.nextthink > time)
30		return;         // already triggered
31
32	if (self.classname == "trigger_secret")
33	{
34		if (self.enemy.classname != "player")
35			return;
36		found_secrets = found_secrets + 1;
37		WriteByte (MSG_ALL, SVC_FOUNDSECRET);
38	}
39
40	if (self.noise)
41		sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
42
43// don't trigger again until reset
44	self.takedamage = DAMAGE_NO;
45
46	activator = self.enemy;
47
48	SUB_UseTargets();
49
50	if (self.wait > 0)
51	{
52		self.think = multi_wait;
53		self.nextthink = time + self.wait;
54	}
55	else
56	{	// we can't just remove (self) here, because this is a touch function
57		// called while C code is looping through area links...
58		self.touch = SUB_Null;
59		self.nextthink = time + 0.1;
60		self.think = SUB_Remove;
61	}
62};
63
64void() multi_killed =
65{
66	self.enemy = damage_attacker;
67	multi_trigger();
68};
69
70void() multi_use =
71{
72	self.enemy = activator;
73	multi_trigger();
74};
75
76void() multi_touch =
77{
78	//if (other.classname != "player")
79	if (!other.cantrigger)
80		return;
81
82// if the trigger has an angles field, check player's facing direction
83	if (self.movedir != '0 0 0')
84	{
85		makevectors (other.angles);
86		if (v_forward * self.movedir < 0)
87			return;		// not facing the right way
88	}
89
90	self.enemy = other;
91	multi_trigger ();
92};
93
94/*QUAKED trigger_multiple (.5 .5 .5) ? notouch
95Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
96If "delay" is set, the trigger waits some time after activating before firing.
97"wait" : Seconds between triggerings. (.2 default)
98If notouch is set, the trigger is only fired by other entities, not by touching.
99NOTOUCH has been obsoleted by trigger_relay!
100sounds
1011)	secret
1022)	beep beep
1033)	large switch
1044)
105set "message" to text string
106*/
107void() trigger_multiple =
108{
109	if (self.sounds == 1)
110	{
111		precache_sound ("misc/secret.wav");
112		self.noise = "misc/secret.wav";
113	}
114	else if (self.sounds == 2)
115	{
116		precache_sound ("misc/talk.wav");
117		self.noise = "misc/talk.wav";
118	}
119	else if (self.sounds == 3)
120	{
121		precache_sound ("misc/trigger1.wav");
122		self.noise = "misc/trigger1.wav";
123	}
124
125	if (!self.wait)
126		self.wait = 0.2;
127	self.use = multi_use;
128
129	InitTrigger ();
130
131	if (self.health)
132	{
133		if (self.spawnflags & SPAWNFLAG_NOTOUCH)
134			objerror ("trigger_multiple or trigger_once: health and notouch don't make sense\n");
135		self.max_health = self.health;
136		self.th_die = multi_killed;
137		self.takedamage = DAMAGE_YES;
138		self.solid = SOLID_BBOX;
139		setorigin (self, self.origin);	// make sure it links into the world
140	}
141	else if (!(self.spawnflags & SPAWNFLAG_NOTOUCH))
142		self.touch = multi_touch;
143};
144
145void() trigger_message2 =
146{
147	self.noise = "misc/talk.wav";
148	precache_sound (self.noise);
149
150	if (!self.wait)
151		self.wait = 0.2;
152	self.use = multi_use;
153
154	if (self.movedir == '0 0 0')
155	if (self.angles != '0 0 0')
156		SetMovedir ();
157	self.solid = SOLID_TRIGGER;
158	self.movetype = MOVETYPE_NONE;
159	setsize (self, self.mins, self.maxs);
160
161	self.touch = multi_touch;
162};
163
164
165/*QUAKED trigger_once (.5 .5 .5) ? notouch
166Variable 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
167"targetname".  If "health" is set, the trigger must be killed to activate.
168If notouch is set, the trigger is only fired by other entities, not by touching.
169if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
170if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
171sounds
1721)	secret
1732)	beep beep
1743)	large switch
1754)
176set "message" to text string
177*/
178void() trigger_once =
179{
180	self.wait = -1;
181	trigger_multiple();
182};
183
184//=============================================================================
185
186/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
187This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
188*/
189void() trigger_relay =
190{
191	self.use = SUB_UseTargets;
192};
193
194
195//=============================================================================
196
197/*QUAKED trigger_secret (.5 .5 .5) ?
198secret counter trigger
199sounds
2001)	secret
2012)	beep beep
2023)
2034)
204set "message" to text string
205*/
206void() trigger_secret =
207{
208	total_secrets = total_secrets + 1;
209	self.wait = -1;
210	if (!self.message)
211		self.message = "You found a secret area!";
212	if (!self.sounds)
213		self.sounds = 1;
214
215	if (self.sounds == 1)
216	{
217		precache_sound ("misc/secret.wav");
218		self.noise = "misc/secret.wav";
219	}
220	else if (self.sounds == 2)
221	{
222		precache_sound ("misc/talk.wav");
223		self.noise = "misc/talk.wav";
224	}
225
226	trigger_multiple ();
227};
228
229//=============================================================================
230
231
232void() counter_use =
233{
234	self.count = self.count - 1;
235	if (self.count < 0)
236		return;
237
238	if (self.count != 0)
239	{
240		if ((activator.flags & FL_CLIENT) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
241		{
242			if (self.count >= 4)
243				centerprint (activator, "There are more to go...");
244			else if (self.count == 3)
245				centerprint (activator, "Only 3 more to go...");
246			else if (self.count == 2)
247				centerprint (activator, "Only 2 more to go...");
248			else
249				centerprint (activator, "Only 1 more to go...");
250		}
251		return;
252	}
253
254	if ((activator.flags & FL_CLIENT) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
255		centerprint(activator, "Sequence completed!");
256	self.enemy = activator;
257	multi_trigger ();
258};
259
260/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
261Acts as an intermediary for an action that takes multiple inputs.
262
263If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
264
265After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
266*/
267void() trigger_counter =
268{
269	self.wait = -1;
270	if (!self.count)
271		self.count = 2;
272
273	self.use = counter_use;
274};
275
276
277/*
278==============================================================================
279
280TELEPORT TRIGGERS
281
282==============================================================================
283*/
284
285float PLAYER_ONLY	= 1;
286float SILENT = 2;
287
288void() play_teleport =
289{
290	local float v;
291	local string tmpstr;
292
293	v = random() * 5;
294	if (v < 1)
295		tmpstr = "misc/r_tele1.wav";
296	else if (v < 2)
297		tmpstr = "misc/r_tele2.wav";
298	else if (v < 3)
299		tmpstr = "misc/r_tele3.wav";
300	else if (v < 4)
301		tmpstr = "misc/r_tele4.wav";
302	else
303		tmpstr = "misc/r_tele5.wav";
304
305	sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM);
306	remove (self);
307};
308
309void(vector org) spawn_tfog =
310{
311	local entity s;
312	s = spawn ();
313	s.nextthink = time + 0.2;
314	s.think = play_teleport;
315	setorigin(s, org);
316
317	te_teleport(org);
318};
319
320void(entity targ, entity attacker, string dmsg, float dtype) Obituary_Telefrag =
321{
322	if (dtype == DTYPE_TEAMKILL)
323	{
324		deathstring1 = targ.netname;
325		deathstring2 = " was telefragged by his teammate ";
326		deathstring3 = attacker.netname;
327		deathstring4 = "";
328	}
329	else
330	{
331		deathstring1 = targ.netname;
332		deathstring2 = " was telefragged by ";
333		deathstring3 = attacker.netname;
334		deathstring4 = "";
335	}
336};
337
338void(entity targ, entity attacker, string dmsg, float dtype) Obituary_TriedToTelefrag =
339{
340	if (dtype == DTYPE_TEAMKILL)
341	{
342		deathstring1 = targ.netname;
343		deathstring2 = "'s telefrag was deflected by his teammate ";
344		deathstring3 = attacker.netname;
345		deathstring4 = "";
346	}
347	else
348	{
349		deathstring1 = targ.netname;
350		deathstring2 = "'s telefrag was deflected by ";
351		deathstring3 = attacker.netname;
352		deathstring4 = "";
353	}
354};
355
356float chthonisdead; // set when chthon dies
357void() tdeath_touch =
358{
359	if (chthonisdead) // fix the chthon gib scene
360	if ((self.owner.health < 1) || (self.owner.flags & FL_MONSTER))
361	{
362		T_Damage (self.owner, self, world, 0, 0, " gibbed", DT_TELEFRAG, self.owner.origin, '0 0 0', Obituary_Generic);
363		return;
364	}
365	if (other == self.owner)
366		return;
367	// so teleporting shots etc can't telefrag
368	if (!self.owner.takedamage)
369		return;
370	if (!other.takedamage)
371		return;
372	//if (other.solid == SOLID_TRIGGER) // big bug in quake engine...  crashs if other is removed during a collision
373	//	return;
374	if ((self.owner.classname == "player") && (self.owner.health >= 1))
375		T_Damage (other, self, self.owner, 0, 0, "TELEFRAG", DT_TELEFRAG, other.origin, '0 0 0', Obituary_Telefrag);
376	else if (other.health < 1) // corpses gib
377		T_Damage (other, self, self.owner, 0, 0, "TELEFRAG", DT_TELEFRAG, other.origin, '0 0 0', Obituary_Telefrag);
378	else // dead bodies and monsters gib themselves instead of telefragging
379		T_Damage (self.owner, self, other, 0, 0, "TRIEDTOTELEFRAG", DT_TELEFRAG, self.owner.origin, '0 0 0', Obituary_TriedToTelefrag);
380};
381
382// org2 is where they will return to if the teleport fails
383void(vector org, entity death_owner, vector org2) spawn_tdeath =
384{
385	local entity death;
386
387	death = spawn();
388//	death.classname = "teledeath";
389	death.movetype = MOVETYPE_NONE;
390	death.solid = SOLID_TRIGGER;
391	death.angles = '0 0 0';
392	death.dest2 = org2;
393	setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1');
394	setorigin (death, org);
395	death.touch = tdeath_touch;
396	death.nextthink = time + 0.2;
397	death.think = SUB_Remove;
398	death.owner = death_owner;
399
400	force_retouch = 2;		// make sure even still objects get hit
401};
402
403.float monsterawaitingteleport;
404
405void() teleport_changevelocityent =
406{
407	if (self.owner.solid) // make sure it still exists
408		self.owner.velocity = self.dest;
409	remove(self);
410};
411
412void() teleport_touch =
413{
414	local entity t;
415
416	//if (!other.takedamage)
417	//        return;
418	if (self.targetname)
419	if (self.nextthink < time)
420	{
421		if (world.worldtype < 3) // normal quake maps
422		if (other.flags & FL_MONSTER) // it's a monster
423		if ((self.spawnflags & PLAYER_ONLY) == 0) // not player only
424			other.monsterawaitingteleport = TRUE; // freeze it
425		return;         // not fired yet
426	}
427
428	if (self.spawnflags & PLAYER_ONLY)
429	if (other.classname != "player")
430		return;
431
432	// only teleport living creatures
433	if (other.health < 1)
434		return;
435	if (other.solid != SOLID_SLIDEBOX)
436		return;
437	if (other.movetype == MOVETYPE_NONE || other.movetype == MOVETYPE_PUSH || other.solid == SOLID_NOT)
438		return;
439
440	other.monsterawaitingteleport = FALSE; // unfreeze monster AI
441
442	SUB_UseTargets ();
443
444	// put a tfog where the player was
445	spawn_tfog (other.origin);
446
447	t = find(world, targetname, self.target);
448	if (!t)
449		objerror ("trigger_teleport: couldn't find target");
450
451	// spawn a tfog flash in front of the destination
452	makevectors (t.mangle);
453	//org = t.origin + 32 * v_forward;
454
455	spawn_tfog (t.origin); //org);
456	spawn_tdeath(t.origin, other, other.origin);
457
458	// move the player and lock him down for a little while
459	setorigin (other, t.origin);
460	// LordHavoc: made all things behave the same
461	newmis = spawn();
462	newmis.think = teleport_changevelocityent;
463	newmis.nextthink = time + 0.01;
464	newmis.owner = other;
465	//if (other.teleportsavevelocity)
466	//	other.velocity = other.teleportsavevelocity;
467	if (other.health < 1)
468	{
469		other.origin = t.origin;
470		other.velocity = (v_forward * other.velocity_x) - (v_right * other.velocity_y) + (v_up * other.velocity_z) + self.dest;
471		newmis.velocity = other.velocity;
472		other.flags = other.flags - (other.flags & FL_ONGROUND);
473		return;
474	}
475	other.velocity = t.dest;
476	newmis.velocity = other.velocity;
477
478	other.angles = t.mangle;
479	if (other.classname == "player")
480	{
481		other.fixangle = 1;		// turn this way immediately
482
483		other.teleport_time = time + 0.5;
484		//other.velocity = t.dest;
485	}
486	other.flags = other.flags - (other.flags & FL_ONGROUND);
487};
488
489/*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32)
490This is the destination
491marker for a teleporter.
492
493It should have a
494"targetname" field with
495the same value as a
496teleporter's "target"
497field.
498
499Keys:
500"dest"
501 velocity on exiting
502 teleport, nice for
503 tricks.
504 default: normal
505 (throws player out
506  a bit)
507*/
508void() info_teleport_destination =
509{
510	// this does nothing, just serves as a target spot
511	self.mangle = self.angles;
512	self.angles = '0 0 0';
513	self.model = "";
514	setorigin(self, self.origin + '0 0 27');
515	if (!self.targetname)
516		objerror ("no targetname");
517	if (self.dest == '0 0 0') // the usual
518	{
519		makevectors(self.mangle);
520		self.dest = v_forward * 200; //300;
521	}
522};
523
524void() teleport_use =
525{
526	self.nextthink = time + 0.2;
527	force_retouch = 2;              // make sure even still objects get hit
528	self.think = SUB_Null;
529};
530
531/*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT
532Any 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.
533
534If the trigger_teleport has a targetname, it will only teleport entities when it has been fired.
535*/
536void() trigger_teleport =
537{
538	self.mdl = self.model;
539	if (deathmatch) // teleporters teleport shots in DM :)
540	{
541		if (self.targetname)
542			InitTrigger ();
543		else
544			InitSolidBSPTrigger ();
545	}
546	else
547		InitTrigger ();
548	self.touch = teleport_touch;
549	// find the destination
550	if (!self.target)
551		objerror ("trigger_teleport: no target");
552	self.use = teleport_use;
553
554	if (!(self.spawnflags & SILENT))
555	{
556		precache_sound ("ambience/hum1.wav");
557		ambientsound ((self.absmin + self.absmax)*0.5, "ambience/hum1.wav",0.5 , ATTN_STATIC);
558	}
559};
560
561void() trigger_teleport2 =
562{
563	if (self.movedir == '0 0 0')
564	if (self.angles != '0 0 0')
565		SetMovedir ();
566	self.solid = SOLID_TRIGGER;
567	self.movetype = MOVETYPE_NONE;
568	setsize (self, self.mins, self.maxs);
569
570	self.touch = teleport_touch;
571	if (!self.target)
572		objerror ("trigger_teleport2: no target");
573	self.use = teleport_use;
574
575	if (!(self.spawnflags & SILENT))
576	{
577		precache_sound ("ambience/hum1.wav");
578		ambientsound (self.origin, "ambience/hum1.wav", 0.5, ATTN_STATIC);
579	}
580};
581
582/*
583==============================================================================
584
585trigger_setskill
586
587==============================================================================
588*/
589
590void() trigger_skill_touch =
591{
592	if (other.classname != "player")
593		return;
594
595	cvar_set ("skill", self.message);
596};
597
598/*QUAKED trigger_setskill (.5 .5 .5) ?
599sets skill level to the value of "message".
600Only used on start map.
601*/
602void() trigger_setskill =
603{
604	InitTrigger ();
605	self.touch = trigger_skill_touch;
606};
607
608void() trigger_setskill2 =
609{
610	if (self.movedir == '0 0 0')
611	if (self.angles != '0 0 0')
612		SetMovedir ();
613	self.solid = SOLID_TRIGGER;
614	self.movetype = MOVETYPE_NONE;
615	setsize (self, self.mins, self.maxs);
616	self.touch = trigger_skill_touch;
617};
618
619/*
620==============================================================================
621
622ONLY REGISTERED TRIGGERS
623
624==============================================================================
625*/
626
627void() trigger_onlyregistered_touch =
628{
629	if (other.classname != "player")
630		return;
631	if (self.attack_finished > time)
632		return;
633
634	self.attack_finished = time + 2;
635	if (cvar("registered"))
636	{
637		self.message = "";
638		SUB_UseTargets ();
639		remove (self);
640	}
641	else
642	{
643		if (self.message != "")
644		{
645			if (other.flags & FL_CLIENT)
646				centerprint (other, self.message);
647			sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
648		}
649	}
650};
651
652/*QUAKED trigger_onlyregistered (.5 .5 .5) ?
653Only fires if playing the registered version, otherwise prints the message
654*/
655void() trigger_onlyregistered =
656{
657	precache_sound ("misc/talk.wav");
658	InitTrigger ();
659	self.touch = trigger_onlyregistered_touch;
660};
661
662//============================================================================
663
664
665void( entity ent, float amount ) hurt_setdamage =
666{
667	ent.dmg = amount;
668	if (!amount)
669		ent.solid = SOLID_NOT;
670	else
671		ent.solid = SOLID_TRIGGER;
672	ent.nextthink = -1;
673};
674
675void() hurt_on =
676{
677	self.solid = SOLID_TRIGGER;
678	self.nextthink = -1;
679};
680
681void() hurt_touch =
682{
683	if (other.takedamage)
684	{
685		self.solid = SOLID_NOT;
686		T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_HURT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
687		self.think = hurt_on;
688		self.nextthink = time + 1;
689		if (self.cnt > 0)
690		{
691			self.cnt = self.cnt - 1;
692			if (self.cnt == 0)
693			{
694				self.touch = SUB_Null;
695				self.nextthink = time + 0.1;
696				self.think = SUB_Remove;
697			}
698		}
699	}
700	return;
701};
702
703/*QUAKED trigger_hurt (.5 .5 .5) ?
704Anything touching will be hurt.
705
706Keys:
707"dmg"
708 damage amount
709 default: 5
710"cnt"
711 how many times to trigger,
712 default: unlimited
713*/
714void() trigger_hurt =
715{
716	if (!self.deathtype) // map makers can override this
717		self.deathtype = " died";
718	InitTrigger ();
719	self.touch = hurt_touch;
720	if (!self.dmg)
721		self.dmg = 5;
722};
723
724//============================================================================
725
726float PUSH_ONCE = 1;
727float PUSH_SILENT = 2;
728
729void() target_position = {};
730
731void() trigger_push_touch =
732{
733	local float flighttime, dist, grav;
734	local vector org, v;
735	if (other.movetype == MOVETYPE_NONE || other.movetype == MOVETYPE_PUSH)
736		return;
737	other.flags = other.flags - (other.flags & FL_ONGROUND);
738	other.velocity = self.movedir;
739	if (self.enemy)
740	{
741		/*
742		// old code for hitting a target landing spot
743		if (self.wait) // flight time
744			t = self.wait;
745		else
746			t = vlen(self.enemy.origin - other.origin) / self.speed - 0.1;
747		// direct path, with boost to counter gravity
748		other.velocity = (self.enemy.origin - other.origin) * (1 / t) + '0 0 1' * (t * t * cvar("sv_gravity"));
749		*/
750
751		// start point
752		org = other.origin;
753
754		// figure out how long it will take to hit the point considering gravity
755		grav = cvar("sv_gravity");
756		flighttime = sqrt((self.enemy.origin_z - org_z) / (0.5 * grav));
757		if (!flighttime)
758		{
759			remove(self);
760			return;
761		}
762
763		// how far in X and Y to move
764		v = (self.enemy.origin - org);
765		v_z = 0;
766		dist = vlen(v);
767
768		// finally calculate the velocity
769		other.velocity = normalize(v) * (dist / flighttime);
770		other.velocity_z = flighttime * grav;
771	}
772	else
773		other.velocity = self.movedir;
774	if (self.noise != "")
775	if (other.classname == "player" || (other.flags & FL_MONSTER))
776	if (other.fly_sound < time)
777	{
778		if (self.target)
779		{
780			other.fly_sound = time + 0.2;
781			if (game == GAME_NEXUIZ)
782				sound (other, CHAN_BODY, "level/jumppad.wav", 1, ATTN_NORM);
783			else
784				sound (other, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
785		}
786		else
787		{
788			other.fly_sound = time + 1.5;
789			sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM);
790		}
791	}
792	if (self.spawnflags & PUSH_ONCE)
793	{
794		self.touch = SUB_Null;
795		self.think = SUB_Remove;
796		self.nextthink = time;
797	}
798};
799
800void() trigger_push_findtarget =
801{
802	// find the target
803	self.enemy = find(world, targetname, self.target);
804	if (!self.enemy)
805		objerror("trigger_push: target not found\n");
806};
807
808/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE SILENT
809Improved version of normal trigger_push.
810
811Pushes anything that touches it,
812or acts as a jump pad, hitting "target"
813at the apogee (peak) of the jump.
814
815Target should be an info_notnull.
816
817Flags:
818"PUSH_ONCE"
819 removes itself after push
820"SILENT"
821 no 'windtunnel' noise
822Keys:
823"angles"
824 direction to push.
825 (not for jumppad)
826"speed"
827 how fast.
828 (not for jumppad)
829"movedir"
830 direction of push,
831 this is turned into
832 a direction. (no speed)
833 (not for jumppad)
834"target"
835 acts as jump pad, hits this entity
836*/
837void() trigger_push =
838{
839	InitTrigger ();
840	if (!(self.spawnflags & PUSH_SILENT))
841	{
842		if (self.target)
843		{
844			if (game == GAME_NEXUIZ)
845				self.noise = "level/jumppad.wav";
846			else
847				self.noise = "player/plyrjmp8.wav";
848		}
849		else
850			self.noise = "ambience/windfly.wav";
851	}
852	if (self.noise != "")
853		precache_sound (self.noise);
854	self.touch = trigger_push_touch;
855	if (!self.speed)
856		self.speed = 1000;
857	self.movedir = self.movedir * self.speed * 10; // why the * 10 I dunno...
858	if (self.target)
859	{
860		self.think = trigger_push_findtarget;
861		self.nextthink = time + 0.2;
862	}
863};
864
865//============================================================================
866
867void() trigger_monsterjump_touch =
868{
869	if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER )
870		return;
871
872// set XY even if not on ground, so the jump will clear lips
873	other.velocity_x = self.movedir_x * self.speed;
874	other.velocity_y = self.movedir_y * self.speed;
875
876	if ( !(other.flags & FL_ONGROUND) )
877		return;
878
879	other.flags = other.flags - FL_ONGROUND;
880
881	other.velocity_z = self.height;
882};
883
884/*QUAKED trigger_monsterjump (.5 .5 .5) ?
885Walking monsters touching
886this will jump in the
887direction of the angle.
888
889Keys:
890"speed"
891 horizontal speed
892 default: 200
893"height"
894 upward speed
895 default: 200
896*/
897void() trigger_monsterjump =
898{
899	if (!self.speed)
900		self.speed = 200;
901	if (!self.height)
902		self.height = 200;
903	if (self.angles == '0 0 0')
904		self.angles = '0 360 0';
905	InitTrigger ();
906	self.touch = trigger_monsterjump_touch;
907};
908
909