]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/monsters/monster/mage.qc
Hardcode monster item drops
[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         W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, MON_CVAR(mage, attack_grenade_damage));
132         w_shotdir = v_forward; // no TrueAim for grenades please
133
134         entity gren = spawn ();
135         gren.owner = gren.realowner = self;
136         gren.classname = "grenade";
137         gren.bot_dodge = FALSE;
138         gren.movetype = MOVETYPE_BOUNCE;
139         gren.solid = SOLID_TRIGGER;
140         gren.projectiledeathtype = DEATH_MONSTER_MAGE;
141         setorigin(gren, w_shotorg);
142         setsize(gren, '-64 -64 -64', '64 64 64');
143
144         gren.nextthink = time + MON_CVAR(mage, attack_grenade_lifetime);
145         gren.think = mage_grenade_explode;
146         gren.use = mage_grenade_explode;
147         gren.touch = mage_grenade_touch;
148
149         gren.missile_flags = MIF_SPLASH | MIF_ARC;
150         W_SetupProjectileVelocityEx(gren, w_shotdir, v_up, MON_CVAR(mage, attack_grenade_speed), MON_CVAR(mage, attack_grenade_speed_up), 0, 0, FALSE);
151         
152         gren.flags = FL_PROJECTILE;
153         
154         setmodel(gren, "models/items/g_h50.md3");
155         
156         self.attack_finished_single = time + 1.5;
157 }
158
159 void mage_spike_explode()
160 {
161         self.event_damage = func_null;
162
163         pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
164         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);
165
166         remove (self);
167 }
168
169 void mage_spike_touch()
170 {
171         PROJECTILE_TOUCH;
172
173         mage_spike_explode();
174 }
175
176 void mage_spike_think()
177 {
178         if(self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime)
179         {
180                 mage_spike_explode();
181                 return;
182         }
183         
184         vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
185         
186         UpdateCSQCProjectile(self);
187         
188         if (monster_skill == 3)
189                 self.velocity = dir * 350;
190         else
191                 self.velocity = dir * 250;
192                 
193         self.nextthink = time + 0.2;
194         self.think = mage_spike_think;  
195 }
196
197 void mage_spike()
198 {
199         entity missile;
200         vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
201
202         makevectors(self.angles);
203
204         missile = spawn ();
205         missile.owner = missile.realowner = self;
206         missile.think = mage_spike_think;
207         missile.ltime = time + 7;
208         missile.nextthink = time;
209         missile.solid = SOLID_BBOX;
210         missile.movetype = MOVETYPE_FLYMISSILE;
211         missile.flags = FL_PROJECTILE;
212         setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
213         setsize (missile, '0 0 0', '0 0 0');    
214         missile.velocity = dir * 400;
215         missile.avelocity = '300 300 300';
216         missile.enemy = self.enemy;
217         missile.touch = mage_spike_touch;
218         
219         CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
220 }
221
222 void mage_heal()
223 {
224         entity head;
225         float washealed = FALSE;
226         
227         for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head))
228         {
229                 washealed = TRUE;
230                 string fx = "";
231                 if(IS_PLAYER(head))
232                 {
233                         switch(self.skin)
234                         {
235                                 case 0:
236                                         if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + MON_CVAR(mage, heal_allies), autocvar_g_balance_health_regenstable);
237                                         fx = "healing_fx";
238                                         break;
239                                 case 1:
240                                         if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
241                                         if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
242                                         if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
243                                         if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
244                                         fx = "ammoregen_fx";
245                                         break;
246                                 case 2:
247                                         if(head.armorvalue < autocvar_g_balance_armor_regenstable)
248                                         {
249                                                 head.armorvalue = bound(0, head.armorvalue + MON_CVAR(mage, heal_allies), autocvar_g_balance_armor_regenstable);
250                                                 fx = "armorrepair_fx";
251                                         }
252                                         break;
253                                 case 3:
254                                         head.health = bound(0, head.health - ((head == self)  ? MON_CVAR(mage, heal_self) : MON_CVAR(mage, heal_allies)), autocvar_g_balance_health_regenstable);
255                                         fx = "rage";
256                                         break;
257                         }
258                         
259                         pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
260                 }
261                 else
262                 {
263                         pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
264                         head.health = bound(0, head.health + MON_CVAR(mage, heal_allies), head.max_health);
265                         WaypointSprite_UpdateHealth(head.sprite, head.health);
266                 }
267         }
268         
269         if(washealed)
270         {
271                 monsters_setframe(mage_anim_attack);
272                 self.attack_finished_single = time + MON_CVAR(mage, heal_delay);
273         }
274 }
275
276 void mage_shield_die()
277 {
278         if not(self.weaponentity)
279                 return; // why would this be called without a shield?
280         
281         self.armorvalue = 1;
282         
283         remove(self.weaponentity);
284         
285         self.weaponentity = world;
286 }
287
288 void mage_shield()
289 {
290         if(self.weaponentity)
291                 return; // already have a shield
292                 
293         entity shield = spawn();
294
295         shield.owner = self;
296         shield.team = self.team;
297         shield.ltime = time + MON_CVAR(mage, shield_time);
298         shield.health = 70;
299         shield.classname = "shield";
300         shield.effects = EF_ADDITIVE;
301         shield.movetype = MOVETYPE_NOCLIP;
302         shield.solid = SOLID_TRIGGER;
303         shield.avelocity = '7 0 11';
304         shield.scale = self.scale * 0.6;
305         
306         setattachment(shield, self, "");
307         setmodel(shield, "models/ctf/shield.md3");
308         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
309         
310         self.weaponentity = shield;
311         
312         self.lastshielded = time + MON_CVAR(mage, shield_delay);
313         
314         monsters_setframe(mage_anim_attack);
315         self.attack_finished_single = time + 1;
316         
317         self.armorvalue = MON_CVAR(mage, shield_blockpercent) / 100;
318 }
319
320 float mage_attack(float attack_type)
321 {
322         switch(attack_type)
323         {
324                 case MONSTER_ATTACK_MELEE:
325                 {
326                         monsters_setframe(mage_anim_attack);
327                         self.attack_finished_single = time + MON_CVAR(mage, attack_melee_delay);
328                         defer(0.2, mageattack_melee);
329                         
330                         return TRUE;
331                 }
332                 case MONSTER_ATTACK_RANGED:
333                 {
334                         if(random() < MON_CVAR(mage, attack_grenade_chance) / 100)
335                         {
336                                 mage_throw_itemgrenade();
337                                 return TRUE;
338                         }
339         
340                         monsters_setframe(mage_anim_attack);
341                         self.attack_finished_single = time + MON_CVAR(mage, attack_spike_delay);
342                         defer(0.2, mage_spike);
343                         
344                         return TRUE;
345                 }
346         }
347         
348         return FALSE;
349 }
350
351 void spawnfunc_monster_mage()
352 {
353         self.classname = "monster_mage";
354         
355         self.monster_spawnfunc = spawnfunc_monster_mage;
356         
357         if(Monster_CheckAppearFlags(self))
358                 return;
359         
360         if not(monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
361 }
362
363 // compatibility with old spawns
364 void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
365
366 float m_mage(float req)
367 {
368         switch(req)
369         {
370                 case MR_THINK:
371                 {
372                         entity head;
373                         float need_help = FALSE;
374                         
375                         FOR_EACH_PLAYER(head)
376                         if(friend_needshelp(head))
377                         {
378                                 need_help = TRUE;
379                                 break; // found 1 player near us who is low on health
380                         }
381                         if(!need_help)
382                         FOR_EACH_MONSTER(head)
383                         if(head != self)
384                         if(friend_needshelp(head))
385                         {
386                                 need_help = TRUE;
387                                 break; // found 1 player near us who is low on health
388                         }
389                         
390                         if(self.weaponentity)
391                         if(time >= self.weaponentity.ltime)
392                                 mage_shield_die();
393                                 
394                         if(self.health < MON_CVAR(mage, heal_minhealth) || need_help)
395                         if(time >= self.attack_finished_single)
396                         if(random() < 0.5)
397                                 mage_heal();
398                                 
399                         if(self.enemy)
400                         if(self.health < self.max_health)
401                         if(time >= self.lastshielded)
402                         if(random() < 0.5)
403                                 mage_shield();
404                         
405                         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);
406                         return TRUE;
407                 }
408                 case MR_DEATH:
409                 {
410                         monsters_setframe(mage_anim_death);
411                         return TRUE;
412                 }
413                 case MR_SETUP:
414                 {
415                         if not(self.health) self.health = MON_CVAR(mage, health);
416                         
417                         self.monster_loot = spawnfunc_item_health_large;
418                         self.monster_attackfunc = mage_attack;
419                         monsters_setframe(mage_anim_walk);
420                         
421                         return TRUE;
422                 }
423                 case MR_INIT:
424                 {
425                         // nothing
426                         return TRUE;
427                 }
428                 case MR_CONFIG:
429                 {
430                         MON_CONFIG_SETTINGS(MAGE_SETTINGS(mage))
431                         return TRUE;
432                 }
433         }
434         
435         return TRUE;
436 }
437
438 #endif // SVQC
439 #ifdef CSQC
440 float m_mage(float req)
441 {
442         switch(req)
443         {
444                 case MR_DEATH:
445                 {
446                         // nothing
447                         return TRUE;
448                 }
449                 case MR_INIT:
450                 {
451                         precache_model ("models/monsters/mage.dpm");
452                         return TRUE;
453                 }
454         }
455         
456         return TRUE;
457 }
458
459 #endif // CSQC
460 #endif // REGISTER_MONSTER