]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/monster/hknight.qc
Rename tarbaby to slime & begin cleanup of monster_attack_melee function
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / monster / hknight.qc
1 // size
2 const vector HELLKNIGHT_MIN = '-20 -20 -32';
3 const vector HELLKNIGHT_MAX = '20 20 41';
4
5 // model
6 string HELLKNIGHT_MODEL = "models/monsters/hknight.mdl";
7
8 #ifdef SVQC
9 // cvars
10 float autocvar_g_monster_hellknight;
11 float autocvar_g_monster_hellknight_health;
12 float autocvar_g_monster_hellknight_melee_damage;
13 float autocvar_g_monster_hellknight_inferno_damage;
14 float autocvar_g_monster_hellknight_inferno_damagetime;
15 float autocvar_g_monster_hellknight_inferno_chance;
16 float autocvar_g_monster_hellknight_speed_walk;
17 float autocvar_g_monster_hellknight_speed_run;
18 float autocvar_g_monster_hellknight_fireball_damage;
19 float autocvar_g_monster_hellknight_fireball_force;
20 float autocvar_g_monster_hellknight_fireball_radius;
21 float autocvar_g_monster_hellknight_fireball_chance;
22 float autocvar_g_monster_hellknight_fireball_edgedamage;
23 float autocvar_g_monster_hellknight_spike_chance;
24 float autocvar_g_monster_hellknight_spike_force;
25 float autocvar_g_monster_hellknight_spike_radius;
26 float autocvar_g_monster_hellknight_spike_edgedamage;
27 float autocvar_g_monster_hellknight_spike_damage;
28 float autocvar_g_monster_hellknight_jump_chance;
29 float autocvar_g_monster_hellknight_jump_damage;
30 float autocvar_g_monster_hellknight_jump_dist;
31
32 // animations
33 const float hellknight_anim_stand       = 0;
34 const float hellknight_anim_walk        = 1;
35 const float hellknight_anim_run         = 2;
36 const float hellknight_anim_pain        = 3;
37 const float hellknight_anim_death1      = 4;
38 const float hellknight_anim_death2      = 5;
39 const float hellknight_anim_charge1 = 6;
40 const float hellknight_anim_magic1      = 7;
41 const float hellknight_anim_magic2      = 8;
42 const float hellknight_anim_charge2 = 9;
43 const float hellknight_anim_slice       = 10;
44 const float hellknight_anim_smash       = 11;
45 const float hellknight_anim_wattack = 12;
46 const float hellknight_anim_magic3      = 13;
47
48 void hknight_spike_think()
49 {
50         if(self)
51         {
52                 RadiusDamage (self, self.realowner, autocvar_g_monster_hellknight_spike_damage, autocvar_g_monster_hellknight_spike_edgedamage, autocvar_g_monster_hellknight_spike_force, world, autocvar_g_monster_hellknight_spike_radius, DEATH_MONSTER_HKNIGHT_SPIKE, other);
53                 remove(self);
54         }
55 }
56
57 void hknight_spike_touch()
58 {
59         PROJECTILE_TOUCH;
60         
61         pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
62         
63         hknight_spike_think();
64 }
65
66 void() hellknight_think;
67 void hknight_shoot ()
68 {
69         local   entity  missile = world;
70         local   vector  dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
71         local   float   dist = vlen (self.enemy.origin - self.origin), flytime = 0;
72
73         flytime = dist * 0.002;
74         if (flytime < 0.1)
75                 flytime = 0.1;
76
77         self.effects |= EF_MUZZLEFLASH;
78
79         missile = spawn ();
80         missile.owner = missile.realowner = self;
81         missile.solid = SOLID_TRIGGER;
82         missile.movetype = MOVETYPE_FLYMISSILE;
83         setsize (missile, '0 0 0', '0 0 0');            
84         setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
85         missile.scale = self.scale;
86         missile.flags = FL_PROJECTILE;
87         missile.velocity = dir * 400;
88         missile.avelocity = '300 300 300';
89         missile.nextthink = time + 5;
90         missile.think = hknight_spike_think;
91         missile.enemy = self.enemy;
92         missile.touch = hknight_spike_touch;
93         CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
94 }
95
96 void hknight_inferno ()
97 {
98         traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
99         if (trace_fraction != 1)
100                 return; // not visible
101         if(vlen(self.enemy.origin - self.origin) <= 2000)
102                 Fire_AddDamage(self.enemy, self, autocvar_g_monster_hellknight_inferno_damage * monster_skill, autocvar_g_monster_hellknight_inferno_damagetime, DEATH_MONSTER_HKNIGHT_INFERNO);
103 }
104
105 void hknight_infernowarning ()
106 {
107         self.monster_delayedattack = func_null;
108         self.delay = -1;
109         if(!self.enemy)
110                 return;
111                 
112         traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
113         if (trace_fraction != 1)
114                 return; // not visible
115         self.enemy.effects |= EF_MUZZLEFLASH;
116         sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM);
117         
118         hknight_inferno();
119 }
120
121 float() hknight_magic;
122 float hknight_checkmagic ()
123 {
124         local vector v1 = '0 0 0', v2 = '0 0 0';
125         local float dot = 0;
126
127         // use magic to kill zombies as they heal too fast for sword
128         if (self.enemy.monsterid == MONSTER_ZOMBIE)
129         {
130                 traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, FALSE, self);
131                 if (trace_ent == self.enemy)
132                 {
133                         hknight_magic();
134                         return TRUE;
135                 }
136         }
137
138         if (random() < 0.25)
139                 return FALSE; // 25% of the time it won't do anything
140         v1 = normalize(self.enemy.velocity);
141         v2 = normalize(self.enemy.origin - self.origin);
142         dot = v1 * v2;
143         if (dot >= 0.7) // moving away
144         if (vlen(self.enemy.velocity) >= 150) // walking/running away
145                 return hknight_magic();
146         return FALSE;
147 }
148
149 void() hellknight_charge;
150 void CheckForCharge ()
151 {
152         // check for mad charge
153         if (time < self.attack_finished_single)
154                 return;
155         if (fabs(self.origin_z - self.enemy.origin_z) > 20)
156                 return;         // too much height change
157         if (vlen (self.origin - self.enemy.origin) < 80)
158                 return;         // use regular attack
159         if (hknight_checkmagic())
160                 return; // chose magic
161
162         // charge
163         hellknight_charge();
164 }
165
166 void CheckContinueCharge ()
167 {
168         if(hknight_checkmagic())
169                 return; // chose magic
170         if(time >= self.attack_finished_single)
171         {
172                 hellknight_think();
173                 return;         // done charging
174         }
175 }
176
177 void hellknight_think ()
178 {
179         self.think = hellknight_think;
180         self.nextthink = time + self.ticrate;
181         
182         monster_move(autocvar_g_monster_hellknight_speed_run, autocvar_g_monster_hellknight_speed_walk, 100, hellknight_anim_run, hellknight_anim_walk, hellknight_anim_stand);
183 }
184
185 .float hknight_cycles;
186 void hellknight_magic ()
187 {
188         self.monster_delayedattack = hknight_infernowarning;
189         self.delay = time + 0.5;
190         self.attack_finished_single = time + 1.2;
191 }
192
193 void hknight_fireball_explode(entity targ)
194 {
195         if(self)
196         {
197                 RadiusDamage (self, self.realowner, autocvar_g_monster_hellknight_fireball_damage, autocvar_g_monster_hellknight_fireball_edgedamage, autocvar_g_monster_hellknight_fireball_force, world, autocvar_g_monster_hellknight_fireball_radius, self.projectiledeathtype, targ);
198                 if(targ)
199                         Fire_AddDamage(targ, self, 5 * monster_skill, autocvar_g_monster_hellknight_inferno_damagetime, self.projectiledeathtype);
200                 remove(self);
201         }
202 }
203
204 void hknight_fireball_think()
205 {
206         hknight_fireball_explode(world);
207 }
208
209 void hknight_fireball_touch()
210 {
211         PROJECTILE_TOUCH;
212         
213         hknight_fireball_explode(other);
214 }
215
216 void hellknight_fireball ()
217 {
218         local   entity  missile = spawn();
219         local   vector  dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
220         vector fmins = '-4 -4 -4', fmaxs = '4 4 4';
221
222         self.effects |= EF_MUZZLEFLASH;
223         sound (self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM);
224
225         missile.owner = missile.realowner = self;
226         missile.solid = SOLID_TRIGGER;
227         missile.movetype = MOVETYPE_FLYMISSILE;
228         self.projectiledeathtype = DEATH_MONSTER_HKNIGHT_FBALL;
229         setsize (missile, fmins, fmaxs);                
230         setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
231         missile.flags = FL_PROJECTILE;
232         missile.velocity = dir * 400;
233         missile.avelocity = '300 300 300';
234         missile.nextthink = time + 5;
235         missile.think = hknight_fireball_think;
236         missile.enemy = self.enemy;
237         missile.touch = hknight_fireball_touch;
238         CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
239         
240         self.delay = -1;
241 }
242
243 void hellknight_magic2 ()
244 {
245         monsters_setframe(hellknight_anim_magic2);
246         self.attack_finished_single = time + 1.2;
247         self.delay = time + 0.4;
248         self.monster_delayedattack = hellknight_fireball;
249 }
250
251 void hellknight_spikes ()
252 {
253         self.monster_delayedattack = hellknight_spikes;
254         self.delay = time + 0.1;
255         self.hknight_cycles += 1;
256         hknight_shoot();
257         if(self.hknight_cycles >= 7)
258         {
259                 self.monster_delayedattack = func_null;
260                 self.delay = -1;
261         }
262 }
263
264 void hellknight_magic3 ()
265 {
266         monsters_setframe(hellknight_anim_magic3);
267         self.attack_finished_single = time + 1.1;
268         self.monster_delayedattack = hellknight_spikes;
269         self.delay = time + 0.4;
270 }
271
272 void hellknight_charge ()
273 {
274         monsters_setframe(hellknight_anim_charge1);
275         self.attack_finished_single = time + 0.5;
276         
277         hknight_checkmagic();
278         monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 0.3, DEATH_MONSTER_HKNIGHT_MELEE, FALSE);
279         hknight_checkmagic();
280 }
281
282 void hellknight_charge2 ()
283 {
284         monsters_setframe(hellknight_anim_charge2);
285         self.attack_finished_single = time + 0.5;
286         
287         CheckContinueCharge ();
288         monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 0.3, DEATH_MONSTER_HKNIGHT_MELEE, FALSE);
289 }
290
291 void hellknight_slice ()
292 {
293         monsters_setframe(hellknight_anim_slice);
294         self.attack_finished_single = time + 0.7;
295         monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 0.3, DEATH_MONSTER_HKNIGHT_MELEE, FALSE);
296 }
297
298 void hellknight_smash ()
299 {
300         monsters_setframe(hellknight_anim_smash);
301         self.attack_finished_single = time + 0.7;
302         monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 0.3, DEATH_MONSTER_HKNIGHT_MELEE, TRUE);
303 }
304
305 void hellknight_weapon_attack ()
306 {
307         monsters_setframe(hellknight_anim_wattack);
308         self.attack_finished_single = time + 0.7;
309         monster_melee(self.enemy, autocvar_g_monster_hellknight_melee_damage, 0.3, DEATH_MONSTER_HKNIGHT_MELEE, TRUE);
310 }
311
312 float hknight_type;
313 float hknight_melee ()
314 {
315         hknight_type += 1;
316
317         if (hknight_type == 1)
318                 hellknight_slice();
319         else if (hknight_type == 2)
320                 hellknight_smash();
321         else
322         {
323                 hellknight_weapon_attack();
324                 hknight_type = 0;
325         }
326         
327         return TRUE;
328 }
329
330 float hknight_magic ()
331 {
332         if not(self.flags & FL_ONGROUND)
333                 return FALSE;
334                 
335         if not(self.enemy)
336                 return FALSE; // calling attack check with no enemy?!
337                 
338         if(time < self.attack_finished_single)
339                 return FALSE;
340                 
341         self.hknight_cycles = 0;
342
343         if (self.enemy.monsterid == MONSTER_ZOMBIE)
344         {
345                 // always use fireball to kill zombies
346                 hellknight_magic2();
347                 self.attack_finished_single = time + 2;
348                 return TRUE;
349         }
350         RandomSelection_Init();
351         RandomSelection_Add(world, 0, "fireball", autocvar_g_monster_hellknight_fireball_chance, 1);
352         RandomSelection_Add(world, 0, "inferno", autocvar_g_monster_hellknight_inferno_chance, 1);
353         RandomSelection_Add(world, 0, "spikes", autocvar_g_monster_hellknight_spike_chance, 1);
354         if(self.health >= 100)
355                 RandomSelection_Add(world, 0, "jump", ((vlen(self.enemy.origin - self.origin) > autocvar_g_monster_hellknight_jump_dist) ? 1 : autocvar_g_monster_hellknight_jump_chance), 1);
356         
357         switch(RandomSelection_chosen_string)
358         {
359                 case "fireball":
360                 {
361                         hellknight_magic2();
362                         self.attack_finished_single = time + 2;
363                         return TRUE;
364                 }
365                 case "spikes":
366                 {
367                         hellknight_magic3();
368                         self.attack_finished_single = time + 3;
369                         return TRUE;
370                 }
371                 case "inferno":
372                 {
373                         hellknight_magic();
374                         self.attack_finished_single = time + 3;
375                         return TRUE;
376                 }
377                 case "jump":
378                 {
379                         if (vlen(self.enemy.origin - self.origin) >= 400)
380                         if (findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self))
381                         {
382                                 self.velocity = findtrajectory_velocity;
383                                 Damage(self.enemy, self, self, autocvar_g_monster_hellknight_jump_damage * monster_skill, DEATH_MONSTER_HKNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin));
384                                 self.attack_finished_single = time + 2;
385                                 return TRUE;
386                         }
387                         return FALSE;
388                 }
389                 default:
390                         return FALSE;
391         }
392         // never get here
393 }
394
395 void hellknight_die ()
396 {
397         float chance = random();
398         Monster_CheckDropCvars ("hellknight");
399         
400         self.think = monster_dead_think;
401         self.nextthink = time + self.ticrate;
402         self.ltime = time + 5;
403         monsters_setframe((random() > 0.5) ? hellknight_anim_death1 : hellknight_anim_death2);
404         
405         if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS)
406         if(self.candrop)
407         {
408                 self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon
409                 self.weapon = WEP_FIREBALL;
410         }
411                 
412         monster_hook_death(); // for post-death mods
413 }
414
415 void hellknight_spawn ()
416 {
417         if not(self.health)
418                 self.health = autocvar_g_monster_hellknight_health;
419
420         self.damageforcescale   = 0.003;
421         self.classname                  = "monster_hellknight";
422         self.checkattack                = GenericCheckAttack;
423         self.attack_melee               = hknight_melee;
424         self.attack_ranged              = hknight_magic;
425         self.nextthink                  = time + random() * 0.5 + 0.1;
426         self.think                              = hellknight_think;
427         
428         monsters_setframe(hellknight_anim_stand);
429         
430         monster_setupsounds("hellknight");
431         
432         monster_hook_spawn(); // for post-spawn mods
433 }
434
435 void spawnfunc_monster_hellknight ()
436 {       
437         if not(autocvar_g_monster_hellknight) { remove(self); return; }
438         
439         self.monster_spawnfunc = spawnfunc_monster_hellknight;
440         
441         if(Monster_CheckAppearFlags(self))
442                 return;
443         
444         self.scale = 1.3;
445         
446         if not (monster_initialize(
447                          "Hell-knight", MONSTER_HELLKNIGHT,
448                          HELLKNIGHT_MIN, HELLKNIGHT_MAX,
449                          FALSE,
450                          hellknight_die, hellknight_spawn))
451         {
452                 remove(self);
453                 return;
454         }
455 }
456
457 // compatibility with old spawns
458 void spawnfunc_monster_hell_knight() { spawnfunc_monster_hellknight(); }
459
460 #endif // SVQC