sin-2015/crossbow.cpp
1999-04-22 00:00:00 +00:00

587 lines
13 KiB
C++

/*
================================================================
PLASMA BOW WEAPON
================================================================
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.
*/
#include "crossbow.h"
#include "explosion.h"
#include "surface.h"
#include "jitter.h"
#include "misc.h"
void G_AddGravity (Entity *ent);
//=================================================================
// plasma bow bolt class
CLASS_DECLARATION( Projectile, PBolt, NULL );
Event EV_PBolt_Explode("explode");
Event EV_PBolt_EnergyTrail("energytrail");
ResponseDef PBolt::Responses[] =
{
{&EV_Touch, (Response)PBolt::BoltTouch},
{&EV_PBolt_Explode, (Response)PBolt::Explode},
{&EV_PBolt_EnergyTrail, (Response)PBolt::EnergyTrail},
{NULL, NULL}
};
// also used to do velocity adjustments
EXPORT_FROM_DLL void PBolt::EnergyTrail (Event *ev)
{
Vector tmpvec;
int roll;
Vector grav;
PostEvent(EV_PBolt_EnergyTrail, 0.1);
roll = angles[ROLL] + 35;
grav[gravity_axis[gravaxis].z] = gravity*sv_gravity->value*FRAMETIME*2*gravity_axis[gravaxis].sign;
tmpvec = velocity - grav;
angles = tmpvec.toAngles();
angles[PITCH] = -angles[PITCH];
angles[ROLL] = roll;
setAngles(angles);
}
void PBolt::Explode (Event *ev)
{
float damg;
Entity *owner;
RadiusJitter *jitter;
owner = G_GetEntity(this->owner);
if(!owner->isClient())
{
damg = 20 + (int)G_Random(10);
}
else
{
damg = 110 + ( int )G_Random(15);
if(!deathmatch->value)
damg *= 1.5;
}
CreateExplosion( origin, damg, 0, false, this, owner, NULL, MOD_PLASMABOWSPLASH, 0.3, 0, ATTN_NORM, 0.2, 1.0, 0.2, 300, 0.6, 0.75);
// make the visual explosion
TempModel(NULL, origin, "0 0 0", "sprites/cbshot.spr", 0, 3.0, 0.8f, TEMPMODEL_ANIMATE_ONCE|TEMPMODEL_ANIMATE_FAST, 2);
damg = damg*0.2 + 32;
SpawnBowExplosion(origin, damg);
// make the view jitter for the explosion
jitter = new RadiusJitter;
jitter->Setup(origin, 150, 0.25, 0.2, 6, 9, 0.2, 4, 6);
// make the explosion sound
RandomPositionedSound(origin, "impact_largeplasmaexplosion", 1.0, CHAN_AUTO, ATTN_NORM);
hideModel();
PostEvent(EV_Remove, 0.1);
}
void PBolt::BoltTouch (Event *ev)
{
int damg, num;
float decay;
Vector v;
Entity *other;
Vector norm;
RadiusJitter *jitter;
float f1, f2;
Entity *owner;
other = ev->GetEntity(1);
if(other)
{
if(other->isSubclassOf(Teleporter))
return;
if(other->entnum == this->owner)
return;
}
CancelEventsOfType(EV_PBolt_EnergyTrail);
CancelEventsOfType(EV_PBolt_Explode);
setSolidType(SOLID_NOT);
setMoveType(MOVETYPE_NONE);
if(HitSky())
{
PostEvent(EV_Remove, 0);
return;
}
owner = G_GetEntity(this->owner);
// here's the different explosion code for the bob's bolts
if(!owner->isClient())
{
damg = 25 + (int)G_Random(10);
if(other && other->takedamage)
{
other->Damage(this, owner, damg, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_PLASMABOW, -1, -1, 1.0f);
}
surfaceManager.DamageSurface (&level.impact_trace, damg, owner);
v = velocity;
v.normalize();
// don't do radius damage to the other, because all the damage
// was done in the impact
v = origin - v * 32;
num = ((float)charge)*0.005;
decay = 0.1 + ((float)charge)*0.0045;
// make the visual explosion
SpawnBowExplosion(v, 0);
charge = 100; // for view jitter
// make the view jitter for the explosion
f1 = 6;
f2 = 4;
jitter = new RadiusJitter;
jitter->Setup(v, damg + 40, 0.25, 0.2, f1, f1*1.5, 0.2, f2, f2*1.5);
// make the explosion sound
RandomPositionedSound(v, "impact_smallplasmaexplosion", 1.0, CHAN_AUTO, ATTN_NORM);
hideModel();
PostEvent(EV_Remove, 0.1);
return;
}
v = velocity;
v.normalize();
// don't do radius damage to the other, because all the damage
// was done in the impact
v = worldorigin - v * 24;
damg = 95 + (int)G_Random(30);
if(!deathmatch->value)
damg *= 1.5;
// hit a damagable entity, let's blow up now
if(other && other->takedamage)
{
other->Damage(this, owner, damg, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_PLASMABOW, -1, -1, 1.0f);
TempModel(NULL, v, "0 0 0", "sprites/cbshot.spr", 0, 0.5, 0.8f, TEMPMODEL_ANIMATE_ONCE|TEMPMODEL_ANIMATE_FAST, 2);
// make the view jitter for the explosion
jitter = new RadiusJitter;
jitter->Setup(v, 64, 0.25, 0.2, 6, 9, 0.2, 4, 6);
RandomPositionedSound(v, "impact_smallplasmaexplosion", 1.0, CHAN_AUTO, ATTN_NORM);
PostEvent(EV_Remove, 0.1);
return;
}
// stick it into the wall
setOrigin(v);
velocity = vec_zero;
surfaceManager.DamageSurface (&level.impact_trace, damg, owner);
RandomAnimate("stick", NULL);
// play sticking sound
RandomSound("snd_stick", 1.0, CHAN_AUTO, ATTN_NORM);
PostEvent(EV_PBolt_Explode, 1);
}
EXPORT_FROM_DLL void PBolt::Setup(Entity *owner, Vector pos, Vector dir, int firecharge)
{
Event *ev;
float fltcharge;
this->owner = owner->entnum;
edict->owner = owner->edict;
// set energy bolt's charge amount
charge = firecharge;
fltcharge = charge;
setMoveType(MOVETYPE_TOSS);
setSolidType(SOLID_BBOX);
edict->clipmask = MASK_SHOT;
SetGravityAxis(owner->gravaxis);
angles = dir.toAngles();
angles[PITCH] = -angles[PITCH];
angles[ROLL] = 45;
setAngles(angles);
if(charge < 0)
{
if(owner->isClient())
speed = 1800;
else
speed = 1000;
}
else
{
speed = 600 + 14*charge;
if(charge > 100)
speed *= 0.5;
}
velocity = dir * speed;
PostEvent(EV_PBolt_EnergyTrail, 0.1);
// set missile duration
ev = new Event(EV_PBolt_Explode);
ev->AddEntity(world);
if(charge > 100) // overloaded, so blow mid-air
PostEvent(ev, 0);
else
PostEvent(ev, 10);
if(owner->isClient())
setModel( "pbolt.def" );
else
setModel( "bobbolt.def" );
edict->s.renderfx |= RF_DLIGHT;
// pick the effect trail to use
if(charge < 0)
{
edict->s.effects |= EF_PLASMATRAIL2;
gravity = 0.15;
edict->s.color_r = 0.5;
edict->s.color_g = 1;
edict->s.color_b = 0.5;
edict->s.radius = 100;
}
else
{
edict->s.effects |= EF_PLASMATRAIL1;
gravity = 0.6 - fltcharge*0.004;
// adjust bolt glow according to charge
edict->s.color_r = fltcharge*0.003;
edict->s.color_g = 1;
edict->s.color_b = fltcharge*0.003;
if(charge > 100) // overloaded, so blow mid-air
edict->s.radius = 200;
else
edict->s.radius = 150;
}
setSize("-2 -2 -2", "2 2 2");
setOrigin(pos);
worldorigin.copyTo(edict->s.old_origin);
}
//=================================================================
// plasmabow weapon class
Event EV_PlasmaBow_ReleaseCheck("plasmabow_releasecheck");
Event EV_PlasmaBow_Full("plasmabow_full");
CLASS_DECLARATION(Weapon, PlasmaBow, "weapon_plasmabow");
ResponseDef PlasmaBow::Responses[] =
{
// charge release check
{&EV_PlasmaBow_ReleaseCheck, (Response)PlasmaBow::ReleaseCheck},
// retain full charge
{&EV_PlasmaBow_Full, (Response)PlasmaBow::FullRetain},
// actual firing from button release
{&EV_Weapon_Shoot, (Response)PlasmaBow::Shoot},
{NULL, NULL}
};
PlasmaBow::PlasmaBow()
{
SetModels("crossbow_w.def", "view_crossbow.def");
modelIndex("pbolt.def");
modelIndex("sprites/cbshot.spr");
SetAmmo("BulletPulse", 5, 90);
SetSecondaryAmmo("BulletPulse", 10, 0);
SetRank(65, 75);
SetType(WEAPON_2HANDED_HI);
dualmode = true;
primary_ammo_type = "BulletPulse";
secondary_ammo_type = "BulletPulse";
SetMinRange(50);
SetProjectileSpeed(500);
}
void PlasmaBow::SecondaryUse (Event *ev)
{
// switch to the shotgun
owner->useWeapon("Shotgun");
}
// called when attack button is first pressed
void PlasmaBow::Fire(void)
{
if (!ReadyToFire())
{
return;
}
if(!HasAmmoInClip())
{
CheckReload();
return;
}
// charge fire mode
if(weaponmode == PRIMARY)
{
UseAmmo(5);
drainammo = 1;
// init. charging state
weaponstate = WEAPON_CHARGING;
fullcycles = MAX_FULL_CYCLES;
chargetime = level.time;
CancelEventsOfType(EV_Weapon_DoneFiring);
RandomAnimate("charge", EV_PlasmaBow_Full);
// check for button release every 0.1 seconds
PostEvent(EV_PlasmaBow_ReleaseCheck, 0.1);
// this is just a precaution that we can re-trigger
NextAttack(5);
}
else // instant fire mode
{
UseAmmo(secondary_ammorequired);
weaponstate = WEAPON_FIRING;
chargetime = level.time + 10;
CancelEventsOfType(EV_Weapon_DoneFiring);
RandomAnimate("fire", EV_Weapon_DoneFiring);
// this is just a precaution that we can re-trigger
NextAttack(5);
}
last_attack_time = level.time;
}
void PlasmaBow::ReleaseCheck(Event *ev)
{
// check for owner dying or getting on a hoverbike
if((!owner) || (this != owner->CurrentWeapon()) || (((Player *)owner.Ptr())->GetHoverbike()) ||
(deathmatch->value == DEATHMATCH_MFD && owner->isClient() && owner->client->resp.informer))
{
//cancel weapon charging
CancelEventsOfType(EV_PlasmaBow_ReleaseCheck);
CancelEventsOfType(EV_PlasmaBow_Full);
weaponstate = WEAPON_READY;
StopAnimating();
DetachFromOwner();
return;
}
// check for attack button release
// also fire if you run out of ammo to charge up
if(!(((Player *)owner.Ptr())->Buttons() & BUTTON_ATTACK))
{
CancelEventsOfType(EV_PlasmaBow_Full);
weaponstate = WEAPON_FIRING;
RandomAnimate("fire", EV_Weapon_DoneFiring);
return;
}
PostEvent(EV_PlasmaBow_ReleaseCheck, 0.1);
}
void PlasmaBow::FullRetain(Event *ev)
{
// check for owner dying or getting on a hoverbike
if((!owner) || (((Player *)owner.Ptr())->GetHoverbike()))
{
CancelEventsOfType(EV_PlasmaBow_ReleaseCheck);
CancelEventsOfType(EV_PlasmaBow_Full);
weaponstate = WEAPON_READY;
StopAnimating();
return;
}
RandomAnimate("full", EV_PlasmaBow_Full);
}
// release button function
void PlasmaBow::Shoot(Event *ev)
{
PBolt *bolt;
Vector pos, dir, r, u;
float boltcharge;
assert(owner);
if(!owner)
return;
// calc. charge amount
boltcharge = level.time - chargetime;
if(boltcharge < 0)
{
boltcharge = -1;
}
else if(boltcharge >= (CHARGE_TIME + MAX_FULL_CYCLES*2))
{
boltcharge = 100; //bolt is overloaded
}
else
{
boltcharge = floor((boltcharge / CHARGE_TIME) * 100);
if(boltcharge > 100)
boltcharge = 100;
}
if(boltcharge >= 0)
{
// put owner into a firing animation
if(owner->isClient())
{
str name;
str prefix;
Player *player;
player = (Player *)owner.Ptr();
prefix = player->AnimPrefixForPlayer();
//
// append the prefix based on which weapon we are holding
//
prefix += str( player->AnimPrefixForWeapon() );
if ( ( player->GetXYspeed() > 20 ) )
{
int num;
int numframes;
if ( ( player->waterlevel > 2 ) || ( player->GetXYspeed() > 250 ) )
name = prefix + str("run_fire");
else
name = prefix + str("walk_fire");
num = gi.Anim_Random( player->edict->s.modelindex, name.c_str() );
if ( num != -1 )
{
numframes = gi.Anim_NumFrames( player->edict->s.modelindex, num );
if ( ( player->last_frame_in_anim + 1 ) == numframes )
player->edict->s.anim = num;
else
player->TempAnim( name.c_str(), NULL );
}
else
{
name = prefix + str("fire");
player->TempAnim( name.c_str(), NULL );
}
}
else
{
name = prefix + str("fire");
player->TempAnim( name.c_str(), NULL );
}
}
else
{
owner->TempAnim("fire", NULL);
}
}
GetMuzzlePosition( &pos, &dir, &r, &u);
pos -= (r*4 + u*4);
bolt = new PBolt;
bolt->Setup(owner, pos, dir, boltcharge);
if(boltcharge < 0)
NextAttack(1.2);
else if(fullcycles == 0)
NextAttack(1.5);
else
NextAttack(0.7);
}
//==================================================================
// And yae, from yonder, hither weapon doth seth fourth evil things...
class EXPORT_FROM_DLL EvilPlasmaBow : public PlasmaBow
{
public:
CLASS_PROTOTYPE( EvilPlasmaBow );
EvilPlasmaBow();
virtual void Shoot(Event *ev);
virtual void Fire(void);
};
CLASS_DECLARATION(PlasmaBow, EvilPlasmaBow, "weapon_evilplasmabow");
ResponseDef EvilPlasmaBow::Responses[] =
{
{&EV_Weapon_Shoot, (Response)EvilPlasmaBow::Shoot},
{NULL, NULL}
};
EvilPlasmaBow::EvilPlasmaBow()
{
}
void EvilPlasmaBow::Fire(void)
{
if (!ReadyToFire())
{
return;
}
if(!HasAmmoInClip())
{
CheckReload();
return;
}
UseAmmo(5);
// init. charging state
fullcycles = MAX_FULL_CYCLES;
chargetime = level.time - CHARGE_TIME;
CancelEventsOfType(EV_Weapon_DoneFiring);
weaponstate = WEAPON_READY;
RandomAnimate("fire", EV_Weapon_DoneFiring);
// this is just a precaution that we can re-trigger
NextAttack(5);
last_attack_time = level.time;
}
void EvilPlasmaBow::Shoot (Event *ev)
{
PlasmaBow::Shoot(ev);
NextAttack(0.1);
}