]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_electro.qc
Begin moving Electro to new settings system, cleanup
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / w_electro.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id */ ELECTRO,
4 /* function */ w_electro,
5 /* ammotype */ IT_CELLS,
6 /* impulse  */ 5,
7 /* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
8 /* rating   */ BOT_PICKUP_RATING_MID,
9 /* model    */ "electro",
10 /* netname  */ "electro",
11 /* fullname */ _("Electro")
12 );
13
14 #define ELECTRO_SETTINGS(w_cvar,w_prop) \
15         w_cvar(WEP_ELECTRO, electro, MO_BOTH, ammo) \
16         w_cvar(WEP_ELECTRO, electro, MO_BOTH, damage) \
17         w_cvar(WEP_ELECTRO, electro, MO_BOTH, edgedamage) \
18         w_cvar(WEP_ELECTRO, electro, MO_BOTH, force) \
19         w_cvar(WEP_ELECTRO, electro, MO_BOTH, radius) \
20         w_cvar(WEP_ELECTRO, electro, MO_BOTH, refire) \
21         w_cvar(WEP_ELECTRO, electro, MO_BOTH, speed) \
22         w_cvar(WEP_ELECTRO, electro, MO_BOTH, damageforcescale) \
23         w_cvar(WEP_ELECTRO, electro, MO_BOTH, health) \
24         w_cvar(WEP_ELECTRO, electro, MO_PRI,  lifetime) \
25         w_cvar(WEP_ELECTRO, electro, MO_NONE, secondary) \
26         w_cvar(WEP_ELECTRO, electro, MO_SEC,  spread) \
27         w_cvar(WEP_ELECTRO, electro, MO_SEC,  lifetime_min) \
28         w_cvar(WEP_ELECTRO, electro, MO_SEC,  lifetime_rand) \
29         w_prop(WEP_ELECTRO, electro, reloading_ammo, reload_ammo) \
30         w_prop(WEP_ELECTRO, electro, reloading_time, reload_time) \
31         w_prop(WEP_ELECTRO, electro, switchdelay_raise, switchdelay_raise) \
32         w_prop(WEP_ELECTRO, electro, switchdelay_drop, switchdelay_drop)
33
34 #ifdef SVQC
35 //ELECTRO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
36 var float autocvar_g_balance_electro_combo_comboradius_thruwall = 200;
37 #endif
38 #else
39 #ifdef SVQC
40 void spawnfunc_weapon_electro() { weapon_defaultspawnfunc(WEP_ELECTRO); }
41
42 .float electro_count;
43 .float electro_secondarytime;
44
45 void W_Plasma_Explode_Combo(void);
46
47 void W_Plasma_TriggerCombo(vector org, float rad, entity own)
48 {
49         entity e = WarpZone_FindRadius(org, rad, !autocvar_g_balance_electro_combo_comboradius_thruwall);
50         while(e)
51         {
52                 if(e.classname == "plasma")
53                 {
54                         // change owner to whoever caused the combo explosion
55                         WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e);
56
57                         if(
58                                 (trace_fraction == 1)
59                                 ||
60                                 (autocvar_g_balance_electro_combo_comboradius_thruwall >= vlen(e.WarpZone_findradius_dist))
61                         )
62                         {
63                                 e.realowner = own;
64                                 e.takedamage = DAMAGE_NO;
65                                 e.classname = "plasma_chain";
66                                 e.think = W_Plasma_Explode_Combo;
67                                 e.nextthink = time + vlen(e.WarpZone_findradius_dist) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
68                         }
69                 }
70                 e = e.chain;
71         }
72 }
73
74 void W_Plasma_Explode(void)
75 {
76         if(other.takedamage == DAMAGE_AIM)
77                 if(IS_PLAYER(other))
78                         if(DIFF_TEAM(self.realowner, other))
79                                 if(other.deadflag == DEAD_NO)
80                                         if(IsFlying(other))
81                                                 Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
82
83         self.event_damage = func_null;
84         self.takedamage = DAMAGE_NO;
85         
86         if(self.movetype == MOVETYPE_BOUNCE)
87         {
88                 RadiusDamage(
89                         self,
90                         self.realowner,
91                         autocvar_g_balance_electro_secondary_damage,
92                         autocvar_g_balance_electro_secondary_edgedamage,
93                         autocvar_g_balance_electro_secondary_radius,
94                         world,
95                         world,
96                         autocvar_g_balance_electro_secondary_force,
97                         self.projectiledeathtype,
98                         other
99                 );
100         }
101         else
102         {
103                 W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_primary_comboradius, self.realowner);
104                 RadiusDamage(
105                         self,
106                         self.realowner,
107                         autocvar_g_balance_electro_primary_damage,
108                         autocvar_g_balance_electro_primary_edgedamage,
109                         autocvar_g_balance_electro_primary_radius,
110                         world,
111                         world,
112                         autocvar_g_balance_electro_primary_force,
113                         self.projectiledeathtype,
114                         other
115                 );
116         }
117
118         remove(self);
119 }
120
121 void W_Plasma_Explode_Combo(void)
122 {
123         W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_combo_comboradius, self.realowner);
124
125         self.event_damage = func_null;
126         
127         RadiusDamage(
128                 self,
129                 self.realowner,
130                 autocvar_g_balance_electro_combo_damage,
131                 autocvar_g_balance_electro_combo_edgedamage,
132                 autocvar_g_balance_electro_combo_radius,
133                 world,
134                 world,
135                 autocvar_g_balance_electro_combo_force,
136                 WEP_ELECTRO | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce
137                 world
138         );
139
140         remove (self);
141 }
142
143 void W_Plasma_Touch(void)
144 {
145         PROJECTILE_TOUCH;
146         if(other.takedamage == DAMAGE_AIM)
147                 { W_Plasma_Explode(); }
148         else
149         {
150                 //UpdateCSQCProjectile(self);
151                 spamsound(self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTEN_NORM);
152                 self.projectiledeathtype |= HITTYPE_BOUNCE;
153         }
154 }
155
156 void W_Plasma_TouchExplode(void)
157 {
158         PROJECTILE_TOUCH;
159         W_Plasma_Explode();
160 }
161
162 void W_Plasma_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
163 {
164         if(self.health <= 0)
165                 return;
166
167         // note: combos are usually triggered by W_Plasma_TriggerCombo, not damage
168         float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim");
169
170         if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
171                 return; // g_projectiles_damage says to halt
172
173         self.health = self.health - damage;
174         if(self.health <= 0)
175         {
176                 self.takedamage = DAMAGE_NO;
177                 self.nextthink = time;
178                 if(is_combo)
179                 {
180                         // change owner to whoever caused the combo explosion
181                         self.realowner = inflictor.realowner;
182                         self.classname = "plasma_chain";
183                         self.think = W_Plasma_Explode_Combo;
184                         self.nextthink = time +
185                                 (
186                                         // bound the length, inflictor may be in a galaxy far far away (warpzones)
187                                         min(
188                                                 autocvar_g_balance_electro_combo_radius,
189                                                 vlen(self.origin - inflictor.origin)
190                                         )
191                                         /
192                                         // delay combo chains, looks cooler
193                                         autocvar_g_balance_electro_combo_speed
194                                 );
195                 }
196                 else
197                 {
198                         self.use = W_Plasma_Explode;
199                         self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately"
200                 }
201         }
202 }
203
204 void W_Electro_Attack()
205 {
206         entity proj;
207
208         W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_reload_ammo);
209
210         W_SetupShot_ProjectileSize(self, '0 0 -3', '0 0 -3', FALSE, 2, "weapons/electro_fire.wav", CH_WEAPON_A, autocvar_g_balance_electro_primary_damage);
211
212         pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
213
214         proj = spawn ();
215         proj.classname = "plasma_prim";
216         proj.owner = proj.realowner = self;
217         proj.bot_dodge = TRUE;
218         proj.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
219         proj.use = W_Plasma_Explode;
220         proj.think = adaptor_think2use_hittype_splash;
221         proj.nextthink = time + autocvar_g_balance_electro_primary_lifetime;
222         PROJECTILE_MAKETRIGGER(proj);
223         proj.projectiledeathtype = WEP_ELECTRO;
224         setorigin(proj, w_shotorg);
225
226         proj.movetype = MOVETYPE_FLY;
227         W_SETUPPROJECTILEVELOCITY(proj, g_balance_electro_primary);
228         proj.angles = vectoangles(proj.velocity);
229         proj.touch = W_Plasma_TouchExplode;
230         setsize(proj, '0 0 -3', '0 0 -3');
231         proj.flags = FL_PROJECTILE;
232         proj.missile_flags = MIF_SPLASH;
233
234         CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO_BEAM, TRUE);
235
236         other = proj; MUTATOR_CALLHOOK(EditProjectile);
237 }
238
239 void W_Electro_Attack2()
240 {
241         W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_secondary_ammo, autocvar_g_balance_electro_reload_ammo);
242
243         W_SetupShot_ProjectileSize(self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, autocvar_g_balance_electro_secondary_damage);
244
245         w_shotdir = v_forward; // no TrueAim for grenades please
246
247         pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
248
249         entity proj = spawn();
250         proj.classname = "plasma";
251         proj.owner = proj.realowner = self;
252         proj.use = W_Plasma_Explode;
253         proj.think = adaptor_think2use_hittype_splash;
254         proj.bot_dodge = TRUE;
255         proj.bot_dodgerating = autocvar_g_balance_electro_secondary_damage;
256         proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime;
257         PROJECTILE_MAKETRIGGER(proj);
258         proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY;
259         setorigin(proj, w_shotorg);
260
261         //proj.glow_size = 50;
262         //proj.glow_color = 45;
263         proj.movetype = MOVETYPE_BOUNCE;
264         W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary);
265         proj.touch = W_Plasma_Touch;
266         setsize(proj, '0 0 -4', '0 0 -4');
267         proj.takedamage = DAMAGE_YES;
268         proj.damageforcescale = autocvar_g_balance_electro_secondary_damageforcescale;
269         proj.health = autocvar_g_balance_electro_secondary_health;
270         proj.event_damage = W_Plasma_Damage;
271         proj.flags = FL_PROJECTILE;
272         proj.damagedbycontents = (autocvar_g_balance_electro_secondary_damagedbycontents);
273
274         proj.bouncefactor = autocvar_g_balance_electro_secondary_bouncefactor;
275         proj.bouncestop = autocvar_g_balance_electro_secondary_bouncestop;
276         proj.missile_flags = MIF_SPLASH | MIF_ARC;
277
278 #if 0
279         entity p2;
280         p2 = spawn();
281         copyentity(proj, p2);
282         setmodel(p2, "models/ebomb.mdl");
283         setsize(p2, proj.mins, proj.maxs);
284 #endif
285
286         CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound
287
288         other = proj; MUTATOR_CALLHOOK(EditProjectile);
289 }
290
291 void w_electro_checkattack()
292 {
293         if(self.electro_count > 1)
294         if(self.BUTTON_ATCK2)
295         if(weapon_prepareattack(1, -1))
296         {
297                 W_Electro_Attack2();
298                 self.electro_count -= 1;
299                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
300                 return;
301         }
302
303         w_ready();
304 }
305
306 .float bot_secondary_electromooth;
307 float w_electro(float req)
308 {
309         float ammo_amount;
310         switch(req)
311         {
312                 case WR_AIM:
313                 {
314                         self.BUTTON_ATCK=FALSE;
315                         self.BUTTON_ATCK2=FALSE;
316                         if(vlen(self.origin-self.enemy.origin) > 1000)
317                                 self.bot_secondary_electromooth = 0;
318                         if(self.bot_secondary_electromooth == 0)
319                         {
320                                 float shoot;
321
322                                 if(autocvar_g_balance_electro_primary_speed)
323                                         shoot = bot_aim(autocvar_g_balance_electro_primary_speed, 0, autocvar_g_balance_electro_primary_lifetime, FALSE);
324                                 else
325                                         shoot = bot_aim(1000000, 0, 0.001, FALSE);
326
327                                 if(shoot)
328                                 {
329                                         self.BUTTON_ATCK = TRUE;
330                                         if(random() < 0.01) self.bot_secondary_electromooth = 1;
331                                 }
332                         }
333                         else
334                         {
335                                 if(bot_aim(autocvar_g_balance_electro_secondary_speed, autocvar_g_balance_mortar_secondary_speed_up, autocvar_g_balance_electro_secondary_lifetime, TRUE)) // WHAT THE ACTUAL FUUUUUUUUUCK?!?!? WEAPONTODO
336                                 {
337                                         self.BUTTON_ATCK2 = TRUE;
338                                         if(random() < 0.03) self.bot_secondary_electromooth = 0;
339                                 }
340                         }
341                         
342                         return TRUE;
343                 }
344                 case WR_THINK:
345                 {
346                         if(autocvar_g_balance_electro_reload_ammo) // forced reload
347                         {
348                                 ammo_amount = 0;
349                                 if(self.clip_load >= autocvar_g_balance_electro_primary_ammo)
350                                         ammo_amount = 1;
351                                 if(self.clip_load >= autocvar_g_balance_electro_secondary_ammo)
352                                         ammo_amount += 1;
353
354                                 if(!ammo_amount)
355                                 {
356                                         WEP_ACTION(self.weapon, WR_RELOAD);
357                                         return FALSE;
358                                 }
359                                 
360                                 return TRUE;
361                         }
362                         if (self.BUTTON_ATCK)
363                         {
364                                 if(weapon_prepareattack(0, autocvar_g_balance_electro_primary_refire))
365                                 {
366                                                 W_Electro_Attack();
367                                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
368                                 }
369                         }
370                         else if(self.BUTTON_ATCK2)
371                         {
372                                 if (time >= self.electro_secondarytime)
373                                 if (weapon_prepareattack(1, autocvar_g_balance_electro_secondary_refire))
374                                 {
375                                         W_Electro_Attack2();
376                                         self.electro_count = autocvar_g_balance_electro_secondary_count;
377                                         weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
378                                         self.electro_secondarytime = time + autocvar_g_balance_electro_secondary_refire2 * W_WeaponRateFactor();
379                                 }
380                         }
381
382                         return TRUE;
383                 }
384                 case WR_INIT:
385                 {
386                         precache_model ("models/weapons/g_electro.md3");
387                         precache_model ("models/weapons/v_electro.md3");
388                         precache_model ("models/weapons/h_electro.iqm");
389                         precache_sound ("weapons/electro_bounce.wav");
390                         precache_sound ("weapons/electro_fire.wav");
391                         precache_sound ("weapons/electro_fire2.wav");
392                         precache_sound ("weapons/electro_impact.wav");
393                         precache_sound ("weapons/electro_impact_combo.wav");
394                         return TRUE;
395                 }
396                 case WR_SETUP:
397                 {
398                         self.current_ammo = ammo_cells;
399                         return TRUE;
400                 }
401                 case WR_CHECKAMMO1:
402                 {
403                         ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_primary_ammo;
404                         ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_primary_ammo;
405                         return ammo_amount;
406                 }
407                 case WR_CHECKAMMO2:
408                 {
409                         if(autocvar_g_balance_electro_combo_safeammocheck) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
410                         {
411                                 ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
412                                 ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
413                         }
414                         else
415                         {
416                                 ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo;
417                                 ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo;
418                         }
419                         return ammo_amount;
420                 }
421                 case WR_RESETPLAYER:
422                 {
423                         self.electro_secondarytime = time;
424                         return TRUE;
425                 }
426                 case WR_RELOAD:
427                 {
428                         W_Reload(min(autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_secondary_ammo), "weapons/reload.wav");
429                         return TRUE;
430                 }
431                 case WR_SUICIDEMESSAGE:
432                 {
433                         if(w_deathtype & HITTYPE_SECONDARY)
434                                 return WEAPON_ELECTRO_SUICIDE_ORBS;
435                         else
436                                 return WEAPON_ELECTRO_SUICIDE_BOLT;
437                 }
438                 case WR_KILLMESSAGE:
439                 {
440                         if(w_deathtype & HITTYPE_SECONDARY)
441                         {
442                                 return WEAPON_ELECTRO_MURDER_ORBS;
443                         }
444                         else
445                         {
446                                 if(w_deathtype & HITTYPE_BOUNCE)
447                                         return WEAPON_ELECTRO_MURDER_COMBO;
448                                 else
449                                         return WEAPON_ELECTRO_MURDER_BOLT;
450                         }
451                 }
452         }
453         return TRUE;
454 }
455 #endif
456 #ifdef CSQC
457 float w_electro(float req)
458 {
459         switch(req)
460         {
461                 case WR_IMPACTEFFECT:
462                 {
463                         vector org2;
464                         org2 = w_org + w_backoff * 6;
465                         if(w_deathtype & HITTYPE_SECONDARY)
466                         {
467                                 pointparticles(particleeffectnum("electro_ballexplode"), org2, '0 0 0', 1);
468                                 if(!w_issilent)
469                                         sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
470                         }
471                         else
472                         {
473                                 if(w_deathtype & HITTYPE_BOUNCE)
474                                 {
475                                         // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
476                                         pointparticles(particleeffectnum("electro_combo"), org2, '0 0 0', 1);
477                                         if(!w_issilent)
478                                                 sound(self, CH_SHOTS, "weapons/electro_impact_combo.wav", VOL_BASE, ATTEN_NORM);
479                                 }
480                                 else
481                                 {
482                                         pointparticles(particleeffectnum("electro_impact"), org2, '0 0 0', 1);
483                                         if(!w_issilent)
484                                                 sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
485                                 }
486                         }
487                         
488                         return TRUE;
489                 }
490                 case WR_INIT:
491                 {
492                         precache_sound("weapons/electro_impact.wav");
493                         precache_sound("weapons/electro_impact_combo.wav");
494                         return TRUE;
495                 }
496         }
497         return TRUE;
498 }
499 #endif
500 #endif