mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00
2954 lines
56 KiB
C++
2954 lines
56 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2015 the OpenMoHAA team
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
OpenMoHAA source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
OpenMoHAA source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenMoHAA source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
// weaputils.cpp: General Weapon Utility Functions
|
|
|
|
#include "weaputils.h"
|
|
#include "specialfx.h"
|
|
#include "sentient.h"
|
|
#include "actor.h"
|
|
#include "decals.h"
|
|
#include "weapon.h"
|
|
#include "player.h"
|
|
#include "VehicleCollisionEntity.h"
|
|
#include "weapturret.h"
|
|
#include "vehicleturret.h"
|
|
#include "earthquake.h"
|
|
#include <trigger.h>
|
|
|
|
static void FlashPlayers
|
|
(
|
|
Vector org,
|
|
float r,
|
|
float g,
|
|
float b,
|
|
float a,
|
|
float rad,
|
|
float time,
|
|
int type
|
|
);
|
|
|
|
qboolean MeleeAttack
|
|
(
|
|
Vector pos,
|
|
Vector end,
|
|
float damage,
|
|
Entity *attacker,
|
|
meansOfDeath_t means_of_death,
|
|
float attack_width,
|
|
float attack_min_height,
|
|
float attack_max_height,
|
|
float knockback,
|
|
qboolean hit_dead,
|
|
Container<Entity *>*victimlist
|
|
)
|
|
|
|
{
|
|
trace_t trace;
|
|
Entity *victim;
|
|
Vector dir;
|
|
float world_dist;
|
|
Vector new_pos;
|
|
Entity *skip_ent;
|
|
qboolean hit_something = false;
|
|
Vector mins;
|
|
Vector maxs;
|
|
Container<Entity *> potential_victimlist;
|
|
int i;
|
|
int num_traces;
|
|
Vector start;
|
|
|
|
// See how far the world is away
|
|
|
|
dir = end - pos;
|
|
world_dist = dir.length();
|
|
|
|
new_pos = pos;
|
|
|
|
skip_ent = attacker;
|
|
|
|
num_traces = 0;
|
|
|
|
while( new_pos != end )
|
|
{
|
|
trace = G_Trace( pos, vec_zero, vec_zero, end, skip_ent, MASK_SOLID, false, "MeleeAttack - World test" );
|
|
|
|
num_traces++;
|
|
|
|
if( trace.fraction < 1 )
|
|
{
|
|
if( ( trace.entityNum == ENTITYNUM_WORLD ) || ( trace.ent && trace.ent->entity && !trace.ent->entity->takedamage ) )
|
|
{
|
|
dir = trace.endpos - pos;
|
|
world_dist = dir.length();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Make sure we don't go backwards any in our trace
|
|
if( Vector( new_pos - pos ).length() + 0.001 >= Vector( trace.endpos - pos ).length() )
|
|
break;
|
|
|
|
if( num_traces > 10 )
|
|
{
|
|
// We have done too many traces, stop here
|
|
dir = trace.endpos - pos;
|
|
world_dist = dir.length();
|
|
break;
|
|
}
|
|
|
|
new_pos = trace.endpos;
|
|
|
|
if( trace.ent )
|
|
skip_ent = trace.ent->entity;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find things hit
|
|
|
|
dir = end - pos;
|
|
dir.normalize();
|
|
end = pos + ( dir * world_dist );
|
|
|
|
start = pos - dir * ( attack_width * 1.2f );
|
|
start.z = pos.z - dir.z * ( end.z - start.z );
|
|
|
|
victim = NULL;
|
|
|
|
mins = Vector( -attack_width, -attack_width, attack_min_height );
|
|
maxs = Vector( attack_width, attack_width, attack_max_height );
|
|
|
|
G_TraceEntities( pos, mins, maxs, end, &potential_victimlist, MASK_MELEE );
|
|
|
|
/*int previous_contents = attacker->edict->r.contents;
|
|
attacker->edict->r.contents = 0;
|
|
trace = G_Trace( pos, mins, maxs, end, ( ( Sentient * )attacker )->GetActiveWeapon( WEAPON_MAIN ), MASK_MELEE, false, "MeleeAttack" );
|
|
|
|
if( trace.ent && trace.ent->entity )
|
|
{
|
|
gi.Printf( "HIT\n" );
|
|
potential_victimlist.AddObject( trace.ent->entity );
|
|
}
|
|
|
|
attacker->edict->r.contents = previous_contents;*/
|
|
|
|
for( i = 1; i <= potential_victimlist.NumObjects(); i++ )
|
|
{
|
|
victim = potential_victimlist.ObjectAt( i );
|
|
|
|
if( victim && victim->takedamage && victim != attacker )
|
|
{
|
|
dir = end - pos;
|
|
dir.normalize();
|
|
|
|
if( dir == vec_zero )
|
|
{
|
|
dir = victim->centroid - pos;
|
|
dir.normalize();
|
|
}
|
|
|
|
if( victim->IsSubclassOfSentient() && !victim->IsDead() )
|
|
hit_something = true;
|
|
|
|
if( victim->health > 0 || hit_dead )
|
|
{
|
|
if( victimlist && victim->IsSubclassOfSentient() && !victim->IsDead() )
|
|
victimlist->AddObject( victim );
|
|
|
|
victim->Damage( attacker, attacker, damage, pos, dir, vec_zero, knockback, 0, means_of_death );
|
|
|
|
if( victim->edict->r.contents & CONTENTS_SOLID )
|
|
{
|
|
victim->Sound( "pistol_hit" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hit_something;
|
|
}
|
|
|
|
#define DEFAULT_SWORD_DAMAGE 10
|
|
#define DEFAULT_SWORD_KNOCKBACK 50
|
|
|
|
Event EV_Projectile_Speed
|
|
(
|
|
"speed",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"projectileSpeed",
|
|
"set the speed of the projectile",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_MinSpeed
|
|
(
|
|
"minspeed",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"minspeed",
|
|
"set the minimum speed of the projectile (this is for charge up weapons)",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_ChargeSpeed
|
|
(
|
|
"chargespeed",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"set the projectile's speed to be determined by the charge time",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_Damage
|
|
(
|
|
"hitdamage",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"projectileHitDamage",
|
|
"set the damage a projectile does when it hits something",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_Life
|
|
(
|
|
"life",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"projectileLife",
|
|
"set the life of the projectile",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_DMLife
|
|
(
|
|
"dmlife",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"projectileLife",
|
|
"set the life of the projectile in DM",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_MinLife
|
|
(
|
|
"minlife",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"minProjectileLife",
|
|
"set the minimum life of the projectile (this is for charge up weapons)",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_ChargeLife
|
|
(
|
|
"chargelife",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"set the projectile's life to be determined by the charge time",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_Knockback
|
|
(
|
|
"knockback",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"projectileKnockback",
|
|
"set the knockback of the projectile when it hits something",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_DLight
|
|
(
|
|
"dlight",
|
|
EV_DEFAULT,
|
|
"ffff",
|
|
"red green blue intensity",
|
|
"set the color and intensity of the dynamic light on the projectile",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_Avelocity
|
|
(
|
|
"avelocity",
|
|
EV_DEFAULT,
|
|
"SFSFSF",
|
|
"[random|crandom] yaw [random|crandom] pitch [random|crandom] roll",
|
|
"set the angular velocity of the projectile",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_MeansOfDeath
|
|
(
|
|
"meansofdeath",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"meansOfDeath",
|
|
"set the meansOfDeath of the projectile",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_BeamCommand
|
|
(
|
|
"beam",
|
|
EV_DEFAULT,
|
|
"sSSSSSS",
|
|
"command arg1 arg2 arg3 arg4 arg5 arg6",
|
|
"send a command to the beam of this projectile",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_UpdateBeam
|
|
(
|
|
"updatebeam",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Update the attached beam",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_BounceTouch
|
|
(
|
|
"bouncetouch",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile bounce when it hits a non-damageable solid",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_BounceSound
|
|
(
|
|
"bouncesound",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Set the name of the sound that is played when the projectile bounces",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_BounceSound_Metal
|
|
(
|
|
"bouncesound_metal",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Set the name of the sound that is played when the projectile bounces off metal",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_BounceSound_Hard
|
|
(
|
|
"bouncesound_hard",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Set the name of the sound that is played when the projectile bounces off hard surfaces",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_BounceSound_Water
|
|
(
|
|
"bouncesound_water",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Set the name of the sound that is played when the projectile bounces off water",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_Explode
|
|
(
|
|
"explode",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile explode",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_ImpactMarkShader
|
|
(
|
|
"impactmarkshader",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"shader",
|
|
"Set the impact mark of the shader",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_ImpactMarkRadius
|
|
(
|
|
"impactmarkradius",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"radius",
|
|
"Set the radius of the impact mark",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_ImpactMarkOrientation
|
|
(
|
|
"impactmarkorientation",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"degrees",
|
|
"Set the orientation of the impact mark",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_SetExplosionModel
|
|
(
|
|
"explosionmodel",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"modelname",
|
|
"Set the modelname of the explosion to be spawned",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_SetAddVelocity
|
|
(
|
|
"addvelocity",
|
|
EV_DEFAULT,
|
|
"fff",
|
|
"velocity_x velocity_y velocity_z",
|
|
"Set a velocity to be added to the projectile when it is created",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_AddOwnerVelocity
|
|
(
|
|
"addownervelocity",
|
|
EV_DEFAULT,
|
|
"b",
|
|
"bool",
|
|
"Set whether or not the owner's velocity is added to the projectile's velocity",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_HeatSeek
|
|
(
|
|
"heatseek",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile heat seek",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_Drunk
|
|
(
|
|
"drunk",
|
|
EV_DEFAULT,
|
|
"ff",
|
|
"amount rate",
|
|
"Make the projectile drunk",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_Prethink
|
|
(
|
|
"prethink",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile think to update it's velocity",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_SetCanHitOwner
|
|
(
|
|
"canhitowner",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile be able to hit its owner",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_ClearOwner
|
|
(
|
|
"clearowner",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile be able to hit its owner now",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_RemoveWhenStopped
|
|
(
|
|
"removewhenstopped",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile get removed when it stops",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_ExplodeOnTouch
|
|
(
|
|
"explodeontouch",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Make the projectile explode when it touches something damagable",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_NoTouchDamage
|
|
(
|
|
"notouchdamage",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Makes the projectile not blow up or deal damage when it touches a damagable object",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_SetSmashThroughGlass
|
|
(
|
|
"smashthroughglass",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"speed",
|
|
"Makes the projectile smash through windows & other damageble glass objects when going above a set speed",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Projectile_SmashThroughGlass
|
|
(
|
|
"_smashthroughglass",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Think function for smashing through glass",
|
|
EV_NORMAL
|
|
);
|
|
|
|
|
|
CLASS_DECLARATION( Animate, Projectile, NULL )
|
|
{
|
|
{ &EV_Touch, &Projectile::Touch },
|
|
{ &EV_Projectile_Speed, &Projectile::SetSpeed },
|
|
{ &EV_Projectile_MinSpeed, &Projectile::SetMinSpeed },
|
|
{ &EV_Projectile_ChargeSpeed, &Projectile::SetChargeSpeed },
|
|
{ &EV_Projectile_Damage, &Projectile::SetDamage },
|
|
{ &EV_Projectile_Life, &Projectile::SetLife },
|
|
{ &EV_Projectile_DMLife, &Projectile::SetDMLife },
|
|
{ &EV_Projectile_MinLife, &Projectile::SetMinLife },
|
|
{ &EV_Projectile_ChargeLife, &Projectile::SetChargeLife },
|
|
{ &EV_Projectile_Knockback, &Projectile::SetKnockback },
|
|
{ &EV_Projectile_DLight, &Projectile::SetDLight },
|
|
{ &EV_Projectile_Avelocity, &Projectile::SetAvelocity },
|
|
{ &EV_Projectile_MeansOfDeath, &Projectile::SetMeansOfDeath },
|
|
{ &EV_Projectile_BounceTouch, &Projectile::SetBounceTouch },
|
|
{ &EV_Projectile_BounceSound, &Projectile::SetBounceSound },
|
|
{ &EV_Projectile_BounceSound_Metal, &Projectile::SetBounceSoundMetal },
|
|
{ &EV_Projectile_BounceSound_Hard, &Projectile::SetBounceSoundHard },
|
|
{ &EV_Projectile_BounceSound_Water, &Projectile::SetBounceSoundWater },
|
|
{ &EV_Projectile_BeamCommand, &Projectile::BeamCommand },
|
|
{ &EV_Projectile_UpdateBeam, &Projectile::UpdateBeam },
|
|
{ &EV_Projectile_Explode, &Projectile::Explode },
|
|
{ &EV_Projectile_ImpactMarkShader, &Projectile::SetImpactMarkShader },
|
|
{ &EV_Projectile_ImpactMarkRadius, &Projectile::SetImpactMarkRadius },
|
|
{ &EV_Projectile_ImpactMarkOrientation, &Projectile::SetImpactMarkOrientation },
|
|
{ &EV_Projectile_SetExplosionModel, &Projectile::SetExplosionModel },
|
|
{ &EV_Projectile_SetAddVelocity, &Projectile::SetAddVelocity },
|
|
{ &EV_Projectile_AddOwnerVelocity, &Projectile::AddOwnerVelocity },
|
|
{ &EV_Projectile_HeatSeek, &Projectile::HeatSeek },
|
|
{ &EV_Projectile_Drunk, &Projectile::Drunk },
|
|
{ &EV_Projectile_Prethink, &Projectile::Prethink },
|
|
{ &EV_Projectile_SetCanHitOwner, &Projectile::SetCanHitOwner },
|
|
{ &EV_Projectile_ClearOwner, &Projectile::ClearOwner },
|
|
{ &EV_Projectile_RemoveWhenStopped, &Projectile::RemoveWhenStopped },
|
|
{ &EV_Projectile_ExplodeOnTouch, &Projectile::ExplodeOnTouch },
|
|
{ &EV_Projectile_NoTouchDamage, &Projectile::SetNoTouchDamage },
|
|
{ &EV_Projectile_SetSmashThroughGlass, &Projectile::SetSmashThroughGlass },
|
|
{ &EV_Projectile_SmashThroughGlass, &Projectile::SmashThroughGlassThink },
|
|
{ &EV_Killed, &Projectile::Explode },
|
|
{ &EV_Stop, &Projectile::Stopped },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Projectile::Projectile()
|
|
{
|
|
entflags |= EF_PROJECTILE;
|
|
|
|
if( LoadingSavegame )
|
|
{
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
|
|
m_beam = NULL;
|
|
speed = 0;
|
|
minspeed = 0;
|
|
damage = 0;
|
|
life = 5;
|
|
dmlife = 0;
|
|
knockback = 0;
|
|
dlight_radius = 0;
|
|
dlight_color = Vector( 1, 1, 1 );
|
|
avelocity = Vector( 0, 0, 0 );
|
|
mins = Vector( -1, -1, -1 );
|
|
maxs = Vector( 1, 1, 1 );
|
|
meansofdeath = ( meansOfDeath_t )0;
|
|
projFlags = 0;
|
|
fLastBounceTime = 0;
|
|
gravity = 0;
|
|
impactmarkradius = 10;
|
|
charge_fraction = 1.0;
|
|
target = NULL;
|
|
fDrunk = 0;
|
|
fDrunkRate = 0;
|
|
m_iSmashThroughGlass = 0;
|
|
addownervelocity = qtrue;
|
|
can_hit_owner = false;
|
|
remove_when_stopped = false;
|
|
m_bExplodeOnTouch = false;
|
|
m_bHurtOwnerOnly = false;
|
|
takedamage = DAMAGE_NO;
|
|
owner = ENTITYNUM_NONE;
|
|
edict->r.ownerNum = ENTITYNUM_NONE;
|
|
m_iOwnerTeam = 0;
|
|
m_bHadPlayerOwner = false;
|
|
|
|
// make this shootable but non-solid on the client
|
|
setContents( CONTENTS_SHOOTONLY );
|
|
|
|
//
|
|
// touch triggers by default
|
|
//
|
|
flags |= FL_TOUCH_TRIGGERS;
|
|
}
|
|
|
|
float Projectile::ResolveMinimumDistance
|
|
(
|
|
Entity *potential_target,
|
|
float currmin
|
|
)
|
|
|
|
{
|
|
float currdist;
|
|
float dot;
|
|
Vector angle;
|
|
Vector delta;
|
|
Vector norm;
|
|
float sine = 0.4f;
|
|
|
|
delta = potential_target->centroid - this->origin;
|
|
|
|
norm = delta;
|
|
norm.normalize();
|
|
|
|
// Test if the target is in front of the missile
|
|
dot = norm * orientation[ 0 ];
|
|
if ( dot < 0 )
|
|
{
|
|
return currmin;
|
|
}
|
|
|
|
// Test if we're within the rocket's viewcone (45 degree cone)
|
|
dot = norm * orientation[ 1 ];
|
|
if ( fabs( dot ) > sine )
|
|
{
|
|
return currmin;
|
|
}
|
|
|
|
dot = norm * orientation[ 2 ];
|
|
if ( fabs( dot ) > sine )
|
|
{
|
|
return currmin;
|
|
}
|
|
|
|
currdist = delta.length();
|
|
if ( currdist < currmin )
|
|
{
|
|
currmin = currdist;
|
|
target = potential_target;
|
|
}
|
|
|
|
return currmin;
|
|
}
|
|
|
|
float Projectile::AdjustAngle
|
|
(
|
|
float maxadjust,
|
|
float currangle,
|
|
float targetangle
|
|
)
|
|
|
|
{
|
|
float dangle;
|
|
float magangle;
|
|
|
|
dangle = currangle - targetangle;
|
|
|
|
if ( dangle )
|
|
{
|
|
magangle = ( float )fabs( dangle );
|
|
|
|
while( magangle >= 360.0f )
|
|
{
|
|
magangle -= 360.0f;
|
|
}
|
|
|
|
if ( magangle < maxadjust )
|
|
{
|
|
currangle = targetangle;
|
|
}
|
|
else
|
|
{
|
|
if ( magangle > 180.0f )
|
|
{
|
|
maxadjust = -maxadjust;
|
|
}
|
|
if ( dangle > 0 )
|
|
{
|
|
maxadjust = -maxadjust;
|
|
}
|
|
currangle += maxadjust;
|
|
}
|
|
}
|
|
|
|
while( currangle >= 360.0f )
|
|
{
|
|
currangle -= 360.0f;
|
|
}
|
|
|
|
while( currangle < 0.0f )
|
|
{
|
|
currangle += 360.0f;
|
|
}
|
|
|
|
return currangle;
|
|
}
|
|
|
|
void Projectile::Drunk
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if( fDrunk ) {
|
|
return;
|
|
}
|
|
|
|
fDrunk = ev->GetFloat( 1 );
|
|
fDrunkRate = ev->GetFloat( 2 );
|
|
|
|
PostEvent( EV_Projectile_Prethink, 0 );
|
|
}
|
|
|
|
void Projectile::HeatSeek
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
float mindist;
|
|
Entity *ent;
|
|
trace_t trace;
|
|
Vector delta;
|
|
Vector v;
|
|
int n;
|
|
int i;
|
|
|
|
if ( ( !target ) || ( target == world ) )
|
|
{
|
|
mindist = 8192.0f;
|
|
|
|
n = SentientList.NumObjects();
|
|
for( i = 1; i <= n; i++ )
|
|
{
|
|
ent = SentientList.ObjectAt( i );
|
|
if ( ent->entnum == owner )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( ( ( ent->takedamage != DAMAGE_AIM ) || ( ent->health <= 0 ) ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
trace = G_Trace( this->origin, vec_zero, vec_zero, ent->centroid, this, MASK_SHOT, qfalse, "DrunkMissile::HeatSeek" );
|
|
if ( ( trace.fraction != 1.0 ) && ( trace.ent != ent->edict ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
mindist = ResolveMinimumDistance( ent, mindist );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float angspeed;
|
|
|
|
delta = target->centroid - this->origin;
|
|
v = delta.toAngles();
|
|
|
|
angspeed = 5.0f;
|
|
angles.x = AdjustAngle( angspeed, angles.x, v.x );
|
|
angles.y = AdjustAngle( angspeed, angles.y, v.y );
|
|
angles.z = AdjustAngle( angspeed, angles.z, v.z );
|
|
}
|
|
PostEvent( EV_Projectile_HeatSeek, 0.1f );
|
|
PostEvent( EV_Projectile_Prethink, 0 );
|
|
}
|
|
|
|
void Projectile::Prethink
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Vector end;
|
|
|
|
if( fDrunk >= 0.0f )
|
|
{
|
|
if( fDrunk )
|
|
{
|
|
float rnd1 = G_Random();
|
|
float rnd2 = G_Random();
|
|
|
|
angles += Vector( ( rnd1 + rnd1 - 1.0f ) * fDrunk, ( rnd2 + rnd2 - 1.0f ) * fDrunk, 0 );
|
|
|
|
speed *= 0.98f;
|
|
fDrunk += fDrunkRate;
|
|
if( speed < 500.0f )
|
|
{
|
|
fDrunk = -fDrunk;
|
|
}
|
|
}
|
|
|
|
setAngles( angles );
|
|
velocity = Vector( orientation[ 0 ] ) * speed;
|
|
}
|
|
else
|
|
{
|
|
fDrunk *= 0.80f;
|
|
|
|
if( fDrunk > -1.0f ) {
|
|
fDrunk = -1.0f;
|
|
}
|
|
|
|
//float rnd1 = rand() & 0x7FFF;
|
|
//float rnd2 = rand() & 0x7FFF;
|
|
|
|
//angles += Vector( ( rnd1 * 0.00003f + rnd1 * 0.00003f - 1.0f ) * fDrunk, ( rnd2 * 0.00003f + rnd2 * 0.00003f - 1.0f ) * fDrunk, 0 );
|
|
velocity[ 2 ] -= 0.05f * sv_gravity->integer * 0.15f;
|
|
|
|
angles = velocity.toAngles();
|
|
setAngles( angles );
|
|
}
|
|
|
|
PostEvent( EV_Projectile_Prethink, 0.05f );
|
|
}
|
|
|
|
void Projectile::AddOwnerVelocity
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
addownervelocity = ev->GetBoolean( 1 );
|
|
}
|
|
|
|
void Projectile::SetAddVelocity
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
addvelocity.x = ev->GetFloat( 1 );
|
|
addvelocity.y = ev->GetFloat( 2 );
|
|
addvelocity.z = ev->GetFloat( 3 );
|
|
}
|
|
|
|
void Projectile::SetExplosionModel
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
explosionmodel = ev->GetString( 1 );
|
|
}
|
|
|
|
void Projectile::SetImpactMarkShader
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
impactmarkshader = ev->GetString( 1 );
|
|
}
|
|
|
|
void Projectile::SetImpactMarkRadius
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
impactmarkradius = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Projectile::SetImpactMarkOrientation
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
impactmarkorientation = ev->GetString( 1 );
|
|
}
|
|
|
|
void Projectile::Explode
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Entity *owner;
|
|
Entity *ignoreEnt = NULL;
|
|
|
|
if( !CheckTeams() )
|
|
{
|
|
PostEvent( EV_Remove, EV_REMOVE );
|
|
return;
|
|
}
|
|
|
|
if( ev->NumArgs() == 1 )
|
|
ignoreEnt = ev->GetEntity( 1 );
|
|
|
|
// Get the owner of this projectile
|
|
owner = G_GetEntity( this->owner );
|
|
|
|
// If the owner's not here, make the world the owner
|
|
if( !owner )
|
|
owner = world;
|
|
|
|
if( owner->IsDead() || owner == world )
|
|
weap = NULL;
|
|
|
|
health = 0;
|
|
deadflag = DEAD_DEAD;
|
|
takedamage = DAMAGE_NO;
|
|
|
|
// Spawn an explosion model
|
|
if( explosionmodel.length() )
|
|
{
|
|
// Move the projectile back off the surface a bit so we can see
|
|
// explosion effects.
|
|
Vector dir, v;
|
|
v = velocity;
|
|
v.normalize();
|
|
dir = v;
|
|
v = origin - v * 36;
|
|
setOrigin( v );
|
|
|
|
ExplosionAttack( v, owner, explosionmodel, dir, ignoreEnt, 1.0f, weap, m_bHurtOwnerOnly );
|
|
}
|
|
|
|
CancelEventsOfType( EV_Projectile_UpdateBeam );
|
|
|
|
// Kill the beam
|
|
if( m_beam )
|
|
{
|
|
m_beam->ProcessEvent( EV_Remove );
|
|
m_beam = NULL;
|
|
}
|
|
|
|
// Remove the projectile
|
|
PostEvent( EV_Remove, level.frametime );
|
|
}
|
|
|
|
void Projectile::SetBounceTouch
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
projFlags |= P_BOUNCE_TOUCH;
|
|
setMoveType( MOVETYPE_BOUNCE );
|
|
}
|
|
|
|
void Projectile::SetNoTouchDamage
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
projFlags |= P_NO_TOUCH_DAMAGE;
|
|
}
|
|
|
|
void Projectile::SetSmashThroughGlass
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
m_iSmashThroughGlass = ev->GetInteger( 1 );
|
|
|
|
PostEvent( EV_Projectile_SmashThroughGlass, level.frametime );
|
|
}
|
|
|
|
void Projectile::SmashThroughGlassThink
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if( velocity.length() > m_iSmashThroughGlass )
|
|
{
|
|
Vector vEnd;
|
|
trace_t trace;
|
|
|
|
Entity *ent = G_GetEntity( owner );
|
|
|
|
vEnd = velocity * level.frametime + velocity * level.frametime + origin;
|
|
|
|
trace = G_Trace( origin,
|
|
vec_zero,
|
|
vec_zero,
|
|
vEnd,
|
|
ent,
|
|
MASK_BEAM,
|
|
false,
|
|
"SmashThroughGlassThink" );
|
|
|
|
if( ( trace.ent ) && ( trace.ent->entity != world ) )
|
|
{
|
|
Entity *obj = trace.ent->entity;
|
|
|
|
if( ( trace.surfaceFlags & SURF_GLASS ) && ( obj->takedamage ) )
|
|
{
|
|
obj->Damage( this,
|
|
ent,
|
|
obj->health + 1,
|
|
origin,
|
|
velocity,
|
|
trace.plane.normal,
|
|
knockback,
|
|
0,
|
|
meansofdeath
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
PostEvent( EV_Projectile_SmashThroughGlass, level.frametime );
|
|
}
|
|
|
|
void Projectile::BeamCommand
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
int i;
|
|
|
|
if ( !m_beam )
|
|
{
|
|
m_beam = new FuncBeam;
|
|
|
|
m_beam->setOrigin( this->origin );
|
|
m_beam->Ghost( NULL );
|
|
}
|
|
|
|
Event *beamev = new Event( ev->GetToken( 1 ) );
|
|
|
|
for( i=2; i<=ev->NumArgs(); i++ )
|
|
{
|
|
beamev->AddToken( ev->GetToken( i ) );
|
|
}
|
|
|
|
m_beam->ProcessEvent( beamev );
|
|
PostEvent( EV_Projectile_UpdateBeam, level.frametime );
|
|
}
|
|
|
|
void Projectile::UpdateBeam
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( m_beam )
|
|
{
|
|
m_beam->setOrigin( this->origin );
|
|
PostEvent( EV_Projectile_UpdateBeam, level.frametime );
|
|
}
|
|
}
|
|
|
|
void Projectile::SetBounceSound
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
bouncesound = ev->GetString( 1 );
|
|
}
|
|
|
|
void Projectile::SetBounceSoundMetal
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
bouncesound_metal = ev->GetString( 1 );
|
|
}
|
|
|
|
void Projectile::SetBounceSoundHard
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
bouncesound_hard = ev->GetString( 1 );
|
|
}
|
|
|
|
void Projectile::SetBounceSoundWater
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
bouncesound_water = ev->GetString( 1 );
|
|
}
|
|
|
|
void Projectile::SetChargeLife
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
projFlags |= P_CHARGE_LIFE;
|
|
}
|
|
|
|
void Projectile::SetMinLife
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
minlife = ev->GetFloat( 1 );
|
|
projFlags |= P_CHARGE_LIFE;
|
|
}
|
|
|
|
void Projectile::SetLife
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
life = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Projectile::SetDMLife
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
dmlife = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Projectile::SetSpeed
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
speed = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Projectile::SetMinSpeed
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
minspeed = ev->GetFloat( 1 );
|
|
projFlags |= P_CHARGE_SPEED;
|
|
}
|
|
|
|
void Projectile::SetChargeSpeed
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
projFlags |= P_CHARGE_SPEED;
|
|
}
|
|
|
|
void Projectile::SetAvelocity
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
int i=1;
|
|
int j=0;
|
|
str vel;
|
|
|
|
if ( ev->NumArgs() < 3 )
|
|
{
|
|
warning( "ClientGameCommandManager::SetAngularVelocity", "Expecting at least 3 args for command randvel" );
|
|
}
|
|
|
|
while ( j < 3 )
|
|
{
|
|
vel = ev->GetString( i++ );
|
|
if ( vel == "crandom" )
|
|
{
|
|
avelocity[j++] = ev->GetFloat( i++ ) * crandom();
|
|
}
|
|
else if ( vel == "random" )
|
|
{
|
|
avelocity[j++] = ev->GetFloat( i++ ) * random();
|
|
}
|
|
else
|
|
{
|
|
avelocity[j++] = atof( vel.c_str() );
|
|
}
|
|
}
|
|
}
|
|
|
|
void Projectile::SetDamage
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
damage = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Projectile::SetKnockback
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
knockback = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Projectile::SetDLight
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
dlight_color[0] = ev->GetFloat( 1 );
|
|
dlight_color[1] = ev->GetFloat( 2 );
|
|
dlight_color[2] = ev->GetFloat( 3 );
|
|
dlight_radius = ev->GetFloat( 4 );
|
|
}
|
|
|
|
void Projectile::SetMeansOfDeath
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
meansofdeath = (meansOfDeath_t )MOD_NameToNum( ev->GetString( 1 ) );
|
|
}
|
|
|
|
void Projectile::DoDecal
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if( impactmarkshader.length() )
|
|
{
|
|
Decal *decal = new Decal;
|
|
decal->setShader( impactmarkshader );
|
|
decal->setOrigin( level.impact_trace.endpos );
|
|
decal->setDirection( level.impact_trace.plane.normal );
|
|
decal->setOrientation( impactmarkorientation );
|
|
decal->setRadius( impactmarkradius );
|
|
}
|
|
}
|
|
|
|
void Projectile::Touch
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Entity *other;
|
|
Entity *owner;
|
|
str realname;
|
|
|
|
// Other is what got hit
|
|
other = ev->GetEntity( 1 );
|
|
assert( other );
|
|
|
|
// Don't touch teleporters
|
|
if( other->isSubclassOf( Teleporter ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Can't hit yourself with a projectile
|
|
if( other->entnum == this->owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove it if we hit the sky
|
|
if( HitSky() )
|
|
{
|
|
PostEvent( EV_Remove, 0 );
|
|
return;
|
|
}
|
|
|
|
if( !CheckTeams() )
|
|
{
|
|
PostEvent( EV_Remove, EV_REMOVE );
|
|
return;
|
|
}
|
|
|
|
// Bouncy Projectile
|
|
if( ( projFlags & P_BOUNCE_TOUCH ) )
|
|
{
|
|
str snd;
|
|
|
|
if( level.time - fLastBounceTime < 0.1f )
|
|
{
|
|
fLastBounceTime = level.time;
|
|
return;
|
|
}
|
|
|
|
if( level.impact_trace.surfaceFlags & CONTENTS_WATER || ( gi.PointContents( level.impact_trace.endpos, 0 ) & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) )
|
|
{
|
|
if( bouncesound_water.length() )
|
|
this->Sound( bouncesound_water, CHAN_BODY );
|
|
}
|
|
else
|
|
{
|
|
if( bouncesound_metal.length() )
|
|
{
|
|
snd = bouncesound_metal;
|
|
}
|
|
else if( bouncesound_hard.length() )
|
|
{
|
|
snd = bouncesound_hard;
|
|
}
|
|
else
|
|
{
|
|
snd = bouncesound;
|
|
}
|
|
|
|
int flags = level.impact_trace.surfaceFlags;
|
|
|
|
if( flags & SURF_MUD )
|
|
{
|
|
if( bouncesound.length() )
|
|
Sound( bouncesound, CHAN_BODY );
|
|
}
|
|
else if( flags & SURF_ROCK )
|
|
{
|
|
if( bouncesound_hard.length() )
|
|
Sound( bouncesound_hard, CHAN_BODY );
|
|
}
|
|
else if( flags & SURF_GRILL )
|
|
{
|
|
if( bouncesound_metal.length() )
|
|
Sound( bouncesound_metal, CHAN_BODY );
|
|
}
|
|
else if( flags & SURF_WOOD )
|
|
{
|
|
if( bouncesound_hard.length() )
|
|
Sound( bouncesound_hard, CHAN_BODY );
|
|
}
|
|
else if( flags & SURF_METAL )
|
|
{
|
|
if( bouncesound_metal.length() )
|
|
Sound( bouncesound_metal, CHAN_BODY );
|
|
}
|
|
else if( flags & SURF_GLASS )
|
|
{
|
|
if( bouncesound_hard.length() )
|
|
Sound( bouncesound_hard, CHAN_BODY );
|
|
}
|
|
else
|
|
{
|
|
if( bouncesound.length() )
|
|
Sound( bouncesound, CHAN_BODY );
|
|
}
|
|
}
|
|
|
|
BroadcastAIEvent( AI_EVENT_WEAPON_IMPACT );
|
|
return;
|
|
}
|
|
|
|
if( !m_bExplodeOnTouch && damage == 0.0f )
|
|
return;
|
|
|
|
// Get the owner of this projectile
|
|
owner = G_GetEntity( this->owner );
|
|
|
|
// If the owner's not here, make the world the owner
|
|
if( !owner )
|
|
owner = world;
|
|
|
|
if( owner->IsDead() || owner == world )
|
|
weap = NULL;
|
|
|
|
// Damage the thing that got hit
|
|
if( other->takedamage )
|
|
{
|
|
other->Damage( this,
|
|
owner,
|
|
damage,
|
|
origin,
|
|
velocity,
|
|
level.impact_trace.plane.normal,
|
|
knockback,
|
|
0,
|
|
meansofdeath
|
|
);
|
|
}
|
|
|
|
if( !g_gametype->integer && weap )
|
|
{
|
|
if( other->IsSubclassOfPlayer() ||
|
|
other->IsSubclassOfVehicle() ||
|
|
other->IsSubclassOfVehicleTank() ||
|
|
other->isSubclassOf( VehicleCollisionEntity ) )
|
|
{
|
|
weap->m_iNumHits++;
|
|
weap->m_iNumTorsoShots++;
|
|
|
|
if( weap->IsSubclassOfVehicleTurretGun() )
|
|
{
|
|
VehicleTurretGun *t = ( VehicleTurretGun * )weap.Pointer();
|
|
Player *p = ( Player * )t->GetRemoteOwner();
|
|
|
|
if( p && p->IsSubclassOfPlayer() )
|
|
{
|
|
p->m_iNumHits++;
|
|
p->m_iNumTorsoShots++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !m_bExplodeOnTouch )
|
|
return;
|
|
|
|
// Make the projectile not solid
|
|
setSolidType( SOLID_NOT );
|
|
setMoveType( MOVETYPE_NONE );
|
|
hideModel();
|
|
|
|
// Do a decal
|
|
DoDecal();
|
|
|
|
BroadcastAIEvent( AI_EVENT_WEAPON_FIRE );
|
|
|
|
// Remove the projectile
|
|
PostEvent( EV_Remove, 0 );
|
|
|
|
// Call the explosion event
|
|
Event *explEv;
|
|
explEv = new Event( EV_Projectile_Explode );
|
|
explEv->AddEntity( other );
|
|
ProcessEvent( explEv );
|
|
}
|
|
|
|
void Projectile::SetCanHitOwner
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
can_hit_owner = true;
|
|
}
|
|
|
|
void Projectile::ClearOwner
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
this->owner = ENTITYNUM_NONE;
|
|
edict->r.ownerNum = ENTITYNUM_NONE;
|
|
}
|
|
|
|
void Projectile::RemoveWhenStopped
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
remove_when_stopped = true;
|
|
}
|
|
|
|
void Projectile::ExplodeOnTouch
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
m_bExplodeOnTouch = true;
|
|
}
|
|
|
|
void Projectile::Stopped
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( remove_when_stopped )
|
|
PostEvent( EV_Remove, 0 );
|
|
}
|
|
|
|
bool Projectile::CheckTeams
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
Player *pOwner = ( Player * )m_pOwnerPtr.Pointer();
|
|
|
|
if( !pOwner )
|
|
{
|
|
// Owner disconnected
|
|
if( m_bHadPlayerOwner )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if( pOwner->IsSubclassOfPlayer() )
|
|
{
|
|
if( ( ( m_iOwnerTeam != pOwner->GetTeam() && g_gametype->integer >= GT_TEAM ) || pOwner->GetTeam() <= TEAM_SPECTATOR ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if( pOwner->IsSubclassOfSentient() )
|
|
{
|
|
if( m_iOwnerTeam != pOwner->m_Team )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Projectile::SetOwner
|
|
(
|
|
Entity *owner
|
|
)
|
|
|
|
{
|
|
this->owner = owner->entnum;
|
|
m_pOwnerPtr = owner;
|
|
|
|
if( owner->IsSubclassOfPlayer() )
|
|
{
|
|
Player *p = ( Player * )owner;
|
|
|
|
m_iOwnerTeam = p->GetTeam();
|
|
m_bHadPlayerOwner = true;
|
|
}
|
|
else if( owner->IsSubclassOfSentient() )
|
|
{
|
|
Sentient *s = ( Sentient * )owner;
|
|
m_iOwnerTeam = s->m_Team;
|
|
}
|
|
}
|
|
|
|
Event EV_Explosion_Radius
|
|
(
|
|
"radius",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"projectileRadius",
|
|
"set the radius for the explosion",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Explosion_ConstantDamage
|
|
(
|
|
"constantdamage",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Makes the explosion do constant damage over the radius",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Explosion_DamageEveryFrame
|
|
(
|
|
"damageeveryframe",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Makes the explosion damage every frame",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Explosion_DamageAgain
|
|
(
|
|
"damageagain",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"This event is generated each frame if explosion is set to damage each frame",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Explosion_Flash
|
|
(
|
|
"flash",
|
|
EV_DEFAULT,
|
|
"fffff",
|
|
"time r g b radius",
|
|
"Flash player screens",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Explosion_RadiusDamage
|
|
(
|
|
"radiusdamage",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"radiusDamage",
|
|
"set the radius damage an explosion does",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Explosion_Effect
|
|
(
|
|
"explosioneffect",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"explosiontype",
|
|
"Make an explosionType explosion effect",
|
|
EV_NORMAL
|
|
);
|
|
|
|
|
|
CLASS_DECLARATION( Projectile, Explosion, NULL )
|
|
{
|
|
{ &EV_Explosion_Radius, &Explosion::SetRadius },
|
|
{ &EV_Explosion_ConstantDamage, &Explosion::SetConstantDamage },
|
|
{ &EV_Explosion_DamageEveryFrame, &Explosion::SetDamageEveryFrame },
|
|
{ &EV_Explosion_DamageAgain, &Explosion::DamageAgain },
|
|
{ &EV_Explosion_Flash, &Explosion::SetFlash },
|
|
{ &EV_Explosion_RadiusDamage, &Explosion::SetRadiusDamage },
|
|
{ &EV_Explosion_Effect, &Explosion::MakeExplosionEffect },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Explosion::Explosion()
|
|
{
|
|
if( LoadingSavegame )
|
|
{
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
|
|
radius = 0;
|
|
constant_damage = false;
|
|
damage_every_frame = false;
|
|
flash_time = 0;
|
|
flash_type = 0;
|
|
owner = ENTITYNUM_NONE;
|
|
hurtOwnerOnly = false;
|
|
}
|
|
|
|
void Explosion::SetFlash
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
flash_time = ev->GetFloat( 1 );
|
|
flash_r = ev->GetFloat( 2 );
|
|
flash_g = ev->GetFloat( 3 );
|
|
flash_b = ev->GetFloat( 4 );
|
|
flash_a = ev->GetFloat( 5 );
|
|
flash_radius = ev->GetFloat( 6 );
|
|
flash_type = 0;
|
|
|
|
if ( ev->NumArgs() > 6 )
|
|
{
|
|
str t = ev->GetString( 7 );
|
|
|
|
if ( !t.icmp( "addblend" ) )
|
|
flash_type = 1;
|
|
else if ( !t.icmp( "alphablend" ) )
|
|
flash_type = 0;
|
|
}
|
|
}
|
|
|
|
void Explosion::SetRadius
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
radius = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Explosion::SetRadiusDamage
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
radius_damage = ev->GetFloat( 1 );
|
|
}
|
|
|
|
void Explosion::SetConstantDamage
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
constant_damage = true;
|
|
}
|
|
|
|
void Explosion::SetDamageEveryFrame
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
damage_every_frame = true;
|
|
}
|
|
|
|
void Explosion::DamageAgain
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Entity *owner_ent;
|
|
Weapon *weapon;
|
|
float dmg;
|
|
float rad;
|
|
|
|
if( !CheckTeams() )
|
|
{
|
|
PostEvent( EV_Remove, EV_REMOVE );
|
|
return;
|
|
}
|
|
|
|
owner_ent = G_GetEntity( owner );
|
|
|
|
if( !owner_ent )
|
|
owner_ent = world;
|
|
|
|
weapon = weap;
|
|
|
|
if( owner_ent == world || owner_ent->IsDead() )
|
|
{
|
|
weap = NULL;
|
|
}
|
|
|
|
dmg = radius_damage;
|
|
rad = radius;
|
|
|
|
if( rad == 0.0f )
|
|
{
|
|
rad = radius_damage + 60.0f;
|
|
}
|
|
|
|
RadiusDamage( origin, this, owner_ent, dmg, NULL, meansofdeath, rad, knockback, constant_damage, weapon, false );
|
|
|
|
PostEvent( EV_Explosion_DamageAgain, level.frametime );
|
|
}
|
|
|
|
void Explosion::MakeExplosionEffect
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
str sEffect = ev->GetString( 1 );
|
|
|
|
gi.SetBroadcastAll();
|
|
|
|
if( !sEffect.icmp( "grenade" ) )
|
|
{
|
|
gi.MSG_StartCGM( 12 );
|
|
}
|
|
else
|
|
{
|
|
gi.MSG_StartCGM( 13 );
|
|
}
|
|
|
|
gi.MSG_WriteCoord( origin[ 0 ] );
|
|
gi.MSG_WriteCoord( origin[ 1 ] );
|
|
gi.MSG_WriteCoord( origin[ 2 ] );
|
|
gi.MSG_EndCGM();
|
|
}
|
|
|
|
Projectile *ProjectileAttack
|
|
(
|
|
Vector start,
|
|
Vector dir,
|
|
Entity *owner,
|
|
str projectileModel,
|
|
float fraction,
|
|
float real_speed,
|
|
Weapon *weap
|
|
)
|
|
|
|
{
|
|
Event *ev;
|
|
Projectile *proj = NULL;
|
|
float newspeed, newlife;
|
|
SpawnArgs args;
|
|
Entity *obj;
|
|
float dot = 0;
|
|
|
|
if( !projectileModel.length() )
|
|
{
|
|
gi.DPrintf( "ProjectileAttack : No model specified for ProjectileAttack" );
|
|
return NULL;
|
|
}
|
|
|
|
args.setArg( "model", projectileModel );
|
|
obj = ( Entity * )args.Spawn();
|
|
|
|
if( !obj )
|
|
{
|
|
gi.DPrintf( "projectile model '%s' not found\n", projectileModel.c_str() );
|
|
return NULL;
|
|
}
|
|
|
|
if( obj->IsSubclassOfProjectile() )
|
|
proj = ( Projectile * )obj;
|
|
else
|
|
gi.DPrintf( "%s is not of class projectile\n", projectileModel.c_str() );
|
|
|
|
if( !proj )
|
|
return NULL;
|
|
|
|
// Create a new projectile entity and set it off
|
|
proj->setModel( projectileModel );
|
|
proj->setMoveType( MOVETYPE_BOUNCE );
|
|
proj->ProcessInitCommands();
|
|
proj->SetOwner( owner );
|
|
proj->edict->r.ownerNum = owner->entnum;
|
|
proj->angles = dir.toAngles();
|
|
proj->charge_fraction = fraction;
|
|
|
|
if( proj->projFlags & P_CHARGE_SPEED )
|
|
{
|
|
newspeed = proj->speed * fraction;
|
|
|
|
if( newspeed < proj->minspeed )
|
|
newspeed = proj->minspeed;
|
|
}
|
|
else
|
|
{
|
|
newspeed = proj->speed;
|
|
}
|
|
|
|
if( real_speed )
|
|
newspeed = real_speed;
|
|
|
|
if( proj->addownervelocity )
|
|
{
|
|
dot = DotProduct( owner->velocity, dir );
|
|
if( dot < 0 )
|
|
dot = 0;
|
|
}
|
|
|
|
proj->velocity = dir * ( newspeed + dot );
|
|
proj->velocity += proj->addvelocity;
|
|
proj->setAngles( proj->angles );
|
|
proj->setSolidType( SOLID_BBOX );
|
|
|
|
proj->edict->clipmask = MASK_PROJECTILE;
|
|
|
|
proj->setSize( proj->mins, proj->maxs );
|
|
proj->setOrigin( start );
|
|
proj->origin.copyTo( proj->edict->s.origin2 );
|
|
|
|
if( proj->m_beam )
|
|
{
|
|
proj->m_beam->setOrigin( start );
|
|
proj->m_beam->origin.copyTo( proj->m_beam->edict->s.origin2 );
|
|
}
|
|
|
|
if( proj->dlight_radius )
|
|
{
|
|
G_SetConstantLight( &proj->edict->s.constantLight,
|
|
&proj->dlight_color[ 0 ],
|
|
&proj->dlight_color[ 1 ],
|
|
&proj->dlight_color[ 2 ],
|
|
&proj->dlight_radius
|
|
);
|
|
}
|
|
|
|
// Calc the life of the projectile
|
|
if( proj->projFlags & P_CHARGE_LIFE )
|
|
{
|
|
if( g_gametype->integer && proj->dmlife )
|
|
newlife = proj->dmlife * fraction;
|
|
else
|
|
newlife = proj->life * fraction;
|
|
|
|
if( newlife < proj->minlife )
|
|
newlife = proj->minlife;
|
|
}
|
|
else
|
|
{
|
|
if( g_gametype->integer && proj->dmlife )
|
|
newlife = proj->dmlife;
|
|
else
|
|
newlife = proj->life;
|
|
}
|
|
|
|
// Remove the projectile after it's life expires
|
|
ev = new Event( EV_Projectile_Explode );
|
|
proj->PostEvent( ev, newlife );
|
|
|
|
proj->NewAnim( "idle" );
|
|
|
|
// If can hit owner clear the owner of this projectile in a second
|
|
|
|
if( proj->can_hit_owner )
|
|
proj->PostEvent( EV_Projectile_ClearOwner, 1 );
|
|
|
|
if( !g_gametype->integer )
|
|
{
|
|
if( weap )
|
|
{
|
|
weap->m_iNumShotsFired++;
|
|
if( owner->IsSubclassOfPlayer() &&
|
|
weap->IsSubclassOfTurretGun() )
|
|
{
|
|
Player *p = ( Player * )owner;
|
|
p->m_iNumShotsFired++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return proj;
|
|
}
|
|
|
|
float BulletAttack
|
|
(
|
|
Vector start,
|
|
Vector vBarrel,
|
|
Vector dir,
|
|
Vector right,
|
|
Vector up,
|
|
float range,
|
|
float damage,
|
|
float knockback,
|
|
int dflags,
|
|
int meansofdeath,
|
|
Vector spread,
|
|
int count,
|
|
Entity *owner,
|
|
int iTracerFrequency,
|
|
int *piTracerCount,
|
|
Weapon *weap
|
|
)
|
|
|
|
{
|
|
Vector vDir;
|
|
Vector vTmpEnd;
|
|
Vector vTraceStart;
|
|
Vector vTraceEnd;
|
|
int i;
|
|
int iTravelDist;
|
|
trace_t trace;
|
|
Entity *ent;
|
|
//Entity *tmpSkipEnt;
|
|
float damage_total = 0;
|
|
float original_value;
|
|
qboolean bLargeBullet;
|
|
qboolean bBulletDone;
|
|
int iContinueCount;
|
|
float vEndArray[ 64 ][ 3 ];
|
|
int iTracerCount = 0;
|
|
static int iNumHit;
|
|
|
|
iNumHit = 0;
|
|
bLargeBullet = damage >= 41.0f;
|
|
|
|
if( count > 63 ) {
|
|
count = 63;
|
|
}
|
|
|
|
if( !owner || owner->IsDead() || owner == world ) {
|
|
weap = NULL;
|
|
}
|
|
|
|
for( i = 0; i < count; i++ )
|
|
{
|
|
vTraceEnd = start + ( dir * range ) +
|
|
( right * grandom() * spread.x ) +
|
|
( up * grandom() * spread.y );
|
|
|
|
vTmpEnd = vTraceEnd - start;
|
|
|
|
VectorNormalizeFast( vTmpEnd );
|
|
|
|
iContinueCount = 0;
|
|
iTravelDist = 0;
|
|
bBulletDone = qfalse;
|
|
|
|
while( !bBulletDone && iTravelDist < 9216 )
|
|
{
|
|
iTravelDist += 9216;
|
|
vTraceEnd = start + vTmpEnd * iTravelDist;
|
|
|
|
memset( &trace, 0, sizeof( trace_t ) );
|
|
|
|
while( trace.fraction < 1.0f )
|
|
{
|
|
trace = G_Trace( start, vec_zero, vec_zero, vTraceEnd, owner, MASK_SHOT, false, "BulletAttack", true );
|
|
|
|
if( trace.ent )
|
|
{
|
|
ent = trace.ent->entity;
|
|
|
|
if( ent != world )
|
|
{
|
|
|
|
if( trace.ent->entity->takedamage ) {
|
|
break;
|
|
}
|
|
|
|
if( ent->edict->solid == 3 && trace.shaderNum >= 0 )
|
|
{
|
|
gi.SetBroadcastVisible( trace.endpos, 0 );
|
|
gi.MSG_StartCGM( 6 );
|
|
gi.MSG_WriteCoord( trace.endpos[ 0 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 1 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 2 ] );
|
|
gi.MSG_WriteDir( trace.plane.normal );
|
|
gi.MSG_WriteBits( bLargeBullet, 1 );
|
|
gi.MSG_EndCGM();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent = NULL;
|
|
}
|
|
|
|
if( trace.fraction < 1.0f )
|
|
{
|
|
if( trace.surfaceFlags & ( SURF_SNOW | SURF_FOLIAGE | SURF_GLASS | SURF_PUDDLE | SURF_PAPER ) ||
|
|
trace.contents & ( CONTENTS_NOTTEAM1 | CONTENTS_WATER ) ||
|
|
trace.startsolid ||
|
|
( bLargeBullet && trace.ent && !trace.ent->r.bmodel && trace.ent->entity->takedamage ))
|
|
{
|
|
if( iContinueCount <= 4 )
|
|
{
|
|
vTraceEnd = vTmpEnd + trace.endpos;
|
|
|
|
if( g_showbullettrace->integer )
|
|
{
|
|
G_DebugLine( start, vTraceEnd, 1, 1, 1, 1 );
|
|
}
|
|
|
|
iContinueCount++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
trace.fraction = 1.0f;
|
|
bBulletDone = qtrue;
|
|
}
|
|
|
|
if( !g_gametype->integer && !iNumHit && ent->IsSubclassOfPlayer() && weap )
|
|
{
|
|
weap->m_iNumHits++;
|
|
switch( trace.location )
|
|
{
|
|
case LOCATION_HEAD:
|
|
case LOCATION_HELMET:
|
|
case LOCATION_NECK:
|
|
weap->m_iNumHeadShots++;
|
|
break;
|
|
case LOCATION_PELVIS:
|
|
weap->m_iNumGroinShots++;
|
|
break;
|
|
case LOCATION_R_ARM_UPPER:
|
|
case LOCATION_R_ARM_LOWER:
|
|
case LOCATION_R_HAND:
|
|
weap->m_iNumRightArmShots++;
|
|
break;
|
|
case LOCATION_L_ARM_UPPER:
|
|
case LOCATION_L_ARM_LOWER:
|
|
case LOCATION_L_HAND:
|
|
weap->m_iNumLeftArmShots++;
|
|
break;
|
|
case LOCATION_R_LEG_UPPER:
|
|
case LOCATION_R_LEG_LOWER:
|
|
case LOCATION_R_FOOT:
|
|
weap->m_iNumRightLegShots++;
|
|
break;
|
|
case LOCATION_L_LEG_UPPER:
|
|
case LOCATION_L_LEG_LOWER:
|
|
case LOCATION_L_FOOT:
|
|
weap->m_iNumLeftLegShots++;
|
|
break;
|
|
default:
|
|
weap->m_iNumTorsoShots++;
|
|
break;
|
|
}
|
|
|
|
if( owner && owner->IsSubclassOfPlayer() && weap && weap->IsSubclassOfTurretGun() )
|
|
{
|
|
Player *p = ( Player * )owner;
|
|
|
|
p->m_iNumHits++;
|
|
|
|
switch( trace.location )
|
|
{
|
|
case LOCATION_HEAD:
|
|
case LOCATION_HELMET:
|
|
case LOCATION_NECK:
|
|
p->m_iNumHeadShots++;
|
|
break;
|
|
case LOCATION_PELVIS:
|
|
p->m_iNumGroinShots++;
|
|
break;
|
|
case LOCATION_R_ARM_UPPER:
|
|
case LOCATION_R_ARM_LOWER:
|
|
case LOCATION_R_HAND:
|
|
p->m_iNumRightArmShots++;
|
|
break;
|
|
case LOCATION_L_ARM_UPPER:
|
|
case LOCATION_L_ARM_LOWER:
|
|
case LOCATION_L_HAND:
|
|
p->m_iNumLeftArmShots++;
|
|
break;
|
|
case LOCATION_R_LEG_UPPER:
|
|
case LOCATION_R_LEG_LOWER:
|
|
case LOCATION_R_FOOT:
|
|
p->m_iNumRightLegShots++;
|
|
break;
|
|
case LOCATION_L_LEG_UPPER:
|
|
case LOCATION_L_LEG_LOWER:
|
|
case LOCATION_L_FOOT:
|
|
p->m_iNumLeftLegShots++;
|
|
break;
|
|
default:
|
|
p->m_iNumTorsoShots++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ent && ent->takedamage )
|
|
{
|
|
/* if ( !ent->deadflag )
|
|
damage_total += damage;
|
|
|
|
if ( ent->IsSubclassOfSentient() )
|
|
{
|
|
Sentient *sent = (Sentient *)ent;
|
|
|
|
if ( sent->Immune( meansofdeath ) )
|
|
damage_total = 0;
|
|
} */
|
|
|
|
// Get the original value of the victims health or water
|
|
|
|
original_value = ent->health;
|
|
|
|
ent->Damage( NULL,
|
|
owner,
|
|
damage,
|
|
trace.endpos,
|
|
dir,
|
|
trace.plane.normal,
|
|
knockback,
|
|
dflags,
|
|
meansofdeath,
|
|
trace.location );
|
|
|
|
// Get the new value of the victims health or water
|
|
|
|
damage_total += original_value - ent->health;
|
|
}
|
|
|
|
if( trace.contents > 0 && trace.surfaceFlags & 0xFFFE000 )
|
|
{
|
|
gi.SetBroadcastVisible( trace.endpos, NULL );
|
|
gi.MSG_StartCGM( 6 );
|
|
gi.MSG_WriteCoord( trace.endpos[ 0 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 1 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 2 ] );
|
|
gi.MSG_WriteDir( trace.plane.normal );
|
|
gi.MSG_WriteBits( bLargeBullet, 1 );
|
|
gi.MSG_EndCGM();
|
|
}
|
|
else if( trace.location >= 0 && ent->IsSubclassOfPlayer() )
|
|
{
|
|
gi.SetBroadcastVisible( trace.endpos, NULL );
|
|
gi.MSG_StartCGM( 7 );
|
|
gi.MSG_WriteCoord( trace.endpos[ 0 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 1 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 2 ] );
|
|
gi.MSG_WriteDir( trace.plane.normal );
|
|
gi.MSG_WriteBits( bLargeBullet, 1 );
|
|
gi.MSG_EndCGM();
|
|
}
|
|
}
|
|
|
|
VectorCopy( vTraceEnd, vEndArray[ i ] );
|
|
|
|
if( iTracerFrequency && piTracerCount )
|
|
{
|
|
( *piTracerCount )++;
|
|
|
|
if( *piTracerCount == iTracerFrequency )
|
|
{
|
|
iTracerCount++;
|
|
*piTracerCount = 0;
|
|
}
|
|
}
|
|
|
|
// Draw a debug trace line to show bullet fire
|
|
if ( g_showbullettrace->integer )
|
|
G_DebugLine( start, vTraceEnd, 1, 1, 1, 1 );
|
|
}
|
|
|
|
if( !g_gametype->integer )
|
|
{
|
|
if( weap )
|
|
{
|
|
weap->m_iNumShotsFired++;
|
|
if( owner && owner->IsSubclassOfPlayer() && weap->IsSubclassOfTurretGun() )
|
|
{
|
|
Player *p = ( Player * )owner;
|
|
p->m_iNumShotsFired++;
|
|
}
|
|
}
|
|
}
|
|
|
|
gi.SetBroadcastVisible( start, trace.endpos );
|
|
|
|
if( count == 1 )
|
|
{
|
|
if( iTracerCount )
|
|
{
|
|
gi.MSG_StartCGM( 1 );
|
|
|
|
gi.MSG_WriteCoord( vBarrel[ 0 ] );
|
|
gi.MSG_WriteCoord( vBarrel[ 1 ] );
|
|
gi.MSG_WriteCoord( vBarrel[ 2 ] );
|
|
}
|
|
else
|
|
{
|
|
gi.MSG_StartCGM( 2 );
|
|
}
|
|
|
|
gi.MSG_WriteCoord( start[ 0 ] );
|
|
gi.MSG_WriteCoord( start[ 1 ] );
|
|
gi.MSG_WriteCoord( start[ 2 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 0 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 1 ] );
|
|
gi.MSG_WriteCoord( trace.endpos[ 2 ] );
|
|
gi.MSG_WriteBits( bLargeBullet, 1 );
|
|
}
|
|
else
|
|
{
|
|
if( iTracerCount )
|
|
{
|
|
gi.MSG_StartCGM( 3 );
|
|
|
|
gi.MSG_WriteCoord( vBarrel[ 0 ] );
|
|
gi.MSG_WriteCoord( vBarrel[ 1 ] );
|
|
gi.MSG_WriteCoord( vBarrel[ 2 ] );
|
|
|
|
if( iTracerCount > 63 )
|
|
iTracerCount = 63;
|
|
|
|
gi.MSG_WriteBits( iTracerCount, 6 );
|
|
}
|
|
else
|
|
{
|
|
gi.MSG_StartCGM( 4 );
|
|
}
|
|
|
|
gi.MSG_WriteCoord( start[ 0 ] );
|
|
gi.MSG_WriteCoord( start[ 1 ] );
|
|
gi.MSG_WriteCoord( start[ 2 ] );
|
|
gi.MSG_WriteBits( bLargeBullet, 1 );
|
|
gi.MSG_WriteBits( count, 6 );
|
|
|
|
for( int i = count; i > 0; i-- )
|
|
{
|
|
gi.MSG_WriteCoord( vEndArray[ i ][ 0 ] );
|
|
gi.MSG_WriteCoord( vEndArray[ i ][ 1 ] );
|
|
gi.MSG_WriteCoord( vEndArray[ i ][ 2 ] );
|
|
}
|
|
}
|
|
|
|
gi.MSG_EndCGM();
|
|
|
|
if( damage_total > 0 )
|
|
return damage_total;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void FakeBulletAttack
|
|
(
|
|
Vector start,
|
|
Vector vBarrel,
|
|
Vector dir,
|
|
Vector right,
|
|
Vector up,
|
|
float range,
|
|
float damage,
|
|
Vector spread,
|
|
int count,
|
|
Entity *owner,
|
|
int iTracerFrequency,
|
|
int *piTracerCount
|
|
)
|
|
|
|
{
|
|
Vector vDir;
|
|
Vector vTraceEnd;
|
|
int i;
|
|
qboolean bLargeBullet = damage >= 41.0f;
|
|
|
|
for( i = 0; i < count; i++ )
|
|
{
|
|
vTraceEnd = start + ( dir * range ) +
|
|
( right * grandom() * spread.x ) +
|
|
( up * grandom() * spread.y );
|
|
|
|
vDir = vTraceEnd - start;
|
|
VectorNormalize( vDir );
|
|
vTraceEnd = start + vDir * 9216.0f;
|
|
|
|
gi.SetBroadcastVisible( start, vTraceEnd );
|
|
if( iTracerFrequency && piTracerCount )
|
|
{
|
|
( *piTracerCount )++;
|
|
if( *piTracerCount == iTracerFrequency )
|
|
{
|
|
gi.MSG_StartCGM( 25 );
|
|
gi.MSG_WriteCoord( vBarrel[ 0 ] );
|
|
gi.MSG_WriteCoord( vBarrel[ 1 ] );
|
|
gi.MSG_WriteCoord( vBarrel[ 2 ] );
|
|
*piTracerCount = 0;
|
|
}
|
|
else
|
|
{
|
|
gi.MSG_StartCGM( 26 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.MSG_StartCGM( 26 );
|
|
}
|
|
|
|
gi.MSG_WriteCoord( start[ 0 ] );
|
|
gi.MSG_WriteCoord( start[ 1 ] );
|
|
gi.MSG_WriteCoord( start[ 2 ] );
|
|
gi.MSG_WriteCoord( vTraceEnd[ 0 ] );
|
|
gi.MSG_WriteCoord( vTraceEnd[ 1 ] );
|
|
gi.MSG_WriteCoord( vTraceEnd[ 2 ] );
|
|
gi.MSG_WriteBits( bLargeBullet, 1 );
|
|
gi.MSG_EndCGM();
|
|
}
|
|
}
|
|
|
|
void ClickItemAttack
|
|
(
|
|
Vector vStart,
|
|
Vector vForward,
|
|
float fRange,
|
|
Entity *pOwner
|
|
)
|
|
|
|
{
|
|
Vector vEnd;
|
|
trace_t trace;
|
|
|
|
vEnd = vStart + vForward * fRange;
|
|
|
|
trace = G_Trace(
|
|
vStart,
|
|
vec_zero,
|
|
vec_zero,
|
|
vEnd,
|
|
pOwner,
|
|
MASK_ALL,
|
|
qfalse,
|
|
"ClickItemAttack" );
|
|
|
|
if( g_showbullettrace->integer )
|
|
G_DebugLine( vStart, vEnd, 1, 1, 1, 1 );
|
|
|
|
if( trace.entityNum != ENTITYNUM_NONE && trace.ent &&
|
|
trace.ent->entity && trace.ent->entity->isSubclassOf( TriggerClickItem ) )
|
|
{
|
|
Event *ev = new Event( EV_Activate );
|
|
ev->AddEntity( pOwner );
|
|
trace.ent->entity->PostEvent( ev, 0 );
|
|
}
|
|
else
|
|
{
|
|
ScriptThreadLabel failThread;
|
|
|
|
// Try to execute a fail thread
|
|
if( failThread.TrySet( "clickitem_fail" ) )
|
|
failThread.Execute();
|
|
}
|
|
}
|
|
|
|
Projectile *HeavyAttack
|
|
(
|
|
Vector start,
|
|
Vector dir,
|
|
str projectileModel,
|
|
float real_speed,
|
|
Entity *owner,
|
|
Weapon *weap
|
|
)
|
|
|
|
{
|
|
Event *ev;
|
|
Projectile *proj = NULL;
|
|
float newspeed, newlife;
|
|
SpawnArgs args;
|
|
Entity *obj;
|
|
float dot = 0;
|
|
|
|
if( !projectileModel.length() )
|
|
{
|
|
gi.DPrintf( "ProjectileAttack : No model specified for ProjectileAttack" );
|
|
return NULL;
|
|
}
|
|
|
|
args.setArg( "model", projectileModel );
|
|
obj = ( Entity * )args.Spawn();
|
|
|
|
if( !obj )
|
|
{
|
|
gi.DPrintf( "projectile model '%s' not found\n", projectileModel.c_str() );
|
|
return NULL;
|
|
}
|
|
|
|
if( obj->IsSubclassOfProjectile() )
|
|
proj = ( Projectile * )obj;
|
|
else
|
|
gi.DPrintf( "%s is not of class projectile\n", projectileModel.c_str() );
|
|
|
|
if( !proj )
|
|
return NULL;
|
|
|
|
// Create a new projectile entity and set it off
|
|
proj->setModel( projectileModel );
|
|
proj->setMoveType( MOVETYPE_BOUNCE );
|
|
proj->ProcessInitCommands();
|
|
proj->SetOwner( owner );
|
|
proj->edict->r.ownerNum = owner->entnum;
|
|
proj->angles = dir.toAngles();
|
|
proj->charge_fraction = 1.0f;
|
|
|
|
newspeed = real_speed;
|
|
if( real_speed == 0.0f )
|
|
newspeed = proj->speed;
|
|
|
|
if( proj->addownervelocity )
|
|
{
|
|
dot = DotProduct( owner->velocity, dir );
|
|
if( dot < 0 )
|
|
dot = 0;
|
|
}
|
|
|
|
proj->velocity = dir * ( newspeed + dot );
|
|
proj->velocity += proj->addvelocity;
|
|
proj->setAngles( proj->angles );
|
|
proj->setSolidType( SOLID_BBOX );
|
|
|
|
proj->edict->clipmask = MASK_PROJECTILE;
|
|
|
|
proj->setSize( proj->mins, proj->maxs );
|
|
proj->setOrigin( start );
|
|
proj->origin.copyTo( proj->edict->s.origin2 );
|
|
|
|
if( proj->m_beam )
|
|
{
|
|
proj->m_beam->setOrigin( start );
|
|
proj->m_beam->origin.copyTo( proj->m_beam->edict->s.origin2 );
|
|
}
|
|
|
|
if( proj->dlight_radius )
|
|
{
|
|
G_SetConstantLight( &proj->edict->s.constantLight,
|
|
&proj->dlight_color[ 0 ],
|
|
&proj->dlight_color[ 1 ],
|
|
&proj->dlight_color[ 2 ],
|
|
&proj->dlight_radius
|
|
);
|
|
}
|
|
|
|
// Calc the life of the projectile
|
|
if( proj->projFlags & P_CHARGE_LIFE )
|
|
{
|
|
if( g_gametype->integer && proj->dmlife )
|
|
newlife = proj->dmlife;
|
|
else
|
|
newlife = proj->life;
|
|
|
|
if( newlife < proj->minlife )
|
|
newlife = proj->minlife;
|
|
}
|
|
else
|
|
{
|
|
if( g_gametype->integer && proj->dmlife )
|
|
newlife = proj->dmlife;
|
|
else
|
|
newlife = proj->life;
|
|
}
|
|
|
|
// Remove the projectile after it's life expires
|
|
ev = new Event( EV_Projectile_Explode );
|
|
proj->PostEvent( ev, newlife );
|
|
|
|
proj->NewAnim( "idle" );
|
|
|
|
if( !g_gametype->integer )
|
|
{
|
|
if( weap )
|
|
{
|
|
weap->m_iNumShotsFired++;
|
|
if( owner->IsSubclassOfPlayer() &&
|
|
weap->IsSubclassOfTurretGun() )
|
|
{
|
|
Player *p = ( Player * )owner;
|
|
p->m_iNumShotsFired++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return proj;
|
|
}
|
|
|
|
void ExplosionAttack
|
|
(
|
|
Vector pos,
|
|
Entity *owner,
|
|
str explosionModel,
|
|
Vector dir,
|
|
Entity *ignore,
|
|
float scale,
|
|
Weapon *weap,
|
|
bool hurtOwnerOnly
|
|
)
|
|
|
|
{
|
|
Explosion *explosion;
|
|
Event *ev;
|
|
|
|
|
|
if( !owner )
|
|
owner = world;
|
|
|
|
if( owner->IsDead() || owner == world )
|
|
weap = NULL;
|
|
|
|
if( explosionModel.length() )
|
|
{
|
|
explosion = new Explosion;
|
|
|
|
// Create a new explosion entity and set it off
|
|
explosion->setModel( explosionModel );
|
|
|
|
explosion->setSolidType( SOLID_NOT );
|
|
|
|
// Process the INIT commands right away
|
|
explosion->ProcessInitCommands();
|
|
|
|
explosion->SetOwner( owner );
|
|
|
|
explosion->edict->r.ownerNum = owner->entnum;
|
|
explosion->angles = dir.toAngles();
|
|
explosion->velocity = dir * explosion->speed;
|
|
explosion->edict->s.scale = scale;
|
|
explosion->setAngles( explosion->angles );
|
|
explosion->setMoveType( MOVETYPE_FLYMISSILE );
|
|
explosion->edict->clipmask = MASK_PROJECTILE;
|
|
explosion->setSize( explosion->mins, explosion->maxs );
|
|
explosion->setOrigin( pos );
|
|
explosion->origin.copyTo( explosion->edict->s.origin2 );
|
|
explosion->hurtOwnerOnly = hurtOwnerOnly;
|
|
|
|
if( explosion->dlight_radius )
|
|
{
|
|
G_SetConstantLight( &explosion->edict->s.constantLight,
|
|
&explosion->dlight_color[ 0 ],
|
|
&explosion->dlight_color[ 1 ],
|
|
&explosion->dlight_color[ 2 ],
|
|
&explosion->dlight_radius
|
|
);
|
|
}
|
|
|
|
explosion->BroadcastAIEvent( AI_EVENT_WEAPON_FIRE );
|
|
explosion->NewAnim( "idle" );
|
|
|
|
RadiusDamage( explosion->origin,
|
|
explosion,
|
|
owner,
|
|
explosion->radius_damage * scale,
|
|
ignore,
|
|
explosion->meansofdeath,
|
|
explosion->radius * scale,
|
|
explosion->knockback,
|
|
explosion->constant_damage,
|
|
weap,
|
|
explosion->hurtOwnerOnly
|
|
);
|
|
|
|
if( explosion->flash_radius )
|
|
{
|
|
FlashPlayers( explosion->origin,
|
|
explosion->flash_r,
|
|
explosion->flash_g,
|
|
explosion->flash_b,
|
|
explosion->flash_a,
|
|
explosion->flash_radius * scale,
|
|
explosion->flash_time,
|
|
explosion->flash_type
|
|
);
|
|
}
|
|
|
|
if( explosion->damage_every_frame )
|
|
{
|
|
explosion->PostEvent( EV_Explosion_DamageAgain, FRAMETIME );
|
|
}
|
|
|
|
// Remove explosion after the life has expired
|
|
if( explosion->life || ( g_gametype->integer && explosion->dmlife ) )
|
|
{
|
|
ev = new Event( EV_Remove );
|
|
|
|
if( g_gametype->integer && explosion->dmlife )
|
|
explosion->PostEvent( ev, explosion->dmlife );
|
|
else
|
|
explosion->PostEvent( ev, explosion->life );
|
|
}
|
|
}
|
|
}
|
|
|
|
void StunAttack
|
|
(
|
|
Vector pos,
|
|
Entity *attacker,
|
|
Entity *inflictor,
|
|
float radius,
|
|
float time,
|
|
Entity *ignore
|
|
)
|
|
|
|
{
|
|
Entity *ent;
|
|
|
|
ent = findradius( NULL, inflictor->origin, radius );
|
|
|
|
while( ent )
|
|
{
|
|
if ( ( ent != ignore ) && ( ( ent->takedamage ) || ent->IsSubclassOfActor() ) )
|
|
{
|
|
if ( inflictor->CanDamage( ent, attacker ) )
|
|
{
|
|
// Fixme : Add in knockback
|
|
ent->Stun( time );
|
|
}
|
|
}
|
|
ent = findradius( ent, inflictor->origin, radius );
|
|
}
|
|
}
|
|
|
|
static float radiusdamage_origin[ 3 ];
|
|
|
|
static int radiusdamage_compare( const void *elem1, const void *elem2 )
|
|
{
|
|
Entity *e1, *e2;
|
|
float delta[ 3 ];
|
|
float d1, d2;
|
|
|
|
e1 = *( Entity ** )elem1;
|
|
e2 = *( Entity ** )elem2;
|
|
|
|
VectorSubtract( radiusdamage_origin, e1->origin, delta );
|
|
d1 = VectorLengthSquared( delta );
|
|
|
|
VectorSubtract( radiusdamage_origin, e2->origin, delta );
|
|
d2 = VectorLengthSquared( delta );
|
|
|
|
if( d2 <= d1 )
|
|
{
|
|
return d1 > d2;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void RadiusDamage
|
|
(
|
|
Vector origin,
|
|
Entity *inflictor,
|
|
Entity *attacker,
|
|
float damage,
|
|
Entity *ignore,
|
|
int mod,
|
|
float radius,
|
|
float knockback,
|
|
qboolean constant_damage,
|
|
Weapon *weap,
|
|
bool hurtOwnerOnly
|
|
)
|
|
|
|
{
|
|
float points;
|
|
Entity *ent;
|
|
Vector org;
|
|
Vector dir;
|
|
float dist;
|
|
int i;
|
|
Container< Entity * > ents;
|
|
|
|
if( g_showdamage->integer )
|
|
{
|
|
Com_Printf( "radiusdamage" );
|
|
Com_Printf( "{\n" );
|
|
Com_Printf( "origin: %f %f %f\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] );
|
|
|
|
if( inflictor ) {
|
|
Com_Printf( "inflictor: (entnum %d, radnum %d)\n", inflictor->entnum, inflictor->radnum );
|
|
}
|
|
|
|
if( attacker ) {
|
|
Com_Printf( "attacker: (entnum %d, radnum %d)\n", attacker->entnum, attacker->radnum );
|
|
}
|
|
|
|
Com_Printf( "damage: %f\n", damage );
|
|
|
|
if( ignore ) {
|
|
Com_Printf( "ignore: (entnum %d, radnum %d)\n", ignore->entnum, ignore->radnum );
|
|
}
|
|
|
|
Com_Printf( "mod: %d\n", mod );
|
|
Com_Printf( "radius: %f\n", radius );
|
|
Com_Printf( "knockback: %f\n", knockback );
|
|
Com_Printf( "constant_damage: %d\n", constant_damage );
|
|
|
|
if( weap )
|
|
{
|
|
Com_Printf( "weapon %s\n", weap->getName().c_str() );
|
|
}
|
|
|
|
Com_Printf( "hurtOwnerOnly: %d\n", hurtOwnerOnly );
|
|
Com_Printf( "}\n" );
|
|
}
|
|
|
|
ent = findradius( NULL, origin, radius );
|
|
|
|
while( ent )
|
|
{
|
|
// Add ents that has contents
|
|
if( ent->getContents() )
|
|
{
|
|
ents.AddObject( ent );
|
|
}
|
|
|
|
ent = findradius( ent, origin, radius );
|
|
}
|
|
|
|
// Sort by the nearest to the fartest entity
|
|
if( ents.NumObjects() )
|
|
{
|
|
VectorCopy( origin, radiusdamage_origin );
|
|
ents.Sort( radiusdamage_compare );
|
|
}
|
|
|
|
for( i = 1; i <= ents.NumObjects(); i++ )
|
|
{
|
|
ent = ents.ObjectAt( i );
|
|
|
|
if( ent == ignore || !( ent->takedamage ) || ( hurtOwnerOnly && ent != attacker ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Add this in for deathmatch maybe
|
|
|
|
if( ent->getContents() == CONTENTS_NOTTEAM1 ||
|
|
G_SightTrace( origin, vec_zero, vec_zero, ent->centroid, inflictor, ent, MASK_EXPLOSION, false, "RadiusDamage" ) )
|
|
{
|
|
if( constant_damage )
|
|
{
|
|
points = damage;
|
|
}
|
|
else
|
|
{
|
|
float ent_rad;
|
|
|
|
ent_rad = fabs( ent->maxs[ 0 ] - ent->mins[ 0 ] );
|
|
|
|
if( ent_rad < fabs( ent->maxs[ 1 ] - ent->mins[ 1 ] ) )
|
|
{
|
|
ent_rad = fabs( ent->maxs[ 1 ] - ent->mins[ 1 ] );
|
|
}
|
|
|
|
if( ent_rad < fabs( ent->maxs[ 2 ] - ent->mins[ 2 ] ) )
|
|
{
|
|
ent_rad = fabs( ent->maxs[ 2 ] - ent->mins[ 2 ] );
|
|
}
|
|
|
|
org = ent->centroid;
|
|
dir = org - origin;
|
|
|
|
dist = dir.length() - ent_rad;
|
|
if( dist < 0.0f ) {
|
|
dist = 0.0f;
|
|
}
|
|
|
|
points = damage - damage * ( dist / radius );
|
|
|
|
knockback -= knockback * ( dist / radius );
|
|
|
|
if( points < 0 )
|
|
points = 0;
|
|
|
|
if( knockback < 0 )
|
|
knockback = 0;
|
|
}
|
|
|
|
// reduce the damage a little for self-damage
|
|
if( ent == attacker )
|
|
{
|
|
points *= 0.9f;
|
|
}
|
|
|
|
if( points > 0 )
|
|
{
|
|
ent->Damage( inflictor,
|
|
attacker,
|
|
points,
|
|
org,
|
|
dir,
|
|
vec_zero,
|
|
knockback,
|
|
DAMAGE_RADIUS,
|
|
mod
|
|
);
|
|
|
|
if( !g_gametype->integer && weap )
|
|
{
|
|
if( ent->IsSubclassOfPlayer() ||
|
|
ent->IsSubclassOfVehicle() ||
|
|
ent->IsSubclassOfVehicleTank() ||
|
|
ent->isSubclassOf( VehicleCollisionEntity ) )
|
|
{
|
|
weap->m_iNumHits++;
|
|
weap->m_iNumTorsoShots++;
|
|
|
|
if( attacker && attacker->IsSubclassOfPlayer() )
|
|
{
|
|
Player *player = ( Player * )attacker;
|
|
|
|
if( weap->IsSubclassOfTurretGun() )
|
|
{
|
|
player->m_iNumHits++;
|
|
player->m_iNumTorsoShots++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( mod == MOD_EXPLOSION )
|
|
{
|
|
// Create an earthquake
|
|
new ViewJitter(
|
|
origin,
|
|
radius + 128.0f,
|
|
0.05f,
|
|
Vector( damage * 0.05f, damage * 0.05f, damage * 0.06f ),
|
|
0,
|
|
vec_zero,
|
|
0
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
void FlashPlayers
|
|
(
|
|
Vector org,
|
|
float r,
|
|
float g,
|
|
float b,
|
|
float a,
|
|
float radius,
|
|
float time,
|
|
int type
|
|
)
|
|
|
|
{
|
|
trace_t trace;
|
|
Vector delta;
|
|
float length;
|
|
Player *player;
|
|
gentity_t *ed;
|
|
int i;
|
|
Entity *ent;
|
|
float newa = 1;
|
|
|
|
for( i = 0; i < game.maxclients; i++ )
|
|
{
|
|
ed = &g_entities[ i ];
|
|
if ( !ed->inuse || !ed->entity )
|
|
continue;
|
|
|
|
ent = ed->entity;
|
|
if ( !ent->IsSubclassOfPlayer() )
|
|
continue;
|
|
|
|
player = ( Player * )ent;
|
|
|
|
if ( !player->WithinDistance( org, radius ) )
|
|
continue;
|
|
|
|
trace = G_Trace( org, vec_zero, vec_zero, player->origin, player, MASK_OPAQUE, qfalse, "FlashPlayers" );
|
|
if ( trace.fraction != 1.0 )
|
|
continue;
|
|
|
|
delta = org - trace.endpos;
|
|
length = delta.length();
|
|
|
|
// If alpha is specified, then modify it by the amount of distance away from the flash the player is
|
|
if ( a != -1 )
|
|
newa = a * ( 1 - length / radius );
|
|
|
|
level.m_fade_alpha = newa;
|
|
level.m_fade_color[0] = r;
|
|
level.m_fade_color[1] = g;
|
|
level.m_fade_color[2] = b;
|
|
level.m_fade_time = time;
|
|
level.m_fade_time_start = time;
|
|
|
|
if ( type == 1 )
|
|
level.m_fade_style = additive;
|
|
else
|
|
level.m_fade_style = alphablend;
|
|
|
|
// gi.SendServerCommand( NULL, va( "fadein %0.2f %0.2f %0.2f %0.2f %i",time*1000,r*newa,g*newa,b*newa,type ) );
|
|
}
|
|
}
|
|
|