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