]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/monsters/monster/mage.qc
Use an effect for mage shield
[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 */ MON_FLAG_MELEE | MON_FLAG_RANGED,
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_spike_accel) \
18         MON_ADD_CVAR(monster, attack_spike_decel) \
19         MON_ADD_CVAR(monster, attack_spike_turnrate) \
20         MON_ADD_CVAR(monster, attack_spike_speed_max) \
21         MON_ADD_CVAR(monster, attack_spike_smart) \
22         MON_ADD_CVAR(monster, attack_spike_smart_trace_min) \
23         MON_ADD_CVAR(monster, attack_spike_smart_trace_max) \
24         MON_ADD_CVAR(monster, attack_spike_smart_mindist) \
25         MON_ADD_CVAR(monster, attack_push_damage) \
26         MON_ADD_CVAR(monster, attack_push_radius) \
27         MON_ADD_CVAR(monster, attack_push_delay) \
28         MON_ADD_CVAR(monster, attack_push_force) \
29         MON_ADD_CVAR(monster, heal_self) \
30         MON_ADD_CVAR(monster, heal_allies) \
31         MON_ADD_CVAR(monster, heal_minhealth) \
32         MON_ADD_CVAR(monster, heal_range) \
33         MON_ADD_CVAR(monster, heal_delay) \
34         MON_ADD_CVAR(monster, shield_time) \
35         MON_ADD_CVAR(monster, shield_delay) \
36         MON_ADD_CVAR(monster, shield_blockpercent) \
37         MON_ADD_CVAR(monster, speed_stop) \
38         MON_ADD_CVAR(monster, speed_run) \
39         MON_ADD_CVAR(monster, speed_walk) 
40
41 #ifdef SVQC
42 MAGE_SETTINGS(mage)
43 #endif // SVQC
44 #else
45 #ifdef SVQC
46 const float mage_anim_idle              = 0;
47 const float mage_anim_walk              = 1;
48 const float mage_anim_attack    = 2;
49 const float mage_anim_pain              = 3;
50 const float mage_anim_death     = 4;
51 const float mage_anim_run               = 5;
52
53 void() mage_heal;
54 void() mage_shield;
55
56 .entity mage_spike;
57 .float shield_ltime;
58
59 float friend_needshelp(entity e)
60 {
61         if(e == world)
62                 return FALSE;
63         if(e.health <= 0)
64                 return FALSE;
65         if(DIFF_TEAM(e, self) && e != self.monster_owner)
66                 return FALSE;
67         if(e.frozen)
68                 return FALSE;
69         if(!IS_PLAYER(e))
70                 return (e.flags & FL_MONSTER && e.health < e.max_health);
71         if(e.items & IT_INVINCIBLE)
72                 return FALSE;
73
74         switch(self.skin)
75         {
76                 case 0: return (e.health < autocvar_g_balance_health_regenstable);
77                 case 1: return ((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));
78                 case 2: return (e.armorvalue < autocvar_g_balance_armor_regenstable);
79                 case 3: return (e.health > 0);
80         }
81         
82         return FALSE;
83 }
84
85 void mage_spike_explode()
86 {
87         self.event_damage = func_null;
88         
89         sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
90         
91         self.realowner.mage_spike = world;
92         
93         pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
94         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);
95
96         remove (self);
97 }
98
99 void mage_spike_touch()
100 {
101         PROJECTILE_TOUCH;
102
103         mage_spike_explode();
104 }
105
106 // copied from W_Seeker_Think
107 void mage_spike_think()
108 {
109         entity e;
110         vector desireddir, olddir, newdir, eorg;
111         float turnrate;
112         float dist;
113         float spd;
114
115         if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0)
116         {
117                 self.projectiledeathtype |= HITTYPE_SPLASH;
118                 mage_spike_explode();
119         }
120
121         spd = vlen(self.velocity);
122         spd = bound(
123                 spd - MON_CVAR(mage, attack_spike_decel) * frametime,
124                 MON_CVAR(mage, attack_spike_speed_max),
125                 spd + MON_CVAR(mage, attack_spike_accel) * frametime
126         );
127
128         if (self.enemy != world)
129                 if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
130                         self.enemy = world;
131
132         if (self.enemy != world)
133         {
134                 e               = self.enemy;
135                 eorg            = 0.5 * (e.absmin + e.absmax);
136                 turnrate        = MON_CVAR(mage, attack_spike_turnrate); // how fast to turn
137                 desireddir      = normalize(eorg - self.origin);
138                 olddir          = normalize(self.velocity); // get my current direction
139                 dist            = vlen(eorg - self.origin);
140
141                 // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
142                 if (MON_CVAR(mage, attack_spike_smart) && (dist > MON_CVAR(mage, attack_spike_smart_mindist)))
143                 {
144                         // Is it a better idea (shorter distance) to trace to the target itself?
145                         if ( vlen(self.origin + olddir * self.wait) < dist)
146                                 traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
147                         else
148                                 traceline(self.origin, eorg, FALSE, self);
149
150                         // Setup adaptive tracelength
151                         self.wait = bound(MON_CVAR(mage, attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = MON_CVAR(mage, attack_spike_smart_trace_max));
152
153                         // Calc how important it is that we turn and add this to the desierd (enemy) dir.
154                         desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
155                 }
156                 
157                 newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
158                 self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
159         }
160         else
161                 dist = 0;
162                 
163         ///////////////
164
165         //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
166         self.nextthink = time;// + 0.05; // csqc projectiles
167         UpdateCSQCProjectile(self);
168 }
169
170 void mage_attack_spike()
171 {
172         entity missile;
173         vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
174
175         makevectors(self.angles);
176
177         missile = spawn ();
178         missile.owner = missile.realowner = self;
179         missile.think = mage_spike_think;
180         missile.ltime = time + 7;
181         missile.nextthink = time;
182         missile.solid = SOLID_BBOX;
183         missile.movetype = MOVETYPE_FLYMISSILE;
184         missile.flags = FL_PROJECTILE;
185         setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
186         setsize (missile, '0 0 0', '0 0 0');    
187         missile.velocity = dir * 400;
188         missile.avelocity = '300 300 300';
189         missile.enemy = self.enemy;
190         missile.touch = mage_spike_touch;
191         
192         self.mage_spike = missile;
193         
194         CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
195 }
196
197 void mage_heal()
198 {
199         entity head;
200         float washealed = FALSE;
201         
202         for(head = findradius(self.origin, MON_CVAR(mage, heal_range)); head; head = head.chain) if(friend_needshelp(head))
203         {
204                 washealed = TRUE;
205                 string fx = "";
206                 if(IS_PLAYER(head))
207                 {
208                         switch(self.skin)
209                         {
210                                 case 0:
211                                         if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + MON_CVAR(mage, heal_allies), autocvar_g_balance_health_regenstable);
212                                         fx = "healing_fx";
213                                         break;
214                                 case 1:
215                                         if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
216                                         if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
217                                         if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
218                                         if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
219                                         fx = "ammoregen_fx";
220                                         break;
221                                 case 2:
222                                         if(head.armorvalue < autocvar_g_balance_armor_regenstable)
223                                         {
224                                                 head.armorvalue = bound(0, head.armorvalue + MON_CVAR(mage, heal_allies), autocvar_g_balance_armor_regenstable);
225                                                 fx = "armorrepair_fx";
226                                         }
227                                         break;
228                                 case 3:
229                                         head.health = bound(0, head.health - ((head == self)  ? MON_CVAR(mage, heal_self) : MON_CVAR(mage, heal_allies)), autocvar_g_balance_health_regenstable);
230                                         fx = "rage";
231                                         break;
232                         }
233                         
234                         pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
235                 }
236                 else
237                 {
238                         pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
239                         head.health = bound(0, head.health + MON_CVAR(mage, heal_allies), head.max_health);
240                         WaypointSprite_UpdateHealth(head.sprite, head.health);
241                 }
242         }
243         
244         if(washealed)
245         {
246                 self.frame = mage_anim_attack;
247                 self.attack_finished_single = time + MON_CVAR(mage, heal_delay);
248         }
249 }
250
251 void mage_push()
252 {
253         sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM);
254         RadiusDamage (self, self, MON_CVAR(mage, attack_push_damage), MON_CVAR(mage, attack_push_damage), MON_CVAR(mage, attack_push_radius), world, MON_CVAR(mage, attack_push_force), DEATH_MONSTER_MAGE, self.enemy);
255         pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1);
256         
257         self.frame = mage_anim_attack;
258         self.attack_finished_single = time + MON_CVAR(mage, attack_push_delay);
259 }
260
261 void mage_teleport()
262 {
263         if(vlen(self.enemy.origin - self.origin) >= 500)
264                 return;
265
266         makevectors(self.enemy.angles);
267         tracebox(self.enemy.origin + ((v_forward * -1) * 200), self.mins, self.maxs, self.origin, MOVE_NOMONSTERS, self);
268         
269         if(trace_fraction < 1)
270                 return;
271                 
272         pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1);
273         setorigin(self, self.enemy.origin + ((v_forward * -1) * 200));
274         
275         self.attack_finished_single = time + 0.2;
276 }
277
278 void mage_shield_remove()
279 {
280         self.effects &= ~(EF_ADDITIVE | EF_BLUE);
281         self.armorvalue = 0;
282         self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
283 }
284
285 void mage_shield()
286 {
287         self.effects |= (EF_ADDITIVE | EF_BLUE);
288         self.lastshielded = time + MON_CVAR(mage, shield_delay);
289         self.m_armor_blockpercent = MON_CVAR(mage, shield_blockpercent);
290         self.armorvalue = self.health;
291         self.shield_ltime = time + MON_CVAR(mage, shield_time);
292         self.frame = mage_anim_attack;
293         self.attack_finished_single = time + 1;
294 }
295
296 float mage_attack(float attack_type)
297 {
298         switch(attack_type)
299         {
300                 case MONSTER_ATTACK_MELEE:
301                 {
302                         if(random() <= 0.7)
303                         {
304                                 mage_push();
305                                 return TRUE;
306                         }
307                                 
308                         return FALSE;
309                 }
310                 case MONSTER_ATTACK_RANGED:
311                 {
312                         if not(self.mage_spike)
313                         {
314                                 if(random() <= 0.4)
315                                 {
316                                         mage_teleport();
317                                         return TRUE;
318                                 }
319                                 else
320                                 {
321                                         self.frame = mage_anim_attack;
322                                         self.attack_finished_single = time + MON_CVAR(mage, attack_spike_delay);
323                                         defer(0.2, mage_attack_spike);
324                                         return TRUE;
325                                 }
326                         }
327                         
328                         if(self.mage_spike)
329                                 return TRUE;
330                         else
331                                 return FALSE;
332                 }
333         }
334         
335         return FALSE;
336 }
337
338 void spawnfunc_monster_mage()
339 {
340         self.classname = "monster_mage";
341         
342         self.monster_spawnfunc = spawnfunc_monster_mage;
343         
344         if(Monster_CheckAppearFlags(self))
345                 return;
346         
347         if not(monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
348 }
349
350 // compatibility with old spawns
351 void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
352
353 float m_mage(float req)
354 {
355         switch(req)
356         {
357                 case MR_THINK:
358                 {
359                         entity head;
360                         float need_help = FALSE;
361                         
362                         for(head = findradius(self.origin, MON_CVAR(mage, heal_range)); head; head = head.chain)
363                         if(head != self)
364                         if(friend_needshelp(head))
365                         {
366                                 need_help = TRUE;
367                                 break;
368                         }
369                                 
370                         if(self.health < MON_CVAR(mage, heal_minhealth) || need_help)
371                         if(time >= self.attack_finished_single)
372                         if(random() < 0.5)
373                                 mage_heal();
374                                 
375                         if(time >= self.shield_ltime && self.armorvalue)
376                                 mage_shield_remove();
377                                 
378                         if(self.enemy)
379                         if(self.health < self.max_health)
380                         if(time >= self.lastshielded)
381                         if(random() < 0.5)
382                                 mage_shield();
383                         
384                         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);
385                         return TRUE;
386                 }
387                 case MR_DEATH:
388                 {
389                         self.frame = mage_anim_death;
390                         return TRUE;
391                 }
392                 case MR_SETUP:
393                 {
394                         if not(self.health) self.health = MON_CVAR(mage, health);
395                         
396                         self.monster_loot = spawnfunc_item_health_large;
397                         self.monster_attackfunc = mage_attack;
398                         self.frame = mage_anim_walk;
399                         
400                         return TRUE;
401                 }
402                 case MR_PRECACHE:
403                 {
404                         precache_model ("models/monsters/mage.dpm");
405                         precache_sound ("weapons/grenade_impact.wav");
406                         precache_sound ("weapons/tagexp1.wav");
407                         return TRUE;
408                 }
409                 case MR_CONFIG:
410                 {
411                         MON_CONFIG_SETTINGS(MAGE_SETTINGS(mage))
412                         return TRUE;
413                 }
414         }
415         
416         return TRUE;
417 }
418
419 #endif // SVQC
420 #ifdef CSQC
421 float m_mage(float req)
422 {
423         switch(req)
424         {
425                 case MR_PRECACHE:
426                 {
427                         precache_model ("models/monsters/mage.dpm");
428                         return TRUE;
429                 }
430         }
431         
432         return TRUE;
433 }
434
435 #endif // CSQC
436 #endif // REGISTER_MONSTER