]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_fireball.qc
Commonize even more code, now that its possible
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_fireball.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(FIREBALL, w_fireball, IT_FUEL, 9, WEP_TYPE_SPLASH, BOT_PICKUP_RATING_MID, "fireball", "fireball", _("Fireball"));
3 #else
4 #ifdef SVQC
5 .float bot_primary_fireballmooth; // whatever a mooth is
6 .vector fireball_impactvec;
7 .float fireball_primarytime;
8
9 void W_Fireball_SetAmmoCounter()
10 {
11         // set clip_load to the weapon we have switched to, if the gun uses reloading
12         if(!autocvar_g_balance_fireball_reload_ammo)
13                 self.clip_load = 0; // also keeps crosshair ammo from displaying
14         else
15         {
16                 self.clip_load = self.weapon_load[WEP_FIREBALL];
17                 self.clip_size = autocvar_g_balance_fireball_reload_ammo; // for the crosshair ammo display
18         }
19 }
20
21 void W_Fireball_Reload()
22 {
23         // fuel can be a non-whole number, which brakes stuff here when between 0 and 1
24         if(self.ammo_fuel < 1)
25                 self.ammo_fuel = 0;
26
27         self.reload_ammo_player = ammo_fuel;
28         self.reload_ammo_min = min(autocvar_g_balance_fireball_primary_ammo, autocvar_g_balance_fireball_secondary_ammo);
29         self.reload_ammo_amount = autocvar_g_balance_fireball_reload_ammo;
30         self.reload_time = autocvar_g_balance_fireball_reload_time;
31         self.reload_sound = "weapons/reload.wav";
32
33         W_Reload();
34 }
35
36 void W_Fireball_Explode (void)
37 {
38         entity e;
39         float dist;
40         float points;
41         vector dir;
42         float d;
43
44         self.event_damage = SUB_Null;
45         self.takedamage = DAMAGE_NO;
46
47         // 1. dist damage
48         d = (self.owner.health + self.owner.armorvalue);
49         RadiusDamage (self, self.realowner, autocvar_g_balance_fireball_primary_damage, autocvar_g_balance_fireball_primary_edgedamage, autocvar_g_balance_fireball_primary_radius, world, autocvar_g_balance_fireball_primary_force, self.projectiledeathtype, other);
50         if(self.realowner.health + self.realowner.armorvalue >= d)
51         if(!self.cnt)
52         {
53                 modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, autocvar_g_balance_fireball_primary_bfgradius, 0.2, 0.05, 0.25);
54
55                 // 2. bfg effect
56                 // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
57                 for(e = findradius(self.origin, autocvar_g_balance_fireball_primary_bfgradius); e; e = e.chain)
58                 if(e != self.owner) if(e.takedamage == DAMAGE_AIM) if(e.classname != "player" || !self.owner || IsDifferentTeam(e, self))
59                 {
60                         // can we see fireball?
61                         traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
62                         if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
63                                 continue;
64                         // can we see player who shot fireball?
65                         traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
66                         if(trace_ent != self.realowner)
67                         if(/* trace_startsolid || */ trace_fraction != 1)
68                                 continue;
69                         dist = vlen(self.origin - e.origin - e.view_ofs);
70                         points = (1 - sqrt(dist / autocvar_g_balance_fireball_primary_bfgradius));
71                         if(points <= 0)
72                                 continue;
73                         dir = normalize(e.origin + e.view_ofs - self.origin);
74
75                         if(accuracy_isgooddamage(self.realowner, e))
76                                 accuracy_add(self.realowner, WEP_FIREBALL, 0, autocvar_g_balance_fireball_primary_bfgdamage * points);
77
78                         Damage(e, self, self.realowner, autocvar_g_balance_fireball_primary_bfgdamage * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, autocvar_g_balance_fireball_primary_bfgforce * dir);
79                         pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1);
80                 }
81         }
82
83         remove (self);
84 }
85
86 void W_Fireball_TouchExplode (void)
87 {
88         PROJECTILE_TOUCH;
89         W_Fireball_Explode ();
90 }
91
92 void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime)
93 {
94         entity e;
95         float d;
96         vector p;
97
98         if(damage <= 0)
99                 return;
100
101         RandomSelection_Init();
102         for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain)
103         if(e != self.owner) if(e.takedamage == DAMAGE_AIM) if(e.classname != "player" || !self.owner || IsDifferentTeam(e, self))
104         {
105                 p = e.origin;
106                 p_x += e.mins_x + random() * (e.maxs_x - e.mins_x);
107                 p_y += e.mins_y + random() * (e.maxs_y - e.mins_y);
108                 p_z += e.mins_z + random() * (e.maxs_z - e.mins_z);
109                 d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
110                 if(d < dist)
111                 {
112                         e.fireball_impactvec = p;
113                         RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
114                 }
115         }
116         if(RandomSelection_chosen_ent)
117         {
118                 d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
119                 d = damage + (edgedamage - damage) * (d / dist);
120                 Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
121                 //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
122                 pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
123         }
124 }
125
126 void W_Fireball_Think()
127 {
128         if(time > self.pushltime)
129         {
130                 self.cnt = 1;
131                 self.projectiledeathtype |= HITTYPE_SPLASH;
132                 W_Fireball_Explode();
133                 return;
134         }
135
136         W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_primary_laserradius, autocvar_g_balance_fireball_primary_laserdamage, autocvar_g_balance_fireball_primary_laseredgedamage, autocvar_g_balance_fireball_primary_laserburntime);
137
138         self.nextthink = time + 0.1;
139 }
140
141 void W_Fireball_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
142 {
143         if(self.health <= 0)
144                 return;
145         self.health = self.health - damage;
146         if (self.health <= 0)
147         {
148                 self.cnt = 1;
149                 W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
150         }
151 }
152
153 void W_Fireball_Attack1()
154 {
155         local entity proj;
156
157         W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 2, "weapons/fireball_fire2.wav", CHAN_WEAPON, autocvar_g_balance_fireball_primary_damage + autocvar_g_balance_fireball_primary_bfgdamage);
158
159         pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
160
161         proj = spawn ();
162         proj.classname = "plasma_prim";
163         proj.owner = proj.realowner = self;
164         proj.bot_dodge = TRUE;
165         proj.bot_dodgerating = autocvar_g_balance_fireball_primary_damage;
166         proj.pushltime = time + autocvar_g_balance_fireball_primary_lifetime;
167         proj.use = W_Fireball_Explode;
168         proj.think = W_Fireball_Think;
169         proj.nextthink = time;
170         proj.health = autocvar_g_balance_fireball_primary_health;
171         proj.team = self.team;
172         proj.event_damage = W_Fireball_Damage;
173         proj.takedamage = DAMAGE_YES;
174         proj.damageforcescale = autocvar_g_balance_fireball_primary_damageforcescale;
175         PROJECTILE_MAKETRIGGER(proj);
176         proj.projectiledeathtype = WEP_FIREBALL;
177         setorigin(proj, w_shotorg);
178
179         proj.movetype = MOVETYPE_FLY;
180         W_SETUPPROJECTILEVELOCITY(proj, g_balance_fireball_primary);
181         proj.angles = vectoangles(proj.velocity);
182         proj.touch = W_Fireball_TouchExplode;
183         setsize(proj, '-16 -16 -16', '16 16 16');
184         proj.flags = FL_PROJECTILE;
185
186         CSQCProjectile(proj, TRUE, PROJECTILE_FIREBALL, TRUE);
187
188         other = proj; MUTATOR_CALLHOOK(EditProjectile);
189 }
190
191 void W_Fireball_AttackEffect(float i, vector f_diff)
192 {
193         W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 0, "", 0, 0);
194         w_shotorg += f_diff_x * v_up + f_diff_y * v_right;
195         pointparticles(particleeffectnum("fireball_preattack_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
196 }
197
198 void W_Fireball_Attack1_Frame4()
199 {
200         W_Fireball_Attack1();
201         weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, w_ready);
202 }
203
204 void W_Fireball_Attack1_Frame3()
205 {
206         W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
207         weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame4);
208 }
209
210 void W_Fireball_Attack1_Frame2()
211 {
212         W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
213         weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame3);
214 }
215
216 void W_Fireball_Attack1_Frame1()
217 {
218         W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
219         weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame2);
220 }
221
222 void W_Fireball_Attack1_Frame0()
223 {
224         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
225         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
226         {
227                 if(autocvar_g_balance_fireball_reload_ammo)
228                 {
229                         self.clip_load -= autocvar_g_balance_fireball_primary_ammo;
230                         self.weapon_load[WEP_FIREBALL] = self.clip_load;
231                 }
232                 else
233                         self.ammo_fuel -= autocvar_g_balance_fireball_primary_ammo;
234         }
235
236         W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
237         sound (self, CHAN_WEAPON, "weapons/fireball_prefire2.wav", VOL_BASE, ATTN_NORM);
238         weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame1);
239 }
240
241 void W_Firemine_Think()
242 {
243         if(time > self.pushltime)
244         {
245                 remove(self);
246                 return;
247         }
248
249         // make it "hot" once it leaves its owner
250         if(self.owner)
251         {
252                 if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > autocvar_g_balance_fireball_secondary_laserradius)
253                 {
254                         self.cnt += 1;
255                         if(self.cnt == 3)
256                                 self.owner = world;
257                 }
258                 else
259                         self.cnt = 0;
260         }
261
262         W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_secondary_laserradius, autocvar_g_balance_fireball_secondary_laserdamage, autocvar_g_balance_fireball_secondary_laseredgedamage, autocvar_g_balance_fireball_secondary_laserburntime);
263
264         self.nextthink = time + 0.1;
265 }
266
267 void W_Firemine_Touch (void)
268 {
269         PROJECTILE_TOUCH;
270         if (other.takedamage == DAMAGE_AIM)
271         if(Fire_AddDamage(other, self.realowner, autocvar_g_balance_fireball_secondary_damage, autocvar_g_balance_fireball_secondary_damagetime, self.projectiledeathtype | HITTYPE_HEADSHOT) >= 0)
272         {
273                 remove(self);
274                 return;
275         }
276         self.projectiledeathtype |= HITTYPE_BOUNCE;
277 }
278
279 void W_Fireball_Attack2()
280 {
281         local entity proj;
282         vector f_diff;
283         float c;
284
285         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
286         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
287         {
288                 if(autocvar_g_balance_fireball_reload_ammo)
289                 {
290                         self.clip_load -= autocvar_g_balance_fireball_secondary_ammo;
291                         self.weapon_load[WEP_FIREBALL] = self.clip_load;
292                 }
293                 else
294                         self.ammo_fuel -= autocvar_g_balance_fireball_secondary_ammo;
295         }
296
297         c = mod(self.bulletcounter, 4);
298         switch(c)
299         {
300                 case 0:
301                         f_diff = '-1.25 -3.75 0';
302                         break;
303                 case 1:
304                         f_diff = '+1.25 -3.75 0';
305                         break;
306                 case 2:
307                         f_diff = '-1.25 +3.75 0';
308                         break;
309                 case 3:
310                 default:
311                         f_diff = '+1.25 +3.75 0';
312                         break;
313         }
314         W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, "weapons/fireball_fire.wav", CHAN_WEAPON, autocvar_g_balance_fireball_secondary_damage);
315         traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self);
316         w_shotorg = trace_endpos;
317
318         pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
319
320         proj = spawn ();
321         proj.owner = proj.realowner = self;
322         proj.classname = "grenade";
323         proj.bot_dodge = TRUE;
324         proj.bot_dodgerating = autocvar_g_balance_fireball_secondary_damage;
325         proj.movetype = MOVETYPE_BOUNCE;
326         proj.projectiledeathtype = WEP_FIREBALL | HITTYPE_SECONDARY;
327         proj.touch = W_Firemine_Touch;
328         PROJECTILE_MAKETRIGGER(proj);
329         setsize(proj, '-4 -4 -4', '4 4 4');
330         setorigin(proj, w_shotorg);
331         proj.think = W_Firemine_Think;
332         proj.nextthink = time;
333         proj.damageforcescale = autocvar_g_balance_fireball_secondary_damageforcescale;
334         proj.pushltime = time + autocvar_g_balance_fireball_secondary_lifetime;
335         W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_fireball_secondary);
336
337         proj.angles = vectoangles(proj.velocity);
338         proj.flags = FL_PROJECTILE;
339
340         CSQCProjectile(proj, TRUE, PROJECTILE_FIREMINE, TRUE);
341
342         other = proj; MUTATOR_CALLHOOK(EditProjectile);
343 }
344
345 void spawnfunc_weapon_fireball (void)
346 {
347         weapon_defaultspawnfunc(WEP_FIREBALL);
348 }
349
350 float w_fireball(float req)
351 {
352         float ammo_amount;
353         if (req == WR_AIM)
354         {
355                 self.BUTTON_ATCK = FALSE;
356                 self.BUTTON_ATCK2 = FALSE;
357                 if (self.bot_primary_fireballmooth == 0)
358                 {
359                         if(bot_aim(autocvar_g_balance_fireball_primary_speed, 0, autocvar_g_balance_fireball_primary_lifetime, FALSE))
360                         {
361                                 self.BUTTON_ATCK = TRUE;
362                                 if(random() < 0.02) self.bot_primary_fireballmooth = 0;
363                         }
364                 }
365                 else
366                 {
367                         if(bot_aim(autocvar_g_balance_fireball_secondary_speed, autocvar_g_balance_fireball_secondary_speed_up, autocvar_g_balance_fireball_secondary_lifetime, TRUE))
368                         {
369                                 self.BUTTON_ATCK2 = TRUE;
370                                 if(random() < 0.01) self.bot_primary_fireballmooth = 1;
371                         }
372                 }
373         }
374         else if (req == WR_THINK)
375         {
376                 if(autocvar_g_balance_fireball_reload_ammo && self.clip_load < min(autocvar_g_balance_fireball_primary_ammo, autocvar_g_balance_fireball_secondary_ammo)) // forced reload
377                         W_Fireball_Reload();
378                 else if (self.BUTTON_ATCK)
379                 {
380                         if (time >= self.fireball_primarytime)
381                         if (weapon_prepareattack(0, autocvar_g_balance_fireball_primary_refire))
382                         {
383                                 W_Fireball_Attack1_Frame0();
384                                 self.fireball_primarytime = time + autocvar_g_balance_fireball_primary_refire2;
385                         }
386                 }
387                 else if (self.BUTTON_ATCK2)
388                 {
389                         if (weapon_prepareattack(1, autocvar_g_balance_fireball_secondary_refire))
390                         {
391                                 W_Fireball_Attack2();
392                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_fireball_secondary_animtime, w_ready);
393                         }
394                 }
395         }
396         else if (req == WR_PRECACHE)
397         {
398                 precache_model ("models/weapons/g_fireball.md3");
399                 precache_model ("models/weapons/v_fireball.md3");
400                 precache_model ("models/weapons/h_fireball.iqm");
401                 precache_model ("models/sphere/sphere.md3");
402                 precache_sound ("weapons/fireball_fire.wav");
403                 precache_sound ("weapons/fireball_fire2.wav");
404                 precache_sound ("weapons/fireball_prefire2.wav");
405                 precache_sound ("weapons/reload.wav");
406         }
407         else if (req == WR_SETUP)
408         {
409                 weapon_setup(WEP_FIREBALL);
410                 W_Fireball_SetAmmoCounter();
411         }
412         else if (req == WR_CHECKAMMO1)
413         {
414                 ammo_amount = self.ammo_fuel >= autocvar_g_balance_fireball_primary_ammo;
415                 ammo_amount += self.weapon_load[WEP_FIREBALL] >= autocvar_g_balance_fireball_primary_ammo;
416                 return ammo_amount;
417         }
418         else if (req == WR_CHECKAMMO2)
419         {
420                 ammo_amount = self.ammo_fuel >= autocvar_g_balance_fireball_secondary_ammo;
421                 ammo_amount += self.weapon_load[WEP_FIREBALL] >= autocvar_g_balance_fireball_secondary_ammo;
422                 return ammo_amount;
423         }
424         else if (req == WR_RESETPLAYER)
425         {
426                 self.fireball_primarytime = time;
427         }
428         else if (req == WR_RELOAD)
429         {
430                 W_Fireball_Reload();
431         }
432         return TRUE;
433 };
434 #endif
435 #ifdef CSQC
436 float w_fireball(float req)
437 {
438         if(req == WR_IMPACTEFFECT)
439         {
440                 vector org2;
441                 if(w_deathtype & HITTYPE_SECONDARY)
442                 {
443                         // firemine goes out silently
444                 }
445                 else
446                 {
447                         org2 = w_org + w_backoff * 16;
448                         pointparticles(particleeffectnum("fireball_explode"), org2, '0 0 0', 1);
449                         if(!w_issilent)
450                                 sound(self, CHAN_PROJECTILE, "weapons/fireball_impact2.wav", VOL_BASE, ATTN_NORM * 0.25); // long range boom
451                 }
452         }
453         else if(req == WR_PRECACHE)
454         {
455                 precache_sound("weapons/fireball_impact2.wav");
456         }
457         else if (req == WR_SUICIDEMESSAGE)
458         {
459                 if(w_deathtype & HITTYPE_SECONDARY)
460                         w_deathtypestring = _("%s forgot about some firemine");
461                 else
462                         w_deathtypestring = _("%s should have used a smaller gun");
463         }
464         else if (req == WR_KILLMESSAGE)
465         {
466                 if(w_deathtype & HITTYPE_SECONDARY)
467                 {
468                         if(w_deathtype & HITTYPE_HEADSHOT)
469                                 w_deathtypestring = _("%s tried to catch %s's firemine");
470                         else
471                                 w_deathtypestring = _("%s fatefully ignored %s's firemine");
472                 }
473                 else
474                 {
475                         if(w_deathtype & HITTYPE_BOUNCE)
476                         {
477                                 if(w_deathtype & HITTYPE_SPLASH) // BFG effect
478                                         w_deathtypestring = _("%s could not hide from %s's fireball");
479                                 else // laser
480                                         w_deathtypestring = _("%s saw the pretty lights of %s's fireball");
481                         }
482                         else if(w_deathtype & HITTYPE_SPLASH)
483                                 w_deathtypestring = _("%s got too close to %s's fireball");
484                         else
485                                 w_deathtypestring = _("%s tasted %s's fireball");
486                 }
487         }
488         return TRUE;
489 }
490 #endif
491 #endif