]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/fireball.qc
weapon independency fixes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / fireball.qc
1 #include "fireball.qh"
2
3 #ifdef SVQC
4
5 void W_Fireball_Explode(entity this, entity directhitentity)
6 {
7         entity e;
8         float dist;
9         float points;
10         vector dir;
11         float d;
12
13         this.event_damage = func_null;
14         this.takedamage = DAMAGE_NO;
15
16         // 1. dist damage
17         d = (GetResource(this.realowner, RES_HEALTH) + GetResource(this.realowner, RES_ARMOR));
18
19         RadiusDamage(
20                 this,
21                 this.realowner,
22                 WEP_CVAR_PRI(fireball, damage),
23                 WEP_CVAR_PRI(fireball, edgedamage),
24                 WEP_CVAR_PRI(fireball, radius),
25                 NULL,
26                 NULL,
27                 WEP_CVAR_PRI(fireball, force),
28                 this.projectiledeathtype,
29                 this.weaponentity_fld, directhitentity
30         );
31
32         if(GetResource(this.realowner, RES_HEALTH) + GetResource(this.realowner, RES_ARMOR) >= d)
33         if(!this.cnt)
34         {
35                 modeleffect_spawn("models/sphere/sphere.md3", 0, 0, this.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
36
37                 // 2. bfg effect
38                 // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
39                 for(e = findradius(this.origin, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain)
40                 {
41                         if(e != this.realowner && e.takedamage == DAMAGE_AIM && !IS_INDEPENDENT_PLAYER(e))
42                         if(!IS_PLAYER(e) || !this.realowner || DIFF_TEAM(e, this))
43                         {
44
45                                 // can we see fireball?
46                                 traceline(e.origin + e.view_ofs, this.origin, MOVE_NORMAL, e);
47                                 if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
48                                         continue;
49                                 // can we see player who shot fireball?
50                                 traceline(e.origin + e.view_ofs, this.realowner.origin + this.realowner.view_ofs, MOVE_NORMAL, e);
51                                 if(trace_ent != this.realowner)
52                                 if(/* trace_startsolid || */ trace_fraction != 1)
53                                         continue;
54                                 dist = vlen(this.origin - e.origin - e.view_ofs);
55                                 points = (1 - sqrt(dist / WEP_CVAR_PRI(fireball, bfgradius)));
56                                 if(points <= 0)
57                                         continue;
58                                 dir = normalize(e.origin + e.view_ofs - this.origin);
59
60                                 if(accuracy_isgooddamage(this.realowner, e))
61                                         accuracy_add(this.realowner, WEP_FIREBALL, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
62
63                                 Damage(
64                                         e,
65                                         this,
66                                         this.realowner,
67                                         WEP_CVAR_PRI(fireball, bfgdamage) * points,
68                                         this.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH,
69                                         this.weaponentity_fld,
70                                         e.origin + e.view_ofs,
71                                         WEP_CVAR_PRI(fireball, bfgforce) * dir
72                                 );
73
74                                 Send_Effect(EFFECT_FIREBALL_BFGDAMAGE, e.origin, -1 * dir, 1);
75                         }
76                 }
77         }
78
79         delete(this);
80 }
81
82 void W_Fireball_Explode_think(entity this)
83 {
84         W_Fireball_Explode(this, NULL);
85 }
86
87 void W_Fireball_Explode_use(entity this, entity actor, entity trigger)
88 {
89         W_Fireball_Explode(this, trigger);
90 }
91
92 void W_Fireball_TouchExplode(entity this, entity toucher)
93 {
94         PROJECTILE_TOUCH(this, toucher);
95         W_Fireball_Explode(this, toucher);
96 }
97
98 void W_Fireball_LaserPlay(entity this, float dt, float dist, float damage, float edgedamage, float burntime)
99 {
100         entity e;
101         float d;
102         vector p;
103
104         if(damage <= 0)
105                 return;
106
107         RandomSelection_Init();
108         for(e = WarpZone_FindRadius(this.origin, dist, true); e; e = e.chain)
109         {
110                 if(STAT(FROZEN, e)) continue;
111                 if(e == this.realowner) continue;
112                 if(IS_INDEPENDENT_PLAYER(e)) continue;
113                 if(e.takedamage != DAMAGE_AIM) continue;
114                 if(IS_PLAYER(e) && this.realowner && SAME_TEAM(e, this)) continue;
115
116                 p = e.origin;
117                 p.x += e.mins.x + random() * (e.maxs.x - e.mins.x);
118                 p.y += e.mins.y + random() * (e.maxs.y - e.mins.y);
119                 p.z += e.mins.z + random() * (e.maxs.z - e.mins.z);
120                 d = vlen(WarpZone_UnTransformOrigin(e, this.origin) - p);
121                 if(d < dist)
122                 {
123                         e.fireball_impactvec = p;
124                         RandomSelection_AddEnt(e, 1 / (1 + d), !StatusEffects_active(STATUSEFFECT_Burning, e));
125                 }
126         }
127         if(RandomSelection_chosen_ent)
128         {
129                 d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, this.origin) - RandomSelection_chosen_ent.fireball_impactvec);
130                 d = damage + (edgedamage - damage) * (d / dist);
131                 Fire_AddDamage(RandomSelection_chosen_ent, this.realowner, d * burntime, burntime, this.projectiledeathtype | HITTYPE_BOUNCE);
132                 //trailparticles(this, particleeffectnum(EFFECT_FIREBALL_LASER), this.origin, RandomSelection_chosen_ent.fireball_impactvec);
133                 Send_Effect(EFFECT_FIREBALL_LASER, this.origin, RandomSelection_chosen_ent.fireball_impactvec - this.origin, 1);
134         }
135 }
136
137 void W_Fireball_Think(entity this)
138 {
139         if(time > this.pushltime)
140         {
141                 this.cnt = 1;
142                 this.projectiledeathtype |= HITTYPE_SPLASH;
143                 W_Fireball_Explode(this, NULL);
144                 return;
145         }
146
147         W_Fireball_LaserPlay(this, 0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime));
148
149         this.nextthink = time + 0.1;
150 }
151
152 void W_Fireball_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
153 {
154         if(GetResource(this, RES_HEALTH) <= 0)
155                 return;
156
157         if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
158                 return; // g_projectiles_damage says to halt
159
160         TakeResource(this, RES_HEALTH, damage);
161         if(GetResource(this, RES_HEALTH) <= 0)
162         {
163                 this.cnt = 1;
164                 W_PrepareExplosionByDamage(this, attacker, W_Fireball_Explode_think);
165         }
166 }
167
168 void W_Fireball_Attack1(entity actor, .entity weaponentity)
169 {
170         W_SetupShot_ProjectileSize(actor, weaponentity, '-16 -16 -16', '16 16 16', false, 2, SND_FIREBALL_FIRE2, CH_WEAPON_A, WEP_CVAR_PRI(fireball, damage) + WEP_CVAR_PRI(fireball, bfgdamage), WEP_FIREBALL.m_id);
171
172         W_MuzzleFlash(WEP_FIREBALL, actor, weaponentity, w_shotorg, w_shotdir);
173
174         entity proj = new(plasma_prim);
175         proj.owner = proj.realowner = actor;
176         proj.bot_dodge = true;
177         proj.bot_dodgerating = WEP_CVAR_PRI(fireball, damage);
178         proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime);
179         proj.use = W_Fireball_Explode_use;
180         setthink(proj, W_Fireball_Think);
181         proj.nextthink = time;
182         SetResourceExplicit(proj, RES_HEALTH, WEP_CVAR_PRI(fireball, health));
183         proj.team = actor.team;
184         proj.event_damage = W_Fireball_Damage;
185         proj.takedamage = DAMAGE_YES;
186         proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale);
187         PROJECTILE_MAKETRIGGER(proj);
188         proj.projectiledeathtype = WEP_FIREBALL.m_id;
189         proj.weaponentity_fld = weaponentity;
190         setorigin(proj, w_shotorg);
191
192         set_movetype(proj, MOVETYPE_FLY);
193         W_SetupProjVelocity_PRI(proj, fireball);
194         proj.angles = vectoangles(proj.velocity);
195         settouch(proj, W_Fireball_TouchExplode);
196         setsize(proj, '-16 -16 -16', '16 16 16');
197         proj.flags = FL_PROJECTILE;
198         IL_PUSH(g_projectiles, proj);
199         IL_PUSH(g_bot_dodge, proj);
200         proj.missile_flags = MIF_SPLASH | MIF_PROXY;
201
202         CSQCProjectile(proj, true, PROJECTILE_FIREBALL, true);
203
204         MUTATOR_CALLHOOK(EditProjectile, actor, proj);
205 }
206
207 void W_Fireball_AttackEffect(entity actor, .entity weaponentity, float i, vector f_diff)
208 {
209         W_SetupShot_ProjectileSize(actor, weaponentity, '-16 -16 -16', '16 16 16', false, 0, SND_Null, 0, 0, WEP_FIREBALL.m_id); // TODO: probably doesn't need deathtype, just a prefire effect
210         w_shotorg += f_diff.x * v_up + f_diff.y * v_right;
211         Send_Effect(EFFECT_FIREBALL_PRE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
212 }
213
214 void W_Fireball_Attack1_Frame4(Weapon thiswep, entity actor, .entity weaponentity, int fire)
215 {
216         W_Fireball_Attack1(actor, weaponentity);
217         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready);
218 }
219
220 void W_Fireball_Attack1_Frame3(Weapon thiswep, entity actor, .entity weaponentity, int fire)
221 {
222         W_Fireball_AttackEffect(actor, weaponentity, 0, '+1.25 +3.75 0');
223         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4);
224 }
225
226 void W_Fireball_Attack1_Frame2(Weapon thiswep, entity actor, .entity weaponentity, int fire)
227 {
228         W_Fireball_AttackEffect(actor, weaponentity, 0, '-1.25 +3.75 0');
229         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3);
230 }
231
232 void W_Fireball_Attack1_Frame1(Weapon thiswep, entity actor, .entity weaponentity, int fire)
233 {
234         W_Fireball_AttackEffect(actor, weaponentity, 1, '+1.25 -3.75 0');
235         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2);
236 }
237
238 void W_Fireball_Attack1_Frame0(Weapon thiswep, entity actor, .entity weaponentity, int fire)
239 {
240         W_Fireball_AttackEffect(actor, weaponentity, 0, '-1.25 -3.75 0');
241         sound(actor, CH_WEAPON_SINGLE, SND_FIREBALL_PREFIRE2, VOL_BASE, ATTEN_NORM);
242         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1);
243 }
244
245 void W_Fireball_Firemine_Think(entity this)
246 {
247         if(time > this.pushltime)
248         {
249                 delete(this);
250                 return;
251         }
252
253         // make it "hot" once it leaves its owner
254         if(this.owner)
255         {
256                 if(vdist(this.origin - this.owner.origin - this.owner.view_ofs, >, WEP_CVAR_SEC(fireball, laserradius)))
257                 {
258                         this.cnt += 1;
259                         if(this.cnt == 3)
260                                 this.owner = NULL;
261                 }
262                 else
263                         this.cnt = 0;
264         }
265
266         W_Fireball_LaserPlay(this, 0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime));
267
268         this.nextthink = time + 0.1;
269 }
270
271 void W_Fireball_Firemine_Touch(entity this, entity toucher)
272 {
273         PROJECTILE_TOUCH(this, toucher);
274         if(toucher.takedamage == DAMAGE_AIM)
275         if(Fire_AddDamage(toucher, this.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), this.projectiledeathtype) >= 0)
276         {
277                 delete(this);
278                 return;
279         }
280         this.projectiledeathtype |= HITTYPE_BOUNCE;
281 }
282
283 void W_Fireball_Attack2(entity actor, .entity weaponentity)
284 {
285         entity proj;
286         vector f_diff;
287         float c;
288
289         c = actor.(weaponentity).bulletcounter % 4;
290         switch(c)
291         {
292                 case 0:
293                         f_diff = '-1.25 -3.75 0';
294                         break;
295                 case 1:
296                         f_diff = '+1.25 -3.75 0';
297                         break;
298                 case 2:
299                         f_diff = '-1.25 +3.75 0';
300                         break;
301                 case 3:
302                 default:
303                         f_diff = '+1.25 +3.75 0';
304                         break;
305         }
306         W_SetupShot_ProjectileSize(actor, weaponentity, '-4 -4 -4', '4 4 4', false, 2, SND_FIREBALL_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(fireball, damage), WEP_FIREBALL.m_id | HITTYPE_SECONDARY);
307         traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, actor);
308         w_shotorg = trace_endpos;
309
310         W_MuzzleFlash(WEP_FIREBALL, actor, weaponentity, w_shotorg, w_shotdir);
311
312         proj = new(grenade);
313         proj.owner = proj.realowner = actor;
314         proj.bot_dodge = true;
315         proj.bot_dodgerating = WEP_CVAR_SEC(fireball, damage);
316         set_movetype(proj, MOVETYPE_BOUNCE);
317         proj.projectiledeathtype = WEP_FIREBALL.m_id | HITTYPE_SECONDARY;
318         settouch(proj, W_Fireball_Firemine_Touch);
319         PROJECTILE_MAKETRIGGER(proj);
320         setsize(proj, '-4 -4 -4', '4 4 4');
321         setorigin(proj, w_shotorg);
322         setthink(proj, W_Fireball_Firemine_Think);
323         proj.nextthink = time;
324         proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale);
325         proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime);
326         W_SetupProjVelocity_UP_SEC(proj, fireball);
327
328         proj.angles = vectoangles(proj.velocity);
329         proj.flags = FL_PROJECTILE;
330         IL_PUSH(g_projectiles, proj);
331         IL_PUSH(g_bot_dodge, proj);
332         proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
333
334         CSQCProjectile(proj, true, PROJECTILE_FIREMINE, true);
335
336         MUTATOR_CALLHOOK(EditProjectile, actor, proj);
337 }
338
339 METHOD(Fireball, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
340 {
341     PHYS_INPUT_BUTTON_ATCK(actor) = false;
342     PHYS_INPUT_BUTTON_ATCK2(actor) = false;
343     if(actor.bot_primary_fireballmooth == 0)
344     {
345         if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false))
346         {
347             PHYS_INPUT_BUTTON_ATCK(actor) = true;
348             if(random() < 0.02) actor.bot_primary_fireballmooth = 0;
349         }
350     }
351     else
352     {
353         if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true))
354         {
355             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
356             if(random() < 0.01) actor.bot_primary_fireballmooth = 1;
357         }
358     }
359 }
360 METHOD(Fireball, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
361 {
362     if(fire & 1)
363     {
364         if(time >= actor.(weaponentity).fireball_primarytime)
365         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(fireball, refire)))
366         {
367             W_Fireball_Attack1_Frame0(thiswep, actor, weaponentity, fire);
368             actor.(weaponentity).fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor(actor);
369         }
370     }
371     else if(fire & 2)
372     {
373         if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(fireball, refire)))
374         {
375             W_Fireball_Attack2(actor, weaponentity);
376             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready);
377         }
378     }
379 }
380 METHOD(Fireball, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
381 {
382     return true; // infinite ammo
383 }
384 METHOD(Fireball, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
385 {
386     return true; // fireball has infinite ammo
387 }
388 METHOD(Fireball, wr_resetplayer, void(entity thiswep, entity actor))
389 {
390         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
391         {
392                 .entity weaponentity = weaponentities[slot];
393         actor.(weaponentity).fireball_primarytime = time;
394         }
395 }
396 METHOD(Fireball, wr_suicidemessage, Notification(entity thiswep))
397 {
398     if(w_deathtype & HITTYPE_SECONDARY)
399         return WEAPON_FIREBALL_SUICIDE_FIREMINE;
400     else
401         return WEAPON_FIREBALL_SUICIDE_BLAST;
402 }
403 METHOD(Fireball, wr_killmessage, Notification(entity thiswep))
404 {
405     if(w_deathtype & HITTYPE_SECONDARY)
406         return WEAPON_FIREBALL_MURDER_FIREMINE;
407     else
408         return WEAPON_FIREBALL_MURDER_BLAST;
409 }
410
411 #endif
412 #ifdef CSQC
413
414 METHOD(Fireball, wr_impacteffect, void(entity thiswep, entity actor))
415 {
416     vector org2;
417     if(w_deathtype & HITTYPE_SECONDARY)
418     {
419         // firemine goes out silently
420     }
421     else
422     {
423         org2 = w_org + w_backoff * 16;
424         pointparticles(EFFECT_FIREBALL_EXPLODE, org2, '0 0 0', 1);
425         if(!w_issilent)
426             sound(actor, CH_SHOTS, SND_FIREBALL_IMPACT2, VOL_BASE, ATTEN_NORM * 0.25); // long range boom
427     }
428 }
429
430 #endif