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