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