]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/monster/shalrath.qc
Unfreze player before disconnecting/spectating
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / monster / shalrath.qc
1 // size
2 const vector SHALRATH_MIN = '-36 -36 -24';
3 const vector SHALRATH_MAX = '36 36 50';
4
5 // model
6 string SHALRATH_MODEL = "models/monsters/mage.dpm";
7
8 #ifdef SVQC
9 // cvars
10 float autocvar_g_monster_shalrath;
11 float autocvar_g_monster_shalrath_health;
12 float autocvar_g_monster_shalrath_speed;
13 float autocvar_g_monster_shalrath_attack_spike_damage;
14 float autocvar_g_monster_shalrath_attack_spike_radius;
15 float autocvar_g_monster_shalrath_attack_spike_delay;
16 float autocvar_g_monster_shalrath_attack_melee_damage;
17 float autocvar_g_monster_shalrath_attack_melee_delay;
18 float autocvar_g_monster_shalrath_heal_self;
19 float autocvar_g_monster_shalrath_heal_friends;
20 float autocvar_g_monster_shalrath_heal_minhealth;
21 float autocvar_g_monster_shalrath_heal_range;
22 float autocvar_g_monster_shalrath_heal_delay;
23 float autocvar_g_monster_shalrath_shield_time;
24 float autocvar_g_monster_shalrath_shield_delay;
25 float autocvar_g_monster_shalrath_shield_blockpercent;
26 float autocvar_g_monster_shalrath_attack_grenade_damage;
27 float autocvar_g_monster_shalrath_attack_grenade_edgedamage;
28 float autocvar_g_monster_shalrath_attack_grenade_radius;
29 float autocvar_g_monster_shalrath_attack_grenade_lifetime;
30 float autocvar_g_monster_shalrath_attack_grenade_force;
31 float autocvar_g_monster_shalrath_attack_grenade_chance;
32
33 // animations
34 const float shalrath_anim_idle          = 0;
35 const float shalrath_anim_walk          = 1;
36 const float shalrath_anim_attack        = 2;
37 const float shalrath_anim_pain          = 3;
38 const float shalrath_anim_death         = 4;
39 const float shalrath_anim_run           = 5;
40
41 void() ShalMissile;
42 float() shal_missile;
43 void() shalrath_heal;
44 void() shalrath_shield;
45 void() shalrath_shield_die;
46
47 void shalrath_think ()
48 {
49         entity head;
50         float friend_needshelp = FALSE;
51         
52         FOR_EACH_PLAYER(head)
53         {
54                 if not(IsDifferentTeam(head, self))
55                 if(head.health > 0)
56                 if(vlen(head.origin - self.origin) < autocvar_g_monster_shalrath_heal_range)
57                 if((!g_minstagib && head.health < autocvar_g_balance_health_regenstable) || (g_minstagib && head.ammo_cells < start_ammo_cells))
58                 {
59                         friend_needshelp = TRUE;
60                         break; // found 1 player near us who is low on health
61                 }
62         }
63         FOR_EACH_MONSTER(head)
64         {
65                 if not(IsDifferentTeam(head, self))
66                 if(head.health > 0)
67                 if(vlen(head.origin - self.origin) < autocvar_g_monster_shalrath_heal_range)
68                 if(head.health < head.max_health)
69                 {
70                         friend_needshelp = TRUE;
71                         break; // found 1 player near us who is low on health
72                 }
73         }
74         
75         self.think = shalrath_think;
76         self.nextthink = time + self.ticrate;
77         
78         if(self.weaponentity)
79         if(time >= self.weaponentity.ltime)
80                 shalrath_shield_die();
81                 
82         if(self.health < autocvar_g_monster_shalrath_heal_minhealth || friend_needshelp)
83         if(time >= self.attack_finished_single)
84         if(random() < 0.5)
85                 shalrath_heal();
86                 
87         if(self.enemy)
88         if(self.health < self.max_health)
89         if(time >= self.lastshielded)
90         if(random() < 0.5)
91                 shalrath_shield();
92         
93         monster_move(autocvar_g_monster_shalrath_speed, autocvar_g_monster_shalrath_speed, 50, shalrath_anim_walk, shalrath_anim_run, shalrath_anim_idle);
94 }
95
96 void shalrath_attack ()
97 {
98         monsters_setframe(shalrath_anim_attack);
99         self.delay = time + 0.2;
100         self.attack_finished_single = time + autocvar_g_monster_shalrath_attack_spike_delay;
101         self.monster_delayedattack = ShalMissile;
102 }
103
104 void shalrathattack_melee ()
105 {
106         monster_melee(self.enemy, autocvar_g_monster_shalrath_attack_melee_damage, 0.3, DEATH_MONSTER_MAGE, TRUE);
107 }
108
109 void shalrath_attack_melee ()
110 {
111         self.monster_delayedattack = shalrathattack_melee;
112         self.delay = time + 0.2;
113         monsters_setframe(shalrath_anim_attack);
114         self.attack_finished_single = time + autocvar_g_monster_shalrath_attack_melee_delay;
115 }
116
117 void shalrath_grenade_explode()
118 {
119         pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
120         
121         sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
122         RadiusDamage (self, self.realowner, autocvar_g_monster_shalrath_attack_grenade_damage, autocvar_g_monster_shalrath_attack_grenade_edgedamage, autocvar_g_monster_shalrath_attack_grenade_radius, world, autocvar_g_monster_shalrath_attack_grenade_force, DEATH_MONSTER_MAGE, other);
123         remove(self);
124 }
125
126 void shalrath_grenade_touch()
127 {
128         if(IS_PLAYER(other))
129         {
130                 PROJECTILE_TOUCH;
131                 shalrath_grenade_explode();
132                 return;
133         }
134 }
135
136 void shalrath_throw_itemgrenade()
137 {
138         makevectors(self.angles);
139
140         W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, autocvar_g_monster_shalrath_attack_grenade_damage);
141         w_shotdir = v_forward; // no TrueAim for grenades please
142
143         entity gren = spawn ();
144         gren.owner = gren.realowner = self;
145         gren.classname = "grenade";
146         gren.bot_dodge = FALSE;
147         gren.movetype = MOVETYPE_BOUNCE;
148         gren.solid = SOLID_TRIGGER;
149         gren.projectiledeathtype = DEATH_MONSTER_MAGE;
150         setorigin(gren, w_shotorg);
151         setsize(gren, '-64 -64 -64', '64 64 64');
152
153         gren.nextthink = time + autocvar_g_monster_shalrath_attack_grenade_lifetime;
154         gren.think = shalrath_grenade_explode;
155         gren.use = shalrath_grenade_explode;
156         gren.touch = shalrath_grenade_touch;
157
158         gren.missile_flags = MIF_SPLASH | MIF_ARC;
159         W_SETUPPROJECTILEVELOCITY_UP(gren, g_monster_shalrath_attack_grenade);
160         
161         gren.flags = FL_PROJECTILE;
162         
163         setmodel(gren, "models/items/g_h50.md3");
164         
165         self.attack_finished_single = time + 1.5;
166 }
167
168 float shal_missile ()
169 {
170         if(random() < autocvar_g_monster_shalrath_attack_grenade_chance / 100)
171         {
172                 shalrath_throw_itemgrenade();
173                 return TRUE;
174         }
175         
176         shalrath_attack();
177         
178         return TRUE;
179 }
180
181 void ShalHome ()
182 {
183         local vector dir = '0 0 0', vtemp = self.enemy.origin + '0 0 10';
184         
185         if (self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime)
186         {
187                 remove(self);
188                 return;
189         }
190         dir = normalize(vtemp - self.origin);
191         UpdateCSQCProjectile(self);
192         if (monster_skill == 3)
193                 self.velocity = dir * 350;
194         else
195                 self.velocity = dir * 250;
196         self.nextthink = time + 0.2;
197         self.think = ShalHome;  
198 }
199
200 void shal_spike_explode ()
201 {
202         self.event_damage = func_null;
203
204         pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
205         RadiusDamage (self, self.realowner, autocvar_g_monster_shalrath_attack_spike_damage, autocvar_g_monster_shalrath_attack_spike_damage * 0.5, autocvar_g_monster_shalrath_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other);
206
207         remove (self);
208 }
209
210 void shal_spike_touchexplode()
211 {
212         PROJECTILE_TOUCH;
213
214         shal_spike_explode();
215 }
216
217 void ShalMissile ()
218 {
219         local   entity  missile = world;
220         local   vector  dir = '0 0 0';
221         local   float   dist = 0;
222         
223         self.effects |= EF_MUZZLEFLASH;
224
225         missile = spawn ();
226         missile.owner = missile.realowner = self;
227         
228         self.v_angle = self.angles;
229         makevectors (self.angles);
230         
231         dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
232         dist = vlen (self.enemy.origin - self.origin);
233
234         missile.think = ShalHome;
235         missile.ltime = time + 7;
236         missile.nextthink = time;
237         missile.solid = SOLID_BBOX;
238         missile.movetype = MOVETYPE_FLYMISSILE;
239         missile.flags = FL_PROJECTILE;
240         setorigin (missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
241         setsize (missile, '0 0 0', '0 0 0');    
242         missile.velocity = dir * 400;
243         missile.avelocity = '300 300 300';
244         missile.enemy = self.enemy;
245         missile.touch = shal_spike_touchexplode;
246         
247         CSQCProjectile(missile, TRUE, PROJECTILE_VORE_SPIKE, TRUE);
248 }
249
250 float ShalrathCheckAttack ()
251 {
252         vector spot1 = '0 0 0', spot2 = '0 0 0';
253
254         if (self.health <= 0)
255                 return FALSE;
256                 
257         // reset delays when we have no enemy
258         if not(self.enemy)
259         {
260                 self.monster_delayedattack = func_null;
261                 self.delay = -1;
262         }
263         
264         if(self.monster_delayedattack && self.delay != -1)
265         {
266                 if(time < self.delay)
267                         return FALSE;
268                         
269                 self.monster_delayedattack();
270                 self.delay = -1;
271                 self.monster_delayedattack = func_null;
272         }
273         
274         if(time < self.attack_finished_single)
275                 return FALSE;
276         
277         if (vlen(self.enemy.origin - self.origin) <= 120)
278         {       // melee attack
279                 if (self.attack_melee)
280                 {
281                         monster_sound(self.msound_attack_melee, 0, FALSE); // no delay for attack sounds
282                         self.attack_melee();
283                         return TRUE;
284                 }
285         }
286
287 // see if any entities are in the way of the shot
288         spot1 = self.origin + self.view_ofs;
289         spot2 = self.enemy.origin + self.enemy.view_ofs;
290
291         traceline (spot1, spot2, FALSE, self);
292
293         if (trace_ent != self.enemy && trace_fraction < 1)
294                 return FALSE; // don't have a clear shot
295
296         //if (trace_inopen && trace_inwater)
297         //      return FALSE; // sight line crossed contents
298
299         if (self.attack_ranged())
300                 return TRUE;
301
302         return FALSE;
303 }
304
305 void shalrath_heal()
306 {
307         entity head;
308         if(self.health < self.max_health) // only show our effect if we are healing ourself too
309                 pointparticles(particleeffectnum("healing_fx"), self.origin, '0 0 0', 1);
310         self.health = bound(0, self.health + autocvar_g_monster_shalrath_heal_self, self.max_health);
311         WaypointSprite_UpdateHealth(self.sprite, self.health);
312         monsters_setframe(shalrath_anim_attack);
313         self.attack_finished_single = time + autocvar_g_monster_shalrath_heal_delay;
314         
315         for(head = world; (head = findfloat(head, monster_attack, TRUE)); )
316         {
317                 if(head.health > 0)
318                 if not(head.frozen)
319                 if(vlen(head.origin - self.origin) < autocvar_g_monster_shalrath_heal_range)
320                 if not(IsDifferentTeam(head, self))
321                 {
322                         if(IS_PLAYER(head))
323                         {
324                                 if((g_minstagib && head.ammo_cells < start_ammo_cells) || head.health < g_pickup_healthmedium_max)
325                                         pointparticles(particleeffectnum(((g_minstagib) ? "ammoregen_fx" : "healing_fx")), head.origin, '0 0 0', 1);
326                                 if(g_minstagib)
327                                         head.ammo_cells = bound(0, head.ammo_cells + 1, start_ammo_cells);
328                                 else
329                                         head.health = bound(0, head.health + autocvar_g_monster_shalrath_heal_friends, g_pickup_healthmedium_max);
330                         }
331                         else
332                         {
333                                 if(head.health < head.max_health)
334                                         pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
335                                 head.health = bound(0, head.health + autocvar_g_monster_shalrath_heal_friends, head.max_health);
336                                 WaypointSprite_UpdateHealth(head.sprite, head.health);
337                         }
338                 }
339         }
340 }
341
342 void shalrath_shield_die()
343 {
344         if not(self.weaponentity)
345                 return; // why would this be called without a shield?
346         
347         self.armorvalue = 1;
348         
349         remove(self.weaponentity);
350         
351         self.weaponentity = world;
352 }
353
354 void shalrath_shield()
355 {
356         if(self.weaponentity)
357                 return; // already have a shield
358                 
359         entity shield = spawn();
360
361         shield.owner = self;
362         shield.team = self.team;
363         shield.ltime = time + autocvar_g_monster_shalrath_shield_time;
364         shield.health = 70;
365         shield.classname = "shield";
366         shield.effects = EF_ADDITIVE;
367         shield.movetype = MOVETYPE_NOCLIP;
368         shield.solid = SOLID_TRIGGER;
369         shield.avelocity = '7 0 11';
370         shield.scale = self.scale * 0.6;
371         
372         setattachment(shield, self, "");
373         setmodel(shield, "models/ctf/shield.md3");
374         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
375         
376         self.weaponentity = shield;
377         
378         self.lastshielded = time + autocvar_g_monster_shalrath_shield_delay;
379         
380         monsters_setframe(shalrath_anim_attack);
381         self.attack_finished_single = time + 1;
382         
383         self.armorvalue = autocvar_g_monster_shalrath_shield_blockpercent / 100;
384 }
385
386 void shalrath_die ()
387 {
388         Monster_CheckDropCvars ("shalrath");
389         
390         self.think = monster_dead_think;
391         self.nextthink = time + self.ticrate;
392         self.ltime = time + 5;
393         monsters_setframe(shalrath_anim_death);
394         
395         monster_hook_death(); // for post-death mods
396 }
397
398 void shalrath_spawn ()
399 {
400         if not(self.health)
401                 self.health = autocvar_g_monster_shalrath_health;
402
403         self.damageforcescale   = 0.003;
404         self.classname                  = "monster_shalrath";
405         self.checkattack                = ShalrathCheckAttack;
406         self.attack_ranged              = shal_missile;
407         self.attack_melee               = shalrath_attack_melee;
408         self.nextthink                  = time + random() * 0.5 + 0.1;
409         self.think                              = shalrath_think;
410         
411         monsters_setframe(shalrath_anim_walk);
412         
413         monster_setupsounds("shalrath");
414         
415         monster_hook_spawn(); // for post-spawn mods
416 }
417
418 void spawnfunc_monster_shalrath ()
419 {       
420         if not(autocvar_g_monster_shalrath) { remove(self); return; }
421         
422         self.monster_spawnfunc = spawnfunc_monster_shalrath;
423         
424         if(Monster_CheckAppearFlags(self))
425                 return;
426         
427         if not (monster_initialize(
428                          "Mage", MONSTER_MAGE,
429                          SHALRATH_MIN, SHALRATH_MAX,
430                          FALSE,
431                          shalrath_die, shalrath_spawn))
432         {
433                 remove(self);
434                 return;
435         }
436 }
437
438 // compatibility with old spawns
439 void spawnfunc_monster_vore () { spawnfunc_monster_shalrath(); }
440
441 #endif // SVQC