]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/monsters/monster/mage.qc
ab2f4e32dc69ede9db1fe6056c5c329ec2d7556e
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / monsters / monster / mage.qc
1 #ifdef REGISTER_MONSTER
2 REGISTER_MONSTER(
3 /* MON_##id   */ MAGE,
4 /* function   */ m_mage,
5 /* mins,maxs  */ '-36 -36 -24', '36 36 50',
6 /* model      */ "mage.dpm",
7 /* netname    */ "mage",
8 /* fullname   */ _("Mage")
9 );
10
11 #else
12 #ifdef SVQC
13 float autocvar_g_monster_mage;
14 float autocvar_g_monster_mage_health;
15 float autocvar_g_monster_mage_speed;
16 float autocvar_g_monster_mage_attack_spike_damage;
17 float autocvar_g_monster_mage_attack_spike_radius;
18 float autocvar_g_monster_mage_attack_spike_delay;
19 float autocvar_g_monster_mage_attack_melee_damage;
20 float autocvar_g_monster_mage_attack_melee_delay;
21 float autocvar_g_monster_mage_heal_self;
22 float autocvar_g_monster_mage_heal_friends;
23 float autocvar_g_monster_mage_heal_minhealth;
24 float autocvar_g_monster_mage_heal_range;
25 float autocvar_g_monster_mage_heal_delay;
26 float autocvar_g_monster_mage_shield_time;
27 float autocvar_g_monster_mage_shield_delay;
28 float autocvar_g_monster_mage_shield_blockpercent;
29 float autocvar_g_monster_mage_attack_grenade_damage;
30 float autocvar_g_monster_mage_attack_grenade_edgedamage;
31 float autocvar_g_monster_mage_attack_grenade_radius;
32 float autocvar_g_monster_mage_attack_grenade_lifetime;
33 float autocvar_g_monster_mage_attack_grenade_force;
34 float autocvar_g_monster_mage_attack_grenade_chance;
35
36 const float mage_anim_idle              = 0;
37 const float mage_anim_walk              = 1;
38 const float mage_anim_attack    = 2;
39 const float mage_anim_pain              = 3;
40 const float mage_anim_death     = 4;
41 const float mage_anim_run               = 5;
42
43 void() mage_heal;
44 void() mage_shield;
45 void() mage_shield_die;
46
47 float friend_needshelp(entity e)
48 {
49         if(e == world)
50                 return FALSE;
51         if(e.health <= 0)
52                 return FALSE;
53         if(vlen(e.origin - self.origin) > autocvar_g_monster_mage_heal_range)
54                 return FALSE;
55         if(IsDifferentTeam(e, self))
56                 return FALSE;
57         if(e.frozen)
58                 return FALSE;
59         if(!IS_PLAYER(e))
60                 return (e.health < e.max_health);
61         if(e.items & IT_INVINCIBLE)
62                 return FALSE;
63
64         switch(self.skin)
65         {
66                 case 0:
67                 {
68                         if(e.health < autocvar_g_balance_health_regenstable)
69                                 return TRUE;
70                         break;
71                 }
72                 case 1:
73                 {
74                         if((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max))
75                                 return TRUE;
76                         break;
77                 }
78                 case 2:
79                 {
80                         if(e.armorvalue < autocvar_g_balance_armor_regenstable)
81                                 return TRUE;
82                         break;
83                 }
84                 case 3:
85                 {
86                         if(e.health > 0)
87                                 return TRUE;
88                         break;
89                 }
90         }
91         
92         return FALSE;
93 }
94
95 void mageattack_melee()
96 {
97         monster_melee(self.enemy, autocvar_g_monster_mage_attack_melee_damage, 0.3, DEATH_MONSTER_MAGE, TRUE);
98 }
99
100 void mage_grenade_explode()
101 {
102         pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
103         
104         sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
105         RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_grenade_damage, autocvar_g_monster_mage_attack_grenade_edgedamage, autocvar_g_monster_mage_attack_grenade_radius, world, autocvar_g_monster_mage_attack_grenade_force, DEATH_MONSTER_MAGE, other);
106         remove(self);
107 }
108
109 void mage_grenade_touch()
110 {
111         if(IS_PLAYER(other))
112         {
113                 PROJECTILE_TOUCH;
114                 mage_grenade_explode();
115                 return;
116         }
117 }
118
119 void mage_throw_itemgrenade()
120 {
121         makevectors(self.angles);
122
123         W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, autocvar_g_monster_mage_attack_grenade_damage);
124         w_shotdir = v_forward; // no TrueAim for grenades please
125
126         entity gren = spawn ();
127         gren.owner = gren.realowner = self;
128         gren.classname = "grenade";
129         gren.bot_dodge = FALSE;
130         gren.movetype = MOVETYPE_BOUNCE;
131         gren.solid = SOLID_TRIGGER;
132         gren.projectiledeathtype = DEATH_MONSTER_MAGE;
133         setorigin(gren, w_shotorg);
134         setsize(gren, '-64 -64 -64', '64 64 64');
135
136         gren.nextthink = time + autocvar_g_monster_mage_attack_grenade_lifetime;
137         gren.think = mage_grenade_explode;
138         gren.use = mage_grenade_explode;
139         gren.touch = mage_grenade_touch;
140
141         gren.missile_flags = MIF_SPLASH | MIF_ARC;
142         W_SETUPPROJECTILEVELOCITY_UP(gren, g_monster_mage_attack_grenade);
143         
144         gren.flags = FL_PROJECTILE;
145         
146         setmodel(gren, "models/items/g_h50.md3");
147         
148         self.attack_finished_single = time + 1.5;
149 }
150
151 void mage_spike_explode()
152 {
153         self.event_damage = func_null;
154
155         pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
156         RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_spike_damage, autocvar_g_monster_mage_attack_spike_damage * 0.5, autocvar_g_monster_mage_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other);
157
158         remove (self);
159 }
160
161 void mage_spike_touch()
162 {
163         PROJECTILE_TOUCH;
164
165         mage_spike_explode();
166 }
167
168 void mage_spike_think()
169 {
170         if(self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime)
171         {
172                 mage_spike_explode();
173                 return;
174         }
175         
176         vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
177         
178         UpdateCSQCProjectile(self);
179         
180         if (monster_skill == 3)
181                 self.velocity = dir * 350;
182         else
183                 self.velocity = dir * 250;
184                 
185         self.nextthink = time + 0.2;
186         self.think = mage_spike_think;  
187 }
188
189 void mage_spike()
190 {
191         entity missile;
192         vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
193
194         makevectors(self.angles);
195
196         missile = spawn ();
197         missile.owner = missile.realowner = self;
198         missile.think = mage_spike_think;
199         missile.ltime = time + 7;
200         missile.nextthink = time;
201         missile.solid = SOLID_BBOX;
202         missile.movetype = MOVETYPE_FLYMISSILE;
203         missile.flags = FL_PROJECTILE;
204         setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
205         setsize (missile, '0 0 0', '0 0 0');    
206         missile.velocity = dir * 400;
207         missile.avelocity = '300 300 300';
208         missile.enemy = self.enemy;
209         missile.touch = mage_spike_touch;
210         
211         CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
212 }
213
214 void mage_heal()
215 {
216         entity head;
217         float washealed = FALSE;
218         
219         for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head))
220         {
221                 washealed = TRUE;
222                 string fx = "";
223                 if(IS_PLAYER(head))
224                 {
225                         switch(self.skin)
226                         {
227                                 case 0:
228                                         if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_health_regenstable);
229                                         fx = "healing_fx";
230                                         break;
231                                 case 1:
232                                         if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
233                                         if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
234                                         if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
235                                         if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
236                                         fx = "ammoregen_fx";
237                                         break;
238                                 case 2:
239                                         if(head.armorvalue < autocvar_g_balance_armor_regenstable)
240                                         {
241                                                 head.armorvalue = bound(0, head.armorvalue + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_armor_regenstable);
242                                                 fx = "armorrepair_fx";
243                                         }
244                                         break;
245                                 case 3:
246                                         head.health = bound(0, head.health - ((head == self)  ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_friends), autocvar_g_balance_health_regenstable);
247                                         fx = "rage";
248                                         break;
249                         }
250                         
251                         pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
252                 }
253                 else
254                 {
255                         pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
256                         head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, head.max_health);
257                         WaypointSprite_UpdateHealth(head.sprite, head.health);
258                 }
259         }
260         
261         if(washealed)
262         {
263                 monsters_setframe(mage_anim_attack);
264                 self.attack_finished_single = time + autocvar_g_monster_mage_heal_delay;
265         }
266 }
267
268 void mage_shield_die()
269 {
270         if not(self.weaponentity)
271                 return; // why would this be called without a shield?
272         
273         self.armorvalue = 1;
274         
275         remove(self.weaponentity);
276         
277         self.weaponentity = world;
278 }
279
280 void mage_shield()
281 {
282         if(self.weaponentity)
283                 return; // already have a shield
284                 
285         entity shield = spawn();
286
287         shield.owner = self;
288         shield.team = self.team;
289         shield.ltime = time + autocvar_g_monster_mage_shield_time;
290         shield.health = 70;
291         shield.classname = "shield";
292         shield.effects = EF_ADDITIVE;
293         shield.movetype = MOVETYPE_NOCLIP;
294         shield.solid = SOLID_TRIGGER;
295         shield.avelocity = '7 0 11';
296         shield.scale = self.scale * 0.6;
297         
298         setattachment(shield, self, "");
299         setmodel(shield, "models/ctf/shield.md3");
300         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
301         
302         self.weaponentity = shield;
303         
304         self.lastshielded = time + autocvar_g_monster_mage_shield_delay;
305         
306         monsters_setframe(mage_anim_attack);
307         self.attack_finished_single = time + 1;
308         
309         self.armorvalue = autocvar_g_monster_mage_shield_blockpercent / 100;
310 }
311
312 float mage_attack(float attack_type)
313 {
314         switch(attack_type)
315         {
316                 case MONSTER_ATTACK_MELEE:
317                 {
318                         monsters_setframe(mage_anim_attack);
319                         self.attack_finished_single = time + autocvar_g_monster_mage_attack_melee_delay;
320                         defer(0.2, mageattack_melee);
321                         
322                         return TRUE;
323                 }
324                 case MONSTER_ATTACK_RANGED:
325                 {
326                         if(random() < autocvar_g_monster_mage_attack_grenade_chance / 100)
327                         {
328                                 mage_throw_itemgrenade();
329                                 return TRUE;
330                         }
331         
332                         monsters_setframe(mage_anim_attack);
333                         self.attack_finished_single = time + autocvar_g_monster_mage_attack_spike_delay;
334                         defer(0.2, mage_spike);
335                         
336                         return TRUE;
337                 }
338         }
339         
340         return FALSE;
341 }
342
343 void spawnfunc_monster_mage()
344 {
345         if not(autocvar_g_monster_mage) { remove(self); return; }
346         
347         self.classname = "monster_mage";
348         
349         self.monster_spawnfunc = spawnfunc_monster_mage;
350         
351         if(Monster_CheckAppearFlags(self))
352                 return;
353         
354         if not(monster_initialize(MON_MAGE, FALSE, 0)) { remove(self); return; }
355 }
356
357 // compatibility with old spawns
358 void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
359
360 float m_mage(float req)
361 {
362         switch(req)
363         {
364                 case MR_THINK:
365                 {
366                         entity head;
367                         float need_help = FALSE;
368                         
369                         FOR_EACH_PLAYER(head)
370                         if(friend_needshelp(head))
371                         {
372                                 need_help = TRUE;
373                                 break; // found 1 player near us who is low on health
374                         }
375                         if(!need_help)
376                         FOR_EACH_MONSTER(head)
377                         if(head != self)
378                         if(friend_needshelp(head))
379                         {
380                                 need_help = TRUE;
381                                 break; // found 1 player near us who is low on health
382                         }
383                         
384                         if(self.weaponentity)
385                         if(time >= self.weaponentity.ltime)
386                                 mage_shield_die();
387                                 
388                         if(self.health < autocvar_g_monster_mage_heal_minhealth || need_help)
389                         if(time >= self.attack_finished_single)
390                         if(random() < 0.5)
391                                 mage_heal();
392                                 
393                         if(self.enemy)
394                         if(self.health < self.max_health)
395                         if(time >= self.lastshielded)
396                         if(random() < 0.5)
397                                 mage_shield();
398                         
399                         monster_move(autocvar_g_monster_mage_speed, autocvar_g_monster_mage_speed, 50, mage_anim_walk, mage_anim_run, mage_anim_idle);
400                         return TRUE;
401                 }
402                 case MR_DEATH:
403                 {
404                         monsters_setframe(mage_anim_death);
405                         return TRUE;
406                 }
407                 case MR_SETUP:
408                 {
409                         if not(self.health) self.health = autocvar_g_monster_mage_health;
410                                 
411                         self.monster_attackfunc = mage_attack;
412                         monsters_setframe(mage_anim_walk);
413                         
414                         return TRUE;
415                 }
416                 case MR_INIT:
417                 {
418                         // nothing
419                         return TRUE;
420                 }
421         }
422         
423         return TRUE;
424 }
425
426 #endif // SVQC
427 #ifdef CSQC
428 float m_mage(float req)
429 {
430         switch(req)
431         {
432                 case MR_DEATH:
433                 {
434                         // nothing
435                         return TRUE;
436                 }
437                 case MR_INIT:
438                 {
439                         precache_model ("models/monsters/mage.dpm");
440                         return TRUE;
441                 }
442         }
443         
444         return TRUE;
445 }
446
447 #endif // CSQC
448 #endif // REGISTER_MONSTER