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