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