]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/fireball.qc
0cc765f955da106e7a36731059d16878a674884c
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / fireball.qc
1 #ifndef IMPLEMENTATION
2 REGISTER_WEAPON(
3 /* WEP_##id  */ FIREBALL,
4 /* function  */ W_Fireball,
5 /* ammotype  */ ammo_none,
6 /* impulse   */ 9,
7 /* flags     */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH,
8 /* rating    */ BOT_PICKUP_RATING_MID,
9 /* color     */ '1 0.5 0',
10 /* modelname */ "fireball",
11 /* model     */ MDL_FIREBALL_ITEM,
12 /* simplemdl */ "foobar",
13 /* crosshair */ "gfx/crosshairfireball",
14 /* wepimg    */ "weaponfireball",
15 /* refname   */ "fireball",
16 /* wepname   */ _("Fireball")
17 );
18
19 #define FIREBALL_SETTINGS(w_cvar,w_prop) FIREBALL_SETTINGS_LIST(w_cvar, w_prop, FIREBALL, fireball)
20 #define FIREBALL_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
21         w_cvar(id, sn, BOTH, animtime) \
22         w_cvar(id, sn, BOTH, refire) \
23         w_cvar(id, sn, BOTH, damage) \
24         w_cvar(id, sn, BOTH, damageforcescale) \
25         w_cvar(id, sn, BOTH, speed) \
26         w_cvar(id, sn, BOTH, spread) \
27         w_cvar(id, sn, BOTH, lifetime) \
28         w_cvar(id, sn, BOTH, laserburntime) \
29         w_cvar(id, sn, BOTH, laserdamage) \
30         w_cvar(id, sn, BOTH, laseredgedamage) \
31         w_cvar(id, sn, BOTH, laserradius) \
32         w_cvar(id, sn, PRI,  edgedamage) \
33         w_cvar(id, sn, PRI,  force) \
34         w_cvar(id, sn, PRI,  radius) \
35         w_cvar(id, sn, PRI,  health) \
36         w_cvar(id, sn, PRI,  refire2) \
37         w_cvar(id, sn, PRI,  bfgdamage) \
38         w_cvar(id, sn, PRI,  bfgforce) \
39         w_cvar(id, sn, PRI,  bfgradius) \
40         w_cvar(id, sn, SEC,  damagetime) \
41         w_cvar(id, sn, SEC,  speed_up) \
42         w_cvar(id, sn, SEC,  speed_z) \
43         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
44         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
45         w_prop(id, sn, string, weaponreplace, weaponreplace) \
46         w_prop(id, sn, float,  weaponstart, weaponstart) \
47         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
48         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
49
50 #ifdef SVQC
51 FIREBALL_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
52 .float bot_primary_fireballmooth; // whatever a mooth is
53 .vector fireball_impactvec;
54 .float fireball_primarytime;
55 #endif
56 #endif
57 #ifdef IMPLEMENTATION
58 #ifdef SVQC
59 void spawnfunc_weapon_fireball(void) { weapon_defaultspawnfunc(WEP_FIREBALL.m_id); }
60
61 void W_Fireball_Explode(void)
62 {SELFPARAM();
63         entity e;
64         float dist;
65         float points;
66         vector dir;
67         float d;
68
69         self.event_damage = func_null;
70         self.takedamage = DAMAGE_NO;
71
72         // 1. dist damage
73         d = (self.realowner.health + self.realowner.armorvalue);
74         RadiusDamage(self, self.realowner, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), world, world, WEP_CVAR_PRI(fireball, force), self.projectiledeathtype, other);
75         if(self.realowner.health + self.realowner.armorvalue >= d)
76         if(!self.cnt)
77         {
78                 modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
79
80                 // 2. bfg effect
81                 // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
82                 for(e = findradius(self.origin, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain)
83                 if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
84                 {
85                         // can we see fireball?
86                         traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
87                         if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
88                                 continue;
89                         // can we see player who shot fireball?
90                         traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
91                         if(trace_ent != self.realowner)
92                         if(/* trace_startsolid || */ trace_fraction != 1)
93                                 continue;
94                         dist = vlen(self.origin - e.origin - e.view_ofs);
95                         points = (1 - sqrt(dist / WEP_CVAR_PRI(fireball, bfgradius)));
96                         if(points <= 0)
97                                 continue;
98                         dir = normalize(e.origin + e.view_ofs - self.origin);
99
100                         if(accuracy_isgooddamage(self.realowner, e))
101                                 accuracy_add(self.realowner, WEP_FIREBALL.m_id, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
102
103                         Damage(e, self, self.realowner, WEP_CVAR_PRI(fireball, bfgdamage) * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, WEP_CVAR_PRI(fireball, bfgforce) * dir);
104                         Send_Effect(EFFECT_FIREBALL_BFGDAMAGE, e.origin, -1 * dir, 1);
105                 }
106         }
107
108         remove(self);
109 }
110
111 void W_Fireball_TouchExplode(void)
112 {
113         PROJECTILE_TOUCH;
114         W_Fireball_Explode();
115 }
116
117 void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime)
118 {SELFPARAM();
119         entity e;
120         float d;
121         vector p;
122
123         if(damage <= 0)
124                 return;
125
126         RandomSelection_Init();
127         for(e = WarpZone_FindRadius(self.origin, dist, true); e; e = e.chain)
128         if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
129         {
130                 p = e.origin;
131                 p.x += e.mins.x + random() * (e.maxs.x - e.mins.x);
132                 p.y += e.mins.y + random() * (e.maxs.y - e.mins.y);
133                 p.z += e.mins.z + random() * (e.maxs.z - e.mins.z);
134                 d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
135                 if(d < dist)
136                 {
137                         e.fireball_impactvec = p;
138                         RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
139                 }
140         }
141         if(RandomSelection_chosen_ent)
142         {
143                 d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
144                 d = damage + (edgedamage - damage) * (d / dist);
145                 Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
146                 //trailparticles(self, particleeffectnum(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
147                 Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
148         }
149 }
150
151 void W_Fireball_Think(void)
152 {SELFPARAM();
153         if(time > self.pushltime)
154         {
155                 self.cnt = 1;
156                 self.projectiledeathtype |= HITTYPE_SPLASH;
157                 W_Fireball_Explode();
158                 return;
159         }
160
161         W_Fireball_LaserPlay(0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime));
162
163         self.nextthink = time + 0.1;
164 }
165
166 void W_Fireball_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
167 {SELFPARAM();
168         if(self.health <= 0)
169                 return;
170
171         if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
172                 return; // g_projectiles_damage says to halt
173
174         self.health = self.health - damage;
175         if(self.health <= 0)
176         {
177                 self.cnt = 1;
178                 W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
179         }
180 }
181
182 void W_Fireball_Attack1(void)
183 {SELFPARAM();
184         entity proj;
185
186         W_SetupShot_ProjectileSize(self, '-16 -16 -16', '16 16 16', false, 2, SND(FIREBALL_FIRE2), CH_WEAPON_A, WEP_CVAR_PRI(fireball, damage) + WEP_CVAR_PRI(fireball, bfgdamage));
187
188         Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
189
190         proj = spawn();
191         proj.classname = "plasma_prim";
192         proj.owner = proj.realowner = self;
193         proj.bot_dodge = true;
194         proj.bot_dodgerating = WEP_CVAR_PRI(fireball, damage);
195         proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime);
196         proj.use = W_Fireball_Explode;
197         proj.think = W_Fireball_Think;
198         proj.nextthink = time;
199         proj.health = WEP_CVAR_PRI(fireball, health);
200         proj.team = self.team;
201         proj.event_damage = W_Fireball_Damage;
202         proj.takedamage = DAMAGE_YES;
203         proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale);
204         PROJECTILE_MAKETRIGGER(proj);
205         proj.projectiledeathtype = WEP_FIREBALL.m_id;
206         setorigin(proj, w_shotorg);
207
208         proj.movetype = MOVETYPE_FLY;
209         W_SetupProjVelocity_PRI(proj, fireball);
210         proj.angles = vectoangles(proj.velocity);
211         proj.touch = W_Fireball_TouchExplode;
212         setsize(proj, '-16 -16 -16', '16 16 16');
213         proj.flags = FL_PROJECTILE;
214     proj.missile_flags = MIF_SPLASH | MIF_PROXY;
215
216         CSQCProjectile(proj, true, PROJECTILE_FIREBALL, true);
217
218         MUTATOR_CALLHOOK(EditProjectile, self, proj);
219 }
220
221 void W_Fireball_AttackEffect(float i, vector f_diff)
222 {SELFPARAM();
223         W_SetupShot_ProjectileSize(self, '-16 -16 -16', '16 16 16', false, 0, "", 0, 0);
224         w_shotorg += f_diff.x * v_up + f_diff.y * v_right;
225         Send_Effect(EFFECT_FIREBALL_PRE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
226 }
227
228 void W_Fireball_Attack1_Frame4(void)
229 {
230         W_Fireball_Attack1();
231         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready);
232 }
233
234 void W_Fireball_Attack1_Frame3(void)
235 {
236         W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
237         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4);
238 }
239
240 void W_Fireball_Attack1_Frame2(void)
241 {
242         W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
243         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3);
244 }
245
246 void W_Fireball_Attack1_Frame1(void)
247 {
248         W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
249         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2);
250 }
251
252 void W_Fireball_Attack1_Frame0(void)
253 {SELFPARAM();
254         W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
255         sound(self, CH_WEAPON_SINGLE, SND_FIREBALL_PREFIRE2, VOL_BASE, ATTEN_NORM);
256         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1);
257 }
258
259 void W_Fireball_Firemine_Think(void)
260 {SELFPARAM();
261         if(time > self.pushltime)
262         {
263                 remove(self);
264                 return;
265         }
266
267         // make it "hot" once it leaves its owner
268         if(self.owner)
269         {
270                 if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > WEP_CVAR_SEC(fireball, laserradius))
271                 {
272                         self.cnt += 1;
273                         if(self.cnt == 3)
274                                 self.owner = world;
275                 }
276                 else
277                         self.cnt = 0;
278         }
279
280         W_Fireball_LaserPlay(0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime));
281
282         self.nextthink = time + 0.1;
283 }
284
285 void W_Fireball_Firemine_Touch(void)
286 {SELFPARAM();
287         PROJECTILE_TOUCH;
288         if(other.takedamage == DAMAGE_AIM)
289         if(Fire_AddDamage(other, self.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), self.projectiledeathtype) >= 0)
290         {
291                 remove(self);
292                 return;
293         }
294         self.projectiledeathtype |= HITTYPE_BOUNCE;
295 }
296
297 void W_Fireball_Attack2(void)
298 {SELFPARAM();
299         entity proj;
300         vector f_diff;
301         float c;
302
303         c = self.bulletcounter % 4;
304         switch(c)
305         {
306                 case 0:
307                         f_diff = '-1.25 -3.75 0';
308                         break;
309                 case 1:
310                         f_diff = '+1.25 -3.75 0';
311                         break;
312                 case 2:
313                         f_diff = '-1.25 +3.75 0';
314                         break;
315                 case 3:
316                 default:
317                         f_diff = '+1.25 +3.75 0';
318                         break;
319         }
320         W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', false, 2, SND(FIREBALL_FIRE), CH_WEAPON_A, WEP_CVAR_SEC(fireball, damage));
321         traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self);
322         w_shotorg = trace_endpos;
323
324         Send_Effect(EFFECT_FIREBALL_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
325
326         proj = spawn();
327         proj.owner = proj.realowner = self;
328         proj.classname = "grenade";
329         proj.bot_dodge = true;
330         proj.bot_dodgerating = WEP_CVAR_SEC(fireball, damage);
331         proj.movetype = MOVETYPE_BOUNCE;
332         proj.projectiledeathtype = WEP_FIREBALL.m_id | HITTYPE_SECONDARY;
333         proj.touch = W_Fireball_Firemine_Touch;
334         PROJECTILE_MAKETRIGGER(proj);
335         setsize(proj, '-4 -4 -4', '4 4 4');
336         setorigin(proj, w_shotorg);
337         proj.think = W_Fireball_Firemine_Think;
338         proj.nextthink = time;
339         proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale);
340         proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime);
341         W_SetupProjVelocity_UP_SEC(proj, fireball);
342
343         proj.angles = vectoangles(proj.velocity);
344         proj.flags = FL_PROJECTILE;
345     proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
346
347         CSQCProjectile(proj, true, PROJECTILE_FIREMINE, true);
348
349         MUTATOR_CALLHOOK(EditProjectile, self, proj);
350 }
351
352 bool W_Fireball(entity thiswep, int req)
353 {SELFPARAM();
354         switch(req)
355         {
356                 case WR_AIM:
357                 {
358                         self.BUTTON_ATCK = false;
359                         self.BUTTON_ATCK2 = false;
360                         if(self.bot_primary_fireballmooth == 0)
361                         {
362                                 if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false))
363                                 {
364                                         self.BUTTON_ATCK = true;
365                                         if(random() < 0.02) self.bot_primary_fireballmooth = 0;
366                                 }
367                         }
368                         else
369                         {
370                                 if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true))
371                                 {
372                                         self.BUTTON_ATCK2 = true;
373                                         if(random() < 0.01) self.bot_primary_fireballmooth = 1;
374                                 }
375                         }
376
377                         return true;
378                 }
379                 case WR_THINK:
380                 {
381                         if(self.BUTTON_ATCK)
382                         {
383                                 if(time >= self.fireball_primarytime)
384                                 if(weapon_prepareattack(0, WEP_CVAR_PRI(fireball, refire)))
385                                 {
386                                         W_Fireball_Attack1_Frame0();
387                                         self.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor();
388                                 }
389                         }
390                         else if(self.BUTTON_ATCK2)
391                         {
392                                 if(weapon_prepareattack(1, WEP_CVAR_SEC(fireball, refire)))
393                                 {
394                                         W_Fireball_Attack2();
395                                         weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready);
396                                 }
397                         }
398
399                         return true;
400                 }
401                 case WR_INIT:
402                 {
403                         FIREBALL_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
404                         return true;
405                 }
406                 case WR_SETUP:
407                 {
408                         self.ammo_field = ammo_none;
409                         return true;
410                 }
411                 case WR_CHECKAMMO1:
412                 case WR_CHECKAMMO2:
413                 {
414                         return true; // fireball has infinite ammo
415                 }
416                 case WR_CONFIG:
417                 {
418                         FIREBALL_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
419                         return true;
420                 }
421                 case WR_RESETPLAYER:
422                 {
423                         self.fireball_primarytime = time;
424                         return true;
425                 }
426                 case WR_SUICIDEMESSAGE:
427                 {
428                         if(w_deathtype & HITTYPE_SECONDARY)
429                                 return WEAPON_FIREBALL_SUICIDE_FIREMINE;
430                         else
431                                 return WEAPON_FIREBALL_SUICIDE_BLAST;
432                 }
433                 case WR_KILLMESSAGE:
434                 {
435                         if(w_deathtype & HITTYPE_SECONDARY)
436                                 return WEAPON_FIREBALL_MURDER_FIREMINE;
437                         else
438                                 return WEAPON_FIREBALL_MURDER_BLAST;
439                 }
440         }
441         return false;
442 }
443 #endif
444 #ifdef CSQC
445 bool W_Fireball(entity thiswep, int req)
446 {SELFPARAM();
447         switch(req)
448         {
449                 case WR_IMPACTEFFECT:
450                 {
451                         vector org2;
452                         if(w_deathtype & HITTYPE_SECONDARY)
453                         {
454                                 // firemine goes out silently
455                         }
456                         else
457                         {
458                                 org2 = w_org + w_backoff * 16;
459                                 pointparticles(particleeffectnum(EFFECT_FIREBALL_EXPLODE), org2, '0 0 0', 1);
460                                 if(!w_issilent)
461                                         sound(self, CH_SHOTS, SND_FIREBALL_IMPACT2, VOL_BASE, ATTEN_NORM * 0.25); // long range boom
462                         }
463
464                         return true;
465                 }
466                 case WR_INIT:
467                 {
468                         return true;
469                 }
470                 case WR_ZOOMRETICLE:
471                 {
472                         // no weapon specific image for this weapon
473                         return false;
474                 }
475         }
476         return false;
477 }
478 #endif
479 #endif