]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/monsters/monster/knight.qc
Partially fix all monsters thinking they're fish
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / monsters / monster / knight.qc
1 #ifdef REGISTER_MONSTER
2 REGISTER_MONSTER(
3 /* MON_##id   */ KNIGHT,
4 /* function   */ m_knight,
5 /* mins,maxs  */ '-20 -20 -32', '20 20 41',
6 /* model      */ "hknight.mdl",
7 /* netname    */ "knight",
8 /* fullname   */ _("Knight")
9 );
10
11 #else
12 #ifdef SVQC
13 float autocvar_g_monster_knight;
14 float autocvar_g_monster_knight_health;
15 float autocvar_g_monster_knight_melee_damage;
16 float autocvar_g_monster_knight_inferno_damage;
17 float autocvar_g_monster_knight_inferno_damagetime;
18 float autocvar_g_monster_knight_inferno_chance;
19 float autocvar_g_monster_knight_speed_walk;
20 float autocvar_g_monster_knight_speed_run;
21 float autocvar_g_monster_knight_fireball_damage;
22 float autocvar_g_monster_knight_fireball_force;
23 float autocvar_g_monster_knight_fireball_radius;
24 float autocvar_g_monster_knight_fireball_chance;
25 float autocvar_g_monster_knight_fireball_edgedamage;
26 float autocvar_g_monster_knight_spike_chance;
27 float autocvar_g_monster_knight_spike_force;
28 float autocvar_g_monster_knight_spike_radius;
29 float autocvar_g_monster_knight_spike_edgedamage;
30 float autocvar_g_monster_knight_spike_damage;
31 float autocvar_g_monster_knight_jump_chance;
32 float autocvar_g_monster_knight_jump_damage;
33 float autocvar_g_monster_knight_jump_dist;
34
35 const float knight_anim_stand   = 0;
36 const float knight_anim_walk    = 1;
37 const float knight_anim_run     = 2;
38 const float knight_anim_pain    = 3;
39 const float knight_anim_death1  = 4;
40 const float knight_anim_death2  = 5;
41 const float knight_anim_charge1 = 6;
42 const float knight_anim_magic1  = 7;
43 const float knight_anim_magic2  = 8;
44 const float knight_anim_charge2 = 9;
45 const float knight_anim_slice   = 10;
46 const float knight_anim_smash   = 11;
47 const float knight_anim_wattack = 12;
48 const float knight_anim_magic3  = 13;
49
50 .float knight_cycles;
51
52 void knight_inferno()
53 {
54         if not(self.enemy)
55                 return;
56                 
57         traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
58         if (trace_fraction != 1)
59                 return; // not visible
60         
61         self.enemy.effects |= EF_MUZZLEFLASH;
62         sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM);
63         
64         if(vlen(self.enemy.origin - self.origin) <= 2000)
65                 Fire_AddDamage(self.enemy, self, autocvar_g_monster_knight_inferno_damage * monster_skill, autocvar_g_monster_knight_inferno_damagetime, DEATH_MONSTER_KNIGHT_INFERNO);
66 }
67
68 void knight_fireball_explode()
69 {
70         entity e;
71         if(self)
72         {
73                 pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
74                 
75                 RadiusDamage(self, self.realowner, autocvar_g_monster_knight_fireball_damage, autocvar_g_monster_knight_fireball_edgedamage, autocvar_g_monster_knight_fireball_force, world, autocvar_g_monster_knight_fireball_radius, self.projectiledeathtype, world);
76                 
77                 for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_knight_fireball_radius)
78                         Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_knight_inferno_damagetime, self.projectiledeathtype);
79                 
80                 remove(self);
81         }
82 }
83
84 void knight_fireball_touch()
85 {
86         PROJECTILE_TOUCH;
87         
88         knight_fireball_explode();
89 }
90
91 void knight_fireball()
92 {
93         entity missile = spawn();
94         vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
95         
96         monster_makevectors(self.enemy);
97         
98         self.effects |= EF_MUZZLEFLASH;
99         sound(self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM);
100
101         missile.owner = missile.realowner = self;
102         missile.solid = SOLID_TRIGGER;
103         missile.movetype = MOVETYPE_FLYMISSILE;
104         missile.projectiledeathtype = DEATH_MONSTER_KNIGHT_FBALL;
105         setsize(missile, '-6 -6 -6', '6 6 6');          
106         setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
107         missile.flags = FL_PROJECTILE;
108         missile.velocity = dir * 400;
109         missile.avelocity = '300 300 300';
110         missile.nextthink = time + 5;
111         missile.think = knight_fireball_explode;
112         missile.enemy = self.enemy;
113         missile.touch = knight_fireball_touch;
114         CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
115 }
116
117 void knight_spike_explode()
118 {
119         if(self)
120         {
121                 pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
122                 
123                 RadiusDamage (self, self.realowner, autocvar_g_monster_knight_spike_damage, autocvar_g_monster_knight_spike_edgedamage, autocvar_g_monster_knight_spike_force, world, autocvar_g_monster_knight_spike_radius, DEATH_MONSTER_KNIGHT_SPIKE, other);
124                 remove(self);
125         }
126 }
127
128 void knight_spike_touch()
129 {
130         PROJECTILE_TOUCH;
131         
132         knight_spike_explode();
133 }
134
135 void knight_spike()
136 {
137         entity missile;
138         vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
139
140         self.effects |= EF_MUZZLEFLASH;
141
142         missile = spawn ();
143         missile.owner = missile.realowner = self;
144         missile.solid = SOLID_TRIGGER;
145         missile.movetype = MOVETYPE_FLYMISSILE;
146         setsize (missile, '0 0 0', '0 0 0');            
147         setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
148         missile.scale = self.scale;
149         missile.flags = FL_PROJECTILE;
150         missile.velocity = dir * 400;
151         missile.avelocity = '300 300 300';
152         missile.nextthink = time + 5;
153         missile.think = knight_spike_explode;
154         missile.enemy = self.enemy;
155         missile.touch = knight_spike_touch;
156         CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
157 }
158
159 void knight_spikes()
160 {
161         self.knight_cycles += 1;
162         knight_spike();
163         
164         if(self.knight_cycles <= 7)
165                 defer(0.1, knight_spikes);
166 }
167
168 float knight_attack_ranged()
169 {
170         if not(self.flags & FL_ONGROUND)
171                 return FALSE;
172                 
173         self.knight_cycles = 0;
174         
175         RandomSelection_Init();
176         RandomSelection_Add(world, 1, "", autocvar_g_monster_knight_fireball_chance, 1);
177         RandomSelection_Add(world, 2, "", autocvar_g_monster_knight_inferno_chance, 1);
178         RandomSelection_Add(world, 3, "", autocvar_g_monster_knight_spike_chance, 1);
179         if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > autocvar_g_monster_knight_jump_dist) ? 1 : autocvar_g_monster_knight_jump_chance), 1);
180         
181         switch(RandomSelection_chosen_float)
182         {
183                 case 1:
184                 {
185                         monsters_setframe(knight_anim_magic2);
186                         self.attack_finished_single = time + 2;
187                         defer(0.4, knight_fireball);
188                         
189                         return TRUE;
190                 }
191                 case 2:
192                 {
193                         self.attack_finished_single = time + 3;
194                         defer(0.5, knight_inferno);
195                         return TRUE;
196                 }
197                 case 3:
198                 {
199                         monsters_setframe(knight_anim_magic3);
200                         self.attack_finished_single = time + 3;
201                         defer(0.4, knight_spikes);
202                         
203                         return TRUE;
204                 }
205                 case 4:
206                 {
207                         float er = vlen(self.enemy.origin - self.origin);
208                         
209                         if(er >= 400 && er < 1200)
210                         if(findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self))
211                         {
212                                 self.velocity = findtrajectory_velocity;
213                                 Damage(self.enemy, self, self, autocvar_g_monster_knight_jump_damage * monster_skill, DEATH_MONSTER_KNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin));
214                                 self.attack_finished_single = time + 2;
215                                 return TRUE;
216                         }
217                         return FALSE;
218                 }
219         }
220         
221         return FALSE;
222 }
223
224 float knight_attack(float attack_type)
225 {
226         switch(attack_type)
227         {
228                 case MONSTER_ATTACK_MELEE:
229                 {
230                         float anim;
231                         if(random() < 0.3)
232                                 anim = knight_anim_slice;
233                         else if(random() < 0.6)
234                                 anim = knight_anim_smash;
235                         else
236                                 anim = knight_anim_wattack;
237                         
238                         monsters_setframe(anim);
239                         self.attack_finished_single = time + 0.7;
240                         monster_melee(self.enemy, autocvar_g_monster_knight_melee_damage, 0.3, DEATH_MONSTER_KNIGHT_MELEE, TRUE);
241                         
242                         return TRUE;
243                 }
244                 case MONSTER_ATTACK_RANGED:
245                 {
246                         if(knight_attack_ranged())
247                                 return TRUE;
248                 }
249         }
250         
251         return FALSE;
252 }
253
254 void spawnfunc_monster_knight()
255 {
256         if not(autocvar_g_monster_knight) { remove(self); return; }
257         
258         self.classname = "monster_knight";
259         
260         self.monster_spawnfunc = spawnfunc_monster_knight;
261         
262         if(Monster_CheckAppearFlags(self))
263                 return;
264         
265         if not(monster_initialize(MON_KNIGHT, FALSE, MONSTER_SIZE_BROKEN)) { remove(self); return; }
266 }
267
268 // compatibility with old spawns
269 void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); }
270
271 float m_knight(float req)
272 {
273         switch(req)
274         {
275                 case MR_THINK:
276                 {
277                         monster_move(autocvar_g_monster_knight_speed_run, autocvar_g_monster_knight_speed_walk, 100, knight_anim_run, knight_anim_walk, knight_anim_stand);
278                         return TRUE;
279                 }
280                 case MR_DEATH:
281                 {
282                         float chance = random();
283                         monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2);
284                         if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS)
285                         if(self.candrop)
286                         {
287                                 self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon
288                                 self.weapon = WEP_FIREBALL;
289                         }
290                         return TRUE;
291                 }
292                 case MR_SETUP:
293                 {
294                         if not(self.health) self.health = autocvar_g_monster_knight_health;
295                                 
296                         self.monster_attackfunc = knight_attack;
297                         monsters_setframe(knight_anim_stand);
298                         
299                         return TRUE;
300                 }
301                 case MR_INIT:
302                 {
303                         precache_sound ("player/lava.wav");
304                         return TRUE;
305                 }
306         }
307         
308         return TRUE;
309 }
310
311 #endif // SVQC
312 #ifdef CSQC
313 float m_knight(float req)
314 {
315         switch(req)
316         {
317                 case MR_DEATH:
318                 {
319                         // nothing
320                         return TRUE;
321                 }
322                 case MR_INIT:
323                 {
324                         precache_model ("models/monsters/hknight.mdl");
325                         return TRUE;
326                 }
327         }
328         
329         return TRUE;
330 }
331
332 #endif // CSQC
333 #endif // REGISTER_MONSTER