]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/fireball.qc
Merge branch 'master' into Mario/bulldozer
[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, m_name, 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 spawnfunc(weapon_fireball) { weapon_defaultspawnfunc(this, WEP_FIREBALL); }
61
62 void W_Fireball_Explode()
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()
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()
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()
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 = new(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(Weapon thiswep, entity actor, .entity weaponentity, int fire)
229 {
230         W_Fireball_Attack1();
231         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready);
232 }
233
234 void W_Fireball_Attack1_Frame3(Weapon thiswep, entity actor, .entity weaponentity, int fire)
235 {
236         W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
237         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4);
238 }
239
240 void W_Fireball_Attack1_Frame2(Weapon thiswep, entity actor, .entity weaponentity, int fire)
241 {
242         W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
243         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3);
244 }
245
246 void W_Fireball_Attack1_Frame1(Weapon thiswep, entity actor, .entity weaponentity, int fire)
247 {
248         W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
249         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2);
250 }
251
252 void W_Fireball_Attack1_Frame0(Weapon thiswep, entity actor, .entity weaponentity, int fire)
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(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1);
257 }
258
259 void W_Fireball_Firemine_Think()
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()
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()
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 = new(grenade);
327         proj.owner = proj.realowner = self;
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                 METHOD(Fireball, wr_aim, void(entity thiswep))
352                 {
353                         self.BUTTON_ATCK = false;
354                         self.BUTTON_ATCK2 = false;
355                         if(self.bot_primary_fireballmooth == 0)
356                         {
357                                 if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false))
358                                 {
359                                         self.BUTTON_ATCK = true;
360                                         if(random() < 0.02) self.bot_primary_fireballmooth = 0;
361                                 }
362                         }
363                         else
364                         {
365                                 if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true))
366                                 {
367                                         self.BUTTON_ATCK2 = true;
368                                         if(random() < 0.01) self.bot_primary_fireballmooth = 1;
369                                 }
370                         }
371                 }
372                 METHOD(Fireball, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
373                 {
374                         if(fire & 1)
375                         {
376                                 if(time >= actor.fireball_primarytime)
377                                 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(fireball, refire)))
378                                 {
379                                         W_Fireball_Attack1_Frame0(thiswep, actor, weaponentity, fire);
380                                         actor.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor();
381                                 }
382                         }
383                         else if(fire & 2)
384                         {
385                                 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(fireball, refire)))
386                                 {
387                                         W_Fireball_Attack2();
388                                         weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready);
389                                 }
390                         }
391                 }
392                 METHOD(Fireball, wr_init, void(entity thiswep))
393                 {
394                         FIREBALL_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
395                 }
396                 METHOD(Fireball, wr_setup, void(entity thiswep))
397                 {
398                         self.ammo_field = ammo_none;
399                 }
400                 METHOD(Fireball, wr_checkammo1, bool(entity thiswep))
401                 {
402                         return true; // infinite ammo
403                 }
404                 METHOD(Fireball, wr_checkammo2, bool(entity thiswep))
405                 {
406                         return true; // fireball has infinite ammo
407                 }
408                 METHOD(Fireball, wr_config, void(entity thiswep))
409                 {
410                         FIREBALL_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
411                 }
412                 METHOD(Fireball, wr_resetplayer, void(entity thiswep))
413                 {
414                         self.fireball_primarytime = time;
415                 }
416                 METHOD(Fireball, wr_suicidemessage, int(entity thiswep))
417                 {
418                         if(w_deathtype & HITTYPE_SECONDARY)
419                                 return WEAPON_FIREBALL_SUICIDE_FIREMINE;
420                         else
421                                 return WEAPON_FIREBALL_SUICIDE_BLAST;
422                 }
423                 METHOD(Fireball, wr_killmessage, int(entity thiswep))
424                 {
425                         if(w_deathtype & HITTYPE_SECONDARY)
426                                 return WEAPON_FIREBALL_MURDER_FIREMINE;
427                         else
428                                 return WEAPON_FIREBALL_MURDER_BLAST;
429                 }
430
431 #endif
432 #ifdef CSQC
433
434                 METHOD(Fireball, wr_impacteffect, void(entity thiswep))
435                 {
436                         vector org2;
437                         if(w_deathtype & HITTYPE_SECONDARY)
438                         {
439                                 // firemine goes out silently
440                         }
441                         else
442                         {
443                                 org2 = w_org + w_backoff * 16;
444                                 pointparticles(EFFECT_FIREBALL_EXPLODE, org2, '0 0 0', 1);
445                                 if(!w_issilent)
446                                         sound(self, CH_SHOTS, SND_FIREBALL_IMPACT2, VOL_BASE, ATTEN_NORM * 0.25); // long range boom
447                         }
448                 }
449
450 #endif
451 #endif