Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
common.qc
Go to the documentation of this file.
1#include "common.qh"
2
3#include <common/constants.qh>
8#include <common/state.qh>
9#include <common/stats.qh>
10#include <common/util.qh>
12#include <common/wepent.qh>
14#include <server/damage.qh>
15#include <server/hook.qh>
16#include <server/items/items.qh>
19
21{
22 int held_weapons = 0;
23 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
24 {
25 .entity weaponentity = weaponentities[slot];
26 if (player.(weaponentity) && player.(weaponentity).m_switchweapon != WEP_Null)
27 ++held_weapons;
28 }
29
30 return held_weapons > 1;
31}
32
33void W_GiveWeapon(entity e, int wep)
34{
35 if (!wep)
36 return;
37 STAT(WEAPONS, e) |= WepSet_FromWeapon(REGISTRY_GET(Weapons, wep));
38}
39
44
45float W_CheckProjectileDamage(entity inflictor, entity projowner, int deathtype, float exception)
46{
47 bool is_from_contents = (deathtype == DEATH_SLIME.m_id || deathtype == DEATH_LAVA.m_id);
48 bool is_from_owner = (inflictor == projowner);
49 bool is_from_exception = (exception != -1);
50
51 //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")));
52
54 {
55 return false; // no damage to projectiles at all, not even with the exceptions
56 }
57 else if (autocvar_g_projectiles_damage == -1)
58 {
59 if (is_from_exception)
60 return exception; // if exception is detected, allow it to override
61 else
62 return false; // otherwise, no other damage is allowed
63 }
65 {
66 if (is_from_exception)
67 return exception; // if exception is detected, allow it to override
68 else if (!is_from_contents)
69 return false; // otherwise, only allow damage from contents
70 }
72 {
73 if (is_from_exception)
74 return exception; // if exception is detected, allow it to override
75 else if (!(is_from_contents || is_from_owner))
76 return false; // otherwise, only allow self damage and damage from contents
77 }
78 else if (autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
79 {
80 if (is_from_exception)
81 return exception; // if exception is detected, allow it to override
82 }
83
84 return true; // if none of these return, then allow damage anyway.
85}
86
87void W_PrepareExplosionByDamage(entity this, entity attacker, void(entity this) explode)
88{
89 this.takedamage = DAMAGE_NO;
90 this.event_damage = func_null;
91
92 MUTATOR_CALLHOOK(PrepareExplosionByDamage, this, attacker);
93
95 {
96 this.owner = attacker;
97 this.realowner = attacker;
98 }
99
100 // do not explode NOW but in the NEXT FRAME!
101 // because recursive calls to RadiusDamage are not allowed
102 this.nextthink = time;
103 setthink(this, explode);
104}
105
106void adaptor_think2use_hittype_splash(entity this) // for timed projectile detonation
107{
108 if (!(IS_ONGROUND(this))) // if onground, we ARE touching something, but HITTYPE_SPLASH is to be networked if the damage causing projectile is not touching ANYTHING
110 adaptor_think2use(this);
111}
112
114{
115 // zero hitcontents = this is not the real impact, but either the
116 // mirror-impact of something hitting the projectile instead of the
117 // projectile hitting the something, or a touchareagrid one. Neither of
118 // these stop the projectile from moving, so...
119 // NOTE: this notice is disabled to prevent spam as projectiles can hit content-less objects (other projectiles!)
120#if 0
121 if (trace_dphitcontents == 0)
122 {
123 LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct. (edict: %i, classname: %s, origin: %v)", this, this.classname, this.origin);
124 checkclient(this); // TODO: .health is checked in the engine with this, possibly replace with a QC function?
125 }
126#endif
128 return true;
129
130 if (toucher == NULL && this.size != '0 0 0')
131 {
132 vector tic = this.velocity * sys_frametime;
133 tic += normalize(tic) * vlen(this.maxs - this.mins);
134 traceline(this.origin - tic, this.origin + tic, MOVE_NORMAL, this);
135 if (trace_fraction >= 1)
136 {
137 // NOTE: this notice can occur when projectiles hit non-world objects, better to not spam the console!
138 //LOG_TRACE("Odd... did not hit...?");
139 }
141 {
142 LOG_TRACE("Detected and prevented the sky-grapple bug.");
143 return true;
144 }
145 }
146
147 return false;
148}
149
151{
152 if (toucher && toucher == this.owner)
153 return true;
154
155 if (autocvar_g_projectiles_interact == 1 && toucher.classname == "blasterbolt")
156 {
158 // We'll bounce off it due to limitations so let it deflect us
159 // to hide the problem, see PROJECTILE_MAKETRIGGER.
160 gettouch(toucher)(toucher, this);
161 return true;
162 }
163
164 if (SUB_NoImpactCheck(this, toucher))
165 {
166 if (this.classname == "nade")
167 return false; // no checks here
168 else if (this.classname == "grapplinghook")
169 RemoveHook(this);
170 else
171 delete(this);
172 return true;
173 }
174 if (trace_ent && trace_ent.solid > SOLID_TRIGGER)
176 return false;
177}
178
179
183{
184 if (end == W_LaserPlay_end_org) // must be the final trace segment
185 return;
187 Send_Effect(EFFECT_FIREBALL_LASER, start, hit - start, 1);
188 //trailparticles(this, particleeffectnum(EFFECT_FIREBALL_LASER), start, hit);
189}
190
191void W_LaserPlay(entity this, float rad, float damage, float edgedamage, float burntime, WarpZone_FindRadius_cond_callback_t cb)
192{
193 if (damage <= 0 && edgedamage <= 0)
194 return;
195
197 entity last = NULL;
198 float rad2 = rad * rad;
200 for (entity e = WarpZone_SearchInRadius(this.origin, rad, MOVE_NOMONSTERS, cb); e; e = e.chain)
201 {
202 // adjust the hit location to be a random position inside the entity's bbox
203 vector p = e.origin + e.mins;
204 p.x += random() * (e.maxs.x - e.mins.x);
205 p.y += random() * (e.maxs.y - e.mins.y);
206 p.z += random() * (e.maxs.z - e.mins.z);
207 float dist2 = vlen2(this.origin - WarpZone_UnTransformOrigin(e, p));
208 if (dist2 <= rad2) // there's a chance this is false, so we optimize by only calling sqrt if true
209 {
210 e.dest = p;
211 e.dmg_radius = sqrt(dist2);
212 last = e;
213 RandomSelection_AddEnt(e, 1 / (1 + e.dmg_radius), !StatusEffects_active(STATUSEFFECT_Burning, e));
214 }
215 }
217 {
218 float dist = damage + (edgedamage - damage) * (RandomSelection_chosen_ent.dmg_radius / rad);
220
221 // WarpZone_TraceLine_ThroughZone with callback to draw the line-effect through zones on the way to the end,
222 // but then for the final segment draw towards .dest instead of the trace's end (.WarpZone_findradius_nearest).
223 // This is probably the best we can do
225 if (RandomSelection_chosen_ent.warpzone_transform != '0 0 0' || RandomSelection_chosen_ent.warpzone_shift != '0 0 0')
226 {
227 W_LaserPlay_end_org = RandomSelection_chosen_ent.WarpZone_findradius_nearest;
230 }
232 //trailparticles(this, particleeffectnum(EFFECT_FIREBALL_LASER), this.origin, RandomSelection_chosen_ent.dest);
233 }
234}
235
236.float wait;
240 float speed_decel, float speed_accel, float speed_max, float turn_rate,
241 bool smart, float smart_mindist, float smart_trace_min, float smart_trace_max)
242{
243 if (this.enemy != NULL
244 && this.enemy.takedamage != DAMAGE_AIM || IS_DEAD(this.enemy))
245 this.enemy = NULL;
246 if (!this.enemy)
247 return 0;
248
249 float spd = vlen(this.velocity);
250 spd = bound(
251 spd - speed_decel * frametime,
252 speed_max,
253 spd + speed_accel * frametime);
254
255 vector eorg = WarpZone_RefSys_TransformOrigin(this.enemy, this, 0.5 * (this.enemy.absmin + this.enemy.absmax));
256 float dist = vlen(eorg - this.origin);
257 vector desireddir = (eorg - this.origin) * 1 / dist;
258 vector olddir = this.velocity * 1 / spd; // get my current direction
259
260 // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
261 if (smart && dist > smart_mindist)
262 {
263 // Is it a better idea (shorter distance) to trace to the target itself?
264 if (vdist(this.origin + olddir * this.wait, <, dist))
265 WarpZone_TraceLine(this.origin, this.origin + olddir * this.wait, false, this);
266 else
267 WarpZone_TraceLine(this.origin, eorg, false, this);
268
269 // Setup adaptive tracelength
270 this.wait = bound(
271 smart_trace_min,
273 smart_trace_max);
274
275 // Calc how important it is that we turn and add this to the desired (enemy) dir
276 desireddir = normalize((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction));
277 }
278
279 vector newdir = normalize(olddir + desireddir * turn_rate); // take the average of the 2 directions; not the best method but simple & easy
280 this.velocity = newdir * spd; // make me fly in the new direction at my flight speed
281 return dist;
282}
283
286 float duration, float duration_power,
287 float damage, float edgedamage, float rad, float force,
288 entity cantbe, float think_delay)
289{
290 float dt = time - this.teleport_time;
291 float dmg_remaining_next = bound(0, 1 - dt / duration, 1) ** duration_power;
292
293 float f = this.dmg_last - dmg_remaining_next;
294 this.dmg_last = dmg_remaining_next;
295
296 RadiusDamage(this, this.realowner,
297 f * damage,
298 f * edgedamage,
299 rad,
300 cantbe,
301 NULL,
302 f * force,
304 this.weaponentity_fld,
305 NULL
306 );
307 this.projectiledeathtype |= HITTYPE_SPAM; // ensure it doesn't spam its effect
308
309 if (dt < duration)
310 this.nextthink = time + think_delay;
311 else
312 delete(this);
313}
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float wait
Definition items.qc:17
entity owner
Definition main.qh:87
#define IS_CLIENT(s)
Definition player.qh:241
#define IS_DEAD(s)
Definition player.qh:244
float teleport_time
Definition player.qh:218
string classname
float movetype
const float MOVE_NOMONSTERS
float trace_dphitcontents
entity trace_ent
const float SOLID_TRIGGER
float frametime
const float MOVE_NORMAL
vector mins
vector velocity
float time
vector trace_endpos
vector maxs
vector size
float nextthink
float trace_dphitq3surfaceflags
vector origin
float trace_fraction
float Q3SURFACEFLAG_NOIMPACT
vector trace_plane_normal
void UpdateCSQCProjectile(entity e)
float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype,.entity weaponentity, entity directhitentity)
Definition damage.qc:937
float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
Definition damage.qc:968
const int HITTYPE_BOUNCE
set manually after projectile has bounced
Definition all.qh:33
const int HITTYPE_SPAM
set manually after first RadiusDamage, stops effect spam
Definition all.qh:36
const int HITTYPE_SPLASH
automatically set by RadiusDamage
Definition all.qh:32
void Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:155
Weapons
Definition guide.qh:113
#define STAT(...)
Definition stats.qh:94
entity WarpZone_SearchInRadius(vector org, float rad, float nomonsters, WarpZone_FindRadius_cond_callback_t cb)
Modified findradius aware of warpzones, that only includes entities that meet the "cb" criteria.
Definition common.qc:680
vector WarpZone_UnTransformOrigin(entity wz, vector org)
Transforms origin org backwards through warpzone wz, as the inverse of WarpZone_TransformOrigin.
Definition common.qc:526
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Transform origin org in from's reference into to's reference.
Definition common.qc:787
#define WarpZone_TraceLine(org, end, nomonsters, forent)
Definition common.qh:51
noref entity WarpZone_SearchInRadius_otherent
Free to use in the callback.
Definition common.qh:71
#define WarpZone_TraceLine_ThroughZone(org, end, nomonsters, forent, zone, cb)
Definition common.qh:47
entity WarpZone_trace_transform
Transform accumulator during a trace.
Definition common.qh:40
#define MOVE_NOTHING
Definition common.qh:35
bool(entity e) WarpZone_FindRadius_cond_callback_t
Definition common.qh:67
#define LOG_TRACEF(...)
Definition log.qh:75
#define LOG_TRACE(...)
Definition log.qh:74
float bound(float min, float value, float max)
float random(void)
float vlen(vector v)
float sqrt(float f)
vector normalize(vector v)
const int MOVETYPE_BOUNCEMISSILE
Definition movetypes.qh:144
const int MOVETYPE_BOUNCE
Definition movetypes.qh:143
#define IS_ONGROUND(s)
Definition movetypes.qh:16
var void func_null()
#define NULL
Definition post.qh:14
#define checkclient
Definition pre.qh:4
ERASEABLE void RandomSelection_Init()
Definition random.qc:4
#define RandomSelection_AddEnt(e, weight, priority)
Definition random.qh:14
entity RandomSelection_chosen_ent
Definition random.qh:5
#define REGISTRY_GET(id, i)
Definition registry.qh:62
#define gettouch(e)
Definition self.qh:78
#define setthink(e, f)
vector
Definition self.qh:96
entity entity toucher
Definition self.qh:76
float sys_frametime
Definition common.qh:57
void RemoveHook(entity this)
Definition hook.qc:48
float W_CheckProjectileDamage(entity inflictor, entity projowner, int deathtype, float exception)
Definition common.qc:45
bool WarpZone_Projectile_Touch_ImpactFilter_Callback(entity this, entity toucher)
Definition common.qc:150
void W_PlayStrengthSound(entity player)
Definition common.qc:40
void W_LaserPlay(entity this, float rad, float damage, float edgedamage, float burntime, WarpZone_FindRadius_cond_callback_t cb)
Randomly chooses a player within the radius to shoot a laser towards.
Definition common.qc:191
void W_PrepareExplosionByDamage(entity this, entity attacker, void(entity this) explode)
Definition common.qc:87
void W_DamageOverTime_Think(entity this, float duration, float duration_power, float damage, float edgedamage, float rad, float force, entity cantbe, float think_delay)
Applies damage over time, called within a think function.
Definition common.qc:285
void W_GiveWeapon(entity e, int wep)
Definition common.qc:33
void W_LaserPlay_trace_callback(vector start, vector hit, vector end)
Definition common.qc:182
bool SUB_NoImpactCheck(entity this, entity toucher)
Definition common.qc:113
vector W_LaserPlay_end_org
Definition common.qc:181
float W_SeekOutEnemy(entity this, float speed_decel, float speed_accel, float speed_max, float turn_rate, bool smart, float smart_mindist, float smart_trace_min, float smart_trace_max)
Automatically guides a projectile towards its enemy.
Definition common.qc:239
vector W_LaserPlay_last_hit_org
Definition common.qc:180
void adaptor_think2use_hittype_splash(entity this)
Definition common.qc:106
bool W_DualWielding(entity player)
Definition common.qc:20
int projectiledeathtype
Definition common.qh:33
float dmg_last
Definition common.qh:27
bool autocvar_g_projectiles_keep_owner
Definition common.qh:7
int autocvar_g_projectiles_damage
Definition common.qh:5
int autocvar_g_projectiles_interact
Definition common.qh:6
bool StatusEffects_active(StatusEffect this, entity actor)
const int DAMAGE_NO
Definition subs.qh:79
const int DAMAGE_AIM
Definition subs.qh:81
float takedamage
Definition subs.qh:78
entity enemy
Definition sv_ctf.qh:152
entity realowner
#define vlen2(v)
Definition vector.qh:4
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt().
Definition vector.qh:8
#define WepSet_FromWeapon(it)
Definition all.qh:48
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17
entity weaponentity_fld