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