]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/monster/zombie.qc
The first of many bad mistakes (forgot to include the new files)
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / monster / zombie.qc
1 /**
2  * Special purpose fields:
3  * .delay - time at which to check if zombie's enemy is still in range
4  * .enemy - enemy of this zombie
5  * .state - state of the zombie, see ZOMBIE_STATE_*
6  */
7  
8 // cvars
9 float autocvar_g_monster_zombie;
10 float autocvar_g_monster_zombie_stopspeed;
11 float autocvar_g_monster_zombie_attack_leap_damage;
12 float autocvar_g_monster_zombie_attack_leap_delay;
13 float autocvar_g_monster_zombie_attack_leap_force;
14 float autocvar_g_monster_zombie_attack_leap_range;
15 float autocvar_g_monster_zombie_attack_leap_speed;
16 float autocvar_g_monster_zombie_attack_stand_damage;
17 float autocvar_g_monster_zombie_attack_stand_delay;
18 float autocvar_g_monster_zombie_attack_stand_range;
19 float autocvar_g_monster_zombie_health;
20 float autocvar_g_monster_zombie_idle_timer;
21 float autocvar_g_monster_zombie_speed_walk;
22 float autocvar_g_monster_zombie_speed_run;
23 float autocvar_g_monster_zombie_target_recheck_delay;
24 float autocvar_g_monster_zombie_target_range;
25
26 // zombie animations
27 #define zombie_anim_attackleap                  0
28 #define zombie_anim_attackrun1                  1
29 #define zombie_anim_attackrun2                  2
30 #define zombie_anim_attackrun3                  3
31 #define zombie_anim_attackstanding1             4
32 #define zombie_anim_attackstanding2             5
33 #define zombie_anim_attackstanding3             6
34 #define zombie_anim_blockend                    7
35 #define zombie_anim_blockstart                  8
36 #define zombie_anim_deathback1                  9
37 #define zombie_anim_deathback2                  10
38 #define zombie_anim_deathback3                  11
39 #define zombie_anim_deathfront1                 12
40 #define zombie_anim_deathfront2                 13
41 #define zombie_anim_deathfront3                 14
42 #define zombie_anim_deathleft1                  15
43 #define zombie_anim_deathleft2                  16
44 #define zombie_anim_deathright1                 17
45 #define zombie_anim_deathright2                 18
46 #define zombie_anim_idle                                19
47 #define zombie_anim_painback1                   20
48 #define zombie_anim_painback2                   21
49 #define zombie_anim_painfront1                  22
50 #define zombie_anim_painfront2                  23
51 #define zombie_anim_runbackwards                24
52 #define zombie_anim_runbackwardsleft    25
53 #define zombie_anim_runbackwardsright   26
54 #define zombie_anim_runforward                  27
55 #define zombie_anim_runforwardleft              28
56 #define zombie_anim_runforwardright             29
57 #define zombie_anim_spawn                               30
58
59 const vector ZOMBIE_MIN                          = '-18 -18 -25';
60 const vector ZOMBIE_MAX                          = '18 18 47';
61
62 #define ZOMBIE_STATE_SPAWNING           0
63 #define ZOMBIE_STATE_IDLE                       1
64 #define ZOMBIE_STATE_ANGRY                      2
65 #define ZOMBIE_STATE_ATTACK_LEAP        3
66
67 void zombie_spawn();
68 void spawnfunc_monster_zombie();
69 void zombie_think();
70
71 void zombie_die ()
72 {
73         Monster_CheckDropCvars ("zombie");
74         
75         self.solid                      = SOLID_NOT;
76         self.takedamage         = DAMAGE_NO;
77         self.event_damage   = func_null;
78         self.enemy                      = world;
79         self.movetype           = MOVETYPE_TOSS;
80         self.think                      = Monster_Fade;
81         self.nextthink          = time + 2.1;
82         self.pain_finished  = self.nextthink;
83         
84         if (random() > 0.5)
85                 self.frame = zombie_anim_deathback1;
86         else
87                 self.frame = zombie_anim_deathfront1;
88                 
89         monster_hook_death(); // for post-death mods
90 }
91
92 void zombie_attack_standing()
93 {
94         float rand = random(), dot = 0, bigdmg = 0;
95
96         self.velocity_x = 0;
97         self.velocity_y = 0;
98         
99         if(self.monster_owner == self.enemy)
100         {
101                 self.enemy = world;
102                 return;
103         }
104         
105         bigdmg = autocvar_g_monster_zombie_attack_stand_damage * self.scale;
106
107         //print("zombie attacks!\n");
108         makevectors (self.angles);
109         dot = normalize (self.enemy.origin - self.origin) * v_forward;
110         if(dot > 0.3)
111         {
112                 Damage(self.enemy, self, self, bigdmg * monster_skill, DEATH_MONSTER_MELEE, self.origin, '0 0 0');
113         }
114         
115         if (!monster_isvalidtarget(self.enemy, self, FALSE))
116                 self.enemy = world;
117                 
118         if (rand < 0.33)
119                 self.frame = zombie_anim_attackstanding1;
120         else if (rand < 0.66)
121                 self.frame = zombie_anim_attackstanding2;
122         else
123                 self.frame = zombie_anim_attackstanding3;
124
125         self.nextthink = time + autocvar_g_monster_zombie_attack_stand_delay;
126 }
127
128 void zombie_attack_leap_touch()
129 {
130         vector angles_face = '0 0 0';
131         float bigdmg = autocvar_g_monster_zombie_attack_leap_damage * self.scale;
132         
133         if (other.deadflag != DEAD_NO)
134                 return;
135                 
136         if (self.monster_owner == other)
137                 return;
138         
139         if (other.takedamage == DAMAGE_NO)
140                 return;
141                 
142         //void Damage (entity targ, entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
143         traceline(self.origin, other.origin, FALSE, self);
144
145         angles_face = vectoangles(self.moveto - self.origin);
146         angles_face = normalize(angles_face) * autocvar_g_monster_zombie_attack_leap_force;
147         Damage(other, self, self, bigdmg * monster_skill, DEATH_MONSTER_MELEE, trace_endpos, angles_face);      
148
149         // make this guy zombie's priority if it wasn't already
150         if (other.deadflag == DEAD_NO)
151         if (self.enemy != other)
152                 self.enemy = other;
153                 
154         self.touch = MonsterTouch;
155 }
156
157 void zombie_attack_leap()
158 {
159         vector angles_face = '0 0 0', vel = '0 0 0';
160
161         // face the enemy       
162         self.state = ZOMBIE_STATE_ATTACK_LEAP;
163         self.frame = zombie_anim_attackleap;
164         angles_face = vectoangles(self.enemy.origin - self.origin);
165         self.angles_y = angles_face_y ;
166         self.nextthink = time + autocvar_g_monster_zombie_attack_leap_delay;
167         self.touch = zombie_attack_leap_touch;
168         makevectors(self.angles);
169         vel = normalize(v_forward);
170         self.velocity = vel * autocvar_g_monster_zombie_attack_leap_speed;
171 }
172
173 void zombie_think()
174 {
175         float finished = FALSE, enemyDistance = 0, mySpeed = 0;
176         
177         self.think = zombie_think;
178         
179         if (self.state == ZOMBIE_STATE_ATTACK_LEAP) {
180                 // reset to angry
181                 self.state = ZOMBIE_STATE_ANGRY;
182                 self.touch = func_null;
183         }
184         
185         if (self.state == ZOMBIE_STATE_SPAWNING) {
186                 // become idle when zombie spawned
187                 self.frame = zombie_anim_idle;
188                 self.state = ZOMBIE_STATE_IDLE;
189         }
190         
191         if(self.enemy && !monster_isvalidtarget(self.enemy, self, FALSE))
192                 self.enemy = world;
193         
194         if (self.enemy)
195         if (self.enemy.team == self.team || self.monster_owner == self.enemy)
196                 self.enemy = world;
197         
198         if(teamplay && autocvar_g_monsters_teams && self.monster_owner.team != self.team)
199                 self.monster_owner = world;     
200         
201         // remove enemy that ran away
202         if (self.enemy)
203         if (self.delay <= time) // check if we can do the rescan now
204         if (vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_target_range * self.scale) 
205         {
206                 //print("removing enemy, he is too far: ", ftos(vlen(self.origin - self.enemy.origin)), "\n");
207                 //print("delay was ", ftos(self.delay), "\n");
208                 self.enemy = world;
209         } 
210         else
211                 self.delay = time + autocvar_g_monster_zombie_target_recheck_delay;
212         
213         // find an enemy if no enemy available
214         if not(self.enemy) 
215         {
216                 self.enemy = FindTarget(self);
217                 if (self.enemy)
218                         self.delay = time + autocvar_g_monster_zombie_target_recheck_delay;
219         }
220
221         if (self.enemy) 
222         {
223                 // make sure zombie is angry
224                 self.state = ZOMBIE_STATE_ANGRY;
225                 
226
227                 // this zombie has an enemy, attack if close enough, go to it if not!
228                 traceline(self.origin, self.enemy.origin, FALSE, self);
229                 enemyDistance = vlen(trace_endpos - self.origin);
230                 mySpeed = vlen(self.velocity);
231                 
232                 //print("speed ", ftos(mySpeed), "\n");
233                 
234                 if (trace_ent == self.enemy)
235                 if (self.enemy.deadflag == DEAD_NO)
236                 if (mySpeed <= 30)
237                         if (enemyDistance <= autocvar_g_monster_zombie_attack_stand_range * self.scale) 
238                         {
239                                 //RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity ignore, float forceintensity, float deathtype, entity directhitentity)
240                                 zombie_attack_standing();
241                                 finished = TRUE;
242                         } 
243                         else if (enemyDistance <= autocvar_g_monster_zombie_attack_leap_range * self.scale) 
244                         {
245                                 // do attackleap (set yaw, velocity, and check do damage on the first player entity it touches)
246                                 zombie_attack_leap();
247                                 finished = TRUE;
248                         }
249                 
250         }
251         
252         self.nextthink = time + 1;
253
254         if not(finished) 
255         {
256                 monster_move(autocvar_g_monster_zombie_speed_run, autocvar_g_monster_zombie_speed_walk, autocvar_g_monster_zombie_stopspeed, zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
257                 
258                 if (self.enemy || self.monster_owner)
259                 {
260                         self.nextthink = time + 0.1;
261                         return;
262                 }   
263         }
264         
265         if not(self.enemy || self.monster_owner || self.goalentity) 
266         {
267                 // stay idle
268                 //print("zombie is idling while waiting for some fresh meat...\n");
269                 self.frame = ((mySpeed <= 20) ? zombie_anim_idle : zombie_anim_runforward);
270                 self.nextthink = time + autocvar_g_monster_zombie_idle_timer * random();        
271         }
272 }
273
274 void zombie_spawn() 
275 {
276         if not(self.health)
277                 self.health = autocvar_g_monster_zombie_health * self.scale;
278         
279         self.classname                  = "monster_zombie";
280         self.nextthink                  = time + 2.1;
281         self.pain_finished      = self.nextthink;
282         self.state                              = ZOMBIE_STATE_SPAWNING;
283         self.frame                              = zombie_anim_spawn;
284         self.think                              = zombie_think;
285         self.sprite_height      = 50 * self.scale;
286         self.skin                               = rint(random() * 4);
287         
288         monster_hook_spawn(); // for post-spawn mods
289 }
290
291 /*QUAKED monster_zombie (1 0 0) (-18 -18 -25) (18 18 47)
292 Zombie, 60 health points.
293 -------- KEYS --------
294 -------- SPAWNFLAGS --------
295 MONSTERFLAG_APPEAR: monster will spawn when triggered.
296 ---------NOTES----------
297 Original Quake 1 zombie entity used a smaller box ('-16 -16 -24', '16 16 32').
298 -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY --------
299 modeldisabled="models/monsters/zombie.dpm"
300 */
301 void spawnfunc_monster_zombie() 
302 {
303         if not(autocvar_g_monster_zombie) 
304         {
305                 remove(self);
306                 return;
307         }
308         
309         self.monster_spawnfunc = spawnfunc_monster_zombie;
310         
311         if(self.spawnflags & MONSTERFLAG_APPEAR)
312         {
313                 self.think = func_null;
314                 self.nextthink = -1;
315                 self.use = Monster_Appear;
316                 return;
317         }
318         
319         if not (monster_initialize(
320                          "Zombie",
321                          "models/monsters/zombie.dpm",
322                          ZOMBIE_MIN, ZOMBIE_MAX,
323                          FALSE,
324                          zombie_die, zombie_spawn))
325         {
326                 remove(self);
327                 return;
328         }
329 }