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