1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 20?? COR Entertainment, LLC.
4 
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 
14 See the GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "g_local.h"
26 
AttackFinished(edict_t * self,float time)27 void AttackFinished (edict_t *self, float time)
28 {
29 	self->monsterinfo.attack_finished = level.time + time;
30 }
31 
32 
M_CheckGround(edict_t * ent)33 void M_CheckGround (edict_t *ent)
34 {
35 	vec3_t		point;
36 	trace_t		trace;
37 
38 	if (ent->flags & (FL_SWIM|FL_FLY))
39 		return;
40 
41 	if (ent->velocity[2] > 100)
42 	{
43 		ent->groundentity = NULL;
44 		return;
45 	}
46 
47 // if the hull point one-quarter unit down is solid the entity is on ground
48 	point[0] = ent->s.origin[0];
49 	point[1] = ent->s.origin[1];
50 	point[2] = ent->s.origin[2] - 0.25;
51 
52 	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
53 
54 	// check steepness
55 	if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
56 	{
57 		ent->groundentity = NULL;
58 		return;
59 	}
60 
61 //	ent->groundentity = trace.ent;
62 //	ent->groundentity_linkcount = trace.ent->linkcount;
63 //	if (!trace.startsolid && !trace.allsolid)
64 //		VectorCopy (trace.endpos, ent->s.origin);
65 	if (!trace.startsolid && !trace.allsolid)
66 	{
67 		VectorCopy (trace.endpos, ent->s.origin);
68 		ent->groundentity = trace.ent;
69 		ent->groundentity_linkcount = trace.ent->linkcount;
70 		ent->velocity[2] = 0;
71 	}
72 }
73 
74 
M_CatagorizePosition(edict_t * ent)75 void M_CatagorizePosition (edict_t *ent)
76 {
77 	vec3_t		point;
78 	int			cont;
79 
80 //
81 // get waterlevel
82 //
83 	point[0] = ent->s.origin[0];
84 	point[1] = ent->s.origin[1];
85 	point[2] = ent->s.origin[2] + ent->mins[2] + 1;
86 	cont = gi.pointcontents (point);
87 
88 	if (!(cont & MASK_WATER))
89 	{
90 		ent->waterlevel = 0;
91 		ent->watertype = 0;
92 		return;
93 	}
94 
95 	ent->watertype = cont;
96 	ent->waterlevel = 1;
97 	point[2] += 26;
98 	cont = gi.pointcontents (point);
99 	if (!(cont & MASK_WATER))
100 		return;
101 
102 	ent->waterlevel = 2;
103 	point[2] += 22;
104 	cont = gi.pointcontents (point);
105 	if (cont & MASK_WATER)
106 		ent->waterlevel = 3;
107 }
108 
109 
M_WorldEffects(edict_t * ent)110 void M_WorldEffects (edict_t *ent)
111 {
112 	int		dmg;
113 
114 	if (ent->health > 0)
115 	{
116 		if (!(ent->flags & FL_SWIM))
117 		{
118 			if (ent->waterlevel < 3)
119 			{
120 				ent->air_finished = level.time + 12;
121 			}
122 			else if (ent->air_finished < level.time)
123 			{	// drown!
124 				if (ent->pain_debounce_time < level.time)
125 				{
126 					dmg = 2 + 2 * floor(level.time - ent->air_finished);
127 					if (dmg > 15)
128 						dmg = 15;
129 					T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
130 					ent->pain_debounce_time = level.time + 1;
131 				}
132 			}
133 		}
134 		else
135 		{
136 			if (ent->waterlevel > 0)
137 			{
138 				ent->air_finished = level.time + 9;
139 			}
140 			else if (ent->air_finished < level.time)
141 			{	// suffocate!
142 				if (ent->pain_debounce_time < level.time)
143 				{
144 					dmg = 2 + 2 * floor(level.time - ent->air_finished);
145 					if (dmg > 15)
146 						dmg = 15;
147 					T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
148 					ent->pain_debounce_time = level.time + 1;
149 				}
150 			}
151 		}
152 	}
153 
154 	if (ent->waterlevel == 0)
155 	{
156 		if (ent->flags & FL_INWATER)
157 		{
158 			gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
159 			ent->flags &= ~FL_INWATER;
160 		}
161 		return;
162 	}
163 
164 	if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
165 	{
166 		if (ent->damage_debounce_time < level.time)
167 		{
168 			ent->damage_debounce_time = level.time + 0.2;
169 			T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
170 		}
171 	}
172 	if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
173 	{
174 		if (ent->damage_debounce_time < level.time)
175 		{
176 			ent->damage_debounce_time = level.time + 1;
177 			T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
178 		}
179 	}
180 
181 	if ( !(ent->flags & FL_INWATER) )
182 	{
183 		if (!(ent->svflags & SVF_DEADMONSTER))
184 		{
185 			if (ent->watertype & CONTENTS_LAVA)
186 				if (random() <= 0.5)
187 					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
188 				else
189 					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
190 			else if (ent->watertype & CONTENTS_SLIME)
191 				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
192 			else if (ent->watertype & CONTENTS_WATER)
193 				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
194 		}
195 
196 		ent->flags |= FL_INWATER;
197 		ent->damage_debounce_time = 0;
198 	}
199 }
200 
201 
M_droptofloor(edict_t * ent)202 void M_droptofloor (edict_t *ent)
203 {
204 	vec3_t		end;
205 	trace_t		trace;
206 
207 	ent->s.origin[2] += 1;
208 	VectorCopy (ent->s.origin, end);
209 	end[2] -= 256;
210 
211 	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
212 
213 	if (trace.fraction == 1 || trace.allsolid)
214 		return;
215 
216 	VectorCopy (trace.endpos, ent->s.origin);
217 
218 	gi.linkentity (ent);
219 	M_CheckGround (ent);
220 	M_CatagorizePosition (ent);
221 }
222 
223 
M_SetEffects(edict_t * ent)224 void M_SetEffects (edict_t *ent)
225 {
226 	ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
227 	ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
228 
229 	if (ent->monsterinfo.aiflags & AI_RESURRECTING)
230 	{
231 		ent->s.effects |= EF_COLOR_SHELL;
232 		ent->s.renderfx |= RF_SHELL_BLUE;
233 	}
234 
235 	if (ent->health <= 0)
236 		return;
237 }
238 
239 
M_MoveFrame(edict_t * self)240 void M_MoveFrame (edict_t *self)
241 {
242 	mmove_t	*move;
243 	int		index;
244 
245 	move = self->monsterinfo.currentmove;
246 	self->nextthink = level.time + FRAMETIME;
247 
248 	if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
249 	{
250 		self->s.frame = self->monsterinfo.nextframe;
251 		self->monsterinfo.nextframe = 0;
252 	}
253 	else
254 	{
255 		if (self->s.frame == move->lastframe)
256 		{
257 			if (move->endfunc)
258 			{
259 				move->endfunc (self);
260 
261 				// regrab move, endfunc is very likely to change it
262 				move = self->monsterinfo.currentmove;
263 
264 				// check for death
265 				if (self->svflags & SVF_DEADMONSTER)
266 					return;
267 			}
268 		}
269 
270 		if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
271 		{
272 			self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
273 			self->s.frame = move->firstframe;
274 		}
275 		else
276 		{
277 			if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
278 			{
279 				self->s.frame++;
280 				if (self->s.frame > move->lastframe)
281 					self->s.frame = move->firstframe;
282 			}
283 		}
284 	}
285 
286 	index = self->s.frame - move->firstframe;
287 	if (move->frame[index].aifunc)
288 	{
289 		if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
290 			move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
291 		else
292 			move->frame[index].aifunc (self, 0);
293 	}
294 
295 	if (move->frame[index].thinkfunc)
296 		move->frame[index].thinkfunc (self);
297 }
298 
299 
monster_think(edict_t * self)300 void monster_think (edict_t *self)
301 {
302 	M_MoveFrame (self);
303 	if (self->linkcount != self->monsterinfo.linkcount)
304 	{
305 		self->monsterinfo.linkcount = self->linkcount;
306 		M_CheckGround (self);
307 	}
308 	M_CatagorizePosition (self);
309 	M_WorldEffects (self);
310 	M_SetEffects (self);
311 }
312 
313 
314 /*
315 ================
316 monster_use
317 
318 Using a monster makes it angry at the current activator
319 ================
320 */
monster_use(edict_t * self,edict_t * other,edict_t * activator)321 void monster_use (edict_t *self, edict_t *other, edict_t *activator)
322 {
323 	if (self->enemy)
324 		return;
325 	if (self->health <= 0)
326 		return;
327 	if (activator->flags & FL_NOTARGET)
328 		return;
329 	if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
330 		return;
331 
332 // delay reaction so if the monster is teleported, its sound is still heard
333 	self->enemy = activator;
334 	FoundTarget (self);
335 }
336 
337 
338 void monster_start_go (edict_t *self);
339 
340 
monster_triggered_spawn(edict_t * self)341 void monster_triggered_spawn (edict_t *self)
342 {
343 	self->s.origin[2] += 1;
344 	KillBox (self);
345 
346 	self->solid = SOLID_BBOX;
347 	self->movetype = MOVETYPE_STEP;
348 	self->svflags &= ~SVF_NOCLIENT;
349 	self->air_finished = level.time + 12;
350 	gi.linkentity (self);
351 
352 	monster_start_go (self);
353 
354 	if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
355 	{
356 		FoundTarget (self);
357 	}
358 	else
359 	{
360 		self->enemy = NULL;
361 	}
362 }
363 
monster_triggered_spawn_use(edict_t * self,edict_t * other,edict_t * activator)364 void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
365 {
366 	// we have a one frame delay here so we don't telefrag the guy who activated us
367 	self->think = monster_triggered_spawn;
368 	self->nextthink = level.time + FRAMETIME;
369 	if (activator->client)
370 		self->enemy = activator;
371 	self->use = monster_use;
372 }
373 
monster_triggered_start(edict_t * self)374 void monster_triggered_start (edict_t *self)
375 {
376 	self->solid = SOLID_NOT;
377 	self->movetype = MOVETYPE_NONE;
378 	self->svflags |= SVF_NOCLIENT;
379 	self->nextthink = 0;
380 	self->use = monster_triggered_spawn_use;
381 }
382 
383 
384 /*
385 ================
386 monster_death_use
387 
388 When a monster dies, it fires all of its targets with the current
389 enemy as activator.
390 ================
391 */
monster_death_use(edict_t * self)392 void monster_death_use (edict_t *self)
393 {
394 	self->flags &= ~(FL_FLY|FL_SWIM);
395 	self->monsterinfo.aiflags &= AI_GOOD_GUY;
396 
397 	if (self->item)
398 	{
399 		Drop_Item (self, self->item);
400 		self->item = NULL;
401 	}
402 
403 	if (self->deathtarget)
404 		self->target = self->deathtarget;
405 
406 	if (!self->target)
407 		return;
408 
409 	G_UseTargets (self, self->enemy);
410 }
411 
412 
413 //============================================================================
414 
monster_start(edict_t * self)415 qboolean monster_start (edict_t *self)
416 {
417 
418 	if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
419 	{
420 		self->spawnflags &= ~4;
421 		self->spawnflags |= 1;
422 //		gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
423 	}
424 
425 	if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
426 		level.total_monsters++;
427 
428 	self->nextthink = level.time + FRAMETIME;
429 	self->svflags |= SVF_MONSTER;
430 	self->takedamage = DAMAGE_AIM;
431 	self->air_finished = level.time + 12;
432 	self->use = monster_use;
433 	self->max_health = self->health;
434 	self->clipmask = MASK_MONSTERSOLID;
435 
436 	self->s.skinnum = 0;
437 	self->deadflag = DEAD_NO;
438 	self->svflags &= ~SVF_DEADMONSTER;
439 
440 	if (!self->monsterinfo.checkattack)
441 		self->monsterinfo.checkattack = M_CheckAttack;
442 	VectorCopy (self->s.origin, self->s.old_origin);
443 
444 	if (st.item)
445 	{
446 		self->item = FindItemByClassname (st.item);
447 		if (!self->item)
448 			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
449 	}
450 
451 	// randomize what frame they start on
452 	if (self->monsterinfo.currentmove)
453 		self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
454 
455 	return true;
456 }
457 
monster_start_go(edict_t * self)458 void monster_start_go (edict_t *self)
459 {
460 	vec3_t	v;
461 
462 	if (self->health <= 0)
463 		return;
464 
465 	// check for target to combat_point and change to combattarget
466 	if (self->target)
467 	{
468 		qboolean	notcombat;
469 		qboolean	fixup;
470 		edict_t		*target;
471 
472 		target = NULL;
473 		notcombat = false;
474 		fixup = false;
475 		while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
476 		{
477 			if (strcmp(target->classname, "point_combat") == 0)
478 			{
479 				self->combattarget = self->target;
480 				fixup = true;
481 			}
482 			else
483 			{
484 				notcombat = true;
485 			}
486 		}
487 		if (notcombat && self->combattarget)
488 			gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
489 		if (fixup)
490 			self->target = NULL;
491 	}
492 
493 	// validate combattarget
494 	if (self->combattarget)
495 	{
496 		edict_t		*target;
497 
498 		target = NULL;
499 
500 		while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
501 		{
502 			if (strcmp(target->classname, "point_combat") != 0)
503 			{
504 				gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
505 					self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
506 					self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
507 					(int)target->s.origin[2]);
508 			}
509 		}
510 	}
511 
512 	if (self->target)
513 	{
514 		self->goalentity = self->movetarget = G_PickTarget(self->target);
515 		if (!self->movetarget)
516 		{
517 			gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
518 			self->target = NULL;
519 			self->monsterinfo.pausetime = 100000000;
520 			self->monsterinfo.stand (self);
521 		}
522 		else if (strcmp (self->movetarget->classname, "path_corner") == 0)
523 		{
524 			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
525 			self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
526 			self->monsterinfo.walk (self);
527 			self->target = NULL;
528 		}
529 		else
530 		{
531 			self->goalentity = self->movetarget = NULL;
532 			self->monsterinfo.pausetime = 100000000;
533 			self->monsterinfo.stand (self);
534 		}
535 	}
536 	else
537 	{
538 		self->monsterinfo.pausetime = 100000000;
539 		self->monsterinfo.stand (self);
540 	}
541 
542 	self->think = monster_think;
543 	self->nextthink = level.time + FRAMETIME;
544 }
545 
546 
walkmonster_start_go(edict_t * self)547 void walkmonster_start_go (edict_t *self)
548 {
549 	if (!(self->spawnflags & 2) && level.time < 1)
550 	{
551 		M_droptofloor (self);
552 
553 		if (self->groundentity)
554 			if (!M_walkmove (self, 0, 0))
555 				gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
556 	}
557 
558 	if (!self->yaw_speed)
559 		self->yaw_speed = 20;
560 	self->viewheight = 25;
561 
562 	monster_start_go (self);
563 
564 	if (self->spawnflags & 2)
565 		monster_triggered_start (self);
566 }
567 
walkmonster_start(edict_t * self)568 void walkmonster_start (edict_t *self)
569 {
570 	self->think = walkmonster_start_go;
571 	monster_start (self);
572 }
573 
574 
flymonster_start_go(edict_t * self)575 void flymonster_start_go (edict_t *self)
576 {
577 	if (!M_walkmove (self, 0, 0))
578 		gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
579 
580 	if (!self->yaw_speed)
581 		self->yaw_speed = 10;
582 	self->viewheight = 25;
583 
584 	monster_start_go (self);
585 
586 	if (self->spawnflags & 2)
587 		monster_triggered_start (self);
588 }
589 
590 
flymonster_start(edict_t * self)591 void flymonster_start (edict_t *self)
592 {
593 	self->flags |= FL_FLY;
594 	self->think = flymonster_start_go;
595 	monster_start (self);
596 }
597 
598 
swimmonster_start_go(edict_t * self)599 void swimmonster_start_go (edict_t *self)
600 {
601 	if (!self->yaw_speed)
602 		self->yaw_speed = 10;
603 	self->viewheight = 10;
604 
605 	monster_start_go (self);
606 
607 	if (self->spawnflags & 2)
608 		monster_triggered_start (self);
609 }
610 
swimmonster_start(edict_t * self)611 void swimmonster_start (edict_t *self)
612 {
613 	self->flags |= FL_SWIM;
614 	self->think = swimmonster_start_go;
615 	monster_start (self);
616 }
617