1void() movetarget_f;
2void() t_movetarget;
3void() knight_walk1;
4void() knight_bow6;
5void() knight_bow1;
6void(entity etemp, entity stemp, entity stemp, float dmg) T_Damage;
7/*
8
9.enemy
10Will be world if not currently angry at anyone.
11
12.movetarget
13The next path spot to walk toward.  If .enemy, ignore .movetarget.
14When an enemy is killed, the monster will try to return to it's path.
15
16.huntt_ime
17Set to time + something when the player is in sight, but movement straight for
18him is blocked.  This causes the monster to use wall following code for
19movement direction instead of sighting on the player.
20
21.ideal_yaw
22A yaw angle of the intended direction, which will be turned towards at up
23to 45 deg / state.  If the enemy is in view and hunt_time is not active,
24this will be the exact line towards the enemy.
25
26.pausetime
27A monster will leave it's stand state and head towards it's .movetarget when
28time > .pausetime.
29
30walkmove(angle, speed) primitive is all or nothing
31*/
32
33
34//
35// globals
36//
37float	current_yaw;
38
39//
40// when a monster becomes angry at a player, that monster will be used
41// as the sight target the next frame so that monsters near that one
42// will wake up even if they wouldn't have noticed the player
43//
44entity	sight_entity;
45float	sight_entity_time;
46
47float(float v) anglemod =
48{
49	while (v >= 360)
50		v = v - 360;
51	while (v < 0)
52		v = v + 360;
53	return v;
54};
55
56/*
57==============================================================================
58
59MOVETARGET CODE
60
61The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target.
62
63targetname
64must be present.  The name of this movetarget.
65
66target
67the next spot to move to.  If not present, stop here for good.
68
69pausetime
70The number of seconds to spend standing or bowing for path_stand or path_bow
71
72==============================================================================
73*/
74
75
76void() movetarget_f =
77{
78	if (!self.targetname)
79		objerror ("monster_movetarget: no targetname");
80
81	self.solid = SOLID_TRIGGER;
82	self.touch = t_movetarget;
83	setsize (self, '-8 -8 -8', '8 8 8');
84
85};
86
87/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8)
88Monsters will continue walking towards the next target corner.
89*/
90void() path_corner =
91{
92	movetarget_f ();
93};
94
95
96/*
97=============
98t_movetarget
99
100Something has bumped into a movetarget.  If it is a monster
101moving towards it, change the next destination and continue.
102==============
103*/
104void() t_movetarget =
105{
106local entity	temp;
107
108	if (other.movetarget != self)
109		return;
110
111	if (other.enemy)
112		return;		// fighting, not following a path
113
114	temp = self;
115	self = other;
116	other = temp;
117
118	if (self.classname == "monster_ogre")
119		sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound
120
121//dprint ("t_movetarget\n");
122	self.goalentity = self.movetarget = find (world, targetname, other.target);
123	self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
124	if (!self.movetarget)
125	{
126		self.pausetime = time + 999999;
127		self.th_stand ();
128		return;
129	}
130};
131
132
133
134//============================================================================
135
136/*
137=============
138range
139
140returns the range catagorization of an entity reletive to self
1410	melee range, will become hostile even if back is turned
1421	visibility and infront, or visibility and show hostile
1432	infront and show hostile
1443	only triggered by damage
145=============
146*/
147float(entity targ) range =
148{
149local vector	spot1, spot2;
150local float		r;
151	spot1 = self.origin + self.view_ofs;
152	spot2 = targ.origin + targ.view_ofs;
153
154	r = vlen (spot1 - spot2);
155	if (r < 120)
156		return RANGE_MELEE;
157	if (r < 500)
158		return RANGE_NEAR;
159	if (r < 1000)
160		return RANGE_MID;
161	return RANGE_FAR;
162};
163
164/*
165=============
166visible
167
168returns 1 if the entity is visible to self, even if not infront ()
169=============
170*/
171float (entity targ) visible =
172{
173	local vector	spot1, spot2;
174
175	spot1 = self.origin + self.view_ofs;
176	spot2 = targ.origin + targ.view_ofs;
177	traceline (spot1, spot2, TRUE, self);	// see through other monsters
178
179	if (trace_inopen && trace_inwater)
180		return FALSE;			// sight line crossed contents
181
182	if (trace_fraction == 1)
183		return TRUE;
184	return FALSE;
185};
186
187
188/*
189=============
190infront
191
192returns 1 if the entity is in front (in sight) of self
193=============
194*/
195float(entity targ) infront =
196{
197	local vector	vec;
198	local float		dot;
199
200	makevectors (self.angles);
201	vec = normalize (targ.origin - self.origin);
202	dot = vec * v_forward;
203
204	if ( dot > 0.3)
205	{
206		return TRUE;
207	}
208	return FALSE;
209};
210
211
212//============================================================================
213
214/*
215===========
216ChangeYaw
217
218Turns towards self.ideal_yaw at self.yaw_speed
219Sets the global variable current_yaw
220Called every 0.1 sec by monsters
221============
222*/
223/*
224
225void() ChangeYaw =
226{
227	local float		ideal, move;
228
229//current_yaw = self.ideal_yaw;
230// mod down the current angle
231	current_yaw = anglemod( self.angles_y );
232	ideal = self.ideal_yaw;
233
234	if (current_yaw == ideal)
235		return;
236
237	move = ideal - current_yaw;
238	if (ideal > current_yaw)
239	{
240		if (move > 180)
241			move = move - 360;
242	}
243	else
244	{
245		if (move < -180)
246			move = move + 360;
247	}
248
249	if (move > 0)
250	{
251		if (move > self.yaw_speed)
252			move = self.yaw_speed;
253	}
254	else
255	{
256		if (move < 0-self.yaw_speed )
257			move = 0-self.yaw_speed;
258	}
259
260	current_yaw = anglemod (current_yaw + move);
261
262	self.angles_y = current_yaw;
263};
264
265*/
266
267
268//============================================================================
269
270void() HuntTarget =
271{
272	self.goalentity = self.enemy;
273	self.think = self.th_run;
274	self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
275	self.nextthink = time + 0.1;
276	SUB_AttackFinished (1);	// wait a while before first attack
277};
278
279void() SightSound =
280{
281local float	rsnd;
282
283	if (self.classname == "monster_ogre")
284		sound (self, CHAN_VOICE, "ogre/ogwake.wav", 1, ATTN_NORM);
285	else if (self.classname == "monster_knight")
286		sound (self, CHAN_VOICE, "knight/ksight.wav", 1, ATTN_NORM);
287	else if (self.classname == "monster_shambler")
288		sound (self, CHAN_VOICE, "shambler/ssight.wav", 1, ATTN_NORM);
289	else if (self.classname == "monster_demon1")
290		sound (self, CHAN_VOICE, "demon/sight2.wav", 1, ATTN_NORM);
291	else if (self.classname == "monster_wizard")
292		sound (self, CHAN_VOICE, "wizard/wsight.wav", 1, ATTN_NORM);
293	else if (self.classname == "monster_zombie")
294		sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_NORM);
295	else if (self.classname == "monster_dog")
296		sound (self, CHAN_VOICE, "dog/dsight.wav", 1, ATTN_NORM);
297	else if (self.classname == "monster_hell_knight")
298		sound (self, CHAN_VOICE, "hknight/sight1.wav", 1, ATTN_NORM);
299	else if (self.classname == "monster_tarbaby")
300		sound (self, CHAN_VOICE, "blob/sight1.wav", 1, ATTN_NORM);
301	else if (self.classname == "monster_vomit")
302		sound (self, CHAN_VOICE, "vomitus/v_sight1.wav", 1, ATTN_NORM);
303	else if (self.classname == "monster_enforcer")
304	{
305		rsnd = rint(random() * 3);
306		if (rsnd == 1)
307			sound (self, CHAN_VOICE, "enforcer/sight1.wav", 1, ATTN_NORM);
308		else if (rsnd == 2)
309			sound (self, CHAN_VOICE, "enforcer/sight2.wav", 1, ATTN_NORM);
310		else if (rsnd == 0)
311			sound (self, CHAN_VOICE, "enforcer/sight3.wav", 1, ATTN_NORM);
312		else
313			sound (self, CHAN_VOICE, "enforcer/sight4.wav", 1, ATTN_NORM);
314	}
315	else if (self.classname == "monster_army")
316		sound (self, CHAN_VOICE, "soldier/sight1.wav", 1, ATTN_NORM);
317	else if (self.classname == "monster_shalrath")
318		sound (self, CHAN_VOICE, "shalrath/sight.wav", 1, ATTN_NORM);
319};
320
321void() FoundTarget =
322{
323	if (self.enemy.classname == "player")
324	{	// let other monsters see this monster for a while
325		sight_entity = self;
326		sight_entity_time = time;
327	}
328
329	self.show_hostile = time + 1;		// wake up other monsters
330
331	SightSound ();
332	HuntTarget ();
333};
334
335/*
336===========
337FindTarget
338
339Self is currently not attacking anything, so try to find a target
340
341Returns TRUE if an enemy was sighted
342
343When a player fires a missile, the point of impact becomes a fakeplayer so
344that monsters that see the impact will respond as if they had seen the
345player.
346
347To avoid spending too much time, only a single client (or fakeclient) is
348checked each frame.  This means multi player games will have slightly
349slower noticing monsters.
350============
351*/
352float() FindTarget =
353{
354	local entity	client;
355	local float		r;
356
357// if the first spawnflag bit is set, the monster will only wake up on
358// really seeing the player, not another monster getting angry
359
360// spawnflags & 3 is a big hack, because zombie crucified used the first
361// spawn flag prior to the ambush flag, and I forgot about it, so the second
362// spawn flag works as well
363	if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) )
364	{
365		client = sight_entity;
366		if (client.enemy == self.enemy)
367			return;
368	}
369	else
370	{
371		client = checkclient ();
372		if (!client)
373			return FALSE;	// current check entity isn't in PVS
374	}
375
376	if (client == self.enemy)
377		return FALSE;
378
379	if (client.flags & FL_NOTARGET)
380		return FALSE;
381	if (client.items & IT_INVISIBILITY)
382		return FALSE;
383
384	r = range (client);
385	if (r == RANGE_FAR)
386		return FALSE;
387
388	if (!visible (client))
389		return FALSE;
390
391	if (r == RANGE_NEAR)
392	{
393		if (client.show_hostile < time && !infront (client))
394			return FALSE;
395	}
396	else if (r == RANGE_MID)
397	{
398		if ( /* client.show_hostile < time || */ !infront (client))
399			return FALSE;
400	}
401
402//
403// got one
404//
405	self.enemy = client;
406	if (self.enemy.classname != "player")
407	{
408		self.enemy = self.enemy.enemy;
409		if (self.enemy.classname != "player")
410		{
411			self.enemy = world;
412			return FALSE;
413		}
414	}
415
416	FoundTarget ();
417
418	return TRUE;
419};
420
421
422//=============================================================================
423
424void(float dist) ai_forward =
425{
426	walkmove (self.angles_y, dist);
427};
428
429void(float dist) ai_back =
430{
431	walkmove ( (self.angles_y+180), dist);
432};
433
434
435/*
436=============
437ai_pain
438
439stagger back a bit
440=============
441*/
442void(float dist) ai_pain =
443{
444	ai_back (dist);
445/*
446	local float	away;
447
448	away = anglemod (vectoyaw (self.origin - self.enemy.origin)
449	+ 180*(random()- 0.5) );
450
451	walkmove (away, dist);
452*/
453};
454
455/*
456=============
457ai_painforward
458
459stagger back a bit
460=============
461*/
462void(float dist) ai_painforward =
463{
464	walkmove (self.ideal_yaw, dist);
465};
466
467/*
468=============
469ai_walk
470
471The monster is walking it's beat
472=============
473*/
474void(float dist) ai_walk =
475{
476	local vector		mtemp;
477
478	movedist = dist;
479
480	if (self.classname == "monster_dragon")
481	{
482		movetogoal (dist);
483		return;
484	}
485	// check for noticing a player
486	if (FindTarget ())
487		return;
488
489	movetogoal (dist);
490};
491
492
493/*
494=============
495ai_stand
496
497The monster is staying in one place for a while, with slight angle turns
498=============
499*/
500void() ai_stand =
501{
502	if (FindTarget ())
503		return;
504
505	if (time > self.pausetime)
506	{
507		self.th_walk ();
508		return;
509	}
510
511// change angle slightly
512
513};
514
515/*
516=============
517ai_turn
518
519don't move, but turn towards ideal_yaw
520=============
521*/
522void() ai_turn =
523{
524	if (FindTarget ())
525		return;
526
527	ChangeYaw ();
528};
529
530//=============================================================================
531
532/*
533=============
534ChooseTurn
535=============
536*/
537void(vector dest3) ChooseTurn =
538{
539	local vector	dir, newdir;
540
541	dir = self.origin - dest3;
542
543	newdir_x = trace_plane_normal_y;
544	newdir_y = 0 - trace_plane_normal_x;
545	newdir_z = 0;
546
547	if (dir * newdir > 0)
548	{
549		dir_x = 0 - trace_plane_normal_y;
550		dir_y = trace_plane_normal_x;
551	}
552	else
553	{
554		dir_x = trace_plane_normal_y;
555		dir_y = 0 - trace_plane_normal_x;
556	}
557
558	dir_z = 0;
559	self.ideal_yaw = vectoyaw(dir);
560};
561
562/*
563============
564FacingIdeal
565
566============
567*/
568float() FacingIdeal =
569{
570	local	float	delta;
571
572	delta = anglemod(self.angles_y - self.ideal_yaw);
573	if (delta > 45 && delta < 315)
574		return FALSE;
575	return TRUE;
576};
577
578
579//=============================================================================
580
581float()	WizardCheckAttack;
582float()	DogCheckAttack;
583
584float() CheckAnyAttack =
585{
586	if (!enemy_vis)
587		return;
588	if (self.classname == "monster_army")
589		return SoldierCheckAttack ();
590	if (self.classname == "monster_ogre")
591		return OgreCheckAttack ();
592	if (self.classname == "monster_shambler")
593		return ShamCheckAttack ();
594	if (self.classname == "monster_demon1")
595		return DemonCheckAttack ();
596	if (self.classname == "monster_dog")
597		return DogCheckAttack ();
598	if (self.classname == "monster_wizard")
599		return WizardCheckAttack ();
600	return CheckAttack ();
601};
602
603
604/*
605=============
606ai_run_melee
607
608Turn and close until within an angle to launch a melee attack
609=============
610*/
611void() ai_run_melee =
612{
613	self.ideal_yaw = enemy_yaw;
614	ChangeYaw ();
615
616	if (FacingIdeal())
617	{
618		self.th_melee ();
619		self.attack_state = AS_STRAIGHT;
620	}
621};
622
623
624/*
625=============
626ai_run_missile
627
628Turn in place until within an angle to launch a missile attack
629=============
630*/
631void() ai_run_missile =
632{
633	self.ideal_yaw = enemy_yaw;
634	ChangeYaw ();
635	if (FacingIdeal())
636	{
637		self.th_missile ();
638		self.attack_state = AS_STRAIGHT;
639	}
640};
641
642
643/*
644=============
645ai_run_slide
646
647Strafe sideways, but stay at aproximately the same range
648=============
649*/
650void() ai_run_slide =
651{
652	local float	ofs;
653
654	self.ideal_yaw = enemy_yaw;
655	ChangeYaw ();
656	if (self.lefty)
657		ofs = 90;
658	else
659		ofs = -90;
660
661	if (walkmove (self.ideal_yaw + ofs, movedist))
662		return;
663
664	self.lefty = 1 - self.lefty;
665
666	walkmove (self.ideal_yaw - ofs, movedist);
667};
668
669
670/*
671=============
672ai_run
673
674The monster has an enemy it is trying to kill
675=============
676*/
677void(float dist) ai_run =
678{
679	local	vector	delta;
680	local	float	axis;
681	local	float	direct, ang_rint, ang_floor, ang_ceil;
682
683	movedist = dist;
684// see if the enemy is dead
685	if (self.enemy.health <= 0)
686	{
687		self.enemy = world;
688	// FIXME: look all around for other targets
689		if (self.oldenemy.health > 0)
690		{
691			self.enemy = self.oldenemy;
692			HuntTarget ();
693		}
694		else
695		{
696			if (self.movetarget)
697				self.th_walk ();
698			else
699				self.th_stand ();
700			return;
701		}
702	}
703
704	self.show_hostile = time + 1;		// wake up other monsters
705
706// check knowledge of enemy
707	enemy_vis = visible(self.enemy);
708	if (enemy_vis)
709		self.search_time = time + 5;
710
711// look for other coop players
712	if (coop && self.search_time < time)
713	{
714		if (FindTarget ())
715			return;
716	}
717
718	enemy_infront = infront(self.enemy);
719	enemy_range = range(self.enemy);
720	enemy_yaw = vectoyaw(self.enemy.origin - self.origin);
721
722	if (self.attack_state == AS_MISSILE)
723	{
724//dprint ("ai_run_missile\n");
725		ai_run_missile ();
726		return;
727	}
728	if (self.attack_state == AS_MELEE)
729	{
730//dprint ("ai_run_melee\n");
731		ai_run_melee ();
732		return;
733	}
734
735	if (CheckAnyAttack ())
736		return;					// beginning an attack
737
738	if (self.attack_state == AS_SLIDING)
739	{
740		ai_run_slide ();
741		return;
742	}
743
744// head straight in
745	movetogoal (dist);		// done in C code...
746};
747
748