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