monster_zombie (not enabled by default, -DMONSTES_ENABLED to enable)
[divverent/nexuiz.git] / data / qcsrc / server / monsters / monster_zombie.qc
1 //#define MONSTES_ENABLED\r
2 #ifdef MONSTES_ENABLED\r
3 \r
4 #define zombie_anim_attackleap         0\r
5 #define zombie_anim_attackrun1         1\r
6 #define zombie_anim_attackrun2         2\r
7 #define zombie_anim_attackrun3         3\r
8 #define zombie_anim_attackstanding1    4\r
9 #define zombie_anim_attackstanding2    5\r
10 #define zombie_anim_attackstanding3    6\r
11 #define zombie_anim_blockend           7\r
12 #define zombie_anim_blockstart         8\r
13 #define zombie_anim_deathback1         9\r
14 #define zombie_anim_deathback2         10\r
15 #define zombie_anim_deathback3         11\r
16 #define zombie_anim_deathfront1        12\r
17 #define zombie_anim_deathfront2        13\r
18 #define zombie_anim_deathfront3        14\r
19 #define zombie_anim_deathleft1         15\r
20 #define zombie_anim_deathleft2         16\r
21 #define zombie_anim_deathright1        17\r
22 #define zombie_anim_deathright2        18\r
23 #define zombie_anim_idle               19\r
24 #define zombie_anim_painback1          20\r
25 #define zombie_anim_painback2          21\r
26 #define zombie_anim_painfront1         22\r
27 #define zombie_anim_painfront2         23\r
28 #define zombie_anim_runbackwards       24\r
29 #define zombie_anim_runbackwardsleft   25\r
30 #define zombie_anim_runbackwardsright  26\r
31 #define zombie_anim_runforward         27\r
32 #define zombie_anim_runforwardleft     28\r
33 #define zombie_anim_runforwardright    29\r
34 #define zombie_anim_spawn              30\r
35 \r
36 #define ZOMBIE_MIN                                       '-18 -18 -25'\r
37 #define ZOMBIE_MAX                                       '18 18 47'\r
38 \r
39 #define ZV_IDLE     10\r
40 \r
41 #define ZV_PATH     100\r
42 #define ZV_HUNT     200\r
43 \r
44 #define ZV_ATTACK_FIND  10\r
45 #define ZV_ATTACK_RUN   20\r
46 #define ZV_ATTACK_STAND 30\r
47 \r
48 #define ZV_PATH2 10000\r
49 \r
50 //.entity verbs_idle;\r
51 //.entity verbs_attack;\r
52 //.entity verbs_move;\r
53 \r
54 //.float  state_timeout;\r
55 //.void() monster_state;\r
56 #define MONSTERFLAG_NORESPAWN 2\r
57 \r
58 void zombie_spawn();\r
59 \r
60 float zombie_scoretarget(entity trg)\r
61 {\r
62     float  tmp;\r
63     vector ang1;\r
64 \r
65     if (trg.takedamage == DAMAGE_AIM)\r
66     if not (trg.flags & FL_NOTARGET)\r
67     if (trg.deadflag == DEAD_NO)\r
68     if (trg.team != self.team)\r
69     {\r
70         if((self.origin_z - trg.origin_z) < 128)\r
71         {\r
72             ang1 = normalize(self.origin - trg.origin);\r
73             tmp = vlen(ang1 - v_forward);\r
74             if(tmp > 1.5)\r
75             {\r
76                 traceline(self.origin + '0 0 47',trg.origin + '0 0 32',MOVE_NORMAL,self);\r
77                 if(trace_ent != trg)\r
78                     return 0;\r
79 \r
80                 return (cvar("g_monster_zombie_targetrange") - vlen(self.origin - trg.origin)) * tmp;\r
81             }\r
82             else if(self.enemy == trg)\r
83                 return (cvar("g_monster_zombie_targetrange") - vlen(self.origin - trg.origin)) * tmp;\r
84         }\r
85     }\r
86 \r
87     return 0;\r
88 }\r
89 \r
90 void zombie_corpse_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)\r
91 {\r
92     //dprint("zombie_corpse_damage\n");\r
93     Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);\r
94 \r
95     self.health -= damage;\r
96 \r
97     if(self.health < 0)\r
98     {\r
99         Violence_GibSplash(self, 1, 1, attacker);\r
100         remove(self);\r
101     }\r
102 }\r
103 \r
104 void zombie_die(vector dir)\r
105 {\r
106     vector v;\r
107     float f;\r
108 \r
109     entity dummy;\r
110 \r
111     dummy = spawn();\r
112     setmodel(dummy,"models/monsters/zombie.dpm");\r
113     setorigin(dummy, self.origin);\r
114     dummy.velocity  = self.velocity;\r
115     dummy.movetype  = MOVETYPE_BOUNCE;\r
116     dummy.think     = SUB_Remove;\r
117     dummy.nextthink = time + 3;\r
118     dummy.health    = 50;\r
119     dummy.takedamage = DAMAGE_YES;\r
120     dummy.event_damage = zombie_corpse_damage;\r
121     dummy.solid      = SOLID_CORPSE;\r
122     setsize(dummy,self.mins,self.maxs);\r
123 \r
124     SUB_SetFade(dummy,time + 5,2);\r
125 \r
126 \r
127     v = normalize(self.origin - dir);\r
128     f = vlen(v_forward - v) - 1;\r
129     if(f > 0.5)\r
130         dummy.frame = zombie_anim_deathfront1 + rint(random() * 2);\r
131     else if(f < 0.5)\r
132         dummy.frame = zombie_anim_deathback1 + rint(random() * 2);\r
133     else\r
134     {\r
135         f = vlen(v_right - v) - 1;\r
136         if(f > 0.5)\r
137             dummy.frame = zombie_anim_deathright1 + rint(random() * 2);\r
138         else if(f < 0.5)\r
139             dummy.frame = zombie_anim_deathleft1 + rint(random() * 2);\r
140     }\r
141 \r
142 \r
143     if(self.spawnflags & MONSTERFLAG_NORESPAWN)\r
144     {\r
145         self.think = SUB_Remove;\r
146         self.nextthink = time;\r
147         return;\r
148     }\r
149 \r
150     setmodel(self,"");\r
151     self.solid          = SOLID_NOT;\r
152     self.takedamage     = DAMAGE_NO;\r
153     self.event_damage   = SUB_Null;\r
154     self.enemy          = world;\r
155     self.think          = zombie_spawn;\r
156     self.nextthink      = time + cvar("g_monster_zombie_respawntime");\r
157     self.pain_finished  = self.nextthink;\r
158 }\r
159 \r
160 void zombie_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)\r
161 {\r
162 \r
163     vector v;\r
164     float f;\r
165 \r
166     v = normalize(self.origin - hitloc);\r
167     f = vlen(v_forward - v) - 1;\r
168 \r
169 \r
170     self.health -= damage;\r
171     self.velocity = self.velocity + force;\r
172     if(self.health <= 0)\r
173     {\r
174         zombie_die(hitloc);\r
175         return;\r
176     }\r
177 \r
178     Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);\r
179 \r
180         if (damage > 50)\r
181                 Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);\r
182         if (damage > 100)\r
183                 Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);\r
184 \r
185     if (time > self.pain_finished)\r
186     {\r
187         if(f < 0.5)\r
188         {\r
189             if(random() < 0.5)\r
190                 self.frame = zombie_anim_painback1;\r
191             else\r
192                 self.frame = zombie_anim_painback2;\r
193         }\r
194         else\r
195         {\r
196             if(random() < 0.5)\r
197                 self.frame = zombie_anim_painfront1;\r
198             else\r
199                 self.frame = zombie_anim_painfront2;\r
200         }\r
201 \r
202         self.pain_finished = time + 0.36;\r
203     }\r
204 }\r
205 \r
206 .vector bvec;\r
207 .float bvec_time;\r
208 \r
209 void zombie_move()\r
210 {\r
211     vector real_angle;\r
212     float vz, tdiff, tspeed;\r
213 \r
214     tdiff = time - self.zoomstate;\r
215     tspeed = tdiff * cvar("g_monster_zombie_turnspeed");\r
216     vz = self.velocity_z;\r
217     self.zoomstate = time;\r
218 \r
219     if(self.bvec_time < time)\r
220     {\r
221         self.bvec_time = time + 0.2;\r
222         self.bvec = steerlib_beamsteer(steerlib_attract2(self.moveto,0.5,500,0.95),512,32,34,64);\r
223     }\r
224 \r
225     if(self.enemy)\r
226         self.moveto = self.enemy.origin;\r
227     else\r
228         self.moveto = self.origin + v_forward;\r
229 \r
230     self.steerto = normalize(steerlib_attract2(self.moveto,0.5,500,0.95) + self.bvec);\r
231 \r
232     self.angles_y = safeangle(self.angles_y);\r
233     real_angle = vectoangles(self.steerto) - self.angles;\r
234     self.angles_y += bound(-10, real_angle_y, 10);\r
235 \r
236     if(vlen(self.origin - self.moveto) > 64)\r
237     {\r
238         movelib_move_simple(v_forward ,cvar("g_monster_zombie_movespeed"),0.6);\r
239         if(time > self.pain_finished)\r
240             if(self.attack_finished_single < time)\r
241                 self.frame = zombie_anim_runforward;\r
242     }\r
243     else\r
244     {\r
245         movelib_beak_simple(cvar("g_monster_zombie_stopspeed"));\r
246         if(time > self.pain_finished)\r
247             if(self.attack_finished_single < time)\r
248                 self.frame = zombie_anim_idle;\r
249     }\r
250 \r
251     self.velocity_z = vz;\r
252     self.steerto = self.origin;\r
253 }\r
254 \r
255 float zombie_verb_idle_roam(float eval)\r
256 {\r
257     switch (eval)\r
258     {\r
259     case VCM_EVAL:\r
260 \r
261         if(self.enemy)\r
262             return VS_CALL_NO;\r
263 \r
264         return verb.verb_static_value;\r
265 \r
266     case VCM_DO:\r
267 \r
268         self.moveto = v_forward * 128;\r
269         self.steerto = v_forward; //steerlib_beamsteer(v_forward,512,32,34,64);\r
270 \r
271         return VS_CALL_YES_DOING;\r
272     }\r
273 \r
274     return VS_CALL_YES_DONE;\r
275 }\r
276 \r
277 float zombie_verb_idle_stand(float eval)\r
278 {\r
279     switch (eval)\r
280     {\r
281     case VCM_EVAL:\r
282 \r
283         if(self.enemy)\r
284             return VS_CALL_NO;\r
285 \r
286         return verb.verb_static_value;\r
287 \r
288     case VCM_DO:\r
289 \r
290         self.moveto   = self.origin;\r
291         self.frame    = zombie_anim_idle;\r
292         self.velocity = '0 0 0';\r
293 \r
294         return VS_CALL_YES_DOING;\r
295     }\r
296 \r
297     return VS_CALL_YES_DONE;\r
298 }\r
299 \r
300 float zombie_verb_idle(float eval)\r
301 {\r
302     switch (eval)\r
303     {\r
304     case VCM_EVAL:\r
305 \r
306         if(self.enemy)\r
307             return VS_CALL_NO;\r
308 \r
309         return verb.verb_static_value;\r
310 \r
311     case VCM_DO:\r
312         float t;\r
313 \r
314         t = cvar("g_monster_zombie_idle_timer_max") -  cvar("g_monster_zombie_idle_timer_min");\r
315         t = cvar("g_monster_zombie_idle_timer_min") + (random() * t);\r
316 \r
317         if(random() < 0.5)\r
318             verbstack_push(self.verbs_idle, zombie_verb_idle_roam,  ZV_IDLE + 1, t, self);\r
319         else\r
320             verbstack_push(self.verbs_idle, zombie_verb_idle_stand, ZV_IDLE + 1, 0.1, self);\r
321 \r
322         return VS_CALL_YES_DOING;\r
323     }\r
324 \r
325     return VS_CALL_YES_DONE;\r
326 }\r
327 \r
328 float zombie_verb_attack_findtarget(float eval)\r
329 {\r
330     switch (eval)\r
331     {\r
332     case VCM_EVAL:\r
333         if(self.enemy)\r
334             return VS_CALL_NO;\r
335 \r
336         return verb.verb_static_value;\r
337 \r
338     case VCM_DO:\r
339 \r
340         entity trg, best_trg;\r
341         float trg_score, best_trg_score;\r
342 \r
343         trg = findradius(self.origin,cvar("g_monster_zombie_targetrange"));\r
344         while(trg)\r
345         {\r
346             trg_score = zombie_scoretarget(trg);\r
347             if(trg_score > best_trg_score)\r
348             {\r
349                 best_trg = trg;\r
350                 best_trg_score = trg_score;\r
351             }\r
352 \r
353             trg = trg.chain;\r
354         }\r
355 \r
356         if(best_trg)\r
357         {\r
358             self.enemy = best_trg;\r
359             dprint("Selected: ",best_trg.netname, " as target.\n");\r
360         }\r
361 \r
362         return VS_CALL_YES_DOING;\r
363     }\r
364 \r
365     return VS_CALL_YES_DONE;\r
366 }\r
367 \r
368 void zombie_runattack_damage()\r
369 {\r
370     entity oldself;\r
371     oldself = self;\r
372     self = self.owner;\r
373 \r
374     if(vlen(self.origin - self.enemy.origin) > cvar("g_monster_zombie_attack_run_hitrange"))\r
375         return;\r
376 \r
377     if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.6)\r
378         return;\r
379 \r
380     Damage(self.enemy, self, self, cvar("g_monster_zombie_attack_run_damage"), DEATH_TURRET, self.enemy.origin, normalize(self.enemy.origin - self.origin)  * cvar("g_monster_zombie_attack_run_force"));\r
381 \r
382     self = oldself;\r
383     self.think = SUB_Remove;\r
384     self.nextthink = time;\r
385 }\r
386 \r
387 float zombie_verb_attack_run(float eval)\r
388 {\r
389     switch (eval)\r
390     {\r
391     case VCM_EVAL:\r
392         if not (self.enemy)\r
393             return VS_CALL_NO;\r
394 \r
395         if(self.attack_finished_single > time)\r
396             return VS_CALL_NO;\r
397 \r
398         if(vlen(self.origin - self.enemy.origin) > cvar("g_monster_zombie_attack_run_range"))\r
399             return VS_CALL_NO;\r
400 \r
401         if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.6)\r
402             return VS_CALL_NO;\r
403 \r
404         return verb.verb_static_value;\r
405 \r
406     case VCM_DO:\r
407         entity pain;\r
408         pain = spawn();\r
409         pain.owner = self;\r
410         pain.think = zombie_runattack_damage;\r
411         pain.nextthink = time + cvar("g_monster_zombie_attack_run_delay");\r
412 \r
413         self.attack_finished_single = time + 0.7;\r
414         self.frame = zombie_anim_attackrun1 + rint(random() * 2);\r
415 \r
416         return VS_CALL_YES_DOING;\r
417     }\r
418 \r
419     return VS_CALL_YES_DONE;\r
420 }\r
421 \r
422 void zombie_standattack_damage()\r
423 {\r
424     //entity oldself;\r
425     //oldself = self;\r
426     //self = self.owner;\r
427 \r
428     setorigin(self,self.owner.origin + v_forward * 32);\r
429     RadiusDamage(self, self.owner, cvar("g_monster_zombie_attack_stand_damage"),cvar("g_monster_zombie_attack_stand_damage"),16,self, cvar("g_monster_zombie_attack_stand_force"),DEATH_TURRET,world);\r
430     //float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity ignore, float forceintensity, float deathtype, entity directhitentity)\r
431 \r
432 \r
433     //self = oldself;\r
434     self.think = SUB_Remove;\r
435     self.nextthink = time;\r
436 }\r
437 \r
438 float zombie_verb_attack_stand(float eval)\r
439 {\r
440     switch (eval)\r
441     {\r
442     case VCM_EVAL:\r
443         if not (self.enemy)\r
444             return VS_CALL_NO;\r
445 \r
446         if(self.attack_finished_single > time)\r
447             return VS_CALL_NO;\r
448 \r
449         if(vlen(self.origin - self.enemy.origin) > cvar("g_monster_zombie_attack_stand_range"))\r
450             return VS_CALL_NO;\r
451 \r
452         if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.8)\r
453             return VS_CALL_NO;\r
454 \r
455         return verb.verb_static_value;\r
456 \r
457     case VCM_DO:\r
458         entity pain;\r
459         pain = spawn();\r
460         pain.owner = self;\r
461         pain.think = zombie_runattack_damage;\r
462         pain.nextthink = time + cvar("g_monster_zombie_attack_stand_delay");\r
463 \r
464         self.attack_finished_single = time + 0.7;\r
465         self.frame = zombie_anim_attackstanding1 + rint(random() * 1);\r
466         dprint("frame:",ftos(self.frame),"\n");\r
467 \r
468         return VS_CALL_YES_DOING;\r
469     }\r
470 \r
471     return VS_CALL_YES_DONE;\r
472 }\r
473 \r
474 void zombie_think()\r
475 {\r
476     self.angles_x *= -1;\r
477     makevectors(self.angles);\r
478     self.angles_x *= -1;\r
479 \r
480     if (zombie_scoretarget(self.enemy) == 0)\r
481         self.enemy = world;\r
482 \r
483     verbstack_pop(self.verbs_attack);\r
484     //verbstack_pop(self.verbs_move);\r
485 \r
486     if not (self.enemy)\r
487         verbstack_pop(self.verbs_idle);\r
488 \r
489     zombie_move();\r
490 \r
491     if(self.enemy)\r
492         self.nextthink = time;\r
493     else\r
494         self.nextthink = time + 0.2;\r
495 }\r
496 \r
497 void zombie_spawn()\r
498 {\r
499     setmodel(self,"models/monsters/zombie.dpm");\r
500 \r
501     self.solid          = SOLID_BBOX;\r
502     self.takedamage     = DAMAGE_AIM;\r
503     self.event_damage   = zombie_damage;\r
504     self.enemy          = world;\r
505     self.frame          = zombie_anim_spawn;\r
506     self.think          = zombie_think;\r
507     self.nextthink      = time + 2.1;\r
508     self.pain_finished  = self.nextthink;\r
509     self.movetype       = MOVETYPE_WALK;\r
510     self.health         = cvar("g_monster_zombie_health");\r
511     self.velocity       = '0 0 0';\r
512     self.angles         = self.pos2;\r
513     self.moveto         = self.origin;\r
514     self.flags          = FL_MONSTER;\r
515 \r
516     setorigin(self,self.pos1);\r
517     setsize(self,ZOMBIE_MIN,ZOMBIE_MAX);\r
518 }\r
519 \r
520 \r
521 void spawnfunc_monster_zombie()\r
522 {\r
523     if not(cvar("g_monsters"))\r
524     {\r
525         remove(self);\r
526         return;\r
527     }\r
528 \r
529     precache_model("models/monsters/zombie.dpm");\r
530 \r
531 \r
532     self.verbs_idle   = spawn();\r
533     self.verbs_attack = spawn();\r
534 \r
535     self.verbs_idle.owner = self;\r
536     self.verbs_attack.owner = self;\r
537 \r
538     self.think      = zombie_spawn;\r
539     self.nextthink  = time + 2;\r
540 \r
541     traceline(self.origin + '0 0 10', self.origin - '0 0 32', MOVE_WORLDONLY, self);\r
542 \r
543     self.pos1 = trace_endpos;\r
544     self.pos2 = self.angles;\r
545     self.team = MAX_SHOT_DISTANCE -1;\r
546 \r
547     verbstack_push(self.verbs_idle, zombie_verb_idle, ZV_IDLE,0 , self);\r
548 \r
549     verbstack_push(self.verbs_attack, zombie_verb_attack_findtarget, ZV_ATTACK_FIND,0 , self);\r
550     verbstack_push(self.verbs_attack, zombie_verb_attack_run, ZV_ATTACK_RUN,0 , self);\r
551     verbstack_push(self.verbs_attack, zombie_verb_attack_stand, ZV_ATTACK_STAND,0 , self);\r
552 \r
553 }\r
554 \r
555 #endif // MONSTES_ENABLED\r