2 void W_GiveWeapon (entity e, float wep)
9 e.weapons |= WepSet_FromWeapon(wep);
15 { Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_WEAPON_GOT, wep); }
20 .float railgundistance;
22 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype)
24 vector hitloc, force, endpoint, dir;
26 float endq3surfaceflags;
33 entity pseudoprojectile;
36 pseudoprojectile = world;
38 dir = normalize(end - start);
39 length = vlen(end - start);
42 // go a little bit into the wall because we need to hit this wall later
47 // trace multiple times until we hit a wall, each obstacle will be made
48 // non-solid so we can hit the next, while doing this we spawn effects and
49 // note down which entities were hit so we can damage them later
53 if(self.antilag_debug)
54 WarpZone_traceline_antilag (self, start, end, FALSE, o, self.antilag_debug);
56 WarpZone_traceline_antilag (self, start, end, FALSE, o, ANTILAG_LATENCY(self));
57 if(o && WarpZone_trace_firstzone)
63 if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
64 Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, self);
66 // if it is world we can't hurt it so stop now
67 if (trace_ent == world || trace_fraction == 1)
70 // make the entity non-solid so we can hit the next one
71 trace_ent.railgunhit = TRUE;
72 trace_ent.railgunhitloc = end;
73 trace_ent.railgunhitsolidbackup = trace_ent.solid;
74 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
75 trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
77 // stop if this is a wall
78 if (trace_ent.solid == SOLID_BSP)
81 // make the entity non-solid
82 trace_ent.solid = SOLID_NOT;
85 endpoint = trace_endpos;
87 endq3surfaceflags = trace_dphitq3surfaceflags;
89 // find all the entities the railgun hit and restore their solid state
90 ent = findfloat(world, railgunhit, TRUE);
93 // restore their solid type
94 ent.solid = ent.railgunhitsolidbackup;
95 ent = findfloat(ent, railgunhit, TRUE);
98 // spawn a temporary explosion entity for RadiusDamage calls
99 //explosion = spawn();
101 // Find all non-hit players the beam passed close by
102 if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
104 FOR_EACH_REALCLIENT(msg_entity)
105 if(msg_entity != self)
106 if(!msg_entity.railgunhit)
107 if(!(IS_SPEC(msg_entity) && msg_entity.enemy == self)) // we use realclient, so spectators can hear the whoosh too
109 // nearest point on the beam
110 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
112 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
116 snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
118 if(!pseudoprojectile)
119 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
120 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTEN_NONE);
124 remove(pseudoprojectile);
127 // find all the entities the railgun hit and hurt them
128 ent = findfloat(world, railgunhit, TRUE);
131 // get the details we need to call the damage function
132 hitloc = ent.railgunhitloc;
134 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
135 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
137 if(accuracy_isgooddamage(self.realowner, ent))
138 totaldmg += bdamage * f;
142 Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
144 // create a small explosion to throw gibs around (if applicable)
145 //setorigin (explosion, hitloc);
146 //RadiusDamage (explosion, self, 10, 0, 50, world, 300, deathtype);
148 ent.railgunhitloc = '0 0 0';
149 ent.railgunhitsolidbackup = SOLID_NOT;
150 ent.railgunhit = FALSE;
151 ent.railgundistance = 0;
153 // advance to the next entity
154 ent = findfloat(ent, railgunhit, TRUE);
157 // calculate hits and fired shots for hitscan
158 accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
160 trace_endpos = endpoint;
162 trace_dphitq3surfaceflags = endq3surfaceflags;
165 float fireBallisticBullet_trace_callback_eff;
166 void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
168 if(vlen(hit - start) > 16)
169 trailparticles(world, fireBallisticBullet_trace_callback_eff, start, hit);
170 WarpZone_trace_forent = world;
173 void fireBullet(vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, float tracereffects)
175 // TODO antilag takeback
178 dir = normalize(dir + randomvec() * spread);
179 end = start + dir * MAX_SHOT_DISTANCE;
182 entity last_hit = world;
183 float solid_penetration_left = 1;
184 float total_damage = 0;
186 if(tracereffects & EF_RED)
187 fireBallisticBullet_trace_callback_eff = particleeffectnum("tr_rifle");
188 else if(tracereffects & EF_BLUE)
189 fireBallisticBullet_trace_callback_eff = particleeffectnum("tr_rifle_weak");
191 fireBallisticBullet_trace_callback_eff = particleeffectnum("tr_bullet");
193 float lag = ANTILAG_LATENCY(self);
196 if (!IS_REAL_CLIENT(self))
198 if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
199 lag = 0; // only do hitscan, but no antilag
203 antilag_takeback(pl, time - lag);
207 // TODO also show effect while tracing
208 WarpZone_TraceBox_ThroughZone(start, '0 0 0', '0 0 0', end, FALSE, self, world, fireBallisticBullet_trace_callback);
209 dir = WarpZone_TransformVelocity(WarpZone_trace_transform, dir);
210 end = WarpZone_TransformOrigin(WarpZone_trace_transform, end);
211 start = trace_endpos;
212 entity hit = trace_ent;
214 // When hitting sky, stop.
215 if (pointcontents(start) == CONTENT_SKY)
218 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
221 // if we hit "weapclip", bail out
223 // rationale of this check:
225 // any shader that is solid, nodraw AND trans is meant to clip weapon
226 // shots and players, but has no other effect!
228 // if it is not trans, it is caulk and should not have this side effect
231 // common/weapclip (intended)
232 // common/noimpact (is supposed to eat projectiles, but is erased anyway)
233 float is_weapclip = 0;
234 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
235 if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
236 if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
239 // Avoid self-damage // FIXME can this happen?
242 if(hit.solid == SOLID_BSP || hit.solid == SOLID_SLIDEBOX)
243 Damage_DamageInfo(self.origin, damage * solid_penetration_left, 0, 0, max(1, force) * dir * solid_penetration_left, dtype, hit.species, self);
245 if(hit && hit != last_hit)
248 float g = accuracy_isgooddamage(self, hit);
249 // FIXME preserve trace stuff
250 Damage(hit, self, self, damage * solid_penetration_left, dtype, self.origin, force * dir * solid_penetration_left);
251 // calculate hits for ballistic weapons
254 // do not exceed 100%
255 float added_damage = min(damage - total_damage, damage * solid_penetration_left);
256 total_damage += damage * solid_penetration_left;
257 accuracy_add(self, self.weapon, 0, added_damage);
261 last_hit = hit; // don't hit the same player twice with the same bullet
268 // outside the world? forget it
269 if(end_x > world.maxs_x || end_y > world.maxs_y || end_z > world.maxs_z || end_x < world.mins_x || end_y < world.mins_y || end_z < world.mins_z)
273 if(max_solid_penetration < 0)
275 else if(hit.ballistics_density < -1)
276 break; // -2: no solid penetration, ever
277 else if(hit.ballistics_density < 0)
278 maxdist = vlen(hit.maxs - hit.mins) + 1; // -1: infinite travel distance
279 else if(hit.ballistics_density == 0)
280 maxdist = max_solid_penetration * solid_penetration_left;
282 maxdist = max_solid_penetration * solid_penetration_left * hit.ballistics_density;
284 if(maxdist <= autocvar_g_ballistics_mindistance)
287 // move the entity along its velocity until it's out of solid, then let it resume
288 traceline_inverted (start, self.origin + dir * maxdist, MOVE_NORMAL, self, TRUE);
289 if(trace_fraction == 1) // 1: we never got out of solid
292 float dist_taken = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - start));
293 solid_penetration_left *= (dist_taken / maxdist);
295 // Only show effect when going through a player (invisible otherwise)
296 if (hit && (hit.solid != SOLID_BSP))
297 if(vlen(trace_endpos - start) > 4)
298 trailparticles(self, fireBallisticBullet_trace_callback_eff, start, trace_endpos);
300 start = trace_endpos;
302 if(hit.solid == SOLID_BSP)
303 Damage_DamageInfo(start, 0, 0, 0, max(1, force) * normalize(dir) * -solid_penetration_left, dtype, 0, self);
312 // DEPRECATED kill this adaptor once we can
313 void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float bulletconstant)
315 fireBullet(start, dir, spread, (0.5 * pSpeed * pSpeed * bulletconstant) / autocvar_g_ballistics_materialconstant, damage, force, dtype, tracereffects);
317 void endFireBallisticBullet()
321 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
323 float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
324 float is_from_owner = (inflictor == projowner);
325 float is_from_exception = (exception != -1);
327 //dprint(strcat("W_CheckProjectileDamage: from_contents ", ftos(is_from_contents), " : from_owner ", ftos(is_from_owner), " : exception ", strcat(ftos(is_from_exception), " (", ftos(exception), "). \n")));
329 if(autocvar_g_projectiles_damage <= -2)
331 return FALSE; // no damage to projectiles at all, not even with the exceptions
333 else if(autocvar_g_projectiles_damage == -1)
335 if(is_from_exception)
336 return (exception); // if exception is detected, allow it to override
338 return FALSE; // otherwise, no other damage is allowed
340 else if(autocvar_g_projectiles_damage == 0)
342 if(is_from_exception)
343 return (exception); // if exception is detected, allow it to override
344 else if (!is_from_contents)
345 return FALSE; // otherwise, only allow damage from contents
347 else if(autocvar_g_projectiles_damage == 1)
349 if(is_from_exception)
350 return (exception); // if exception is detected, allow it to override
351 else if (!(is_from_contents || is_from_owner))
352 return FALSE; // otherwise, only allow self damage and damage from contents
354 else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
356 if(is_from_exception)
357 return (exception); // if exception is detected, allow it to override
360 return TRUE; // if none of these return, then allow damage anyway.
363 void W_PrepareExplosionByDamage(entity attacker, void() explode)
365 self.takedamage = DAMAGE_NO;
366 self.event_damage = func_null;
368 if(IS_CLIENT(attacker) && !autocvar_g_projectiles_keep_owner)
370 self.owner = attacker;
371 self.realowner = attacker;
374 // do not explode NOW but in the NEXT FRAME!
375 // because recursive calls to RadiusDamage are not allowed
376 self.nextthink = time;
377 self.think = explode;