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