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