942 lines
22 KiB
C++
942 lines
22 KiB
C++
/*
|
|
================================================================
|
|
HOVERBIKE WEAPONS
|
|
================================================================
|
|
|
|
Copyright (C) 1998 by 2015, Inc.
|
|
All rights reserved.
|
|
|
|
This source is may not be distributed and/or modified without
|
|
expressly written permission by 2015, Inc.
|
|
|
|
serves as both the view model and the world model
|
|
for when a bike has a rider. Also does all the weapon
|
|
related stuff for hoverbikes.
|
|
*/
|
|
|
|
#include "hoverweap.h"
|
|
#include "player.h"
|
|
#include "explosion.h"
|
|
#include "surface.h"
|
|
#include "jitter.h"
|
|
#include "misc.h"
|
|
|
|
CLASS_DECLARATION(Weapon, HoverWeap, "weapon_hoverweap");
|
|
|
|
ResponseDef HoverWeap::Responses[] =
|
|
{
|
|
{&EV_Weapon_Shoot, (Response)HoverWeap::Shoot},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
HoverWeap::HoverWeap()
|
|
{
|
|
SetModels("bike_prototype.def", "view_hoverbike.def");
|
|
modelIndex("stinger.def");
|
|
modelIndex("hovermine.def");
|
|
modelIndex("beam_bike.def");
|
|
SetAmmo(NULL, 0, 0);
|
|
|
|
notdroppable = true;
|
|
side = 1;
|
|
|
|
edict->s.effects |= EF_WARM;
|
|
}
|
|
|
|
void HoverWeap::AttachGun (void)
|
|
{
|
|
int groupindex;
|
|
int tri_num;
|
|
Vector orient;
|
|
|
|
if(!owner)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (attached)
|
|
DetachGun();
|
|
|
|
// uses the built in fake bone that works like Quake2's modelindexes did.
|
|
gi.GetBoneInfo(owner->edict->s.modelindex, "origin", &groupindex, &tri_num, orient.vec3());
|
|
attached = true;
|
|
attach(owner->entnum, groupindex, tri_num, orient);
|
|
showModel();
|
|
|
|
edict->s.gunmodelindex = modelIndex(worldmodel.c_str());
|
|
edict->s.gunanim = 0;
|
|
edict->s.gunframe = 0;
|
|
|
|
// set the bike and rider values for the hoverbike
|
|
if(owner->isSubclassOf(Player))
|
|
{
|
|
Player *player;
|
|
|
|
player = (Player *)owner.Ptr();
|
|
bike = player->GetHoverbike();
|
|
}
|
|
else
|
|
gi.dprintf("ent number %i is not a player\n", owner->entnum);
|
|
}
|
|
|
|
void HoverWeap::DetachGun (void)
|
|
{
|
|
if(attached)
|
|
{
|
|
attached = false;
|
|
detach();
|
|
hideModel();
|
|
edict->s.gunmodelindex = 0;
|
|
edict->s.gunanim = 0;
|
|
edict->s.gunframe = 0;
|
|
}
|
|
}
|
|
|
|
void HoverWeap::Fire (void)
|
|
{
|
|
int noammo;
|
|
|
|
if(!ReadyToFire())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if bike isn't set yet, set it
|
|
if(!bike)
|
|
{
|
|
if(owner->isSubclassOf(Player))
|
|
{
|
|
Player *player;
|
|
|
|
player = (Player *)owner.Ptr();
|
|
bike = player->GetHoverbike();
|
|
}
|
|
|
|
// still not set
|
|
if(!bike)
|
|
return;
|
|
}
|
|
|
|
// use appropriate type of ammo when appropriate
|
|
noammo = 0;
|
|
if(owner->isClient() && !(owner->flags & FL_GODMODE) && !(DM_FLAG(DF_INFINITE_AMMO)))
|
|
{
|
|
switch(bike->weaponmode)
|
|
{
|
|
case HWMODE_ROCKETS:
|
|
if(bike->rockets < 2)
|
|
noammo = 1;
|
|
else
|
|
bike->rockets -= 2;
|
|
break;
|
|
case HWMODE_CHAINGUN:
|
|
if(bike->bullets < 1)
|
|
noammo = 1;
|
|
else
|
|
bike->bullets -= 1;
|
|
break;
|
|
case HWMODE_MINES:
|
|
if(bike->mines < 1)
|
|
noammo = 1;
|
|
else
|
|
bike->mines -= 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// don't have enough ammo to fire current weapon
|
|
if(noammo)
|
|
{
|
|
// auto weapon switch
|
|
switch(bike->weaponmode)
|
|
{
|
|
case HWMODE_ROCKETS:
|
|
{
|
|
if(bike->bullets > 0)
|
|
{
|
|
bike->SelectWeapon(HWMODE_CHAINGUN);
|
|
}
|
|
else if(bike->mines > 0)
|
|
{
|
|
bike->SelectWeapon(HWMODE_MINES);
|
|
}
|
|
break;
|
|
}
|
|
case HWMODE_CHAINGUN:
|
|
{
|
|
if(bike->rockets > 0)
|
|
{
|
|
bike->SelectWeapon(HWMODE_ROCKETS);
|
|
}
|
|
else if(bike->mines > 0)
|
|
{
|
|
bike->SelectWeapon(HWMODE_MINES);
|
|
}
|
|
break;
|
|
}
|
|
case HWMODE_MINES:
|
|
{
|
|
if(bike->rockets > 0)
|
|
{
|
|
bike->SelectWeapon(HWMODE_ROCKETS);
|
|
}
|
|
else if(bike->bullets > 0)
|
|
{
|
|
bike->SelectWeapon(HWMODE_CHAINGUN);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
bike->WeaponNoAmmoSound();
|
|
NextAttack(1);
|
|
return;
|
|
}
|
|
|
|
weaponstate = WEAPON_FIRING;
|
|
|
|
CancelEventsOfType(EV_Weapon_DoneFiring);
|
|
// this is just a precaution that we can re-trigger
|
|
NextAttack(5);
|
|
// activate the corrent firing animation for the view
|
|
if(bike->weaponmode == HWMODE_ROCKETS)
|
|
{
|
|
RandomAnimate("rocket", EV_Weapon_DoneFiring);
|
|
}
|
|
else if(bike->weaponmode == HWMODE_CHAINGUN)
|
|
{
|
|
// fire a laser beam out from the correct side
|
|
if(side > 0)
|
|
RandomAnimate("rightlaser", EV_Weapon_DoneFiring);
|
|
else
|
|
RandomAnimate("leftlaser", EV_Weapon_DoneFiring);
|
|
}
|
|
else if(bike->weaponmode == HWMODE_MINES)
|
|
{
|
|
RandomAnimate("mine", EV_Weapon_DoneFiring);
|
|
}
|
|
last_attack_time = level.time;
|
|
}
|
|
|
|
void HoverWeap::Shoot (Event *ev)
|
|
{
|
|
Vector pos, dir, forward, right, up;
|
|
Vector tmpvec;
|
|
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
if(!bike)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(bike->player)
|
|
{
|
|
Player *pent;
|
|
|
|
pent = (Player *)bike->rider.Ptr();
|
|
|
|
// need to scale the player's view pitch up to
|
|
// compensate for the weapons firing from
|
|
// so far forward.
|
|
tmpvec[PITCH] = pent->v_angle[PITCH]*1.05;
|
|
}
|
|
|
|
if(tmpvec[PITCH] > 45)
|
|
tmpvec[PITCH] = 45;
|
|
else if(tmpvec[PITCH] < -45)
|
|
tmpvec[PITCH] = -45;
|
|
|
|
tmpvec[YAW] = bike->move_angles[YAW];
|
|
tmpvec[ROLL] = 0;
|
|
tmpvec.AngleVectors(&dir, NULL, NULL);
|
|
|
|
// trans dir to bike's grav axis
|
|
if(bike->gravaxis)
|
|
{
|
|
Vector tmpvec2;
|
|
|
|
tmpvec2[gravity_axis[bike->gravaxis].x] = dir.x;
|
|
tmpvec2[gravity_axis[bike->gravaxis].y] = dir.y*gravity_axis[bike->gravaxis].sign;
|
|
tmpvec2[gravity_axis[bike->gravaxis].z] = dir.z*gravity_axis[bike->gravaxis].sign;
|
|
dir = tmpvec2;
|
|
}
|
|
|
|
tmpvec[PITCH] = 0;
|
|
tmpvec.AngleVectors(&forward, &right, &up);
|
|
|
|
// trans dir to bike's grav axis
|
|
if(bike->gravaxis)
|
|
{
|
|
Vector tmpvec2;
|
|
|
|
tmpvec2[gravity_axis[bike->gravaxis].x] = forward.x;
|
|
tmpvec2[gravity_axis[bike->gravaxis].y] = forward.y*gravity_axis[bike->gravaxis].sign;
|
|
tmpvec2[gravity_axis[bike->gravaxis].z] = forward.z*gravity_axis[bike->gravaxis].sign;
|
|
forward = tmpvec2;
|
|
|
|
tmpvec2[gravity_axis[bike->gravaxis].x] = right.x;
|
|
tmpvec2[gravity_axis[bike->gravaxis].y] = right.y*gravity_axis[bike->gravaxis].sign;
|
|
tmpvec2[gravity_axis[bike->gravaxis].z] = right.z*gravity_axis[bike->gravaxis].sign;
|
|
right = tmpvec2;
|
|
|
|
tmpvec2[gravity_axis[bike->gravaxis].x] = up.x;
|
|
tmpvec2[gravity_axis[bike->gravaxis].y] = up.y*gravity_axis[bike->gravaxis].sign;
|
|
tmpvec2[gravity_axis[bike->gravaxis].z] = up.z*gravity_axis[bike->gravaxis].sign;
|
|
up = tmpvec2;
|
|
}
|
|
|
|
|
|
// fire the currently selected weapon mode
|
|
if(bike->weaponmode == HWMODE_ROCKETS)
|
|
{
|
|
HBRocket *rocket;
|
|
|
|
pos = bike->worldorigin + forward*40 + right*18*side + up*8;
|
|
|
|
rocket = new HBRocket;
|
|
rocket->Setup(owner, pos, dir);
|
|
|
|
pos = bike->worldorigin + forward*40 - right*18*side + up*8;
|
|
|
|
rocket = new HBRocket;
|
|
rocket->Setup(owner, pos, dir);
|
|
|
|
NextAttack(0.8);
|
|
}
|
|
else if(bike->weaponmode == HWMODE_CHAINGUN)
|
|
{
|
|
pos = bike->worldorigin + forward*40 + right*10*side + up*8;
|
|
FireBullets(pos, dir, 1, "100 100 100", 6, 8, DAMAGE_BULLET, MOD_HB_GUN, true);
|
|
|
|
// also do a bullet trace in the center to make it easier to hit things
|
|
pos = bike->worldorigin + forward*40 + up*8;
|
|
FireBullets(pos, dir, 4, "50 50 50", 6, 8, DAMAGE_BULLET, MOD_HB_GUN, false);
|
|
|
|
// Set the pitch so the client can use it to fire tracers in the right direction
|
|
angles = dir.toAngles();
|
|
setAngles(angles);
|
|
|
|
NextAttack(0.1);
|
|
}
|
|
else if(bike->weaponmode == HWMODE_MINES)
|
|
{
|
|
HBMine *mine;
|
|
|
|
pos = bike->worldorigin - forward*30 + up*16;
|
|
dir = forward*(-1);
|
|
|
|
mine = new HBMine;
|
|
mine->Setup(owner, pos, dir);
|
|
|
|
NextAttack(1.2);
|
|
}
|
|
|
|
// alternate firing side
|
|
side *= -1;
|
|
}
|
|
|
|
// never drop a hoverbike weapon
|
|
qboolean HoverWeap::Drop (void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//==============================================================
|
|
// rocket weapon stuff
|
|
|
|
#define ROCKET_SPEED 1800
|
|
#define ROCKET_RADIUS 150
|
|
|
|
CLASS_DECLARATION( Projectile, HBRocket, NULL );
|
|
|
|
Event EV_HBRocket_Explode( "explode" );
|
|
|
|
ResponseDef HBRocket::Responses[] =
|
|
{
|
|
{&EV_Touch, (Response)HBRocket::Explode},
|
|
{&EV_HBRocket_Explode, (Response)HBRocket::Explode},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
EXPORT_FROM_DLL void HBRocket::Explode (Event *ev)
|
|
{
|
|
int damg;
|
|
Vector v;
|
|
Entity *other;
|
|
Vector norm;
|
|
RadiusJitter *jitter;
|
|
Entity *owner;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
assert( other );
|
|
|
|
if(other->isSubclassOf(Teleporter))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(other->entnum == this->owner ||
|
|
other->entnum == bike ||
|
|
other->entnum == frontbox ||
|
|
other->entnum == backbox)
|
|
{
|
|
return;
|
|
}
|
|
|
|
stopsound( CHAN_VOICE );
|
|
|
|
setSolidType( SOLID_NOT );
|
|
hideModel();
|
|
if ( HitSky() )
|
|
{
|
|
PostEvent( EV_Remove, 0 );
|
|
return;
|
|
}
|
|
|
|
owner = G_GetEntity(this->owner);
|
|
|
|
damg = 60 + ( int )G_Random( 20 );
|
|
|
|
if (other->takedamage)
|
|
other->Damage( this, owner, damg, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_HB_ROCKET, -1, -1, 1.0f );
|
|
|
|
SpawnBlastDamage( &level.impact_trace, damg, owner );
|
|
|
|
v = velocity;
|
|
v.normalize();
|
|
v = origin - v * 36;
|
|
|
|
// don't do radius damage to the other, because all the damage
|
|
// was done in the impact
|
|
CreateExplosion( v, damg, 0.75, true, this, owner, this, MOD_HB_ROCKETSPLASH, 0.35);
|
|
|
|
jitter = new RadiusJitter;
|
|
jitter->SetupLarge(v);
|
|
|
|
PostEvent( EV_Remove, 0.1 );
|
|
}
|
|
|
|
EXPORT_FROM_DLL void HBRocket::Setup (Entity *owner, Vector pos, Vector dir)
|
|
{
|
|
Event *ev;
|
|
Player *rider;
|
|
Hoverbike *bike;
|
|
|
|
this->owner = owner->entnum;
|
|
edict->owner = owner->edict;
|
|
rider = (Player *)owner;
|
|
bike = rider->GetHoverbike();
|
|
this->bike = bike->entnum;
|
|
frontbox = bike->frontbox->entnum;
|
|
backbox = bike->backbox->entnum;
|
|
|
|
setMoveType( MOVETYPE_FLYMISSILE );
|
|
setSolidType( SOLID_BBOX );
|
|
edict->clipmask = MASK_PROJECTILE;
|
|
|
|
angles = dir.toAngles();
|
|
angles[ PITCH ] = - angles[ PITCH ];
|
|
setAngles( angles );
|
|
|
|
speed = ROCKET_SPEED;
|
|
velocity = dir * ROCKET_SPEED;
|
|
|
|
// set missile duration
|
|
ev = new Event( EV_Remove );
|
|
ev->AddEntity( world );
|
|
PostEvent( ev, 20 );
|
|
|
|
setModel( "stinger.def" );
|
|
edict->s.effects |= EF_ANIMEROCKET;
|
|
edict->s.effects |= EF_EVERYFRAME;
|
|
edict->s.angles[ROLL] = rand() % 360;
|
|
avelocity = "0 0 90";
|
|
gravity = 0;
|
|
|
|
// setup ambient thrust
|
|
ev = new Event( EV_RandomEntitySound );
|
|
ev->AddString( "thrust" );
|
|
ProcessEvent( ev );
|
|
|
|
setSize( "-1 -1 -1", "1 1 1" );
|
|
setOrigin( pos );
|
|
worldorigin.copyTo(edict->s.old_origin);
|
|
}
|
|
|
|
//==============================================================
|
|
// bullet weapon stuff
|
|
|
|
#define MAX_RICOCHETS 4
|
|
|
|
void HoverWeap::TraceAttack
|
|
(
|
|
Vector start,
|
|
Vector end,
|
|
int damage,
|
|
trace_t *trace,
|
|
int numricochets,
|
|
int kick,
|
|
int dflags,
|
|
int meansofdeath,
|
|
qboolean server_effects
|
|
)
|
|
{
|
|
Vector org;
|
|
Vector dir;
|
|
Vector endpos;
|
|
int surfflags;
|
|
int surftype;
|
|
int timeofs;
|
|
Entity *ent;
|
|
qboolean ricochet;
|
|
|
|
if ( HitSky( trace ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ricochet = false;
|
|
dir = end - start;
|
|
dir.normalize();
|
|
|
|
org = end - dir;
|
|
|
|
ent = trace->ent->entity;
|
|
|
|
if ( !trace->surface )
|
|
{
|
|
surfflags = 0;
|
|
surftype = 0;
|
|
}
|
|
else
|
|
{
|
|
surfflags = trace->surface->flags;
|
|
surftype = SURFACETYPE_FROM_FLAGS( surfflags );
|
|
surfaceManager.DamageSurface( trace, damage, owner );
|
|
|
|
if ( surfflags & SURF_RICOCHET )
|
|
ricochet = true;
|
|
}
|
|
if ( trace->intersect.valid && ent )
|
|
{
|
|
//
|
|
// see if the parent group has ricochet turned on
|
|
//
|
|
if ( trace->intersect.parentgroup >= 0 )
|
|
{
|
|
int flags;
|
|
|
|
flags = gi.Group_Flags( ent->edict->s.modelindex, trace->intersect.parentgroup );
|
|
if ( flags & MDL_GROUP_RICOCHET )
|
|
{
|
|
surftype = ( flags >> 8 ) & 0xf;
|
|
ricochet = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ent )
|
|
{
|
|
if ( !(ent->flags & FL_SHIELDS) )
|
|
{
|
|
if ( ent->flags & FL_SPARKS )
|
|
{
|
|
// Take care of ricochet effects on the server
|
|
if ( server_effects && !ricochet )
|
|
{
|
|
timeofs = MAX_RICOCHETS - numricochets;
|
|
if ( timeofs > 0xf )
|
|
{
|
|
timeofs = 0xf;
|
|
}
|
|
|
|
gi.WriteByte( svc_temp_entity );
|
|
gi.WriteByte( TE_SCALED_EXPLOSION );
|
|
gi.WritePosition( org.vec3() );
|
|
gi.WriteByte( 16 );
|
|
gi.multicast( org.vec3(), MULTICAST_PVS );
|
|
}
|
|
MadeBreakingSound( org, owner );
|
|
}
|
|
|
|
if ( ent->takedamage )
|
|
{
|
|
if ( trace->intersect.valid )
|
|
{
|
|
// We hit a valid group so send in location based damage
|
|
ent->Damage( this,
|
|
owner,
|
|
damage,
|
|
trace->endpos,
|
|
dir,
|
|
trace->plane.normal,
|
|
kick,
|
|
dflags,
|
|
meansofdeath,
|
|
trace->intersect.parentgroup,
|
|
-1,
|
|
trace->intersect.damage_multiplier );
|
|
}
|
|
else
|
|
{
|
|
// We didn't hit any groups, so send in generic damage
|
|
ent->Damage( this,
|
|
owner,
|
|
damage,
|
|
trace->endpos,
|
|
dir,
|
|
trace->plane.normal,
|
|
kick,
|
|
dflags,
|
|
meansofdeath,
|
|
-1,
|
|
-1,
|
|
1 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
surftype = SURF_TYPE_METAL;
|
|
ricochet = true;
|
|
}
|
|
}
|
|
|
|
if ( ricochet && ( server_effects || ( numricochets < MAX_RICOCHETS ) ) )
|
|
{
|
|
timeofs = MAX_RICOCHETS - numricochets;
|
|
if ( timeofs > 0xf )
|
|
{
|
|
timeofs = 0xf;
|
|
}
|
|
gi.WriteByte( svc_temp_entity );
|
|
gi.WriteByte( TE_GUNSHOT );
|
|
gi.WritePosition( org.vec3() );
|
|
gi.WriteDir( trace->plane.normal );
|
|
gi.WriteByte( timeofs );
|
|
gi.multicast( org.vec3(), MULTICAST_PVS );
|
|
}
|
|
|
|
if (
|
|
ricochet &&
|
|
numricochets &&
|
|
damage
|
|
)
|
|
{
|
|
dir += Vector( trace->plane.normal ) * 2;
|
|
endpos = org + dir * 8192;
|
|
|
|
//
|
|
// since this is a ricochet, we don't ignore the weapon owner this time.
|
|
//
|
|
if(DM_FLAG(DF_BBOX_BULLETS))
|
|
{
|
|
*trace = G_Trace( org, vec_zero, vec_zero, endpos, NULL, MASK_SHOT, "BulletWeapon::TraceAttack" );
|
|
}
|
|
else
|
|
{
|
|
*trace = G_FullTrace( org, vec_zero, vec_zero, endpos, 5, NULL, MASK_SHOT, "BulletWeapon::TraceAttack" );
|
|
}
|
|
|
|
if ( trace->fraction != 1.0 )
|
|
{
|
|
endpos = trace->endpos;
|
|
TraceAttack( org, endpos, damage * 0.8f, trace, numricochets - 1, kick, dflags, meansofdeath, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
void HoverWeap::FireBullets
|
|
(
|
|
Vector src,
|
|
Vector dir,
|
|
int numbullets,
|
|
Vector spread,
|
|
int mindamage,
|
|
int maxdamage,
|
|
int dflags,
|
|
int meansofdeath,
|
|
qboolean server_effects
|
|
)
|
|
{
|
|
Vector end;
|
|
trace_t trace;
|
|
Vector right;
|
|
Vector up;
|
|
int i;
|
|
|
|
assert( owner );
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
owner->angles.AngleVectors( NULL, &right, &up );
|
|
|
|
angles = dir.toAngles();
|
|
setAngles( angles );
|
|
|
|
for( i = 0; i < numbullets; i++ )
|
|
{
|
|
end = src +
|
|
dir * 8192 +
|
|
right * G_CRandom() * spread.x +
|
|
up * G_CRandom() * spread.y;
|
|
|
|
// first need to do a regular trace to check for hitting a hoverbike
|
|
trace = G_Trace(src, vec_zero, vec_zero, end, bike->frontbox, MASK_SHOT, "BulletWeapon::FireBullets");
|
|
if(trace.fraction != 1)
|
|
{
|
|
Entity *hit;
|
|
|
|
hit = trace.ent->entity;
|
|
|
|
if(hit->isSubclassOf(Hoverbike) || hit->isSubclassOf(HoverbikeBox))
|
|
{
|
|
trace_t trace2;
|
|
|
|
// also do a short full trace to see if we're hitting a player on a hoverbike
|
|
end = trace.endpos + dir * 64;
|
|
trace2 = G_FullTrace(Vector(trace.endpos), vec_zero, vec_zero, end, 5, bike->frontbox, MASK_SHOT, "BulletWeapon::FireBullets");
|
|
if(trace2.fraction != 1)
|
|
{
|
|
Entity *hit2;
|
|
|
|
hit2 = trace2.ent->entity;
|
|
if(hit2->takedamage && hit2->isClient())
|
|
{
|
|
// probably traced to the rider, so hit him instead
|
|
hit2->Damage(this, owner, mindamage + (int)G_Random(maxdamage - mindamage + 1),
|
|
trace.endpos, dir, trace.plane.normal, kick, dflags, meansofdeath,
|
|
trace.intersect.parentgroup, -1, trace.intersect.damage_multiplier);
|
|
return;
|
|
}
|
|
}
|
|
|
|
hit->Damage(this, owner, mindamage + (int)G_Random( maxdamage - mindamage + 1 ), trace.endpos, dir, trace.plane.normal, kick, dflags, meansofdeath, -1, -1, 1);
|
|
|
|
return; // hit something already, so don't do a regular full trace
|
|
}
|
|
}
|
|
|
|
if(!damagedtarget && DM_FLAG(DF_BBOX_BULLETS))
|
|
{
|
|
trace = G_Trace(src, vec_zero, vec_zero, end, owner, MASK_SHOT, "BulletWeapon::FireBullets");
|
|
|
|
if(trace.fraction != 1.0)
|
|
{
|
|
// do less than regular damage on a bbox hit
|
|
TraceAttack(src, trace.endpos, (mindamage + (int)G_Random( maxdamage - mindamage + 1 ))*0.85, &trace, MAX_RICOCHETS, kick, dflags, meansofdeath, server_effects);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trace = G_FullTrace( src, vec_zero, vec_zero, end, 5, bike->frontbox, MASK_SHOT, "BulletWeapon::FireBullets" );
|
|
#if 0
|
|
Com_Printf("Server OWNER Angles:%0.2f %0.2f %0.2f\n",owner->angles[0],owner->angles[1],owner->angles[2]);
|
|
Com_Printf("Server Bullet Angles:%0.2f %0.2f %0.2f\n",angles[0],angles[1],angles[2]);
|
|
Com_Printf("Right :%0.2f %0.2f %0.2f\n",right[0],right[1],right[2]);
|
|
Com_Printf("Up :%0.2f %0.2f %0.2f\n",up[0],up[1],up[2]);
|
|
Com_Printf("Direction :%0.2f %0.2f %0.2f\n",dir[0],dir[1],dir[2]);
|
|
Com_Printf("Endpoint :%0.2f %0.2f %0.2f\n",end[0],end[1],end[2]);
|
|
Com_Printf("Server Trace Start :%0.2f %0.2f %0.2f\n",src[0],src[1],src[2]);
|
|
Com_Printf("Server Trace End :%0.2f %0.2f %0.2f\n",trace.endpos[0],trace.endpos[1],trace.endpos[2]);
|
|
Com_Printf("\n");
|
|
#endif
|
|
damagedtarget = false;
|
|
if ( trace.fraction != 1.0 )
|
|
{
|
|
TraceAttack( src, trace.endpos, mindamage + (int)G_Random( maxdamage - mindamage + 1 ), &trace, MAX_RICOCHETS, kick, dflags, meansofdeath, server_effects );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================
|
|
// mine weapon stuff
|
|
|
|
#define MINE_SPEED 300
|
|
#define MINE_RADIUS 160
|
|
#define MINE_TIME 5
|
|
#define MINE_PUSH 700
|
|
#define MINE_DAMAGE 100
|
|
|
|
CLASS_DECLARATION( Projectile, HBMine, NULL );
|
|
|
|
Event EV_HBMine_Explode( "explode" );
|
|
Event EV_HBMine_Detect( "proximity_detect" );
|
|
|
|
ResponseDef HBMine::Responses[] =
|
|
{
|
|
{&EV_HBMine_Explode, (Response)HBMine::Explode},
|
|
{&EV_HBMine_Detect, (Response)HBMine::Detect},
|
|
{&EV_Killed, (Response)HBMine::Explode},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
EXPORT_FROM_DLL void HBMine::Detect (Event *ev)
|
|
{
|
|
Entity *ent;
|
|
|
|
// check if we should blow up now
|
|
if((detonate_time + MINE_TIME) < level.time)
|
|
{
|
|
takedamage = DAMAGE_NO;
|
|
ProcessEvent(EV_HBMine_Explode);
|
|
return;
|
|
}
|
|
|
|
ent = findradius(NULL, origin.vec3(), MINE_RADIUS);
|
|
while(ent)
|
|
{
|
|
if(ent->isSubclassOf(Sentient))
|
|
{
|
|
if(!(detonate_time > level.time && ent->entnum == owner))
|
|
{
|
|
takedamage = DAMAGE_NO;
|
|
PostEvent(EV_HBMine_Explode, 0.1);
|
|
return;
|
|
}
|
|
}
|
|
ent = findradius(ent, origin.vec3(), MINE_RADIUS);
|
|
}
|
|
|
|
PostEvent(EV_HBMine_Detect, 0.1);
|
|
}
|
|
|
|
EXPORT_FROM_DLL void HBMine::Explode (Event *ev)
|
|
{
|
|
int damg;
|
|
Entity *ent;
|
|
float tmpflt;
|
|
Vector tmpvec;
|
|
|
|
CancelPendingEvents();
|
|
|
|
stopsound(CHAN_VOICE);
|
|
|
|
takedamage = DAMAGE_NO;
|
|
setSolidType(SOLID_NOT);
|
|
hideModel();
|
|
|
|
damg = MINE_DAMAGE;
|
|
|
|
if(!deathmatch->value)
|
|
damg *= 1.5;
|
|
|
|
// don't do radius damage to the other, because all the damage
|
|
// was done in the impact
|
|
CreateExplosion(origin, damg, 1.0f, true, this, G_GetEntity(owner), world, MOD_HB_MINE, 0.1);
|
|
|
|
// push sentient's away from the blast
|
|
ent = findradius(NULL, origin.vec3(), MINE_RADIUS);
|
|
while(ent)
|
|
{
|
|
if(ent->isSubclassOf(Sentient))
|
|
{
|
|
tmpvec = ent->origin - origin;
|
|
tmpflt = MINE_PUSH - tmpvec.length()*3;
|
|
if((tmpvec[gravity_axis[gravaxis].z]*gravity_axis[gravaxis].sign) < 0)
|
|
{
|
|
tmpvec[gravity_axis[gravaxis].z] = 0;
|
|
}
|
|
// was just tmpvec.normalize(), but the compiler is a wanker
|
|
tmpvec = tmpvec.normalize();
|
|
tmpvec *= tmpflt;
|
|
tmpvec[gravity_axis[gravaxis].z] += 150*gravity_axis[gravaxis].sign;
|
|
// check if it's a player on a bike
|
|
if(ent->isSubclassOf(Player))
|
|
{
|
|
Player *rider;
|
|
Hoverbike *bike;
|
|
|
|
rider = (Player *)ent;
|
|
bike = rider->GetHoverbike();
|
|
|
|
if(bike)
|
|
{
|
|
// bike's get a bit of slow down from mines
|
|
// but only half the push
|
|
bike->velocity *= 0.9;
|
|
bike->velocity += tmpvec*0.75;
|
|
}
|
|
else
|
|
{
|
|
rider->velocity += tmpvec;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->velocity += tmpvec;
|
|
}
|
|
}
|
|
ent = findradius(ent, origin.vec3(), MINE_RADIUS);
|
|
}
|
|
|
|
PostEvent(EV_Remove, 0.1);
|
|
}
|
|
|
|
EXPORT_FROM_DLL void HBMine::Setup (Entity *owner, Vector pos, Vector dir)
|
|
{
|
|
Event *ev;
|
|
Vector smins, smaxs;
|
|
Vector forward, right, up;
|
|
Vector t[3];
|
|
|
|
this->owner = owner->entnum;
|
|
edict->owner = owner->edict;
|
|
|
|
setMoveType(MOVETYPE_TOSS);
|
|
setSolidType(SOLID_BBOX);
|
|
edict->clipmask = MASK_PROJECTILE | MASK_WATER;
|
|
|
|
SetGravityAxis(owner->gravaxis);
|
|
|
|
angles = vec_zero;
|
|
angles[YAW] = rand() % 360;
|
|
angles.AngleVectors(&t[0], &t[1], &t[2]);
|
|
forward[gravity_axis[gravaxis].x] = t[0][0];
|
|
forward[gravity_axis[gravaxis].y] = t[0][1] * gravity_axis[gravaxis].sign;
|
|
forward[gravity_axis[gravaxis].z] = t[0][2] * gravity_axis[gravaxis].sign;
|
|
right[gravity_axis[gravaxis].x] = t[1][0];
|
|
right[gravity_axis[gravaxis].y] = t[1][1] * gravity_axis[gravaxis].sign;
|
|
right[gravity_axis[gravaxis].z] = t[1][2] * gravity_axis[gravaxis].sign;
|
|
up[gravity_axis[gravaxis].x] = t[2][0];
|
|
up[gravity_axis[gravaxis].y] = t[2][1] * gravity_axis[gravaxis].sign;
|
|
up[gravity_axis[gravaxis].z] = t[2][2] * gravity_axis[gravaxis].sign;
|
|
VectorsToEulerAngles(forward.vec3(), right.vec3(), up.vec3(), angles.vec3());
|
|
setAngles(angles);
|
|
|
|
velocity = owner->velocity*0.5 + dir * MINE_SPEED;
|
|
velocity[gravity_axis[gravaxis].z] += 100*gravity_axis[gravaxis].sign;
|
|
|
|
// set mine to proximity detect
|
|
detonate_time = level.time + 2;
|
|
PostEvent(EV_HBMine_Detect, 0.5);
|
|
|
|
takedamage = DAMAGE_YES;
|
|
health = 25;
|
|
|
|
setModel( "hovermine.def" );
|
|
edict->s.effects |= EF_ROCKET;
|
|
edict->s.effects |= EF_EVERYFRAME;
|
|
gravity = 0.75;
|
|
|
|
// setup ambient thrust
|
|
ev = new Event(EV_RandomEntitySound);
|
|
ev->AddString("hover");
|
|
ProcessEvent(ev);
|
|
|
|
smins = Vector(-10, -10, -10);
|
|
smaxs = Vector(10, 10, 10);
|
|
smaxs[gravity_axis[gravaxis].z] = 18;
|
|
smins[gravity_axis[gravaxis].z] = -18;
|
|
setSize(smins, smaxs);
|
|
setOrigin(pos);
|
|
worldorigin.copyTo(edict->s.old_origin);
|
|
|
|
RandomAnimate("idle", NULL);
|
|
}
|