prozac-qfcc/airfist.qc

595 lines
18 KiB
C++

#include "defs.qh"
// AIRG_MAIN_START
/*
* File: horn.qc
* Date: 3 Jan 1997
*
* Description: Air gun main code.
*
* Copyright and Distribution Permissions
* --------------------------------------
*
* The modifications included in this archive are Copyright 1997, the Evolve team.
* The original QuakeC source is Copyright 1996, id software.
*
* Authors MAY NOT use these modifications as a basis for commercially available work.
*
* You may distribute this Quake modification in any electronic format as long as
* all the files in this archive remain intact and unmodified and are distributed
* together.
*
* QuakeC, Model and artwork can be reused, but credit for the individual developers on
* the AirFist team is required.
*
*/
// Spam Light - I changed how it decides which objects to push
// OfN - I fixed the "stuck in wall" stupid bug, and added more entities for airfist pushing
#define AIRG_STEPCONVERTEDTOFLY 2
#define AIRG_HIT 4
void(float nearAWall, float adjustForward, float adjustRight, float adjustUp, float positionRight, float spriteSpeed) hornBlastSprite;
float (entity e) hornInfront;
void() removeFlyMode;
$cd id1/progs/s_ablast
$frame ablast1 ablast2 ablast3 ablast4 ablast5 ablast6
float (entity thing) canairpush =
{
if (thing.classname == "player") {
return TRUE;
}
else if (thing.classname == "pipebomb")
{
thing.avelocity = '300 300 300';
return TRUE;
}
else if (thing.classname == "spike")
return TRUE;
else if (thing.classname == "grenade")
{
thing.avelocity = '300 300 300';
return TRUE;
}
else if (thing.classname == "rocket")
return TRUE;
else if (thing.classname == "caltrop")
{
thing.avelocity = '500 500 500';
return TRUE;
}
#ifdef pushable_army
else if (thing.classname == "monster_army")
return TRUE;
#endif
#ifdef pushable_fiend
else if (thing.classname == "monster_demon1")
return TRUE;
#endif
#ifdef pushable_scrag
else if (thing.classname == "monster_wizard")
return TRUE;
#endif
else if (thing.classname == "detpack" && no_detpush == 0)
{
thing.avelocity = '300 300 300';
thing.movetype = MOVETYPE_BOUNCE;
return TRUE;
}
return FALSE;
};
// The Main code for the AirFist that is called when the AirFist is fired.
void() launch_horn =
{
// Local variables used in the function
local entity e;
local vector delta;
local vector dir;
local float eSpeed;
local float dist;
local float percent;
local float ldmg;
local float nearAWall;
// Constants used in the control of how the AirFist is works
// Maximum strength of the AirFist. The strength of the affected entity's
// movement is based on the distance from the center of the shot.
local float strength;
strength = AIRGUN_STRENGTH; // full power of weapon (now same as recoil - GR)
// Maximum damage that can be incurred by the AirFist. The actual entity's
// damage is based on the distance from the center of the shot.
local float inDamage;
inDamage = 30; // full damage of weapon GR was 20
// Range of the AirFist blast.
local float inRange;
// Number of bubbles generated when the AirFist is fired under water.
local float numBubbles = 3; // number of bubbles generated in the water
// Recoil strength of the AirFist on the player that shot the weapon.
local float recoil = AIRGUN_STRENGTH; // recoil strength of the gun
// Reshot time for the airgun attack in seconds.
local float attackTime = 0.5;
// Max number of times that the weapon can fired in a period of time.
local float maxFireRate = 5;
// The maximum number of seconds that maxFireRate can fire in. If the
// player has reached the maxFireRate in under the shotTimeout then all
// shots up to the shotTimeout are "failed" shots that don't do anything.
// e.g. time to fire is 0.5 seconds.
// maxFireRate is 5.
// shotTimeout is 5.5 seconds.
//
// If 5 shots where fired at the max rate this would take 2.5 seconds.
// So there would be a 3 seconds wait before the AirFist will fire again.
// Any fire rate greater than 2.5 seconds will incure a smaller or no
// wait time. Any shots fired in that wait time are "failed" shots that
// will do nothing.
local float shotTimeout = 5.5; // number of seconds for the maxFireRate
// The AirFist fire codde:
//Set initial range
inRange = 400;
//Set intital recoil
// By default, assume not near a wall.
nearAWall = 0;
// Make such that all previous attack code completes.
if (!self.button0)
{player_run ();return;} // Complete any other attacks first.
// How long the fire time is.
self.attack_finished = time + attackTime;
// First attack in the shotTimeout period
if(self.AIRG_FireCount == 0 || self.AIRG_Timeout < time)
{
// Set count and timeout length
self.AIRG_Timeout = time + shotTimeout + attackTime;
self.AIRG_FireCount = 1;
}
// Max Fire Rate reached, so this is a failed shot.
else if(self.AIRG_FireCount >= maxFireRate)
{
// play failed AirFist sound
if(self.waterlevel > 2)
{ // below the water, play under water sound
sound (self, CHAN_AUTO, "weapons/agwfail.wav", 1, ATTN_NORM);
}
else
{ // play above water sound
sound (self, CHAN_AUTO, "weapons/agfail.wav", 1, ATTN_NORM);
}
// play Failed AirFist animation
player_failedairgun1();
// and get out of here.
return;
}
else
{
// Count number of shots.
self.AIRG_FireCount = self.AIRG_FireCount + 1;
}
if(self.waterlevel > 2) // if under water, change the variables
{
// reduced the range by %20
inRange = inRange * 0.8;
// double the damage possible
inDamage = inDamage * 2;
}
// Get all the entity's in the shot range
makevectors(self.v_angle);
e = findradius(self.origin, inRange);
while (e)
{
// This is the exclusion code. It excludes everything that is "Illegal" to
// move. eg. doors.
// You may find this code looks funny, it is the same is doing
// a if( exp & exp & exp & exp) but this way is faster.
// (read the QuakeC manual for reason).
// only affect monsters and projectiles
//if (visible(e)) // must be visible AND // - OfN candamage
if(CanDamage(e,self)) // - OfN - For forcefields
if(self.waterlevel > 2 || hornInfront(e)) // infront of self or anywhere in the water AND
if(e.movetype != MOVETYPE_NONE) // anything that can move
if(e.movetype != MOVETYPE_PUSH)
if(e.movetype != MOVETYPE_NOCLIP)
// AIRG_EXCLUDE_START
//if((e.AIRG_Flags & AIRG_EXCLUDEENTITY) != AIRG_EXCLUDEENTITY) // custom exclusion (like laser fire)
// AIRG_EXCLUDE_END
if (canairpush(e)) //Can only push things in canairpush
if(e != self) // but not myself!!
{
e.AIRG_Flags |= AIRG_HIT;
e.AIRG_FlyTracker = self; // for stuff we airfist that hurts, don't want the orig firer to get tk strikes.
// if flying creature and movetype is step then change to fly
// NOTE: For some reason the normal quakeC, flying monsters has set a
// movetype of MOVETYPE_STEP which means that changing the velocity
// does not do anything. Our workaround was to change the movetype
// to MOVETYPE_FLY. Flying monsters where then affected by
// velocity. In all our testing we found that this only affected the
// entity's when they died. When dead, they fall UP instead of down.
// Other than this they did not seam to have any adverse affect. If you
// find that you have trouble with this code, let us know.
if(e.flags & FL_FLY)
{
if(e.movetype == MOVETYPE_STEP)
{
// set so we can affect velocity
e.movetype = MOVETYPE_FLY;
// create a entity to remove the MOVETYPE_FLY when we are done with it.
// the only problem with using this method is that while the entity
// is dead but the tracker has not converted it back to STEP, the
// entity will fall up until changed. For a generic method, we
// at the Evolve team can live with that.
e.AIRG_FlyTracker = spawn();
e.AIRG_Flags = e.AIRG_Flags + AIRG_STEPCONVERTEDTOFLY;
e.AIRG_FlyTracker.owner = e;
e.AIRG_FlyTracker.nextthink = time + 2;
e.AIRG_FlyTracker.think = removeFlyMode;
}
else if(e.AIRG_Flags & AIRG_STEPCONVERTEDTOFLY)
{
// We have already converted this one, just extend the time to convert
e.AIRG_FlyTracker.nextthink = time + 2;
}
}
// Calculate the distance from the entity.
delta = e.origin - self.origin + self.view_ofs;
dist = vlen(delta);
// Convert distance to a percentage.
percent = (inRange - dist) / inRange;
if (e.flags & FL_ONGROUND && !(e.classname == "player" && e.cutf_items & CUTF_HWGUY))
{
// We biased the up direction when entity is on the ground.
// Looks cooler and small entitys (heath, etc) go somewhere instead
// of along the ground (cas there below the line of sight).
// If on the ground, makem go up, up, and away
// But this makes no sense..
if (e.classname == "player" || IsMonster(e))
{
if (delta_z < 0)
delta_z = delta_z / -5;
delta = delta * 0.7;
} else {
if (delta_z < 0)
delta_z = delta_z / -2;
delta = delta * 0.7;
if (delta_z < 100)
delta_z = 100;
}
}
delta = normalize(delta);
delta *= percent * strength;
if (e.classname == "player") {
if (e.cutf_items & CUTF_HWGUY)
delta *= 0.3;
if (e.cutf_items & CUTF_GYMNAST)
delta *= 2;
}
else if (e.classname == "monster_wizard" || e.classname == "detpack")
delta *= 3;
else if (!IsMonster(e))
delta *= 5;
if(self.waterlevel > 2) // if under water, change the blast amount
{
// if within 1/2 radius of the blast
if(percent >= 0.50)
if(e.classname == "player") // and is a player
if (self.radsuit_finished == 0) // and not wearing bio
{
// that's it, no more air for him, chock time!!!
self.air_finished = time - 1;
}
if(hornInfront(e)) // infront of self
{
// reduce by %20
delta = delta * 0.80;
}
else // All other entity's are hit by water movement.
{
// reduce by %50
delta = delta * 0.50;
// reduce the damage possible as well
percent = percent * 0.50;
}
}
if(e.movetype == MOVETYPE_FLYMISSILE)
{
// If its a missile, change the direction but keep the same speed.
eSpeed = vlen(e.velocity);
e.velocity = normalize(delta) * eSpeed;
}
else
{
// Apply the velocity adjustment
e.velocity = e.velocity + delta;
//move them 1 unit so there's no friction
if (e.flags & FL_ONGROUND)
if (e.classname != "player" || !(e.cutf_items & CUTF_HWGUY))
{
checkmove(e.origin, e.mins, e.maxs, e.origin + normalize(e.velocity), MOVE_NORMAL, e);
setorigin(e, e.origin + normalize(e.velocity)*trace_fraction); // - OfN - And make them stuck in wall? :) nope..
e.flags &= ~FL_ONGROUND;
}
// calculate the damage amount
ldmg = percent * inDamage;
if (e.classname == "player") {
if (e.cutf_items & CUTF_HWGUY) // hwguy can't get knocked, but it hurts more
ldmg *= 2;
if (e.cutf_items & CUTF_GYMNAST) // gymnast goes with the flow, less damage
ldmg *= 0.3;
}
checkmove(e.origin, e.mins, e.maxs, e.origin + e.velocity *0.2, MOVE_NORMAL, e);
ldmg *= 1.3 - trace_fraction; // extra hurt for airfist into wall!
// and less for not
// This section of code is to even of the "figure momentum add" in the
// T_Damage function that recoils damaged entity's away from the attacker.
// NOTE: If that section of code in T_Damage changesm then this will have to
// change.
if(e.movetype != MOVETYPE_WALK) // to even out the T_Damage "figure momentum add"
{
dir = e.origin - (self.absmin + self.absmax) * 0.5;
dir = normalize(dir);
e.velocity = e.velocity + dir * ldmg * 8;
}
// Apply damage to the entity.
deathmsg = DMSG_AIRG;
if(self.waterlevel > 2)
deathmsg = DMSG_AIRG_WATER;
T_Damage(e, self, self, ldmg);
}
}
e = e.chain;
}
if(self.waterlevel > 2)
{ // below the water, produce bubbles
DeathBubbles(numBubbles);
}
// check if near a wall
makevectors(self.v_angle);
dir = self.origin + self.view_ofs;
traceline (dir, dir + v_forward * 64, FALSE, self);
if (trace_fraction != 1.0 && !trace_ent.takedamage)
{
nearAWall = 1;
}
// Produce the AirFist fire blast sprites to the top left and right of the
// self's view.
//void(float nearAWall, float adjustForward, float adjustRight, float adjustUp, float positionRight, float spriteSpeed) hornBlastSprite =
if(self.waterlevel > 2)
{
// under water, go slower (%50 slower)
hornBlastSprite(nearAWall, 50, -50, 300, -20, 0.50);
hornBlastSprite(nearAWall, 50, 50, 300, 20, 0.50);
}
else
{
// above water, normal speed
hornBlastSprite(nearAWall, 50, -50, 300, -20, 1.0);
hornBlastSprite(nearAWall, 50, 50, 300, 20, 1.0);
}
if(nearAWall)
{ // hit wall, Bounce based on the distance from the wall
recoil = recoil + (recoil * (1.0 - trace_fraction)); //now recoil is in range strength - strengh*2
}
if (self.cutf_items & CUTF_HWGUY)
recoil *= 0.3;
else if (self.cutf_items & CUTF_GYMNAST)
recoil *= 2;
// recoil self
self.velocity = self.velocity + v_forward * recoil * -1;
if (self.flags & FL_ONGROUND)
{
// If self on the ground, raise me up so that the velocity will
// take affect.
// raise the bugger a bit
checkmove(self.origin, self.mins, self.maxs, self.origin + normalize(self.velocity), MOVE_NORMAL, self);
setorigin(self, self.origin + normalize(self.velocity) * trace_fraction); // - OfN - And make them stuck in wall?
self.flags = self.flags - FL_ONGROUND;
}
if(self.waterlevel > 2)
{ // below the water, play under water sound
sound (self, CHAN_AUTO, "weapons/agwater.wav", 1, ATTN_NORM);
}
else
{ // play above water sound
sound (self, CHAN_AUTO, "weapons/agfire.wav", 1, ATTN_NORM);
}
// AirFist gun frame animation
makeImmune(self, time + 4); //- OfN - make immune to speed cheat check as the airfist increases our velocity
player_airgun1();
};
// modification of infront() from ai.qc to be tighter
float (entity e) hornInfront =
{
local vector vec;
local float dot;
makevectors (self.v_angle);
vec = normalize (e.origin - self.origin - self.view_ofs);
dot = vec * v_forward;
if ( dot > 0.8 )
{
return TRUE;
}
return FALSE;
};
// The AirFist fire blast sprite animation code.
void() run_ablast1 =[$ablast1, run_ablast2 ] {};
void() run_ablast2 =[$ablast2, run_ablast3 ] {};
void() run_ablast3 =[$ablast3, run_ablast4 ] {};
void() run_ablast4 =[$ablast4, run_ablast5 ] {};
void() run_ablast5 =[$ablast5, run_ablast6 ] {};
void() run_ablast6 =[$ablast6, run_ablast1 ]
{
remove(self);
};
// AirFist fire blast sprite animation under water (slowed down).
void() run_ablastWater1 =[$ablast1, run_ablastWater2 ] {};
void() run_ablastWater2 =[$ablast1, run_ablastWater3 ] {};
void() run_ablastWater3 =[$ablast2, run_ablastWater4 ] {};
void() run_ablastWater4 =[$ablast2, run_ablastWater5 ] {};
void() run_ablastWater5 =[$ablast3, run_ablastWater6 ] {};
void() run_ablastWater6 =[$ablast3, run_ablastWater7 ] {};
void() run_ablastWater7 =[$ablast4, run_ablastWater8 ] {};
void() run_ablastWater8 =[$ablast4, run_ablastWater9 ] {};
void() run_ablastWater9 =[$ablast5, run_ablastWater10 ] {};
void() run_ablastWater10 =[$ablast5, run_ablastWater1 ]
{
remove(self);
};
// Create a AirFist blast sprite
void(float nearAWall, float adjustForward, float adjustRight, float adjustUp, float positionRight, float spriteSpeed) hornBlastSprite =
{
local entity sprite, oldself;
oldself = self;
// Create the blast sprite and sets the variables.
sprite = spawn();
sprite.solid = SOLID_NOT;
if(oldself.waterlevel > 2)
{ // below the water, play under water animation
sprite.movetype = MOVETYPE_NOCLIP;
}
else
{ // above water, play normal animation
sprite.movetype = MOVETYPE_BOUNCE;
}
setmodel(sprite, "progs/s_ablast.spr");
// Set the velocity based on the parameters passed
sprite.velocity =
v_forward * random() * adjustForward +
v_right * random() * adjustRight + // [MWH:01/12/97] Reduced left/right/up/down variablilty
v_up * random() * adjustUp;
// set Speed
sprite.velocity = sprite.velocity * spriteSpeed;
if(nearAWall)
{
// If near a wall, blast in face.
setorigin(sprite, self.origin + self.view_ofs + (v_right * positionRight)); // [MWH:01/12/97] raised origin a bit
}
else
{
// Not near a wall, so set start position in front of the player.
setorigin(sprite, self.origin + self.view_ofs + (v_forward * 30) + (v_right * positionRight)); // [MWH:01/12/97] raised origin a bit
}
setsize(sprite, '-8 -8 -8', '8 8 8');
// play sprite animation.
self = sprite;
if(oldself.waterlevel > 2)
{ // below the water, play under water animation
run_ablastWater1();
}
else
{ // above water, play normal animation
run_ablast1();
}
self = oldself;
};
void() removeFlyMode =
{
// convert the movetype back to MOVETYPE_STEP cas we are finished with
// the velocity change (we should be anyway).
self.owner.movetype = MOVETYPE_STEP;
self.owner.AIRG_Flags = self.owner.AIRG_Flags - AIRG_STEPCONVERTEDTOFLY;
// don't need self anymore, remove self.
remove(self);
};
// AIRG_MAIN_END