4133 lines
150 KiB
Diff
4133 lines
150 KiB
Diff
diff --git a/Game/Game/g_tiers.h b/Game/Game/g_tiers.h
|
|
index 93d8185..deeac80 100644
|
|
--- a/Game/Game/g_tiers.h
|
|
+++ b/Game/Game/g_tiers.h
|
|
@@ -1,58 +1,58 @@
|
|
-typedef struct{
|
|
- qboolean exists;
|
|
- qboolean permanent;
|
|
- qboolean requirementButtonUp;
|
|
- qboolean requirementButtonDown;
|
|
- int capabilities;
|
|
- int requirementUseSkill;
|
|
- int requirementCurrent;
|
|
- int requirementCurrentPercent;
|
|
- int requirementMaximum;
|
|
- int requirementFatigue;
|
|
- int requirementHealth;
|
|
- int requirementHealthMaximum;
|
|
- int sustainCurrent;
|
|
- int sustainCurrentPercent;
|
|
- int sustainMaximum;
|
|
- int sustainHealth;
|
|
- int sustainFatigue;
|
|
- int speed;
|
|
- int defenseRecoveryDelay;
|
|
- int transformFirstDuration;
|
|
- int transformFirstFatigue;
|
|
- int transformFirstHealth;
|
|
- int transformFirstEffectMaximum;
|
|
- int transformDuration;
|
|
- int transformFatigue;
|
|
- int transformHealth;
|
|
- int transformEffectMaximum;
|
|
- int collisionSize[3];
|
|
- int meleeDistance;
|
|
- float transformSubsequentDuration;
|
|
- float transformSubsequentFatigueScale;
|
|
- float transformSubsequentHealthScale;
|
|
- float transformSubsequentMaximumScale;
|
|
- float defenseMelee;
|
|
- float defenseEnergy;
|
|
- float defenseCapacity;
|
|
- float defenseRecovery;
|
|
- float fatigueRecovery;
|
|
- float boostCost;
|
|
- float zanzokenCost;
|
|
- float zanzokenQuickCost;
|
|
- float zanzokenSpeed;
|
|
- float zanzokenDistance;
|
|
- float zanzokenQuickDistance;
|
|
- float airBrakeCost;
|
|
- float knockbackPower;
|
|
- float knockbackIntensity;
|
|
- float meleeAttack;
|
|
- float energyAttackDamage;
|
|
- float energyAttackCost;
|
|
- float transformTime;
|
|
- float effectCurrent;
|
|
- float effectFatigue;
|
|
- float effectMaximum;
|
|
- float effectHealth;
|
|
- float breakLimitRate;
|
|
-}tierConfig_g;
|
|
+typedef struct{
|
|
+ qboolean exists;
|
|
+ qboolean permanent;
|
|
+ qboolean requirementButtonUp;
|
|
+ qboolean requirementButtonDown;
|
|
+ int capabilities;
|
|
+ int requirementUseSkill;
|
|
+ int requirementCurrent;
|
|
+ int requirementCurrentPercent;
|
|
+ int requirementMaximum;
|
|
+ int requirementFatigue;
|
|
+ int requirementHealth;
|
|
+ int requirementHealthMaximum;
|
|
+ int sustainCurrent;
|
|
+ int sustainCurrentPercent;
|
|
+ int sustainMaximum;
|
|
+ int sustainHealth;
|
|
+ int sustainFatigue;
|
|
+ int speed;
|
|
+ int defenseRecoveryDelay;
|
|
+ int transformFirstDuration;
|
|
+ int transformFirstFatigue;
|
|
+ int transformFirstHealth;
|
|
+ int transformFirstEffectMaximum;
|
|
+ int transformDuration;
|
|
+ int transformFatigue;
|
|
+ int transformHealth;
|
|
+ int transformEffectMaximum;
|
|
+ int collisionSize[3];
|
|
+ int meleeDistance;
|
|
+ float transformSubsequentDuration;
|
|
+ float transformSubsequentFatigueScale;
|
|
+ float transformSubsequentHealthScale;
|
|
+ float transformSubsequentMaximumScale;
|
|
+ float defenseMelee;
|
|
+ float defenseEnergy;
|
|
+ float defenseCapacity;
|
|
+ float defenseRecovery;
|
|
+ float fatigueRecovery;
|
|
+ float boostCost;
|
|
+ float zanzokenCost;
|
|
+ float zanzokenQuickCost;
|
|
+ float zanzokenSpeed;
|
|
+ float zanzokenDistance;
|
|
+ float zanzokenQuickDistance;
|
|
+ float airBrakeCost;
|
|
+ float knockbackPower;
|
|
+ float knockbackIntensity;
|
|
+ float meleeAttack;
|
|
+ float energyAttackDamage;
|
|
+ float energyAttackCost;
|
|
+ float transformTime;
|
|
+ float effectCurrent;
|
|
+ float effectFatigue;
|
|
+ float effectMaximum;
|
|
+ float effectHealth;
|
|
+ float breakLimitRate;
|
|
+}tierConfig_g;
|
|
diff --git a/Game/Game/g_usermissile.c b/Game/Game/g_usermissile.c
|
|
index 0030fcd..75bf16f 100644
|
|
--- a/Game/Game/g_usermissile.c
|
|
+++ b/Game/Game/g_usermissile.c
|
|
@@ -1,1609 +1,1609 @@
|
|
-#include "g_local.h"
|
|
-#define MISSILE_PRESTEP_TIME 50
|
|
-#define SOLID_ADD 400 // Add extra to guide line to go behind solids with weapon
|
|
-#define BEAM_SECTION_TIME 520 // Timeframe to spawn a new beam section
|
|
-/*-------------------------------
|
|
- M I S C F U N C T I O N S
|
|
--------------------------------*/
|
|
-/*=======================
|
|
-GetMissileOwnerEntity
|
|
-=======================*/
|
|
-gentity_t *GetMissileOwnerEntity (gentity_t *missile) {
|
|
- gentity_t *parent;
|
|
-
|
|
- parent = missile->parent;
|
|
- while((parent->s.eType != ET_PLAYER)&&(parent->s.eType != ET_INVISIBLE)) {
|
|
- parent = GetMissileOwnerEntity( parent );
|
|
- }
|
|
- return parent;
|
|
-}
|
|
-/*---------------------------------
|
|
- T H I N K F U N C T I O N S
|
|
----------------------------------*/
|
|
-
|
|
-// Prototypes
|
|
-void Think_NormalMissile (gentity_t *self);
|
|
-void Think_NormalMissileStruggle (gentity_t *self);
|
|
-static void Think_NormalMissileBurnPlayer (gentity_t *self);
|
|
-static void Think_NormalMissileRidePlayer (gentity_t *self);
|
|
-static void Think_NormalMissileStrugglePlayer (gentity_t *self);
|
|
-static void G_StruggleUserMissile( gentity_t *self, gentity_t *other );
|
|
-static void G_PushUserMissile( gentity_t *self, gentity_t *other );
|
|
-static void G_BounceUserMissile( gentity_t *self, trace_t *trace );
|
|
-void G_UserWeaponDamage(gentity_t *target,gentity_t *inflictor,gentity_t *attacker,vec3_t dir,vec3_t point,int damage,int dflags,int knockback);
|
|
-void G_LocationImpact(vec3_t point, gentity_t* targ, gentity_t* attacker);
|
|
-
|
|
-/*
|
|
-===============
|
|
-Think_ProxDet
|
|
-===============
|
|
-*/
|
|
-void Think_ProxDet( gentity_t *self ) {
|
|
- int i; // loop variable
|
|
- gentity_t *target_ent;
|
|
- gentity_t *target_owner;
|
|
- vec3_t midbody;
|
|
- float proxDistance; // The distance between a potential
|
|
- // target and the missile.
|
|
-
|
|
- for (i = 0; i < level.maxclients; i++) {
|
|
- // Here we use target_ent to point to potential targets
|
|
- target_ent = &g_entities[i];
|
|
- target_owner = GetMissileOwnerEntity( self );
|
|
-
|
|
- // We don't bother with non-used clients, ourselves,
|
|
- // or clients on our team.
|
|
- if ( !target_ent->inuse ) continue;
|
|
- if ( target_ent == target_owner ) continue;
|
|
- if ( OnSameTeam( target_ent, target_owner ) ) continue;
|
|
-
|
|
- // Judge the distance from the middle of the body, not the feet
|
|
- midbody[0] = target_ent->r.currentOrigin[0] +
|
|
- (target_ent->r.mins[0] + target_ent->r.maxs[0]) * 0.5;
|
|
- midbody[1] = target_ent->r.currentOrigin[1] +
|
|
- (target_ent->r.mins[1] + target_ent->r.maxs[1]) * 0.5;
|
|
- midbody[2] = target_ent->r.currentOrigin[2] +
|
|
- (target_ent->r.mins[2] + target_ent->r.maxs[2]) * 0.5;
|
|
-
|
|
- proxDistance = Distance(midbody, self->r.currentOrigin);
|
|
-
|
|
- // We don't detonate on this entity if it's not within homing range.
|
|
- if ( proxDistance > self->homRange ) continue;
|
|
-
|
|
-
|
|
- // We're within range and have to detonate with the next think.
|
|
- self->think = G_ExplodeUserWeapon;
|
|
-
|
|
- // Force premature exit of Bounded Linear Search
|
|
- i = level.maxclients;
|
|
- }
|
|
-
|
|
- // If the weapon has existed too long, make the next think detonate it.
|
|
- if ( ( self->missileSpawnTime + self->maxMissileTime ) <= level.time) {
|
|
- self->think = G_ExplodeUserWeapon;
|
|
- }
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-
|
|
-
|
|
-/*
|
|
-==============
|
|
-Think_Guided
|
|
-==============
|
|
-*/
|
|
-void Think_Guided (gentity_t *self) {
|
|
- vec3_t forward, right, up;
|
|
- vec3_t muzzle;
|
|
- float dist;
|
|
- gentity_t *owner = GetMissileOwnerEntity(self);
|
|
-
|
|
- self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
- if(self->isDrainable && self->s.eType != ET_BEAMHEAD){
|
|
- self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
- }
|
|
- if (self->powerLevelCurrent <= 0) {
|
|
- G_RemoveUserWeapon(self);
|
|
- return;
|
|
- }
|
|
- if((owner->client->ps.bitFlags & usingBoost) && self->s.eType == ET_BEAMHEAD){
|
|
- self->powerLevelCurrent += 1 + ((float)self->powerLevelTotal * 0.005);
|
|
- self->speed += 10 + ((float)owner->client->ps.powerLevel[plMaximum] * 0.0003);
|
|
- }
|
|
-
|
|
- // If our weapon's owner can't be found, skip anything related to guiding.
|
|
- if (!owner){G_Printf( S_COLOR_YELLOW "WARNING: Think_Guided reports unknown fired weapon!\n" );}
|
|
- else{
|
|
- AngleVectors( owner->client->ps.viewangles, forward, right, up );
|
|
- CalcMuzzlePoint( owner, forward, right, up, muzzle );
|
|
- VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
- VectorSubtract( muzzle, self->r.currentOrigin, muzzle );
|
|
- dist = VectorLength( muzzle ) + SOLID_ADD;
|
|
- VectorScale( forward, dist, forward );
|
|
- VectorAdd( forward, muzzle, muzzle );
|
|
- VectorNormalize( muzzle );
|
|
- VectorScale( muzzle, self->speed, forward );
|
|
- VectorCopy( forward, self->s.pos.trDelta );
|
|
- vectoangles( muzzle, self->s.angles );
|
|
- SnapVector( self->s.pos.trDelta );
|
|
- self->s.pos.trType = TR_LINEAR;
|
|
- self->s.pos.trTime = level.time;
|
|
- }
|
|
- // If we're dealing with a beam, we have to record beamsections.
|
|
- if (self->s.eType == ET_BEAMHEAD) {
|
|
- // Have we reached the time at which to start a new section?
|
|
- if ( ( self->sectionSpawnTime + self->newSectionTime ) <= level.time ) {
|
|
- // FIXME: HOOKUP TO SERVERSIDE BEAMTABLES
|
|
- // Reset the timer for the drop of the next beamsection.
|
|
- self->sectionSpawnTime = level.time;
|
|
- }
|
|
- } // END OF BEAM SECTIONS
|
|
- // If the weapon has existed too long, make the next think detonate it.
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-
|
|
-
|
|
-/*
|
|
-==============
|
|
-Think_Homing
|
|
-==============
|
|
-*/
|
|
-void Think_Homing (gentity_t *self) {
|
|
- gentity_t *target_ent, *target_owner;
|
|
- float target_length;
|
|
- int i;
|
|
- vec3_t target_dir, forward, midbody;
|
|
- trace_t tr;
|
|
-
|
|
- vec3_t chosen_dir;
|
|
- float chosen_length;
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity(self);
|
|
-
|
|
- self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
- if(self->isDrainable){
|
|
- self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
- }
|
|
- if (self->powerLevelCurrent <= 0) {
|
|
- G_RemoveUserWeapon(self);
|
|
- return;
|
|
- }
|
|
-
|
|
- // Best way to get forward vector for this rocket?
|
|
- VectorCopy(self->s.pos.trDelta, forward);
|
|
- VectorNormalize(forward);
|
|
-
|
|
- // Set the base position
|
|
- VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
- self->s.pos.trTime = level.time;
|
|
- self->s.pos.trType = TR_LINEAR;
|
|
-
|
|
- // Set the first choice to an 'empty' one.
|
|
- VectorCopy(forward, chosen_dir);
|
|
- chosen_length = -1;
|
|
-
|
|
- // Cycle through the clients for a qualified target
|
|
- for (i = 0; i < level.maxclients; i++) {
|
|
- // Here we use target_ent to point to potential targets
|
|
- target_ent = &g_entities[i];
|
|
- target_owner = GetMissileOwnerEntity( self );
|
|
-
|
|
- if (!target_ent->inuse) continue;
|
|
- if (target_ent == target_owner) continue;
|
|
- if ( OnSameTeam( target_ent, target_owner ) ) continue;
|
|
-
|
|
- // Aim for the body, not the feet
|
|
- midbody[0] = target_ent->r.currentOrigin[0] +
|
|
- (target_ent->r.mins[0] + target_ent->r.maxs[0]) * 0.5;
|
|
- midbody[1] = target_ent->r.currentOrigin[1] +
|
|
- (target_ent->r.mins[1] + target_ent->r.maxs[1]) * 0.5;
|
|
- midbody[2] = target_ent->r.currentOrigin[2] +
|
|
- (target_ent->r.mins[2] + target_ent->r.maxs[2]) * 0.5;
|
|
-
|
|
- // Get the distance
|
|
- VectorSubtract(midbody, self->r.currentOrigin, target_dir);
|
|
- target_length = VectorLength(target_dir);
|
|
-
|
|
- // We don't home in on this entity if its not within range.
|
|
- if ( target_length > self->homRange ) continue;
|
|
-
|
|
- // Quick normalization of target_dir since
|
|
- // we already have the length
|
|
- target_dir[0] /= target_length;
|
|
- target_dir[1] /= target_length;
|
|
- target_dir[2] /= target_length;
|
|
-
|
|
- // We don't home in on this entity if its outside our view 'funnel' either.
|
|
- if ( DotProduct(forward, target_dir) < cos(DEG2RAD(self->homAngle)) ) continue;
|
|
-
|
|
- trap_Trace( &tr, self->r.currentOrigin, NULL, NULL,
|
|
- self->r.currentOrigin, ENTITYNUM_NONE, MASK_SHOT );
|
|
- if ( target_ent != &g_entities[tr.entityNum] ) continue;
|
|
-
|
|
- // Only pick this target if it's the closest to the missile,
|
|
- // but keep in account that there might not have been a pick
|
|
- // yet!
|
|
- if ( (target_length > chosen_length) && (chosen_length >= 0) ) continue;
|
|
-
|
|
- VectorCopy(target_dir, chosen_dir);
|
|
- chosen_length = target_length;
|
|
- }
|
|
-
|
|
- // Set the new direction.
|
|
- // FIXME: Does this remain the same if chosen_dir == forward? (aka no target found)
|
|
- VectorMA(forward, 0.05, chosen_dir, chosen_dir);
|
|
- VectorNormalize(chosen_dir);
|
|
- VectorScale(chosen_dir, self->speed, self->s.pos.trDelta);
|
|
-
|
|
- // If the weapon has existed too long, make the next think detonate it.
|
|
- if ( ( self->missileSpawnTime + self->maxMissileTime ) <= level.time ) {
|
|
- self->think = G_ExplodeUserWeapon;
|
|
- }
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-
|
|
-
|
|
-/*
|
|
-======================
|
|
-Think_CylinderHoming
|
|
-======================
|
|
-*/
|
|
-void Think_CylinderHoming (gentity_t *self) {
|
|
- gentity_t *target_ent, *target_owner;
|
|
- float target_length;
|
|
- int i;
|
|
- vec3_t target_dir, forward, midbody;
|
|
- trace_t tr;
|
|
-
|
|
- vec3_t chosen_dir;
|
|
- float chosen_length;
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity(self);
|
|
-
|
|
- self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
- if(self->isDrainable){
|
|
- self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
- }
|
|
- if (self->powerLevelCurrent <= 0) {
|
|
- G_RemoveUserWeapon(self);
|
|
- return;
|
|
- }
|
|
-
|
|
- // Best way to get forward vector for this rocket?
|
|
- VectorCopy(self->s.pos.trDelta, forward);
|
|
- VectorNormalize(forward);
|
|
-
|
|
- // Set the base position
|
|
- VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
- self->s.pos.trTime = level.time;
|
|
- self->s.pos.trType = TR_LINEAR;
|
|
-
|
|
- // Set the first choice to an 'empty' one.
|
|
- VectorCopy(forward, chosen_dir);
|
|
- chosen_length = -1;
|
|
-
|
|
- // Cycle through the clients for a qualified target
|
|
- for (i = 0; i < level.maxclients; i++) {
|
|
- // Here we use target_ent to point to potential targets
|
|
- target_ent = &g_entities[i];
|
|
- target_owner = GetMissileOwnerEntity( self );
|
|
-
|
|
- if (!target_ent->inuse) continue;
|
|
- if (target_ent == target_owner) continue;
|
|
- if ( OnSameTeam( target_ent, target_owner ) ) continue;
|
|
-
|
|
- // Aim for the body, not the feet
|
|
- midbody[0] = target_ent->r.currentOrigin[0] +
|
|
- (target_ent->r.mins[0] + target_ent->r.maxs[0]) * 0.5;
|
|
- midbody[1] = target_ent->r.currentOrigin[1] +
|
|
- (target_ent->r.mins[1] + target_ent->r.maxs[1]) * 0.5;
|
|
- midbody[2] = target_ent->r.currentOrigin[2] +
|
|
- (target_ent->r.mins[2] + target_ent->r.maxs[2]) * 0.5;
|
|
-
|
|
- // Get the distance on the horizontal plane
|
|
- VectorSubtract(midbody, self->r.currentOrigin, target_dir);
|
|
- target_dir[2] = 0; // <-- eliminate vertical difference here!
|
|
- target_length = VectorLength(target_dir);
|
|
-
|
|
- // We don't home in on this entity if its not within range on the
|
|
- // horizontal plane.
|
|
- if ( target_length > self->homRange ) continue;
|
|
-
|
|
- // We don't home in on this entity if it is 'higher up' than the missile is.
|
|
- if (midbody[2] > self->r.currentOrigin[2]) continue;
|
|
-
|
|
- // Now set the true direction, including the vertical offset!
|
|
- VectorSubtract(midbody, self->r.currentOrigin, target_dir);
|
|
- target_length = VectorLength(target_dir);
|
|
-
|
|
- // Quick normalization of target_dir since
|
|
- // we already have the length
|
|
- target_dir[0] /= target_length;
|
|
- target_dir[1] /= target_length;
|
|
- target_dir[2] /= target_length;
|
|
-
|
|
-
|
|
- trap_Trace( &tr, self->r.currentOrigin, NULL, NULL,
|
|
- self->r.currentOrigin, ENTITYNUM_NONE, MASK_SHOT );
|
|
- if ( target_ent != &g_entities[tr.entityNum] ) continue;
|
|
-
|
|
- // Only pick this target if it's the closest to the missile,
|
|
- // but keep in account that there might not have been a pick
|
|
- // yet!
|
|
- if ( (target_length > chosen_length) && (chosen_length >= 0) ) continue;
|
|
-
|
|
- VectorCopy(target_dir, chosen_dir);
|
|
- chosen_length = target_length;
|
|
- }
|
|
-
|
|
- // Set the new direction.
|
|
- // FIXME: Does this remain the same if chosen_dir == forward? (aka no target found)
|
|
- VectorMA(forward, 0.05, chosen_dir, chosen_dir);
|
|
- VectorNormalize(chosen_dir);
|
|
- VectorScale(chosen_dir, self->speed, self->s.pos.trDelta);
|
|
-
|
|
- // If the weapon has existed too long, make the next think detonate it.
|
|
- if ( ( self->missileSpawnTime + self->maxMissileTime ) <= level.time ) {
|
|
- self->think = G_ExplodeUserWeapon;
|
|
- }
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-
|
|
-/*
|
|
-=====================
|
|
-Think_NormalMissile
|
|
-=====================
|
|
-*/
|
|
-void Think_NormalMissile (gentity_t *self) {
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity(self);
|
|
-
|
|
- if(level.time > self->startTimer && self->radius){
|
|
- int radius = self->radius;
|
|
- self->radius = 0;
|
|
- VectorSet( self->r.mins, -radius, -radius, -radius );
|
|
- VectorSet( self->r.maxs, radius, radius, radius );
|
|
- VectorCopy( self->r.mins, self->r.absmin );
|
|
- VectorCopy( self->r.maxs, self->r.absmax );
|
|
- }
|
|
-
|
|
- self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
-
|
|
- if ((self->missileSpawnTime + self->maxMissileTime) <= level.time){
|
|
- self->think = G_ExplodeUserWeapon;
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
- return;
|
|
- }
|
|
-
|
|
- if(self->isDrainable && self->s.eType != ET_BEAMHEAD){
|
|
- self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
- }
|
|
- if (self->powerLevelCurrent <= 0) {
|
|
- G_RemoveUserWeapon(self);
|
|
- return;
|
|
- }
|
|
- if((missileOwner->client->ps.bitFlags & usingBoost) && self->s.eType == ET_BEAMHEAD){
|
|
- self->powerLevelCurrent += 10 + ((float)self->powerLevelTotal * 0.005);
|
|
- self->speed += 10 + ((float)missileOwner->client->ps.powerLevel[plMaximum] * 0.0003);
|
|
- }
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-
|
|
-/*
|
|
-=====================
|
|
-Think_NormalMissileStruggle
|
|
-=====================
|
|
-*/
|
|
-void Think_NormalMissileStruggle (gentity_t *self) {
|
|
- int powerDifference;
|
|
- int speedDifference;
|
|
- int power1,power2;
|
|
- int speed1,speed2;
|
|
- int result;
|
|
- vec3_t forward,dir,dir2,start;
|
|
- trace_t *trace;
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
- self->s.dashDir[1] = self->powerLevelCurrent;
|
|
- if(self->powerLevelCurrent < 1){
|
|
- G_RemoveUserWeapon(self);
|
|
- return;
|
|
- }
|
|
- self->enemy->client->ps.bitFlags |= isStruggling;
|
|
- missileOwner->client->ps.bitFlags |= isStruggling;
|
|
- if((missileOwner->client->ps.bitFlags & usingBoost) && self->s.eType == ET_BEAMHEAD && missileOwner->client->ps.powerLevel[plCurrent] > 1){
|
|
- self->powerLevelCurrent += 5 + ((float)self->powerLevelTotal * 0.005);
|
|
- self->speed += 5 + ((float)missileOwner->client->ps.powerLevel[plMaximum] * 0.0003);
|
|
- }
|
|
- if(self->strugglingAllyAttack){
|
|
- if(self->s.eType == ET_BEAMHEAD){
|
|
- self->ally->speed += 5 + ((float)self->speed * 0.0003);
|
|
- self->speed -= 5 + ((float)self->speed * 0.0003);
|
|
- }else{
|
|
- self->speed -= 5;
|
|
- self->ally->speed += 5;
|
|
- }
|
|
- }
|
|
- speed1 = self->speed;
|
|
- speed2 = self->enemy->speed;
|
|
- power1 = self->powerLevelCurrent;
|
|
- power2 = self->enemy->powerLevelCurrent;
|
|
- speedDifference = speed1-speed2;
|
|
- powerDifference = power1-power2;
|
|
- result = /*speedDifference +*/ powerDifference;
|
|
- VectorCopy(self->r.currentOrigin,start);
|
|
- VectorCopy(self->s.pos.trDelta,dir);
|
|
- VectorCopy(self->r.currentAngles,dir2);
|
|
- VectorNormalize(dir);
|
|
- VectorScale(dir,0.2,dir);
|
|
- VectorAdd(dir,dir2,dir);
|
|
- VectorNormalize(dir);
|
|
- VectorCopy(start,self->s.pos.trBase);
|
|
- VectorScale(dir,result,self->s.pos.trDelta);
|
|
- VectorCopy(start,self->r.currentOrigin);
|
|
- VectorCopy(dir,self->r.currentAngles);
|
|
- self->s.pos.trType = TR_LINEAR;
|
|
- self->s.pos.trTime = level.time;
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-
|
|
-/*
|
|
-====================
|
|
-G_UserWeaponDamage
|
|
-====================
|
|
-Does damage to a client or object
|
|
-target = entity to be damaged
|
|
-inflictor = entity doing the damage
|
|
-attacker = entity the inflictor belongs to
|
|
-dir = direction for knockback
|
|
-point = origin of attack
|
|
-*/
|
|
-void G_UserWeaponDamage(gentity_t *target,gentity_t *inflictor,gentity_t *attacker,vec3_t dir,vec3_t point,int damage,int dflags,int knockback){
|
|
- gclient_t *tgClient;
|
|
- if(!target->takedamage){return;}
|
|
- if(level.intermissionQueued){return;}
|
|
- if(!inflictor){inflictor = &g_entities[ENTITYNUM_WORLD];}
|
|
- if(!attacker){attacker = &g_entities[ENTITYNUM_WORLD];}
|
|
- tgClient = target->client;
|
|
- if(tgClient && tgClient->noclip){return;}
|
|
- else{VectorNormalize(dir);}
|
|
- if(target == attacker){knockback = 0;}
|
|
- if(target->flags & FL_NO_KNOCKBACK) {knockback = 0;}
|
|
- if(tgClient){
|
|
- //VectorCopy(dir ? dir : target->r.currentOrigin,tgClient->damage_from);
|
|
- tgClient->ps.persistant[PERS_ATTACKER] = attacker ? attacker->s.number : ENTITYNUM_WORLD;
|
|
- tgClient->damage_fromWorld = dir ? qfalse : qtrue;
|
|
- tgClient->lasthurt_client = attacker->s.number;
|
|
- tgClient->lasthurt_mod = 1;
|
|
- }
|
|
- if(damage){
|
|
- if(tgClient){
|
|
- attacker->client->ps.states |= causedDamage;
|
|
- G_LocationImpact(inflictor->r.currentOrigin,target,inflictor);
|
|
- if(target == attacker){damage *= 0.2f;}
|
|
- attacker->client->ps.powerLevel[plHealthPool] += damage * 0.7;
|
|
- attacker->client->ps.powerLevel[plMaximumPool] += damage * 0.3;
|
|
- tgClient->ps.powerLevel[plDamageFromEnergy] += damage;
|
|
- if(inflictor->impede){tgClient->ps.timers[tmImpede] = inflictor->impede;}
|
|
- if(knockback){
|
|
- tgClient->ps.timers[tmKnockback] = knockback;
|
|
- tgClient->ps.powerups[PW_KNOCKBACK_SPEED] = knockback;
|
|
- if(tgClient->lasthurt_location == LOCATION_BACK){tgClient->ps.knockBackDirection = 6;}
|
|
- else if(tgClient->lasthurt_location == LOCATION_LEFT){tgClient->ps.knockBackDirection = 4;}
|
|
- else if(tgClient->lasthurt_location == LOCATION_RIGHT){tgClient->ps.knockBackDirection = 3;}
|
|
- else{tgClient->ps.knockBackDirection = 5;}
|
|
- }
|
|
- }
|
|
- }
|
|
-}
|
|
-
|
|
-/*
|
|
-=====================
|
|
-G_UserRadiusDamage
|
|
-=====================
|
|
-*/
|
|
-qboolean G_UserRadiusDamage ( vec3_t origin, gentity_t *attacker, gentity_t *ignore, float centerDamage, float radius,int extraKnockback ) {
|
|
- int entityList[MAX_GENTITIES];
|
|
- int numListedEntities;
|
|
-
|
|
- float realDamage, distance;
|
|
- gentity_t *ent;
|
|
- gentity_t *owner = GetMissileOwnerEntity(ignore);
|
|
-
|
|
- vec3_t mins, maxs;
|
|
- vec3_t deltaRadius;
|
|
- vec3_t dir;
|
|
- int i, e;
|
|
- qboolean hitClient = qfalse;
|
|
-
|
|
- if ( radius < 1 ) {
|
|
- radius = 1;
|
|
- }
|
|
-
|
|
- for ( i = 0 ; i < 3 ; i++ ) {
|
|
- mins[i] = origin[i] - radius;
|
|
- maxs[i] = origin[i] + radius;
|
|
- }
|
|
-
|
|
- numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
-
|
|
- for ( e = 0 ; e < numListedEntities ; e++ ) {
|
|
- ent = &g_entities[entityList[ e ]];
|
|
-
|
|
- if (ent == ignore)
|
|
- continue;
|
|
- if (!ent->takedamage)
|
|
- continue;
|
|
- if (ent == owner && ignore->isBlindable)
|
|
- continue;
|
|
-
|
|
- // Find the distance between the perimeter of the entities' bounding box
|
|
- // and the explosion's origin.
|
|
- for ( i = 0 ; i < 3 ; i++ ) {
|
|
- if ( origin[i] < ent->r.absmin[i] ) {
|
|
- deltaRadius[i] = ent->r.absmin[i] - origin[i];
|
|
- } else if ( origin[i] > ent->r.absmax[i] ) {
|
|
- deltaRadius[i] = origin[i] - ent->r.absmax[i];
|
|
- } else {
|
|
- deltaRadius[i] = 0;
|
|
- }
|
|
- }
|
|
-
|
|
- distance = VectorLength( deltaRadius );
|
|
- if ( distance >= radius ) {
|
|
- continue;
|
|
- }
|
|
-
|
|
- realDamage = centerDamage /** ( 1.0 - distance / radius )*/;
|
|
-
|
|
- if(CanDamage(ent, origin)){
|
|
- VectorSubtract (ent->r.currentOrigin, origin, dir);
|
|
- // push the center of mass higher than the origin so players
|
|
- // get knocked into the air more
|
|
- dir[2] += 24;
|
|
- G_LocationImpact(origin,ent,attacker);
|
|
- if(ent->client){
|
|
- ent->client->ps.powerLevel[plDamageGeneric] += realDamage;
|
|
- if(ent->pain){ent->pain(ent,attacker,realDamage);}
|
|
- /*if(ent->client->lasthurt_location == LOCATION_FRONT){
|
|
- ent->client->ps.timers[tmBlind] = 10000;
|
|
- }*/
|
|
- }
|
|
- }
|
|
- }
|
|
- return hitClient;
|
|
-}
|
|
-void UserHitscan_Fire (gentity_t *self, g_userWeapon_t *weaponInfo, int weaponNum, vec3_t muzzle, vec3_t forward ) {
|
|
- trace_t tr;
|
|
- vec3_t end;
|
|
- gentity_t *tempEnt;
|
|
- gentity_t *tempEnt2;
|
|
- gentity_t *traceEnt;
|
|
- int i, passent;
|
|
- float rnd;
|
|
- float physics_range;
|
|
- // Get the end point
|
|
- if ( weaponInfo->physics_range_min != weaponInfo->physics_range_max ) {
|
|
- rnd = crandom();
|
|
- physics_range = ( 1.0f - rnd ) * weaponInfo->physics_range_min + rnd * weaponInfo->physics_range_max;
|
|
- } else {
|
|
- physics_range = weaponInfo->physics_range_max;
|
|
- }
|
|
- VectorMA (muzzle, physics_range, forward, end);
|
|
-
|
|
- // Set the player to not be able of being hurt by this shot
|
|
- passent = self->s.number;
|
|
-
|
|
- // Note; if bounce is enabled, this will let the projectile bounce a max. of 10 times
|
|
- for (i = 0; i < 10; i++) {
|
|
-
|
|
- trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);
|
|
- traceEnt = &g_entities[ tr.entityNum ];
|
|
- // snap the endpos to integers, but nudged towards the line
|
|
- SnapVectorTowards( tr.endpos, muzzle );
|
|
-
|
|
- // Add an explosion event entity, and make it free itself after the event.
|
|
- tempEnt = G_Spawn();
|
|
- tempEnt->s.eType = ET_GENERAL;
|
|
- tempEnt->s.clientNum = self->s.number;
|
|
- tempEnt->s.weapon = weaponNum;
|
|
- SnapVectorTowards( tr.endpos, tempEnt->s.pos.trBase ); // save net bandwidth
|
|
- G_SetOrigin( tempEnt, tr.endpos );
|
|
-
|
|
- if ( traceEnt->takedamage && traceEnt->client ) {
|
|
- G_AddEvent( tempEnt, EV_MISSILE_HIT, DirToByte( tr.plane.normal ) );
|
|
- tempEnt->s.otherEntityNum = traceEnt->s.number;
|
|
-
|
|
- // Log an accuracy hit for this weapon
|
|
-
|
|
- } else if( tr.fraction == 1.0f || tr.surfaceFlags & SURF_NOIMPACT ) {
|
|
- G_AddEvent( tempEnt, EV_MISSILE_MISS_AIR, DirToByte( tr.plane.normal ) );
|
|
- } else if( tr.surfaceFlags & SURF_METALSTEPS ) {
|
|
- G_AddEvent( tempEnt, EV_MISSILE_MISS_METAL, DirToByte( tr.plane.normal ) );
|
|
- } else {
|
|
- G_AddEvent( tempEnt, EV_MISSILE_MISS, DirToByte( tr.plane.normal ) );
|
|
- }
|
|
- tempEnt->freeAfterEvent = qtrue;
|
|
- trap_LinkEntity( tempEnt );
|
|
-
|
|
- // the final trace endpos will be the terminal point of the rail trail
|
|
-
|
|
-
|
|
- // Send railgun beam effect. The client will draw it only if set through the
|
|
- // weapon's scripted graphics clientside, but we _always_ have to send the event.
|
|
- tempEnt2 = G_TempEntity( tr.endpos, EV_NULL );
|
|
- tempEnt2->s.clientNum = self->s.number;
|
|
- tempEnt2->s.weapon = weaponNum;
|
|
- VectorCopy( muzzle, tempEnt2->s.origin2 );
|
|
-
|
|
- if ( tr.surfaceFlags & SURF_NOIMPACT ) {
|
|
- return;
|
|
- }
|
|
-
|
|
- if ( traceEnt->takedamage) {
|
|
- G_UserWeaponDamage( traceEnt, self, self, forward, tr.endpos,
|
|
- weaponInfo->damage_damage, 0,
|
|
- weaponInfo->damage_extraKnockback );
|
|
- }
|
|
- break;
|
|
- }
|
|
-}
|
|
-
|
|
-
|
|
-void Release_UserWeapon( gentity_t *self, qboolean altfire ) {
|
|
- // Work on primary or alternate fire.
|
|
- if ( altfire ) {
|
|
-
|
|
- // If the altfired entity exists, then destroy it and null the pointer (just incase).
|
|
- if ( self->contAltfire_ent ) {
|
|
- G_FreeEntity( self->contAltfire_ent );
|
|
- self->contAltfire_ent = NULL;
|
|
- }
|
|
- } else {
|
|
- // If the fired entity exists, then destroy it and null the pointer (just incase).
|
|
- if ( self->contFire_ent ) {
|
|
- G_FreeEntity( self->contAltfire_ent );
|
|
- self->contAltfire_ent = NULL;
|
|
- }
|
|
- }
|
|
-}
|
|
-
|
|
-/*
|
|
-=================
|
|
-Fire_UserWeapon
|
|
-=================
|
|
-*/
|
|
-void Fire_UserWeapon( gentity_t *self, vec3_t start, vec3_t dir, qboolean altfire ) {
|
|
- gentity_t *bolt;
|
|
- g_userWeapon_t *weaponInfo;
|
|
- float powerScale;
|
|
- int homingType;
|
|
- vec3_t muzzle, forward, right, up;
|
|
- vec3_t firingDir, firingStart; // <-- Contain the altered aiming and origin vectors.
|
|
- powerScale = ((float)self->client->ps.powerLevel[plCurrent] / 1000.0) * self->client->ps.baseStats[stEnergyAttack];
|
|
- // Get a hold of the weapon we're firing.
|
|
- // If altfire is used and it exists, use altfire, else use regular fire
|
|
- // Due to the nature of altfire presence detection, the regular fire must
|
|
- // always be loaded first. Since this is only one pointer dereference it is
|
|
- // negligable.
|
|
- weaponInfo = G_FindUserWeaponData( self->s.clientNum, self->s.weapon );
|
|
- if ( ( weaponInfo->general_bitflags & WPF_ALTWEAPONPRESENT ) && altfire ) {
|
|
- weaponInfo = G_FindUserAltWeaponData( self->s.clientNum, self->s.weapon );
|
|
- }
|
|
-
|
|
- // Retrieve the muzzle that was set in g_weapons.c
|
|
- G_GetMuzzleSettings(muzzle, forward, right, up);
|
|
-
|
|
-
|
|
- // Determine the corrected firing angle and firing origin
|
|
-
|
|
- VectorCopy( dir, firingDir);
|
|
- if ( weaponInfo->firing_angleW_min || weaponInfo->firing_angleW_max ) {
|
|
- vec3_t temp;
|
|
- float rnd;
|
|
- float firing_angleW;
|
|
-
|
|
- rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
- firing_angleW = ( 1.0f - rnd ) * weaponInfo->firing_angleW_min + rnd * weaponInfo->firing_angleW_max;
|
|
-
|
|
- if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetWFlip ) {
|
|
- RotatePointAroundVector( temp, up, firingDir, firing_angleW );
|
|
- } else {
|
|
- RotatePointAroundVector( temp, up, firingDir, -firing_angleW );
|
|
- }
|
|
- VectorCopy( temp, firingDir );
|
|
- }
|
|
- if ( weaponInfo->firing_angleH_min || weaponInfo->firing_angleH_max ) {
|
|
- vec3_t temp;
|
|
- float rnd;
|
|
- float firing_angleH;
|
|
-
|
|
- rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
- firing_angleH = ( 1.0f - rnd ) * weaponInfo->firing_angleH_min + rnd * weaponInfo->firing_angleH_max;
|
|
-
|
|
- if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetHFlip ) {
|
|
- RotatePointAroundVector( temp, right, firingDir, firing_angleH );
|
|
- } else {
|
|
- RotatePointAroundVector( temp, right, firingDir, -firing_angleH );
|
|
- }
|
|
- VectorCopy( temp, firingDir );
|
|
- }
|
|
- VectorNormalize( firingDir );
|
|
-
|
|
- VectorCopy( start, firingStart );
|
|
- if ( weaponInfo->firing_offsetW_min || weaponInfo->firing_offsetW_max ) {
|
|
- float rnd;
|
|
- float firing_offsetW;
|
|
-
|
|
- rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
- firing_offsetW = ( 1.0f - rnd ) * weaponInfo->firing_offsetW_min + rnd * weaponInfo->firing_offsetW_max;
|
|
-
|
|
- if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetWFlip ) {
|
|
- VectorMA( firingStart, -firing_offsetW, right, firingStart );
|
|
- } else {
|
|
- VectorMA( firingStart, firing_offsetW, right, firingStart );
|
|
- }
|
|
- }
|
|
- if ( weaponInfo->firing_offsetH_min || weaponInfo->firing_offsetH_max ) {
|
|
- float rnd;
|
|
- float firing_offsetH;
|
|
-
|
|
- rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
- firing_offsetH = ( 1.0f - rnd ) * weaponInfo->firing_offsetH_min + rnd * weaponInfo->firing_offsetH_max;
|
|
-
|
|
- if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetHFlip ) {
|
|
- VectorMA( firingStart, -firing_offsetH, up, firingStart );
|
|
- } else {
|
|
- VectorMA( firingStart, firing_offsetH, up, firingStart );
|
|
- }
|
|
- }
|
|
-
|
|
-
|
|
- // Take the necessary steps to configure and fire the weapon.
|
|
- switch ( weaponInfo->general_type ) {
|
|
-
|
|
- /* MISSILES */
|
|
-
|
|
- case skillTypeMISSILE:
|
|
- bolt = G_Spawn();
|
|
- bolt->classname = "rocket";
|
|
- bolt->s.eType = ET_MISSILE;
|
|
- bolt->startTimer = level.time + 800;
|
|
- bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
-
|
|
- // Set the weapon number correct, depending on altfire status.
|
|
- if ( !altfire ) {
|
|
- bolt->s.weapon = self->s.weapon;
|
|
- } else {
|
|
- bolt->s.weapon = self->s.weapon + ALTWEAPON_OFFSET;
|
|
- }
|
|
- bolt->r.ownerNum = self->s.number;
|
|
- bolt->s.clientNum = self->s.number; // <-- clientNum is free on all but ET_PLAYER so
|
|
- bolt->parent = self; // use it to store the owner for clientside.
|
|
-
|
|
- bolt->damage = weaponInfo->damage_damage;
|
|
- bolt->splashRadius = weaponInfo->damage_radius;
|
|
- bolt->splashDuration = weaponInfo->damage_radiusDuration;
|
|
- bolt->extraKnockback = weaponInfo->damage_extraKnockback;
|
|
- bolt->impede = weaponInfo->damage_impede;
|
|
- bolt->isSwattable = weaponInfo->physics_swat;
|
|
- bolt->isDrainable = weaponInfo->physics_drain;
|
|
- bolt->isBlindable = weaponInfo->physics_blind;
|
|
- if (altfire) {
|
|
- bolt->chargelvl = self->client->ps.stats[stChargePercentSecondary];
|
|
- self->client->ps.stats[stChargePercentSecondary] = 0; // Only reset it here!
|
|
- } else {
|
|
- bolt->chargelvl = self->client->ps.stats[stChargePercentPrimary];
|
|
- self->client->ps.stats[stChargePercentPrimary] = 0; // Only reset it here!
|
|
- }
|
|
- bolt->s.powerups = bolt->chargelvl; // Use this free field to transfer chargelvl
|
|
- bolt->powerLevelCurrent = bolt->powerLevelTotal = bolt->damage * ((float)bolt->chargelvl / 100.0f) * powerScale;
|
|
- bolt->s.dashDir[0] = bolt->powerLevelTotal; // Use this free field to transfer total power level
|
|
-
|
|
- bolt->clipmask = MASK_SHOT;
|
|
- bolt->target_ent = NULL;
|
|
- bolt->takedamage = qtrue;
|
|
- self->s.dashDir[2] = 0.0f;
|
|
- bolt->count = 0;
|
|
- {
|
|
- float radius;
|
|
- radius = weaponInfo->physics_radius + weaponInfo->physics_radiusMultiplier * (bolt->chargelvl / 100.0f);
|
|
- radius = sqrt((radius * radius) / 3); // inverse of Pythagoras
|
|
- bolt->radius = radius;
|
|
- radius = 1;
|
|
- VectorSet( bolt->r.mins, -radius, -radius, -radius );
|
|
- VectorSet( bolt->r.maxs, radius, radius, radius );
|
|
- VectorCopy( bolt->r.mins, bolt->r.absmin );
|
|
- VectorCopy( bolt->r.maxs, bolt->r.absmax );
|
|
-
|
|
- bolt->r.contents = CONTENTS_CORPSE; // So we can pass through a missile, but can still fire at it.
|
|
-
|
|
- bolt->die = G_DieUserWeapon;
|
|
- }
|
|
-
|
|
- bolt->speed = weaponInfo->physics_speed;
|
|
- bolt->accel = weaponInfo->physics_acceleration;
|
|
- bolt->bounceFrac = weaponInfo->physics_bounceFrac;
|
|
- bolt->bouncesLeft = weaponInfo->physics_maxBounces;
|
|
- bolt->timesTriggered = 0;
|
|
-
|
|
- // Configure the type correctly if we're using gravity or acceleration
|
|
- // Gravity affection takes precedence over acceleration.
|
|
- // FIXME: Can we get both simultaneously?
|
|
- if (weaponInfo->physics_gravity > 0) {
|
|
- bolt->gravity = (weaponInfo->physics_gravity / 100) * 1000;
|
|
- bolt->s.pos.trType = TR_MAPGRAVITY;
|
|
- bolt->s.pos.trDuration = bolt->gravity;
|
|
- } else {
|
|
- if (bolt->accel != 0.0f) {
|
|
- bolt->s.pos.trType = TR_ACCEL;
|
|
- bolt->s.pos.trDuration = bolt->accel;
|
|
- } else {
|
|
- bolt->s.pos.trType = TR_LINEAR;
|
|
- }
|
|
- }
|
|
-
|
|
- // move a bit on the very first frame
|
|
- bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
|
|
- VectorCopy( firingStart, bolt->s.pos.trBase );
|
|
- VectorCopy( firingStart, bolt->r.currentOrigin );
|
|
-
|
|
- VectorCopy( self->client->ps.viewangles, self->s.angles );
|
|
- VectorCopy( self->client->ps.viewangles, bolt->s.angles2 );
|
|
-
|
|
- VectorScale( firingDir, bolt->speed, bolt->s.pos.trDelta );
|
|
- VectorCopy(firingDir,bolt->movedir);
|
|
- // This saves network bandwidth.
|
|
- SnapVector( bolt->s.pos.trDelta );
|
|
-
|
|
- // Set the correct think based on homing properties.
|
|
- switch (weaponInfo->homing_type) {
|
|
- case HOM_PROX:
|
|
- bolt->think = Think_ProxDet;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- bolt->homRange = weaponInfo->homing_range;
|
|
- break;
|
|
- case HOM_GUIDED:
|
|
- bolt->think = Think_Guided;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- // Kill off previously set accel, bounce and gravity. Guided missiles
|
|
- // do not accelerate, bounce or experience gravity.
|
|
- bolt->accel = 0;
|
|
- bolt->bounceFrac = 0;
|
|
- bolt->bouncesLeft = 0;
|
|
- bolt->gravity = 0;
|
|
- // Set guidetarget.
|
|
- bolt->s.eFlags |= EF_GUIDED;
|
|
- self->client->guidetarget = bolt;
|
|
- bolt->guided = qtrue;
|
|
- break;
|
|
- case HOM_REGULAR:
|
|
- bolt->think = Think_Homing;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- bolt->homRange = weaponInfo->homing_range;
|
|
- //bolt->homAccel = weaponInfo->homing_accel;
|
|
- break;
|
|
- case HOM_CYLINDER:
|
|
- bolt->think = Think_CylinderHoming;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- bolt->homRange = weaponInfo->homing_range;
|
|
- //bolt->homAccel = weaponInfo->homing_accel;
|
|
- break;
|
|
- case HOM_ARCH:
|
|
- bolt->think = Think_NormalMissile;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- bolt->bounceFrac = 0;
|
|
- bolt->bouncesLeft = 0;
|
|
- bolt->accel = 0;
|
|
- bolt->gravity = 0;
|
|
- {
|
|
- trace_t trace;
|
|
- vec3_t begin, mid, end;
|
|
- float length;
|
|
-
|
|
- VectorCopy( bolt->s.pos.trBase, begin );
|
|
- VectorMA( begin, weaponInfo->homing_range, dir, end );
|
|
- trap_Trace( &trace, begin, NULL, NULL, end, self->s.number, MASK_SHOT );
|
|
- VectorCopy( trace.endpos, end );
|
|
-
|
|
- length = Distance( begin, end );
|
|
-
|
|
- VectorMA( begin, length / 2.0f, firingDir, mid );
|
|
-
|
|
- bolt->s.pos.trType = TR_ARCH;
|
|
- bolt->s.pos.trDuration = 1000.0f * length / weaponInfo->physics_speed;
|
|
- VectorCopy( begin, bolt->s.pos.trBase );
|
|
- VectorCopy( mid, bolt->s.angles2 );
|
|
- VectorCopy( end, bolt->s.pos.trDelta );
|
|
- }
|
|
- break;
|
|
- case HOM_DRUNKEN:
|
|
- bolt->think = Think_NormalMissile;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- bolt->s.pos.trType = TR_DRUNKEN;
|
|
- bolt->s.pos.trDuration = weaponInfo->homing_range;
|
|
- break;
|
|
- case HOM_NONE:
|
|
- bolt->think = Think_NormalMissile;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- break;
|
|
- default:
|
|
- // This should never happen!
|
|
- break;
|
|
- }
|
|
- bolt->missileSpawnTime = level.time;
|
|
- bolt->maxMissileTime = weaponInfo->physics_lifetime;
|
|
- break;
|
|
- /* BEAMS */
|
|
- case skillTypeBEAM:
|
|
- homingType = weaponInfo->homing_type;
|
|
- bolt = G_Spawn();
|
|
- bolt->classname = "user_beam";
|
|
- bolt->s.eType = ET_BEAMHEAD;
|
|
- bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
- bolt->startTimer = level.time + 800;
|
|
- // Set the weapon number correct, depending on altfire status.
|
|
- if ( !altfire ) {
|
|
- bolt->s.weapon = self->s.weapon;
|
|
- }
|
|
- else{
|
|
- bolt->s.weapon = self->s.weapon + ALTWEAPON_OFFSET;
|
|
- }
|
|
- bolt->r.ownerNum = self->s.number;
|
|
- bolt->s.clientNum = self->s.number; // <-- clientNum is free on all but ET_PLAYER so
|
|
- bolt->parent = self; // use it to store the owner for clientside.
|
|
- VectorCopy( self->client->ps.viewangles, bolt->s.angles ); // store the correct angles
|
|
- bolt->damage = weaponInfo->damage_damage;
|
|
- bolt->splashRadius = weaponInfo->damage_radius;
|
|
- bolt->splashDuration = weaponInfo->damage_radiusDuration;
|
|
- bolt->extraKnockback = weaponInfo->damage_extraKnockback;
|
|
- bolt->isSwattable = weaponInfo->physics_swat;
|
|
- bolt->isDrainable = weaponInfo->physics_drain;
|
|
- bolt->isBlindable = weaponInfo->physics_blind;
|
|
- if (altfire) {
|
|
- bolt->chargelvl = self->client->ps.stats[stChargePercentSecondary];
|
|
- self->client->ps.stats[stChargePercentSecondary] = 0;
|
|
- }
|
|
- else{
|
|
- bolt->chargelvl = self->client->ps.stats[stChargePercentPrimary];
|
|
- self->client->ps.stats[stChargePercentPrimary] = 0;
|
|
- }
|
|
- bolt->s.powerups = bolt->chargelvl;
|
|
- bolt->powerLevelCurrent = bolt->powerLevelTotal = bolt->damage * (1.0f+(float)bolt->chargelvl / 100.0f) * powerScale;
|
|
- bolt->s.dashDir[0] = bolt->powerLevelTotal; // Use this free field to transfer total power level
|
|
- // FIXME: Hack into the old mod style, since it's still needed for now
|
|
- bolt->takedamage = qtrue;
|
|
- bolt->clipmask = MASK_SHOT;
|
|
- bolt->target_ent = NULL;
|
|
- self->s.dashDir[2] = 0.0f;
|
|
- bolt->count = 0;
|
|
- {
|
|
- float radius;
|
|
- radius = weaponInfo->physics_radius + weaponInfo->physics_radiusMultiplier * (bolt->chargelvl / 100.0f);
|
|
- radius = sqrt((radius * radius) / 3); // inverse of Pythagoras
|
|
- bolt->radius = radius;
|
|
- radius = 1;
|
|
- VectorSet( bolt->r.mins, -radius, -radius, -radius );
|
|
- VectorSet( bolt->r.maxs, radius, radius, radius );
|
|
- VectorCopy( bolt->r.mins, bolt->r.absmin );
|
|
- VectorCopy( bolt->r.maxs, bolt->r.absmax );
|
|
- bolt->r.contents = CONTENTS_CORPSE; // So we can pass through a missile, but can still fire at it.
|
|
- bolt->die = G_DieUserWeapon;
|
|
- }
|
|
- bolt->speed = weaponInfo->physics_speed;
|
|
- bolt->accel = 0;
|
|
- bolt->bounceFrac = 0;
|
|
- bolt->bouncesLeft = 0;
|
|
- bolt->gravity = 0;
|
|
- bolt->s.pos.trType = TR_LINEAR;
|
|
- VectorScale( dir, bolt->speed, bolt->s.pos.trDelta );
|
|
- SnapVector( bolt->s.pos.trDelta );
|
|
- bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
|
|
- VectorCopy( start, bolt->s.pos.trBase );
|
|
- VectorCopy( start, bolt->r.currentOrigin );
|
|
- VectorCopy( firingDir, bolt->movedir );
|
|
- VectorCopy( self->client->ps.viewangles, self->s.angles );
|
|
- VectorCopy( self->client->ps.viewangles, bolt->s.angles2 );
|
|
- if(self->client->ps.lockedTarget > 0){homingType = HOM_GUIDED;}
|
|
- switch (homingType) {
|
|
- case HOM_GUIDED:
|
|
- bolt->think = Think_Guided;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- bolt->s.eFlags |= EF_GUIDED;
|
|
- self->client->guidetarget = bolt;
|
|
- bolt->guided = qtrue;
|
|
- break;
|
|
-
|
|
- case HOM_ARCH:
|
|
- case HOM_PROX:
|
|
- case HOM_REGULAR:
|
|
- case HOM_CYLINDER:
|
|
- case HOM_NONE:
|
|
- bolt->think = Think_NormalMissile;
|
|
- bolt->nextthink = level.time + FRAMETIME;
|
|
- self->client->guidetarget = bolt;
|
|
- bolt->guided = qtrue;
|
|
- break;
|
|
- default:
|
|
- break;
|
|
- }
|
|
- bolt->missileSpawnTime = level.time;
|
|
- bolt->maxMissileTime = weaponInfo->physics_lifetime;
|
|
- break;
|
|
- case skillTypeHITSCAN:
|
|
- if ( !altfire ) {
|
|
- UserHitscan_Fire( self, weaponInfo, self->s.weapon, firingStart, firingDir );
|
|
- self->client->ps.stats[stChargePercentPrimary] = 0;
|
|
- } else {
|
|
- UserHitscan_Fire( self, weaponInfo, self->s.weapon + ALTWEAPON_OFFSET, firingStart, firingDir );
|
|
- self->client->ps.stats[stChargePercentSecondary] = 0;
|
|
- }
|
|
- break;
|
|
- case skillTypeTRIGGER:
|
|
- {
|
|
- gentity_t *trigTarget;
|
|
- trace_t trace;
|
|
- vec3_t newBase, newDir, traceEnd;
|
|
- int pass_ent, i;
|
|
- float traceDist;
|
|
-
|
|
- for ( i = 0; i < MAX_GENTITIES; i++ ) {
|
|
-
|
|
- // Select the entity
|
|
- trigTarget = &g_entities[i];
|
|
-
|
|
- // See if it is affected by the trigger
|
|
- if ( !trigTarget->inuse ) {
|
|
- continue;
|
|
- } else if ( trigTarget->s.eType != ET_MISSILE ) {
|
|
- continue;
|
|
- } else if ( !altfire && ( trigTarget->s.weapon != self->s.weapon + ALTWEAPON_OFFSET )) {
|
|
- continue;
|
|
- } else if ( altfire && ( trigTarget->s.weapon != self->s.weapon )) {
|
|
- continue;
|
|
- } else if (trigTarget->s.clientNum != self->s.number){
|
|
- continue;
|
|
- }
|
|
-
|
|
- // Get the target's current position
|
|
- BG_EvaluateTrajectory( &trigTarget->s, &trigTarget->s.pos, level.time, newBase );
|
|
-
|
|
- // Don't process any missiles beyond the maximum range of control
|
|
- if ( Distance(newBase, start) > weaponInfo->physics_range_max ) {
|
|
- continue;
|
|
- }
|
|
-
|
|
- // Set the target's new starting position and increase its trigger count
|
|
- VectorCopy( newBase, trigTarget->s.pos.trBase );
|
|
- trigTarget->timesTriggered++;
|
|
-
|
|
- // Setup the new directional vector for the triggered weapon.
|
|
- VectorSubtract( start, newBase, newDir );
|
|
- traceDist = VectorLength( newDir ) + 100000;
|
|
-
|
|
- VectorScale( dir, traceDist, traceEnd );
|
|
- VectorAdd( traceEnd, start, traceEnd );
|
|
-
|
|
- pass_ent = self->s.number;
|
|
- trap_Trace( &trace, start, NULL, NULL, traceEnd, pass_ent, MASK_PLAYERSOLID );
|
|
- VectorSubtract( trace.endpos, start, traceEnd );
|
|
-
|
|
- VectorAdd( traceEnd, newDir, newDir );
|
|
- VectorNormalize( newDir );
|
|
-
|
|
- // Set the triggered weapon's new direction and speed
|
|
- VectorScale( newDir, weaponInfo->physics_speed, trigTarget->s.pos.trDelta );
|
|
- vectoangles( newDir, trigTarget->s.angles );
|
|
- trigTarget->s.pos.trTime = level.time;
|
|
-
|
|
- // Configure the type correctly if we're using gravity or acceleration
|
|
- // Gravity affection takes precedence over acceleration.
|
|
- // FIXME: Can we get both simultaneously?
|
|
- if (weaponInfo->physics_gravity > 0) {
|
|
- trigTarget->accel = 0;
|
|
- trigTarget->gravity = (weaponInfo->physics_gravity / 100) * 1000;
|
|
- trigTarget->s.pos.trType = TR_MAPGRAVITY;
|
|
- trigTarget->s.pos.trDuration = trigTarget->gravity;
|
|
- } else if (weaponInfo->physics_acceleration != 0.0f) {
|
|
- trigTarget->gravity = 0;
|
|
- trigTarget->accel = weaponInfo->physics_acceleration;
|
|
- trigTarget->s.pos.trType = TR_ACCEL;
|
|
- trigTarget->s.pos.trDuration = trigTarget->accel;
|
|
- } else {
|
|
- trigTarget->accel = 0;
|
|
- trigTarget->gravity = 0;
|
|
- trigTarget->s.pos.trType = TR_LINEAR;
|
|
- }
|
|
- }
|
|
- }
|
|
- break;
|
|
- case skillTypeNONE:
|
|
- break;
|
|
- default:
|
|
- break;
|
|
- }
|
|
-}
|
|
-void G_DieUserWeapon(gentity_t *self, gentity_t *inflictor,gentity_t *attacker, int damage, int mod ) {
|
|
- if (inflictor == self)
|
|
- return;
|
|
- self->takedamage = qfalse;
|
|
- self->think = G_ExplodeUserWeapon;
|
|
- self->nextthink = level.time;
|
|
-}
|
|
-void G_ExplodeUserWeapon( gentity_t *self ) {
|
|
- // Handles actual detonation of the weapon in question.
|
|
- vec3_t dir = { 0, 0, 1};
|
|
- vec3_t origin;
|
|
- self->takedamage = qfalse;
|
|
-
|
|
- // Terminate guidance
|
|
- if ( self->guided ) {
|
|
- g_entities[ self->s.clientNum ].client->ps.weaponstate = WEAPON_READY;
|
|
- }
|
|
-
|
|
- // Calculate current position
|
|
- BG_EvaluateTrajectory(&self->s,&self->s.pos,level.time,origin);
|
|
- SnapVector( origin ); // save net bandwith
|
|
- SnapVectorTowards( origin, self->s.pos.trBase ); // save net bandwidth
|
|
- G_SetOrigin( self, origin );
|
|
- self->s.eType = ET_EXPLOSION;
|
|
- self->splashEnd = level.time + self->splashDuration;
|
|
- self->splashTimer = level.time;
|
|
- G_AddEvent( self, EV_MISSILE_MISS_AIR, DirToByte( dir ) );
|
|
- trap_LinkEntity( self );
|
|
-}
|
|
-static void G_BounceUserMissile( gentity_t *self, trace_t *trace ) {
|
|
- vec3_t velocity;
|
|
- float dot;
|
|
- int hitTime;
|
|
-
|
|
- // reflect the velocity on the trace plane
|
|
- hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
|
|
- BG_EvaluateTrajectoryDelta( &self->s, &self->s.pos, hitTime, velocity );
|
|
- dot = DotProduct( velocity, trace->plane.normal );
|
|
- VectorMA( velocity, -2 * dot, trace->plane.normal, self->s.pos.trDelta );
|
|
-
|
|
- VectorScale( self->s.pos.trDelta, self->bounceFrac, self->s.pos.trDelta );
|
|
-
|
|
- // check for stop
|
|
- if ( trace->plane.normal[2] > 0.2 && VectorLength( self->s.pos.trDelta ) < 40 ) {
|
|
- G_ExplodeUserWeapon ( self );
|
|
- return;
|
|
- }
|
|
-
|
|
- VectorAdd( self->r.currentOrigin, trace->plane.normal, self->r.currentOrigin);
|
|
- VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
- self->s.pos.trTime = level.time;
|
|
-}
|
|
-
|
|
-static void G_HoldUserMissile( gentity_t *self, gentity_t *other ) {
|
|
- vec3_t velocity;
|
|
- vec3_t dir;
|
|
- float dot;
|
|
- int hitTime;
|
|
-
|
|
- if(other->s.eType == ET_PLAYER){
|
|
- if(self->s.eType == ET_MISSILE){
|
|
- VectorCopy(self->s.angles,dir);
|
|
- }else{
|
|
- VectorCopy(self->client->ps.viewangles,dir);
|
|
- }
|
|
- hitTime = level.previousTime + ( level.time - level.previousTime ) * 0;
|
|
- BG_EvaluateTrajectoryDelta( &self->s, &self->s.pos, hitTime, velocity );
|
|
- dot = DotProduct( velocity, dir );
|
|
- VectorMA( velocity, -2 * dot, dir, self->s.pos.trDelta );
|
|
-
|
|
- VectorScale( self->s.pos.trDelta, self->bounceFrac, self->s.pos.trDelta );
|
|
-
|
|
- VectorAdd( self->r.currentOrigin, dir, self->r.currentOrigin);
|
|
- VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
- }
|
|
-
|
|
- self->s.pos.trTime = level.time;
|
|
-}
|
|
-
|
|
-static void G_PushUserMissile( gentity_t *self, gentity_t *other ) {
|
|
- vec3_t forward, right, up;
|
|
- vec3_t muzzle;
|
|
- float dist;
|
|
-
|
|
- // Calculate based on the other player view angles.
|
|
- AngleVectors( other->client->ps.viewangles, forward, right, up );
|
|
- CalcMuzzlePoint( other, forward, right, up, muzzle );
|
|
-
|
|
- // Copy our starting position to the trajectory.
|
|
- VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
-
|
|
- // Setup the new directional vector for the fired weapon.
|
|
- VectorSubtract( muzzle, self->r.currentOrigin, muzzle );
|
|
- dist = VectorLength( muzzle ) + SOLID_ADD;
|
|
-
|
|
- VectorScale( forward, dist, forward );
|
|
- VectorAdd( forward, muzzle, muzzle );
|
|
- VectorNormalize( muzzle );
|
|
-
|
|
- // Set the weapon's direction and speed in the trajectory,
|
|
- // and set its angles.
|
|
- VectorScale( muzzle, self->speed, forward );
|
|
- VectorCopy( forward, self->s.pos.trDelta );
|
|
- VectorScale( self->s.pos.trDelta, self->bounceFrac, self->s.pos.trDelta );
|
|
- vectoangles( muzzle, self->s.angles );
|
|
-
|
|
- // This will save net bandwidth.
|
|
- SnapVector( self->s.pos.trDelta );
|
|
-
|
|
- self->s.pos.trType = TR_LINEAR;
|
|
- self->s.pos.trTime = level.time;
|
|
-}
|
|
-
|
|
-static void Think_NormalMissileStrugglePlayer( gentity_t *self ) {
|
|
- vec3_t fwd;
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
- AngleVectors(self->enemy->r.currentAngles, fwd, NULL, NULL);
|
|
- VectorNormalize(fwd);
|
|
- G_HoldUserMissile(self,self->enemy);
|
|
- self->s.dashDir[1] = self->powerLevelCurrent;
|
|
- self->bounceFrac = 0.0f;
|
|
- self->enemy->client->ps.timers[tmStruggleBlock] += 100;
|
|
- if(self->enemy->client->ps.timers[tmStruggleBlock] >= 3000){
|
|
- self->enemy->client->ps.bitFlags &= ~isStruggling;
|
|
- missileOwner->client->ps.bitFlags &= ~isStruggling;
|
|
- G_ExplodeUserWeapon(self);
|
|
- return;
|
|
- //if(self->s.eType == ET_MISSILE){self->think = Think_NormalMissileBurnPlayer;}
|
|
- //if(self->s.eType == ET_BEAMHEAD){self->think = Think_NormalMissileRidePlayer;}
|
|
- }
|
|
- self->enemy->client->ps.bitFlags |= isStruggling;
|
|
- missileOwner->client->ps.bitFlags |= isStruggling;
|
|
- self->powerLevelCurrent -= (self->enemy->client->ps.powerLevel[plFatigue] * 0.2) * 0.1;
|
|
- if(self->enemy->client->ps.powerLevel[plFatigue] * 0.5 >= (self->powerLevelCurrent)){
|
|
- self->enemy->client->ps.bitFlags &= ~isStruggling;
|
|
- missileOwner->client->ps.bitFlags &= ~isStruggling;
|
|
- self->s.eType = ET_MISSILE;
|
|
- self->think = Think_NormalMissile;
|
|
- self->bounceFrac = 0.75;
|
|
- G_PushUserMissile(self,self->enemy);
|
|
- }
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-static void Think_NormalMissileBurnPlayer( gentity_t *self ) {
|
|
- vec3_t fwd;
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
- AngleVectors(self->enemy->r.currentAngles, fwd, NULL, NULL);
|
|
- VectorNormalize(fwd);
|
|
- G_HoldUserMissile(self,self->enemy);
|
|
- self->s.dashDir[1] = self->powerLevelCurrent;
|
|
- self->bounceFrac = 0.0f;
|
|
- self->enemy->client->ps.states |= isBurning;
|
|
- self->enemy->client->ps.timers[tmBurning] += 100;
|
|
- G_UserWeaponDamage(self->enemy,self,missileOwner,fwd,self->s.origin,self->powerLevelCurrent / 15.0,0,self->extraKnockback);
|
|
- if(self->enemy->client->ps.timers[tmBurning] >= 1500){
|
|
- self->freeAfterEvent = qtrue;
|
|
- self->enemy->client->ps.states &= ~isBurning;
|
|
- }
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-static void Think_NormalMissileRidePlayer( gentity_t *self ){
|
|
- vec3_t fwd;
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
- AngleVectors(self->enemy->r.currentAngles, fwd, NULL, NULL);
|
|
- VectorNormalize(fwd);
|
|
- self->s.dashDir[1] = self->powerLevelCurrent;
|
|
- self->bounceFrac = 0.0f;
|
|
- self->enemy->client->ps.states |= isRiding;
|
|
- self->enemy->client->ps.timers[tmRiding] += 100;
|
|
- G_UserWeaponDamage(self->enemy,self,missileOwner,fwd,self->s.origin,self->powerLevelCurrent / 25.0,0,self->extraKnockback);
|
|
- if(self->enemy->client->ps.timers[tmRiding] >= 2500){
|
|
- self->freeAfterEvent = qtrue;
|
|
- self->enemy->client->ps.states &= ~isRiding;
|
|
- //self->enemy->client->ps.states |= canRideEscape;
|
|
- }
|
|
- self->nextthink = level.time + FRAMETIME;
|
|
-}
|
|
-/*
|
|
-============
|
|
-G_LocationImpact
|
|
-============
|
|
-*/
|
|
-void G_LocationImpact(vec3_t point, gentity_t* targ, gentity_t* attacker) {
|
|
- vec3_t attackPath;
|
|
- vec3_t attackAngle;
|
|
- int clientRotation;
|
|
- int attackRotation; // Degrees rotation around client.
|
|
- int impactRotation; // used to check back of head vs. face
|
|
- VectorSubtract(targ->r.currentOrigin, point, attackPath);
|
|
- vectoangles(attackPath, attackAngle);
|
|
- clientRotation = targ->client->ps.viewangles[YAW];
|
|
- attackRotation = attackAngle[YAW];
|
|
- impactRotation = abs(clientRotation-attackRotation);
|
|
- impactRotation += 45; // just to make it easier to work with
|
|
- impactRotation = impactRotation % 360; // Keep it in the 0-359 range
|
|
- if (impactRotation < 90)
|
|
- targ->client->lasthurt_location = LOCATION_BACK;
|
|
- else if (impactRotation < 180)
|
|
- targ->client->lasthurt_location = LOCATION_RIGHT;
|
|
- else if (impactRotation < 270)
|
|
- targ->client->lasthurt_location = LOCATION_FRONT;
|
|
- else if (impactRotation < 360)
|
|
- targ->client->lasthurt_location = LOCATION_LEFT;
|
|
- else
|
|
- targ->client->lasthurt_location = LOCATION_NONE;
|
|
-}
|
|
-
|
|
-// Handles impact of the weapon with map geometry or entities.
|
|
-void G_ImpactUserWeapon(gentity_t *self,trace_t *trace){
|
|
- gentity_t *other;
|
|
- qboolean hitClient = qfalse;
|
|
- vec3_t velocity;
|
|
- int radius;
|
|
- other = &g_entities[trace->entityNum];
|
|
- G_LocationImpact(trace->endpos,other,GetMissileOwnerEntity(self));
|
|
- //G_Printf("Attack's power level is : %i\n",self->powerLevelCurrent);
|
|
- // Initiate Player Interaction
|
|
- if(other->s.eType == ET_PLAYER){
|
|
- SnapVectorTowards( trace->endpos, self->s.pos.trBase );
|
|
- G_SetOrigin( self, trace->endpos );
|
|
- if((other->client->ps.bitFlags & usingBlock) && other->client->lasthurt_location == LOCATION_FRONT){
|
|
- if(self->isSwattable){
|
|
- self->bounceFrac = 1.0f;
|
|
- G_PushUserMissile(self, other);
|
|
- return;
|
|
- }
|
|
- else{
|
|
- self->strugglingPlayer = qtrue;
|
|
- self->enemy = other;
|
|
- self->enemy->client->ps.timers[tmStruggleBlock] = 0;
|
|
- self->think = Think_NormalMissileStrugglePlayer;
|
|
- self->nextthink = level.time;
|
|
- if(!other->client->ps.lockedTarget){
|
|
- other->client->ps.lockedTarget = self->s.number;
|
|
- other->client->ps.lockedPosition = &self->r.currentOrigin;
|
|
- }
|
|
- return;
|
|
- }
|
|
- }
|
|
- else if(self->s.eType == ET_MISSILE){
|
|
- if(self->isSwattable){
|
|
- if(((self->powerLevelCurrent * 10) < other->client->ps.powerLevel[plFatigue]) && other->client->ps.bitFlags & usingAlter){
|
|
- self->freeAfterEvent = qtrue;
|
|
- return;
|
|
- }
|
|
- //Do Explosion Below
|
|
- }
|
|
- else{
|
|
- /*self->enemy = other;
|
|
- self->enemy->client->ps.timers[tmBurning] = 0;
|
|
- self->think = Think_NormalMissileBurnPlayer;
|
|
- self->nextthink = level.time;
|
|
- return;*/
|
|
- }
|
|
- }
|
|
- else if(self->s.eType == ET_BEAMHEAD && !(other->client->ps.states & isRiding)){
|
|
- /*self->enemy = other;
|
|
- self->enemy->client->ps.timers[tmRiding] = 0;
|
|
- self->think = Think_NormalMissileRidePlayer;
|
|
- self->nextthink = level.time;
|
|
- return;*/
|
|
- }
|
|
- }
|
|
- // Initiate Attack Interaction
|
|
- else if(self->s.eType == ET_MISSILE){
|
|
- //G_Printf("I am definitely a missile!\n");
|
|
- if(other->s.eType == ET_BEAMHEAD){
|
|
- //G_Printf("Their Beam absorbed our missile!\n");
|
|
- other->powerLevelCurrent += self->powerLevelCurrent;
|
|
- self->freeAfterEvent = qtrue;
|
|
- return;
|
|
- }
|
|
- else if(other->s.eType == ET_MISSILE){
|
|
- if(other->isSwattable){
|
|
- if(!self->isSwattable){
|
|
- //G_Printf("Our missile absorbed their swattable!\n");
|
|
- self->powerLevelCurrent += other->powerLevelCurrent;
|
|
- other->freeAfterEvent = qtrue;
|
|
- return;
|
|
- }
|
|
- else{
|
|
- //G_Printf("Swattables collide explosion!\n");
|
|
- }
|
|
- }
|
|
- else{
|
|
- if(other->powerLevelCurrent >= self->powerLevelCurrent){
|
|
- //G_Printf("Our missile absorbed their weaker missile!\n");
|
|
- other->powerLevelCurrent += self->powerLevelCurrent;
|
|
- self->freeAfterEvent = qtrue;
|
|
- }
|
|
- return;
|
|
- }
|
|
- }
|
|
- }
|
|
- else if(self->s.eType == ET_BEAMHEAD){
|
|
- if(other->s.eType == ET_BEAMHEAD){
|
|
- if(!(self->client->ps.bitFlags & isStruggling)){
|
|
- G_AddEvent(self,EV_POWER_STRUGGLE_START, DirToByte( trace->plane.normal));
|
|
- other->enemy = self;
|
|
- self->s.dashDir[2] = 1.0f;
|
|
- self->think = Think_NormalMissileStruggle;
|
|
- self->nextthink = level.time;
|
|
- self->enemy = other;
|
|
- self->enemy->s.dashDir[2] = 1.0f;
|
|
- self->enemy->think = Think_NormalMissileStruggle;
|
|
- self->enemy->nextthink = level.time;
|
|
- if(self->s.eFlags & EF_GUIDED){self->s.eFlags &= ~EF_GUIDED;}
|
|
- if(other->s.eFlags & EF_GUIDED){other->s.eFlags &= ~EF_GUIDED;}
|
|
- }
|
|
- return;
|
|
- }
|
|
- else if(other->s.eType == ET_MISSILE){return;}
|
|
- }
|
|
- // Bounce off the world
|
|
- if(self->bounceFrac && self->bouncesLeft && !other->takedamage){
|
|
- G_BounceUserMissile(self, trace);
|
|
- self->bouncesLeft--;
|
|
- return;
|
|
- }
|
|
- // Hit a player or the world
|
|
- else if(other->client || !other->takedamage){
|
|
- self->takedamage = qtrue;
|
|
- if(other->takedamage && self->damage) {
|
|
- BG_EvaluateTrajectoryDelta(&self->s, &self->s.pos, level.time, velocity);
|
|
- if(VectorLength( velocity ) == 0){
|
|
- velocity[2] = 1;
|
|
- }
|
|
- G_UserWeaponDamage(other,self,GetMissileOwnerEntity(self),velocity,self->s.origin,self->powerLevelCurrent,0,self->extraKnockback);
|
|
- }
|
|
- }
|
|
- // Explode
|
|
- if(!(other->takedamage) || (other->s.eType == ET_PLAYER)){
|
|
- if(self->guided){g_entities[self->s.clientNum].client->ps.weaponstate = WEAPON_READY;}
|
|
- if(other->takedamage && other->client){
|
|
- G_AddEvent( self, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
|
|
- self->s.otherEntityNum = other->s.number;
|
|
- }
|
|
- else if(trace->surfaceFlags & SURF_METALSTEPS){G_AddEvent( self, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) );}
|
|
- else{G_AddEvent( self, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) );}
|
|
- SnapVectorTowards( trace->endpos, self->s.pos.trBase );
|
|
- G_SetOrigin( self, trace->endpos );
|
|
- self->powerLevelCurrent *= 0.5;
|
|
- self->s.eType = ET_EXPLOSION;
|
|
- self->splashEnd = level.time + self->splashDuration;
|
|
- self->splashTimer = level.time + 100;
|
|
- trap_LinkEntity(self);
|
|
- }
|
|
-}
|
|
-void G_DetachUserWeapon (gentity_t *self) {
|
|
- gentity_t *other;
|
|
- other = self->enemy;
|
|
- if(self->s.eType == ET_BEAMHEAD && self->powerLevelCurrent > 0){
|
|
- g_entities[self->s.clientNum].client->ps.weaponstate = WEAPON_READY;
|
|
- self->s.eType = ET_MISSILE;
|
|
- self->think = Think_NormalMissile;
|
|
- }
|
|
-}
|
|
-void G_RemoveUserWeapon (gentity_t *self) {
|
|
- gentity_t *other;
|
|
- other = self->enemy;
|
|
- other->client->ps.bitFlags &= ~isStruggling;
|
|
- self->client->ps.bitFlags &= ~isStruggling;
|
|
- if(other->client->ps.lockedTarget >= MAX_CLIENTS){
|
|
- other->client->ps.lockedPosition = NULL;
|
|
- other->client->ps.lockedTarget = 0;
|
|
- }
|
|
- if (self->guided) {
|
|
- g_entities[ self->s.clientNum ].client->ps.weaponstate = WEAPON_READY;
|
|
- }
|
|
- if (self->s.eType == ET_BEAMHEAD && self->powerLevelCurrent > 0) {
|
|
- G_ExplodeUserWeapon ( self );
|
|
- return;
|
|
- }
|
|
- G_FreeEntity( self );
|
|
-}
|
|
-
|
|
-void Missile_Smooth (gentity_t *ent, vec3_t origin, trace_t *tr) {
|
|
- int touch[MAX_GENTITIES];
|
|
- vec3_t mins, maxs;
|
|
- int num;
|
|
-
|
|
- num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES);
|
|
- VectorAdd(origin,ent->r.mins, mins);
|
|
- VectorAdd(origin,ent->r.maxs, maxs);
|
|
- VectorCopy(origin,ent->s.pos.trBase);
|
|
- ent->s.pos.trTime = level.time;
|
|
-}
|
|
-
|
|
-/*
|
|
- -----------------------------------------
|
|
- R U N F R A M E F U N C T I O N S
|
|
- -----------------------------------------
|
|
-*/
|
|
-void G_RunUserExplosion(gentity_t *ent) {
|
|
- int radius,power;
|
|
- float step;
|
|
- if(level.time > ent->splashTimer && ent->powerLevelCurrent > 0){
|
|
- ent->splashTimer = level.time + 100;
|
|
- step = (1.0 - ((float)ent->splashEnd - (float)level.time) / (float)ent->splashDuration);
|
|
- radius = step * ent->splashRadius;
|
|
- power = (ent->powerLevelCurrent * (100.0/(float)ent->splashDuration));
|
|
- //G_Printf("Explosion power : %i\n",power);
|
|
- G_UserRadiusDamage(ent->r.currentOrigin,GetMissileOwnerEntity(ent),ent,power,radius,ent->extraKnockback);
|
|
- }
|
|
- if(level.time >= ent->splashEnd || ent->powerLevelCurrent <= 0){
|
|
- ent->freeAfterEvent = qtrue;
|
|
- }
|
|
-}
|
|
-void G_RunUserMissile( gentity_t *ent ) {
|
|
- vec3_t origin;
|
|
- trace_t trace;
|
|
- int pass_ent;
|
|
- int beamKnockBack;
|
|
- gentity_t *traceEnt;
|
|
- vec3_t muzzle, forward, right, up;
|
|
- vec3_t start,end;
|
|
- trace_t trace2;
|
|
- gentity_t *traceEnt2;
|
|
- gentity_t *missileOwner = GetMissileOwnerEntity(ent);
|
|
- BG_EvaluateTrajectory( &ent->s, &ent->s.pos, level.time, origin );
|
|
- if (ent->count) {
|
|
- pass_ent = ent->s.number;
|
|
- }
|
|
- else{
|
|
- pass_ent = ent->r.ownerNum;
|
|
- }
|
|
- // trace a line from the previous position to the current position
|
|
- trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, pass_ent, ent->clipmask );
|
|
-
|
|
- if ( trace.startsolid || trace.allsolid ) {
|
|
- // make sure the trace.entityNum is set to the entity we're stuck in
|
|
- trap_Trace(&trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, pass_ent, ent->clipmask );
|
|
- trace.fraction = 0;
|
|
- }
|
|
- else {
|
|
- VectorCopy( trace.endpos, ent->r.currentOrigin );
|
|
- }
|
|
- //Missile_Smooth(ent,origin,&trace);
|
|
- trap_LinkEntity(ent);
|
|
- //G_Printf("%i\n",ent->r.currentOrigin);
|
|
-
|
|
- if(trace.fraction != 1){
|
|
- // never explode or bounce on sky
|
|
- if ( trace.surfaceFlags & SURF_NOIMPACT ) {
|
|
- G_RemoveUserWeapon( ent );
|
|
- return;
|
|
- }
|
|
-
|
|
- G_ImpactUserWeapon(ent, &trace);
|
|
-
|
|
- if ((ent->s.eType != ET_MISSILE) && (ent->s.eType != ET_BEAMHEAD)){return;}
|
|
- }
|
|
- // if the attack wasn't yet outside the player body
|
|
- if (!ent->count) {
|
|
- // check if the attack is outside the owner bbox
|
|
- trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ENTITYNUM_NONE, ent->clipmask );
|
|
- if (!trace.startsolid || trace.entityNum != ent->r.ownerNum) {
|
|
- ent->count = 1;
|
|
- }
|
|
- }
|
|
-
|
|
- // Here we test if something entered a beam
|
|
- if(ent->s.eType == ET_BEAMHEAD && !(ent->s.eFlags & EF_GUIDED)){
|
|
- // Get player direction
|
|
- AngleVectors( missileOwner->client->ps.viewangles, forward, right, up );
|
|
- CalcMuzzlePoint( missileOwner, forward, right, up, muzzle );
|
|
- // Get player position and beam head position
|
|
- BG_EvaluateTrajectory( &missileOwner->s, &missileOwner->s.pos, level.time, start );
|
|
- BG_EvaluateTrajectory( &ent->s, &ent->s.pos, level.time, end );
|
|
- // Trace between the two positions
|
|
- trap_Trace (&trace2, start, ent->r.mins, ent->r.maxs, end, ent->s.number, MASK_SHOT);
|
|
- traceEnt2 = &g_entities[ trace2.entityNum ];
|
|
- // Snap the endpos to integers, but nudged towards the line
|
|
- SnapVectorTowards( trace2.endpos, muzzle );
|
|
- // If a player is holding block when he entered the beam, inititate push struggle.
|
|
- if (traceEnt2->client && traceEnt2->takedamage && trace2.fraction != 1 && (traceEnt2->client->ps.bitFlags & usingBlock)) {
|
|
- G_SetOrigin(ent,trace2.endpos);
|
|
- G_ImpactUserWeapon(ent,&trace2);
|
|
- // Else if a player that entered the beam isn't blocking, burn them and/or push them.
|
|
- }else if (traceEnt2->client && traceEnt2->takedamage && trace2.fraction != 1 && !(traceEnt2->client->ps.bitFlags & usingBlock)) {
|
|
- }
|
|
- if ((ent->s.eType != ET_MISSILE) && (ent->s.eType != ET_BEAMHEAD)) {
|
|
- return;
|
|
- }
|
|
- }
|
|
-
|
|
- // check think function after bouncing
|
|
- G_RunThink( ent );
|
|
-}
|
|
+#include "g_local.h"
|
|
+#define MISSILE_PRESTEP_TIME 50
|
|
+#define SOLID_ADD 400 // Add extra to guide line to go behind solids with weapon
|
|
+#define BEAM_SECTION_TIME 520 // Timeframe to spawn a new beam section
|
|
+/*-------------------------------
|
|
+ M I S C F U N C T I O N S
|
|
+-------------------------------*/
|
|
+/*=======================
|
|
+GetMissileOwnerEntity
|
|
+=======================*/
|
|
+gentity_t *GetMissileOwnerEntity (gentity_t *missile) {
|
|
+ gentity_t *parent;
|
|
+
|
|
+ parent = missile->parent;
|
|
+ while((parent->s.eType != ET_PLAYER)&&(parent->s.eType != ET_INVISIBLE)) {
|
|
+ parent = GetMissileOwnerEntity( parent );
|
|
+ }
|
|
+ return parent;
|
|
+}
|
|
+/*---------------------------------
|
|
+ T H I N K F U N C T I O N S
|
|
+---------------------------------*/
|
|
+
|
|
+// Prototypes
|
|
+void Think_NormalMissile (gentity_t *self);
|
|
+void Think_NormalMissileStruggle (gentity_t *self);
|
|
+static void Think_NormalMissileBurnPlayer (gentity_t *self);
|
|
+static void Think_NormalMissileRidePlayer (gentity_t *self);
|
|
+static void Think_NormalMissileStrugglePlayer (gentity_t *self);
|
|
+static void G_StruggleUserMissile( gentity_t *self, gentity_t *other );
|
|
+static void G_PushUserMissile( gentity_t *self, gentity_t *other );
|
|
+static void G_BounceUserMissile( gentity_t *self, trace_t *trace );
|
|
+void G_UserWeaponDamage(gentity_t *target,gentity_t *inflictor,gentity_t *attacker,vec3_t dir,vec3_t point,int damage,int dflags,int knockback);
|
|
+void G_LocationImpact(vec3_t point, gentity_t* targ, gentity_t* attacker);
|
|
+
|
|
+/*
|
|
+===============
|
|
+Think_ProxDet
|
|
+===============
|
|
+*/
|
|
+void Think_ProxDet( gentity_t *self ) {
|
|
+ int i; // loop variable
|
|
+ gentity_t *target_ent;
|
|
+ gentity_t *target_owner;
|
|
+ vec3_t midbody;
|
|
+ float proxDistance; // The distance between a potential
|
|
+ // target and the missile.
|
|
+
|
|
+ for (i = 0; i < level.maxclients; i++) {
|
|
+ // Here we use target_ent to point to potential targets
|
|
+ target_ent = &g_entities[i];
|
|
+ target_owner = GetMissileOwnerEntity( self );
|
|
+
|
|
+ // We don't bother with non-used clients, ourselves,
|
|
+ // or clients on our team.
|
|
+ if ( !target_ent->inuse ) continue;
|
|
+ if ( target_ent == target_owner ) continue;
|
|
+ if ( OnSameTeam( target_ent, target_owner ) ) continue;
|
|
+
|
|
+ // Judge the distance from the middle of the body, not the feet
|
|
+ midbody[0] = target_ent->r.currentOrigin[0] +
|
|
+ (target_ent->r.mins[0] + target_ent->r.maxs[0]) * 0.5;
|
|
+ midbody[1] = target_ent->r.currentOrigin[1] +
|
|
+ (target_ent->r.mins[1] + target_ent->r.maxs[1]) * 0.5;
|
|
+ midbody[2] = target_ent->r.currentOrigin[2] +
|
|
+ (target_ent->r.mins[2] + target_ent->r.maxs[2]) * 0.5;
|
|
+
|
|
+ proxDistance = Distance(midbody, self->r.currentOrigin);
|
|
+
|
|
+ // We don't detonate on this entity if it's not within homing range.
|
|
+ if ( proxDistance > self->homRange ) continue;
|
|
+
|
|
+
|
|
+ // We're within range and have to detonate with the next think.
|
|
+ self->think = G_ExplodeUserWeapon;
|
|
+
|
|
+ // Force premature exit of Bounded Linear Search
|
|
+ i = level.maxclients;
|
|
+ }
|
|
+
|
|
+ // If the weapon has existed too long, make the next think detonate it.
|
|
+ if ( ( self->missileSpawnTime + self->maxMissileTime ) <= level.time) {
|
|
+ self->think = G_ExplodeUserWeapon;
|
|
+ }
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+==============
|
|
+Think_Guided
|
|
+==============
|
|
+*/
|
|
+void Think_Guided (gentity_t *self) {
|
|
+ vec3_t forward, right, up;
|
|
+ vec3_t muzzle;
|
|
+ float dist;
|
|
+ gentity_t *owner = GetMissileOwnerEntity(self);
|
|
+
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
+ if(self->isDrainable && self->s.eType != ET_BEAMHEAD){
|
|
+ self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
+ }
|
|
+ if (self->powerLevelCurrent <= 0) {
|
|
+ G_RemoveUserWeapon(self);
|
|
+ return;
|
|
+ }
|
|
+ if((owner->client->ps.bitFlags & usingBoost) && self->s.eType == ET_BEAMHEAD){
|
|
+ self->powerLevelCurrent += 1 + ((float)self->powerLevelTotal * 0.005);
|
|
+ self->speed += 10 + ((float)owner->client->ps.powerLevel[plMaximum] * 0.0003);
|
|
+ }
|
|
+
|
|
+ // If our weapon's owner can't be found, skip anything related to guiding.
|
|
+ if (!owner){G_Printf( S_COLOR_YELLOW "WARNING: Think_Guided reports unknown fired weapon!\n" );}
|
|
+ else{
|
|
+ AngleVectors( owner->client->ps.viewangles, forward, right, up );
|
|
+ CalcMuzzlePoint( owner, forward, right, up, muzzle );
|
|
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
+ VectorSubtract( muzzle, self->r.currentOrigin, muzzle );
|
|
+ dist = VectorLength( muzzle ) + SOLID_ADD;
|
|
+ VectorScale( forward, dist, forward );
|
|
+ VectorAdd( forward, muzzle, muzzle );
|
|
+ VectorNormalize( muzzle );
|
|
+ VectorScale( muzzle, self->speed, forward );
|
|
+ VectorCopy( forward, self->s.pos.trDelta );
|
|
+ vectoangles( muzzle, self->s.angles );
|
|
+ SnapVector( self->s.pos.trDelta );
|
|
+ self->s.pos.trType = TR_LINEAR;
|
|
+ self->s.pos.trTime = level.time;
|
|
+ }
|
|
+ // If we're dealing with a beam, we have to record beamsections.
|
|
+ if (self->s.eType == ET_BEAMHEAD) {
|
|
+ // Have we reached the time at which to start a new section?
|
|
+ if ( ( self->sectionSpawnTime + self->newSectionTime ) <= level.time ) {
|
|
+ // FIXME: HOOKUP TO SERVERSIDE BEAMTABLES
|
|
+ // Reset the timer for the drop of the next beamsection.
|
|
+ self->sectionSpawnTime = level.time;
|
|
+ }
|
|
+ } // END OF BEAM SECTIONS
|
|
+ // If the weapon has existed too long, make the next think detonate it.
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+==============
|
|
+Think_Homing
|
|
+==============
|
|
+*/
|
|
+void Think_Homing (gentity_t *self) {
|
|
+ gentity_t *target_ent, *target_owner;
|
|
+ float target_length;
|
|
+ int i;
|
|
+ vec3_t target_dir, forward, midbody;
|
|
+ trace_t tr;
|
|
+
|
|
+ vec3_t chosen_dir;
|
|
+ float chosen_length;
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity(self);
|
|
+
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
+ if(self->isDrainable){
|
|
+ self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
+ }
|
|
+ if (self->powerLevelCurrent <= 0) {
|
|
+ G_RemoveUserWeapon(self);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Best way to get forward vector for this rocket?
|
|
+ VectorCopy(self->s.pos.trDelta, forward);
|
|
+ VectorNormalize(forward);
|
|
+
|
|
+ // Set the base position
|
|
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
+ self->s.pos.trTime = level.time;
|
|
+ self->s.pos.trType = TR_LINEAR;
|
|
+
|
|
+ // Set the first choice to an 'empty' one.
|
|
+ VectorCopy(forward, chosen_dir);
|
|
+ chosen_length = -1;
|
|
+
|
|
+ // Cycle through the clients for a qualified target
|
|
+ for (i = 0; i < level.maxclients; i++) {
|
|
+ // Here we use target_ent to point to potential targets
|
|
+ target_ent = &g_entities[i];
|
|
+ target_owner = GetMissileOwnerEntity( self );
|
|
+
|
|
+ if (!target_ent->inuse) continue;
|
|
+ if (target_ent == target_owner) continue;
|
|
+ if ( OnSameTeam( target_ent, target_owner ) ) continue;
|
|
+
|
|
+ // Aim for the body, not the feet
|
|
+ midbody[0] = target_ent->r.currentOrigin[0] +
|
|
+ (target_ent->r.mins[0] + target_ent->r.maxs[0]) * 0.5;
|
|
+ midbody[1] = target_ent->r.currentOrigin[1] +
|
|
+ (target_ent->r.mins[1] + target_ent->r.maxs[1]) * 0.5;
|
|
+ midbody[2] = target_ent->r.currentOrigin[2] +
|
|
+ (target_ent->r.mins[2] + target_ent->r.maxs[2]) * 0.5;
|
|
+
|
|
+ // Get the distance
|
|
+ VectorSubtract(midbody, self->r.currentOrigin, target_dir);
|
|
+ target_length = VectorLength(target_dir);
|
|
+
|
|
+ // We don't home in on this entity if its not within range.
|
|
+ if ( target_length > self->homRange ) continue;
|
|
+
|
|
+ // Quick normalization of target_dir since
|
|
+ // we already have the length
|
|
+ target_dir[0] /= target_length;
|
|
+ target_dir[1] /= target_length;
|
|
+ target_dir[2] /= target_length;
|
|
+
|
|
+ // We don't home in on this entity if its outside our view 'funnel' either.
|
|
+ if ( DotProduct(forward, target_dir) < cos(DEG2RAD(self->homAngle)) ) continue;
|
|
+
|
|
+ trap_Trace( &tr, self->r.currentOrigin, NULL, NULL,
|
|
+ self->r.currentOrigin, ENTITYNUM_NONE, MASK_SHOT );
|
|
+ if ( target_ent != &g_entities[tr.entityNum] ) continue;
|
|
+
|
|
+ // Only pick this target if it's the closest to the missile,
|
|
+ // but keep in account that there might not have been a pick
|
|
+ // yet!
|
|
+ if ( (target_length > chosen_length) && (chosen_length >= 0) ) continue;
|
|
+
|
|
+ VectorCopy(target_dir, chosen_dir);
|
|
+ chosen_length = target_length;
|
|
+ }
|
|
+
|
|
+ // Set the new direction.
|
|
+ // FIXME: Does this remain the same if chosen_dir == forward? (aka no target found)
|
|
+ VectorMA(forward, 0.05, chosen_dir, chosen_dir);
|
|
+ VectorNormalize(chosen_dir);
|
|
+ VectorScale(chosen_dir, self->speed, self->s.pos.trDelta);
|
|
+
|
|
+ // If the weapon has existed too long, make the next think detonate it.
|
|
+ if ( ( self->missileSpawnTime + self->maxMissileTime ) <= level.time ) {
|
|
+ self->think = G_ExplodeUserWeapon;
|
|
+ }
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+======================
|
|
+Think_CylinderHoming
|
|
+======================
|
|
+*/
|
|
+void Think_CylinderHoming (gentity_t *self) {
|
|
+ gentity_t *target_ent, *target_owner;
|
|
+ float target_length;
|
|
+ int i;
|
|
+ vec3_t target_dir, forward, midbody;
|
|
+ trace_t tr;
|
|
+
|
|
+ vec3_t chosen_dir;
|
|
+ float chosen_length;
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity(self);
|
|
+
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
+ if(self->isDrainable){
|
|
+ self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
+ }
|
|
+ if (self->powerLevelCurrent <= 0) {
|
|
+ G_RemoveUserWeapon(self);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Best way to get forward vector for this rocket?
|
|
+ VectorCopy(self->s.pos.trDelta, forward);
|
|
+ VectorNormalize(forward);
|
|
+
|
|
+ // Set the base position
|
|
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
+ self->s.pos.trTime = level.time;
|
|
+ self->s.pos.trType = TR_LINEAR;
|
|
+
|
|
+ // Set the first choice to an 'empty' one.
|
|
+ VectorCopy(forward, chosen_dir);
|
|
+ chosen_length = -1;
|
|
+
|
|
+ // Cycle through the clients for a qualified target
|
|
+ for (i = 0; i < level.maxclients; i++) {
|
|
+ // Here we use target_ent to point to potential targets
|
|
+ target_ent = &g_entities[i];
|
|
+ target_owner = GetMissileOwnerEntity( self );
|
|
+
|
|
+ if (!target_ent->inuse) continue;
|
|
+ if (target_ent == target_owner) continue;
|
|
+ if ( OnSameTeam( target_ent, target_owner ) ) continue;
|
|
+
|
|
+ // Aim for the body, not the feet
|
|
+ midbody[0] = target_ent->r.currentOrigin[0] +
|
|
+ (target_ent->r.mins[0] + target_ent->r.maxs[0]) * 0.5;
|
|
+ midbody[1] = target_ent->r.currentOrigin[1] +
|
|
+ (target_ent->r.mins[1] + target_ent->r.maxs[1]) * 0.5;
|
|
+ midbody[2] = target_ent->r.currentOrigin[2] +
|
|
+ (target_ent->r.mins[2] + target_ent->r.maxs[2]) * 0.5;
|
|
+
|
|
+ // Get the distance on the horizontal plane
|
|
+ VectorSubtract(midbody, self->r.currentOrigin, target_dir);
|
|
+ target_dir[2] = 0; // <-- eliminate vertical difference here!
|
|
+ target_length = VectorLength(target_dir);
|
|
+
|
|
+ // We don't home in on this entity if its not within range on the
|
|
+ // horizontal plane.
|
|
+ if ( target_length > self->homRange ) continue;
|
|
+
|
|
+ // We don't home in on this entity if it is 'higher up' than the missile is.
|
|
+ if (midbody[2] > self->r.currentOrigin[2]) continue;
|
|
+
|
|
+ // Now set the true direction, including the vertical offset!
|
|
+ VectorSubtract(midbody, self->r.currentOrigin, target_dir);
|
|
+ target_length = VectorLength(target_dir);
|
|
+
|
|
+ // Quick normalization of target_dir since
|
|
+ // we already have the length
|
|
+ target_dir[0] /= target_length;
|
|
+ target_dir[1] /= target_length;
|
|
+ target_dir[2] /= target_length;
|
|
+
|
|
+
|
|
+ trap_Trace( &tr, self->r.currentOrigin, NULL, NULL,
|
|
+ self->r.currentOrigin, ENTITYNUM_NONE, MASK_SHOT );
|
|
+ if ( target_ent != &g_entities[tr.entityNum] ) continue;
|
|
+
|
|
+ // Only pick this target if it's the closest to the missile,
|
|
+ // but keep in account that there might not have been a pick
|
|
+ // yet!
|
|
+ if ( (target_length > chosen_length) && (chosen_length >= 0) ) continue;
|
|
+
|
|
+ VectorCopy(target_dir, chosen_dir);
|
|
+ chosen_length = target_length;
|
|
+ }
|
|
+
|
|
+ // Set the new direction.
|
|
+ // FIXME: Does this remain the same if chosen_dir == forward? (aka no target found)
|
|
+ VectorMA(forward, 0.05, chosen_dir, chosen_dir);
|
|
+ VectorNormalize(chosen_dir);
|
|
+ VectorScale(chosen_dir, self->speed, self->s.pos.trDelta);
|
|
+
|
|
+ // If the weapon has existed too long, make the next think detonate it.
|
|
+ if ( ( self->missileSpawnTime + self->maxMissileTime ) <= level.time ) {
|
|
+ self->think = G_ExplodeUserWeapon;
|
|
+ }
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+
|
|
+/*
|
|
+=====================
|
|
+Think_NormalMissile
|
|
+=====================
|
|
+*/
|
|
+void Think_NormalMissile (gentity_t *self) {
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity(self);
|
|
+
|
|
+ if(level.time > self->startTimer && self->radius){
|
|
+ int radius = self->radius;
|
|
+ self->radius = 0;
|
|
+ VectorSet( self->r.mins, -radius, -radius, -radius );
|
|
+ VectorSet( self->r.maxs, radius, radius, radius );
|
|
+ VectorCopy( self->r.mins, self->r.absmin );
|
|
+ VectorCopy( self->r.maxs, self->r.absmax );
|
|
+ }
|
|
+
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent; // Use this free field to transfer current power level
|
|
+
|
|
+ if ((self->missileSpawnTime + self->maxMissileTime) <= level.time){
|
|
+ self->think = G_ExplodeUserWeapon;
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if(self->isDrainable && self->s.eType != ET_BEAMHEAD){
|
|
+ self->powerLevelCurrent -= (float)self->powerLevelTotal * 0.01;
|
|
+ }
|
|
+ if (self->powerLevelCurrent <= 0) {
|
|
+ G_RemoveUserWeapon(self);
|
|
+ return;
|
|
+ }
|
|
+ if((missileOwner->client->ps.bitFlags & usingBoost) && self->s.eType == ET_BEAMHEAD){
|
|
+ self->powerLevelCurrent += 10 + ((float)self->powerLevelTotal * 0.005);
|
|
+ self->speed += 10 + ((float)missileOwner->client->ps.powerLevel[plMaximum] * 0.0003);
|
|
+ }
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+
|
|
+/*
|
|
+=====================
|
|
+Think_NormalMissileStruggle
|
|
+=====================
|
|
+*/
|
|
+void Think_NormalMissileStruggle (gentity_t *self) {
|
|
+ int powerDifference;
|
|
+ int speedDifference;
|
|
+ int power1,power2;
|
|
+ int speed1,speed2;
|
|
+ int result;
|
|
+ vec3_t forward,dir,dir2,start;
|
|
+ trace_t *trace;
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent;
|
|
+ if(self->powerLevelCurrent < 1){
|
|
+ G_RemoveUserWeapon(self);
|
|
+ return;
|
|
+ }
|
|
+ self->enemy->client->ps.bitFlags |= isStruggling;
|
|
+ missileOwner->client->ps.bitFlags |= isStruggling;
|
|
+ if((missileOwner->client->ps.bitFlags & usingBoost) && self->s.eType == ET_BEAMHEAD && missileOwner->client->ps.powerLevel[plCurrent] > 1){
|
|
+ self->powerLevelCurrent += 5 + ((float)self->powerLevelTotal * 0.005);
|
|
+ self->speed += 5 + ((float)missileOwner->client->ps.powerLevel[plMaximum] * 0.0003);
|
|
+ }
|
|
+ if(self->strugglingAllyAttack){
|
|
+ if(self->s.eType == ET_BEAMHEAD){
|
|
+ self->ally->speed += 5 + ((float)self->speed * 0.0003);
|
|
+ self->speed -= 5 + ((float)self->speed * 0.0003);
|
|
+ }else{
|
|
+ self->speed -= 5;
|
|
+ self->ally->speed += 5;
|
|
+ }
|
|
+ }
|
|
+ speed1 = self->speed;
|
|
+ speed2 = self->enemy->speed;
|
|
+ power1 = self->powerLevelCurrent;
|
|
+ power2 = self->enemy->powerLevelCurrent;
|
|
+ speedDifference = speed1-speed2;
|
|
+ powerDifference = power1-power2;
|
|
+ result = /*speedDifference +*/ powerDifference;
|
|
+ VectorCopy(self->r.currentOrigin,start);
|
|
+ VectorCopy(self->s.pos.trDelta,dir);
|
|
+ VectorCopy(self->r.currentAngles,dir2);
|
|
+ VectorNormalize(dir);
|
|
+ VectorScale(dir,0.2,dir);
|
|
+ VectorAdd(dir,dir2,dir);
|
|
+ VectorNormalize(dir);
|
|
+ VectorCopy(start,self->s.pos.trBase);
|
|
+ VectorScale(dir,result,self->s.pos.trDelta);
|
|
+ VectorCopy(start,self->r.currentOrigin);
|
|
+ VectorCopy(dir,self->r.currentAngles);
|
|
+ self->s.pos.trType = TR_LINEAR;
|
|
+ self->s.pos.trTime = level.time;
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+
|
|
+/*
|
|
+====================
|
|
+G_UserWeaponDamage
|
|
+====================
|
|
+Does damage to a client or object
|
|
+target = entity to be damaged
|
|
+inflictor = entity doing the damage
|
|
+attacker = entity the inflictor belongs to
|
|
+dir = direction for knockback
|
|
+point = origin of attack
|
|
+*/
|
|
+void G_UserWeaponDamage(gentity_t *target,gentity_t *inflictor,gentity_t *attacker,vec3_t dir,vec3_t point,int damage,int dflags,int knockback){
|
|
+ gclient_t *tgClient;
|
|
+ if(!target->takedamage){return;}
|
|
+ if(level.intermissionQueued){return;}
|
|
+ if(!inflictor){inflictor = &g_entities[ENTITYNUM_WORLD];}
|
|
+ if(!attacker){attacker = &g_entities[ENTITYNUM_WORLD];}
|
|
+ tgClient = target->client;
|
|
+ if(tgClient && tgClient->noclip){return;}
|
|
+ else{VectorNormalize(dir);}
|
|
+ if(target == attacker){knockback = 0;}
|
|
+ if(target->flags & FL_NO_KNOCKBACK) {knockback = 0;}
|
|
+ if(tgClient){
|
|
+ //VectorCopy(dir ? dir : target->r.currentOrigin,tgClient->damage_from);
|
|
+ tgClient->ps.persistant[PERS_ATTACKER] = attacker ? attacker->s.number : ENTITYNUM_WORLD;
|
|
+ tgClient->damage_fromWorld = dir ? qfalse : qtrue;
|
|
+ tgClient->lasthurt_client = attacker->s.number;
|
|
+ tgClient->lasthurt_mod = 1;
|
|
+ }
|
|
+ if(damage){
|
|
+ if(tgClient){
|
|
+ attacker->client->ps.states |= causedDamage;
|
|
+ G_LocationImpact(inflictor->r.currentOrigin,target,inflictor);
|
|
+ if(target == attacker){damage *= 0.2f;}
|
|
+ attacker->client->ps.powerLevel[plHealthPool] += damage * 0.7;
|
|
+ attacker->client->ps.powerLevel[plMaximumPool] += damage * 0.3;
|
|
+ tgClient->ps.powerLevel[plDamageFromEnergy] += damage;
|
|
+ if(inflictor->impede){tgClient->ps.timers[tmImpede] = inflictor->impede;}
|
|
+ if(knockback){
|
|
+ tgClient->ps.timers[tmKnockback] = knockback;
|
|
+ tgClient->ps.powerups[PW_KNOCKBACK_SPEED] = knockback;
|
|
+ if(tgClient->lasthurt_location == LOCATION_BACK){tgClient->ps.knockBackDirection = 6;}
|
|
+ else if(tgClient->lasthurt_location == LOCATION_LEFT){tgClient->ps.knockBackDirection = 4;}
|
|
+ else if(tgClient->lasthurt_location == LOCATION_RIGHT){tgClient->ps.knockBackDirection = 3;}
|
|
+ else{tgClient->ps.knockBackDirection = 5;}
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+=====================
|
|
+G_UserRadiusDamage
|
|
+=====================
|
|
+*/
|
|
+qboolean G_UserRadiusDamage ( vec3_t origin, gentity_t *attacker, gentity_t *ignore, float centerDamage, float radius,int extraKnockback ) {
|
|
+ int entityList[MAX_GENTITIES];
|
|
+ int numListedEntities;
|
|
+
|
|
+ float realDamage, distance;
|
|
+ gentity_t *ent;
|
|
+ gentity_t *owner = GetMissileOwnerEntity(ignore);
|
|
+
|
|
+ vec3_t mins, maxs;
|
|
+ vec3_t deltaRadius;
|
|
+ vec3_t dir;
|
|
+ int i, e;
|
|
+ qboolean hitClient = qfalse;
|
|
+
|
|
+ if ( radius < 1 ) {
|
|
+ radius = 1;
|
|
+ }
|
|
+
|
|
+ for ( i = 0 ; i < 3 ; i++ ) {
|
|
+ mins[i] = origin[i] - radius;
|
|
+ maxs[i] = origin[i] + radius;
|
|
+ }
|
|
+
|
|
+ numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
+
|
|
+ for ( e = 0 ; e < numListedEntities ; e++ ) {
|
|
+ ent = &g_entities[entityList[ e ]];
|
|
+
|
|
+ if (ent == ignore)
|
|
+ continue;
|
|
+ if (!ent->takedamage)
|
|
+ continue;
|
|
+ if (ent == owner && ignore->isBlindable)
|
|
+ continue;
|
|
+
|
|
+ // Find the distance between the perimeter of the entities' bounding box
|
|
+ // and the explosion's origin.
|
|
+ for ( i = 0 ; i < 3 ; i++ ) {
|
|
+ if ( origin[i] < ent->r.absmin[i] ) {
|
|
+ deltaRadius[i] = ent->r.absmin[i] - origin[i];
|
|
+ } else if ( origin[i] > ent->r.absmax[i] ) {
|
|
+ deltaRadius[i] = origin[i] - ent->r.absmax[i];
|
|
+ } else {
|
|
+ deltaRadius[i] = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ distance = VectorLength( deltaRadius );
|
|
+ if ( distance >= radius ) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ realDamage = centerDamage /** ( 1.0 - distance / radius )*/;
|
|
+
|
|
+ if(CanDamage(ent, origin)){
|
|
+ VectorSubtract (ent->r.currentOrigin, origin, dir);
|
|
+ // push the center of mass higher than the origin so players
|
|
+ // get knocked into the air more
|
|
+ dir[2] += 24;
|
|
+ G_LocationImpact(origin,ent,attacker);
|
|
+ if(ent->client){
|
|
+ ent->client->ps.powerLevel[plDamageGeneric] += realDamage;
|
|
+ if(ent->pain){ent->pain(ent,attacker,realDamage);}
|
|
+ /*if(ent->client->lasthurt_location == LOCATION_FRONT){
|
|
+ ent->client->ps.timers[tmBlind] = 10000;
|
|
+ }*/
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return hitClient;
|
|
+}
|
|
+void UserHitscan_Fire (gentity_t *self, g_userWeapon_t *weaponInfo, int weaponNum, vec3_t muzzle, vec3_t forward ) {
|
|
+ trace_t tr;
|
|
+ vec3_t end;
|
|
+ gentity_t *tempEnt;
|
|
+ gentity_t *tempEnt2;
|
|
+ gentity_t *traceEnt;
|
|
+ int i, passent;
|
|
+ float rnd;
|
|
+ float physics_range;
|
|
+ // Get the end point
|
|
+ if ( weaponInfo->physics_range_min != weaponInfo->physics_range_max ) {
|
|
+ rnd = crandom();
|
|
+ physics_range = ( 1.0f - rnd ) * weaponInfo->physics_range_min + rnd * weaponInfo->physics_range_max;
|
|
+ } else {
|
|
+ physics_range = weaponInfo->physics_range_max;
|
|
+ }
|
|
+ VectorMA (muzzle, physics_range, forward, end);
|
|
+
|
|
+ // Set the player to not be able of being hurt by this shot
|
|
+ passent = self->s.number;
|
|
+
|
|
+ // Note; if bounce is enabled, this will let the projectile bounce a max. of 10 times
|
|
+ for (i = 0; i < 10; i++) {
|
|
+
|
|
+ trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);
|
|
+ traceEnt = &g_entities[ tr.entityNum ];
|
|
+ // snap the endpos to integers, but nudged towards the line
|
|
+ SnapVectorTowards( tr.endpos, muzzle );
|
|
+
|
|
+ // Add an explosion event entity, and make it free itself after the event.
|
|
+ tempEnt = G_Spawn();
|
|
+ tempEnt->s.eType = ET_GENERAL;
|
|
+ tempEnt->s.clientNum = self->s.number;
|
|
+ tempEnt->s.weapon = weaponNum;
|
|
+ SnapVectorTowards( tr.endpos, tempEnt->s.pos.trBase ); // save net bandwidth
|
|
+ G_SetOrigin( tempEnt, tr.endpos );
|
|
+
|
|
+ if ( traceEnt->takedamage && traceEnt->client ) {
|
|
+ G_AddEvent( tempEnt, EV_MISSILE_HIT, DirToByte( tr.plane.normal ) );
|
|
+ tempEnt->s.otherEntityNum = traceEnt->s.number;
|
|
+
|
|
+ // Log an accuracy hit for this weapon
|
|
+
|
|
+ } else if( tr.fraction == 1.0f || tr.surfaceFlags & SURF_NOIMPACT ) {
|
|
+ G_AddEvent( tempEnt, EV_MISSILE_MISS_AIR, DirToByte( tr.plane.normal ) );
|
|
+ } else if( tr.surfaceFlags & SURF_METALSTEPS ) {
|
|
+ G_AddEvent( tempEnt, EV_MISSILE_MISS_METAL, DirToByte( tr.plane.normal ) );
|
|
+ } else {
|
|
+ G_AddEvent( tempEnt, EV_MISSILE_MISS, DirToByte( tr.plane.normal ) );
|
|
+ }
|
|
+ tempEnt->freeAfterEvent = qtrue;
|
|
+ trap_LinkEntity( tempEnt );
|
|
+
|
|
+ // the final trace endpos will be the terminal point of the rail trail
|
|
+
|
|
+
|
|
+ // Send railgun beam effect. The client will draw it only if set through the
|
|
+ // weapon's scripted graphics clientside, but we _always_ have to send the event.
|
|
+ tempEnt2 = G_TempEntity( tr.endpos, EV_NULL );
|
|
+ tempEnt2->s.clientNum = self->s.number;
|
|
+ tempEnt2->s.weapon = weaponNum;
|
|
+ VectorCopy( muzzle, tempEnt2->s.origin2 );
|
|
+
|
|
+ if ( tr.surfaceFlags & SURF_NOIMPACT ) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if ( traceEnt->takedamage) {
|
|
+ G_UserWeaponDamage( traceEnt, self, self, forward, tr.endpos,
|
|
+ weaponInfo->damage_damage, 0,
|
|
+ weaponInfo->damage_extraKnockback );
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+void Release_UserWeapon( gentity_t *self, qboolean altfire ) {
|
|
+ // Work on primary or alternate fire.
|
|
+ if ( altfire ) {
|
|
+
|
|
+ // If the altfired entity exists, then destroy it and null the pointer (just incase).
|
|
+ if ( self->contAltfire_ent ) {
|
|
+ G_FreeEntity( self->contAltfire_ent );
|
|
+ self->contAltfire_ent = NULL;
|
|
+ }
|
|
+ } else {
|
|
+ // If the fired entity exists, then destroy it and null the pointer (just incase).
|
|
+ if ( self->contFire_ent ) {
|
|
+ G_FreeEntity( self->contAltfire_ent );
|
|
+ self->contAltfire_ent = NULL;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+=================
|
|
+Fire_UserWeapon
|
|
+=================
|
|
+*/
|
|
+void Fire_UserWeapon( gentity_t *self, vec3_t start, vec3_t dir, qboolean altfire ) {
|
|
+ gentity_t *bolt;
|
|
+ g_userWeapon_t *weaponInfo;
|
|
+ float powerScale;
|
|
+ int homingType;
|
|
+ vec3_t muzzle, forward, right, up;
|
|
+ vec3_t firingDir, firingStart; // <-- Contain the altered aiming and origin vectors.
|
|
+ powerScale = ((float)self->client->ps.powerLevel[plCurrent] / 1000.0) * self->client->ps.baseStats[stEnergyAttack];
|
|
+ // Get a hold of the weapon we're firing.
|
|
+ // If altfire is used and it exists, use altfire, else use regular fire
|
|
+ // Due to the nature of altfire presence detection, the regular fire must
|
|
+ // always be loaded first. Since this is only one pointer dereference it is
|
|
+ // negligable.
|
|
+ weaponInfo = G_FindUserWeaponData( self->s.clientNum, self->s.weapon );
|
|
+ if ( ( weaponInfo->general_bitflags & WPF_ALTWEAPONPRESENT ) && altfire ) {
|
|
+ weaponInfo = G_FindUserAltWeaponData( self->s.clientNum, self->s.weapon );
|
|
+ }
|
|
+
|
|
+ // Retrieve the muzzle that was set in g_weapons.c
|
|
+ G_GetMuzzleSettings(muzzle, forward, right, up);
|
|
+
|
|
+
|
|
+ // Determine the corrected firing angle and firing origin
|
|
+
|
|
+ VectorCopy( dir, firingDir);
|
|
+ if ( weaponInfo->firing_angleW_min || weaponInfo->firing_angleW_max ) {
|
|
+ vec3_t temp;
|
|
+ float rnd;
|
|
+ float firing_angleW;
|
|
+
|
|
+ rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
+ firing_angleW = ( 1.0f - rnd ) * weaponInfo->firing_angleW_min + rnd * weaponInfo->firing_angleW_max;
|
|
+
|
|
+ if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetWFlip ) {
|
|
+ RotatePointAroundVector( temp, up, firingDir, firing_angleW );
|
|
+ } else {
|
|
+ RotatePointAroundVector( temp, up, firingDir, -firing_angleW );
|
|
+ }
|
|
+ VectorCopy( temp, firingDir );
|
|
+ }
|
|
+ if ( weaponInfo->firing_angleH_min || weaponInfo->firing_angleH_max ) {
|
|
+ vec3_t temp;
|
|
+ float rnd;
|
|
+ float firing_angleH;
|
|
+
|
|
+ rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
+ firing_angleH = ( 1.0f - rnd ) * weaponInfo->firing_angleH_min + rnd * weaponInfo->firing_angleH_max;
|
|
+
|
|
+ if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetHFlip ) {
|
|
+ RotatePointAroundVector( temp, right, firingDir, firing_angleH );
|
|
+ } else {
|
|
+ RotatePointAroundVector( temp, right, firingDir, -firing_angleH );
|
|
+ }
|
|
+ VectorCopy( temp, firingDir );
|
|
+ }
|
|
+ VectorNormalize( firingDir );
|
|
+
|
|
+ VectorCopy( start, firingStart );
|
|
+ if ( weaponInfo->firing_offsetW_min || weaponInfo->firing_offsetW_max ) {
|
|
+ float rnd;
|
|
+ float firing_offsetW;
|
|
+
|
|
+ rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
+ firing_offsetW = ( 1.0f - rnd ) * weaponInfo->firing_offsetW_min + rnd * weaponInfo->firing_offsetW_max;
|
|
+
|
|
+ if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetWFlip ) {
|
|
+ VectorMA( firingStart, -firing_offsetW, right, firingStart );
|
|
+ } else {
|
|
+ VectorMA( firingStart, firing_offsetW, right, firingStart );
|
|
+ }
|
|
+ }
|
|
+ if ( weaponInfo->firing_offsetH_min || weaponInfo->firing_offsetH_max ) {
|
|
+ float rnd;
|
|
+ float firing_offsetH;
|
|
+
|
|
+ rnd = crandom(); // <-- between 0.0f and 1.0f
|
|
+ firing_offsetH = ( 1.0f - rnd ) * weaponInfo->firing_offsetH_min + rnd * weaponInfo->firing_offsetH_max;
|
|
+
|
|
+ if ( ( self->client->ps.bitFlags & hasFlipOffset ) && weaponInfo->firing_offsetHFlip ) {
|
|
+ VectorMA( firingStart, -firing_offsetH, up, firingStart );
|
|
+ } else {
|
|
+ VectorMA( firingStart, firing_offsetH, up, firingStart );
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ // Take the necessary steps to configure and fire the weapon.
|
|
+ switch ( weaponInfo->general_type ) {
|
|
+
|
|
+ /* MISSILES */
|
|
+
|
|
+ case skillTypeMISSILE:
|
|
+ bolt = G_Spawn();
|
|
+ bolt->classname = "rocket";
|
|
+ bolt->s.eType = ET_MISSILE;
|
|
+ bolt->startTimer = level.time + 800;
|
|
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
+
|
|
+ // Set the weapon number correct, depending on altfire status.
|
|
+ if ( !altfire ) {
|
|
+ bolt->s.weapon = self->s.weapon;
|
|
+ } else {
|
|
+ bolt->s.weapon = self->s.weapon + ALTWEAPON_OFFSET;
|
|
+ }
|
|
+ bolt->r.ownerNum = self->s.number;
|
|
+ bolt->s.clientNum = self->s.number; // <-- clientNum is free on all but ET_PLAYER so
|
|
+ bolt->parent = self; // use it to store the owner for clientside.
|
|
+
|
|
+ bolt->damage = weaponInfo->damage_damage;
|
|
+ bolt->splashRadius = weaponInfo->damage_radius;
|
|
+ bolt->splashDuration = weaponInfo->damage_radiusDuration;
|
|
+ bolt->extraKnockback = weaponInfo->damage_extraKnockback;
|
|
+ bolt->impede = weaponInfo->damage_impede;
|
|
+ bolt->isSwattable = weaponInfo->physics_swat;
|
|
+ bolt->isDrainable = weaponInfo->physics_drain;
|
|
+ bolt->isBlindable = weaponInfo->physics_blind;
|
|
+ if (altfire) {
|
|
+ bolt->chargelvl = self->client->ps.stats[stChargePercentSecondary];
|
|
+ self->client->ps.stats[stChargePercentSecondary] = 0; // Only reset it here!
|
|
+ } else {
|
|
+ bolt->chargelvl = self->client->ps.stats[stChargePercentPrimary];
|
|
+ self->client->ps.stats[stChargePercentPrimary] = 0; // Only reset it here!
|
|
+ }
|
|
+ bolt->s.powerups = bolt->chargelvl; // Use this free field to transfer chargelvl
|
|
+ bolt->powerLevelCurrent = bolt->powerLevelTotal = bolt->damage * ((float)bolt->chargelvl / 100.0f) * powerScale;
|
|
+ bolt->s.dashDir[0] = bolt->powerLevelTotal; // Use this free field to transfer total power level
|
|
+
|
|
+ bolt->clipmask = MASK_SHOT;
|
|
+ bolt->target_ent = NULL;
|
|
+ bolt->takedamage = qtrue;
|
|
+ self->s.dashDir[2] = 0.0f;
|
|
+ bolt->count = 0;
|
|
+ {
|
|
+ float radius;
|
|
+ radius = weaponInfo->physics_radius + weaponInfo->physics_radiusMultiplier * (bolt->chargelvl / 100.0f);
|
|
+ radius = sqrt((radius * radius) / 3); // inverse of Pythagoras
|
|
+ bolt->radius = radius;
|
|
+ radius = 1;
|
|
+ VectorSet( bolt->r.mins, -radius, -radius, -radius );
|
|
+ VectorSet( bolt->r.maxs, radius, radius, radius );
|
|
+ VectorCopy( bolt->r.mins, bolt->r.absmin );
|
|
+ VectorCopy( bolt->r.maxs, bolt->r.absmax );
|
|
+
|
|
+ bolt->r.contents = CONTENTS_CORPSE; // So we can pass through a missile, but can still fire at it.
|
|
+
|
|
+ bolt->die = G_DieUserWeapon;
|
|
+ }
|
|
+
|
|
+ bolt->speed = weaponInfo->physics_speed;
|
|
+ bolt->accel = weaponInfo->physics_acceleration;
|
|
+ bolt->bounceFrac = weaponInfo->physics_bounceFrac;
|
|
+ bolt->bouncesLeft = weaponInfo->physics_maxBounces;
|
|
+ bolt->timesTriggered = 0;
|
|
+
|
|
+ // Configure the type correctly if we're using gravity or acceleration
|
|
+ // Gravity affection takes precedence over acceleration.
|
|
+ // FIXME: Can we get both simultaneously?
|
|
+ if (weaponInfo->physics_gravity > 0) {
|
|
+ bolt->gravity = (weaponInfo->physics_gravity / 100) * 1000;
|
|
+ bolt->s.pos.trType = TR_MAPGRAVITY;
|
|
+ bolt->s.pos.trDuration = bolt->gravity;
|
|
+ } else {
|
|
+ if (bolt->accel != 0.0f) {
|
|
+ bolt->s.pos.trType = TR_ACCEL;
|
|
+ bolt->s.pos.trDuration = bolt->accel;
|
|
+ } else {
|
|
+ bolt->s.pos.trType = TR_LINEAR;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // move a bit on the very first frame
|
|
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
|
|
+ VectorCopy( firingStart, bolt->s.pos.trBase );
|
|
+ VectorCopy( firingStart, bolt->r.currentOrigin );
|
|
+
|
|
+ VectorCopy( self->client->ps.viewangles, self->s.angles );
|
|
+ VectorCopy( self->client->ps.viewangles, bolt->s.angles2 );
|
|
+
|
|
+ VectorScale( firingDir, bolt->speed, bolt->s.pos.trDelta );
|
|
+ VectorCopy(firingDir,bolt->movedir);
|
|
+ // This saves network bandwidth.
|
|
+ SnapVector( bolt->s.pos.trDelta );
|
|
+
|
|
+ // Set the correct think based on homing properties.
|
|
+ switch (weaponInfo->homing_type) {
|
|
+ case HOM_PROX:
|
|
+ bolt->think = Think_ProxDet;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ bolt->homRange = weaponInfo->homing_range;
|
|
+ break;
|
|
+ case HOM_GUIDED:
|
|
+ bolt->think = Think_Guided;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ // Kill off previously set accel, bounce and gravity. Guided missiles
|
|
+ // do not accelerate, bounce or experience gravity.
|
|
+ bolt->accel = 0;
|
|
+ bolt->bounceFrac = 0;
|
|
+ bolt->bouncesLeft = 0;
|
|
+ bolt->gravity = 0;
|
|
+ // Set guidetarget.
|
|
+ bolt->s.eFlags |= EF_GUIDED;
|
|
+ self->client->guidetarget = bolt;
|
|
+ bolt->guided = qtrue;
|
|
+ break;
|
|
+ case HOM_REGULAR:
|
|
+ bolt->think = Think_Homing;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ bolt->homRange = weaponInfo->homing_range;
|
|
+ //bolt->homAccel = weaponInfo->homing_accel;
|
|
+ break;
|
|
+ case HOM_CYLINDER:
|
|
+ bolt->think = Think_CylinderHoming;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ bolt->homRange = weaponInfo->homing_range;
|
|
+ //bolt->homAccel = weaponInfo->homing_accel;
|
|
+ break;
|
|
+ case HOM_ARCH:
|
|
+ bolt->think = Think_NormalMissile;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ bolt->bounceFrac = 0;
|
|
+ bolt->bouncesLeft = 0;
|
|
+ bolt->accel = 0;
|
|
+ bolt->gravity = 0;
|
|
+ {
|
|
+ trace_t trace;
|
|
+ vec3_t begin, mid, end;
|
|
+ float length;
|
|
+
|
|
+ VectorCopy( bolt->s.pos.trBase, begin );
|
|
+ VectorMA( begin, weaponInfo->homing_range, dir, end );
|
|
+ trap_Trace( &trace, begin, NULL, NULL, end, self->s.number, MASK_SHOT );
|
|
+ VectorCopy( trace.endpos, end );
|
|
+
|
|
+ length = Distance( begin, end );
|
|
+
|
|
+ VectorMA( begin, length / 2.0f, firingDir, mid );
|
|
+
|
|
+ bolt->s.pos.trType = TR_ARCH;
|
|
+ bolt->s.pos.trDuration = 1000.0f * length / weaponInfo->physics_speed;
|
|
+ VectorCopy( begin, bolt->s.pos.trBase );
|
|
+ VectorCopy( mid, bolt->s.angles2 );
|
|
+ VectorCopy( end, bolt->s.pos.trDelta );
|
|
+ }
|
|
+ break;
|
|
+ case HOM_DRUNKEN:
|
|
+ bolt->think = Think_NormalMissile;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ bolt->s.pos.trType = TR_DRUNKEN;
|
|
+ bolt->s.pos.trDuration = weaponInfo->homing_range;
|
|
+ break;
|
|
+ case HOM_NONE:
|
|
+ bolt->think = Think_NormalMissile;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ break;
|
|
+ default:
|
|
+ // This should never happen!
|
|
+ break;
|
|
+ }
|
|
+ bolt->missileSpawnTime = level.time;
|
|
+ bolt->maxMissileTime = weaponInfo->physics_lifetime;
|
|
+ break;
|
|
+ /* BEAMS */
|
|
+ case skillTypeBEAM:
|
|
+ homingType = weaponInfo->homing_type;
|
|
+ bolt = G_Spawn();
|
|
+ bolt->classname = "user_beam";
|
|
+ bolt->s.eType = ET_BEAMHEAD;
|
|
+ bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
+ bolt->startTimer = level.time + 800;
|
|
+ // Set the weapon number correct, depending on altfire status.
|
|
+ if ( !altfire ) {
|
|
+ bolt->s.weapon = self->s.weapon;
|
|
+ }
|
|
+ else{
|
|
+ bolt->s.weapon = self->s.weapon + ALTWEAPON_OFFSET;
|
|
+ }
|
|
+ bolt->r.ownerNum = self->s.number;
|
|
+ bolt->s.clientNum = self->s.number; // <-- clientNum is free on all but ET_PLAYER so
|
|
+ bolt->parent = self; // use it to store the owner for clientside.
|
|
+ VectorCopy( self->client->ps.viewangles, bolt->s.angles ); // store the correct angles
|
|
+ bolt->damage = weaponInfo->damage_damage;
|
|
+ bolt->splashRadius = weaponInfo->damage_radius;
|
|
+ bolt->splashDuration = weaponInfo->damage_radiusDuration;
|
|
+ bolt->extraKnockback = weaponInfo->damage_extraKnockback;
|
|
+ bolt->isSwattable = weaponInfo->physics_swat;
|
|
+ bolt->isDrainable = weaponInfo->physics_drain;
|
|
+ bolt->isBlindable = weaponInfo->physics_blind;
|
|
+ if (altfire) {
|
|
+ bolt->chargelvl = self->client->ps.stats[stChargePercentSecondary];
|
|
+ self->client->ps.stats[stChargePercentSecondary] = 0;
|
|
+ }
|
|
+ else{
|
|
+ bolt->chargelvl = self->client->ps.stats[stChargePercentPrimary];
|
|
+ self->client->ps.stats[stChargePercentPrimary] = 0;
|
|
+ }
|
|
+ bolt->s.powerups = bolt->chargelvl;
|
|
+ bolt->powerLevelCurrent = bolt->powerLevelTotal = bolt->damage * (1.0f+(float)bolt->chargelvl / 100.0f) * powerScale;
|
|
+ bolt->s.dashDir[0] = bolt->powerLevelTotal; // Use this free field to transfer total power level
|
|
+ // FIXME: Hack into the old mod style, since it's still needed for now
|
|
+ bolt->takedamage = qtrue;
|
|
+ bolt->clipmask = MASK_SHOT;
|
|
+ bolt->target_ent = NULL;
|
|
+ self->s.dashDir[2] = 0.0f;
|
|
+ bolt->count = 0;
|
|
+ {
|
|
+ float radius;
|
|
+ radius = weaponInfo->physics_radius + weaponInfo->physics_radiusMultiplier * (bolt->chargelvl / 100.0f);
|
|
+ radius = sqrt((radius * radius) / 3); // inverse of Pythagoras
|
|
+ bolt->radius = radius;
|
|
+ radius = 1;
|
|
+ VectorSet( bolt->r.mins, -radius, -radius, -radius );
|
|
+ VectorSet( bolt->r.maxs, radius, radius, radius );
|
|
+ VectorCopy( bolt->r.mins, bolt->r.absmin );
|
|
+ VectorCopy( bolt->r.maxs, bolt->r.absmax );
|
|
+ bolt->r.contents = CONTENTS_CORPSE; // So we can pass through a missile, but can still fire at it.
|
|
+ bolt->die = G_DieUserWeapon;
|
|
+ }
|
|
+ bolt->speed = weaponInfo->physics_speed;
|
|
+ bolt->accel = 0;
|
|
+ bolt->bounceFrac = 0;
|
|
+ bolt->bouncesLeft = 0;
|
|
+ bolt->gravity = 0;
|
|
+ bolt->s.pos.trType = TR_LINEAR;
|
|
+ VectorScale( dir, bolt->speed, bolt->s.pos.trDelta );
|
|
+ SnapVector( bolt->s.pos.trDelta );
|
|
+ bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
|
|
+ VectorCopy( start, bolt->s.pos.trBase );
|
|
+ VectorCopy( start, bolt->r.currentOrigin );
|
|
+ VectorCopy( firingDir, bolt->movedir );
|
|
+ VectorCopy( self->client->ps.viewangles, self->s.angles );
|
|
+ VectorCopy( self->client->ps.viewangles, bolt->s.angles2 );
|
|
+ if(self->client->ps.lockedTarget > 0){homingType = HOM_GUIDED;}
|
|
+ switch (homingType) {
|
|
+ case HOM_GUIDED:
|
|
+ bolt->think = Think_Guided;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ bolt->s.eFlags |= EF_GUIDED;
|
|
+ self->client->guidetarget = bolt;
|
|
+ bolt->guided = qtrue;
|
|
+ break;
|
|
+
|
|
+ case HOM_ARCH:
|
|
+ case HOM_PROX:
|
|
+ case HOM_REGULAR:
|
|
+ case HOM_CYLINDER:
|
|
+ case HOM_NONE:
|
|
+ bolt->think = Think_NormalMissile;
|
|
+ bolt->nextthink = level.time + FRAMETIME;
|
|
+ self->client->guidetarget = bolt;
|
|
+ bolt->guided = qtrue;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ bolt->missileSpawnTime = level.time;
|
|
+ bolt->maxMissileTime = weaponInfo->physics_lifetime;
|
|
+ break;
|
|
+ case skillTypeHITSCAN:
|
|
+ if ( !altfire ) {
|
|
+ UserHitscan_Fire( self, weaponInfo, self->s.weapon, firingStart, firingDir );
|
|
+ self->client->ps.stats[stChargePercentPrimary] = 0;
|
|
+ } else {
|
|
+ UserHitscan_Fire( self, weaponInfo, self->s.weapon + ALTWEAPON_OFFSET, firingStart, firingDir );
|
|
+ self->client->ps.stats[stChargePercentSecondary] = 0;
|
|
+ }
|
|
+ break;
|
|
+ case skillTypeTRIGGER:
|
|
+ {
|
|
+ gentity_t *trigTarget;
|
|
+ trace_t trace;
|
|
+ vec3_t newBase, newDir, traceEnd;
|
|
+ int pass_ent, i;
|
|
+ float traceDist;
|
|
+
|
|
+ for ( i = 0; i < MAX_GENTITIES; i++ ) {
|
|
+
|
|
+ // Select the entity
|
|
+ trigTarget = &g_entities[i];
|
|
+
|
|
+ // See if it is affected by the trigger
|
|
+ if ( !trigTarget->inuse ) {
|
|
+ continue;
|
|
+ } else if ( trigTarget->s.eType != ET_MISSILE ) {
|
|
+ continue;
|
|
+ } else if ( !altfire && ( trigTarget->s.weapon != self->s.weapon + ALTWEAPON_OFFSET )) {
|
|
+ continue;
|
|
+ } else if ( altfire && ( trigTarget->s.weapon != self->s.weapon )) {
|
|
+ continue;
|
|
+ } else if (trigTarget->s.clientNum != self->s.number){
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // Get the target's current position
|
|
+ BG_EvaluateTrajectory( &trigTarget->s, &trigTarget->s.pos, level.time, newBase );
|
|
+
|
|
+ // Don't process any missiles beyond the maximum range of control
|
|
+ if ( Distance(newBase, start) > weaponInfo->physics_range_max ) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // Set the target's new starting position and increase its trigger count
|
|
+ VectorCopy( newBase, trigTarget->s.pos.trBase );
|
|
+ trigTarget->timesTriggered++;
|
|
+
|
|
+ // Setup the new directional vector for the triggered weapon.
|
|
+ VectorSubtract( start, newBase, newDir );
|
|
+ traceDist = VectorLength( newDir ) + 100000;
|
|
+
|
|
+ VectorScale( dir, traceDist, traceEnd );
|
|
+ VectorAdd( traceEnd, start, traceEnd );
|
|
+
|
|
+ pass_ent = self->s.number;
|
|
+ trap_Trace( &trace, start, NULL, NULL, traceEnd, pass_ent, MASK_PLAYERSOLID );
|
|
+ VectorSubtract( trace.endpos, start, traceEnd );
|
|
+
|
|
+ VectorAdd( traceEnd, newDir, newDir );
|
|
+ VectorNormalize( newDir );
|
|
+
|
|
+ // Set the triggered weapon's new direction and speed
|
|
+ VectorScale( newDir, weaponInfo->physics_speed, trigTarget->s.pos.trDelta );
|
|
+ vectoangles( newDir, trigTarget->s.angles );
|
|
+ trigTarget->s.pos.trTime = level.time;
|
|
+
|
|
+ // Configure the type correctly if we're using gravity or acceleration
|
|
+ // Gravity affection takes precedence over acceleration.
|
|
+ // FIXME: Can we get both simultaneously?
|
|
+ if (weaponInfo->physics_gravity > 0) {
|
|
+ trigTarget->accel = 0;
|
|
+ trigTarget->gravity = (weaponInfo->physics_gravity / 100) * 1000;
|
|
+ trigTarget->s.pos.trType = TR_MAPGRAVITY;
|
|
+ trigTarget->s.pos.trDuration = trigTarget->gravity;
|
|
+ } else if (weaponInfo->physics_acceleration != 0.0f) {
|
|
+ trigTarget->gravity = 0;
|
|
+ trigTarget->accel = weaponInfo->physics_acceleration;
|
|
+ trigTarget->s.pos.trType = TR_ACCEL;
|
|
+ trigTarget->s.pos.trDuration = trigTarget->accel;
|
|
+ } else {
|
|
+ trigTarget->accel = 0;
|
|
+ trigTarget->gravity = 0;
|
|
+ trigTarget->s.pos.trType = TR_LINEAR;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ case skillTypeNONE:
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+void G_DieUserWeapon(gentity_t *self, gentity_t *inflictor,gentity_t *attacker, int damage, int mod ) {
|
|
+ if (inflictor == self)
|
|
+ return;
|
|
+ self->takedamage = qfalse;
|
|
+ self->think = G_ExplodeUserWeapon;
|
|
+ self->nextthink = level.time;
|
|
+}
|
|
+void G_ExplodeUserWeapon( gentity_t *self ) {
|
|
+ // Handles actual detonation of the weapon in question.
|
|
+ vec3_t dir = { 0, 0, 1};
|
|
+ vec3_t origin;
|
|
+ self->takedamage = qfalse;
|
|
+
|
|
+ // Terminate guidance
|
|
+ if ( self->guided ) {
|
|
+ g_entities[ self->s.clientNum ].client->ps.weaponstate = WEAPON_READY;
|
|
+ }
|
|
+
|
|
+ // Calculate current position
|
|
+ BG_EvaluateTrajectory(&self->s,&self->s.pos,level.time,origin);
|
|
+ SnapVector( origin ); // save net bandwith
|
|
+ SnapVectorTowards( origin, self->s.pos.trBase ); // save net bandwidth
|
|
+ G_SetOrigin( self, origin );
|
|
+ self->s.eType = ET_EXPLOSION;
|
|
+ self->splashEnd = level.time + self->splashDuration;
|
|
+ self->splashTimer = level.time;
|
|
+ G_AddEvent( self, EV_MISSILE_MISS_AIR, DirToByte( dir ) );
|
|
+ trap_LinkEntity( self );
|
|
+}
|
|
+static void G_BounceUserMissile( gentity_t *self, trace_t *trace ) {
|
|
+ vec3_t velocity;
|
|
+ float dot;
|
|
+ int hitTime;
|
|
+
|
|
+ // reflect the velocity on the trace plane
|
|
+ hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
|
|
+ BG_EvaluateTrajectoryDelta( &self->s, &self->s.pos, hitTime, velocity );
|
|
+ dot = DotProduct( velocity, trace->plane.normal );
|
|
+ VectorMA( velocity, -2 * dot, trace->plane.normal, self->s.pos.trDelta );
|
|
+
|
|
+ VectorScale( self->s.pos.trDelta, self->bounceFrac, self->s.pos.trDelta );
|
|
+
|
|
+ // check for stop
|
|
+ if ( trace->plane.normal[2] > 0.2 && VectorLength( self->s.pos.trDelta ) < 40 ) {
|
|
+ G_ExplodeUserWeapon ( self );
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ VectorAdd( self->r.currentOrigin, trace->plane.normal, self->r.currentOrigin);
|
|
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
+ self->s.pos.trTime = level.time;
|
|
+}
|
|
+
|
|
+static void G_HoldUserMissile( gentity_t *self, gentity_t *other ) {
|
|
+ vec3_t velocity;
|
|
+ vec3_t dir;
|
|
+ float dot;
|
|
+ int hitTime;
|
|
+
|
|
+ if(other->s.eType == ET_PLAYER){
|
|
+ if(self->s.eType == ET_MISSILE){
|
|
+ VectorCopy(self->s.angles,dir);
|
|
+ }else{
|
|
+ VectorCopy(self->client->ps.viewangles,dir);
|
|
+ }
|
|
+ hitTime = level.previousTime + ( level.time - level.previousTime ) * 0;
|
|
+ BG_EvaluateTrajectoryDelta( &self->s, &self->s.pos, hitTime, velocity );
|
|
+ dot = DotProduct( velocity, dir );
|
|
+ VectorMA( velocity, -2 * dot, dir, self->s.pos.trDelta );
|
|
+
|
|
+ VectorScale( self->s.pos.trDelta, self->bounceFrac, self->s.pos.trDelta );
|
|
+
|
|
+ VectorAdd( self->r.currentOrigin, dir, self->r.currentOrigin);
|
|
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
+ }
|
|
+
|
|
+ self->s.pos.trTime = level.time;
|
|
+}
|
|
+
|
|
+static void G_PushUserMissile( gentity_t *self, gentity_t *other ) {
|
|
+ vec3_t forward, right, up;
|
|
+ vec3_t muzzle;
|
|
+ float dist;
|
|
+
|
|
+ // Calculate based on the other player view angles.
|
|
+ AngleVectors( other->client->ps.viewangles, forward, right, up );
|
|
+ CalcMuzzlePoint( other, forward, right, up, muzzle );
|
|
+
|
|
+ // Copy our starting position to the trajectory.
|
|
+ VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
+
|
|
+ // Setup the new directional vector for the fired weapon.
|
|
+ VectorSubtract( muzzle, self->r.currentOrigin, muzzle );
|
|
+ dist = VectorLength( muzzle ) + SOLID_ADD;
|
|
+
|
|
+ VectorScale( forward, dist, forward );
|
|
+ VectorAdd( forward, muzzle, muzzle );
|
|
+ VectorNormalize( muzzle );
|
|
+
|
|
+ // Set the weapon's direction and speed in the trajectory,
|
|
+ // and set its angles.
|
|
+ VectorScale( muzzle, self->speed, forward );
|
|
+ VectorCopy( forward, self->s.pos.trDelta );
|
|
+ VectorScale( self->s.pos.trDelta, self->bounceFrac, self->s.pos.trDelta );
|
|
+ vectoangles( muzzle, self->s.angles );
|
|
+
|
|
+ // This will save net bandwidth.
|
|
+ SnapVector( self->s.pos.trDelta );
|
|
+
|
|
+ self->s.pos.trType = TR_LINEAR;
|
|
+ self->s.pos.trTime = level.time;
|
|
+}
|
|
+
|
|
+static void Think_NormalMissileStrugglePlayer( gentity_t *self ) {
|
|
+ vec3_t fwd;
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
+ AngleVectors(self->enemy->r.currentAngles, fwd, NULL, NULL);
|
|
+ VectorNormalize(fwd);
|
|
+ G_HoldUserMissile(self,self->enemy);
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent;
|
|
+ self->bounceFrac = 0.0f;
|
|
+ self->enemy->client->ps.timers[tmStruggleBlock] += 100;
|
|
+ if(self->enemy->client->ps.timers[tmStruggleBlock] >= 3000){
|
|
+ self->enemy->client->ps.bitFlags &= ~isStruggling;
|
|
+ missileOwner->client->ps.bitFlags &= ~isStruggling;
|
|
+ G_ExplodeUserWeapon(self);
|
|
+ return;
|
|
+ //if(self->s.eType == ET_MISSILE){self->think = Think_NormalMissileBurnPlayer;}
|
|
+ //if(self->s.eType == ET_BEAMHEAD){self->think = Think_NormalMissileRidePlayer;}
|
|
+ }
|
|
+ self->enemy->client->ps.bitFlags |= isStruggling;
|
|
+ missileOwner->client->ps.bitFlags |= isStruggling;
|
|
+ self->powerLevelCurrent -= (self->enemy->client->ps.powerLevel[plFatigue] * 0.2) * 0.1;
|
|
+ if(self->enemy->client->ps.powerLevel[plFatigue] * 0.5 >= (self->powerLevelCurrent)){
|
|
+ self->enemy->client->ps.bitFlags &= ~isStruggling;
|
|
+ missileOwner->client->ps.bitFlags &= ~isStruggling;
|
|
+ self->s.eType = ET_MISSILE;
|
|
+ self->think = Think_NormalMissile;
|
|
+ self->bounceFrac = 0.75;
|
|
+ G_PushUserMissile(self,self->enemy);
|
|
+ }
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+static void Think_NormalMissileBurnPlayer( gentity_t *self ) {
|
|
+ vec3_t fwd;
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
+ AngleVectors(self->enemy->r.currentAngles, fwd, NULL, NULL);
|
|
+ VectorNormalize(fwd);
|
|
+ G_HoldUserMissile(self,self->enemy);
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent;
|
|
+ self->bounceFrac = 0.0f;
|
|
+ self->enemy->client->ps.states |= isBurning;
|
|
+ self->enemy->client->ps.timers[tmBurning] += 100;
|
|
+ G_UserWeaponDamage(self->enemy,self,missileOwner,fwd,self->s.origin,self->powerLevelCurrent / 15.0,0,self->extraKnockback);
|
|
+ if(self->enemy->client->ps.timers[tmBurning] >= 1500){
|
|
+ self->freeAfterEvent = qtrue;
|
|
+ self->enemy->client->ps.states &= ~isBurning;
|
|
+ }
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+static void Think_NormalMissileRidePlayer( gentity_t *self ){
|
|
+ vec3_t fwd;
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity( self );
|
|
+ AngleVectors(self->enemy->r.currentAngles, fwd, NULL, NULL);
|
|
+ VectorNormalize(fwd);
|
|
+ self->s.dashDir[1] = self->powerLevelCurrent;
|
|
+ self->bounceFrac = 0.0f;
|
|
+ self->enemy->client->ps.states |= isRiding;
|
|
+ self->enemy->client->ps.timers[tmRiding] += 100;
|
|
+ G_UserWeaponDamage(self->enemy,self,missileOwner,fwd,self->s.origin,self->powerLevelCurrent / 25.0,0,self->extraKnockback);
|
|
+ if(self->enemy->client->ps.timers[tmRiding] >= 2500){
|
|
+ self->freeAfterEvent = qtrue;
|
|
+ self->enemy->client->ps.states &= ~isRiding;
|
|
+ //self->enemy->client->ps.states |= canRideEscape;
|
|
+ }
|
|
+ self->nextthink = level.time + FRAMETIME;
|
|
+}
|
|
+/*
|
|
+============
|
|
+G_LocationImpact
|
|
+============
|
|
+*/
|
|
+void G_LocationImpact(vec3_t point, gentity_t* targ, gentity_t* attacker) {
|
|
+ vec3_t attackPath;
|
|
+ vec3_t attackAngle;
|
|
+ int clientRotation;
|
|
+ int attackRotation; // Degrees rotation around client.
|
|
+ int impactRotation; // used to check back of head vs. face
|
|
+ VectorSubtract(targ->r.currentOrigin, point, attackPath);
|
|
+ vectoangles(attackPath, attackAngle);
|
|
+ clientRotation = targ->client->ps.viewangles[YAW];
|
|
+ attackRotation = attackAngle[YAW];
|
|
+ impactRotation = abs(clientRotation-attackRotation);
|
|
+ impactRotation += 45; // just to make it easier to work with
|
|
+ impactRotation = impactRotation % 360; // Keep it in the 0-359 range
|
|
+ if (impactRotation < 90)
|
|
+ targ->client->lasthurt_location = LOCATION_BACK;
|
|
+ else if (impactRotation < 180)
|
|
+ targ->client->lasthurt_location = LOCATION_RIGHT;
|
|
+ else if (impactRotation < 270)
|
|
+ targ->client->lasthurt_location = LOCATION_FRONT;
|
|
+ else if (impactRotation < 360)
|
|
+ targ->client->lasthurt_location = LOCATION_LEFT;
|
|
+ else
|
|
+ targ->client->lasthurt_location = LOCATION_NONE;
|
|
+}
|
|
+
|
|
+// Handles impact of the weapon with map geometry or entities.
|
|
+void G_ImpactUserWeapon(gentity_t *self,trace_t *trace){
|
|
+ gentity_t *other;
|
|
+ qboolean hitClient = qfalse;
|
|
+ vec3_t velocity;
|
|
+ int radius;
|
|
+ other = &g_entities[trace->entityNum];
|
|
+ G_LocationImpact(trace->endpos,other,GetMissileOwnerEntity(self));
|
|
+ //G_Printf("Attack's power level is : %i\n",self->powerLevelCurrent);
|
|
+ // Initiate Player Interaction
|
|
+ if(other->s.eType == ET_PLAYER){
|
|
+ SnapVectorTowards( trace->endpos, self->s.pos.trBase );
|
|
+ G_SetOrigin( self, trace->endpos );
|
|
+ if((other->client->ps.bitFlags & usingBlock) && other->client->lasthurt_location == LOCATION_FRONT){
|
|
+ if(self->isSwattable){
|
|
+ self->bounceFrac = 1.0f;
|
|
+ G_PushUserMissile(self, other);
|
|
+ return;
|
|
+ }
|
|
+ else{
|
|
+ self->strugglingPlayer = qtrue;
|
|
+ self->enemy = other;
|
|
+ self->enemy->client->ps.timers[tmStruggleBlock] = 0;
|
|
+ self->think = Think_NormalMissileStrugglePlayer;
|
|
+ self->nextthink = level.time;
|
|
+ if(!other->client->ps.lockedTarget){
|
|
+ other->client->ps.lockedTarget = self->s.number;
|
|
+ other->client->ps.lockedPosition = &self->r.currentOrigin;
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ else if(self->s.eType == ET_MISSILE){
|
|
+ if(self->isSwattable){
|
|
+ if(((self->powerLevelCurrent * 10) < other->client->ps.powerLevel[plFatigue]) && other->client->ps.bitFlags & usingAlter){
|
|
+ self->freeAfterEvent = qtrue;
|
|
+ return;
|
|
+ }
|
|
+ //Do Explosion Below
|
|
+ }
|
|
+ else{
|
|
+ /*self->enemy = other;
|
|
+ self->enemy->client->ps.timers[tmBurning] = 0;
|
|
+ self->think = Think_NormalMissileBurnPlayer;
|
|
+ self->nextthink = level.time;
|
|
+ return;*/
|
|
+ }
|
|
+ }
|
|
+ else if(self->s.eType == ET_BEAMHEAD && !(other->client->ps.states & isRiding)){
|
|
+ /*self->enemy = other;
|
|
+ self->enemy->client->ps.timers[tmRiding] = 0;
|
|
+ self->think = Think_NormalMissileRidePlayer;
|
|
+ self->nextthink = level.time;
|
|
+ return;*/
|
|
+ }
|
|
+ }
|
|
+ // Initiate Attack Interaction
|
|
+ else if(self->s.eType == ET_MISSILE){
|
|
+ //G_Printf("I am definitely a missile!\n");
|
|
+ if(other->s.eType == ET_BEAMHEAD){
|
|
+ //G_Printf("Their Beam absorbed our missile!\n");
|
|
+ other->powerLevelCurrent += self->powerLevelCurrent;
|
|
+ self->freeAfterEvent = qtrue;
|
|
+ return;
|
|
+ }
|
|
+ else if(other->s.eType == ET_MISSILE){
|
|
+ if(other->isSwattable){
|
|
+ if(!self->isSwattable){
|
|
+ //G_Printf("Our missile absorbed their swattable!\n");
|
|
+ self->powerLevelCurrent += other->powerLevelCurrent;
|
|
+ other->freeAfterEvent = qtrue;
|
|
+ return;
|
|
+ }
|
|
+ else{
|
|
+ //G_Printf("Swattables collide explosion!\n");
|
|
+ }
|
|
+ }
|
|
+ else{
|
|
+ if(other->powerLevelCurrent >= self->powerLevelCurrent){
|
|
+ //G_Printf("Our missile absorbed their weaker missile!\n");
|
|
+ other->powerLevelCurrent += self->powerLevelCurrent;
|
|
+ self->freeAfterEvent = qtrue;
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ else if(self->s.eType == ET_BEAMHEAD){
|
|
+ if(other->s.eType == ET_BEAMHEAD){
|
|
+ if(!(self->client->ps.bitFlags & isStruggling)){
|
|
+ G_AddEvent(self,EV_POWER_STRUGGLE_START, DirToByte( trace->plane.normal));
|
|
+ other->enemy = self;
|
|
+ self->s.dashDir[2] = 1.0f;
|
|
+ self->think = Think_NormalMissileStruggle;
|
|
+ self->nextthink = level.time;
|
|
+ self->enemy = other;
|
|
+ self->enemy->s.dashDir[2] = 1.0f;
|
|
+ self->enemy->think = Think_NormalMissileStruggle;
|
|
+ self->enemy->nextthink = level.time;
|
|
+ if(self->s.eFlags & EF_GUIDED){self->s.eFlags &= ~EF_GUIDED;}
|
|
+ if(other->s.eFlags & EF_GUIDED){other->s.eFlags &= ~EF_GUIDED;}
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+ else if(other->s.eType == ET_MISSILE){return;}
|
|
+ }
|
|
+ // Bounce off the world
|
|
+ if(self->bounceFrac && self->bouncesLeft && !other->takedamage){
|
|
+ G_BounceUserMissile(self, trace);
|
|
+ self->bouncesLeft--;
|
|
+ return;
|
|
+ }
|
|
+ // Hit a player or the world
|
|
+ else if(other->client || !other->takedamage){
|
|
+ self->takedamage = qtrue;
|
|
+ if(other->takedamage && self->damage) {
|
|
+ BG_EvaluateTrajectoryDelta(&self->s, &self->s.pos, level.time, velocity);
|
|
+ if(VectorLength( velocity ) == 0){
|
|
+ velocity[2] = 1;
|
|
+ }
|
|
+ G_UserWeaponDamage(other,self,GetMissileOwnerEntity(self),velocity,self->s.origin,self->powerLevelCurrent,0,self->extraKnockback);
|
|
+ }
|
|
+ }
|
|
+ // Explode
|
|
+ if(!(other->takedamage) || (other->s.eType == ET_PLAYER)){
|
|
+ if(self->guided){g_entities[self->s.clientNum].client->ps.weaponstate = WEAPON_READY;}
|
|
+ if(other->takedamage && other->client){
|
|
+ G_AddEvent( self, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
|
|
+ self->s.otherEntityNum = other->s.number;
|
|
+ }
|
|
+ else if(trace->surfaceFlags & SURF_METALSTEPS){G_AddEvent( self, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) );}
|
|
+ else{G_AddEvent( self, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) );}
|
|
+ SnapVectorTowards( trace->endpos, self->s.pos.trBase );
|
|
+ G_SetOrigin( self, trace->endpos );
|
|
+ self->powerLevelCurrent *= 0.5;
|
|
+ self->s.eType = ET_EXPLOSION;
|
|
+ self->splashEnd = level.time + self->splashDuration;
|
|
+ self->splashTimer = level.time + 100;
|
|
+ trap_LinkEntity(self);
|
|
+ }
|
|
+}
|
|
+void G_DetachUserWeapon (gentity_t *self) {
|
|
+ gentity_t *other;
|
|
+ other = self->enemy;
|
|
+ if(self->s.eType == ET_BEAMHEAD && self->powerLevelCurrent > 0){
|
|
+ g_entities[self->s.clientNum].client->ps.weaponstate = WEAPON_READY;
|
|
+ self->s.eType = ET_MISSILE;
|
|
+ self->think = Think_NormalMissile;
|
|
+ }
|
|
+}
|
|
+void G_RemoveUserWeapon (gentity_t *self) {
|
|
+ gentity_t *other;
|
|
+ other = self->enemy;
|
|
+ other->client->ps.bitFlags &= ~isStruggling;
|
|
+ self->client->ps.bitFlags &= ~isStruggling;
|
|
+ if(other->client->ps.lockedTarget >= MAX_CLIENTS){
|
|
+ other->client->ps.lockedPosition = NULL;
|
|
+ other->client->ps.lockedTarget = 0;
|
|
+ }
|
|
+ if (self->guided) {
|
|
+ g_entities[ self->s.clientNum ].client->ps.weaponstate = WEAPON_READY;
|
|
+ }
|
|
+ if (self->s.eType == ET_BEAMHEAD && self->powerLevelCurrent > 0) {
|
|
+ G_ExplodeUserWeapon ( self );
|
|
+ return;
|
|
+ }
|
|
+ G_FreeEntity( self );
|
|
+}
|
|
+
|
|
+void Missile_Smooth (gentity_t *ent, vec3_t origin, trace_t *tr) {
|
|
+ int touch[MAX_GENTITIES];
|
|
+ vec3_t mins, maxs;
|
|
+ int num;
|
|
+
|
|
+ num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES);
|
|
+ VectorAdd(origin,ent->r.mins, mins);
|
|
+ VectorAdd(origin,ent->r.maxs, maxs);
|
|
+ VectorCopy(origin,ent->s.pos.trBase);
|
|
+ ent->s.pos.trTime = level.time;
|
|
+}
|
|
+
|
|
+/*
|
|
+ -----------------------------------------
|
|
+ R U N F R A M E F U N C T I O N S
|
|
+ -----------------------------------------
|
|
+*/
|
|
+void G_RunUserExplosion(gentity_t *ent) {
|
|
+ int radius,power;
|
|
+ float step;
|
|
+ if(level.time > ent->splashTimer && ent->powerLevelCurrent > 0){
|
|
+ ent->splashTimer = level.time + 100;
|
|
+ step = (1.0 - ((float)ent->splashEnd - (float)level.time) / (float)ent->splashDuration);
|
|
+ radius = step * ent->splashRadius;
|
|
+ power = (ent->powerLevelCurrent * (100.0/(float)ent->splashDuration));
|
|
+ //G_Printf("Explosion power : %i\n",power);
|
|
+ G_UserRadiusDamage(ent->r.currentOrigin,GetMissileOwnerEntity(ent),ent,power,radius,ent->extraKnockback);
|
|
+ }
|
|
+ if(level.time >= ent->splashEnd || ent->powerLevelCurrent <= 0){
|
|
+ ent->freeAfterEvent = qtrue;
|
|
+ }
|
|
+}
|
|
+void G_RunUserMissile( gentity_t *ent ) {
|
|
+ vec3_t origin;
|
|
+ trace_t trace;
|
|
+ int pass_ent;
|
|
+ int beamKnockBack;
|
|
+ gentity_t *traceEnt;
|
|
+ vec3_t muzzle, forward, right, up;
|
|
+ vec3_t start,end;
|
|
+ trace_t trace2;
|
|
+ gentity_t *traceEnt2;
|
|
+ gentity_t *missileOwner = GetMissileOwnerEntity(ent);
|
|
+ BG_EvaluateTrajectory( &ent->s, &ent->s.pos, level.time, origin );
|
|
+ if (ent->count) {
|
|
+ pass_ent = ent->s.number;
|
|
+ }
|
|
+ else{
|
|
+ pass_ent = ent->r.ownerNum;
|
|
+ }
|
|
+ // trace a line from the previous position to the current position
|
|
+ trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, pass_ent, ent->clipmask );
|
|
+
|
|
+ if ( trace.startsolid || trace.allsolid ) {
|
|
+ // make sure the trace.entityNum is set to the entity we're stuck in
|
|
+ trap_Trace(&trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, pass_ent, ent->clipmask );
|
|
+ trace.fraction = 0;
|
|
+ }
|
|
+ else {
|
|
+ VectorCopy( trace.endpos, ent->r.currentOrigin );
|
|
+ }
|
|
+ //Missile_Smooth(ent,origin,&trace);
|
|
+ trap_LinkEntity(ent);
|
|
+ //G_Printf("%i\n",ent->r.currentOrigin);
|
|
+
|
|
+ if(trace.fraction != 1){
|
|
+ // never explode or bounce on sky
|
|
+ if ( trace.surfaceFlags & SURF_NOIMPACT ) {
|
|
+ G_RemoveUserWeapon( ent );
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ G_ImpactUserWeapon(ent, &trace);
|
|
+
|
|
+ if ((ent->s.eType != ET_MISSILE) && (ent->s.eType != ET_BEAMHEAD)){return;}
|
|
+ }
|
|
+ // if the attack wasn't yet outside the player body
|
|
+ if (!ent->count) {
|
|
+ // check if the attack is outside the owner bbox
|
|
+ trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ENTITYNUM_NONE, ent->clipmask );
|
|
+ if (!trace.startsolid || trace.entityNum != ent->r.ownerNum) {
|
|
+ ent->count = 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Here we test if something entered a beam
|
|
+ if(ent->s.eType == ET_BEAMHEAD && !(ent->s.eFlags & EF_GUIDED)){
|
|
+ // Get player direction
|
|
+ AngleVectors( missileOwner->client->ps.viewangles, forward, right, up );
|
|
+ CalcMuzzlePoint( missileOwner, forward, right, up, muzzle );
|
|
+ // Get player position and beam head position
|
|
+ BG_EvaluateTrajectory( &missileOwner->s, &missileOwner->s.pos, level.time, start );
|
|
+ BG_EvaluateTrajectory( &ent->s, &ent->s.pos, level.time, end );
|
|
+ // Trace between the two positions
|
|
+ trap_Trace (&trace2, start, ent->r.mins, ent->r.maxs, end, ent->s.number, MASK_SHOT);
|
|
+ traceEnt2 = &g_entities[ trace2.entityNum ];
|
|
+ // Snap the endpos to integers, but nudged towards the line
|
|
+ SnapVectorTowards( trace2.endpos, muzzle );
|
|
+ // If a player is holding block when he entered the beam, inititate push struggle.
|
|
+ if (traceEnt2->client && traceEnt2->takedamage && trace2.fraction != 1 && (traceEnt2->client->ps.bitFlags & usingBlock)) {
|
|
+ G_SetOrigin(ent,trace2.endpos);
|
|
+ G_ImpactUserWeapon(ent,&trace2);
|
|
+ // Else if a player that entered the beam isn't blocking, burn them and/or push them.
|
|
+ }else if (traceEnt2->client && traceEnt2->takedamage && trace2.fraction != 1 && !(traceEnt2->client->ps.bitFlags & usingBlock)) {
|
|
+ }
|
|
+ if ((ent->s.eType != ET_MISSILE) && (ent->s.eType != ET_BEAMHEAD)) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // check think function after bouncing
|
|
+ G_RunThink( ent );
|
|
+}
|
|
diff --git a/Game/Game/g_userweapons.c b/Game/Game/g_userweapons.c
|
|
index 15badf5..42bccac 100644
|
|
--- a/Game/Game/g_userweapons.c
|
|
+++ b/Game/Game/g_userweapons.c
|
|
@@ -1,168 +1,168 @@
|
|
-#include "g_local.h"
|
|
-
|
|
-static int weaponPhysicsMasks[MAX_CLIENTS];
|
|
-static g_userWeapon_t weaponPhysicsDatabase[MAX_CLIENTS][ALTSPAWN_OFFSET + MAX_PLAYERWEAPONS];
|
|
- // NOTE: SPAWN_OFFSET is the upper bound because it is the first
|
|
- // element after the alternate fires!
|
|
-
|
|
-/*
|
|
-======================
|
|
-G_FindUserWeaponMask
|
|
-======================
|
|
-*/
|
|
-int *G_FindUserWeaponMask( int clientNum ) {
|
|
- return &weaponPhysicsMasks[clientNum];
|
|
-}
|
|
-
|
|
-/*
|
|
-======================
|
|
-G_FindUserWeaponData
|
|
-======================
|
|
-*/
|
|
-g_userWeapon_t *G_FindUserWeaponData( int clientNum, int weaponNum ) {
|
|
- return &weaponPhysicsDatabase[clientNum][weaponNum - 1];
|
|
-}
|
|
-
|
|
-/*
|
|
-===========================
|
|
-G_FindUserWeaponSpawnData
|
|
-===========================
|
|
-*/
|
|
-g_userWeapon_t *G_FindUserWeaponSpawnData( int clientNum, int weaponNum ) {
|
|
- return &weaponPhysicsDatabase[clientNum][weaponNum - 1 + SPAWN_OFFSET];
|
|
-}
|
|
-
|
|
-/*
|
|
-=========================
|
|
-G_FindUserAltWeaponData
|
|
-=========================
|
|
-*/
|
|
-g_userWeapon_t *G_FindUserAltWeaponData( int clientNum, int weaponNum ) {
|
|
- return &weaponPhysicsDatabase[clientNum][weaponNum - 1 + ALTWEAPON_OFFSET];
|
|
-}
|
|
-
|
|
-/*
|
|
-==============================
|
|
-G_FindUserAltWeaponSpawnData
|
|
-==============================
|
|
-*/
|
|
-g_userWeapon_t *G_FindUserAltWeaponSpawnData( int clientNum, int weaponNum ) {
|
|
- return &weaponPhysicsDatabase[clientNum][weaponNum - 1 + ALTSPAWN_OFFSET];
|
|
-}
|
|
-
|
|
-/*======================
|
|
-G_CheckSkills
|
|
-======================*/
|
|
-void G_CheckSkills(playerState_t *ps){
|
|
- int weapon;
|
|
- weapon = 1;
|
|
- ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL1;
|
|
- ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL2;
|
|
- ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL3;
|
|
- ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL4;
|
|
- ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL5;
|
|
- ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL6;
|
|
- while(weapon < 7){
|
|
- qboolean usable;
|
|
- g_userWeapon_t *weaponSettings;
|
|
- usable = qtrue;
|
|
- weaponSettings = G_FindUserWeaponData(ps->clientNum,weapon);
|
|
- // =========================
|
|
- // REQUIREMENTS
|
|
- // =========================
|
|
- if(weaponSettings->require_maximum && ps->powerLevel[plMaximum] < weaponSettings->require_maximum){usable = qfalse;}
|
|
- if(weaponSettings->require_minPowerLevel && ps->powerLevel[plCurrent] < weaponSettings->require_minPowerLevel){usable = qfalse;}
|
|
- if(weaponSettings->require_maxPowerLevel && ps->powerLevel[plCurrent] > weaponSettings->require_maxPowerLevel){usable = qfalse;}
|
|
- if(weaponSettings->require_minFatigue && ps->powerLevel[plFatigue] < weaponSettings->require_minFatigue){usable = qfalse;}
|
|
- if(weaponSettings->require_maxFatigue && ps->powerLevel[plFatigue] > weaponSettings->require_maxFatigue){usable = qfalse;}
|
|
- if(weaponSettings->require_minHealth && ps->powerLevel[plHealth] < weaponSettings->require_minHealth){usable = qfalse;}
|
|
- if(weaponSettings->require_maxHealth && ps->powerLevel[plHealth] > weaponSettings->require_maxHealth){usable = qfalse;}
|
|
- if(weaponSettings->require_minTier && ps->powerLevel[plTierCurrent] < weaponSettings->require_minTier-1){usable = qfalse;}
|
|
- if(weaponSettings->require_maxTier && ps->powerLevel[plTierCurrent] > weaponSettings->require_maxTier-1){usable = qfalse;}
|
|
- if(weaponSettings->require_minTotalTier && ps->powerLevel[plTierTotal] < weaponSettings->require_minTotalTier-1){usable = qfalse;}
|
|
- if(weaponSettings->require_maxTotalTier && ps->powerLevel[plTierTotal] > weaponSettings->require_maxTotalTier-1){usable = qfalse;}
|
|
- if(weaponSettings->require_ground && !(ps->bitFlags & atopGround)){usable = qfalse;}
|
|
- if(weaponSettings->require_flight && !(ps->bitFlags & usingFlight)){usable = qfalse;}
|
|
- if(weaponSettings->require_water && !(ps->bitFlags & underWater)){usable = qfalse;}
|
|
- if(usable){
|
|
- if(weapon == 1){ps->powerups[PW_SKILLS] |= USABLE_SKILL1;}
|
|
- if(weapon == 2){ps->powerups[PW_SKILLS] |= USABLE_SKILL2;}
|
|
- if(weapon == 3){ps->powerups[PW_SKILLS] |= USABLE_SKILL3;}
|
|
- if(weapon == 4){ps->powerups[PW_SKILLS] |= USABLE_SKILL4;}
|
|
- if(weapon == 5){ps->powerups[PW_SKILLS] |= USABLE_SKILL5;}
|
|
- if(weapon == 6){ps->powerups[PW_SKILLS] |= USABLE_SKILL6;}
|
|
- }
|
|
- weapon += 1;
|
|
- }
|
|
-}
|
|
-
|
|
-/*
|
|
-======================
|
|
-G_LinkUserWeaponData
|
|
-======================
|
|
-NOTE: This needs to be called on the server by the EV_CHANGE_WEAPON event.
|
|
-There is currently not a case statement included for it.
|
|
-*/
|
|
-void G_LinkUserWeaponData( playerState_t *ps ) {
|
|
- g_userWeapon_t *weaponSettings;
|
|
- g_userWeapon_t *alt_weaponSettings;
|
|
- // Prevent reading below bound of array pointed to by weaponSettings.
|
|
- if ( ps->weapon == WP_NONE ) {
|
|
- memset(ps->currentSkill, 0, sizeof(ps->currentSkill) );
|
|
- return;
|
|
- }
|
|
-
|
|
- // NOTE: This function will NEVER be able to be called with a weapon that
|
|
- // was not defined, so the next statement is SAFE.
|
|
- weaponSettings = G_FindUserWeaponData( ps->clientNum, ps->weapon );
|
|
- ps->currentSkill[WPSTAT_NUMCHECK] = ps->weapon;
|
|
- ps->currentSkill[WPSTAT_POWER] = weaponSettings->damage_damage;
|
|
- ps->currentSkill[WPSTAT_POWERLEVELCOST] = weaponSettings->costs_powerLevel;
|
|
- ps->currentSkill[WPSTAT_MAXIMUMCOST] = weaponSettings->costs_maximum;
|
|
- ps->currentSkill[WPSTAT_HEALTHCOST] = weaponSettings->costs_health;
|
|
- ps->currentSkill[WPSTAT_FATIGUECOST] = weaponSettings->costs_fatigue;
|
|
- ps->currentSkill[WPSTAT_CHRGTIME] = weaponSettings->costs_chargeTime;
|
|
- ps->currentSkill[WPSTAT_CHRGREADY] = weaponSettings->costs_chargeReady;
|
|
- ps->currentSkill[WPSTAT_COOLTIME] = weaponSettings->costs_cooldownTime;
|
|
- ps->currentSkill[WPSTAT_RESTRICT_MOVEMENT] = weaponSettings->restrict_movement;
|
|
- ps->currentSkill[WPSTAT_BITFLAGS] = weaponSettings->general_bitflags;
|
|
- ps->currentSkill[WPSTAT_RESTRICT_MOVEMENT] = weaponSettings->restrict_movement;
|
|
- // Set the ready state of a charged weapon
|
|
- if ( ( ps->stats[stChargePercentPrimary] >= weaponSettings->costs_chargeReady ) && ( ps->currentSkill[WPSTAT_BITFLAGS] & WPF_NEEDSCHARGE ) ) {
|
|
- ps->currentSkill[WPSTAT_BITFLAGS] |= WPF_READY;
|
|
- } else {
|
|
- ps->currentSkill[WPSTAT_BITFLAGS] &= ~WPF_READY;
|
|
- }
|
|
- // Guided trajectories and beams should use WEAPON_GUIDING state
|
|
- if ( weaponSettings->homing_type == HOM_GUIDED || weaponSettings->general_type == skillTypeBEAM ) {
|
|
- ps->currentSkill[WPSTAT_BITFLAGS] |= WPF_GUIDED;
|
|
- }
|
|
-
|
|
- // Only attempt to link altfire weapon when it exists.
|
|
- if (ps->currentSkill[WPSTAT_BITFLAGS] & WPF_ALTWEAPONPRESENT) {
|
|
- alt_weaponSettings = G_FindUserAltWeaponData( ps->clientNum, ps->weapon );
|
|
- ps->currentSkill[WPSTAT_ALT_POWER] = alt_weaponSettings->damage_damage;
|
|
- ps->currentSkill[WPSTAT_ALT_POWERLEVELCOST] = alt_weaponSettings->costs_powerLevel;
|
|
- ps->currentSkill[WPSTAT_ALT_MAXIMUMCOST] = alt_weaponSettings->costs_maximum;
|
|
- ps->currentSkill[WPSTAT_ALT_HEALTHCOST] = alt_weaponSettings->costs_health;
|
|
- ps->currentSkill[WPSTAT_ALT_FATIGUECOST] = alt_weaponSettings->costs_fatigue;
|
|
- ps->currentSkill[WPSTAT_ALT_CHRGTIME] = alt_weaponSettings->costs_chargeTime;
|
|
- ps->currentSkill[WPSTAT_ALT_CHRGREADY] = alt_weaponSettings->costs_chargeReady;
|
|
- ps->currentSkill[WPSTAT_ALT_COOLTIME] = alt_weaponSettings->costs_cooldownTime;
|
|
- ps->currentSkill[WPSTAT_ALT_RESTRICT_MOVEMENT] = alt_weaponSettings->restrict_movement;
|
|
- ps->currentSkill[WPSTAT_ALT_BITFLAGS] = alt_weaponSettings->general_bitflags;
|
|
- ps->currentSkill[WPSTAT_ALT_RESTRICT_MOVEMENT] = alt_weaponSettings->restrict_movement;
|
|
- // Guided trajectories and beams should use WEAPON_GUIDING state
|
|
- if ( alt_weaponSettings->homing_type == HOM_GUIDED || alt_weaponSettings->general_type == skillTypeBEAM ) {
|
|
- ps->currentSkill[WPSTAT_ALT_BITFLAGS] |= WPF_GUIDED;
|
|
- }
|
|
-
|
|
- // Set the ready state of a charged altfire weapon
|
|
- if ( ( ps->stats[stChargePercentSecondary] >= alt_weaponSettings->costs_chargeReady ) && ( ps->currentSkill[WPSTAT_ALT_BITFLAGS] & WPF_NEEDSCHARGE ) ) {
|
|
- ps->currentSkill[WPSTAT_ALT_BITFLAGS] |= WPF_READY;
|
|
- } else {
|
|
- ps->currentSkill[WPSTAT_ALT_BITFLAGS] &= ~WPF_READY;
|
|
- }
|
|
- }
|
|
-
|
|
-}
|
|
+#include "g_local.h"
|
|
+
|
|
+static int weaponPhysicsMasks[MAX_CLIENTS];
|
|
+static g_userWeapon_t weaponPhysicsDatabase[MAX_CLIENTS][ALTSPAWN_OFFSET + MAX_PLAYERWEAPONS];
|
|
+ // NOTE: SPAWN_OFFSET is the upper bound because it is the first
|
|
+ // element after the alternate fires!
|
|
+
|
|
+/*
|
|
+======================
|
|
+G_FindUserWeaponMask
|
|
+======================
|
|
+*/
|
|
+int *G_FindUserWeaponMask( int clientNum ) {
|
|
+ return &weaponPhysicsMasks[clientNum];
|
|
+}
|
|
+
|
|
+/*
|
|
+======================
|
|
+G_FindUserWeaponData
|
|
+======================
|
|
+*/
|
|
+g_userWeapon_t *G_FindUserWeaponData( int clientNum, int weaponNum ) {
|
|
+ return &weaponPhysicsDatabase[clientNum][weaponNum - 1];
|
|
+}
|
|
+
|
|
+/*
|
|
+===========================
|
|
+G_FindUserWeaponSpawnData
|
|
+===========================
|
|
+*/
|
|
+g_userWeapon_t *G_FindUserWeaponSpawnData( int clientNum, int weaponNum ) {
|
|
+ return &weaponPhysicsDatabase[clientNum][weaponNum - 1 + SPAWN_OFFSET];
|
|
+}
|
|
+
|
|
+/*
|
|
+=========================
|
|
+G_FindUserAltWeaponData
|
|
+=========================
|
|
+*/
|
|
+g_userWeapon_t *G_FindUserAltWeaponData( int clientNum, int weaponNum ) {
|
|
+ return &weaponPhysicsDatabase[clientNum][weaponNum - 1 + ALTWEAPON_OFFSET];
|
|
+}
|
|
+
|
|
+/*
|
|
+==============================
|
|
+G_FindUserAltWeaponSpawnData
|
|
+==============================
|
|
+*/
|
|
+g_userWeapon_t *G_FindUserAltWeaponSpawnData( int clientNum, int weaponNum ) {
|
|
+ return &weaponPhysicsDatabase[clientNum][weaponNum - 1 + ALTSPAWN_OFFSET];
|
|
+}
|
|
+
|
|
+/*======================
|
|
+G_CheckSkills
|
|
+======================*/
|
|
+void G_CheckSkills(playerState_t *ps){
|
|
+ int weapon;
|
|
+ weapon = 1;
|
|
+ ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL1;
|
|
+ ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL2;
|
|
+ ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL3;
|
|
+ ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL4;
|
|
+ ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL5;
|
|
+ ps->powerups[PW_SKILLS] &= ~ USABLE_SKILL6;
|
|
+ while(weapon < 7){
|
|
+ qboolean usable;
|
|
+ g_userWeapon_t *weaponSettings;
|
|
+ usable = qtrue;
|
|
+ weaponSettings = G_FindUserWeaponData(ps->clientNum,weapon);
|
|
+ // =========================
|
|
+ // REQUIREMENTS
|
|
+ // =========================
|
|
+ if(weaponSettings->require_maximum && ps->powerLevel[plMaximum] < weaponSettings->require_maximum){usable = qfalse;}
|
|
+ if(weaponSettings->require_minPowerLevel && ps->powerLevel[plCurrent] < weaponSettings->require_minPowerLevel){usable = qfalse;}
|
|
+ if(weaponSettings->require_maxPowerLevel && ps->powerLevel[plCurrent] > weaponSettings->require_maxPowerLevel){usable = qfalse;}
|
|
+ if(weaponSettings->require_minFatigue && ps->powerLevel[plFatigue] < weaponSettings->require_minFatigue){usable = qfalse;}
|
|
+ if(weaponSettings->require_maxFatigue && ps->powerLevel[plFatigue] > weaponSettings->require_maxFatigue){usable = qfalse;}
|
|
+ if(weaponSettings->require_minHealth && ps->powerLevel[plHealth] < weaponSettings->require_minHealth){usable = qfalse;}
|
|
+ if(weaponSettings->require_maxHealth && ps->powerLevel[plHealth] > weaponSettings->require_maxHealth){usable = qfalse;}
|
|
+ if(weaponSettings->require_minTier && ps->powerLevel[plTierCurrent] < weaponSettings->require_minTier-1){usable = qfalse;}
|
|
+ if(weaponSettings->require_maxTier && ps->powerLevel[plTierCurrent] > weaponSettings->require_maxTier-1){usable = qfalse;}
|
|
+ if(weaponSettings->require_minTotalTier && ps->powerLevel[plTierTotal] < weaponSettings->require_minTotalTier-1){usable = qfalse;}
|
|
+ if(weaponSettings->require_maxTotalTier && ps->powerLevel[plTierTotal] > weaponSettings->require_maxTotalTier-1){usable = qfalse;}
|
|
+ if(weaponSettings->require_ground && !(ps->bitFlags & atopGround)){usable = qfalse;}
|
|
+ if(weaponSettings->require_flight && !(ps->bitFlags & usingFlight)){usable = qfalse;}
|
|
+ if(weaponSettings->require_water && !(ps->bitFlags & underWater)){usable = qfalse;}
|
|
+ if(usable){
|
|
+ if(weapon == 1){ps->powerups[PW_SKILLS] |= USABLE_SKILL1;}
|
|
+ if(weapon == 2){ps->powerups[PW_SKILLS] |= USABLE_SKILL2;}
|
|
+ if(weapon == 3){ps->powerups[PW_SKILLS] |= USABLE_SKILL3;}
|
|
+ if(weapon == 4){ps->powerups[PW_SKILLS] |= USABLE_SKILL4;}
|
|
+ if(weapon == 5){ps->powerups[PW_SKILLS] |= USABLE_SKILL5;}
|
|
+ if(weapon == 6){ps->powerups[PW_SKILLS] |= USABLE_SKILL6;}
|
|
+ }
|
|
+ weapon += 1;
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+======================
|
|
+G_LinkUserWeaponData
|
|
+======================
|
|
+NOTE: This needs to be called on the server by the EV_CHANGE_WEAPON event.
|
|
+There is currently not a case statement included for it.
|
|
+*/
|
|
+void G_LinkUserWeaponData( playerState_t *ps ) {
|
|
+ g_userWeapon_t *weaponSettings;
|
|
+ g_userWeapon_t *alt_weaponSettings;
|
|
+ // Prevent reading below bound of array pointed to by weaponSettings.
|
|
+ if ( ps->weapon == WP_NONE ) {
|
|
+ memset(ps->currentSkill, 0, sizeof(ps->currentSkill) );
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // NOTE: This function will NEVER be able to be called with a weapon that
|
|
+ // was not defined, so the next statement is SAFE.
|
|
+ weaponSettings = G_FindUserWeaponData( ps->clientNum, ps->weapon );
|
|
+ ps->currentSkill[WPSTAT_NUMCHECK] = ps->weapon;
|
|
+ ps->currentSkill[WPSTAT_POWER] = weaponSettings->damage_damage;
|
|
+ ps->currentSkill[WPSTAT_POWERLEVELCOST] = weaponSettings->costs_powerLevel;
|
|
+ ps->currentSkill[WPSTAT_MAXIMUMCOST] = weaponSettings->costs_maximum;
|
|
+ ps->currentSkill[WPSTAT_HEALTHCOST] = weaponSettings->costs_health;
|
|
+ ps->currentSkill[WPSTAT_FATIGUECOST] = weaponSettings->costs_fatigue;
|
|
+ ps->currentSkill[WPSTAT_CHRGTIME] = weaponSettings->costs_chargeTime;
|
|
+ ps->currentSkill[WPSTAT_CHRGREADY] = weaponSettings->costs_chargeReady;
|
|
+ ps->currentSkill[WPSTAT_COOLTIME] = weaponSettings->costs_cooldownTime;
|
|
+ ps->currentSkill[WPSTAT_RESTRICT_MOVEMENT] = weaponSettings->restrict_movement;
|
|
+ ps->currentSkill[WPSTAT_BITFLAGS] = weaponSettings->general_bitflags;
|
|
+ ps->currentSkill[WPSTAT_RESTRICT_MOVEMENT] = weaponSettings->restrict_movement;
|
|
+ // Set the ready state of a charged weapon
|
|
+ if ( ( ps->stats[stChargePercentPrimary] >= weaponSettings->costs_chargeReady ) && ( ps->currentSkill[WPSTAT_BITFLAGS] & WPF_NEEDSCHARGE ) ) {
|
|
+ ps->currentSkill[WPSTAT_BITFLAGS] |= WPF_READY;
|
|
+ } else {
|
|
+ ps->currentSkill[WPSTAT_BITFLAGS] &= ~WPF_READY;
|
|
+ }
|
|
+ // Guided trajectories and beams should use WEAPON_GUIDING state
|
|
+ if ( weaponSettings->homing_type == HOM_GUIDED || weaponSettings->general_type == skillTypeBEAM ) {
|
|
+ ps->currentSkill[WPSTAT_BITFLAGS] |= WPF_GUIDED;
|
|
+ }
|
|
+
|
|
+ // Only attempt to link altfire weapon when it exists.
|
|
+ if (ps->currentSkill[WPSTAT_BITFLAGS] & WPF_ALTWEAPONPRESENT) {
|
|
+ alt_weaponSettings = G_FindUserAltWeaponData( ps->clientNum, ps->weapon );
|
|
+ ps->currentSkill[WPSTAT_ALT_POWER] = alt_weaponSettings->damage_damage;
|
|
+ ps->currentSkill[WPSTAT_ALT_POWERLEVELCOST] = alt_weaponSettings->costs_powerLevel;
|
|
+ ps->currentSkill[WPSTAT_ALT_MAXIMUMCOST] = alt_weaponSettings->costs_maximum;
|
|
+ ps->currentSkill[WPSTAT_ALT_HEALTHCOST] = alt_weaponSettings->costs_health;
|
|
+ ps->currentSkill[WPSTAT_ALT_FATIGUECOST] = alt_weaponSettings->costs_fatigue;
|
|
+ ps->currentSkill[WPSTAT_ALT_CHRGTIME] = alt_weaponSettings->costs_chargeTime;
|
|
+ ps->currentSkill[WPSTAT_ALT_CHRGREADY] = alt_weaponSettings->costs_chargeReady;
|
|
+ ps->currentSkill[WPSTAT_ALT_COOLTIME] = alt_weaponSettings->costs_cooldownTime;
|
|
+ ps->currentSkill[WPSTAT_ALT_RESTRICT_MOVEMENT] = alt_weaponSettings->restrict_movement;
|
|
+ ps->currentSkill[WPSTAT_ALT_BITFLAGS] = alt_weaponSettings->general_bitflags;
|
|
+ ps->currentSkill[WPSTAT_ALT_RESTRICT_MOVEMENT] = alt_weaponSettings->restrict_movement;
|
|
+ // Guided trajectories and beams should use WEAPON_GUIDING state
|
|
+ if ( alt_weaponSettings->homing_type == HOM_GUIDED || alt_weaponSettings->general_type == skillTypeBEAM ) {
|
|
+ ps->currentSkill[WPSTAT_ALT_BITFLAGS] |= WPF_GUIDED;
|
|
+ }
|
|
+
|
|
+ // Set the ready state of a charged altfire weapon
|
|
+ if ( ( ps->stats[stChargePercentSecondary] >= alt_weaponSettings->costs_chargeReady ) && ( ps->currentSkill[WPSTAT_ALT_BITFLAGS] & WPF_NEEDSCHARGE ) ) {
|
|
+ ps->currentSkill[WPSTAT_ALT_BITFLAGS] |= WPF_READY;
|
|
+ } else {
|
|
+ ps->currentSkill[WPSTAT_ALT_BITFLAGS] &= ~WPF_READY;
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/Game/Game/g_userweapons.h b/Game/Game/g_userweapons.h
|
|
index cfa42c1..25fbfc0 100644
|
|
--- a/Game/Game/g_userweapons.h
|
|
+++ b/Game/Game/g_userweapons.h
|
|
@@ -1,110 +1,110 @@
|
|
-typedef enum{
|
|
- skillTypeMISSILE, // Default missile type; ki blasts, discs, etc.
|
|
- skillTypeBEAM, // Beam type
|
|
- skillTypeHITSCAN, // Rail- / lightninggun style hitscan attack
|
|
- skillTypeTRIGGER, // For redirecting an existing weapon.
|
|
- skillTypeNONE // Useful for self-healing, since it spawns nothing.
|
|
-}userWeaponType_t;
|
|
-typedef enum{
|
|
- HOM_NONE, // Attack doesn't home in any way.
|
|
- HOM_PROX, // As soon as a target enters homing range the attack detonates.
|
|
- HOM_GUIDED, // Attack is guided by the player. ( NOTE: Bots will use a special kind of homing. )
|
|
- HOM_REGULAR, // Attack can pickup targets within its range and vision.
|
|
- HOM_ARCH, // Attack will arch outwards and curve back inwards towards opponents registered location.
|
|
- HOM_DRUNKEN, // Attack will wobble around drunkenly
|
|
- HOM_CYLINDER // Attack can only pickup targets in a cylinder, extending downwards from
|
|
- // a little below its current position. ( NOTE: A little below, to prevent
|
|
- // turning into horizontal travel.)
|
|
- // FIXME: Do we really need this special case for Kakusan Ha?
|
|
- // NOTE: We probably do...
|
|
-}userWeaponHoming_t;
|
|
-typedef struct{
|
|
- // General
|
|
- userWeaponType_t general_type; // Weapon's type
|
|
- int general_bitflags; // Bitflag settings for weapon (see above)
|
|
-
|
|
- // Reactions
|
|
- qboolean isSwattable;
|
|
-
|
|
- // Homing
|
|
- userWeaponHoming_t homing_type; // homing AI for weapon (see above)
|
|
- int homing_range; // range within which the weapon scans for targets
|
|
- int homing_FOV; // FOV of the weapon's range of vision when scanning for targets
|
|
-
|
|
- // Restrictions
|
|
- int restrict_movement;
|
|
-
|
|
- // Requirements
|
|
- int require_maximum;
|
|
- int require_minPowerLevel;
|
|
- int require_maxPowerLevel;
|
|
- int require_minHealth;
|
|
- int require_maxHealth;
|
|
- int require_minFatigue;
|
|
- int require_maxFatigue;
|
|
- int require_minTier;
|
|
- int require_maxTier;
|
|
- int require_minTotalTier;
|
|
- int require_maxTotalTier;
|
|
- int require_ground;
|
|
- int require_flight;
|
|
- int require_water;
|
|
-
|
|
- // Costs
|
|
- int costs_powerLevel;
|
|
- int costs_maximum;
|
|
- int costs_health;
|
|
- int costs_fatigue;
|
|
- int costs_chargeTime; // Time to charge 1% of the weapon
|
|
- int costs_chargeReady; // Percentage at which the weapon can be fired
|
|
- int costs_cooldownTime; // Time the weapon must rest after being fired, before it can be fired again.
|
|
-
|
|
- // Damage
|
|
- int damage_damage; // Amount of damage, done at the impact point.
|
|
- int damage_impede;
|
|
- int damage_radius; // Splash radius of damage. Actual damage linearly decreases the further away you are from the impact point.
|
|
- int damage_radiusDuration;
|
|
- int damage_multiplier; // Damage dealt = damage_damage + multiplier * charge level / 100.
|
|
- int damage_radiusMultiplier; // Actual radius = damage_radius + multiplier * charge level / 100.
|
|
- int damage_extraKnockback; // Additional knockback to add to an attack's knockback caused directly by damage
|
|
-
|
|
- // Physics
|
|
- float physics_speed; // Speed at which the missile initially travels.
|
|
- float physics_acceleration; // Acceleration the missile has.
|
|
- float physics_gravity; // Percentage of the map's gravity the attack is affected by.
|
|
- int physics_radius; // Spherical collision radius of the attack
|
|
- int physics_radiusMultiplier; // Actual radius = physics_radius * multiplier * charge level / 100.
|
|
- int physics_range_min; // Minimum range a hitscan attack has.
|
|
- int physics_range_max; // Maximum range a hitscan attack has.
|
|
- int physics_lifetime; // Maximum time the attack can exist before 'blowing up'.
|
|
- float physics_bounceFrac; // How much of the energy to pass on in a bounce. 0 == no bounce
|
|
- int physics_maxBounces; // How many times at max the missile can bounce.
|
|
- qboolean physics_swat; // If the attack can be swatted away regardless of it's power.
|
|
- qboolean physics_drain; // Does the attack's power drain during it's lifetime?
|
|
- qboolean physics_blind; // Are you vulnerable to your own attack's blinding ability?
|
|
-
|
|
- // Firing
|
|
- int firing_angleW_min; // Minimum width angle ( + and - ) a projectile can deviate from its firing path.
|
|
- int firing_angleW_max; // Maximum width angle ( + and - ) a projectile can deviate from its firing path.
|
|
- int firing_angleH_min; // Minimum height angle ( + and - ) a projectile can deviate from its firing path.
|
|
- int firing_angleH_max; // Maximum height angle ( + and - ) a projectile can deviate from its firing path.
|
|
- int firing_offsetW_max; // Maximum width offset a shot will deviate from the player's regular muzzle point.
|
|
- int firing_offsetW_min; // Minimum width offset a shot will deviate from the player's regular muzzle point.
|
|
- int firing_offsetH_max; // Maximum height offset a shot will deviate from the player's regular muzzle point.
|
|
- int firing_offsetH_min; // Minimum height offset a shot will deviate from the player's regular muzzle point.
|
|
- qboolean firing_offsetWFlip; // Flip the width offset every alternating shot. ( NOTE: For left-right-left-... weapons. )
|
|
- qboolean firing_offsetHFlip; // Flip the height offset every alternating shot. ( NOTE: For up-down-up-... weapons. )
|
|
- int firing_nrShots; // Nr of projectiles fired in one shot.
|
|
-} g_userWeapon_t;
|
|
-
|
|
-// For use in the physics parser
|
|
-typedef g_userWeapon_t g_userWeaponParseBuffer_t; // <-- is just the same.
|
|
-
|
|
-// function declarations for g_userweapons.c
|
|
-
|
|
-g_userWeapon_t *G_FindUserWeaponData( int clientNum, int weaponNum );
|
|
-g_userWeapon_t *G_FindUserWeaponSpawnData( int clientNum, int weaponNum );
|
|
-g_userWeapon_t *G_FindUserAltWeaponData( int clientNum, int weaponNum );
|
|
-g_userWeapon_t *G_FindUserAltWeaponSpawnData( int clientNum, int weaponNum );
|
|
-void G_LinkUserWeaponData( playerState_t *ps );
|
|
-int *G_FindUserWeaponMask( int clientNum );
|
|
+typedef enum{
|
|
+ skillTypeMISSILE, // Default missile type; ki blasts, discs, etc.
|
|
+ skillTypeBEAM, // Beam type
|
|
+ skillTypeHITSCAN, // Rail- / lightninggun style hitscan attack
|
|
+ skillTypeTRIGGER, // For redirecting an existing weapon.
|
|
+ skillTypeNONE // Useful for self-healing, since it spawns nothing.
|
|
+}userWeaponType_t;
|
|
+typedef enum{
|
|
+ HOM_NONE, // Attack doesn't home in any way.
|
|
+ HOM_PROX, // As soon as a target enters homing range the attack detonates.
|
|
+ HOM_GUIDED, // Attack is guided by the player. ( NOTE: Bots will use a special kind of homing. )
|
|
+ HOM_REGULAR, // Attack can pickup targets within its range and vision.
|
|
+ HOM_ARCH, // Attack will arch outwards and curve back inwards towards opponents registered location.
|
|
+ HOM_DRUNKEN, // Attack will wobble around drunkenly
|
|
+ HOM_CYLINDER // Attack can only pickup targets in a cylinder, extending downwards from
|
|
+ // a little below its current position. ( NOTE: A little below, to prevent
|
|
+ // turning into horizontal travel.)
|
|
+ // FIXME: Do we really need this special case for Kakusan Ha?
|
|
+ // NOTE: We probably do...
|
|
+}userWeaponHoming_t;
|
|
+typedef struct{
|
|
+ // General
|
|
+ userWeaponType_t general_type; // Weapon's type
|
|
+ int general_bitflags; // Bitflag settings for weapon (see above)
|
|
+
|
|
+ // Reactions
|
|
+ qboolean isSwattable;
|
|
+
|
|
+ // Homing
|
|
+ userWeaponHoming_t homing_type; // homing AI for weapon (see above)
|
|
+ int homing_range; // range within which the weapon scans for targets
|
|
+ int homing_FOV; // FOV of the weapon's range of vision when scanning for targets
|
|
+
|
|
+ // Restrictions
|
|
+ int restrict_movement;
|
|
+
|
|
+ // Requirements
|
|
+ int require_maximum;
|
|
+ int require_minPowerLevel;
|
|
+ int require_maxPowerLevel;
|
|
+ int require_minHealth;
|
|
+ int require_maxHealth;
|
|
+ int require_minFatigue;
|
|
+ int require_maxFatigue;
|
|
+ int require_minTier;
|
|
+ int require_maxTier;
|
|
+ int require_minTotalTier;
|
|
+ int require_maxTotalTier;
|
|
+ int require_ground;
|
|
+ int require_flight;
|
|
+ int require_water;
|
|
+
|
|
+ // Costs
|
|
+ int costs_powerLevel;
|
|
+ int costs_maximum;
|
|
+ int costs_health;
|
|
+ int costs_fatigue;
|
|
+ int costs_chargeTime; // Time to charge 1% of the weapon
|
|
+ int costs_chargeReady; // Percentage at which the weapon can be fired
|
|
+ int costs_cooldownTime; // Time the weapon must rest after being fired, before it can be fired again.
|
|
+
|
|
+ // Damage
|
|
+ int damage_damage; // Amount of damage, done at the impact point.
|
|
+ int damage_impede;
|
|
+ int damage_radius; // Splash radius of damage. Actual damage linearly decreases the further away you are from the impact point.
|
|
+ int damage_radiusDuration;
|
|
+ int damage_multiplier; // Damage dealt = damage_damage + multiplier * charge level / 100.
|
|
+ int damage_radiusMultiplier; // Actual radius = damage_radius + multiplier * charge level / 100.
|
|
+ int damage_extraKnockback; // Additional knockback to add to an attack's knockback caused directly by damage
|
|
+
|
|
+ // Physics
|
|
+ float physics_speed; // Speed at which the missile initially travels.
|
|
+ float physics_acceleration; // Acceleration the missile has.
|
|
+ float physics_gravity; // Percentage of the map's gravity the attack is affected by.
|
|
+ int physics_radius; // Spherical collision radius of the attack
|
|
+ int physics_radiusMultiplier; // Actual radius = physics_radius * multiplier * charge level / 100.
|
|
+ int physics_range_min; // Minimum range a hitscan attack has.
|
|
+ int physics_range_max; // Maximum range a hitscan attack has.
|
|
+ int physics_lifetime; // Maximum time the attack can exist before 'blowing up'.
|
|
+ float physics_bounceFrac; // How much of the energy to pass on in a bounce. 0 == no bounce
|
|
+ int physics_maxBounces; // How many times at max the missile can bounce.
|
|
+ qboolean physics_swat; // If the attack can be swatted away regardless of it's power.
|
|
+ qboolean physics_drain; // Does the attack's power drain during it's lifetime?
|
|
+ qboolean physics_blind; // Are you vulnerable to your own attack's blinding ability?
|
|
+
|
|
+ // Firing
|
|
+ int firing_angleW_min; // Minimum width angle ( + and - ) a projectile can deviate from its firing path.
|
|
+ int firing_angleW_max; // Maximum width angle ( + and - ) a projectile can deviate from its firing path.
|
|
+ int firing_angleH_min; // Minimum height angle ( + and - ) a projectile can deviate from its firing path.
|
|
+ int firing_angleH_max; // Maximum height angle ( + and - ) a projectile can deviate from its firing path.
|
|
+ int firing_offsetW_max; // Maximum width offset a shot will deviate from the player's regular muzzle point.
|
|
+ int firing_offsetW_min; // Minimum width offset a shot will deviate from the player's regular muzzle point.
|
|
+ int firing_offsetH_max; // Maximum height offset a shot will deviate from the player's regular muzzle point.
|
|
+ int firing_offsetH_min; // Minimum height offset a shot will deviate from the player's regular muzzle point.
|
|
+ qboolean firing_offsetWFlip; // Flip the width offset every alternating shot. ( NOTE: For left-right-left-... weapons. )
|
|
+ qboolean firing_offsetHFlip; // Flip the height offset every alternating shot. ( NOTE: For up-down-up-... weapons. )
|
|
+ int firing_nrShots; // Nr of projectiles fired in one shot.
|
|
+} g_userWeapon_t;
|
|
+
|
|
+// For use in the physics parser
|
|
+typedef g_userWeapon_t g_userWeaponParseBuffer_t; // <-- is just the same.
|
|
+
|
|
+// function declarations for g_userweapons.c
|
|
+
|
|
+g_userWeapon_t *G_FindUserWeaponData( int clientNum, int weaponNum );
|
|
+g_userWeapon_t *G_FindUserWeaponSpawnData( int clientNum, int weaponNum );
|
|
+g_userWeapon_t *G_FindUserAltWeaponData( int clientNum, int weaponNum );
|
|
+g_userWeapon_t *G_FindUserAltWeaponSpawnData( int clientNum, int weaponNum );
|
|
+void G_LinkUserWeaponData( playerState_t *ps );
|
|
+int *G_FindUserWeaponMask( int clientNum );
|
|
diff --git a/Game/Game/game.bat b/Game/Game/game.bat
|
|
index cc9f3e6..9824fed 100644
|
|
--- a/Game/Game/game.bat
|
|
+++ b/Game/Game/game.bat
|
|
@@ -1,74 +1,74 @@
|
|
-@echo off
|
|
-md vm
|
|
-cd vm
|
|
-
|
|
-set cc=call ..\..\..\Tools\Compiler\Perform.bat
|
|
-if "%1"=="TA" goto TA
|
|
-
|
|
-%cc% ../g_main.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../bg_misc.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../bg_lib.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../bg_pmove.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../bg_slidemove.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../../../Shared/q_math.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../../../Shared/q_shared.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_active.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_arenas.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_client.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_cmds.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_combat.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_mem.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_misc.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_mover.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_session.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_spawn.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_svcmds.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_target.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_team.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_trigger.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_utils.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_weapon.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_usermissile.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_userweapons.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_tiers.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_radar.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_weapPhysParser.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_weapPhysScanner.c
|
|
-@if errorlevel 1 goto quit
|
|
-%cc% ../g_weapPhysAttributes.c
|
|
-@if errorlevel 1 goto quit
|
|
-
|
|
-
|
|
-..\..\..\Tools\Compiler\q3asm -f ../game
|
|
-:quit
|
|
-del *.asm
|
|
-cd ..
|
|
+@echo off
|
|
+md vm
|
|
+cd vm
|
|
+
|
|
+set cc=call ..\..\..\Tools\Compiler\Perform.bat
|
|
+if "%1"=="TA" goto TA
|
|
+
|
|
+%cc% ../g_main.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../bg_misc.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../bg_lib.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../bg_pmove.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../bg_slidemove.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../../../Shared/q_math.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../../../Shared/q_shared.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_active.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_arenas.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_client.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_cmds.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_combat.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_mem.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_misc.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_mover.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_session.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_spawn.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_svcmds.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_target.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_team.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_trigger.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_utils.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_weapon.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_usermissile.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_userweapons.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_tiers.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_radar.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_weapPhysParser.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_weapPhysScanner.c
|
|
+@if errorlevel 1 goto quit
|
|
+%cc% ../g_weapPhysAttributes.c
|
|
+@if errorlevel 1 goto quit
|
|
+
|
|
+
|
|
+..\..\..\Tools\Compiler\q3asm -f ../game
|
|
+:quit
|
|
+del *.asm
|
|
+cd ..
|
|
rd vm
|
|
\ No newline at end of file
|
|
diff --git a/Game/Game/game.q3asm b/Game/Game/game.q3asm
|
|
index d63ac45..72ca7ae 100644
|
|
--- a/Game/Game/game.q3asm
|
|
+++ b/Game/Game/game.q3asm
|
|
@@ -1,32 +1,32 @@
|
|
--o "..\..\..\..\Build\ZEQ2\vm\game.qvm"
|
|
-g_main
|
|
-bg_misc
|
|
-..\g_syscalls
|
|
-bg_lib
|
|
-bg_pmove
|
|
-bg_slidemove
|
|
-q_math
|
|
-q_shared
|
|
-g_active
|
|
-g_arenas
|
|
-g_client
|
|
-g_cmds
|
|
-g_combat
|
|
-g_mem
|
|
-g_misc
|
|
-g_mover
|
|
-g_session
|
|
-g_spawn
|
|
-g_svcmds
|
|
-g_target
|
|
-g_team
|
|
-g_trigger
|
|
-g_utils
|
|
-g_weapon
|
|
-g_userweapons
|
|
-g_usermissile
|
|
-g_tiers
|
|
-g_weapPhysParser
|
|
-g_weapPhysScanner
|
|
-g_weapPhysAttributes
|
|
-g_radar
|
|
+-o "..\..\..\..\Build\ZEQ2\vm\game.qvm"
|
|
+g_main
|
|
+bg_misc
|
|
+..\g_syscalls
|
|
+bg_lib
|
|
+bg_pmove
|
|
+bg_slidemove
|
|
+q_math
|
|
+q_shared
|
|
+g_active
|
|
+g_arenas
|
|
+g_client
|
|
+g_cmds
|
|
+g_combat
|
|
+g_mem
|
|
+g_misc
|
|
+g_mover
|
|
+g_session
|
|
+g_spawn
|
|
+g_svcmds
|
|
+g_target
|
|
+g_team
|
|
+g_trigger
|
|
+g_utils
|
|
+g_weapon
|
|
+g_userweapons
|
|
+g_usermissile
|
|
+g_tiers
|
|
+g_weapPhysParser
|
|
+g_weapPhysScanner
|
|
+g_weapPhysAttributes
|
|
+g_radar
|