mirror of
https://git.code.sf.net/p/quake/prozac-qfcc
synced 2024-11-10 07:11:51 +00:00
585 lines
18 KiB
C++
585 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 (delta_z < 0)
|
|
delta_z = delta_z / -5;
|
|
delta = delta * 0.7;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
// calculate the velocity adjustment.
|
|
delta = normalize(delta);
|
|
delta = delta * percent * strength;
|
|
|
|
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
|
|
|