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