".
+
+The names must be the full pathname after the basedir, like
+"models/weapons/v_launch/tris.md3" or "players/male/tris.md3"
+
+Testmodel will create a fake entity 100 units in front of the current view
+position, directly facing the viewer. It will remain immobile, so you can
+move around it to view it from different angles.
+
+Testgun will cause the model to follow the player around and supress the real
+view weapon model. The default frame 0 of most guns is completely off screen,
+so you will probably have to cycle a couple frames to see it.
+
+"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the
+frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in
+q3default.cfg.
+
+If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let
+you adjust the positioning.
+
+Note that none of the model testing features update while the game is paused, so
+it may be convenient to test with deathmatch set to 1 so that bringing down the
+console doesn't pause the game.
+
+=============================================================================
+*/
+
+/*
+=================
+CG_TestModel_f
+
+Creates an entity in front of the current position, which
+can then be moved around
+=================
+*/
+void CG_TestModel_f (void) {
+ vec3_t angles;
+
+ memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) );
+ if ( trap_Argc() < 2 ) {
+ return;
+ }
+
+ Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH );
+ cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
+
+ if ( trap_Argc() == 3 ) {
+ cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) );
+ cg.testModelEntity.frame = 1;
+ cg.testModelEntity.oldframe = 0;
+ }
+ if (! cg.testModelEntity.hModel ) {
+ CG_Printf( "Can't register model\n" );
+ return;
+ }
+
+ VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin );
+
+ angles[PITCH] = 0;
+ angles[YAW] = 180 + cg.refdefViewAngles[1];
+ angles[ROLL] = 0;
+
+ AnglesToAxis( angles, cg.testModelEntity.axis );
+ cg.testGun = qfalse;
+}
+
+/*
+=================
+CG_TestGun_f
+
+Replaces the current view weapon with the given model
+=================
+*/
+void CG_TestGun_f (void) {
+ CG_TestModel_f();
+ cg.testGun = qtrue;
+ cg.testModelEntity.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON;
+}
+
+
+void CG_TestModelNextFrame_f (void) {
+ cg.testModelEntity.frame++;
+ CG_Printf( "frame %i\n", cg.testModelEntity.frame );
+}
+
+void CG_TestModelPrevFrame_f (void) {
+ cg.testModelEntity.frame--;
+ if ( cg.testModelEntity.frame < 0 ) {
+ cg.testModelEntity.frame = 0;
+ }
+ CG_Printf( "frame %i\n", cg.testModelEntity.frame );
+}
+
+void CG_TestModelNextSkin_f (void) {
+ cg.testModelEntity.skinNum++;
+ CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
+}
+
+void CG_TestModelPrevSkin_f (void) {
+ cg.testModelEntity.skinNum--;
+ if ( cg.testModelEntity.skinNum < 0 ) {
+ cg.testModelEntity.skinNum = 0;
+ }
+ CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
+}
+
+static void CG_AddTestModel (void) {
+ int i;
+
+ // re-register the model, because the level may have changed
+ cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
+ if (! cg.testModelEntity.hModel ) {
+ CG_Printf ("Can't register model\n");
+ return;
+ }
+
+ // if testing a gun, set the origin reletive to the view origin
+ if ( cg.testGun ) {
+ VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin );
+ VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] );
+ VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] );
+ VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] );
+
+ // allow the position to be adjusted
+ for (i=0 ; i<3 ; i++) {
+ cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value;
+ cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value;
+ cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value;
+ }
+ }
+
+ trap_R_AddRefEntityToScene( &cg.testModelEntity );
+}
+
+//============================================================================
+
+/*
+=================
+CG_CalcVrect
+
+Sets the coordinates of the rendered window
+=================
+*/
+static void CG_CalcVrect (void) {
+ int size;
+
+ // the intermission should allways be full screen
+ if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
+ size = 100;
+ } else {
+ // bound normal viewsize
+ if (cg_viewsize.integer < 30) {
+ trap_Cvar_Set ("cg_viewsize","30");
+ size = 30;
+ } else if (cg_viewsize.integer > 100) {
+ trap_Cvar_Set ("cg_viewsize","100");
+ size = 100;
+ } else {
+ size = cg_viewsize.integer;
+ }
+
+ }
+ cg.refdef.width = cgs.glconfig.vidWidth*size * 0.01;
+ cg.refdef.width &= ~1;
+
+ cg.refdef.height = cgs.glconfig.vidHeight*size * 0.01;
+ cg.refdef.height &= ~1;
+
+ cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width) * 0.5;
+ cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height) * 0.5;
+}
+
+//==============================================================================
+
+/*==============================================================================
+New Third Person Camera Code
+TiM: Based off of the logic of the camera code in Raven's Jedi Knight series,
+however written by me, and tuned down since EF may not be that fast to handle
+all of it.
+
+Although I consider copying someone else's logic to be somewhat lame, my programming
+skills are not yet at the point I could do this by myself. I understand the concept behind
+how the camera is offset and how the relevant angles are calculated, but am unsure how this
+is coupled with a non-linear interpolation algorithm.
+I am hoping that being able to trace how the JK code works will enlighten me to some
+furthur graphical programming methodology
+
+Meanings of CG variables from JKA:
+
+Notes on the camera viewpoint in and out...
+
+cg.refdef.vieworg
+--at the start of the function holds the player actor's origin (center of player model).
+--it is set to the final view location of the camera at the end of the camera code.
+cg.refdef.viewangles
+--at the start holds the client's view angles
+--it is set to the final view angle of the camera at the end of the camera code.
+*/
+
+//TiM: Static Global Variables
+#define CAMERA_DAMP_INTERVAL 50
+#define CAMERA_SIZE 4
+#define MASK_CAMERACLIP (MASK_SOLID|CONTENTS_PLAYERCLIP)
+
+//Bounding Boxes for volume traces
+static vec3_t cameraMins = { -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE };
+static vec3_t cameraMaxs = { CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE };
+
+//Directional Vectors
+vec3_t cameraForward, cameraUp, cameraRight;
+
+vec3_t cameraFocusAngles, cameraFocusLoc; //location and view angles of the player's head
+vec3_t cameraIdealTarget, cameraIdealLoc; //location and view angles of where the camera should be
+vec3_t cameraCurTarget={0,0,0}, cameraCurLoc={0,0,0}; //Current view and location of camera
+vec3_t CameraOldLoc={0,0,0}, cameraNewLoc={0,0,0}; //Backup data for the lerp func
+
+int cameraLastFrame=0;
+
+float cameraLastYaw=0;
+float cameraStiffFactor=0.0f;
+
+qboolean freeRotate;
+
+/*
+===============
+CG_CalcIdealThirdPersonViewTarget
+TiM:
+First Function : Calculate the point we should be looking at
+as long as nothing is in the way
+===============
+*/
+static void CG_CalcIdealThirdPersonViewTarget ( void ) {
+ //ATM, vieworg is the base of the feet
+ VectorCopy ( cg.refdef.vieworg, cameraFocusLoc );
+
+ //offset the Z value so it lines up with the eyeheight of the player
+ if ( freeRotate )
+ VectorMA( cameraFocusLoc, (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height, cameraUp, cameraFocusLoc );
+ else
+ cameraFocusLoc[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; //cg.snap->ps.viewheight
+
+ // emote based model offset
+ if ( cg.predictedPlayerState.stats[EMOTES] & EMOTE_LOWER )
+ {
+ vec3_t yawForward, forward;
+ VectorSet( yawForward, 0, cg.predictedPlayerEntity.pe.legs.yawAngle, 0 );
+ AngleVectors( yawForward, forward, NULL, NULL );
+
+ VectorMA( cameraFocusLoc, cgs.clientinfo[cg.predictedPlayerState.clientNum].modelOffset, forward, cameraFocusLoc );
+ }
+
+ //Transfer FocusLoc to CamTarget and use that from there
+ VectorCopy( cameraFocusLoc, cameraIdealTarget );
+
+ //Add in Horz offset
+ if ( cg.zoomedLeft ) {
+ float ratio = cg_thirdPersonRange.value * 0.02f;
+ if ( ratio > 1.0f ) ratio = 1.0f;
+
+ cg_thirdPersonHorzOffset.value += ( cg_thirdPersonZoomRate.value * ratio * 0.075 );
+ }
+ if ( cg.zoomedRight ) {
+ float ratio = cg_thirdPersonRange.value * 0.02f;
+ if ( ratio > 1.0f ) ratio = 1.0f;
+
+ cg_thirdPersonHorzOffset.value -= ( cg_thirdPersonZoomRate.value * ratio * 0.075 );
+ }
+
+ if ( cg_thirdPersonHorzOffset.value ) {
+ VectorMA( cameraIdealTarget, -cg_thirdPersonHorzOffset.value, cameraRight, cameraIdealTarget );
+ }
+
+ //Add in the vertOffset
+ if ( cg.zoomedUp ) {
+ float ratio = cg_thirdPersonRange.value * 0.02f;
+ if ( ratio > 1.0f ) ratio = 1.0f;
+
+ cg_thirdPersonVertOffset.value += ( cg_thirdPersonZoomRate.value * ratio * 0.075 );
+ }
+ if ( cg.zoomedDown ) {
+ float ratio = cg_thirdPersonRange.value * 0.02f;
+ if ( ratio > 1.0f ) ratio = 1.0f;
+
+ cg_thirdPersonVertOffset.value -= ( cg_thirdPersonZoomRate.value * ratio * 0.075 );
+ }
+ if ( cg_thirdPersonVertOffset.value ) {
+ if ( freeRotate )
+ VectorMA( cameraFocusLoc, cg_thirdPersonVertOffset.value, cameraUp, cameraFocusLoc );
+ else
+ cameraIdealTarget[2] += cg_thirdPersonVertOffset.value;
+ }
+}
+
+/*
+===============
+CG_CalcIdealThirdPersonViewLocation
+TiM:
+Second Function : Calculate the point we should be looking out
+from given all is good :)
+===============
+*/
+static void CG_CalcIdealThirdPersonViewLocation ( void ) {
+ //float offset;
+
+ if ( cg.zoomedForward ) {
+ cg_thirdPersonRange.value -= ( cg_thirdPersonZoomRate.value * 0.1 );
+ }
+ else if ( cg.zoomedBackward ) {
+ cg_thirdPersonRange.value += ( cg_thirdPersonZoomRate.value * 0.1 );
+ }
+
+ VectorMA( cameraIdealTarget, -cg_thirdPersonRange.value, cameraForward, cameraIdealLoc );
+}
+
+/*
+===============
+CG_ResetThirdPersonViewDamp
+TiM:
+Third Function : Reset all of the lerp and
+set it back to normal
+===============
+*/
+
+void CG_ResetThirdPersonViewDamp ( void ) {
+ trace_t tr;
+
+ //Clamp the pitch, so it won't cause bugs
+ if ( !freeRotate )
+ cameraFocusAngles[PITCH] = Com_Clamp( -89.0f, 89.0f, cameraFocusAngles[PITCH] );
+
+ //Take our look directions and calculate vector angles
+ AngleVectors( cameraFocusAngles, cameraForward, cameraRight, cameraUp );
+
+ //Calc ideal cam target now
+ CG_CalcIdealThirdPersonViewTarget();
+
+ //Calc ideal cam view loaction now
+ CG_CalcIdealThirdPersonViewLocation();
+
+ //Take our ideal locations, and then set them to our active variables
+ VectorCopy( cameraIdealLoc, cameraCurLoc );
+ VectorCopy( cameraIdealTarget, cameraCurTarget );
+
+ //Do a trace from the player's head out to the main location, in case something may be in the way
+ //This is mainly for stopping things like the camera going thru ceilings n stuff
+ CG_Trace( &tr, cameraFocusLoc, cameraMins, cameraMaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_CAMERACLIP );
+ VectorCopy( tr.endpos, cameraCurTarget );
+
+ //Do a trace from the target to our current location to see if there's anything potentially
+ //blocking our view
+ CG_Trace( &tr, cameraCurTarget, cameraMins, cameraMaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_CAMERACLIP );
+ VectorCopy( tr.endpos, cameraCurLoc );
+
+ //Initialise the lerp data
+ cameraLastFrame = cg.time;
+ cameraLastYaw = cameraFocusAngles[YAW];
+ cameraStiffFactor = 0.0f;
+}
+
+/*
+======================
+CG_UpdateThirdPersonTargetDamp
+
+TiM: From the looks of this, target damp lags
+the position of the camera behind a certain amount
+when the player moves. The end result being a more
+fluid movement. :)
+Still trying to figure out how lerp actually works.
+======================
+*/
+static void CG_UpdateThirdPersonTargetDamp ( void ) {
+ trace_t tr;
+ vec3_t targetDiff; //difference between our aimed target and current target
+ float dampFactor, dampTime, dampRatio;
+
+ //Just to be on the safe side, let's set the current ideal data again
+ CG_CalcIdealThirdPersonViewTarget();
+
+ //if the CVAR says no delay, or if we're currently teleporting, don't do the lerp (Or we could make the player sick lol)
+ if ( cg_thirdPersonTargetDamp.value >= 1.0 || cg.thisFrameTeleport || cg.nextFrameTeleport || freeRotate || cg.thirdPersonNoLerp ) {
+ VectorCopy( cameraIdealTarget, cameraCurTarget );
+ }
+ else if ( cg_thirdPersonTargetDamp.value >= 0.0 ) { //Okay, all's good, so let's get lerping lol
+ //First, let's get the difference between where we're at, and where we should be
+ VectorSubtract( cameraIdealTarget, cameraCurTarget, targetDiff );
+
+ //Ugh.... maaaaath >.<
+ //The JKA code says the equation is "(Damp)^(time)", so I'm guessing it's inverse exponential to
+ //get that cool slowy down effect :)
+ if ( !freeRotate )
+ dampFactor = 1.0 - cg_thirdPersonTargetDamp.value; //yeh, I guess this is the inverse exponential bit.
+ else
+ dampFactor = 1.0 - Q_fabs( cameraFocusAngles[PITCH] ) / 90.0f;
+
+ dampTime = (float)(cg.time - cameraLastFrame) * (1.0f/(float)CAMERA_DAMP_INTERVAL); //chikushou! I don't know how this time factor is caluclated O_o
+
+ //Square this number for each unit of dampTime
+ dampRatio = Q_powf( dampFactor, dampTime);
+
+ //Okay, so our current position is calulated as the difference * -ratio + ideal... O_o
+ VectorMA( cameraIdealTarget, -dampRatio, targetDiff, cameraCurTarget );
+ }
+
+ //Now, let's make sure we didn't lerp our way into a wall or summin
+ CG_Trace( &tr, cameraFocusLoc, cameraMins, cameraMaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_CAMERACLIP );
+ if ( tr.fraction < 1.0 ) {
+ VectorCopy( tr.endpos, cameraCurTarget );
+ }
+}
+
+/*
+===============
+CG_UpdateThirdPersonCameraDamp
+
+TiM: Okay, since the above function lagged
+the camera's position, logic stands to reason
+this one lags the camera's actual angles.
+With the dynamic crosshair enabled, this should look pretty damn sweet. :)
+Looks somewhat similar to Target Damp
+================
+*/
+static void CG_UpdateThirdPersonCameraDamp ( void ) {
+ trace_t tr;
+ vec3_t locationDiff;
+ float dampFactor=0.0, dampTime, dampRatio;
+
+ //Initialise our goal angle
+ CG_CalcIdealThirdPersonViewLocation();
+
+ //If we need to do any damping at all
+ if ( cg_thirdPersonCameraDamp.value != 0.0 ) {
+ float pitch;
+
+ //get pitch, and make it all positive. Direction don't matter here
+ pitch = Q_fabs( cameraFocusAngles[PITCH] );
+
+ //If we're floating and rotate all around, perform this so the damping isn't so extreme
+ /*if ( pitch > 89.0f ) {
+ pitch = 90.0f - ( pitch - 90.0f );
+ }*/
+
+ //The JKA code says these statments are to get it to damp less the more u look up.
+ //Makes sense. Still looking how tho lol
+ pitch /= 115.0; //magic number I guess lol.
+ dampFactor = (1.0-cg_thirdPersonCameraDamp.value) * ( pitch * pitch );
+
+ dampFactor += cg_thirdPersonCameraDamp.value;
+
+ //the stiff factor is based off speed, so faster yaw changes seem stiffer
+ if ( cameraStiffFactor > 0.0f ) {
+ dampFactor += ( 1.0 - dampFactor) * cameraStiffFactor;
+ }
+ }
+
+ //if our result meant no damping, or we're actively teleporting
+ //sigh I guess we'll need to disable dampin upon rotation. it causes absolute hell at the model's polar angles
+ if ( dampFactor >= 1.0 || cg.thisFrameTeleport || cg.nextFrameTeleport || freeRotate || cg.thirdPersonNoLerp ) {
+ VectorCopy( cameraIdealLoc, cameraCurLoc );
+ }
+ else if ( dampFactor >= 0.0 ) {
+ //First, let's get the difference between where we're at, and where we should be
+ VectorSubtract( cameraIdealLoc, cameraCurLoc, locationDiff );
+
+ //Ugh.... maaaaath >.<
+ //The JKA code says the equation is "(Damp)^(time)", so I'm guessing it's inverse exponential to
+ //get that cool slowy down effect :)
+ dampFactor = 1.0 - dampFactor; //yeh, I guess this is the inverse exponential bit.
+ dampTime = (float)(cg.time - cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); //chikushou! I don't know how this time factor is caluclated O_o
+
+ //Square this number for each unit of dampTime
+ dampRatio = Q_powf( dampFactor, dampTime);
+
+ //Okay, so our current position is calulated as the difference * -ratio + ideal... O_o
+ VectorMA( cameraIdealLoc, -dampRatio, locationDiff, cameraCurLoc );
+ }
+
+ //Now do a trace to see if we're all good for this loc
+ CG_Trace( &tr, cameraCurTarget, cameraMins, cameraMaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_CAMERACLIP );
+
+ //Now Raven added a huge hacky code tidbit at this stage regarding being on moving entities
+ //I'll see if I can get away without instituting it...
+ if ( tr.fraction < 1.0 ) {
+ VectorCopy( tr.endpos, cameraCurLoc );
+ }
+}
+
+/*
+===============
+CG_OffsetThirdPersonView
+
+TiM: The end is nigh!
+So, all of the funky code above is
+finally consolidated into this main function.
+
+Let's see if I can understand this in any way lol
+===============
+*/
+static void CG_OffsetThirdPersonView( void ) {
+ vec3_t diff;
+ float deltaYaw;
+ qboolean neg=qfalse;
+
+ cameraStiffFactor = 0.0f;
+
+ //TiM: change the math a tad if we're in free rotate mode
+ if ( cg.predictedPlayerEntity.currentState.eFlags & EF_FULL_ROTATE )
+ freeRotate = qtrue;
+ else
+ freeRotate = qfalse;
+
+ //copy in our raw data values
+ VectorCopy( cg.refdefViewAngles, cameraFocusAngles );
+
+ //Add a rotation offset for viewAngle
+ if ( cg.zoomAngleRight ) {
+ cg_thirdPersonAngle.value -= ( cg_thirdPersonZoomRate.value * 0.1 );
+ }
+ if ( cg.zoomAngleLeft ) {
+ cg_thirdPersonAngle.value += ( cg_thirdPersonZoomRate.value * 0.1 );
+ }
+ cameraFocusAngles[YAW] -= cg_thirdPersonAngle.value; //TiM - offset so it swings the right way lol
+
+ //Add in pitch
+ if ( cg.zoomPitchUp ) {
+ cg_thirdPersonPitchOffset.value += ( cg_thirdPersonZoomRate.value * 0.075 );
+ cg_thirdPersonPitchOffset.value = Com_Clamp( -89.0f, 89.0f, cg_thirdPersonPitchOffset.value );
+ }
+ if ( cg.zoomPitchDown ) {
+ cg_thirdPersonPitchOffset.value -= ( cg_thirdPersonZoomRate.value * 0.075 );
+ cg_thirdPersonPitchOffset.value = Com_Clamp( -89.0f, 89.0f, cg_thirdPersonPitchOffset.value );
+ }
+ cameraFocusAngles[PITCH] += cg_thirdPersonPitchOffset.value;
+
+ //if something messed up, or we're just starting, initiliaze sample
+ if ( cameraLastFrame == 0 || cameraLastFrame > cg.time ) {
+ CG_ResetThirdPersonViewDamp();
+ }
+ else {
+ //Cap the final angles :)
+ if ( !freeRotate ) {
+ cameraFocusAngles[PITCH] = Com_Clamp( -80.0, 89.0, cameraFocusAngles[PITCH] );
+ }
+
+ AngleVectors( cameraFocusAngles, cameraForward, cameraRight, cameraUp );
+
+ deltaYaw = fabs( cameraFocusAngles[YAW] - cameraLastYaw );
+ //if we exceeded our norms, stick it back
+ if (deltaYaw > 180.0f ) {
+ deltaYaw = fabs( deltaYaw - 360.0f );
+ }
+
+ cameraStiffFactor = deltaYaw / (float)(cg.time-cameraLastFrame);
+ if ( cameraStiffFactor < 1.0 ) {
+ cameraStiffFactor = 0.0;
+ }
+ else if ( cameraStiffFactor > 2.5 ) {
+ cameraStiffFactor = 0.75;
+ }
+ else {
+ cameraStiffFactor = (cameraStiffFactor-1.0f)*0.5f;
+ }
+ cameraLastYaw = cameraFocusAngles[YAW];
+
+ CG_UpdateThirdPersonTargetDamp();
+ CG_UpdateThirdPersonCameraDamp();
+ }
+
+ VectorSubtract( cameraCurTarget, cameraCurLoc, diff );
+
+ //if we're hitting something, use cameraForward to calc new angles
+ if ( VectorNormalize(diff) == 0 || diff[0] == 0 || diff[1] == 0 ) {
+ VectorCopy( cameraForward, diff );
+ }
+
+ //Hack-a-dood-do. vectoangles cannot comprehend if a player is upside-down.
+ //It assumes it's just an opposite direction vector, so everything is rendered the right way up. >.<
+ //To fix this, I'll hackily copy the viewangle pitch data, and then reset the angles afterwards
+
+ if ( freeRotate && Q_fabs( cg.refdefViewAngles[PITCH] ) > 90.0f )
+ neg = qtrue;
+
+ vectoangles( diff, cg.refdefViewAngles );
+
+ //Also if rotating, provide an offset when players turn fully upside down
+ if ( freeRotate && neg ) {
+ cg.refdefViewAngles[ROLL] -= 180; //AngleNormalize360( cg.refdefViewAngles[YAW] - 180);
+ }
+
+ /*if ( cg_thirdPersonHorzOffset.value != 0.0f ) {
+ AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
+ VectorMA( cameraCurLoc, cg_thirdPersonHorzOffset.value, cg.refdef.viewaxis[1], cameraCurLoc );
+ }*/
+
+ //And update our origin lol
+ VectorCopy( cameraCurLoc, cg.refdef.vieworg );
+
+ cameraLastFrame = cg.time;
+}
+
+#define FOCUS_DISTANCE 512 //512
+/*static void CG_OffsetThirdPersonView( void ) {
+ vec3_t forward, right, up;
+ vec3_t view;
+ vec3_t focusAngles;
+ trace_t trace;
+ static vec3_t mins = { -4, -4, -4 };
+ static vec3_t maxs = { 4, 4, 4 };
+ vec3_t focusPoint;
+ float focusDist;
+ float forwardScale, sideScale;
+ char medicrevive[32];
+ int medicrevive_int;
+ vec3_t camPlayerPos; //TiM
+
+ //cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight;
+ cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height;
+
+ VectorCopy( cg.refdefViewAngles, focusAngles );
+ VectorCopy( cg.refdef.vieworg, camPlayerPos); //Copy the values b4 we offset;
+
+ //RPG-X: TiM - Incorporated offsets so third person can be more dynamic
+ //Woo! I figured out how AngleVectors and VectorMA work!! ^_^
+ AngleVectors( cg.refdefViewAngles, NULL, right, NULL);
+ VectorMA( cg.refdef.vieworg, cg_thirdPersonHorzOffset.value, right, cg.refdef.vieworg );
+ //cg.refdef.vieworg[0] += cg_thirdPersonHorzOffset.value;
+ cg.refdef.vieworg[2] += cg_thirdPersonVertOffset.value;
+
+
+ // if dead, look at killer
+ //RPG-X: Fix camera movment when play dies with medics revive turned on
+ trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 );
+ medicrevive_int = atoi(medicrevive);
+
+ //TiM: Meh, you don't spin around to look at your killer in real life. O_o
+ //Plus, this screws up the model system :(
+ if(medicrevive_int == 1){
+ if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 1 ) {
+
+ focusAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle;
+ cg.refdefViewAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle;
+ //focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW];
+ //cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW];
+ }
+ }else{
+ if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) {
+
+ focusAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle;
+ cg.refdefViewAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle;
+ //focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW];
+ //cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW];
+ }
+ }
+
+ /*if ( focusAngles[PITCH] > 89.9 )
+ {
+ focusAngles[PITCH] = 89.9f; // don't go too far overhead - has to be under 90 or bad things happen
+ }
+ else if ( focusAngles[PITCH] < -89.9 ) //89 - Stop from going through legs
+ {
+ focusAngles[PITCH] = -89.9f;
+ }
+
+ if ( cg.refdefViewAngles[PITCH] > 89.9 )
+ {
+ cg.refdefViewAngles[PITCH] = 89.9f; // don't go too far overhead - has to be under 90 or bad things happen
+ }
+ else if ( cg.refdefViewAngles[PITCH] < -79.9 ) //89 - Stop from going through legs
+ {
+ cg.refdefViewAngles[PITCH] = -79.9f;
+ }
+
+ AngleVectors( focusAngles, forward, NULL, NULL );
+
+ VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint );
+
+ VectorCopy( cg.refdef.vieworg, view );
+
+ view[2] += 16;
+
+ //cg.refdefViewAngles[PITCH] *= 0.5;
+
+ AngleVectors( cg.refdefViewAngles, forward, right, up );
+
+ //VectorScale( forward, cg_thirdPersonAngle.value, normalize );
+ forwardScale = VectorNormalize( forward ); //cos( cg_thirdPersonAngle.value / 180 * M_PI );
+
+ ///VectorScale( right, cg_thirdPersonAngle.value, normalize );
+ sideScale = VectorNormalize( right );//sin( cg_thirdPersonAngle.value / 180 * M_PI );
+ VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view );
+ VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view );
+
+ // trace a ray from the origin to the viewpoint to make sure the view isn't
+ // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything
+
+ //TiM : Sometimes if the value of these variables is set to extreme numbers, they'll go thru walls. O_o
+ //This trace function is to fix that.
+ //If player is using these CVARs...
+ if ( cg_thirdPersonVertOffset.value != 0 || cg_thirdPersonHorzOffset.value != 0) {
+ //Do a trace from playermodel's head to our view location
+ CG_Trace( &trace, camPlayerPos, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
+
+ //Okay, the trace hit something... O_o
+ if ( trace.fraction != 1.0 ) {
+ //copy where it hit to our view origin. :)
+ VectorCopy( trace.endpos, cg.refdef.vieworg );
+ }
+ }
+
+ CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
+
+ if ( trace.fraction != 1.0 ) {
+ VectorCopy( trace.endpos, view );
+ view[2] += (1.0 - trace.fraction) * 32;
+ // try another trace to this position, because a tunnel may have the ceiling
+ // close enogh that this is poking out
+
+ CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
+ VectorCopy( trace.endpos, view );
+ }
+
+ VectorCopy( view, cg.refdef.vieworg );
+
+ // select pitch to look at focus point from vieword
+ VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint );
+ focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] );
+ if ( focusDist < 1 ) {
+ focusDist = 1; // should never happen
+ }
+ //cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist );
+ cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value;
+}*/
+
+// this causes a compiler bug on mac MrC compiler
+static void CG_StepOffset( void ) {
+ int timeDelta;
+
+ // smooth out stair climbing
+ timeDelta = cg.time - cg.stepTime;
+ if ( timeDelta < STEP_TIME ) {
+ cg.refdef.vieworg[2] -= cg.stepChange
+ * (STEP_TIME - timeDelta) / STEP_TIME;
+ }
+}
+
+/*
+===============
+CG_OffsetFirstPersonView
+
+===============
+*/
+static void CG_OffsetFirstPersonView( void ) {
+ float *origin;
+ float *angles;
+ float bob;
+ float ratio;
+ float delta;
+ float speed;
+ float f;
+ vec3_t predictedVelocity;
+ int timeDelta;
+ char medicrevive[32];
+ int medicrevive_int;
+
+ if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
+ return;
+ }
+
+ origin = cg.refdef.vieworg;
+ angles = cg.refdefViewAngles;
+
+ // emote based model offset
+ if ( cg.predictedPlayerState.stats[EMOTES] & EMOTE_LOWER )
+ {
+ vec3_t yawForward, forward;
+ VectorSet( yawForward, 0, cg.predictedPlayerEntity.pe.legs.yawAngle, 0 );
+ AngleVectors( yawForward, forward, NULL, NULL );
+
+ VectorMA( origin, cgs.clientinfo[cg.predictedPlayerState.clientNum].modelOffset, forward, origin );
+
+ //CG_Printf(S_COLOR_RED "%i\n", cgs.clientinfo[cg.predictedPlayerState.clientNum].modelOffset );
+ }
+
+ // if dead, fix the angle and don't add any kick
+ //RPG-X: Fix camera movment when play dies with medics revive turned on
+ trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 );
+ medicrevive_int = atoi(medicrevive);
+ if(medicrevive_int == 1){
+ if ( cg.snap->ps.stats[STAT_HEALTH] <= 1 ) {
+ angles[ROLL] = 40;
+ angles[PITCH] = -15;
+ //angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW];
+ angles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle;
+ origin[2] += cg.predictedPlayerState.viewheight;
+ return;
+ }
+ }else{
+ if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) {
+ angles[ROLL] = 40;
+ angles[PITCH] = -15;
+ //angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW];
+ angles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle;
+ origin[2] += cg.predictedPlayerState.viewheight;
+ return;
+ }
+ }
+
+ // add angles based on weapon kick
+ VectorAdd (angles, cg.kick_angles, angles);
+
+ // add angles based on damage kick
+ if ( cg.damageTime ) {
+ ratio = cg.time - cg.damageTime;
+ if ( ratio < DAMAGE_DEFLECT_TIME ) {
+ ratio /= DAMAGE_DEFLECT_TIME;
+ angles[PITCH] += ratio * cg.v_dmg_pitch;
+ angles[ROLL] += ratio * cg.v_dmg_roll;
+ } else {
+ ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME;
+ if ( ratio > 0 ) {
+ angles[PITCH] += ratio * cg.v_dmg_pitch;
+ angles[ROLL] += ratio * cg.v_dmg_roll;
+ }
+ }
+ }
+
+ // add pitch based on fall kick
+#if 0
+ ratio = ( cg.time - cg.landTime) / FALL_TIME;
+ if (ratio < 0)
+ ratio = 0;
+ angles[PITCH] += ratio * cg.fall_value;
+#endif
+
+ // add angles based on velocity
+ VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity );
+
+ delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]);
+ angles[PITCH] += delta * cg_runpitch.value;
+
+ delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]);
+ angles[ROLL] -= delta * cg_runroll.value;
+
+ // add angles based on bob
+
+ // make sure the bob is visible even at low speeds
+ speed = cg.xyspeed > 200 ? cg.xyspeed : 200;
+
+ delta = cg.bobfracsin * cg_bobpitch.value * speed;
+ if (cg.predictedPlayerState.pm_flags & PMF_DUCKED)
+ delta *= 3; // crouching
+ angles[PITCH] += delta;
+ delta = cg.bobfracsin * cg_bobroll.value * speed;
+ if (cg.predictedPlayerState.pm_flags & PMF_DUCKED)
+ delta *= 3; // crouching accentuates roll
+ if (cg.bobcycle & 1)
+ delta = -delta;
+ angles[ROLL] += delta;
+
+//===================================
+
+ // add view height
+ //origin[2] += cg.predictedPlayerState.viewheight;
+ origin[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height;
+ //TiM: Model system enhancements
+
+ // smooth out duck height changes
+ timeDelta = cg.time - cg.duckTime;
+ if ( timeDelta < DUCK_TIME) {
+ cg.refdef.vieworg[2] -= cg.duckChange
+ * (DUCK_TIME - timeDelta) / DUCK_TIME;
+ }
+
+ // add bob height
+ bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value;
+ if (bob > 6) {
+ bob = 6;
+ }
+
+ origin[2] += bob;
+
+
+ // add fall height
+ delta = cg.time - cg.landTime;
+ if ( delta < LAND_DEFLECT_TIME ) {
+ f = delta / LAND_DEFLECT_TIME;
+ cg.refdef.vieworg[2] += cg.landChange * f;
+ } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
+ delta -= LAND_DEFLECT_TIME;
+ f = 1.0 - ( delta / LAND_RETURN_TIME );
+ cg.refdef.vieworg[2] += cg.landChange * f;
+ }
+
+ // add step offset
+ CG_StepOffset();
+
+ // add kick offset
+
+ VectorAdd (origin, cg.kick_origin, origin);
+
+ //TiM : For rotated players
+ //if ( (cg.predictedPlayerEntity.currentState.eFlags & EF_FULL_ROTATE) && Q_fabs( angles[PITCH] ) > 89 )
+ //angles[ROLL] += 180;
+
+ // pivot the eye based on a neck length
+//#if 0
+ {
+#define NECK_LENGTH 8//8
+ vec3_t forward, up;
+
+ cg.refdef.vieworg[2] -= NECK_LENGTH;
+ AngleVectors( cg.refdefViewAngles, forward, NULL, up );
+ VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg );
+ VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg );
+ }
+//#endif
+}
+
+/*
+-------------------------
+CGCam_Shake
+-------------------------
+*/
+
+void CG_CameraShake( float intensity, int duration, qboolean addRumbleSound )
+{
+ if ( intensity > MAX_SHAKE_INTENSITY )
+ intensity = MAX_SHAKE_INTENSITY;
+
+ cg.shake_intensity = intensity;
+ cg.shake_duration = duration;
+ cg.shake_start = cg.time;
+}
+
+
+
+
+/*
+-------------------------
+CG_UpdateShake
+
+This doesn't actually affect the camera's info, but passed information instead
+-------------------------
+*/
+
+extern void CG_ClientShakeCamera( void );
+
+void CG_UpdateCameraShake( vec3_t origin, vec3_t angles )
+{
+ vec3_t curOrigin, curAngle; //moveDir,
+ //vec3_t zero = {0, 0, 0};
+ float intensity_scale, intensity;
+ //float ranIntensity;
+ float ratio;
+ int i;
+
+ //TiM - restart a server loop shake
+ if ( cg.shake_duration <= 0 && cg.shake_serverIndex > (cg.time - cgs.levelStartTime ) ) {
+ CG_ClientShakeCamera();
+ }
+
+ if ( cg.shake_duration <= 0 ) {
+ //VectorSet( cg.shake_LastOrigin, 0, 0, 0 );
+ //VectorSet( cg.shake_LastAngle, 0, 0, 0 );
+
+ //VectorSet( cg.shake_LerpOrigin, 0, 0, 0 );
+ //VectorSet( cg.shake_LerpAngle, 0, 0, 0 );
+
+ memset( &cg.shake_LastOrigin, 0, sizeof( cg.shake_LastOrigin ) );
+ memset( &cg.shake_LastAngle, 0, sizeof( cg.shake_LastAngle ) );
+
+ memset( &cg.shake_LerpOrigin, 0, sizeof( cg.shake_LerpOrigin ) );
+ memset( &cg.shake_LerpAngle, 0, sizeof( cg.shake_LerpAngle ) );
+
+ return;
+ }
+
+ //This is designed to try and make it lerp back to normal at the end
+ if ( cg.time > ( cg.shake_start + cg.shake_duration ) )
+ {
+ cg.shake_intensity = 0;
+ cg.shake_duration = 0;
+ cg.shake_start = 0;
+
+ return;
+ }
+
+ //intensity_scale now also takes into account FOV with 90.0 as normal
+ intensity_scale = 1.0f - ( (float) ( cg.time - cg.shake_start ) / (float) cg.shake_duration ) * (cg.refdef.fov_x/90.0f);
+
+ intensity = cg.shake_intensity * intensity_scale;
+
+ //LerpCode
+ if ( cg.time > cg.shake_nextLerp ) {
+
+ VectorCopy( cg.shake_LerpOrigin, cg.shake_LastOrigin );
+ VectorCopy( cg.shake_LerpAngle, cg.shake_LastAngle );
+
+ //ranIntensity = flrandom( ( 10000.0f * ( 1.0f - Q_fabs( intensity ) ) ), ( 30000.0f * ( 1.0f - Q_fabs( intensity ) )) );
+
+ cg.shake_lastLerp = cg.shake_nextLerp;
+ //cg.shake_nextLerp = cg.time + (int)ranIntensity;
+ cg.shake_nextLerp = cg.time + irandom( 30, 40 );
+
+ if( cg.shake_nextLerp > ( cg.shake_start + cg.shake_duration ) ) {
+ cg.shake_nextLerp = (cg.shake_start + cg.shake_duration);
+ VectorSet( cg.shake_LerpOrigin, 0, 0, 0 );
+ VectorSet( cg.shake_LerpAngle, 0, 0, 0 );
+ }
+ else {
+ for (i=0; i < 3; i++ ) {
+ cg.shake_LerpOrigin[i] = ( crandom() * intensity );
+ cg.shake_LerpAngle[i] = ( crandom() * intensity );
+ }
+
+ //If we're moving out of our boundary, away from the player... >.<
+ //clamp it bak in by inverting it
+ for ( i = 0; i < 3; i++ ) {
+ if ( ( cg.shake_LerpOrigin[i] + cg.shake_LastOrigin[i] ) > ( origin[i] * intensity ) ) {
+ cg.shake_LerpOrigin[i] = -(cg.shake_LerpOrigin[i]);
+ }
+
+ if ( ( cg.shake_LerpAngle[i] + cg.shake_LastAngle[i] ) > ( angles[i] * intensity ) ) {
+ cg.shake_LerpAngle[i] = -(cg.shake_LerpAngle[i]);
+ }
+ }
+
+ /*i = 0;
+ while ( 1 ) {
+ if ( ( cg.shake_LerpOrigin[i] + cg.shake_LastOrigin[i] ) > ( origin[i] * intensity ) || ( cg.shake_LerpAngle[i] + cg.shake_LastAngle[i] ) > ( angles[i] * intensity ) ) {
+ cg.shake_LerpOrigin[i] = crandom() * intensity;
+ cg.shake_LerpAngle[i] = crandom() * intensity;
+ }
+ else {
+ if ( i == 2 ) {
+ break;
+ }
+ }
+ i++;
+ if ( i == 3 ) {
+ i= 0;
+ }
+ }*/
+ }
+ }
+
+ //Com_Printf( S_COLOR_RED "NextLerp: %i, origin = { %f, %f, %f }\n", cg.shake_nextLerp, origin[0], origin[1], origin[2] );
+
+ //FIXME: Lerp
+ //TiM : Doing that
+
+ ratio = ((float)( cg.time - cg.shake_lastLerp ) / (float)( cg.shake_nextLerp - cg.shake_lastLerp ) );
+ if (ratio < 0 )
+ ratio = 0.0f;
+ else if (ratio > 1 )
+ ratio = 1.0f;
+
+ for ( i = 0; i < 3; i++ ) {
+ curOrigin[i] = ratio * (float)( (origin[i] + cg.shake_LerpOrigin[i] ) - ( origin[i] + cg.shake_LastOrigin[i] ) ); //origin
+ }
+
+ //Move the camera
+ //VectorAdd( origin, curOrigin, origin );
+ VectorAdd( origin, cg.shake_LastOrigin, origin );
+ VectorAdd( origin, curOrigin, origin );
+
+ /*for ( i=0; i < 3; i++ )
+ moveDir[i] = ( crandom() * intensity );*/
+ for ( i =0; i < 3; i++ ) {
+ curAngle[i] = ratio * (float)( ( angles[i] + cg.shake_LerpAngle[i] ) - ( angles[i] + cg.shake_LastAngle[i] ) ); //angles
+ }
+
+ //FIXME: Lerp
+
+ //Move the angles
+ //VectorAdd( angles, curAngle, angles );
+ VectorAdd( angles, cg.shake_LastAngle, angles );
+ VectorAdd( angles, curAngle, angles );
+
+ //Com_Printf( S_COLOR_RED "ratio: %f, origin = { %f, %f, %f }\n", ratio, origin[0], origin[1], origin[2] );
+}
+
+
+//======================================================================
+
+void CG_ZoomDown_f( void )
+{
+ //if we're not holding a rifle or TR-116, don't draw
+ if ( !( cg.snap->ps.weapon == WP_COMPRESSION_RIFLE || cg.snap->ps.weapon == WP_TR116 ) ) {
+ cg.zoomed = qfalse;
+ cg.zoomLocked = qfalse;
+ return;
+ }
+
+ /*if ( cg.snap->ps.persistant[PERS_CLASS] == PC_NOCLASS
+ || cg.snap->ps.persistant[PERS_CLASS] != PC_SECURITY
+ && cg.snap->ps.persistant[PERS_CLASS] != PC_ALPHAOMEGA22
+ && cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN )
+ {//in a class-based game, only these can zoom
+ cg.zoomed = qfalse;
+ cg.zoomLocked = qfalse;
+ return;
+ }*/
+
+ // The zoom hasn't been started yet, so do it now
+ if ( !cg.zoomed )
+ {
+ cg.zoomLocked = qfalse;
+ cg.zoomed = qtrue;
+ cg_zoomFov.value = cg_fov.value;
+ cg.zoomTime = cg.time;
+ if ( cg.snap->ps.weapon == WP_TR116 ) {
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomStart116 );
+ }
+ else {
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomStart );
+ }
+ return;
+ }
+
+ // Can only snap out of the zoom mode if it has already been locked (CG_ZoomUp_f has been called)
+ if ( cg.zoomLocked )
+ {
+ // Snap out of zoom mode
+ cg.zoomed = qfalse;
+ cg.zoomTime = cg.time;
+
+ if ( cg.snap->ps.weapon == WP_TR116 ) {
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomEnd116 );
+ }
+ else {
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomEnd );
+ }
+ }
+}
+
+void CG_ZoomUp_f( void )
+{
+
+ if ( cg.zoomed ) {
+ // Freeze the zoom mode
+ cg.zoomLocked = qtrue;
+ }
+}
+
+/*
+====================
+CG_CalcFov
+
+Fixed fov at intermissions, otherwise account for fov variable and zooms.
+====================
+*/
+#define WAVE_AMPLITUDE 1
+#define WAVE_FREQUENCY 0.4
+
+#define FOV_MAX 120
+
+static int CG_CalcFov( void ) {
+ float x;
+ float phase;
+ float v;
+ int contents;
+ float fov_x, fov_y;
+ float zoomFov;
+ float f;
+ int inwater;
+ qboolean warpEffect=qfalse;
+
+ if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
+ // if in intermission, use a fixed value
+ fov_x = 90;
+ } else {
+ // user selectable
+ if ( cgs.dmflags & DF_FIXED_FOV ) {
+ // dmflag to prevent wide fov for all clients
+ fov_x = 80;
+ } else {
+ fov_x = cg_fov.value;
+ if ( fov_x < 1 ) {
+ fov_x = 1;
+ } else if ( fov_x > FOV_MAX ) {
+ fov_x = FOV_MAX;
+ }
+ }
+
+ // account for zooms
+ zoomFov = cg_zoomFov.value;
+ if ( zoomFov < 1 ) {
+ zoomFov = 1;
+ } else if ( zoomFov > FOV_MAX) {
+ zoomFov = FOV_MAX;
+ }
+
+ // Disable zooming when in third person
+ if ( cg.zoomed && !cg.renderingThirdPerson )
+ {
+ if ( !cg.zoomLocked )
+ {
+ // Interpolate current zoom level
+ cg_zoomFov.value = cg_fov.value - ((float)( cg.time - cg.zoomTime ) / ZOOM_IN_TIME + ZOOM_START_PERCENT)
+ * ( cg_fov.value - MAX_ZOOM_FOV );
+
+ // Clamp zoomFov
+ if ( cg_zoomFov.value < MAX_ZOOM_FOV )
+ {
+ cg_zoomFov.value = MAX_ZOOM_FOV;
+ }
+ else if ( cg_zoomFov.value > cg_fov.value )
+ {
+ cg_zoomFov.value = cg_fov.value;
+ }
+ else
+ {//still zooming
+ static int zoomSoundTime = 0;
+
+ if ( zoomSoundTime < cg.time )
+ {
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.zoomLoop );
+ zoomSoundTime = cg.time + 300;
+ }
+ }
+ }
+
+ fov_x = cg_zoomFov.value;
+ } else {
+ f = ( cg.time - cg.zoomTime ) / (float)ZOOM_OUT_TIME;
+ if ( f > 1.0 ) {
+ fov_x = fov_x;
+ } else {
+ fov_x = zoomFov + f * ( fov_x - zoomFov );
+ }
+ }
+ }
+
+ /*if (cg.predictedPlayerState.introTime > cg.time)
+ { // The stuff is "holodecking in".
+ fov_x = 80;
+ }*/
+
+
+ x = cg.refdef.width / tan( fov_x / 360 * M_PI );
+ fov_y = atan2( cg.refdef.height, x );
+ fov_y = fov_y * 360 / M_PI;
+
+ // warp if underwater //TiM Also do it if we're critically injured
+ contents = CG_PointContents( cg.refdef.vieworg, -1 );
+
+ warpEffect = ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
+ || ( !cg.renderingThirdPerson && cg.predictedPlayerState.stats[STAT_HEALTH] <= INJURED_MODE_HEALTH && cg.predictedPlayerState.stats[STAT_HEALTH] > 1 );
+
+ if ( warpEffect ){
+ //phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
+ phase = cg.time * 0.001 * WAVE_FREQUENCY * M_PI * 2;
+ v = WAVE_AMPLITUDE * sin( phase );
+ fov_x += v;
+ fov_y -= v;
+ inwater = qtrue;
+ }
+ else {
+ inwater = qfalse;
+ }
+
+
+ // set it
+ cg.refdef.fov_x = fov_x;
+ cg.refdef.fov_y = fov_y;
+
+ if ( !cg.zoomed ) {
+ cg.zoomSensitivity = 1;
+ } else {
+ cg.zoomSensitivity = cg.refdef.fov_y / 75.0;
+ }
+
+ return inwater;
+}
+
+
+
+
+/*
+===============
+CG_CalcViewValues
+
+Sets cg.refdef view values
+===============
+*/
+static int CG_CalcViewValues( void ) {
+ playerState_t *ps;
+
+ memset( &cg.refdef, 0, sizeof( cg.refdef ) );
+
+ // strings for in game rendering
+ // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) );
+ // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) );
+
+ // calculate size of 3D view
+ CG_CalcVrect();
+
+ ps = &cg.predictedPlayerState;
+
+ // intermission view
+ if ( ps->pm_type == PM_INTERMISSION ) {
+ VectorCopy( ps->origin, cg.refdef.vieworg );
+ VectorCopy( ps->viewangles, cg.refdefViewAngles );
+ AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
+ return CG_CalcFov();
+ }
+
+ cg.bobcycle = ( ps->bobCycle & 128 ) >> 7;
+ cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) );
+ cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] +
+ ps->velocity[1] * ps->velocity[1] );
+
+
+ VectorCopy( ps->origin, cg.refdef.vieworg );
+ VectorCopy( ps->viewangles, cg.refdefViewAngles );
+
+ // add error decay
+ if ( cg_errorDecay.value > 0 ) {
+ int t;
+ float f;
+
+ t = cg.time - cg.predictedErrorTime;
+ f = ( cg_errorDecay.value - t ) / cg_errorDecay.value;
+ if ( f > 0 && f < 1 ) {
+ VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg );
+ } else {
+ cg.predictedErrorTime = 0;
+ }
+ }
+
+ if ( cg.renderingThirdPerson ) {
+ // back away from character
+ CG_OffsetThirdPersonView();
+ } else {
+ // offset for local bobbing and kicks
+ CG_OffsetFirstPersonView();
+ }
+
+ // shake the camera if necessary
+ CG_UpdateCameraShake( cg.refdef.vieworg, cg.refdefViewAngles );
+ // position eye reletive to origin
+ AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
+
+ //TiM - As far as I can see, all this does is cause flashy
+ //effects on-screen when a player teleports the hide the delay.
+ //This probably doesn't really apply to us now...
+ /*if ( cg.hyperspace ) {
+ cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE;
+ }*/
+
+ // field of view
+ return CG_CalcFov();
+}
+
+
+/*
+=====================
+CG_PowerupTimerSounds
+=====================
+*/
+static void CG_PowerupTimerSounds( void ) {
+ int i;
+ int t;
+
+ // powerup timers going away
+ for ( i = 0 ; i < MAX_POWERUPS ; i++ ) {
+ t = cg.snap->ps.powerups[i];
+
+ // kef -- hack hack hack. additionally, hack.
+ if ( (PW_OUCH == i) || (PW_GHOST == i) )
+ {
+ continue;
+ }
+ if ( t <= cg.time ) {
+ continue;
+ }
+ if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) {
+ continue;
+ }
+ if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) {
+ //trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound );
+ }
+ }
+}
+
+
+
+//=========================================================================
+
+/*
+=============
+CG_IntroModel
+
+This is when the player is starting the level.
+=============
+*/
+/*void CG_AddIntroModel(playerState_t *ps, int time)
+{
+ static int soundpoint=0, lasttime=999999;
+ refEntity_t doorbox;
+ float alpha;
+ byte a;
+ //char pClass[MAX_QPATH];
+ //char pRank[MAX_QPATH];
+
+ if (lasttime > time)
+ { // Restart everything.
+ soundpoint=0;
+ }
+
+ lasttime=time;
+
+ // add the model
+ memset( &doorbox, 0, sizeof( doorbox ) );
+ VectorCopy( cg.refdef.vieworg, doorbox.lightingOrigin );
+
+ doorbox.shaderRGBA[0] = 255;
+ doorbox.shaderRGBA[1] = 255;
+ doorbox.shaderRGBA[2] = 255;
+ doorbox.shaderRGBA[3] = 255;
+
+ doorbox.hModel = cgs.media.doorbox;
+ if (!doorbox.hModel) {
+ return;
+ }
+
+ VectorMA(cg.refdef.vieworg, 25, cg.refdef.viewaxis[0], doorbox.origin);
+ VectorMA(doorbox.origin, -35, cg.refdef.viewaxis[2], doorbox.origin);
+ AnglesToAxis(cg.refdefViewAngles, doorbox.axis);
+
+ VectorScale(doorbox.axis[0], -1.0, doorbox.axis[0]);
+ VectorScale(doorbox.axis[1], -1.0, doorbox.axis[1]);
+
+ if (soundpoint <= 0)
+ { // First part... "Prepare to compete."
+ if (time >= TIME_INIT)
+ {
+ soundpoint = 1;
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoInitSound );
+ }
+ doorbox.frame = 0;
+ }
+ else if (soundpoint == 1)
+ { // Second part... Open door after "prepare".
+ if (time >= TIME_DOOR_START)
+ {
+ soundpoint = 2;
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoDoorSound );
+ }
+ doorbox.frame = 0;
+ }
+ else if (soundpoint == 2)
+ { // Third part... Fade in after opening door.
+ if (time >= TIME_FADE_START)
+ {
+ soundpoint = 3;
+ trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoFadeSound );
+ doorbox.frame = FRAMES_DOOR-1;
+ }
+ else
+ {
+ doorbox.frame = ((float)(time - TIME_DOOR_START) / 100.0) + 1;
+ if (doorbox.frame >= FRAMES_DOOR)
+ {
+ doorbox.frame=FRAMES_DOOR-1;
+ }
+ else
+ {
+ doorbox.oldframe = doorbox.frame-1;
+ doorbox.backlerp = (float)(doorbox.frame) - ((float)(time - TIME_DOOR_START) / 100.0);
+ }
+ }
+ }
+ else
+ { // Final part... Fade out the model.
+
+ alpha = 1.0 - ((float)(time - TIME_FADE_START) / (float)TIME_FADE_DUR);
+
+ if (alpha<0.0)
+ {
+ alpha=0.0;
+ }
+
+ a=255.0*alpha;
+ if (a<=0)
+ { // An alpha of zero defaults to opaque... Makes sense, why even send something that is 100% transparent?
+ a=1;
+ }
+
+ doorbox.shaderRGBA[0] = 255;
+ doorbox.shaderRGBA[1] = 255;
+ doorbox.shaderRGBA[2] = 255;
+ doorbox.shaderRGBA[3] = a;
+ doorbox.frame = FRAMES_DOOR-1;
+ }
+
+ doorbox.renderfx |= (RF_DEPTHHACK|RF_FORCE_ENT_ALPHA|RF_FULLBRIGHT);
+
+ trap_R_AddRefEntityToScene(&doorbox);
+}*/
+
+void CG_DrawEVAHelmet ( playerState_t *ps )
+{
+ refEntity_t helmet;
+
+ if ( !ps->powerups[PW_EVOSUIT] && !( cgs.clientinfo[ps->clientNum].isHazardModel && ps->powerups[PW_BOLTON] ) ) {
+ return;
+ }
+
+ memset( &helmet, 0, sizeof(helmet) );
+ VectorCopy( ps->origin, helmet.lightingOrigin );
+ helmet.renderfx = RF_LIGHTING_ORIGIN | RF_DEPTHHACK | RF_FIRST_PERSON;
+ helmet.hModel = cgs.media.evaInterior;
+
+ if ( !helmet.hModel ) {
+ CG_Printf("EVA Helmet Model not found\n");
+ return;
+ }
+
+ VectorCopy( cg.refdef.vieworg, helmet.origin );
+ AxisCopy( cg.refdef.viewaxis, helmet.axis );
+ VectorMA( helmet.origin, 6, cg.refdef.viewaxis[0], helmet.origin );
+
+ CG_AddRefEntityWithPowerups( &helmet,
+ cg.predictedPlayerEntity.currentState.powerups,
+ cg.predictedPlayerEntity.currentState.eFlags,
+ &cg.predictedPlayerEntity.beamData,
+ cg.predictedPlayerEntity.cloakTime,
+ cg.predictedPlayerEntity.decloakTime,
+ qfalse );
+
+}
+
+
+//=========================================================================
+
+/*
+=================
+CG_DrawActiveFrame
+
+Generates and draws a game scene and status information at the given time.
+=================
+*/
+void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) {
+ int inwater;
+ char cvarYaw[16]; //an uber long floating point value lol
+ float yaw;
+
+ cg.time = serverTime;
+ cg.demoPlayback = demoPlayback;
+
+ //RPG-X: TiM - Set up for giant uber rant.
+ //GARRRRRGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHH!!!!!!!!!!!!!!!!!!!!!!!!! ->O_O<-
+ //Here's me looking at the BFP mod, wondering how the gravynuggets they managed
+ //to flip the yaw value when the players rotated beyond the normal view axis.
+ //I developed a totally l33tzor rotational offset algorithm that came oh so close
+ //to working, but after noticing a little inconsistancy in their rotation ingame, I realised
+ //all they did was change the player's mouse yaw CVAR to invert!!!! O_O
+ //Ohhhhh I just lost several years of my life and the use of my wrists.....
+ //Probably just as well we disabled the RPG-X online webcam... that wasn't pretty. O_o
+
+ //load our yaw value
+ trap_Cvar_VariableStringBuffer( "m_yaw", cvarYaw, sizeof( cvarYaw ) );
+ yaw = atof ( cvarYaw );
+
+ if ( cg.predictedPlayerEntity.currentState.eFlags & EF_FULL_ROTATE
+ && Q_fabs( cg.predictedPlayerEntity.lerpAngles[PITCH] ) > 89.0f )
+ {
+ if ( yaw > 0.0f )
+ {
+ //yaw = -yaw;
+ //trap_Cvar_Set( "m_yaw", va( "%f", yaw ) );
+ trap_Cvar_Set( "m_yaw", va( "-%s", cvarYaw ) );
+
+ //CG_Printf( S_COLOR_RED "%f\n", yaw );
+ }
+ }
+ else { //ugh... I hope no one plays with their yaws inverted. >.< This MAY need to be CVAR controlled
+ if ( yaw < 0.0f )
+ {
+ char *tmp = cvarYaw;
+ if ( tmp[0] == '-') tmp++; //erase the neg sign
+
+ //trap_Cvar_Set( "m_yaw", va( "%f", Q_fabs( yaw ) ) );
+ trap_Cvar_Set( "m_yaw", va( "%s", tmp ) );
+ }
+ }
+
+ // update cvars
+ CG_UpdateCvars();
+
+ // if we are only updating the screen as a loading
+ // pacifier, don't even try to read snapshots
+ if ( cg.infoScreenText[0] != 0 ) {
+ CG_DrawInformation();
+ return;
+ }
+
+ // any looped sounds will be respecified as entities
+ // are added to the render list
+ trap_S_ClearLoopingSounds();
+
+ // clear all the render lists
+ trap_R_ClearScene();
+
+ // set up cg.snap and possibly cg.nextSnap
+ CG_ProcessSnapshots();
+
+ // if we haven't received any snapshots yet, all
+ // we can draw is the information screen
+ if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) {
+ CG_DrawInformation();
+ return;
+ }
+
+ // let the client system know what our weapon and zoom settings are
+ trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity );
+
+ // this counter will be bumped for every valid scene we generate
+ cg.clientFrame++;
+
+ // update cg.predictedPlayerState
+ CG_PredictPlayerState();
+
+ // decide on third person view
+ cg.renderingThirdPerson = ( cg_thirdPerson.integer && !cg.zoomed && cg.predictedPlayerState.pm_type != PM_SPECTATOR ) || (cg.snap->ps.stats[STAT_HEALTH] <= 1 ) ; //TiM - So we'll always be first person in zooming //0
+
+ // build cg.refdef
+ inwater = CG_CalcViewValues();
+
+ // first person blend blobs, done after AnglesToAxis
+ if ( !cg.renderingThirdPerson )
+ {
+ CG_DrawFullScreenFX();
+ CG_DrawEVAHelmet( &cg.predictedPlayerState );
+ }
+
+
+ // build the render lists
+ if ( !cg.hyperspace ) {
+ CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct
+ CG_AddMarks();
+ CG_AddLocalEntities();
+ }
+
+ /*if (cg.predictedPlayerState.introTime > cg.time)
+ { // Render the holodeck doors
+ CG_AddIntroModel(&cg.predictedPlayerState, TIME_INTRO - (cg.predictedPlayerState.introTime - cg.time));
+ }*/
+
+ CG_AddViewWeapon( &cg.predictedPlayerState );
+
+ // finish up the rest of the refdef
+ if ( cg.testModelEntity.hModel ) {
+ CG_AddTestModel();
+ }
+ cg.refdef.time = cg.time;
+ memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) );
+
+ // update audio positions
+ trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater );
+
+ // warning sounds when powerup is wearing off
+ //TiM - Not really needed :P
+ //CG_PowerupTimerSounds();
+
+ // make sure the lagometerSample and frame timing isn't done twice when in stereo
+ if ( stereoView != STEREO_RIGHT ) {
+ cg.frametime = cg.time - cg.oldTime;
+ if ( cg.frametime < 0 ) {
+ cg.frametime = 0;
+ }
+ cg.oldTime = cg.time;
+ CG_AddLagometerFrameInfo();
+ }
+
+ // actually issue the rendering calls
+ CG_DrawActive( stereoView );
+
+ if ( cg_stats.integer ) {
+ CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame );
+ }
+
+ //TiM - Reset the lerp code at the end of this frame.
+ if ( cg.thirdPersonNoLerp )
+ cg.thirdPersonNoLerp = qfalse;
+}
+
diff --git a/cgame/cg_weapons.c b/cgame/cg_weapons.c
new file mode 100644
index 0000000..828e1a4
--- /dev/null
+++ b/cgame/cg_weapons.c
@@ -0,0 +1,2503 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+// cg_weapons.c -- events and effects dealing with weapons
+#include "cg_local.h"
+#include "fx_local.h"
+
+//RPG-X : TiM - Weapons Arrays
+static int RAweapons[8] = { WP_PADD,
+ WP_TRICORDER,
+ WP_COMPRESSION_RIFLE,
+ WP_TR116,
+ WP_VOYAGER_HYPO,
+ WP_DERMAL_REGEN,
+ WP_MEDKIT,
+ WP_COFFEE
+ };
+
+static char *RAweapFileName[8] = { "padd",
+ "tricorder",
+ "prifle",
+ "tr116",
+ "hypospray",
+ "dermal_regen",
+ "medkit",
+ "coffeecup"
+ };
+
+
+/*
+=================
+CG_RegisterWeapon
+
+The server says this item is used on this level
+=================
+*/
+
+// kef -- sad? yep.
+typedef struct wpnBarrelInfo_s
+{
+ weapon_t giTag;
+ int numBarrels;
+ int flashTime;
+} wpnBarrelInfo_t;
+
+wpnBarrelInfo_t wpnBarrelData[] =
+{
+ {WP_NULL_HAND, 0, 0},
+
+ {WP_TRICORDER, 0, 0},
+ {WP_PADD, 0, 0},
+ {WP_COFFEE, 0, 0},
+
+ {WP_PHASER, 0, 0},
+ {WP_COMPRESSION_RIFLE, 0, 120},
+ {WP_TR116, 1, 60},
+
+ {WP_GRENADE_LAUNCHER, 2, 150},
+ {WP_QUANTUM_BURST, 1, 200},
+ {WP_DISRUPTOR, 1, 130},
+
+ {WP_MEDKIT, 0, 0},
+ {WP_VOYAGER_HYPO, 0, 0},
+ {WP_DERMAL_REGEN, 0, 0},
+
+ {WP_TOOLKIT, 0, 0},
+ {WP_HYPERSPANNER, 0, 0},
+
+ // make sure this is the last entry in this array, please
+ {WP_NONE, 0, 0},
+};
+
+//wpnBarrelInfo_t wpnBarrelData[] =
+//{
+// {WP_PHASER, 0, 0},
+// {WP_COMPRESSION_RIFLE, 0, 100},
+// {WP_NULL_HAND, 0, 0},
+// {WP_COFFEE, 0, 0},
+// {WP_DISRUPTOR, 1, 80},
+// {WP_GRENADE_LAUNCHER, 2, 140},
+// {WP_TR116, 1, 120},
+// {WP_QUANTUM_BURST, 1, 200},
+// {WP_DERMAL_REGEN, 0, 0},
+// {WP_VOYAGER_HYPO, 0, 0},
+// {WP_TOOLKIT, 0, 0},
+// {WP_MEDKIT, 0, 0},
+// {WP_TRICORDER, 0, 0},
+// {WP_PADD, 0, 0},
+// {WP_NEUTRINO_PROBE, 0, 0},
+// {WP_TR116, 0, 90},
+//
+// // make sure this is the last entry in this array, please
+// {WP_NONE, 0},
+//};
+
+void CG_RegisterWeapon( int weaponNum ) {
+ weaponInfo_t *weaponInfo;
+ gitem_t *item, *ammo;
+ char path[MAX_QPATH];
+ vec3_t mins, maxs;
+ int i;
+ int numBarrels = 0;
+ wpnBarrelInfo_t *barrelInfo = NULL;
+
+
+ weaponInfo = &cg_weapons[weaponNum];
+
+ if ( weaponNum == 0 ) {
+ return;
+ }
+
+ if ( weaponInfo->registered ) {
+ return;
+ }
+
+ memset( weaponInfo, 0, sizeof( *weaponInfo ) );
+ weaponInfo->registered = qtrue;
+
+ for ( item = bg_itemlist + 1 ; item->classname ; item++ ) {
+ if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) {
+ /*if ( weaponNum == WP_DISRUPTOR ) {
+ Com_Printf( S_COLOR_RED "Registering %s with pickup name of %s\n", bg_itemlist[10].classname, bg_itemlist[10].pickup_name );
+ }*/
+ weaponInfo->item = item;
+ break;
+ }
+ }
+ if ( !item->classname ) {
+ CG_Error( "Couldn't find weapon %i", weaponNum );
+ }
+ CG_RegisterItemVisuals( item - bg_itemlist );
+
+ weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model );
+
+ // kef -- load in-view model
+ weaponInfo->viewModel = trap_R_RegisterModel(item->view_model);
+
+ // calc midpoint for rotation
+ trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] );
+ }
+
+ weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon );
+
+ for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) {
+ if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) {
+ break;
+ }
+ }
+// if ( ammo->classname && ammo->world_model ) {
+// weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model );
+// }
+
+ strcpy( path, item->view_model );
+ COM_StripExtension( path, path );
+ strcat( path, "_flash.md3" );
+ weaponInfo->flashModel = trap_R_RegisterModel( path );
+
+ for (barrelInfo = wpnBarrelData; barrelInfo->giTag != WP_NONE; barrelInfo++)
+ {
+ if (barrelInfo->giTag == weaponNum)
+ {
+ numBarrels = barrelInfo->numBarrels;
+ break;
+ }
+ }
+ for (i=0; i< numBarrels; i++) {
+ Q_strncpyz( path, item->view_model, MAX_QPATH );
+ COM_StripExtension( path, path );
+ if (i)
+ {
+ strcat( path, va("_barrel%d.md3", i+1));
+ }
+ else
+ strcat( path, "_barrel.md3" );
+ weaponInfo->barrelModel[i] = trap_R_RegisterModel( path );
+ }
+
+ strcpy( path, item->view_model );
+ COM_StripExtension( path, path );
+ strcat( path, "_hand.md3" );
+ weaponInfo->handsModel = trap_R_RegisterModel( path );
+
+ if ( !weaponInfo->handsModel ) {
+ weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/prifle/prifle_hand.md3" );
+ }
+
+ switch ( weaponNum ) {
+ case WP_PHASER:
+ MAKERGB( weaponInfo->flashDlightColor, 0, 0, 0 );
+
+ weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "phaser/phaserfiring.wav" );
+ weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "phaser/altphaserfiring.wav" );
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "phaser/phaserstart.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "phaser/altphaserstart.wav" );
+ weaponInfo->stopSound = trap_S_RegisterSound(SOUND_DIR "phaser/phaserstop.wav");
+ weaponInfo->altStopSound = trap_S_RegisterSound(SOUND_DIR "phaser/altphaserstop.wav");
+
+ cgs.media.phaserShader = trap_R_RegisterShader( "gfx/misc/phaser_stx" );
+ cgs.media.phaserEmptyShader = trap_R_RegisterShader( "gfx/misc/phaserempty" );
+
+ cgs.media.phaserAltShader = trap_R_RegisterShader("gfx/effects/whitelaser"); // "gfx/misc/phaser_alt" );
+
+ cgs.media.phaserAltEmptyShader = trap_R_RegisterShader( "gfx/misc/phaser_altempty" );
+ cgs.media.phaserMuzzleEmptyShader= trap_R_RegisterShader( "models/weapons2/phaser/muzzle_empty" );
+
+ break;
+
+ case WP_DERMAL_REGEN:
+ weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "dermal_regen/dm_1.wav" );
+ weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "dermal_regen/dm_2.wav" );
+ break;
+
+ case WP_DISRUPTOR:
+ //weaponInfo->missileTrailFunc = FX_StasisProjectileThink;
+ weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/alien_disruptor/disruptor_bolt.md3" );
+ weaponInfo->missileDlight = 70;
+ MAKERGB( weaponInfo->missileDlightColor, 0.0, 1.0, 0.0 );
+ MAKERGB( weaponInfo->flashDlightColor, 0.0, 1.0, 0.0 );
+
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "alien_disruptor/fire.wav" );
+ weaponInfo->mainHitSound = trap_S_RegisterSound(SOUND_DIR "stasis/hit_wall.wav");
+
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "alien_disruptor/disruptorstart.wav" );
+ weaponInfo->stopSound = trap_S_RegisterSound(SOUND_DIR "alien_disruptor/disruptorstop.wav");
+ weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "alien_disruptor/disruptorfiring.wav" );
+
+ cgs.media.disruptorBolt = trap_R_RegisterShader( "gfx/misc/disruptor_bolt" );
+ cgs.media.disruptorStreak = trap_R_RegisterShader( "gfx/misc/disruptor_streak" );
+ //cgs.media.altIMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2alt" );
+ //cgs.media.dnBoltShader = trap_R_RegisterShader( "gfx/misc/dnBolt" );
+
+ cgs.media.greenParticleShader = trap_R_RegisterShader( "gfx/misc/greenparticle" );
+ cgs.media.greenParticleStreakShader = trap_R_RegisterShader( "gfx/misc/greenparticle_anamorphic" );
+
+ cgs.media.disruptorBeam = trap_R_RegisterShader( "gfx/misc/disruptor" );
+
+ break;
+
+ case WP_GRENADE_LAUNCHER:
+ weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile.md3" );
+ if(rpg_ctribgrenade.integer == 1)//RPG-X: - RedTechie Possible Hack! FIX | TiM: Heh, you're a possible hack :)
+ {
+ weaponInfo->alt_missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile2a.md3" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "glauncher/alt_fire.wav" );
+ weaponInfo->altHitSound = trap_S_RegisterSound( "sound/weapons/glauncher/beepa.wav" );
+ cgs.media.grenadeAltStickSound = trap_S_RegisterSound(SOUND_DIR "glauncher/alt_stick.wav");
+
+ }
+ else
+ {
+ weaponInfo->alt_missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile2.md3" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "glauncher/alt_fire.wav" );
+ weaponInfo->altHitSound = trap_S_RegisterSound( SOUND_DIR "glauncher/beep.wav" );
+ cgs.media.grenadeAltStickSound = trap_S_RegisterSound(SOUND_DIR "glauncher/alt_stick.wav");
+ }
+
+ weaponInfo->missileTrailFunc = FX_GrenadeThink;
+ //TiM : No flash anymore
+ MAKERGB( weaponInfo->flashDlightColor, 0.0, 0.0, 0.0 );
+ //MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 );
+
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "glauncher/fire.wav" );
+ cgs.media.grenadeBounceSound1 = trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav");
+ cgs.media.grenadeBounceSound2 = trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav");
+ cgs.media.grenadeExplodeSound = trap_S_RegisterSound(SOUND_DIR "glauncher/explode.wav");
+ cgs.media.grenadeAltExplodeSnd = trap_S_RegisterSound(SOUND_DIR "glauncher/alt_explode.wav" );
+
+ cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" );
+ cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" );
+ cgs.media.whiteLaserShader = trap_R_RegisterShader( "gfx/effects/whitelaser" );
+ cgs.media.borgEyeFlareShader = trap_R_RegisterShader( "gfx/misc/borgeyeflare" );
+ break;
+
+ case WP_COFFEE:
+ //MAKERGB( weaponInfo->flashDlightColor, 1, 0.6, 0.6 );
+
+ /*weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "scavenger/fire.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "scavenger/alt_fire.wav" );
+ weaponInfo->mainHitSound = trap_S_RegisterSound(SOUND_DIR "scavenger/hit_wall.wav");
+ weaponInfo->altHitSound = trap_S_RegisterSound(SOUND_DIR "scavenger/alt_explode.wav");
+ weaponInfo->missileTrailFunc = FX_ScavengerProjectileThink;
+ weaponInfo->alt_missileTrailFunc = FX_ScavengerAltFireThink;
+// weaponInfo->wiTrailTime = 100;
+// weaponInfo->trailRadius = 8;
+ cgs.media.tetrionFlareShader = trap_R_RegisterShader( "gfx/misc/tet1" );
+ cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" );
+ cgs.media.redFlareShader = trap_R_RegisterShader( "gfx/misc/red_flare" );
+
+ cgs.media.scavengerAltShader = trap_R_RegisterShader( "gfx/misc/scavaltfire" );
+ cgs.media.scavExplosionFastShader = trap_R_RegisterShader( "scavExplosionFast" );
+ cgs.media.scavExplosionSlowShader = trap_R_RegisterShader( "scavExplosionSlow" );
+ cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" );*/
+ break;
+
+ case WP_QUANTUM_BURST:
+ MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); //Bluish
+
+ weaponInfo->missileTrailFunc = FX_QuantumThink;
+ weaponInfo->alt_missileTrailFunc = FX_QuantumAltThink;
+
+ weaponInfo->missileDlight = 75;
+ weaponInfo->alt_missileDlight = 100;
+ MAKERGB( weaponInfo->missileDlightColor, 1.0, 1.0, 0.5); //yellowish
+
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "quantum/fire.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "quantum/alt_fire.wav" );
+
+ weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "quantum/hit_wall.wav" );;
+ weaponInfo->altHitSound = trap_S_RegisterSound( SOUND_DIR "quantum/alt_hit_wall.wav" );;
+
+ cgs.media.whiteRingShader = trap_R_RegisterShader( "gfx/misc/whitering" );
+ cgs.media.orangeRingShader = trap_R_RegisterShader( "gfx/misc/orangering" );
+ cgs.media.quantumExplosionShader = trap_R_RegisterShader( "quantumExplosion" );
+ cgs.media.quantumFlashShader = trap_R_RegisterShader( "yellowflash" );
+ //cgs.media.bigBoomShader = trap_R_RegisterShader( "gfx/misc/bigboom" );
+ cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" );
+ cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" );
+ cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" );
+ cgs.media.quantumRingShader = trap_R_RegisterShader( "gfx/misc/detpack3" );
+ cgs.media.quantumBoom = trap_S_RegisterSound ( SOUND_DIR "explosions/explode5.wav" );
+ break;
+
+ case WP_NULL_HAND:
+ /*MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 );
+
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "IMOD/fire.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "IMOD/alt_fire.wav" );
+
+ cgs.media.IMODShader = trap_R_RegisterShader( "gfx/misc/IMOD" );
+ cgs.media.IMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2" );
+ cgs.media.altIMODShader = trap_R_RegisterShader( "gfx/misc/IMODalt" );
+ cgs.media.altIMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2alt" );
+ cgs.media.imodExplosionShader = trap_R_RegisterShader( "imodExplosion" );*/
+ break;
+
+ case WP_COMPRESSION_RIFLE:
+ if(!grp_berp.integer) {
+ MAKERGB( weaponInfo->flashDlightColor, 0.59, 0.24, 0.25 );
+ MAKERGB( weaponInfo->missileDlightColor, 0.59, 0.24, 0.25 );
+ } else {
+ MAKERGB( weaponInfo->flashDlightColor, 0.16, 0.32, 0.5 );
+ MAKERGB( weaponInfo->missileDlightColor, 0.16, 0.32, 0.5 );
+ }
+
+ weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/prifle/prifle_bolt.md3" );
+ weaponInfo->missileDlight = 90;
+
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "prifle/fire.wav" );
+
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "prifle/phaserriflestart.wav" );
+ weaponInfo->altStopSound = trap_S_RegisterSound(SOUND_DIR "prifle/phaserriflestop.wav");
+ weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "prifle/phaserriflefiring.wav" );
+
+ weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "prifle/impact.wav" );;
+
+ cgs.media.prifleImpactShader = trap_R_RegisterShader( "gfx/effects/prifle_hit" );
+ cgs.media.compressionAltBeamShader = trap_R_RegisterShader( "gfx/effects/prifle_altbeam" );
+ cgs.media.compressionAltBlastShader = trap_R_RegisterShader( "gfx/effects/prifle_altblast" );
+ cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" );
+ cgs.media.prifleBolt = trap_R_RegisterShader( "gfx/misc/priflebolt" );
+
+ cgs.media.liteRedParticleStreakShader = trap_R_RegisterShader( "gfx/misc/literedparticle_anamorphic" );
+ cgs.media.liteRedParticleShader = trap_R_RegisterShader( "gfx/misc/literedparticle" );
+
+ cgs.media.flashlightModel = trap_R_RegisterModel( "models/weapons2/prifle/prifle_flashlight.md3" ); //RPG-X : TiM - flashlight model
+
+ cgs.media.prifleBeam = trap_R_RegisterShader( "gfx/misc/phaser_rifle" );
+
+ break;
+
+/* case WP_TR116:
+ MAKERGB( weaponInfo->flashDlightColor, 0.16, 0.16, 1 );
+ weaponInfo->flashSound = trap_S_RegisterSound( "sound/weapons/hitonhead.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( "sound/weapons/guncharge.wav" );
+ cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" );
+ cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" );
+ weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "prifle/impact.wav" );
+ break;*/
+ /*
+ case WP_TR116: //OLD CODE (replaced for TR116)
+ MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 );
+
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "tetrion/fire.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "tetrion/alt_fire.wav" );
+ cgs.media.tetrionRicochetSound1 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet1.wav");
+ cgs.media.tetrionRicochetSound2 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet2.wav");
+ cgs.media.tetrionRicochetSound3 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet3.wav");
+
+ weaponInfo->missileTrailFunc = FX_TetrionProjectileThink;
+ weaponInfo->alt_missileTrailFunc = FX_TetrionProjectileThink;
+
+ cgs.media.greenBurstShader = trap_R_RegisterShader( "gfx/misc/greenburst" );
+ cgs.media.greenTrailShader = trap_R_RegisterShader( "gfx/misc/greentrail" );
+ cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" );
+ cgs.media.tetrionFlareShader = trap_R_RegisterShader( "gfx/misc/tet1" );
+ cgs.media.borgFlareShader = trap_R_RegisterShader( "gfx/misc/borgflare" );
+ cgs.media.bulletmarksShader = trap_R_RegisterShader( "textures/decals/bulletmark4" );
+ break;
+*/
+ case WP_VOYAGER_HYPO:
+ weaponInfo->flashSound = weaponInfo->altFlashSnd = trap_S_RegisterSound( "sound/items/jetpuffmed.wav" );
+ break;
+
+ case WP_TRICORDER:
+ weaponInfo->firingSound= trap_S_RegisterSound( "sound/items/tricorderscan.wav" ); //altFlashSnd
+ weaponInfo->altFiringSound = trap_S_RegisterSound( "sound/ambience/voyager/medictricorder.wav" ); //flashSound
+
+ //weaponInfo->isAnimSndBased = qtrue;
+ break;
+
+ case WP_PADD:
+ weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "padd/padd_1.wav" ); //flashSound
+ weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "padd/padd_2.wav" ); //altFlashSnd
+
+ weaponInfo->isAnimSndBased = qtrue;
+ break;
+
+ case WP_HYPERSPANNER:
+ weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "hyperspanner/spanner_1.wav" );
+ weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "hyperspanner/spanner_2.wav" );
+ break;
+
+ case WP_TR116:
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "hitonhead.wav" );
+ weaponInfo->altFlashSnd = weaponInfo->flashSound;
+ //weaponInfo->altFlashSnd = trap_S_RegisterSound( "sound/weapons/guncharge.wav" );
+ break;
+
+//Toolkit
+ case WP_TOOLKIT:
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "toolkit/toolkit_1.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "toolkit/toolkit_2.wav" );
+ break;
+
+//Medkit
+ case WP_MEDKIT:
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "medkit/medkit_1.wav" );
+ weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "medkit/medkit_2.wav" );
+ break;
+
+ default:
+ MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 );
+ weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "prifle/fire.wav" );
+ break;
+ }
+}
+
+/*
+=================
+CG_RegisterItemVisuals
+
+The server says this item is used on this level
+=================
+*/
+void CG_RegisterItemVisuals( int itemNum ) {
+ itemInfo_t *itemInfo;
+ gitem_t *item;
+
+ itemInfo = &cg_items[ itemNum ];
+ if ( itemInfo->registered ) {
+ return;
+ }
+
+ item = &bg_itemlist[ itemNum ];
+
+ memset( itemInfo, 0, sizeof( &itemInfo ) );
+ itemInfo->registered = qtrue;
+
+ itemInfo->model = trap_R_RegisterModel( item->world_model );
+
+ itemInfo->icon = trap_R_RegisterShader( item->icon );
+
+ if ( item->giType == IT_WEAPON ) {
+ CG_RegisterWeapon( item->giTag );
+ }
+
+ // since the seeker uses the scavenger rifes sounds, we must precache the scavenger rifle stuff if we hit the item seeker
+/* if ( item->giTag == PW_FLASHLIGHT)
+ {
+ CG_RegisterWeapon( WP_COFFEE );
+ }*/
+
+ // hang onto the handles for holdable items in case they're useable (e.g. detpack)
+/* if (IT_HOLDABLE == item->giType)
+ {
+ // sanity check
+ if ( (item->giTag < HI_NUM_HOLDABLE) && (item->giTag > 0) ) // use "> 0" cuz first slot should be empty
+ {
+ if (item->world_model[1])
+ {
+ cgs.useableModels[item->giTag] = trap_R_RegisterModel( item->useablemodel );
+ }
+ else
+ {
+ cgs.useableModels[item->giTag] = itemInfo->model];
+ }
+ }
+ }
+*/
+}
+
+
+/*
+========================================================================================
+
+VIEW WEAPON
+
+========================================================================================
+*/
+
+/*
+=================
+CG_MapTorsoToWeaponFrame
+
+=================
+*/
+static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) {
+ animation_t *anim;
+
+ // change weapon
+ anim = &cg_animsList[ci->animIndex].animations[TORSO_DROPWEAP1];
+ if ( frame >= anim->firstFrame
+ && frame < anim->firstFrame + 9 ) {
+ return frame - anim->firstFrame + 6;
+ }
+
+ // stand attack
+ anim = &cg_animsList[ci->animIndex].animations[BOTH_ATTACK3];
+ if ( frame >= anim->firstFrame && frame < anim->firstFrame + 6 ) {
+ return 1 + frame - anim->firstFrame;
+ }
+
+ // stand attack 2
+ anim = &cg_animsList[ci->animIndex].animations[BOTH_ATTACK2];
+ if ( frame >= anim->firstFrame && frame < anim->firstFrame + 6 ) {
+ return 1 + frame - anim->firstFrame;
+ }
+
+ anim = &cg_animsList[ci->animIndex].animations[TORSO_WEAPONREADY1];
+ if ( frame >= anim->firstFrame && frame < anim->firstFrame + 6 ) {
+ return 1 + frame - anim->firstFrame;
+ }
+
+ // change weapon
+ //USED TO BE TORSO_RAISE
+/* if ( frame >= ci->animations[TORSO_DROPWEAP1].firstFrame
+ && frame < ci->animations[TORSO_DROPWEAP1].firstFrame + 9 ) {
+ return frame - ci->animations[TORSO_DROPWEAP1].firstFrame + 6;
+ }
+
+ // stand attack
+ if ( frame >= ci->animations[BOTH_ATTACK3].firstFrame
+ && frame < ci->animations[BOTH_ATTACK3].firstFrame + 6 ) {
+ return 1 + frame - ci->animations[BOTH_ATTACK3].firstFrame;
+ }
+
+ // stand attack 2
+ if ( frame >= ci->animations[BOTH_ATTACK2].firstFrame
+ && frame < ci->animations[BOTH_ATTACK2].firstFrame + 6 ) {
+ return 1 + frame - ci->animations[BOTH_ATTACK2].firstFrame;
+ }*/
+
+ return 0;
+}
+
+/*
+==============
+CG_CalculateWeaponPosition
+==============
+*/
+//BOOKMARK
+static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) {
+ float scale;
+ int delta;
+ float fracsin;
+
+ VectorCopy( cg.refdef.vieworg, origin );
+ VectorCopy( cg.refdefViewAngles, angles );
+
+ // on odd legs, invert some angles
+ if ( cg.bobcycle & 1 ) {
+ scale = -cg.xyspeed;
+ } else {
+ scale = cg.xyspeed;
+ }
+
+ // gun angles from bobbing
+ angles[ROLL] += scale * cg.bobfracsin * 0.005;
+ angles[YAW] += scale * cg.bobfracsin * 0.01;
+ angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005;
+
+ // drop the weapon when landing
+ delta = cg.time - cg.landTime;
+ if ( delta < LAND_DEFLECT_TIME ) {
+ origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME;
+ } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
+ origin[2] += cg.landChange*0.25 *
+ (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME;
+ }
+
+#if 0
+ // drop the weapon when stair climbing
+ delta = cg.time - cg.stepTime;
+ if ( delta < STEP_TIME/2 ) {
+ origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2);
+ } else if ( delta < STEP_TIME ) {
+ origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2);
+ }
+#endif
+
+ // idle drift
+ scale = cg.xyspeed + 40;
+ fracsin = sin( cg.time * 0.001 );
+ angles[ROLL] += scale * fracsin * 0.01;
+ angles[YAW] += scale * fracsin * 0.01;
+ angles[PITCH] += scale * fracsin * 0.01;
+}
+
+
+/*
+===============
+CG_LightningBolt
+
+Origin will be the exact tag point, which is slightly
+different than the muzzle point used for determining hits.
+The cent should be the non-predicted cent if it is from the player,
+so the endpoint will reflect the simulated strike (lagging the predicted
+angle)
+===============
+*/
+
+#define RANGE_BEAM (2048.0)
+#define BEAM_VARIATION 6
+
+void CG_LightningBolt( centity_t *cent, vec3_t origin )
+{
+ trace_t trace;
+// gentity_t *traceEnt;
+ vec3_t startpos, endpos, forward;
+ qboolean spark = qfalse, impact = qtrue;
+
+ if ( cg.snap->ps.pm_type == PM_INTERMISSION )
+ {
+ return; // Don't draw a phaser during an intermission you crezzy mon!
+ }
+
+ //Must be a durational weapon
+ if ( cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson && !(cent->currentState.eFlags & EF_ITEMPLACEHOLDER ) ) //fuck decoys
+ {
+ // different checks for first person view
+ if ( cg.snap->ps.weapon == WP_HYPERSPANNER ||
+ cg.snap->ps.weapon == WP_PHASER ||
+ cg.snap->ps.weapon == WP_DERMAL_REGEN ||
+ (cg.snap->ps.eFlags & EF_ALT_FIRING && cg.snap->ps.weapon == WP_COMPRESSION_RIFLE )
+ || (!(cg.snap->ps.eFlags & EF_ALT_FIRING) && cg.snap->ps.weapon == WP_DISRUPTOR )
+ )
+ { /*continue*/ }
+ else
+ return;
+ } else {
+ if ( cent->currentState.weapon == WP_HYPERSPANNER ||
+ cent->currentState.weapon == WP_PHASER ||
+ cent->currentState.weapon == WP_DERMAL_REGEN ||
+ (cent->currentState.eFlags & EF_ALT_FIRING && (cent->currentState.weapon == WP_COMPRESSION_RIFLE) ) ||
+ (!(cent->currentState.eFlags & EF_ALT_FIRING) && cent->currentState.weapon == WP_DISRUPTOR)
+ )
+ { /*continue*/ }
+ else
+ return;
+ }
+
+ // Find the impact point of the beam
+ if ( cent->currentState.clientNum == cg.snap->ps.clientNum
+ && !cg.renderingThirdPerson ) {
+ // take origin from view
+/*
+ VectorCopy( cg.refdef.vieworg, origin );
+ VectorMA( origin, -8, cg.refdef.viewaxis[2], origin );
+ VectorMA( origin, 8, cg.refdef.viewaxis[0], origin );
+ VectorMA( origin, -2, cg.refdef.viewaxis[1], origin );
+*/
+ VectorCopy( cg.refdef.viewaxis[0], forward );
+ VectorCopy( cg.refdef.vieworg, startpos);
+ }
+ else
+ {
+ // take origin from entity
+ if ( cent->currentState.clientNum == cg.snap->ps.clientNum )
+ AngleVectors( cg.predictedPlayerState.viewangles, forward, NULL, NULL );
+ else
+ AngleVectors( cent->lerpAngles, forward, NULL, NULL );
+ VectorCopy( origin, startpos);
+
+ // Check first from the center to the muzzle.
+ CG_Trace(&trace, cent->lerpOrigin, vec3_origin, vec3_origin, origin, cent->currentState.number, MASK_SHOT);
+ if (trace.fraction < 1.0)
+ { // We hit something here... Stomp the muzzle back to the eye...
+ VectorCopy(cent->lerpOrigin, startpos);
+ if ( cg.snap->ps.eFlags & EF_FULL_ROTATE && Q_fabs( cg.snap->ps.viewangles[PITCH] ) > 89.0f )
+ startpos[2] -= 20;
+ else
+ startpos[2] += cg.snap->ps.viewheight;
+ }
+ }
+
+ VectorMA( startpos, RANGE_BEAM, forward, endpos );
+
+ // Add a subtle variation to the beam weapon's endpoint
+ /*for (i = 0; i < 3; i ++ )
+ {
+ endpos[i] += crandom() * BEAM_VARIATION;
+ }*/
+
+ CG_Trace( &trace, startpos, vec3_origin, vec3_origin, endpos, cent->currentState.number, MASK_SHOT );
+
+// traceEnt = &g_entities[ trace.entityNum ];
+
+ // Make sparking be a bit less frame-rate dependent..also never add sparking when we hit a surface with a NOIMPACT flag
+ if (!(trace.surfaceFlags & SURF_NOIMPACT))
+ {
+ spark = qtrue;
+ }
+
+ // Don't draw certain kinds of impacts when it hits a player and such..or when we hit a surface with a NOIMPACT flag
+ if ( cg_entities[trace.entityNum].currentState.eType == ET_PLAYER || (trace.surfaceFlags & SURF_NOIMPACT) )
+ {
+ impact = qfalse;
+ }
+
+ // Add in the effect
+ switch ( cent->currentState.weapon )
+ {
+ case WP_PHASER:
+ if (cg.snap->ps.rechargeTime == 0)
+ {
+ if ( cent->currentState.eFlags & EF_ALT_FIRING )
+ FX_PhaserAltFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty );
+ else
+ FX_PhaserFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty );
+ }
+ break;
+ case WP_COMPRESSION_RIFLE:
+ if ( cent->currentState.eFlags & EF_ALT_FIRING )
+ {
+ FX_PrifleBeamFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty );
+ }
+ break;
+ case WP_HYPERSPANNER:
+ if ( cent->currentState.eFlags & EF_ALT_FIRING )
+ FX_ProbeBeam( origin, forward, cent->currentState.clientNum, qtrue );
+ else
+ FX_ProbeBeam( origin, forward, cent->currentState.clientNum, qfalse );
+ break;
+
+ case WP_DERMAL_REGEN:
+ if ( cent->currentState.eFlags & EF_ALT_FIRING )
+ FX_RegenBeam( origin, forward, cent->currentState.clientNum, qtrue );
+ else
+ FX_RegenBeam( origin, forward, cent->currentState.clientNum, qfalse );
+ break;
+
+ case WP_DISRUPTOR:
+ if ( cent->currentState.eFlags & EF_FIRING && !(cent->currentState.eFlags & EF_ALT_FIRING) )
+ FX_DisruptorBeamFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty );
+
+/* case WP_DERMAL_REGEN:
+ if (!(cent->currentState.eFlags & EF_ALT_FIRING))
+ {
+ vec3_t org;
+
+ // Move the beam back a bit to help cover up the poly edges on the fire beam
+ VectorMA( origin, -4, forward, org );
+ FX_DreadnoughtFire( org, trace.endpos, trace.plane.normal, spark, impact );
+ }
+ break;*/
+ }
+}
+
+
+/*
+======================
+CG_MachinegunSpinAngle
+======================
+*/
+#define SPIN_SPEED 0.9
+#define COAST_TIME 1000
+static float CG_MachinegunSpinAngle( centity_t *cent ) {
+ int delta;
+ float angle;
+ float speed;
+
+ delta = cg.time - cent->pe.barrelTime;
+ if ( cent->pe.barrelSpinning ) {
+ angle = cent->pe.barrelAngle + delta * SPIN_SPEED;
+ } else {
+ if ( delta > COAST_TIME ) {
+ delta = COAST_TIME;
+ }
+
+ speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
+ angle = cent->pe.barrelAngle + delta * speed;
+ }
+
+ if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) {
+ cent->pe.barrelTime = cg.time;
+ cent->pe.barrelAngle = AngleMod( angle );
+ cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING);
+ }
+
+ return angle;
+}
+
+
+/*
+========================
+CG_AddWeaponWithPowerups
+========================
+*/
+
+static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups, beamData_t* beamData, int cloakTime, int decloakTime ) //
+{
+ // add powerup effects
+ if ( powerups & ( 1 << PW_INVIS ) || ( !(powerups & ( 1 << PW_INVIS )) && decloakTime > 0 ) ) {
+
+ //TiM - modified so it persists during the first bit of cloaking / last of decloaking
+ if ( ( cloakTime <= 0 && decloakTime <= 0 ) || ( decloakTime > 0 && cg.time < ( decloakTime + Q_FLASH_TIME * 0.5 ) )
+ || ( cloakTime > 0 && cg.time > ( cloakTime + Q_FLASH_TIME * 0.5 ) ) )
+ {
+ if ( /*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/ cgs.clientinfo[cg.snap->ps.clientNum].isAdmin )
+ {//admins can see cloaked people
+ //gun->customShader = cgs.media.teleportEffectShader;
+ //TiM - Make it look cooler - Half invis
+ gun->renderfx |= RF_FORCE_ENT_ALPHA;
+ gun->shaderRGBA[3] = (unsigned char)(0.4f * 255.0f);
+ trap_R_AddRefEntityToScene( gun );
+ }
+ }
+ else
+ trap_R_AddRefEntityToScene( gun );
+
+ //gun->customShader = cgs.media.invisShader;
+ //trap_R_AddRefEntityToScene( gun );
+ }
+ else if ( powerups & ( 1 << PW_BEAM_OUT ) || powerups & ( 1 << PW_QUAD ) )
+ {
+ int btime;
+ btime = cg.time - beamData->beamTimeParam;
+
+ if ( btime <= PLAYER_BEAM_FADE ) {
+ if ( powerups & ( 1 << PW_BEAM_OUT ) ) {
+ gun->shaderRGBA[3] = 255;
+ }
+ else {
+ gun->shaderRGBA[3] = 0;
+ }
+ }
+ else if ( btime >= ( PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME ) ) {
+ if ( powerups & ( 1 << PW_BEAM_OUT ) ) {
+ gun->shaderRGBA[3] = 0;
+ }
+ else {
+ gun->shaderRGBA[3] = 255;
+ }
+ }
+
+ if (btime > PLAYER_BEAM_FADE && btime < (PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME) )
+ {
+ gun->renderfx |= RF_FORCE_ENT_ALPHA;
+ gun->shaderRGBA[3] = (int)(255 * beamData->beamAlpha);
+ }
+
+ if ( gun->shaderRGBA[3] > 0 ) {
+ trap_R_AddRefEntityToScene( gun );
+ gun->renderfx &= ~RF_FORCE_ENT_ALPHA;
+ gun->shaderRGBA[3] = 255;
+ }
+
+ //Just a precaution. Loop it once, then the player should be invisible
+ if ( btime < 4100 ) {
+ gun->customShader = cgs.media.transportShader;
+ gun->shaderTime = beamData->beamTimeParam * 0.001;
+ trap_R_AddRefEntityToScene( gun );
+ }
+ } else if(powerups & (1 << PW_BORG_ADAPT)) {
+ gun->renderfx |= RF_FORCE_ENT_ALPHA;
+ gun->shaderRGBA[3] = 255;
+ trap_R_AddRefEntityToScene(gun);
+ gun->customShader = cgs.media.borgFullBodyShieldShader;
+ trap_R_AddRefEntityToScene(gun);
+ return;
+ }
+ else {
+ trap_R_AddRefEntityToScene( gun );
+
+ if(gun->renderfx & RF_FORCE_ENT_ALPHA) {
+ gun->renderfx &= ~RF_FORCE_ENT_ALPHA;
+ }
+
+/* if ( powerups & ( 1 << PW_BOLTON ) ) {
+ gun->customShader = cgs.media.battleWeaponShader;
+ trap_R_AddRefEntityToScene( gun );
+ }*/
+
+/* if ( powerups & ( 1 << PW_QUAD ) ) {
+ gun->customShader = cgs.media.quadWeaponShader;
+ trap_R_AddRefEntityToScene( gun );
+ }*/
+ /*if (powerups & (1 << PW_OUCH))
+ {
+ gun->customShader = cgs.media.holoOuchShader;
+ // set rgb to 1 of 16 values from 0 to 255. don't use random so that the three
+ //parts of the player model as well as the gun will all look the same
+ gun->shaderRGBA[0] =
+ gun->shaderRGBA[1] =
+ gun->shaderRGBA[2] = ((cg.time % 17)*0.0625)*255;//irandom(0,255);
+ trap_R_AddRefEntityToScene(gun);
+ }*/
+ }
+}
+
+/*void CG_CoffeeSteamFirstPerson ( refEntity_t* parent, weaponInfo_t *weapon ) {
+ refEntity_t steam;
+ vec3_t angle = { 0.0, 0.0, 6.0 };
+
+ CG_PositionEntityOnTag( &steam, parent, weapon->viewModel, "tag_steam1" );
+
+ if ( VectorCompare( steam.origin, parent->origin ) ) {//whelp, for some whacky reason, there's no tag O_o
+ return;
+ }
+
+ //CG_Steam( steam.origin, angle);
+ //Disables the OpenGL Hack where the viewmodel is drawn over EVERYTHING ELSE INCLUDING THE STEAM
+ parent->renderfx &= ~RF_DEPTHHACK;
+
+ if (cg.time % 10 == 0 ) {
+ FX_AddSprite( steam.origin,
+ angle, qfalse,
+ ( random() * 3 + 1), (10), //random() * 4 + 2 //12
+ 0.6 + random() * 0.4, 0.0,
+ random() * 120, //180
+ 0.0,
+ 1300, //300 //random() * 200 + 1200, //300 //
+ cgs.media.steamShader );
+ }
+ /*localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float elasticity,
+ float killTime, qhandle_t shader)*/
+
+ //if (
+/*}
+
+void CG_CoffeeSteamThirdPerson ( refEntity_t* parent, weaponInfo_t *weapon) {
+ refEntity_t steam;
+ localEntity_t *le = NULL;
+
+ vec3_t angle = { 0.0, 0.0, 6.0 };
+
+ CG_PositionEntityOnTag( &steam, parent, weapon->weaponModel, "tag_steam");
+
+ if ( VectorCompare( steam.origin, parent->origin ) ) {//whelp, for some whacky reason, there's no tag O_o
+ return;
+ }
+
+ if (cg.time % 10 == 0 ) {
+ le = FX_AddSprite( steam.origin,
+ angle, qfalse,
+ ( random() * 1.2 + 0.5), ( 5 ), //random() * 4 + 2 //12
+ 0.6 + random() * 0.4, 0.0,
+ random() * 120, //180
+ 0.0,
+ 1300, //300 //random() * 200 + 1200, //300 //
+ cgs.media.steamShader );
+ }
+ /*localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float elasticity,
+ float killTime, qhandle_t shader)*/
+
+ //if (
+//}*/
+
+void CG_CoffeeSteam( refEntity_t* parent, weaponInfo_t *weapon, qboolean thirdperson ) {
+ refEntity_t steam;
+ localEntity_t *le;
+
+ vec3_t angle = { 0.0, 0.0, 10.0 };
+
+ //FIXME: I probably should name the tag the same thing in both models... O_o
+ if ( !thirdperson ) {
+ CG_PositionEntityOnTag( &steam, parent, weapon->viewModel, "tag_steam1" );
+ }
+ else {
+ CG_PositionEntityOnTag( &steam, parent, weapon->weaponModel, "tag_steam");
+ }
+
+ if ( VectorCompare( steam.origin, parent->origin ) ) {//whelp, for some whacky reason, there's no tag O_o
+ return;
+ }
+
+ //CG_Steam( steam.origin, angle);
+ //Disables the OpenGL Hack where the viewmodel is drawn over EVERYTHING ELSE INCLUDING THE STEAM
+ parent->renderfx &= ~RF_DEPTHHACK;
+
+ if (cg.time % 10 == 0 ) { //release a sprite every .01 of a second
+ le = FX_AddSprite( steam.origin,
+ angle, qfalse,
+ ( thirdperson ? random() * 1.2 + 0.5 : random() * 1 + 1), ( thirdperson ? 7 : 10), //random() * 4 + 2 //12
+ 0.05 + random() * 0.1, 0.0,
+ random() * 120, //180
+ 0.0,
+ 1500, //300 //random() * 200 + 1200, //300 //
+ cgs.media.steamShader );
+ }
+ /*localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float elasticity,
+ float killTime, qhandle_t shader)*/
+ //Without this, the steam gets drawn behind the cup... which looks weird
+ //le->refEntity.renderfx |= RF_DEPTHHACK;
+}
+
+/*
+=============
+getClassColor
+
+RPG-X : TiM - used to determine what color skins the weapons should have applied to them
+My way of having to not have to enter in so many conditionals over and over
+=============
+*/
+
+//char *getClassColor ( void )
+//{
+// /*if (( cg.snap->ps.persistant[PERS_CLASS] == PC_SECURITY )
+// || ( cg.snap->ps.persistant[PERS_CLASS] == PC_ENGINEER))
+// {
+// return "default";
+// }
+//
+// if (( cg.snap->ps.persistant[PERS_CLASS] == PC_SCIENCE )
+// || ( cg.snap->ps.persistant[PERS_CLASS] == PC_MEDICAL )
+// || ( cg.snap->ps.persistant[PERS_CLASS] == PC_ALPHAOMEGA22 ))
+// {
+// return "teal";
+// }
+//
+// if ((cg.snap->ps.persistant[PERS_CLASS] == PC_COMMAND)
+// || (cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN))
+// {
+// return "red";
+// }
+// if ( cg.snap->ps.persistant[PERS_CLASS] == PC_NOCLASS ) {
+// return "NULL";
+// }*/
+//
+// //lolz... this time, let's base it off of current model
+// //rather than class
+// cgs.clientinfo[0].
+//
+// return "default";
+//}
+
+
+/*
+=============
+CG_AddPlayerWeapon
+
+Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL)
+The main player will have this called for BOTH cases, so effects like light and
+sound should only be done on the world model case.
+=============
+*/
+void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ) {
+ refEntity_t gun;
+ refEntity_t barrel;
+ refEntity_t flash;
+ vec3_t angles;
+ weapon_t weaponNum;
+ weaponInfo_t *weapon;
+ centity_t *nonPredictedCent;
+ int i = 0, numBarrels = 0;
+ wpnBarrelInfo_t *barrelInfo = NULL;
+
+ char filename[MAX_QPATH];
+ char* skinColor;
+
+ weaponNum = cent->currentState.weapon;
+
+ CG_RegisterWeapon( weaponNum );
+ weapon = &cg_weapons[weaponNum];
+
+ // add the weapon
+ memset( &gun, 0, sizeof( gun ) );
+ VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
+ gun.shadowPlane = parent->shadowPlane;
+ gun.renderfx = parent->renderfx;
+
+ // set custom shading for railgun refire rate
+ /*if ( ps ) {
+ if ( cg.predictedPlayerState.weapon == WP_NULL_HAND
+ && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) {
+ float f;
+
+ f = (float)cg.predictedPlayerState.weaponTime / 1500;
+ gun.shaderRGBA[1] = 0;
+ gun.shaderRGBA[0] =
+ gun.shaderRGBA[2] = 255 * ( 1.0 - f );
+ } else {
+ gun.shaderRGBA[0] = 255;
+ gun.shaderRGBA[1] = 255;
+ gun.shaderRGBA[2] = 255;
+ gun.shaderRGBA[3] = 255;
+ }
+ }*/
+
+ if (ps)
+ {
+ qhandle_t skin;
+
+ gun.hModel = weapon->viewModel;
+
+ skinColor = cgs.clientinfo[cg.snap->ps.clientNum].skinName;
+
+ //if ( skinColor != "NULL" ) { //RPG-X : TiM - Will change the color of the band on the viewmodel's arm, depending what class
+ if(!Q_stricmpn(skinColor, "NULL", 4)) {
+ for ( i = 0; i < 8; i++ ) {
+ if ( cg.predictedPlayerState.weapon == (RAweapons[i]) ) {
+ Com_sprintf( filename, sizeof( filename ),"models/weapons2/%s/model_%s.skin", RAweapFileName[i], skinColor ); //Formulate the skin route
+
+ skin = trap_R_RegisterSkin ( filename );
+
+ if ( !skin )
+ break;
+
+ gun.customSkin = skin; //and 'plonk' it on the model :)
+ break;
+ }
+ }
+ }
+
+ }
+ else
+ {
+ gun.hModel = weapon->weaponModel;
+ }
+
+ if (!gun.hModel) {
+ return;
+ }
+
+ if ( !ps ) {
+ // add weapon stop sound
+ if ( !( cent->currentState.eFlags & EF_FIRING ) && !( cent->currentState.eFlags & EF_ALT_FIRING ) && cent->pe.lightningFiring &&
+ cg.predictedPlayerState.ammo[cg.predictedPlayerState.weapon] )
+ {
+ if (weapon->stopSound)
+ {
+ trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_WEAPON, weapon->stopSound );
+ }
+ else if (weapon->altStopSound )
+ {
+ trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_WEAPON, weapon->altStopSound );
+ }
+ }
+ cent->pe.lightningFiring = qfalse;
+ if ( cent->currentState.eFlags & EF_ALT_FIRING )
+ {
+ // hark, I smell hackery afoot
+ if ((weaponNum == WP_PHASER) && !(cg.predictedPlayerState.ammo[WP_PHASER]))
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.phaserEmptySound );
+ cent->pe.lightningFiring = qtrue;
+ }
+ else if ( weapon->altFiringSound && !weapon->isAnimSndBased )
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->altFiringSound );
+ cent->pe.lightningFiring = qtrue;
+ }
+
+ if ( weaponNum == WP_TOOLKIT || weaponNum == WP_MEDKIT ) {
+ cent->pe.lightningFiring = qtrue;
+ }
+ }
+ else if ( cent->currentState.eFlags & EF_FIRING )
+ {
+ if ((weaponNum == WP_PHASER) && !(cg.predictedPlayerState.ammo[WP_PHASER]))
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.phaserEmptySound );
+ cent->pe.lightningFiring = qtrue;
+ }
+ else if ( weapon->firingSound && !weapon->isAnimSndBased )
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound );
+ cent->pe.lightningFiring = qtrue;
+ }
+
+ //TiM: Haxxor. I want the medkit + toolkit sounds to play only once when u hold them down
+ if ( weaponNum == WP_TOOLKIT || weaponNum == WP_MEDKIT ) {
+ cent->pe.lightningFiring = qtrue;
+ }
+ }
+ }
+
+
+ CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon");
+ //RPG-X : TiM - A little variety here :) Toolkit gets attached to player model's left hand, medkit on waist :)
+ //Hack: I dunno why, but unless I specified thirdperson (ie (!ps) ), the viewmodel went crazy. :P
+ if (!ps) {
+ if (( weaponNum == WP_TOOLKIT ) ) { //Toolkit //cg.predictedPlayerState.weapon
+ CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_lhand");
+ }
+ else if (( weaponNum == WP_MEDKIT ) ) { //Medkit
+ CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_torso");
+ }
+ /*else {
+ CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon");
+ }*/
+ //TiM: also in the hopes of keeping the weapon scale constant in contrast to the player model
+ gun.nonNormalizedAxes = qfalse;
+ }
+
+ if ( weaponNum == WP_COFFEE ) {
+ if ( !ps ) {
+ if ( !(!cg.renderingThirdPerson && cent->currentState.clientNum == cg.predictedPlayerState.clientNum) )
+ CG_CoffeeSteam( &gun, weapon, qtrue );
+ }
+ //else {
+ // CG_CoffeeSteam( &gun, weapon, qfalse );
+ //}
+ }
+
+ CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups, ¢->beamData, cent->cloakTime, cent->decloakTime );
+
+ // add the spinning barrel
+ //
+ //
+ for (barrelInfo = wpnBarrelData; barrelInfo->giTag != WP_NONE; barrelInfo++)
+ {
+ if (barrelInfo->giTag == weaponNum)
+ {
+ numBarrels = barrelInfo->numBarrels;
+ break;
+ }
+ }
+
+ // don't add barrels to world model...only viewmodels
+ if (ps)
+ {
+ for (i = 0; i < numBarrels; i++)
+ {
+ memset( &barrel, 0, sizeof( barrel ) );
+ VectorCopy( parent->lightingOrigin, barrel.lightingOrigin );
+ barrel.shadowPlane = parent->shadowPlane;
+ barrel.renderfx = parent->renderfx;
+
+ barrel.hModel = weapon->barrelModel[i];
+ angles[YAW] = 0;
+ angles[PITCH] = 0;
+ if ( weaponNum == WP_TR116) {
+ angles[ROLL] = CG_MachinegunSpinAngle( cent );
+ } else {
+ angles[ROLL] = 0;//CG_MachinegunSpinAngle( cent );
+ }
+ AnglesToAxis( angles, barrel.axis );
+
+ if (!i) {
+ CG_PositionRotatedEntityOnTag( &barrel, parent, weapon->handsModel, "tag_barrel" );
+ } else {
+ CG_PositionRotatedEntityOnTag( &barrel, parent, weapon->handsModel, va("tag_barrel%d",i+1) );
+ }
+
+ CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ¢->beamData, cent->cloakTime, cent->decloakTime );
+ }
+ }
+
+ // make sure we aren't looking at cg.predictedPlayerEntity for LG
+ nonPredictedCent = &cg_entities[cent->currentState.clientNum];
+
+ // if the index of the nonPredictedCent is not the same as the clientNum
+ // then this is a fake player (like on teh single player podiums), so
+ // go ahead and use the cent
+ if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) {
+ nonPredictedCent = cent;
+ }
+
+ //Com_Printf("eType: %i, eventParm: %i, weaponNum: %i\n", cent->currentState.eType, cent->currentState.eventParm, weaponNum);
+ if ( weaponNum == WP_COMPRESSION_RIFLE
+ &&
+ cent->currentState.powerups & ( 1 << PW_FLASHLIGHT )
+ &&
+ cent->beamData.beamTimeParam == 0
+ &&
+ ( !(cent->currentState.powerups & ( 1 << PW_INVIS ))
+ || cent->currentState.clientNum == cg.predictedPlayerState.clientNum )
+ )
+ { //FIXME: TiM - need to know if flashlight is on or off at the time :S
+ refEntity_t flashlight;
+
+ memset( &flashlight, 0, sizeof( flashlight ) );
+ VectorCopy( parent->lightingOrigin, flashlight.lightingOrigin );
+ flashlight.shadowPlane = parent->shadowPlane;
+ flashlight.renderfx = parent->renderfx;
+
+ flashlight.hModel = cgs.media.flashlightModel;
+ if (!flashlight.hModel) {
+ return;
+ }
+
+ angles[YAW] = 0;
+ angles[PITCH] = 0;
+ angles[ROLL] = 0;
+
+ AnglesToAxis( angles, flashlight.axis );
+
+ if (ps)
+ { // Rendering inside the head...
+ CG_PositionRotatedEntityOnTag( &flashlight, &gun, weapon->viewModel, "tag_flashlight");
+ }
+ else
+ { // Rendering outside the head...
+ CG_PositionRotatedEntityOnTag( &flashlight, &gun, weapon->weaponModel, "tag_flashlight");
+ }
+ trap_R_AddRefEntityToScene( &flashlight );
+ }
+
+ // add the flash
+ if ( ( weaponNum == WP_PHASER ||
+ weaponNum == WP_DERMAL_REGEN)
+ && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) )
+ {
+ // continuous flash
+ }
+ else
+ {
+ // impulse flash
+ //if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME) {
+ if ( cg.time - cent->muzzleFlashTime > wpnBarrelData[weaponNum-1].flashTime )
+ {
+ return;
+ }
+ }
+
+ memset( &flash, 0, sizeof( flash ) );
+ VectorCopy( parent->lightingOrigin, flash.lightingOrigin );
+ flash.shadowPlane = parent->shadowPlane;
+ flash.renderfx = parent->renderfx;
+
+ flash.hModel = weapon->flashModel;
+ if (!flash.hModel) {
+ return;
+ }
+
+ angles[YAW] = 0;
+ angles[PITCH] = 0;
+ angles[ROLL] = 0; //angles[ROLL] = crandom() * 10; //RPG-X - TiM: This stops the lensflare on the muzzle from jiggling around
+
+ AnglesToAxis( angles, flash.axis );
+
+ //TiM - Instead of briefly showing the flash, show it scaling down
+ if (weaponNum != WP_PHASER &&
+ weaponNum != WP_HYPERSPANNER &&
+ weaponNum != WP_DERMAL_REGEN &&
+ !(weaponNum == WP_COMPRESSION_RIFLE && (cent->currentState.eFlags & EF_ALT_FIRING) ) &&
+ !(weaponNum == WP_DISRUPTOR && !(cent->currentState.eFlags & EF_ALT_FIRING) )
+ )
+ {
+ float scale;
+ scale = (1.0f - ( (float)(cg.time - cent->muzzleFlashTime) / (float)wpnBarrelData[weaponNum-1].flashTime )) * 2.0f;
+
+ flash.nonNormalizedAxes = qtrue;
+ VectorScale( flash.axis[0], scale, flash.axis[0] );
+ VectorScale( flash.axis[1], scale, flash.axis[1] );
+ VectorScale( flash.axis[2], scale, flash.axis[2] );
+ }
+
+ //TiM - quick hack
+ //jiggle the scale of the phaser rifle on alt fire around
+ if ( (weaponNum == WP_COMPRESSION_RIFLE && (cent->currentState.eFlags & EF_ALT_FIRING))
+ ||
+ ( weaponNum == WP_DISRUPTOR && !(cent->currentState.eFlags & EF_ALT_FIRING)) )
+ {
+ float min, max;
+
+ if ( weaponNum == WP_COMPRESSION_RIFLE )
+ {
+ min = 1.3f;
+ max = 1.6f;
+ }
+ else
+ {
+ min = 0.8f;
+ max = 0.9f;
+ }
+
+ VectorScale( flash.axis[0], flrandom(min, max), flash.axis[0] );
+ VectorScale( flash.axis[1], flrandom(min, max), flash.axis[1] );
+ VectorScale( flash.axis[2], flrandom(min, max), flash.axis[2] );
+ }
+
+ if (cent->pe.empty)
+ { // Make muzzle flash wussy when empty.
+ flash.customShader = cgs.media.phaserMuzzleEmptyShader;
+ }
+
+ if (ps)
+ { // Rendering inside the head...
+ CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->viewModel, "tag_flash" );
+ }
+ else
+ { // Rendering outside the head...
+ CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash" );
+ }
+
+ if ( !(cent->currentState.powerups & ( 1 << PW_INVIS ))
+ || cent->currentState.clientNum == cg.predictedPlayerState.clientNum )
+ {
+ trap_R_AddRefEntityToScene( &flash );
+ }
+
+ if ( ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum || cg_firstPersonBody.integer )
+ {
+ // add phaser/dreadnought
+ // grrr nonPredictedCent doesn't have the proper empty setting
+ nonPredictedCent->pe.empty = cent->pe.empty;
+ CG_LightningBolt( nonPredictedCent, flash.origin );
+
+ // make a dlight for the flash
+ if ( (weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2]) && !(cent->currentState.powerups & ( 1 << PW_INVIS ) ) ) {
+ trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), weapon->flashDlightColor[0],
+ weapon->flashDlightColor[1], weapon->flashDlightColor[2] );
+ }
+ }
+}
+
+/*
+==============
+CG_AddViewWeapon
+
+Add the weapon, and flash for the player's view
+==============
+*/
+void CG_AddViewWeapon( playerState_t *ps ) {
+ refEntity_t hand;
+ centity_t *cent;
+ clientInfo_t *ci;
+ float fovOffset;
+ vec3_t angles;
+ weaponInfo_t *weapon;
+
+ if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (ps->eFlags&EF_ELIMINATED)*/ ) {
+ return;
+ }
+
+ if ( ps->pm_type == PM_INTERMISSION ) {
+ return;
+ }
+
+ // no gun if in third person view
+ if ( cg.renderingThirdPerson || cg_firstPersonBody.integer ) {
+ return;
+ }
+
+ // allow the gun to be completely removed
+ //TiM: Added alt fire for alt-fire beam weapons
+ if ( !cg_drawGun.integer || cg.zoomed ) {
+ vec3_t origin;
+
+ if ( cg.predictedPlayerState.eFlags & EF_FIRING || cg.predictedPlayerState.eFlags & EF_ALT_FIRING )
+ {
+ // special hack for phaser/dreadnought...
+ VectorCopy( cg.refdef.vieworg, origin );
+ VectorMA( origin, -8, cg.refdef.viewaxis[2], origin );
+ CG_LightningBolt( &cg_entities[ps->clientNum], origin );
+ }
+ return;
+ }
+
+ if ( (cg.zoomed) && (ps->weapon == WP_COMPRESSION_RIFLE) ) { //RPG-X : TiM - People were saying that being able to see the gunsight on the rifle thru the gunsight in zoom mode was weird :P
+ return;
+ }
+
+ // don't draw if testing a gun model
+ if ( cg.testGun ) {
+ return;
+ }
+
+ // drop gun lower at higher fov
+ if ( cg_fov.integer > 80 ) {
+ fovOffset = -0.2 * ( cg_fov.integer - 80 );
+ } else {
+ fovOffset = 0;
+ }
+
+ cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum];
+ CG_RegisterWeapon( ps->weapon );
+ weapon = &cg_weapons[ ps->weapon ];
+
+ memset (&hand, 0, sizeof(hand));
+
+ // set up gun position
+ CG_CalculateWeaponPosition( hand.origin, angles );
+
+ VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin );
+ VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin );
+ VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin );
+
+ AnglesToAxis( angles, hand.axis );
+
+ // map torso animations to weapon animations
+ if ( cg_gun_frame.integer ) {
+ // development tool
+ hand.frame = hand.oldframe = cg_gun_frame.integer;
+ hand.backlerp = 0;
+ } else {
+ // get clientinfo for animation map
+ ci = &cgs.clientinfo[ cent->currentState.clientNum ];
+ hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame );
+ hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame );
+ hand.backlerp = cent->pe.torso.backlerp;
+ }
+
+ hand.hModel = weapon->handsModel;
+ hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON;
+
+ // add everything onto the hand
+ CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity );
+}
+
+/*
+==============================================================================
+
+WEAPON SELECTION
+
+==============================================================================
+*/
+
+void static CG_RegisterWeaponIcon( int weaponNum ) {
+ weaponInfo_t *weaponInfo;
+ gitem_t *item;
+
+ weaponInfo = &cg_weapons[weaponNum];
+
+ if ( weaponNum == 0 ) {
+ return;
+ }
+
+ if ( weaponInfo->registered ) {
+ return;
+ }
+
+ for ( item = bg_itemlist + 1 ; item->classname ; item++ ) {
+ if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) {
+ weaponInfo->item = item;
+ break;
+ }
+ }
+ if ( !item->classname ) {
+ CG_Error( "Couldn't find weapon %i", weaponNum );
+ }
+
+ weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon );
+}
+
+/*
+==================
+CG_DrawWeaponIcon
+RPG-X | Phenix | 08/06/2005
+RPG-X | TiM | 5/1/2006
+===========================
+*/
+void CG_DrawWeaponIcon ( int x, int y, int weapon )
+{
+ /*vec4_t color;
+
+ color[3] = alpha;
+ if ( !color[3] ) {
+ return;
+ }*/
+
+ CG_RegisterWeaponIcon( weapon ); //short version
+
+ // draw selection marker
+
+ if ( weapon == cg.weaponSelect )
+ {
+ trap_R_SetColor( colorTable[CT_LTPURPLE1] );
+ }
+ else
+ {
+ trap_R_SetColor(colorTable[CT_DKPURPLE1]);
+ }
+
+ CG_DrawPic( x-4,y-4,38, 38, cgs.media.weaponbox);
+
+ // draw weapon icon
+ trap_R_SetColor(colorTable[CT_WHITE]);
+ CG_DrawPic( x, y, 32, 32, cg_weapons[weapon].weaponIcon );
+
+ // draw selection marker
+ /*if ( weapon == cg.weaponSelect ) {
+ CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader );*/
+ //}
+ trap_R_SetColor( NULL );
+}
+
+/*
+===================
+CG_DrawWeaponSelect
+===================
+*/
+
+static int weaponRows[6][3] = { WP_NULL_HAND, 0, 0,
+ WP_TRICORDER, WP_PADD, WP_COFFEE,
+ WP_PHASER, WP_COMPRESSION_RIFLE, WP_TR116,
+ WP_GRENADE_LAUNCHER, WP_QUANTUM_BURST, WP_DISRUPTOR,
+ WP_MEDKIT, WP_VOYAGER_HYPO, WP_DERMAL_REGEN,
+ WP_TOOLKIT, WP_HYPERSPANNER, 0 };
+
+void CG_DrawWeaponSelect( void ) {
+ int i, rowCount, cellCount;
+ int bits;
+ //int count;
+ int x, y, w, defaultX, defaultY;
+ char *name;
+ float *color;
+ qboolean WeapOnThisRow = qfalse;
+ //vec4_t color;
+
+ // don't display if dead
+ if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 1 || cg.predictedPlayerState.eFlags & EF_DEAD ) { //RPG-X: RedTechie - No weapons at health 1 (you die at health 1 now)
+ return;
+ }
+
+ color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME );
+ if ( !color ) {
+ return;
+ }
+
+ // showing weapon select clears pickup item display, but not the blend blob
+ cg.itemPickupTime = 0;
+
+ // count the number of weapons owned
+ bits = cg.snap->ps.stats[ STAT_WEAPONS ];
+
+ //NEW HUD FOR RPG-X
+ defaultX = 18;
+ defaultY = 52;
+ x = defaultX;
+
+ y = (BIGCHAR_HEIGHT * 2) + 20;
+
+ for ( i = 0, rowCount = 0, cellCount = 0; i < MAX_WEAPONS; i++, cellCount++ ) {
+ if ( cellCount == 3 ) { //we've hit the end of the row
+ rowCount++; //go to the next row
+ cellCount = 0; //reset cell clock
+
+ if ( WeapOnThisRow ) {
+ //**** Draw the end caps *****
+ //VectorCopy( colorTable[CT_LTPURPLE2], color );
+ trap_R_SetColor(colorTable[CT_LTPURPLE2]);
+ // Left end cap
+ CG_DrawPic( 2, y - 5, 16, 50, cgs.media.weaponcap1); //6
+ // Right End Cap
+ CG_DrawPic( x - 20 + 16, y - 5, 16, 50, cgs.media.weaponcap2); //2 - 6, 16 - 18
+ trap_R_SetColor(NULL);
+
+ y += defaultY;
+ x = defaultX;
+
+ WeapOnThisRow = qfalse;
+ }
+
+ if ( rowCount >= 6 ) { //if we exceed our rows, that's bad O_o
+ break;
+ }
+ }
+
+ if ( weaponRows[rowCount][cellCount] == 0 ) {
+ i--;
+ continue;
+ }
+
+ if (bits & ( 1 << weaponRows[rowCount][cellCount] ) ) {
+ CG_DrawWeaponIcon( x, y, weaponRows[rowCount][cellCount] );
+ x += 40;
+
+ if ( !WeapOnThisRow ) {
+ WeapOnThisRow = qtrue;
+ }
+ }
+ }
+
+ // END HUD
+
+ // draw the selected names
+ if ( cg_weapons[ cg.weaponSelect ].item ) {
+ name = cg_weapons[ cg.weaponSelect ].item->pickup_name;
+ if ( name ) {
+ w= UI_ProportionalStringWidth(name,UI_SMALLFONT);
+ UI_DrawProportionalString(x, y, name, UI_SMALLFONT,color);
+
+ }
+ }
+
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+===============
+CG_WeaponSelectable
+===============
+*/
+static qboolean CG_WeaponSelectable( int i ) {
+ if ( !cg.snap->ps.ammo[i] ) {
+ return qfalse;
+ }
+ if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+extern int altAmmoUsage[];
+/*
+{
+ 0, //WP_NONE,
+ 2, //WP_PHASER,
+ 10, //WP_COMPRESSION_RIFLE,
+ 3, //WP_NULL_HAND,
+ 5, //WP_COFFEE,
+ 1, //WP_DISRUPTOR,
+ 1, //WP_GRENADE_LAUNCHER,
+ 2, //WP_TR116,
+ 2, //WP_QUANTUM_BURST,
+ 5 //WP_DERMAL_REGEN,
+ 20, //WP_VOYAGER_HYPO,
+ ##, //WP_TOOLKIT,
+ ##, //WP_MEDKIT,
+
+};
+*/
+
+/*
+===============
+CG_WeaponAltSelectable
+===============
+*/
+static qboolean CG_WeaponAltSelectable( int i ) {
+ if ( cg.snap->ps.ammo[i] < altAmmoUsage[cg.snap->ps.weapon]) {
+ return qfalse;
+ }
+ if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+/*
+===============
+CG_NextWeapon_f
+===============
+*/
+void CG_NextWeapon_f( void ) {
+ int i; //, topWeapon
+ int original;
+// int newWeapons[16];
+// int bits;
+
+ if ( !cg.snap ) {
+ return;
+ }
+ if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
+ return;
+ }
+
+ cg.weaponSelectTime = cg.time;
+ original = cg.weaponSelect;
+
+
+ //RPG-X | Phenix | 08/06/2005
+ //Removed to be replaced to scroll through our list
+ //TiM | 4/1/2006
+ //Put back in since I optimized the way weapons are handled
+ for ( i = 0 ; i < 16 ; i++ ) {
+ cg.weaponSelect++;
+ if ( cg.weaponSelect == 16 ) {
+ cg.weaponSelect = 0;
+ }
+ if ( CG_WeaponSelectable( cg.weaponSelect ) ) {
+ break;
+ }
+ }
+ if ( i == 16 ) {
+ cg.weaponSelect = original;
+ }
+
+ //TiM: Just for the record. Phenix. Enumerated value lists. Look them up. Use them!
+ //Reading this code was really tricky when it didn't have to be >.<
+ //ie 1 = WP_PHASER etc
+}
+
+/*
+===============
+CG_PrevWeapon_f
+===============
+*/
+void CG_PrevWeapon_f( void ) {
+ int i; //, topWeapon
+ int original;
+// int newWeapons[16];
+// int bits;
+
+ if ( !cg.snap ) {
+ return;
+ }
+ if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
+ return;
+ }
+
+ cg.weaponSelectTime = cg.time;
+ original = cg.weaponSelect;
+
+ //RPG-X | Phenix | 08/06/2005
+ //Removed to be replaced to scroll through our list
+ //TiM | 4/1/2006
+ //Put back in since I optimized the way weapons are handled
+ for ( i = 0 ; i < 16 ; i++ ) {
+ cg.weaponSelect--;
+ if ( cg.weaponSelect == -1 ) {
+ cg.weaponSelect = 15;
+ }
+ if ( CG_WeaponSelectable( cg.weaponSelect ) ) {
+ break;
+ }
+ }
+ if ( i == 16 ) {
+ cg.weaponSelect = original;
+ }
+}
+
+/*
+===============
+CG_Weapon_f
+===============
+*/
+/*TiM : Here for reference
+static int weaponRows[6][3] = { WP_NULL_HAND, 0, 0,
+ WP_TRICORDER, WP_PADD, WP_COFFEE,
+ WP_PHASER, WP_COMPRESSION_RIFLE, WP_TR116,
+ WP_GRENADE_LAUNCHER, WP_QUANTUM_BURST, WP_DISRUPTOR,
+ WP_MEDKIT, WP_VOYAGER_HYPO, WP_DERMAL_REGEN,
+ WP_TOOLKIT, WP_NEUTRINO_PROBE, 0 };*/
+
+void CG_Weapon_f( void ) {
+ int num;
+ //int newWeapons[16];
+ int i;
+ int bits;
+ int weaponsOnRow;
+ int weaponGot[6];
+ int onRow;
+ int onCol;
+ int rowsUsed;
+ int currentWeaponCol;
+
+ if ( !cg.snap ) {
+ return;
+ }
+ if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
+ return;
+ }
+
+ num = atoi( CG_Argv( 1 ) );
+ bits = cg.snap->ps.stats[ STAT_WEAPONS ];
+
+ //TiM - 0 = Null hand weapon now
+ //if ( num < 1 || num > 15 ) {
+ if ( num < 0 || num > 15 ) {
+ return;
+ }
+
+ cg.weaponSelectTime = cg.time;
+
+ //Hacky Override: 0 = Null hand no matter what.
+ if (num == 0 ) {
+ if ( bits & ( 1 << WP_NULL_HAND ) ) {
+ cg.weaponSelect = WP_NULL_HAND;
+ }
+ return;
+ }
+
+ //TiM : The code below went into an infinite loop if a high number was
+ //set as an arg to this command.
+ //Lemme just insert a check to make sure the code NEVER accepts args higher
+ //than the size of our weapons array. I'll put it underneath the weaponSelectTime
+ //statement, so the user will still see a response to their input.
+ else if ( num > 5 ) {
+ return;
+ }
+
+ /* RPG-X | Phenix | 02/02/2006
+ *
+ * Code to group weapons together by keyboard */
+
+ //Init weaponGot values
+ /*for (i = 0; i < 6; i++)
+ weaponGot[i] = -1;*/
+ memset( weaponGot, -1, sizeof( weaponGot ) );
+
+ onCol = 0;
+ weaponsOnRow = 0;
+ rowsUsed = 0;
+ currentWeaponCol = -1;
+
+ //Loop though every weapon in weaponRows (starting on row 2 - WHY TIM WHY!)
+ //TiM: ... because :)
+ for ( i = 0, onRow = 1; i < 15; i++ )
+ {
+ if (onCol == 3)
+ {
+ onCol = 0;
+ weaponsOnRow = 0;
+ onRow++;
+
+ if (onRow > 5) //Something has gone wrong!
+ break;
+ }
+
+ if ( weaponRows[onRow][onCol] > 0)
+ { //Double check this is a weapon
+ if (( bits & ( 1 << weaponRows[onRow][onCol] ) ) && (weaponsOnRow == 0))
+ { //If we have this weapon And it is the first weapon on this row we have
+ weaponGot[rowsUsed] = onRow;
+ weaponsOnRow++;
+ rowsUsed++;
+ }
+
+ if ((cg.predictedPlayerState.weapon == weaponRows[onRow][onCol]) && (rowsUsed == num))
+ { //If this is the selected weapon record what column it is on
+ currentWeaponCol = onCol;
+ }
+ }
+
+ onCol++;
+ }
+
+ //If they selected a row that doesn't exist
+ if (weaponGot[num - 1] == -1)
+ return; //(dont need to worry about num being zero because of tims hack ^^)
+
+ do
+ { //Loop though this row until we come accross a weapon which the player has got and is not "null" (0)
+ currentWeaponCol++;
+
+ if (currentWeaponCol == 3)
+ {
+ currentWeaponCol = 0;
+ }
+ } while ((weaponRows[ weaponGot[num - 1] ][currentWeaponCol] == 0) || !( bits & ( 1 << weaponRows[weaponGot[num - 1]][currentWeaponCol] )));
+
+ cg.weaponSelect = weaponRows[weaponGot[num - 1]][currentWeaponCol];
+
+ //TiM - based on the number we pressed, and whichever
+ //weapons we have in sequential order, select the one that corresponds.
+
+ //Start at number 2, skipping null hand. He owns us all
+ /*for ( i = WP_TRICORDER, weaponCount = 0; i < MAX_WEAPONS; i++ ) {
+ //if we have that weapon
+ if ( bits & ( 1 << i ) ) {
+ weaponCount++;
+
+ if ( weaponCount == num ) {
+ cg.weaponSelect = i;
+ return;
+ }
+ }
+ }*/
+}
+
+/*
+===================
+CG_OutOfAmmoChange
+
+The current weapon has just run out of ammo
+===================
+*/
+void CG_OutOfAmmoChange( qboolean altfire ) {
+ int i;
+
+ cg.weaponSelectTime = cg.time;
+
+ for ( i = 15 ; i > 0 ; i-- )
+ {
+ if (altfire)
+ {
+ if ( CG_WeaponAltSelectable( i ) )
+ {
+ cg.weaponSelect = i;
+ break;
+ }
+ }
+ else
+ {
+ if ( CG_WeaponSelectable( i ) )
+ {
+ cg.weaponSelect = i;
+ break;
+ }
+ }
+ }
+}
+
+
+
+/*
+===================================================================================================
+
+WEAPON EVENTS
+
+===================================================================================================
+*/
+
+/*
+================
+CG_FireWeapon
+
+Caused by an EV_FIRE_WEAPON event
+================
+*/
+int tris_state = 0;
+void CG_FireWeapon( centity_t *cent, qboolean alt_fire ) {
+ entityState_t *ent;
+ weaponInfo_t *weap;
+ int rpg_effectsgun;
+ int rpg_tripmines;
+ const char *info;
+ //const char *info2;
+
+ ent = ¢->currentState;
+ if ( ent->weapon == WP_NONE || ent->weapon == WP_NULL_HAND ) {
+ return;
+ }
+ if ( ent->weapon >= WP_NUM_WEAPONS ) {
+ CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
+ return;
+ }
+ weap = &cg_weapons[ ent->weapon ];
+
+ // mark the entity as muzzle flashing, so when it is added it will
+ // append the flash to the weapon model
+ cent->muzzleFlashTime = cg.time;
+
+ // lightning gun only does this this on initial press
+ if ( ent->weapon == WP_PHASER /*||
+ ent->weapon == WP_DERMAL_REGEN*/
+ || ent->weapon == WP_TOOLKIT
+ || ent->weapon == WP_MEDKIT
+ || (!(cent->currentState.eFlags & EF_ALT_FIRING) && ent->weapon == WP_DISRUPTOR )
+ || (cent->currentState.eFlags & EF_ALT_FIRING && ent->weapon == WP_COMPRESSION_RIFLE )
+ )
+ {
+ if ( cent->pe.lightningFiring ) {
+ return;
+ }
+ }
+
+ // play quad sound if needed
+/* if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) {
+ trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound );
+ }*/
+
+ // play a sound
+ info = CG_ConfigString( CS_SERVERINFO );
+ rpg_tripmines = atoi( Info_ValueForKey( info, "rpg_invisibletripmines" ) );
+ rpg_effectsgun = atoi( Info_ValueForKey( info, "rpg_effectsgun" ) );
+ if (alt_fire)
+ {
+ //RPG-X: RedTechie - Wrong place for show tris
+ /*if( ent->weapon == WP_TR116 )
+ {
+ if(tris_state == 1)
+ tris_state = 0;
+ else
+ tris_state = 1;
+
+ trap_Cvar_Set("r_showtris", va("%i",tris_state));
+ }*/
+ if ( weap->altFlashSnd )
+ {
+ //TiM : Hark, I smell hackery again
+ //admin alt hypos no fire coz it grinds my teeth
+ if ( cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cgs.clientinfo[cent->currentState.clientNum].pClass == PC_ADMIN*/
+ &&
+ cent->currentState.weapon == WP_VOYAGER_HYPO ) {
+ return;
+ }
+
+ if(ent->weapon == WP_GRENADE_LAUNCHER){
+ if(rpg_tripmines != 1){
+ trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd );
+ }
+ }else{
+ trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd );
+ }
+ }
+ }
+ else
+ {
+ if ( weap->flashSound )
+ {
+ if(ent->weapon == WP_GRENADE_LAUNCHER){
+ if((rpg_effectsgun == 1) || (rpg_tripmines == 1)){
+ return;
+ }else{
+ trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound );
+ }
+ }else{
+ trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound );
+ }
+ }
+ }
+}
+
+/*
+================
+CG_FireSeeker
+
+Caused by an EV_FIRE_WEAPON event
+================
+*/
+void CG_FireSeeker( centity_t *cent )
+{
+ entityState_t *ent;
+ weaponInfo_t *weap;
+
+ ent = ¢->currentState;
+ weap = &cg_weapons[ WP_COFFEE ];
+
+ trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound );
+}
+
+/*
+=================
+CG_MissileHitWall
+
+Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
+=================
+*/
+void CG_MissileHitWall( centity_t *cent, int weapon, vec3_t origin, vec3_t dir )
+{
+ qhandle_t mod;
+ qhandle_t mark;
+ qhandle_t shader;
+ sfxHandle_t sfx;
+ float radius;
+ float light;
+ vec3_t lightColor;
+ localEntity_t *le;
+ qboolean isSprite;
+ int duration;
+ qboolean alphaFade;
+// weaponInfo_t *weaponInfo = &cg_weapons[weapon];
+
+ mark = 0;
+ radius = 32;
+ sfx = 0;
+ mod = 0;
+ shader = 0;
+ light = 0;
+ lightColor[0] = 1;
+ lightColor[1] = 1;
+ lightColor[2] = 0;
+
+ // set defaults
+ isSprite = qfalse;
+ duration = 600;
+
+ switch ( weapon ) {
+ default:
+ case WP_PHASER:
+ // no explosion at LG impact, it is added with the beam
+ mark = cgs.media.holeMarkShader;
+ radius = 12;
+ break;
+ case WP_DERMAL_REGEN:
+ // no explosion at LG impact, it is added with the beam
+ mark = cgs.media.holeMarkShader;
+ radius = 12;
+ break;
+ case WP_GRENADE_LAUNCHER:
+ FX_GrenadeExplode( origin, dir );
+ return;
+ break;
+ case WP_DISRUPTOR:
+ FX_StasisWeaponHitWall( origin, dir, 2 ); //cent->currentState.time2
+ return;
+ break;
+ case WP_NULL_HAND:
+ /*mod = cgs.media.ringFlashModel;
+ shader = cgs.media.imodExplosionShader;
+ mark = cgs.media.energyMarkShader;
+ radius = 24;*/
+ break;
+ case WP_COMPRESSION_RIFLE:
+ //mod = cgs.media.ringFlashModel;
+ //shader = cgs.media.imodExplosionShader;
+ //mark = cgs.media.energyMarkShader;
+ //radius = 24;
+ FX_CompressionExplosion( cent->lerpOrigin, origin, dir, qfalse );
+ return;
+ break;
+ case WP_TR116:
+ //FX_TetrionAltHitWall( origin, dir );
+ return;
+ break;
+/* case WP_COFFEE:
+ if (cent->currentState.eFlags & EF_ALT_FIRING)
+ {
+ FX_ScavengerAltExplode( origin, dir );
+ }
+ else
+ {
+ FX_ScavengerWeaponHitWall( origin, dir, qfalse );
+ }
+ return;
+ break;*/
+/* case WP_MEDKIT:
+ if ( !( cent->currentState.eFlags & EF_ALT_FIRING ))
+ {
+ FX_BorgWeaponHitWall( origin, dir );
+ }
+ return;
+ break;*/
+
+ case WP_QUANTUM_BURST:
+ if ( cent->currentState.eFlags & EF_ALT_FIRING )
+ {
+ FX_QuantumAltHitWall( origin, dir );
+ }
+ else
+ {
+ FX_QuantumHitWall( origin, dir );
+ }
+ return;
+ break;
+ }
+
+ if ( sfx ) {
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
+ }
+
+ //
+ // create the explosion
+ //
+ if ( mod ) {
+ le = CG_MakeExplosion( origin, dir,
+ mod, shader,
+ duration, 1, isSprite );
+ le->light = light;
+ VectorCopy( lightColor, le->lightColor );
+ }
+
+ //
+ // impact mark
+ //
+ alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color
+ CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse );
+}
+
+
+/*
+=================
+CG_MissileHitPlayer
+=================
+*/
+void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir)
+{
+ if (cent)
+ { // Showing blood is a no-no.
+
+// CG_Bleed( origin, cent->currentState.otherEntityNum );
+ }
+
+ CG_MissileHitWall( cent, weapon, origin, dir );
+}
+
+
+/*
+=================
+CG_BounceEffect
+
+Caused by an EV_BOUNCE | EV_BOUNCE_HALF event
+=================
+*/
+
+// big fixme. none of these sounds should be registered at runtime
+void CG_BounceEffect( centity_t *cent, int weapon, vec3_t origin, vec3_t normal )
+{
+ int rpg_tripmines;
+ const char *info;
+
+ switch( weapon )
+ {
+ case WP_GRENADE_LAUNCHER:
+ info = CG_ConfigString( CS_SERVERINFO );
+ rpg_tripmines = atoi( Info_ValueForKey( info, "rpg_invisibletripmines" ) );
+ if(rpg_tripmines != 1){
+ if ( rand() & 1 ) {
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav") );
+ } else {
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav") );
+ }
+ }
+ break;
+
+ case WP_TR116:
+ //trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound ( va(SOUND_DIR "tetrion/ricochet%d.wav", irandom(1, 3)) ) );
+ //FX_TetrionRicochet( origin, normal );
+ break;
+
+ default:
+ if ( rand() & 1 ) {
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav") );
+ } else {
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav") );
+ }
+ break;
+ }
+}
+
+
+
+
+/*
+============================================================================
+
+BULLETS
+
+============================================================================
+*/
+
+
+
+/*
+======================
+CG_CalcMuzzlePoint
+======================
+*/
+
+extern qboolean PM_PlayerCrouching ( int legsAnim );
+
+qboolean CG_CalcMuzzlePoint( centity_t *cent, vec3_t muzzle, qboolean isDecoy ) {
+ vec3_t forward;
+ //centity_t *cent;
+ int anim;
+
+ /*if ( entityNum == cg.snap->ps.clientNum && !isDecoy ) {
+ VectorCopy( cg.snap->ps.origin, muzzle );
+ muzzle[2] += cg.snap->ps.viewheight;
+ AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL );
+ VectorMA( muzzle, 14, forward, muzzle );
+ return qtrue;
+ }*/
+
+ //cent = &cg_entities[entityNum];
+ if ( !cent->currentValid ) {
+ return qfalse;
+ }
+
+ //if ( !isDecoy )
+ VectorCopy( cent->currentState.pos.trBase, muzzle );
+ //else
+ // VectorCopy( cent->currentState.origin, muzzle );
+
+ AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL );
+ anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
+ if ( PM_PlayerCrouching( cent->currentState.legsAnim ) ) {
+ muzzle[2] += CROUCH_VIEWHEIGHT;
+ } else {
+ muzzle[2] += DEFAULT_VIEWHEIGHT;
+ }
+
+ VectorMA( muzzle, 14, forward, muzzle );
+
+ return qtrue;
+
+}
+
+
+/*
+================
+CG_SurfaceExplosion
+
+Adds an explosion to a surface
+================
+*/
+
+#define NUM_SPARKS 12
+#define NUM_PUFFS 1
+#define NUM_EXPLOSIONS 4
+
+void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke )
+{
+ localEntity_t *le;
+ vec3_t direction, new_org;
+ vec3_t sprayvel, velocity = { 0, 0, 0 };
+ vec3_t temp_org, temp_vel;
+ float scale, dscale;
+ int i, numSparks;
+
+ //Sparks
+
+ numSparks = 32 + (random() * 16.0f);
+
+ //VectorSet( normal, 0, 0, 1 );
+
+ for ( i = 0; i < numSparks; i++ )
+ {
+ scale = 0.25f + (random() * 2.0f);
+ dscale = -scale*0.5;
+
+ FXE_Spray( normal, 500, 150, 1.0f, sprayvel);
+
+ FX_AddTrail( origin,
+ sprayvel,
+ qtrue,
+ 32.0f,
+ -64.0f,
+ scale,
+ -scale,
+ 1.0f,
+ 0.0f,
+ 0.25f,
+ 4000.0f,
+ cgs.media.sparkShader);
+ }
+
+ //Smoke
+
+ //Move this out a little from the impact surface
+ VectorMA( origin, 4, normal, new_org );
+ VectorSet( velocity, 0.0f, 0.0f, 16.0f );
+
+ for ( i = 0; i < 4; i++ )
+ {
+ VectorSet( temp_org, new_org[0] + (crandom() * 16.0f), new_org[1] + (crandom() * 16.0f), new_org[2] + (random() * 4.0f) );
+ VectorSet( temp_vel, velocity[0] + (crandom() * 8.0f), velocity[1] + (crandom() * 8.0f), velocity[2] + (crandom() * 8.0f) );
+
+ FX_AddSprite( temp_org,
+ temp_vel,
+ qfalse,
+ radius /**96.0f*/ + (random() * 12.0f),
+ 16.0f,
+ 1.0f,
+ 0.0f,
+ 20.0f + (crandom() * 90.0f),
+ 0.5f,
+ 2000.0f,
+ cgs.media.smokeShader);
+ }
+
+ //Core of the explosion
+
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, direction );
+ VectorNormalize( direction );
+
+ //Tag the last one with a light
+ le = CG_MakeExplosion2( origin, direction, cgs.media.explosionModel, 5, cgs.media.surfaceExplosionShader,
+ 500, qfalse, radius * 0.02f + (random() * 0.3f), LEF_NONE);
+ le->light = 150;
+ VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f );
+
+ for ( i = 0; i < NUM_EXPLOSIONS-1; i ++)
+ {
+ VectorSet( new_org, (origin[0] + (32 + (crandom() * 8))*crandom()), (origin[1] + (32 + (crandom() * 8))*crandom()), (origin[2] + (32 + (crandom() * 8))*crandom()) );
+ le = CG_MakeExplosion2( new_org, direction, cgs.media.explosionModel, 5, cgs.media.surfaceExplosionShader,
+ 300 + (rand() & 99), qfalse, radius * 0.05f + (crandom() *0.3f), LEF_NONE);
+ }
+
+ //Shake the camera
+ CG_ExplosionEffects( origin, shake_speed, 350 );
+
+}
+
+void CG_PlayShooterSound(centity_t *cent) {
+ weaponInfo_t *weap;
+
+ weap = &cg_weapons[cent->currentState.eventParm];
+
+ switch(cent->currentState.eventParm) {
+ case WP_COMPRESSION_RIFLE:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ trap_S_StartSound(cent->currentState.origin, cent->currentState.number, CHAN_VOICE, weap->flashSound);
+ break;
+ case WP_DISRUPTOR:
+ trap_S_StartSound(cent->currentState.origin, cent->currentState.number, CHAN_VOICE, weap->altFlashSnd);
+ break;
+ }
+}
diff --git a/cgame/cgame.def b/cgame/cgame.def
new file mode 100644
index 0000000..2ee748e
--- /dev/null
+++ b/cgame/cgame.def
@@ -0,0 +1,3 @@
+EXPORTS
+ vmMain
+ dllEntry
diff --git a/cgame/cgame.dsp b/cgame/cgame.dsp
new file mode 100644
index 0000000..4799da7
--- /dev/null
+++ b/cgame/cgame.dsp
@@ -0,0 +1,337 @@
+# Microsoft Developer Studio Project File - Name="cgame" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=cgame - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "cgame.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "cgame.mak" CFG="cgame - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "cgame - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "cgame - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""$/StarTrek/Code-DM/cgame", VFJBAAAA"
+# PROP Scc_LocalPath "."
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "cgame - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386
+# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /out:"../Release/cgamex86.dll"
+# SUBTRACT LINK32 /incremental:yes /debug
+
+!ELSEIF "$(CFG)" == "cgame - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /G5 /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"../debug/cgamex86.dll"
+# SUBTRACT LINK32 /profile /incremental:no /nodefaultlib
+
+!ENDIF
+
+# Begin Target
+
+# Name "cgame - Win32 Release"
+# Name "cgame - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "c"
+# Begin Source File
+
+SOURCE=..\game\bg_lib.c
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\bg_misc.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\bg_oums.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\bg_pmove.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\bg_slidemove.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_consolecmds.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_draw.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_drawtools.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_effects.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_ents.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_env.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_event.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_info.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_localents.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_main.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_marks.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_players.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_playerstate.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_predict.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_scoreboard.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_screenfx.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_servercmds.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_snapshot.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_syscalls.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_view.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_weapons.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_borg.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_compression.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_dreadnought.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_grenade.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_imod.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_item.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_lib.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_misc.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_phaser.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_quantum.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_scavenger.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_stasis.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_tetrion.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_transporter.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\q_math.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\q_shared.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h"
+# Begin Source File
+
+SOURCE=..\game\bg_local.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\bg_oums.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\bg_public.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_anims.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_local.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_public.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_screenfx.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cg_text.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\cgame.def
+# End Source File
+# Begin Source File
+
+SOURCE=.\fx_local.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\q_shared.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\game\surfaceflags.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\tr_types.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=.\cg_syscalls.asm
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE=.\cgame.bat
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE=.\cgame.q3asm
+# PROP Exclude_From_Build 1
+# End Source File
+# End Target
+# End Project
diff --git a/cgame/cgame.dsw b/cgame/cgame.dsw
new file mode 100644
index 0000000..dc9ecc5
--- /dev/null
+++ b/cgame/cgame.dsw
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "cgame"=".\cgame.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/cgame/cgame.opt b/cgame/cgame.opt
new file mode 100644
index 0000000..2f05cfa
Binary files /dev/null and b/cgame/cgame.opt differ
diff --git a/cgame/cgame.plg b/cgame/cgame.plg
new file mode 100644
index 0000000..cd75540
--- /dev/null
+++ b/cgame/cgame.plg
@@ -0,0 +1,147 @@
+
+
+
+Build Log
+
+--------------------Configuration: cgame - Win32 Debug--------------------
+
+Command Lines
+Creating temporary file "C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP194.tmp" with contents
+[
+/nologo /G5 /MLd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"Release/" /Fp"Release/cgame.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c
+"C:\stvoy\code-dm\game\bg_misc.c"
+"C:\stvoy\code-dm\game\bg_pmove.c"
+"C:\stvoy\code-dm\game\bg_slidemove.c"
+"C:\stvoy\code-dm\cgame\cg_consolecmds.c"
+"C:\stvoy\code-dm\cgame\cg_draw.c"
+"C:\stvoy\code-dm\cgame\cg_drawtools.c"
+"C:\stvoy\code-dm\cgame\cg_effects.c"
+"C:\stvoy\code-dm\cgame\cg_ents.c"
+"C:\stvoy\code-dm\cgame\cg_env.c"
+"C:\stvoy\code-dm\cgame\cg_event.c"
+"C:\stvoy\code-dm\cgame\cg_info.c"
+"C:\stvoy\code-dm\cgame\cg_localents.c"
+"C:\stvoy\code-dm\cgame\cg_main.c"
+"C:\stvoy\code-dm\cgame\cg_marks.c"
+"C:\stvoy\code-dm\cgame\cg_players.c"
+"C:\stvoy\code-dm\cgame\cg_playerstate.c"
+"C:\stvoy\code-dm\cgame\cg_predict.c"
+"C:\stvoy\code-dm\cgame\cg_scoreboard.c"
+"C:\stvoy\code-dm\cgame\cg_screenfx.c"
+"C:\stvoy\code-dm\cgame\cg_servercmds.c"
+"C:\stvoy\code-dm\cgame\cg_snapshot.c"
+"C:\stvoy\code-dm\cgame\cg_syscalls.c"
+"C:\stvoy\code-dm\cgame\cg_view.c"
+"C:\stvoy\code-dm\cgame\cg_weapons.c"
+"C:\stvoy\code-dm\cgame\fx_borg.c"
+"C:\stvoy\code-dm\cgame\fx_compression.c"
+"C:\stvoy\code-dm\cgame\fx_dreadnought.c"
+"C:\stvoy\code-dm\cgame\fx_grenade.c"
+"C:\stvoy\code-dm\cgame\fx_imod.c"
+"C:\stvoy\code-dm\cgame\fx_item.c"
+"C:\stvoy\code-dm\cgame\fx_lib.c"
+"C:\stvoy\code-dm\cgame\fx_misc.c"
+"C:\stvoy\code-dm\cgame\fx_phaser.c"
+"C:\stvoy\code-dm\cgame\fx_quantum.c"
+"C:\stvoy\code-dm\cgame\fx_scavenger.c"
+"C:\stvoy\code-dm\cgame\fx_stasis.c"
+"C:\stvoy\code-dm\cgame\fx_tetrion.c"
+"C:\stvoy\code-dm\cgame\fx_transporter.c"
+]
+Creating command line "cl.exe @C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP194.tmp"
+Creating temporary file "C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP195.tmp" with contents
+[
+/nologo /base:"0x30000000" /subsystem:windows /dll /incremental:yes /pdb:"Release/cgamex86.pdb" /map:"Release/cgamex86.map" /debug /machine:I386 /def:".\cgame.def" /out:"../debug/cgamex86.dll" /implib:"Release/cgamex86.lib"
+".\Release\bg_misc.obj"
+".\Release\bg_oums.obj"
+".\Release\bg_pmove.obj"
+".\Release\bg_slidemove.obj"
+".\Release\cg_consolecmds.obj"
+".\Release\cg_draw.obj"
+".\Release\cg_drawtools.obj"
+".\Release\cg_effects.obj"
+".\Release\cg_ents.obj"
+".\Release\cg_env.obj"
+".\Release\cg_event.obj"
+".\Release\cg_info.obj"
+".\Release\cg_localents.obj"
+".\Release\cg_main.obj"
+".\Release\cg_marks.obj"
+".\Release\cg_players.obj"
+".\Release\cg_playerstate.obj"
+".\Release\cg_predict.obj"
+".\Release\cg_scoreboard.obj"
+".\Release\cg_screenfx.obj"
+".\Release\cg_servercmds.obj"
+".\Release\cg_snapshot.obj"
+".\Release\cg_syscalls.obj"
+".\Release\cg_view.obj"
+".\Release\cg_weapons.obj"
+".\Release\fx_borg.obj"
+".\Release\fx_compression.obj"
+".\Release\fx_dreadnought.obj"
+".\Release\fx_grenade.obj"
+".\Release\fx_imod.obj"
+".\Release\fx_item.obj"
+".\Release\fx_lib.obj"
+".\Release\fx_misc.obj"
+".\Release\fx_phaser.obj"
+".\Release\fx_quantum.obj"
+".\Release\fx_scavenger.obj"
+".\Release\fx_stasis.obj"
+".\Release\fx_tetrion.obj"
+".\Release\fx_transporter.obj"
+".\Release\q_math.obj"
+".\Release\q_shared.obj"
+]
+Creating command line "link.exe @C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP195.tmp"
+Output Window
+Compiling...
+bg_misc.c
+bg_pmove.c
+bg_slidemove.c
+cg_consolecmds.c
+cg_draw.c
+cg_drawtools.c
+cg_effects.c
+cg_ents.c
+cg_env.c
+cg_event.c
+cg_info.c
+cg_localents.c
+cg_main.c
+cg_marks.c
+cg_players.c
+cg_playerstate.c
+cg_predict.c
+cg_scoreboard.c
+cg_screenfx.c
+cg_servercmds.c
+cg_snapshot.c
+cg_syscalls.c
+cg_view.c
+cg_weapons.c
+fx_borg.c
+fx_compression.c
+fx_dreadnought.c
+fx_grenade.c
+fx_imod.c
+fx_item.c
+fx_lib.c
+fx_misc.c
+fx_phaser.c
+fx_quantum.c
+fx_scavenger.c
+fx_stasis.c
+fx_tetrion.c
+fx_transporter.c
+Linking...
+ Creating library Release/cgamex86.lib and object Release/cgamex86.exp
+
+
+
+Results
+cgamex86.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/cgame/cgame.q3asm b/cgame/cgame.q3asm
new file mode 100644
index 0000000..3d09044
--- /dev/null
+++ b/cgame/cgame.q3asm
@@ -0,0 +1,42 @@
+-o "C:\stvoy\code-DM\vm\cgame"
+cg_main
+..\cg_syscalls
+cg_consolecmds
+cg_draw
+cg_drawtools
+cg_effects
+cg_ents
+cg_env
+cg_event
+cg_info
+cg_localents
+cg_marks
+cg_players
+cg_playerstate
+cg_predict
+cg_scoreboard
+cg_screenfx
+cg_servercmds
+cg_snapshot
+cg_view
+cg_weapons
+bg_slidemove
+bg_pmove
+bg_lib
+bg_misc
+q_math
+q_shared
+fx_compression
+fx_imod
+fx_misc
+fx_lib
+fx_phaser
+fx_scavenger
+fx_tetrion
+fx_transporter
+fx_grenade
+fx_quantum
+fx_stasis
+fx_item
+fx_dreadnought
+fx_borg
\ No newline at end of file
diff --git a/cgame/cgame.vcproj b/cgame/cgame.vcproj
new file mode 100644
index 0000000..8ed940d
--- /dev/null
+++ b/cgame/cgame.vcproj
@@ -0,0 +1,1354 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cgame/cgame.vcxproj b/cgame/cgame.vcxproj
new file mode 100644
index 0000000..729ab23
--- /dev/null
+++ b/cgame/cgame.vcxproj
@@ -0,0 +1,531 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {EBB0D9E9-00FC-4DBA-AF4A-4052FE9B17B1}
+ cgame
+
+
+
+ DynamicLibrary
+ false
+
+
+ DynamicLibrary
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ProjectFileVersion>10.0.30319.1
+ ..\Debug\
+ ..\Debug\
+ false
+ ..\Release\
+ ..\Release\
+ false
+ AllRules.ruleset
+
+
+ AllRules.ruleset
+
+
+
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ true
+ true
+ Win32
+ .\Release/cgame.tlb
+
+
+
+
+ MaxSpeed
+ true
+ Speed
+ true
+ lua\include;%(AdditionalIncludeDirectories)
+ WIN32;_DEBUG;_WINDOWS;XTRA;G_LUA;%(PreprocessorDefinitions)
+ MultiThreadedDebug
+ false
+
+
+ .\Release/cgame.pch
+ .\Release/
+ .\Release/
+ .\Release/
+ true
+ Level4
+ true
+ ProgramDatabase
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ 0x0409
+
+
+ odbc32.lib;odbccp32.lib;lua52.lib;%(AdditionalDependencies)
+ C:\Program Files\Raven\Star Trek Voyager Elite Force\RPG-X2\cgamex86.dll
+ true
+ .\cgame.def
+ true
+ .\Release/cgamex86.pdb
+ true
+ .\Release/cgamex86.map
+ Windows
+ UseLinkTimeCodeGeneration
+ 0x30000000
+ false
+
+
+ .\Release/cgamex86.lib
+ MachineX86
+
+
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+ true
+ true
+ Win32
+ .\Release/cgame.tlb
+
+
+
+
+ /analyze %(AdditionalOptions)
+ MaxSpeed
+ OnlyExplicitInline
+ WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+ 4Bytes
+ true
+
+
+ .\Release/cgame.pch
+ .\Release/
+ .\Release/
+ .\Release/
+
+
+ Level4
+ true
+ CompileAsC
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+ 0x0409
+
+
+ odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ ../Release/cgamex86.dll
+ true
+ .\cgame.def
+ .\Release/cgamex86.pdb
+ true
+ .\Release/cgamex86.map
+ Windows
+ 0x30000000
+ false
+
+
+ .\Release/cgamex86.lib
+ MachineX86
+
+
+
+
+ true
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+ Disabled
+ WIN32;_DEBUG;_WINDOWS
+ true
+ MaxSpeed
+ WIN32;NDEBUG;_WINDOWS
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Document
+ true
+ true
+
+
+ Document
+ true
+ true
+
+
+ Document
+ true
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cgame/cgame.vcxproj.filters b/cgame/cgame.vcxproj.filters
new file mode 100644
index 0000000..a97e595
--- /dev/null
+++ b/cgame/cgame.vcxproj.filters
@@ -0,0 +1,186 @@
+
+
+
+
+ {90a720d9-41f5-4abf-91d4-5c109cd2d702}
+ c
+
+
+ {76164a83-7a3a-4473-b6db-f151d30a82d7}
+ h
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Header Files
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cgame/cgame.vcxproj.user b/cgame/cgame.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/cgame/cgame.vcxproj.user
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/cgame/fx_borg.c b/cgame/fx_borg.c
new file mode 100644
index 0000000..6329b7b
--- /dev/null
+++ b/cgame/fx_borg.c
@@ -0,0 +1,214 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+
+#define BORG_SPIN 0.6f
+
+//------------------------------------------------------------------------------
+/*void FX_BorgProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+ float len;
+ vec3_t dir, end;
+
+ FX_AddSprite( cent->lerpOrigin, NULL, qfalse,
+ 8.0f + ( random() * 24.0f ), 0.0f,
+ 1.0f, 1.0f,
+ random() * 360, 0.0f,
+ 1,
+ cgs.media.borgFlareShader);
+
+ // Energy glow
+ FX_AddSprite( cent->lerpOrigin, NULL, qfalse,
+ 18.0f + ( random() * 24.0f ), 0.0f,
+ 0.2f, 0.1f,
+ random() * 360, 0.0f,
+ 1,
+ cgs.media.borgFlareShader);
+
+ VectorSet( dir, crandom(), crandom(), crandom() );
+ VectorNormalize( dir );
+ len = random() * 12.0f + 18.0f;
+ VectorMA( cent->lerpOrigin, len, dir, end );
+ FX_AddElectricity( cent->lerpOrigin, end, 0.2f, 0.6f, 0.0f, 0.3f, 0.0f, 5, cgs.media.borgLightningShaders[2], 1.0f );
+}*/
+
+/*
+-------------------------
+FX_BorgWeaponHitWall
+-------------------------
+*/ /*
+void FX_BorgWeaponHitWall( vec3_t origin, vec3_t normal )
+{
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_MEDKIT];
+
+ // Expanding shock ring
+ FX_AddQuad( origin, normal,
+ 0.5f, 6.4f,
+ 0.8, 0.0,
+ random() * 360.0f,
+ 200,
+ cgs.media.borgLightningShaders[0] );
+
+ // Impact core
+ FX_AddQuad( origin, normal,
+ 16.0f + ( random() * 8.0f ), 3.2f,
+ 0.6f, 0.0f,
+ cg.time * BORG_SPIN,
+ 100,
+ cgs.media.borgLightningShaders[0] );
+
+ //Sound
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound);
+
+ CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, random() + 5.5f, qfalse );
+} */
+
+/*
+-------------------------
+FX_BorgTaser
+-------------------------
+*/
+/*
+void FX_BorgTaser( vec3_t end, vec3_t start )
+{
+ float len;
+ vec3_t dis;
+
+ FX_AddSprite( end, NULL, qfalse, 9.0f, 0.0f, 1.0f, 0.0f, 30.0f, 0.0f, 250, cgs.media.borgLightningShaders[0] );
+ FX_AddSprite( end, NULL, qfalse, 18.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 150, cgs.media.borgLightningShaders[0] );
+ FX_AddSprite( start, NULL, qfalse, 12.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 100, cgs.media.borgLightningShaders[0] );
+ FX_AddSprite( start, NULL, qfalse, 6.0f, 0.0f, 1.0f, 0.0f, 60.0f, 0.0f, 150, cgs.media.borgLightningShaders[0] );
+
+ VectorSubtract( start, end, dis );
+ len = VectorNormalize( dis );
+
+ if ( len < 96 )
+ len = 0.6f;
+ else if ( len < 256 )
+ len = 0.4f;
+ else if ( len > 512 )
+ len = 0.1f;
+ else
+ len = 0.2f;
+
+ FX_AddLine( start, end, 1.0f, 5.0f, 0.0f, 1.0f, 0.0f, 150, cgs.media.borgLightningShaders[1] );
+ FX_AddElectricity( start, end, 0.2f, 3.0f, 0.0f, 1.0f, 0.0f, 150, cgs.media.borgLightningShaders[2], len );
+ FX_AddElectricity( start, end, 0.5f, 2.0f, 0.0f, 1.0f, 0.0f, 150, cgs.media.borgLightningShaders[3], len );
+}*/
+
+//------------------------------------------------
+
+// unused!
+/*void FX_BorgEyeBeam( vec3_t start, vec3_t end, vec3_t normal, qboolean large )
+{
+ float width, alpha;
+ vec3_t rgb = {1.0f,0.0f,0.0f};
+
+ width = 0.5f + ( crandom() * 0.1 );
+ if ( large )
+ width *= 3.5;
+
+ alpha = 0.4f + ( random() * 0.25 );
+
+ FX_AddLine2( start, end, 1.0f,
+ width, 0.0f, width, 0.0f,
+ alpha, alpha,
+ rgb, rgb,
+ 1.0f,
+ cgs.media.whiteLaserShader );
+
+ FX_AddSprite( start, NULL, qfalse,
+ 1.0f + (random() * 2.0f), 0.0f,
+ 0.6f, 0.6f,
+ 0.0f, 0.0f, 1.0f,
+ cgs.media.borgEyeFlareShader );
+
+ FX_AddQuad( end, normal,
+ 2.0f + (crandom() * 1.0f), 0.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ cgs.media.borgEyeFlareShader );
+}*/
+
+#define BORG_PARTICLE_RADIUS 32
+//------------------------------------------------------------------------------------
+void FX_BorgTeleportParticles( vec3_t origin, vec3_t dir )
+{
+ int i;
+ vec3_t neworg, vel;
+
+ for ( i = 0; i < 26; i++ )
+ {
+ VectorSet( neworg,
+ origin[0] + ( crandom() * ( BORG_PARTICLE_RADIUS * 0.5 ) ),
+ origin[1] + ( crandom() * ( BORG_PARTICLE_RADIUS * 0.5 ) ),
+ origin[2] + ( crandom() * 4.0f ) );
+ VectorScale( dir, 32 + ( random() * 96 ), vel );
+
+ FX_AddSprite( neworg, vel, qfalse, 1.0f + ( crandom() * 2.0f ), 0.0f, 1.0f, 0.0f, random() * 360, 0.0f, 1700, cgs.media.borgFlareShader );
+ }
+}
+
+//-------------------------------------
+// unused
+/*
+void FX_BorgTeleport( vec3_t origin )
+{
+ vec3_t org, org2, angles, dir;
+ localEntity_t *le;
+
+ VectorSet( angles, 0, 0, 1 );
+ VectorSet( org, origin[0], origin[1], origin[2] - 32 );
+ FX_BorgTeleportParticles( origin, angles );
+
+ VectorSet( angles, 0, 0, -1 );
+ VectorSet( org2, origin[0], origin[1], origin[2] + 32 );
+ FX_BorgTeleportParticles( origin, angles );
+
+ VectorSubtract( org2, org, dir );
+ VectorNormalize( dir );
+
+ le = FX_AddCylinder( org, dir, 96.0f, 0.0f, 1.0f, 48.0f, 1.0f, 48.0f, 1.0f, 0.0f, 1500, cgs.media.borgFlareShader, 0.5 );
+ le->refEntity.data.cylinder.wrap = qtrue;
+ le->refEntity.data.cylinder.stscale = 24;
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.borgBeamInSound );
+}*/
+
+//-------------------------------------
+//unused
+/*
+void FX_BorgTeleportTrails( vec3_t origin )
+{
+ int i;
+ float scale;
+ vec3_t org, ang, angvect;
+
+ for ( i = 0; i < 2; i++ )
+ {
+ // position on the sphere
+ if ( i == 0 )
+ {
+ ang[ROLL] = 0;
+ ang[PITCH] = sin( cg.time * 0.002f ) * 360;
+ ang[YAW] = cg.time * 0.04f;
+ }
+ else
+ {
+ ang[ROLL] = 0;
+ ang[PITCH] = sin( cg.time * 0.002f + 3.14159f ) * 360;
+ ang[YAW] = cg.time * 0.04f + 180.0f;
+ }
+
+ AngleVectors( ang, angvect, NULL, NULL);
+
+ // Set the particle position
+ org[0] = 12 * angvect[0] + origin[0];
+ org[1] = 12 * angvect[1] + origin[1];
+ org[2] = 32 * angvect[2] + origin[2];
+
+ scale = random() * 4.0f + 4.0f;
+
+ FX_AddSprite( org, NULL, qtrue, scale, -scale, 1.0f, 1.0f, 0.0f, 0.0f, 200.0f + random() * 200.0f, cgs.media.borgFlareShader );
+ }
+}*/
diff --git a/cgame/fx_compression.c b/cgame/fx_compression.c
new file mode 100644
index 0000000..bc346b7
--- /dev/null
+++ b/cgame/fx_compression.c
@@ -0,0 +1,434 @@
+//Compression rifle weapon effects
+
+#include "cg_local.h"
+#include "fx_local.h"
+
+qboolean AltCompressionAftereffect(localEntity_t *le)
+{
+ localEntity_t *cyl = NULL;
+ qhandle_t shader = cgs.media.compressionAltBlastShader;
+ float percentLife = 1.0 - (le->endTime - cg.time)*le->lifeRate;
+ float alpha = 0.6 - (0.6*percentLife);
+ float length = 20;
+ vec3_t vec2, dir2;
+
+ cyl = FX_AddCylinder( le->refEntity.origin,
+ le->data.spawner.dir,
+ length,// height,
+ 0,// dheight,
+ 10,//10+(30*(1-percentLife)),// scale,
+ 210,// dscale,
+ 10+(30*percentLife),// scale2,
+ 210,// dscale2,
+ alpha,// startalpha,
+ 0.0,// endalpha,
+ 500,// killTime,
+ shader,
+ 15);// bias );
+ cyl->leFlags |= LEF_ONE_FRAME;
+
+ VectorMA(le->refEntity.origin, length*2.0, le->data.spawner.dir, vec2);
+ VectorScale(le->data.spawner.dir, -1.0, dir2);
+ cyl = FX_AddCylinder( vec2,
+ dir2,
+ length,// height,
+ 0,// dheight,
+ 10,//10+(30*(1-percentLife)),// scale,
+ 210,// dscale,
+ 10+(30*percentLife),// scale2,
+ 210,// dscale2,
+ alpha,// startalpha,
+ 0.0,// endalpha,
+ 500,// killTime,
+ shader,
+ 15);// bias );
+ cyl->leFlags |= LEF_ONE_FRAME;
+
+ return qtrue;
+}
+
+/*
+-------------------------
+FX_CompressionShot
+-------------------------
+*/
+#define MAXRANGE_CRIFLE 8192
+void FX_CompressionShot( vec3_t start, vec3_t dir )
+{
+ localEntity_t *le;
+ vec3_t end;
+ trace_t trace;
+ qboolean render_impact = qtrue;
+ centity_t *traceEnt = NULL;
+ int clientNum = -1;
+
+ VectorMA(start, MAXRANGE_CRIFLE, dir, end);
+ CG_Trace( &trace, start, NULL, NULL, end, 0, MASK_SHOT );
+
+ // draw the beam
+ le = FX_AddLine(start, trace.endpos, 1.0, 2.0, 0.0, 1.0, 1.0, 100.0, cgs.media.prifleBolt);
+ le->leFlags |= LEF_ONE_FRAME;
+
+ // draw an impact at the endpoint of the trace
+ // If the beam hits a skybox, etc. it would look foolish to add in an explosion
+ if ( trace.surfaceFlags & SURF_NOIMPACT )
+ {
+ render_impact = qfalse;
+ }
+ if ( render_impact )
+ {
+ traceEnt = &cg_entities[trace.entityNum];
+ clientNum = traceEnt->currentState.clientNum;
+ if ( (trace.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) )
+ {
+ FX_CompressionHit(trace.endpos);
+ }
+ else
+ {
+ FX_CompressionExplosion(start, trace.endpos, trace.plane.normal, qfalse);
+ }
+ }
+}
+/*
+-------------------------
+FX_CompressionShot
+-------------------------
+*/
+void FX_CompressionAltShot( vec3_t start, vec3_t dir )
+{
+ vec3_t end, vel = {0,0,0};
+ trace_t trace;
+ qboolean render_impact = qtrue;
+ centity_t *traceEnt = NULL;
+ int clientNum = -1;
+
+ VectorMA(start, MAXRANGE_CRIFLE, dir, end);
+ CG_Trace( &trace, start, NULL, NULL, end, cg_entities[cg.predictedPlayerState.clientNum].currentState.number, MASK_SHOT );
+
+ // draw the beam
+ FX_AddLine( start, trace.endpos, 1.0f, 3.0f, 0.0f, 1.0f, 0.0f, 350/*125.0f*/, cgs.media.sparkShader );
+ FX_AddLine( start, trace.endpos, 1.0f, 6.0f, 20.0f, 0.6f, 0.0f, 800/*175.0f*/, cgs.media.phaserShader);//compressionAltBeamShader );
+
+ FX_AddSpawner( start, dir, vel, NULL, qfalse, 0,
+ 0, 500, AltCompressionAftereffect, 10 );
+
+ // draw an impact at the endpoint of the trace
+ // If the beam hits a skybox, etc. it would look foolish to add in an explosion
+ if ( trace.surfaceFlags & SURF_NOIMPACT )
+ {
+ render_impact = qfalse;
+ }
+ if ( render_impact )
+ {
+ traceEnt = &cg_entities[trace.entityNum];
+ clientNum = traceEnt->currentState.clientNum;
+ if ( (trace.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) )
+ {
+ FX_CompressionHit(trace.endpos);
+ }
+ else
+ {
+ FX_CompressionExplosion(start, trace.endpos, trace.plane.normal, qtrue);
+ }
+ }
+}
+
+/*
+-------------------------
+FX_CompressionExplosion
+-------------------------
+*/
+
+void FX_CompressionExplosion( vec3_t start, vec3_t origin, vec3_t normal, qboolean altfire )
+{
+ localEntity_t *le;
+ vec3_t dir;
+ vec3_t velocity; //, shot_dir;
+ vec3_t hitpos;
+ float scale, dscale;
+ int i, j, numSparks;
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_COMPRESSION_RIFLE];
+ float distance;
+
+ vec3_t color = {0.7, 0.43, 0.44};
+
+ int size = 2;
+
+ //FX_CompressionHit( origin ); //TiM: let's test if the rifle doesn't make stuff explode when its shot :)
+ //return;
+
+ //Sparks
+ //TiM: Calc spark count off proximity to effect
+ VectorSubtract ( cg.refdef.vieworg, origin, dir );
+ distance = VectorNormalize( dir );
+ distance = 50 * ( 1.0f - (distance / 128) ) ;
+ distance = Com_Clamp( 25, 50, distance );
+
+ numSparks = distance + (random() * 4.0f); //4
+
+ if (altfire)
+ {
+ numSparks *= 1.5f;
+ }
+ for ( i = 0; i < numSparks; i++ )
+ {
+ scale = 10.0f + (random() * 1.0f); //.25
+ dscale = -scale;
+
+ //Randomize the direction
+ for (j = 0; j < 3; j ++ )
+ {
+ //if ( j !=5 )
+ //dir[j] = normal[j] + (0.75 * crandom());
+ //else
+ dir[j] = normal[j] + (-1 * crandom()); //0.75
+ }
+
+ VectorNormalize(dir);
+
+ //set the speed
+ VectorScale( dir, 200 + (50 * crandom()), velocity); //200
+
+ le = FX_AddTrail( origin,
+ velocity,
+ qtrue, //qtrue
+ 12.0f,//4
+ -12.0f,//4
+ scale,
+ -scale,
+ 1.0f,
+ 1.0f,
+ 0.5f,
+ 1000.0f, //1000
+ cgs.media.orangeStarShader);
+
+// FXE_Spray( normal, 200, 50, 0.4f, le);
+ }
+
+ VectorMA( origin, 8, normal, dir );
+ VectorSet(velocity, 0, 0, 8);
+/*
+ FX_AddSprite( dir,
+ velocity,
+ qfalse,
+ (altfire?50.0f:32.0f),
+ 16.0f,
+ 1.0f,
+ 0.0f,
+ random()*45.0f,
+ 0.0f,
+ (altfire?1300.0f:1000.0f),
+ cgs.media.steamShader );
+*/
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, dir );
+ VectorNormalize( dir );
+
+ if (!altfire)
+ {
+ CG_InitLensFlare( origin,
+ 350, 350,
+ color, 1.2, 2.0, 1600, 200,
+ color, 1600, 200, 800, 20, qtrue,
+ 0, 0, qfalse, qtrue,
+ qfalse, 1.0, cg.time, 0, 0, 210);
+
+
+ VectorMA(origin, size, normal, hitpos);
+
+ FX_AddSprite( hitpos, NULL, qfalse, size * size * 15.0f, -150.0f,
+ 1.0f, 0.0f, 360*random(), 0, 400, cgs.media.liteRedParticleShader );
+
+ FX_AddSprite( hitpos, NULL, qfalse, size * size * 25.0f, -150.0f,
+ 1.0f, 0.0f, 0.0f, 0, 400, cgs.media.liteRedParticleStreakShader );
+
+
+ le = CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.electricalExplosionSlowShader,
+ 475, qfalse, 1.2f + ( crandom() * 0.3f), LEF_NONE);
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f );
+
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 12, qfalse );
+
+ //Shake the camera
+ CG_ExplosionEffects( origin, 1, 200 );
+ }
+ else
+ {
+ le = CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.electricalExplosionSlowShader,
+ 500, qfalse, 2.2f + ( crandom() * 0.4f), LEF_NONE);
+ le->light = 200;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f );
+
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 28, qfalse );
+
+ //Shake the camera
+ CG_ExplosionEffects( origin, 2, 240 );
+ }
+
+ // nice explosion sound at the point of impact
+ trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound);
+}
+
+/*
+-------------------------
+FX_CompressionHit
+-------------------------
+*/
+
+void FX_CompressionHit( vec3_t origin )
+{
+ FX_AddSprite( origin,
+ NULL,
+ qfalse,
+ 32.0f,
+ -32.0f,
+ 1.0f,
+ 1.0f,
+ random()*360,
+ 0.0f,
+ 250.0f,
+ cgs.media.prifleImpactShader );
+
+ //FIXME: Play an impact sound with a body
+// trap_S_StartSound (origin, NULL, 0, cgi_S_RegisterSound ("sound/weapons/prifle/fire.wav") );
+}
+
+void FX_PrifleBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty )
+{
+ refEntity_t beam;
+ sfxHandle_t sfx;
+ float size;
+ vec3_t velocity;
+ int sparks;
+ vec3_t rgb = { 1,0.9,0.6}, rgb2={1,0.3,0};
+
+ //vec3_t rgb3 = { 1.0, 1.0, 1.0 };
+
+ sfx = 0;
+
+ // Draw beam first.
+ memset( &beam, 0, sizeof( beam ) );
+
+ VectorCopy( startpos, beam.origin);
+ VectorCopy( endpos, beam.oldorigin );
+ beam.reType = RT_LINE;
+ if (empty)
+ {
+ beam.customShader = cgs.media.phaserEmptyShader;
+ }
+ else
+ {
+ beam.customShader = cgs.media.prifleBeam;
+ }
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff;
+ beam.shaderRGBA[1] = 0xff;
+ beam.shaderRGBA[2] = 0xff;
+ beam.shaderRGBA[3] = 0xff;
+ if (empty)
+ {
+ beam.data.line.width = 1.0f + ( crandom() * 0.6f );
+ }
+ else
+ {
+ beam.data.line.width = 2.5f + ( crandom() * 0.6f );
+ }
+ beam.data.line.stscale = 5.0;
+ trap_R_AddRefEntityToScene( &beam );
+
+ // Now draw the hit graphic
+
+ // no explosion at LG impact, it is added with the beam
+
+ if ( sfx )
+ {
+ Com_Printf("playing %s\n", "phaser sound");
+ trap_S_StartSound( endpos, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
+ }
+
+ //
+ // impact mark
+ //
+ if (impact)
+ {
+ if (!empty)
+ { // normal.
+ CG_ImpactMark( cgs.media.scavMarkShader, endpos, normal, random()*360, 1,1,1,0.2, qfalse,
+ random() + 1, qfalse );
+
+ //VectorCopy( endpos, phaserFlare.worldCoord );
+
+ /*CG_InitLensFlare( endpos,
+ 80,
+ 80,
+ rgb,
+ 1.2,
+ 1.5,
+ 1600,
+ 200,
+ colorTable[CT_BLACK],
+ 1600,
+ 200,
+ 80,
+ 5,
+ qfalse,
+ 5,
+ 40,
+ qfalse,
+ qfalse,
+ qfalse,
+ 1.0,
+ 1.0,
+ 200.0,
+ 200.0,
+ 200.0 );*/
+
+ //CG_InitLensFlare( endpos,
+ // 30, 30,
+ // rgb, 1.2, 2.0, 1600, 200,
+ // colorTable[CT_BLACK], 1600, 200, 410, 15, qfalse,
+ // 0, 0, qfalse, qtrue,
+ // qfalse, 1.0, cg.time, 0, 0, 50);
+
+ //TiM : Add your basic cheesy 'seen-way-too-much-in-movies-these-days' anamorphic lens streak :)
+ //CG_DrawLensFlare( &phaserFlare );
+ //FX_AddSprite( endpos, NULL, qfalse, random() * 1.25 + 5.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 50.0, cgs.media.blueParticleStreakShader ); //1.5f
+
+ //FX_AddQuad2( endpos, normal, random() * 1.25 + 8.0f, 0.0f, 1.0f, 1.0f, rgb3, rgb3, 270, 50.0, cgs.media.blueParticleStreakShader );
+ //eh... looked bad :P
+
+ FX_AddQuad2( endpos, normal, random() * 1.25 + 1.5f, 0.0f, 1.0f, 0.0f, rgb, rgb2, rand() % 360, 500 + random() * 200,
+ cgs.media.sunnyFlareShader );
+ }
+ else
+ { // Wuss hit when empty.
+ FX_AddQuad2( endpos, normal, random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200,
+ cgs.media.sunnyFlareShader );
+ }
+ }
+
+ // "Fun" sparks... Not when empty.
+ if ( spark && !empty)
+ {
+ sparks = rand() & 1 + 1;
+ for(;sparks>0;sparks--)
+ {
+ size = 0.2f + (random() * 0.4);
+ FXE_Spray( normal, 200, 75, 0.8f, velocity);
+ if (rand() & LEF_USE_COLLISION)
+ { // This spark bounces.
+ FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f,
+ size, -size, 1.0f, 0.5f, 0.4f, 500.0f, cgs.media.sparkShader);
+ }
+ else
+ {
+ FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f,
+ size, -size, 1.0f, 0.5f, 0.0, 500.0f, cgs.media.sparkShader);
+ }
+ }
+ }
+}
+
diff --git a/cgame/fx_dreadnought.c b/cgame/fx_dreadnought.c
new file mode 100644
index 0000000..105a19c
--- /dev/null
+++ b/cgame/fx_dreadnought.c
@@ -0,0 +1,310 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+/*
+-------------------------
+FX_DreadnoughtHitWall
+-------------------------
+*/
+/*void FX_DreadnoughtHitWall( vec3_t origin, vec3_t normal, qboolean spark )
+{
+ float scale = 1.0f + ( random() * 1.0f );
+ int num, i;
+ localEntity_t *le = NULL;
+ vec3_t vel;
+// weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN];
+
+// trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound);
+
+ le = FX_AddQuad( origin, normal, 32.0 + random() * 48, 0, 0.5, 0.5, 0, 100, cgs.media.purpleParticleShader );
+ if (le)
+ {
+ le->leFlags |= LEF_ONE_FRAME;
+ }
+ le = FX_AddQuad( origin, normal, 24.0 + random() * 32, 0, 0.6, 0.6, 0, 100, cgs.media.ltblueParticleShader );
+ if (le)
+ {
+ le->leFlags |= LEF_ONE_FRAME;
+ }
+
+ CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse,
+ random() * 4 + 8, qfalse );
+
+ if ( spark )
+ {
+ trap_R_AddLightToScene( origin, 75 + (rand()&31), 1.0, 0.8, 1.0 );
+
+ // Drop some sparks
+ num = (int)(random() * 2) + 2;
+
+ for ( i = 0; i < num; i++ )
+ {
+ scale = 0.6f + random();
+ if ( rand() & 1 )
+ FXE_Spray( normal, 70, 80, 0.9f, vel);
+ else
+ FXE_Spray( normal, 80, 200, 0.5f, vel);
+
+ FX_AddTrail( origin, vel, qfalse, 8.0f + random() * 8, -48.0f,
+ scale, -scale, 1.0f, 0.8f, 0.4f, 600.0f, cgs.media.spark2Shader );
+ }
+ }
+}*/
+
+/*
+-------------------------
+FX_DreadnoughtFire
+-------------------------
+*/
+/*void FX_DreadnoughtFire( vec3_t origin, vec3_t end, vec3_t normal, qboolean spark, qboolean impact )
+{
+// localEntity_t *le = NULL;
+ float scale = 1.0f + ( random() * 1.0f );
+ refEntity_t beam;
+
+ // Draw beams first.
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( origin, beam.origin);
+ VectorCopy( end, beam.oldorigin );
+ beam.reType = RT_LINE;
+ beam.customShader = cgs.media.dnBoltShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff*0.2;
+ beam.shaderRGBA[1] = 0xff*0.2;
+ beam.shaderRGBA[2] = 0xff*0.2;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.line.stscale = 2.0;
+ beam.data.line.width = scale*6;
+ trap_R_AddRefEntityToScene( &beam );
+
+ // Add second core beam
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( origin, beam.origin);
+ VectorCopy( end, beam.oldorigin );
+ beam.reType = RT_LINE;
+ beam.customShader = cgs.media.dnBoltShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff*0.8;
+ beam.shaderRGBA[1] = 0xff*0.8;
+ beam.shaderRGBA[2] = 0xff*0.8;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.line.stscale = 1.0;
+ beam.data.line.width = scale*4.5;
+ trap_R_AddRefEntityToScene( &beam );
+
+ if (spark)
+ {
+ // Add first electrical bolt
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( origin, beam.origin);
+ VectorCopy( end, beam.oldorigin );
+ beam.reType = RT_ELECTRICITY;
+ beam.customShader = cgs.media.dnBoltShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff*0.8;
+ beam.shaderRGBA[1] = 0xff*0.8;
+ beam.shaderRGBA[2] = 0xff*0.8;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.electricity.stscale = 1.0;
+ beam.data.electricity.width = scale*0.5;
+ beam.data.electricity.deviation = 0.2;
+ trap_R_AddRefEntityToScene( &beam );
+ }
+
+ // Add next electrical bolt
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( origin, beam.origin);
+ VectorCopy( end, beam.oldorigin );
+ beam.reType = RT_ELECTRICITY;
+ beam.customShader = cgs.media.dnBoltShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff*0.8;
+ beam.shaderRGBA[1] = 0xff*0.8;
+ beam.shaderRGBA[2] = 0xff*0.8;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.electricity.stscale = 1.0;
+ beam.data.electricity.width = scale*0.75;
+ beam.data.electricity.deviation = 0.12;
+ trap_R_AddRefEntityToScene( &beam );
+
+/*
+
+ le = FX_AddLine( origin, end, 2.0f, scale * 6, 0.0f, 0.2f, 0.2f, 100, cgs.media.dnBoltShader );
+ if (le)
+ {
+ le->leFlags |= LEF_ONE_FRAME;
+ }
+
+ le = FX_AddLine( origin, end, 1.0f, scale * 4.5, 0.0f, 0.8f, 0.8f, 100, cgs.media.dnBoltShader );
+ if (le)
+ {
+ le->leFlags |= LEF_ONE_FRAME;
+ }
+
+ if ( spark )
+ {
+ le = FX_AddElectricity( origin, end, 1.0f, scale * 0.5, 0, 0.8, 0.8, 100, cgs.media.dnBoltShader, 0.2 );
+ if (le)
+ {
+ le->leFlags |= LEF_ONE_FRAME;
+ }
+ }
+ le = FX_AddElectricity( origin, end, 1.0f, scale * 0.75, 0, 0.8, 0.8, 100, cgs.media.dnBoltShader, 0.12 );
+ if (le)
+ {
+ le->leFlags |= LEF_ONE_FRAME;
+ }
+
+ // Add a subtle screen shake
+ CG_ExplosionEffects( origin, 1.0f, 15 );
+
+ if (impact)
+ {
+ FX_DreadnoughtHitWall( end, normal, spark );
+ }
+}*/
+
+/*
+-------------------------
+FX_DreadnoughtProjectileThink
+
+Freaky random lightning burst
+-------------------------
+*/
+/*#define FX_DN_ALT_THINK_TIME 100
+
+void FX_DreadnoughtProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+// refEntity_t beam;
+ float scale;
+
+ scale = flrandom(10.0, 15.0);
+
+ // If this is a new thinking time, draw some starting stuff...
+ if (cent->miscTime < cg.time)
+ {
+ trace_t trace;
+ vec3_t fwd, right, boltdir, boltend, mins={-2,-2,-2}, maxs={2,2,2};
+ float len;
+ localEntity_t *le;
+ int playSound = 1;//(irandom(0,1) == 0)?1:0;
+
+ cent->miscTime = cg.time + FX_DN_ALT_THINK_TIME;
+
+ VectorSubtract(cent->currentState.origin, cent->currentState.origin2, fwd);
+
+ // Throw a sprite from the start to the end over the next
+ VectorScale(fwd, 1000.0*(1.0/FX_DN_ALT_THINK_TIME), boltdir);
+ le = FX_AddSprite(cent->currentState.origin2, boltdir, qfalse, scale*8, -scale*2, 1.0, 1.0, 0, 0, FX_DN_ALT_THINK_TIME, cgs.media.blueParticleShader);
+ le->light = 200;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 0.5f, 0.8f, 1.0f );
+
+ len = VectorNormalize(fwd);
+
+ // Illegal if org and org2 are the same.
+ if (len<=0)
+ return;
+
+ // Draw a bolt from the old position to the new.
+ FX_AddLine( cent->currentState.origin2, cent->currentState.origin, 1.0, scale*4, -scale*4, 0.75, 0.0, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader);
+
+ // ALSO draw an electricity bolt from the old position to the new.
+ FX_AddElectricity( cent->currentState.origin2, cent->currentState.origin, 0.2f, scale, -scale, 1.0, 0.5, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader, 0.5 );
+
+ // And a bright new sprite at the current locale.
+ FX_AddSprite(cent->currentState.origin, NULL, qfalse, scale*2, scale*4, 1.0, 1.0, 0, 0, FX_DN_ALT_THINK_TIME, cgs.media.blueParticleShader);
+
+ // Put a sprite in the old position, fading away.
+ FX_AddSprite(cent->currentState.origin2, NULL, qfalse, scale*5, -scale*5, 1.0, 1.0, 0, 0, FX_DN_ALT_THINK_TIME*2, cgs.media.blueParticleShader);
+
+ // Shoot rays out (roughly) to the sides to connect with walls or whatever...
+ // PerpendicularVector(right, fwd);
+ right[0] = fwd[1];
+ right[1] = -fwd[0];
+ right[2] = -fwd[2];
+
+ // Right vector
+ // The boltdir uses a random offset to the perp vector.
+ boltdir[0] = right[0] + flrandom(-0.25, 0.25);
+ boltdir[1] = right[1] + flrandom(-0.25, 0.25);
+ boltdir[2] = right[2] + flrandom(-1.0, 1.0);
+
+ // Shoot a vector off to the side and trace till we hit a wall.
+ VectorMA(cent->currentState.origin, 256, boltdir, boltend);
+ CG_Trace( &trace, cent->currentState.origin, mins, maxs, boltend, cent->currentState.number, MASK_SOLID );
+
+ if (trace.fraction < 1.0)
+ {
+ VectorCopy(trace.endpos, boltend);
+ FX_AddElectricity( cent->currentState.origin, boltend, 0.2f, scale, -scale, 1.0, 0.5, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader, 0.5 );
+ // Put a sprite at the endpoint that stays.
+ FX_AddQuad(trace.endpos, trace.plane.normal, scale, -scale*0.5, 1.0, 0.5, 0.0, FX_DN_ALT_THINK_TIME*2, cgs.media.blueParticleShader);
+ if (playSound)
+ {
+ if (irandom(0,1))
+ {
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN];
+ trap_S_StartSound(trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->alt_missileSound);
+ playSound = 0;
+ }
+ }
+ }
+
+ // Left vector
+ // The boltdir uses a random offset to the perp vector.
+ boltdir[0] = -right[0] + flrandom(-0.25, 0.25);
+ boltdir[1] = -right[1] + flrandom(-0.25, 0.25);
+ boltdir[2] = -right[2] + flrandom(-1.0, 1.0);
+
+ // Shoot a vector off to the side and trace till we hit a wall.
+ VectorMA(cent->currentState.origin, 256, boltdir, boltend);
+ CG_Trace( &trace, cent->currentState.origin, mins, maxs, boltend, cent->currentState.number, MASK_SOLID );
+
+ if (trace.fraction < 1.0)
+ {
+ VectorCopy(trace.endpos, boltend);
+ FX_AddElectricity( cent->currentState.origin, boltend, 0.2f, scale, -scale, 1.0, 0.5, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader, 0.5 );
+ // Put a sprite at the endpoint that stays.
+ FX_AddQuad(trace.endpos, trace.plane.normal, scale, -scale*0.5, 1.0, 0.5, 0.0, FX_DN_ALT_THINK_TIME*2, cgs.media.blueParticleShader);
+ if (playSound)
+ {
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN];
+ trap_S_StartSound(trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->alt_missileSound);
+ playSound = 0;
+ }
+ }
+ }
+}*/
+
+/*
+-------------------------
+FX_DreadnoughtShotMiss
+
+Alt-fire, miss effect
+-------------------------
+*/
+/*void FX_DreadnoughtShotMiss( vec3_t end, vec3_t dir )
+{
+ vec3_t org;
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN];
+
+ trap_S_StartSound(end, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound);
+
+ // Move me away from the wall a bit so that I don't z-buffer into it
+ VectorMA( end, 0.5, dir, org );
+
+ // Expanding rings
+// FX_AddQuad( org, dir, 1, 24, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader );
+// FX_AddQuad( org, dir, 1, 60, 0.8, 0.2, random() * 360, 300, cgs.media.stasisRingShader );
+ // Impact effect
+ FX_AddQuad( org, dir, 7, 35, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader );
+ FX_AddQuad( org, dir, 5, 25, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader );
+
+ CG_ImpactMark( cgs.media.scavMarkShader, org, dir, random()*360, 1,1,1,0.6, qfalse,
+ 8 + random() * 2, qfalse );
+
+ FX_AddSprite( end, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader );
+}*/
+
+
diff --git a/cgame/fx_grenade.c b/cgame/fx_grenade.c
new file mode 100644
index 0000000..fdf0237
--- /dev/null
+++ b/cgame/fx_grenade.c
@@ -0,0 +1,350 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+
+/*
+-------------------------
+FX_GrenadeThink
+-------------------------
+*/
+
+void FX_GrenadeThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+ FX_AddSprite( cent->lerpOrigin, NULL, qfalse, 8.0f + random() * 32.0f, 0.0f, 0.75f, 0.75f, 0, 0.0f, 1, cgs.media.dkorangeParticleShader );
+ if ( rand() & 1 )
+ FX_AddSprite( cent->lerpOrigin, NULL, qfalse, 16.0f + random() * 32.0f, 0.0f, 0.6f, 0.6f, 0, 0.0f, 1, cgs.media.yellowParticleShader );
+}
+
+/*
+-------------------------
+FX_GrenadeHitWall
+-------------------------
+*/
+
+void FX_GrenadeHitWall( vec3_t origin, vec3_t normal )
+{
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExplodeSound );
+ CG_SurfaceExplosion( origin, normal, 8, 1, qfalse );
+}
+
+/*
+-------------------------
+FX_GrenadeHitPlayer
+-------------------------
+*/
+
+void FX_GrenadeHitPlayer( vec3_t origin, vec3_t normal )
+{
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExplodeSound );
+ CG_SurfaceExplosion( origin, normal, 8, 1, qfalse );
+}
+
+/*
+-------------------------
+FX_GrenadeExplode
+-------------------------
+*/
+
+void FX_GrenadeExplode( vec3_t origin, vec3_t normal )
+{
+ localEntity_t *le;
+ qhandle_t null;
+ vec3_t direction, org, vel;
+ int i;
+
+ VectorSet( direction, 0,0,1 );
+
+ // Add an explosion and tag a light to it
+ le = CG_MakeExplosion2( origin, direction, cgs.media.nukeModel, 5, null, 250, qfalse, 25.0f, LEF_FADE_RGB);
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+
+ VectorSet( le->lightColor, 1.0f, 0.6f, 0.2f );
+
+ // Ground ring
+ FX_AddQuad( origin, normal, 5, 100, 1.0, 0.0, random() * 360, 300, cgs.media.bigShockShader );
+ // Flare
+ VectorMA( origin, 12, direction, org );
+ FX_AddSprite( org, NULL, qfalse, 160.0, -160.0, 1.0, 0.0, 0.0, 0.0, 200, cgs.media.sunnyFlareShader );//, FXF_NON_LINEAR_FADE );
+
+ for (i = 0; i < 12; i++)
+ {
+ float width, length;
+ FXE_Spray( normal, 470, 325, 0.5f, vel);
+ length = 24.0 + random() * 12;
+ width = 0.5 + random() * 2;
+ FX_AddTrail( origin, vel, qtrue, length, -length, width, -width,
+ 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader);
+ }
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExplodeSound );
+
+ // Smoke and impact
+// FX_AddSpawner( origin, normal, NULL, NULL, 100, 25.0f, 2000.0f, (void *) CG_SmokeSpawn, NULL, 1024 );
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse,
+ random() * 16 + 48, qfalse );
+}
+
+
+/*void FX_GrenadeShrapnelExplode( vec3_t origin, vec3_t norm )
+{
+ localEntity_t *le;
+ vec3_t direction, org, vel;
+ int i;
+
+ VectorCopy( norm, direction);
+
+ // Add an explosion and tag a light to it
+ le = CG_MakeExplosion2( origin, direction, cgs.media.nukeModel, 5, (qhandle_t)NULL, 250, qfalse, 25.0f, LEF_FADE_RGB);
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+
+ VectorSet( le->lightColor, 1.0f, 0.6f, 0.2f );
+
+ // Ground ring
+ FX_AddQuad( origin, norm, 5, 100, 1.0, 0.0, random() * 360, 300, cgs.media.bigShockShader );
+ // Flare
+ VectorMA( origin, 12, direction, org );
+ FX_AddSprite( org, NULL, qfalse, 160.0, -160.0, 1.0, 0.0, 0.0, 0.0, 200, cgs.media.sunnyFlareShader );//, FXF_NON_LINEAR_FADE );
+
+ for (i = 0; i < 12; i++)
+ {
+ float width, length;
+ FXE_Spray( norm, 470, 325, 0.5f, vel);
+ length = 24.0 + random() * 12;
+ width = 0.5 + random() * 2;
+ FX_AddTrail( origin, vel, qtrue, length, -length, width, -width,
+ 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader);
+ }
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExnull
+ // Smoke and impact
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, norm, random()*360, 1,1,1,1.0, qfalse,
+ random() * 16 + 48, qfalse );
+}*/
+
+//-----------------------------------
+//By: RedTechie - Imported/Modifyed from SP
+//-----------------------------------
+void FX_GrenadeShrapnelExplode( vec3_t origin, vec3_t norm )
+{
+ localEntity_t *le;
+ //FXTrail *fx;
+ vec3_t direction, org, vel;
+ int i;
+
+ CG_InitLensFlare( origin,
+ 350, 350,
+ colorTable[CT_DKRED1], 1.2, 2.0, 1600, 200,
+ colorTable[CT_DKRED1], 1600, 200, 800, 20, qtrue,
+ 0, 0, qfalse, qtrue,
+ qfalse, 1.0, cg.time, 90, 0, 300);
+
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, direction );
+ VectorNormalize( direction );
+
+ VectorMA( origin, 12, direction, org );
+ // Add an explosion and tag a light to it
+ le = CG_MakeExplosion2( org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 700, qfalse, 1.2f + (random()*0.5f),LEF_FADE_RGB ); //RPG-X: RedTechie - Scale use to be 1.2f + (random()*0.3f)
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 1.0f, 0.6f, 0.6f );
+
+ VectorMA( org, 8, norm, direction );
+ VectorSet(vel, 0, 0, 8);
+ //Some smoke
+ FX_AddSprite( direction,
+ vel,
+ qfalse,
+ 20.0f + random()*50.0f,//1.2f + (random()*0.5f),//60.0f - random()*60.0f
+ 16.0f,
+ 100.0f,//1.0f
+ 100.0f,//0.0f
+ random()*45.0f,
+ -12.0f,
+ 8000.0f,
+ cgs.media.steamShader );
+
+
+ for ( i = 0; i < 6; i++)
+ {
+ float width, length;
+ FXE_Spray( norm, 500, 175, 0.8f, vel);//, (FXPrimitive *) fx
+ length = 24.0 + random() * 12;
+ width = 0.5 + random() * 2;
+ FX_AddTrail( origin, vel, qtrue, length, -length, width, -width,
+ 1.0f, 1.0f, 0.5f, 2500.0f, cgs.media.orangeTrailShader);//RPG-X: RedTechie - Killtime use to be 1000.0f
+
+ /*FX_AddTrail( origin, NULL, NULL, 16.0f, -15.0f,
+ 1.5, -1.5, 1.0f, 1.0f, 0.2f, 1000.0f, cgs.media.orangeTrailShader, rand() & FXF_BOUNCE );
+*/
+ /*if ( fx == NULL )
+ return;*/
+
+
+ }
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeAltExplodeSnd );
+
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, norm, random()*360, 1,1,1,1.0, qfalse,
+ random() * 16 + 48, qfalse );
+
+ CG_ExplosionEffects( origin, 2.0, 350 );
+}
+
+qboolean GrenadeBeep(localEntity_t *le)
+{
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_GRENADE_LAUNCHER];
+
+ trap_S_StartSound(le->refEntity.origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound);
+ return qtrue;
+}
+
+/*
+-------------------------
+FX_GrenadeShrapnelBits
+By: RedTechie - From SP
+-------------------------
+*/
+
+/*void FX_BlowBits( vec3_t start, vec3_t end, vec3_t dir, vec3_t user )
+{
+ vec3_t diff, org;
+ float len;
+// FXLine *fx;
+
+ VectorSubtract( end, start, diff );
+ len = VectorNormalize( diff ) * ( 0.2 + random() * 0.3 );
+ VectorMA( start, len, diff, org );
+
+ //fx =
+ FX_AddLine( end, start, (int)(random() * 3.2f), 2.0f + random() * 2, 0, 0.5f, 0.1f, 150 + random() * 150, cgs.media.orangeTrailShader );
+
+ //if ( fx == NULL )
+ // return;
+
+ //fx->SetFlags( FXF_SHRINK );
+
+ FX_AddQuad( end, dir, NULL, NULL, 1.0f, 64.0f, 1.0, 0.0, random() * 360.0f, 0.0f, 0.0, 200, cgs.media.orangeRingShader );
+ // FX_AddQuad( end, dir, NULL, NULL, 20.0, -15.0, 0.6, 0.4, 0.0,0.0,0.0,450, cgs.media.borgEyeFlareShader );
+}
+*/
+#define FX_GRENADE_ALT_STICK_TIME 2500
+void FX_GrenadeShrapnelBits( vec3_t start )
+{
+ vec3_t zero = {0, 0, 0};
+ // check G_MissileStick() to make sure this killtime coincides with that nextthink
+ FX_AddSpawner( start, zero, NULL, NULL, qfalse, 300,
+ 0, FX_GRENADE_ALT_STICK_TIME, GrenadeBeep, 10 );
+}
+
+
+/*
+-------------------------
+FX_fxfunc_Explosion
+-------------------------
+*/
+void FX_fxfunc_Explosion( vec3_t start, vec3_t origin, vec3_t normal )
+{
+ localEntity_t *le;
+ vec3_t dir;
+ vec3_t velocity;
+// vec3_t end;
+// trace_t trace;
+ float scale, dscale;
+ int i, j, numSparks;
+ //weaponInfo_t *weaponInfo = &cg_weapons[WP_COMPRESSION_RIFLE];
+ //float scale, dscale;
+// int s;
+// vec3_t new_org;
+
+ //Sparks
+ numSparks = 20 + (random() * 4.0f);//4
+
+ for ( i = 0; i < numSparks; i++ )
+ {
+ scale = 0.25f + (random() * 1.0f);
+ dscale = -scale;
+
+ //Randomize the direction
+ for (j = 0; j < 3; j ++ )
+ {
+ dir[j] = normal[j] + (0.75 * crandom());
+ }
+
+ VectorNormalize(dir);
+
+ //set the speed
+ VectorScale( dir, 200 + (50 * crandom()), velocity);
+
+ le = FX_AddTrail( origin,
+ velocity,
+ qtrue,
+ 4.0f,
+ -4.0f,
+ scale,
+ -scale,
+ 1.0f,
+ 1.0f,
+ 0.5f,
+ 1000.0f,
+ cgs.media.sparkShader);
+
+ }
+
+ VectorMA( origin, 8, normal, dir );
+ VectorSet(velocity, 0, 0, 8);
+
+ // Smoke puffs
+ FX_AddSprite( dir,
+ velocity,
+ qfalse,
+ 20.0f + random()*60.0f,//2.2f + ( crandom() * 0.9f),//60.0f - random()*60.0f
+ 16.0f,
+ 100.0f,//1.0f
+ 100.0f,//0.0f
+ random()*45.0f,
+ -12.0f,
+ 8000.0f,
+ cgs.media.steamShader );
+
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, dir );
+ VectorNormalize( dir );
+
+ le = CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.electricalExplosionSlowShader, 475, qfalse, 2.2f + ( crandom() * 0.9f), LEF_NONE);//RPG-X: RedTechie - Scale use to be - 1.2f + ( crandom() * 0.3f)
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f );
+
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse,
+ random() * 16 + 48, qfalse );
+ //CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 12, qfalse );
+
+ //Shake the camera
+ CG_ExplosionEffects( origin, 2, 400 );
+
+ // nice explosion sound at the point of impact
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeAltExplodeSnd );
+ //trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound);
+}
+
+
+/*
+-------------------------
+FX_fxfunc_Shot
+-------------------------
+*/
+#define MAXRANGE_CRIFLE 8192
+void FX_fxfunc_Shot( vec3_t start, vec3_t dir )
+{
+ vec3_t end;
+ trace_t trace;
+
+ VectorMA(start, MAXRANGE_CRIFLE, dir, end);
+ CG_Trace( &trace, start, NULL, NULL, end, 0, MASK_SHOT );
+
+ //FX_CompressionExplosion(start, trace.endpos, trace.plane.normal, qfalse );
+ FX_fxfunc_Explosion(start, trace.endpos, trace.plane.normal);
+}
diff --git a/cgame/fx_imod.c b/cgame/fx_imod.c
new file mode 100644
index 0000000..05ab0a4
--- /dev/null
+++ b/cgame/fx_imod.c
@@ -0,0 +1,315 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+/*
+-------------------------
+FX_AltIMODBolt
+-------------------------
+*/
+
+/*void FX_AltIMODBolt( vec3_t start, vec3_t end, vec3_t dir )
+{
+ vec3_t control1, control2, control1_vel, control2_vel,
+ control1_acceleration, control2_acceleration;
+ vec3_t direction, vr, vu;
+ float len;
+ localEntity_t *le;
+
+ MakeNormalVectors( dir, vr, vu );
+
+ VectorSubtract( end, start, direction );
+ len = VectorNormalize( direction );
+
+ VectorMA(start, len*0.5f, direction, control1 );
+ VectorMA(start, len*0.25f, direction, control2 );
+
+ vectoangles( direction, control1_vel );
+ control1_vel[ROLL] += crandom() * 360;
+ AngleVectors( control1_vel, NULL, NULL, control1_vel );
+
+ vectoangles( direction, control2_vel );
+ control2_vel[ROLL] += crandom() * 360;
+ AngleVectors( control2_vel, NULL, NULL, control2_vel );
+
+ VectorScale(control1_vel, 12.0f + (140.0f * random()), control1_vel);
+ VectorScale(control2_vel, -12.0f + (-140.0f * random()), control2_vel);
+
+ VectorClear(control1_acceleration);
+ VectorClear(control2_acceleration);
+ le = FX_AddBezier( start, end,
+ control1, control2, control1_vel, control2_vel, control1_acceleration, control2_acceleration,
+ 4.0f, //scale
+ 1000.0f, //killtime
+ cgs.media.IMOD2Shader );
+ le->alpha = 0.8;
+ le->dalpha = -0.8;
+}*/
+
+/*
+-------------------------
+FX_IMODBolt2
+-------------------------
+*/
+
+/*void FX_IMODBolt2( vec3_t start, vec3_t end, vec3_t dir )
+{
+ vec3_t control1, control2, control1_velocity, control2_velocity,
+ control1_acceleration, control2_acceleration;
+ float length = 0;
+ vec3_t vTemp;
+ localEntity_t *le;
+
+ // initial position of control points
+ VectorSubtract(end, start, vTemp);
+ length = VectorNormalize(vTemp);
+ VectorMA(start, 0.5 * length, vTemp, control1);
+ VectorMA(start, 0.25 * length, vTemp, control2);
+
+ // initial velocity of control points
+ vectoangles(vTemp, control1_velocity);
+ control1_velocity[ROLL] += crandom() * 360;
+ AngleVectors(control1_velocity, NULL, NULL, control1_velocity);
+
+ vectoangles(vTemp, control2_velocity);
+ control2_velocity[ROLL] += crandom() * 360;
+ AngleVectors(control2_velocity, NULL, NULL, control2_velocity);
+
+ VectorScale(control1_velocity, 12.0f + (140.0f * random()), control1_velocity);
+ VectorScale(control2_velocity, -12.0f + (-140.0f * random()), control2_velocity);
+
+ // constant acceleration of control points
+ /*
+ VectorScale(control1_velocity, -1.2, control1_acceleration);
+ for (i = 0; i < 3; i++)
+ {
+ control1_acceleration[i] += flrandom (-10, 10);
+ }
+ VectorScale(control2_velocity, -1.2, control2_acceleration);
+ for (i = 0; i < 3; i++)
+ {
+ control2_acceleration[i] += flrandom (-10, 10);
+ }
+ */
+// VectorClear(control1_acceleration);
+// VectorClear(control2_acceleration);
+//
+// le = FX_AddBezier(start, end, control1, control2, control1_velocity, control2_velocity, control1_acceleration,
+// control2_acceleration, 4, 600, cgs.media.altIMOD2Shader);
+// le->alpha = 0.6;
+// le->dalpha = -0.6;
+//}*/
+
+
+/*
+-------------------------
+FX_IMODShot
+-------------------------
+*/
+
+//#define MAXRANGE_IMOD 8192
+
+/*void FX_IMODShot( vec3_t end, vec3_t start, vec3_t dir)
+{
+ vec3_t ofs, end2;
+ trace_t trace;
+ qboolean render_impact = qtrue;
+
+ VectorMA( end, 1, dir, ofs );
+
+ FX_AddLine(start, end, 1.0, 8.0, -8.0, 1.0, 0.0, 350, cgs.media.altIMODShader);
+
+ FX_IMODBolt2( start, end, dir);
+
+ // cover up the start point of the beam
+ FX_AddSprite( start, NULL, qfalse, irandom(8,12), -8, 1.0, 0.6, random()*360, 0.0, 400, cgs.media.purpleParticleShader);
+ // where do we put an explosion?
+ VectorMA(start, MAXRANGE_IMOD, cg.refdef.viewaxis[0], end2);
+ CG_Trace( &trace, start, NULL, NULL, end2, 0, MASK_SHOT );
+
+ if ( trace.surfaceFlags & SURF_NOIMPACT )
+ {
+ render_impact = qfalse;
+ }
+ if ( render_impact )
+ {
+ FX_IMODExplosion(end, trace.plane.normal);
+ }
+}*/
+
+/*
+-------------------------
+FX_AltIMODShot
+-------------------------
+*/
+//unused
+/*#define FX_ALT_IMOD_HOLD 200
+#define FX_ALT_IMOD_FLASHSIZE 16
+qboolean IMODAltAftereffect(localEntity_t *le)
+{
+ localEntity_t *spr = NULL;
+ qhandle_t shader = cgs.media.blueParticleShader;
+
+ //only want an initial sprite
+ le->endTime = cg.time;
+
+ spr = FX_AddSprite( le->refEntity.origin,// origin
+ NULL, // velocity
+ qfalse, // gravity
+ FX_ALT_IMOD_FLASHSIZE, // scale
+ -10, // dscale
+ 0.8, // startalpha
+ 0.0, // endalpha
+ 0.0, // roll
+ 0.0, // elasticity
+ 700, // killTime
+ shader); // shader
+
+ return qtrue;
+}*/
+
+/*void FX_AltIMODShot( vec3_t end, vec3_t start, vec3_t dir)
+{
+ vec3_t ofs, end2;
+ int i = 0;
+ trace_t trace;
+ qboolean render_impact = qtrue;
+
+ VectorMA( end, 1, dir, ofs );
+
+ FX_AddLine( start, end, 1.0f, 32.0f, -32.0f, 1.0f, 1.0f, 500.0f, cgs.media.IMODShader);
+
+ for ( i = 0; i < 2; i++ )
+ FX_AltIMODBolt( start, end, dir );
+
+ // cover up the start point of the beam
+ FX_AddSprite( start, NULL, qfalse, FX_ALT_IMOD_FLASHSIZE, 0, 1.0, 0.6, 0.0, 0.0, FX_ALT_IMOD_HOLD, cgs.media.blueParticleShader);
+ FX_AddSpawner( start, dir, NULL, NULL, qfalse, FX_ALT_IMOD_HOLD,
+ 0, FX_ALT_IMOD_HOLD+100, IMODAltAftereffect, 10 );
+ // where do we put an explosion?
+ VectorMA(start, MAXRANGE_IMOD, cg.refdef.viewaxis[0], end2);
+ CG_Trace( &trace, start, NULL, NULL, end2, 0, MASK_SHOT );
+
+ if ( trace.surfaceFlags & SURF_NOIMPACT )
+ {
+ render_impact = qfalse;
+ }
+ if ( render_impact )
+ {
+ FX_AltIMODExplosion(end, trace.plane.normal);
+ }
+}*/
+
+/*
+-------------------------
+FX_IMODExplosion
+-------------------------
+*/
+
+/*void FX_IMODExplosion( vec3_t origin, vec3_t normal )
+{
+ localEntity_t *le;
+ vec3_t direction, vel;
+ float scale, dscale;
+ int i, numSparks;
+
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, direction );
+ VectorNormalize( direction );
+
+ //Tag the last one with a light
+ le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, cgs.media.imodExplosionShader, 400, qfalse);
+ le->light = 75;
+ VectorSet( le->lightColor, 1.0f, 0.8f, 0.5f );
+
+ //Sparks
+ numSparks = 3 + (rand() & 7);
+ // kef -- fixme. what does vel do?
+ VectorClear(vel);
+
+ for ( i = 0; i < numSparks; i++ )
+ {
+ scale = 1.0f + (random() * 0.5f);
+ dscale = -scale*0.5;
+
+ FX_AddTrail( origin,
+ NULL,
+ qfalse,
+ 32.0f + (random() * 64.0f),
+ -256.0f,
+ scale,
+ 0.0f,
+ 1.0f,
+ 0.0f,
+ 0.25f,
+ 750.0f,
+ cgs.media.purpleParticleShader );
+
+ //FXE_Spray( normal, 500, 250, 0.75f, /*256,*///vel );
+ //}
+
+ //CG_ImpactMark( cgs.media.IMODMarkShader, origin, normal, random()*360, 1,1,1,0.75, qfalse, 5, qfalse );
+// CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, random() + 1, qfalse );
+
+// CG_ExplosionEffects( origin, 1.0f, 150 );
+//}*/
+
+/*
+-------------------------
+FX_AltIMODExplosion
+-------------------------
+*/
+/*void FX_AltIMODExplosion( vec3_t origin, vec3_t normal )
+{
+ localEntity_t *le;
+ vec3_t direction, org, vel;
+ float scale, dscale;
+ int i, numSparks;
+
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, direction );
+ VectorNormalize( direction );
+
+ //Tag the last one with a light
+ le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, cgs.media.electricalExplosionSlowShader, 475, qfalse);
+ le->light = 150;
+ VectorSet( le->lightColor, 1.0f, 0.8f, 0.5f );
+
+ for ( i = 0; i < 2; i ++)
+ {
+ VectorSet( org, origin[0] + 16 * random(), origin[1] + 16 * random(), origin[2] + 16 * random() );
+ CG_MakeExplosion( org, direction, cgs.media.explosionModel, cgs.media.electricalExplosionFastShader,
+ 250, qfalse);
+ }
+
+ //Sparks
+
+ numSparks = 8 + (rand() & 7);
+
+ // kef -- fixme. what does this vector do!?! waaaaaah!
+ VectorClear(vel);
+
+ for ( i = 0; i < numSparks; i++ )
+ {
+ scale = 1.5f + (random() * 0.5f);
+ dscale = -scale*0.5;
+
+ FX_AddTrail( origin,
+ NULL,
+ qfalse,
+ 32.0f + (random() * 64.0f),
+ -256.0f,
+ scale,
+ 0.0f,
+ 1.0f,
+ 0.0f,
+ 0.25f,
+ 750.0f,
+ cgs.media.spark2Shader );
+
+ FXE_Spray( normal, 500, 250, 0.75f, /*256,*/ //vel );
+ //}
+
+ //CG_ImpactMark( cgs.media.IMODMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 8, qfalse );
+
+ //CG_ExplosionEffects( origin, 2.0f, 250 );
+//}*/
diff --git a/cgame/fx_item.c b/cgame/fx_item.c
new file mode 100644
index 0000000..e35b254
--- /dev/null
+++ b/cgame/fx_item.c
@@ -0,0 +1,306 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+
+
+//
+// detpack
+//
+
+#define NUM_RING_SHADERS 6
+
+qboolean DetpackAftereffect(localEntity_t *le)
+{
+ localEntity_t *cyl = NULL;
+ qhandle_t shader = cgs.media.phaserShader;
+ qhandle_t slowRingShaders[NUM_RING_SHADERS];
+ float percentLife = 1.0 - (le->endTime - cg.time)*le->lifeRate;
+ float alpha = 0.6 - (0.6*percentLife*percentLife);
+ // data for shell
+ float shellLife = percentLife + .2;
+ float height1 = 20 + (percentLife * 150);
+ float height2 =(50*percentLife);
+ float scale1 = 40 + (percentLife * 1500);
+ float scale2 = 20 + (percentLife * 1200);
+ // data for flat energy rings
+ float ringLife = percentLife + .5;
+ float scale3 = 200 + (percentLife * 3400);
+ float scale4 = 100 + (percentLife * 3000);
+ float scale5 = 20 + (percentLife * 1000);
+ float scale6 = 10 + (percentLife * 200);
+ float ringAlpha = 0.6 - (0.6*ringLife*ringLife);
+ vec3_t up = {0,0,1},origin1;
+
+
+ slowRingShaders[0] = cgs.media.testDetpackRingShader1;
+ slowRingShaders[1] = cgs.media.testDetpackRingShader2;
+ slowRingShaders[2] = cgs.media.testDetpackRingShader3;
+ slowRingShaders[3] = cgs.media.testDetpackRingShader4;
+ slowRingShaders[4] = cgs.media.testDetpackRingShader5;
+ slowRingShaders[5] = cgs.media.testDetpackRingShader6;
+
+ // slower, inner ring
+ VectorCopy(le->refEntity.origin, origin1);
+ if (NUM_RING_SHADERS == le->data.spawner.data1)
+ {
+ le->data.spawner.data1 = 0;
+ }
+ else if (le->data.spawner.data1 < 0)
+ {
+ le->data.spawner.data1 = 0;
+ }
+ shader = slowRingShaders[le->data.spawner.data1++];
+ // fast, outer ring
+ cyl = FX_AddCylinder( origin1,
+ up,
+ 0.1,// height,
+ 0,// dheight,
+ scale5,// scale,
+ 0,// dscale,
+ scale6,// scale2,
+ 0,// dscale2,
+ ringAlpha,// startalpha,
+ 0.0,// endalpha,
+ 500,// killTime,
+ shader,
+ 15);// bias );
+ cyl->leFlags |= LEF_ONE_FRAME;
+
+ if (shellLife <= 1.0f)
+ {
+ origin1[2] += height2;
+ shader = cgs.media.phaserShader;
+ cyl = FX_AddCylinder( origin1,
+ up,
+ height1,// height,
+ 0,// dheight,
+ scale1,// scale,
+ 0,// dscale,
+ scale2,// scale2,
+ 0,// dscale2,
+ alpha,// startalpha,
+ 0.0,// endalpha,
+ 500,// killTime,
+ shader,
+ 15);// bias );
+ cyl->leFlags |= LEF_ONE_FRAME;
+
+ cyl = FX_AddCylinder( le->refEntity.origin,
+ up,
+ height2, // height,
+ 0, // dheight,
+ scale1, // scale,
+ 0, // dscale,
+ scale1, // scale2,
+ 0, // dscale2,
+ alpha, // startalpha,
+ 0.0, // endalpha,
+ 500, // killTime,
+ shader,
+ 15); // bias );
+ cyl->leFlags |= LEF_ONE_FRAME;
+ }
+ // flat energy wave thingy
+ if (ringLife <= 1.0f)
+ {
+ shader = cgs.media.testDetpackShader3;
+ VectorCopy(le->refEntity.origin, origin1);
+ // fast, outer ring
+
+ cyl = FX_AddCylinder( origin1,
+ up,
+ 0.1,// height,
+ 0,// dheight,
+ scale3,// scale,
+ 0,// dscale,
+ scale4,// scale2,
+ 0,// dscale2,
+ ringAlpha,// startalpha,
+ 0.0,// endalpha,
+ 500,// killTime,
+ shader,
+ 15);// bias );
+ cyl->leFlags |= LEF_ONE_FRAME;
+ }
+ return qtrue;
+}
+
+
+void FX_Detpack(vec3_t origin)
+{
+ localEntity_t *le;
+ qhandle_t null;
+ vec3_t direction, org, vel, norm = {0,0,1};
+ int i;
+
+ VectorCopy( norm, direction);
+
+ // Add an explosion and tag a light to it
+ le = CG_MakeExplosion2( origin, direction, cgs.media.nukeModel, 5, null, 250, qfalse, 100.0f, LEF_FADE_RGB);
+ le->light = 300;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+
+ VectorSet( le->lightColor, 1.0f, 0.6f, 0.2f );
+
+ // Ground ring
+// FX_AddQuad( origin, norm, 5, 150, 1.0, 0.0, random() * 360, 600, cgs.media.bigShockShader );
+ // Flare
+ VectorMA( origin, 12, direction, org );
+ FX_AddSprite( org, NULL, qfalse, 160.0, -160.0, 1.0, 0.0, 0.0, 0.0, 500, cgs.media.sunnyFlareShader );//, FXF_NON_LINEAR_FADE );
+
+ for (i = 0; i < 12; i++)
+ {
+ float width, length;
+ FXE_Spray( norm, 470, 325, 0.5f, vel);
+ length = 50.0 + random() * 12;
+ width = 1.5 + random() * 2;
+ FX_AddTrail( origin, vel, qtrue, length, -length, width, -width,
+ 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader);
+ }
+
+// trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.detpackExplodeSound );
+
+ // Smoke and impact
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, norm, random()*360, 1,1,1,1.0, qfalse,
+ random() * 16 + 48, qfalse );
+
+ // mondo explosion shock wave cloud thing
+ le = FX_AddSpawner( origin, norm, NULL, NULL, qfalse, 0,
+ 0, 1500, DetpackAftereffect, 10 );
+ le->data.spawner.data1 = 0;
+
+ // shake absolutely _everyone_
+ CG_ExplosionEffects(origin, 5.0f, 8092);
+}
+
+
+//
+// portable shield
+//
+
+//RPG-X ToDo: Modify force field Code Here
+void FX_DrawPortableShield(centity_t *cent)
+{
+ int xaxis, height, posWidth, negWidth, team; // light;
+ vec3_t start, end, normal;
+ //vec4_t RGBA;
+ float halfHeight;
+ localEntity_t *le;
+ qhandle_t shader;
+
+ if (cent->currentState.eFlags & EF_NODRAW)
+ {
+ return;
+ }
+
+ // decode the data stored in time2
+ //pos = ((cent->currentState.time2 >> 32) & 1);
+ //vert = ((cent->currentState.time2 >> 31) & 1);
+ xaxis = ((cent->currentState.time2 >> 30) & 1); //24
+ height = ((cent->currentState.time2 >> 20) & 1023); //16
+ posWidth = ((cent->currentState.time2 >> 10) & 1023); //8
+ negWidth = (cent->currentState.time2 & 1023);
+
+ team = (cent->currentState.otherEntityNum2);
+ halfHeight = (float)height * .5;
+
+ /*if ( !vert )
+ {*/
+ VectorCopy(cent->lerpOrigin, start);
+ VectorCopy(cent->lerpOrigin, end);
+ start[2] += halfHeight;
+ end[2] += halfHeight;
+
+ VectorClear(normal);
+ if (xaxis) // drawing along x-axis
+ {
+ start[0] -= negWidth;
+ end[0] += posWidth;
+ normal[1] = 1;
+ }
+ else
+ {
+ start[1] -= negWidth;
+ end[1] += posWidth;
+ normal[0] = 1;
+ }
+ //}
+ //else
+ //{
+ // VectorCopy(cent->lerpOrigin, start);
+ // VectorCopy(cent->lerpOrigin, end);
+ // if ( xaxis ) {
+ // start[1] += halfHeight;
+ // end[1] += halfHeight;
+ // }
+ // else
+ // {
+ // start[0] += halfHeight;
+ // end[0] += halfHeight;
+ // }
+
+ // VectorClear(normal);
+ // if (xaxis) // drawing along x-axis
+ // {
+ // start[0] -= negWidth;
+ // end[0] += posWidth;
+ // normal[2] = 1;
+ // }
+ // else
+ // {
+ // start[1] -= negWidth;
+ // end[1] += posWidth;
+ // normal[2] = 1;
+ // }
+ //}
+ // draw a rectangle o' shieldness
+/*
+ if (team == TEAM_RED)
+ {
+ if (cent->currentState.eFlags & EF_ITEMPLACEHOLDER)
+ { // Damaged.
+ shader = cgs.media.shieldDamageShaderRed;
+ }
+ else
+ {
+ shader = cgs.media.shieldActivateShaderRed;
+ }
+ }
+ else
+ {*/
+
+ //TiM - Show the forcefield when the place flag is active only
+ //This way, we canhave it flare on events, and invisible the rest of the time
+
+ //tho make sure admins can see it
+ if((int)cent->currentState.origin2[0] == 1) {
+ shader = cgs.media.shieldActivateShaderBorg;
+ }
+ else if((int)cent->currentState.origin2[0] == 2) {
+ shader = cgs.media.shieldActivateShaderYellow;
+ }
+ else if((int)cent->currentState.origin2[0] == 3) {
+ shader = cgs.media.shieldActivateShaderRed;
+ }
+ else {
+ shader = cgs.media.shieldActivateShaderBlue;
+ }
+ if ( cent->currentState.eFlags & EF_ITEMPLACEHOLDER || cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/ )
+ le = FX_AddOrientedLine(start, end, normal, 1.0f, height, 0.0f, 1.0f, 1.0f, 50.0, shader);
+ //TiM
+ //if (cent->currentState.eFlags & EF_ITEMPLACEHOLDER)
+ //{ // Damaged.
+ // shader = cgs.media.shieldDamageShaderBlue;
+ //}
+ //else
+ //{
+ // shader = cgs.media.shieldActivateShaderBlue;
+ //}
+ //-
+// }
+
+ //le = FX_AddOrientedLine(start, end, normal, 1.0f, height, 0.0f, 1.0f, 1.0f, 50.0, shader);
+// le->leFlags |= LEF_ONE_FRAME;
+}
+
+
diff --git a/cgame/fx_lib.c b/cgame/fx_lib.c
new file mode 100644
index 0000000..3d41c45
--- /dev/null
+++ b/cgame/fx_lib.c
@@ -0,0 +1,970 @@
+// FX Library
+
+#include "cg_local.h"
+
+void FXE_Spray (vec3_t direction, float speed, float variation, float cone, vec3_t velocity)
+{
+ vec3_t dir;
+ int i;
+
+ //Randomize the direction
+ for (i = 0; i < 3; i ++ )
+ {
+ dir[i] = direction[i] + (cone * crandom());
+ }
+
+ VectorNormalize(dir);
+
+ //set the speed
+ VectorScale( dir, speed + (variation * crandom()), velocity);
+}
+
+
+
+localEntity_t *FX_AddLine(vec3_t start, vec3_t end, float stScale, float scale, float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader)
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddLine: NULL shader\n");
+ }
+#endif
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_LINE;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+ le->data.line.width = scale;
+ le->data.line.dwidth = dscale;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+
+ le->refEntity.data.line.stscale = stScale;
+ le->refEntity.data.line.width = scale;
+
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( start, le->refEntity.origin);
+ VectorCopy ( end, le->refEntity.oldorigin );
+
+ AxisClear(le->refEntity.axis);
+ le->refEntity.shaderRGBA[0] = 0xff;
+ le->refEntity.shaderRGBA[1] = 0xff;
+ le->refEntity.shaderRGBA[2] = 0xff;
+ le->refEntity.shaderRGBA[3] = 0xff;
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = 1.0;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ return(le);
+}
+
+
+
+localEntity_t *FX_AddLine2(vec3_t start, vec3_t end, float stScale, float width1, float dwidth1, float width2, float dwidth2,
+ float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float killTime, qhandle_t shader)
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddLine2: NULL shader\n");
+ }
+#endif
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_LINE2;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+ le->data.line2.width = width1;
+ le->data.line2.dwidth = dwidth1;
+ le->data.line2.width2 = width2;
+ le->data.line2.dwidth2 = dwidth2;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+ VectorCopy(startRGB, le->data.line2.startRGB);
+ VectorSubtract(endRGB, startRGB, le->data.line2.dRGB);
+
+ le->refEntity.data.line.stscale = stScale;
+ le->refEntity.data.line.width = width1;
+ le->refEntity.data.line.width2 = width2;
+
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( start, le->refEntity.origin);
+ VectorCopy ( end, le->refEntity.oldorigin );
+
+ AxisClear(le->refEntity.axis);
+ le->refEntity.shaderRGBA[0] = 0xff;
+ le->refEntity.shaderRGBA[1] = 0xff;
+ le->refEntity.shaderRGBA[2] = 0xff;
+ le->refEntity.shaderRGBA[3] = 0xff;
+
+ le->color[0] = startRGB[0];
+ le->color[1] = startRGB[1];
+ le->color[2] = startRGB[2];
+ le->color[3] = startalpha;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ return(le);
+}
+
+localEntity_t *FX_AddOrientedLine(vec3_t start, vec3_t end, vec3_t normal, float stScale, float scale,
+ float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader)
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddLine: NULL shader\n");
+ }
+#endif
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_OLINE;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+ le->data.line.width = scale;
+ le->data.line.dwidth = dscale;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+
+ le->refEntity.data.line.stscale = stScale;
+ le->refEntity.data.line.width = scale;
+
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( start, le->refEntity.origin);
+ VectorCopy ( end, le->refEntity.oldorigin );
+
+ AxisClear(le->refEntity.axis);
+ VectorCopy( normal, le->refEntity.axis[0] );
+ RotateAroundDirection( le->refEntity.axis, 0); // le->refEntity.data.sprite.rotation ); This is roll in quad land
+
+ le->refEntity.shaderRGBA[0] = 0xff;
+ le->refEntity.shaderRGBA[1] = 0xff;
+ le->refEntity.shaderRGBA[2] = 0xff;
+ le->refEntity.shaderRGBA[3] = 0xff;
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = 1.0;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ return(le);
+}
+
+localEntity_t *FX_AddTrail( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength,
+ float scale, float dscale, float startalpha, float endalpha,
+ float elasticity, float killTime, qhandle_t shader)
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddTrail: NULL shader\n");
+ }
+#endif
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_TRAIL;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.trail.width = scale;
+ le->data.trail.dwidth = dscale;
+ le->data.trail.length = length;
+ le->data.trail.dlength = dlength;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+ VectorSet(le->data.trail.startRGB, 1, 1, 1);
+ VectorSet(le->data.trail.dRGB, 0, 0, 0);
+
+ le->refEntity.data.line.stscale = 1.0;
+ le->refEntity.data.line.width = scale;
+
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ // kef -- extrapolate oldorigin based on length of trail and origin?
+ if (velocity)
+ {
+ vec3_t vel;
+ VectorNormalize2(velocity, vel);
+ VectorMA(origin, -length, vel, le->refEntity.oldorigin);
+ }
+ else
+ {
+ VectorCopy ( origin, le->refEntity.oldorigin );
+ }
+
+ AxisClear(le->refEntity.axis);
+ le->refEntity.shaderRGBA[0] = 0xff;
+ le->refEntity.shaderRGBA[1] = 0xff;
+ le->refEntity.shaderRGBA[2] = 0xff;
+ le->refEntity.shaderRGBA[3] = 0xff*startalpha;
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = 1.0;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ if (velocity)
+ {
+ le->leFlags |= LEF_MOVE;
+ VectorCopy (origin, le->pos.trBase);
+ VectorCopy (velocity, le->pos.trDelta);
+ if (gravity)
+ le->pos.trType = TR_GRAVITY;
+ else
+ le->pos.trType = TR_LINEAR;
+ le->pos.trTime = cg.time;
+ le->pos.trDuration = killTime;
+
+ if (elasticity > 0)
+ {
+ le->leFlags |= LEF_USE_COLLISION;
+ le->bounceFactor = elasticity;
+ }
+ }
+
+ return(le);
+}
+
+
+
+localEntity_t *FX_AddTrail2( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength,
+ float scale, float dscale, float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB,
+ float elasticity, float killTime, qhandle_t shader)
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddTrail: NULL shader\n");
+ }
+#endif
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_TRAIL;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.trail.width = scale;
+ le->data.trail.dwidth = dscale;
+ le->data.trail.length = length;
+ le->data.trail.dlength = dlength;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+ VectorCopy(startRGB, le->data.trail.startRGB);
+ VectorSubtract(endRGB, startRGB, le->data.trail.dRGB);
+
+ le->refEntity.data.line.stscale = 1.0;
+ le->refEntity.data.line.width = scale;
+
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ // kef -- extrapolate oldorigin based on length of trail and origin?
+ if (velocity)
+ {
+ vec3_t vel;
+ VectorNormalize2(velocity, vel);
+ VectorMA(origin, -length, vel, le->refEntity.oldorigin);
+ }
+ else
+ {
+ VectorCopy ( origin, le->refEntity.oldorigin );
+ }
+
+ AxisClear(le->refEntity.axis);
+ le->refEntity.shaderRGBA[0] = 0xff*startRGB[0];
+ le->refEntity.shaderRGBA[1] = 0xff*startRGB[1];
+ le->refEntity.shaderRGBA[2] = 0xff*startRGB[2];
+ le->refEntity.shaderRGBA[3] = 0xff*startalpha;
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = 1.0;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ if (velocity)
+ {
+ le->leFlags |= LEF_MOVE;
+ VectorCopy (origin, le->pos.trBase);
+ VectorCopy (velocity, le->pos.trDelta);
+ if (gravity)
+ le->pos.trType = TR_GRAVITY;
+ else
+ le->pos.trType = TR_LINEAR;
+ le->pos.trTime = cg.time;
+ le->pos.trDuration = killTime;
+
+ if (elasticity > 0)
+ {
+ le->leFlags |= LEF_USE_COLLISION;
+ le->bounceFactor = elasticity;
+ }
+ }
+
+ return(le);
+}
+
+
+/*
+===============
+FX_AddSprite
+
+Adds a view oriented sprite to the FX wrapper render list
+===============
+*/
+
+localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float elasticity,
+ float killTime, qhandle_t shader)
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddSprite: NULL shader\n");
+ }
+#endif
+
+ // Glow mark
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_VIEWSPRITE;
+ le->refEntity.data.sprite.rotation = roll;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.sprite.radius = scale;
+ le->data.sprite.dradius = dscale;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+ VectorSet(le->data.sprite.startRGB, 1, 1, 1);
+ VectorSet(le->data.sprite.dRGB, 0, 0, 0);
+
+// le->refEntity.hModel = 0;
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ VectorCopy ( origin, le->refEntity.oldorigin );
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = startalpha;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ if (velocity)
+ {
+ le->leFlags |= LEF_MOVE;
+ VectorCopy (origin, le->pos.trBase);
+ VectorCopy (velocity, le->pos.trDelta);
+ if (gravity)
+ le->pos.trType = TR_GRAVITY;
+ else
+ le->pos.trType = TR_LINEAR;
+ le->pos.trTime = cg.time;
+ le->pos.trDuration = killTime;
+
+ if (elasticity > 0)
+ {
+ le->leFlags |= LEF_USE_COLLISION;
+ le->bounceFactor = elasticity;
+ }
+ }
+
+ return(le);
+}
+
+
+/*
+===============
+FX_AddSprite2
+
+Adds a view oriented sprite to the FX wrapper render list
+===============
+*/
+
+localEntity_t *FX_AddSprite2(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float roll, float elasticity,
+ float killTime, qhandle_t shader)
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddSprite: NULL shader\n");
+ }
+#endif
+
+ // Glow mark
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_VIEWSPRITE;
+ le->refEntity.data.sprite.rotation = roll;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.sprite.radius = scale;
+ le->data.sprite.dradius = dscale;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+ VectorCopy(startRGB, le->data.sprite.startRGB);
+ VectorSubtract(endRGB, startRGB, le->data.sprite.dRGB);
+
+// le->refEntity.hModel = 0;
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ VectorCopy ( origin, le->refEntity.oldorigin );
+
+ le->color[0] = startRGB[0];
+ le->color[1] = startRGB[1];
+ le->color[2] = startRGB[2];
+ le->color[3] = startalpha;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ if (velocity)
+ {
+ le->leFlags |= LEF_MOVE;
+ VectorCopy (origin, le->pos.trBase);
+ VectorCopy (velocity, le->pos.trDelta);
+ if (gravity)
+ le->pos.trType = TR_GRAVITY;
+ else
+ le->pos.trType = TR_LINEAR;
+ le->pos.trTime = cg.time;
+ le->pos.trDuration = killTime;
+
+ if (elasticity > 0)
+ {
+ le->leFlags |= LEF_USE_COLLISION;
+ le->bounceFactor = elasticity;
+ }
+ }
+
+ return(le);
+}
+
+/*
+===============
+FX_AddBezier
+
+Adds a Bezier curve to the FX wrapper render list
+===============
+*/
+
+localEntity_t *FX_AddBezier(vec3_t start, vec3_t end, vec3_t cpoint1, vec3_t cpoint2, vec3_t cpointvel1, vec3_t cpointvel2,
+ vec3_t cpointacc1, vec3_t cpointacc2, float width, float killTime, qhandle_t shader)
+{
+ localEntity_t *le = CG_AllocLocalEntity();
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddBezier: NULL shader\n");
+ }
+#endif
+
+ // just testing beziers
+ le->leType = LE_BEZIER;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+ le->data.line.width = width;
+
+ le->alpha = 1.0;
+ le->dalpha = -1.0;
+
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( start, le->refEntity.origin);
+ VectorCopy ( end, le->refEntity.oldorigin );
+
+ AxisClear(le->refEntity.axis);
+ le->refEntity.shaderRGBA[0] = 0xff;
+ le->refEntity.shaderRGBA[1] = 0xff;
+ le->refEntity.shaderRGBA[2] = 0xff;
+ le->refEntity.shaderRGBA[3] = 0xff;
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = 1.0;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ if (cpoint1)
+ {
+ VectorCopy(cpoint1, le->data.line.control1);
+ }
+ if (cpoint2)
+ {
+ VectorCopy(cpoint2, le->data.line.control2);
+ }
+ if (cpointvel1)
+ {
+ VectorCopy(cpointvel1, le->data.line.control1_velocity);
+ }
+ if (cpointvel2)
+ {
+ VectorCopy(cpointvel2, le->data.line.control2_velocity);
+ }
+ if (cpointacc1)
+ {
+ VectorCopy(cpointacc1, le->data.line.control1_acceleration);
+ }
+ if (cpointacc2)
+ {
+ VectorCopy(cpointacc2, le->data.line.control2_acceleration);
+ }
+
+ return le;
+}
+
+/*
+===============
+FX_AddQuad
+
+Adds a quad to the FX wrapper render list
+===============
+*/
+
+localEntity_t *FX_AddQuad( vec3_t origin, vec3_t normal, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float killTime, qhandle_t shader )
+{
+ localEntity_t *le = CG_AllocLocalEntity();
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddQuad: NULL shader\n");
+ }
+#endif
+
+ le->leType = LE_QUAD;
+ le->refEntity.data.sprite.rotation = roll;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.sprite.radius = scale;
+ le->data.sprite.dradius = dscale;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+ VectorSet(le->data.sprite.startRGB, 1, 1, 1);
+ VectorSet(le->data.sprite.dRGB, 0, 0, 0);
+
+// le->refEntity.hModel = 0;
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ VectorCopy ( origin, le->refEntity.oldorigin );
+
+ VectorCopy( normal, le->refEntity.axis[0] );
+ RotateAroundDirection( le->refEntity.axis, le->refEntity.data.sprite.rotation );
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = startalpha;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ return(le);
+}
+
+
+
+/*
+===============
+FX_AddQuad2
+
+Adds a quad to the FX wrapper render list
+===============
+*/
+
+localEntity_t *FX_AddQuad2( vec3_t origin, vec3_t normal, float scale, float dscale, float startalpha, float endalpha,
+ vec3_t startRGB, vec3_t endRGB, float roll, float killTime, qhandle_t shader )
+{
+ localEntity_t *le = CG_AllocLocalEntity();
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddQuad: NULL shader\n");
+ }
+#endif
+
+ le->leType = LE_QUAD;
+ le->refEntity.data.sprite.rotation = roll;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.sprite.radius = scale;
+ le->data.sprite.dradius = dscale;
+ VectorCopy(startRGB, le->data.sprite.startRGB);
+ VectorSubtract(endRGB, startRGB, le->data.sprite.dRGB);
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+
+// le->refEntity.hModel = 0;
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ VectorCopy ( origin, le->refEntity.oldorigin );
+
+ VectorCopy( normal, le->refEntity.axis[0] );
+ RotateAroundDirection( le->refEntity.axis, le->refEntity.data.sprite.rotation );
+
+ le->color[0] = startRGB[0];
+ le->color[1] = startRGB[1];
+ le->color[2] = startRGB[2];
+ le->color[3] = startalpha;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ return(le);
+}
+
+
+
+/*
+===============
+FX_AddCylinder
+
+Adds a cylinder to the FX wrapper render list
+Overloaded for RGB
+===============
+*/
+
+//NOTENOTE: The reigning king of parameters!
+#define DEFAULT_ST_SCALE 1.0f
+
+localEntity_t *FX_AddCylinder( vec3_t start,
+ vec3_t normal,
+ float height,
+ float dheight,
+ float scale,
+ float dscale,
+ float scale2,
+ float dscale2,
+ float startalpha,
+ float endalpha,
+ float killTime,
+ qhandle_t shader,
+ float bias )
+{
+ localEntity_t *le = CG_AllocLocalEntity();
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddCylinder: NULL shader\n");
+ }
+#endif
+
+ le->leType = LE_CYLINDER;
+ le->refEntity.data.cylinder.height = height;
+ le->refEntity.data.cylinder.width = scale;
+ le->refEntity.data.cylinder.width2 = scale2;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.cylinder.height = height;
+ le->data.cylinder.dheight = dheight;
+ le->data.cylinder.width = scale;
+ le->data.cylinder.dwidth = dscale;
+ le->data.cylinder.width2 = scale2;
+ le->data.cylinder.dwidth2 = dscale2;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+
+ le->refEntity.customShader = shader;
+
+ le->refEntity.data.cylinder.bias = bias;
+ le->refEntity.data.cylinder.stscale = 1.0;
+ le->refEntity.data.cylinder.wrap = qtrue;
+
+ // set origin
+ VectorCopy ( start, le->refEntity.origin);
+ VectorCopy ( start, le->refEntity.oldorigin );
+
+ VectorCopy( normal, le->refEntity.axis[0] );
+ RotateAroundDirection( le->refEntity.axis, 0);
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = startalpha;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ return(le);
+}
+
+
+
+/*
+===============
+FX_AddElectricity
+
+Adds a electricity bolt to the scene
+===============
+*/
+
+localEntity_t *FX_AddElectricity( vec3_t origin, vec3_t origin2, float stScale, float scale, float dscale,
+ float startalpha, float endalpha, float killTime, qhandle_t shader, float deviation )
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddElectricity: NULL shader\n");
+ }
+#endif
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_ELECTRICITY;
+
+ // set origin
+ VectorCopy (origin, le->refEntity.origin);
+ VectorCopy (origin2, le->refEntity.oldorigin );
+
+ le->refEntity.data.electricity.stscale = stScale;
+ le->refEntity.data.electricity.deviation = deviation;
+ le->data.electricity.width = scale;
+ le->data.electricity.dwidth = dscale;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->refEntity.customShader = shader;
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = startalpha;
+
+ return(le);
+}
+/*
+===============
+FX_AddParticle
+
+Adds a particle (basically, a sprite with an optional think function) to the FX wrapper render list
+===============
+*/
+
+localEntity_t *FX_AddParticle( vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float elasticity,
+ float killTime, qhandle_t shader, qboolean (*thinkFn)(localEntity_t *le) )
+{
+ localEntity_t *le;
+
+#ifdef _DEBUG
+ if (!shader)
+ {
+ Com_Printf("FX_AddParticle: NULL shader\n");
+ }
+#endif
+
+ // Glow mark
+
+ le = CG_AllocLocalEntity();
+ le->leType = LE_PARTICLE;
+ le->refEntity.data.sprite.rotation = roll;
+
+ le->startTime = cg.time;
+ le->endTime = le->startTime + killTime;
+
+ le->data.particle.radius = scale;
+ le->data.particle.dradius = dscale;
+
+ le->alpha = startalpha;
+ le->dalpha = endalpha - startalpha;
+
+// le->refEntity.hModel = 0;
+ le->refEntity.customShader = shader;
+
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ VectorCopy ( origin, le->refEntity.oldorigin );
+
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = startalpha;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ // wacky think function stuff
+ le->data.particle.thinkFn = thinkFn;
+
+ if (velocity)
+ {
+ le->leFlags |= LEF_MOVE;
+ VectorCopy (origin, le->pos.trBase);
+ VectorCopy (velocity, le->pos.trDelta);
+ if (gravity)
+ le->pos.trType = TR_GRAVITY;
+ else
+ le->pos.trType = TR_LINEAR;
+ le->pos.trTime = cg.time;
+ le->pos.trDuration = killTime;
+
+ if (elasticity > 0)
+ {
+ le->leFlags |= LEF_USE_COLLISION;
+ le->bounceFactor = elasticity;
+ }
+ }
+
+ return(le);
+}
+
+/*
+===============
+FX_AddSpawner
+
+Adds a spawner -- basically, a local entity with a think function. Spawners don't have any rendered entities
+associated with them inherently, but the spawner's think fn probably generates them.
+===============
+*/
+localEntity_t *FX_AddSpawner( vec3_t origin, vec3_t dir, vec3_t velocity, vec3_t user, qboolean gravity, int delay,
+ float variance, float killTime, qboolean (*thinkFn)(localEntity_t *le), int radius )
+{
+ localEntity_t *le = NULL;
+
+ if (NULL == thinkFn)
+ {
+ // a spawner with no think fn is silly. and useless.
+ return NULL;
+ }
+ le = CG_AllocLocalEntity();
+
+ le->leType = LE_SPAWNER;
+
+ le->data.spawner.data1 = radius;
+ le->data.spawner.delay = delay;
+ le->data.spawner.nextthink = cg.time + delay;
+ le->startTime = cg.time;
+ // if we want the spawner to hang around forever, we use a killtime of 0 and the think fn keeps adjusting it.
+ //thing is, we still need it to not get culled right here, so give it an arbitrary endTime somewhere in the future.
+ if (0 == killTime)
+ {
+ le->endTime = le->startTime + 10000;
+ le->data.spawner.dontDie = qtrue;
+ }
+ else
+ {
+ le->endTime = le->startTime + killTime;
+ }
+
+ le->data.spawner.variance = variance;
+ if(dir)
+ VectorCopy(dir, le->data.spawner.dir);
+ // set origin
+ VectorCopy ( origin, le->refEntity.origin);
+ VectorCopy ( origin, le->refEntity.oldorigin );
+
+ // maybe employ the user variable here, like in singleplayer? or in the think fn?
+ le->color[0] = 1.0;
+ le->color[1] = 1.0;
+ le->color[2] = 1.0;
+ le->color[3] = 1.0;
+ le->lifeRate = 1.0 / ( le->endTime - le->startTime );
+
+ // wacky think function stuff
+ le->data.spawner.thinkFn = thinkFn;
+
+ if (velocity)
+ {
+ le->leFlags |= LEF_MOVE;
+ VectorCopy (origin, le->pos.trBase);
+ if(velocity)
+ VectorCopy (velocity, le->pos.trDelta);
+ if (gravity)
+ le->pos.trType = TR_GRAVITY;
+ else
+ le->pos.trType = TR_LINEAR;
+ le->pos.trTime = cg.time;
+ // we better not ever have a spawner with a velocity that we expect to last forever, so just plain
+ //assigning killTime here _should_ be ok
+ le->pos.trDuration = killTime;
+
+// if (elasticity > 0)
+// {
+// le->leFlags |= LEF_USE_COLLISION;
+// le->bounceFactor = elasticity;
+// }
+ }
+
+ return (le);
+}
+
+// provide the center of the circle, a normal out from it (normalized, please), and the radius.
+//out will then become a random position on the radius of the circle.
+void fxRandCircumferencePos(vec3_t center, vec3_t normal, float radius, vec3_t out)
+{
+ float rnd = flrandom(0, 2*M_PI);
+ float s = sin(rnd);
+ float c = cos(rnd);
+ vec3_t vTemp, radialX, radialY;
+
+ vTemp[0]=0.57735;
+ vTemp[1]=0.57735;
+ vTemp[2]=0.57735;
+ CrossProduct(normal, vTemp, radialX);
+ CrossProduct(normal, radialX, radialY);
+ VectorScale(radialX, radius, radialX);
+ VectorScale(radialY, radius, radialY);
+ VectorMA(center, s, radialX, out);
+ VectorMA(out, c, radialY, out);
+}
diff --git a/cgame/fx_local.h b/cgame/fx_local.h
new file mode 100644
index 0000000..f6c40c3
--- /dev/null
+++ b/cgame/fx_local.h
@@ -0,0 +1,197 @@
+
+#define DEFAULT_DEVIATION 0.5
+
+//
+// fx_*.c
+//
+
+void FXE_Spray (vec3_t direction, float speed, float variation, float cone, vec3_t velocity);
+localEntity_t *FX_AddLine(vec3_t start, vec3_t end, float stScale, float scale, float dscale,
+ float startalpha, float endalpha, float killTime, qhandle_t shader);
+localEntity_t *FX_AddLine2(vec3_t start, vec3_t end, float stScale, float width1, float dwidth1, float width2, float dwidth2,
+ float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float killTime, qhandle_t shader);
+localEntity_t *FX_AddOrientedLine(vec3_t start, vec3_t end, vec3_t normal, float stScale, float scale,
+ float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader);
+localEntity_t *FX_AddTrail( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength,
+ float scale, float dscale, float startalpha, float endalpha,
+ float elasticity, float killTime, qhandle_t shader);
+localEntity_t *FX_AddTrail2( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength,
+ float scale, float dscale, float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB,
+ float elasticity, float killTime, qhandle_t shader);
+localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float elasticity,
+ float killTime, qhandle_t shader);
+localEntity_t *FX_AddSprite2(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float roll, float elasticity,
+ float killTime, qhandle_t shader);
+localEntity_t *FX_AddBezier(vec3_t start, vec3_t end, vec3_t cpoint1, vec3_t cpoint2, vec3_t cpointvel1,
+ vec3_t cpointvel2,vec3_t cpointacc1, vec3_t cpointacc2, float width,
+ float killTime, qhandle_t shader);
+localEntity_t *FX_AddQuad( vec3_t origin, vec3_t normal, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float killTime, qhandle_t shader );
+localEntity_t *FX_AddQuad2( vec3_t origin, vec3_t normal, float scale, float dscale, float startalpha, float endalpha,
+ vec3_t startRGB, vec3_t endRGB, float roll, float killTime, qhandle_t shader );
+localEntity_t *FX_AddCylinder( vec3_t start,
+ vec3_t normal,
+ float height,
+ float dheight,
+ float scale,
+ float dscale,
+ float scale2,
+ float dscale2,
+ float startalpha,
+ float endalpha,
+ float killTime,
+ qhandle_t shader,
+ float bias );
+localEntity_t *FX_AddElectricity( vec3_t origin, vec3_t origin2, float stScale, float scale, float dscale,
+ float startalpha, float endalpha, float killTime, qhandle_t shader, float deviation );
+localEntity_t *FX_AddParticle( vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ float startalpha, float endalpha, float roll, float elasticity,
+ float killTime, qhandle_t shader, qboolean (*thinkFn)(localEntity_t *le));
+
+localEntity_t *FX_AddSpawner( vec3_t origin, vec3_t dir, vec3_t velocity, vec3_t user, qboolean gravity, int delay,
+ float variance, float killTime, qboolean (*thinkFn)(localEntity_t *le), int radius );
+
+//
+// phaser
+//
+
+void FX_PhaserFire( vec3_t start, vec3_t end, vec3_t normal, qboolean spark, qboolean impact, qboolean empty );
+void FX_PhaserAltFire( vec3_t start, vec3_t end, vec3_t normal, qboolean spark, qboolean impact, qboolean empty );
+
+
+//
+// compression rifle
+//
+
+void FX_CompressionShot( vec3_t start, vec3_t end );
+void FX_CompressionAltShot( vec3_t start, vec3_t end );
+void FX_CompressionExplosion( vec3_t start, vec3_t origin, vec3_t normal, qboolean altfire );
+void FX_CompressionHit( vec3_t origin );
+//void FX_CompressionHitWall( vec3_t origin, vec3_t dir );
+void FX_PrifleBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty );
+
+void FX_ProbeBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire );
+void FX_RegenBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire );
+
+//
+// imod
+//
+
+//void FX_IMODShot( vec3_t end, vec3_t start, vec3_t dir);
+//void FX_IMODExplosion( vec3_t origin, vec3_t normal );
+//void FX_AltIMODShot( vec3_t end, vec3_t start, vec3_t dir );
+//void FX_AltIMODExplosion( vec3_t origin, vec3_t normal );
+//
+// tetrion disruptor
+//
+//void FX_TetrionProjectileThink( centity_t *cent, const struct weaponInfo_s *wi );
+void FX_TetrionShot( vec3_t start, vec3_t forward );
+void FX_TetrionWeaponHitWall( vec3_t origin, vec3_t normal );
+//void FX_TetrionRicochet( vec3_t origin, vec3_t normal );
+//void FX_TetrionAltHitWall( vec3_t origin, vec3_t normal );
+void FX_TetrionAltHitPlayer( vec3_t origin, vec3_t normal );
+//
+// Scavenger Rifle
+//
+void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red );
+//void FX_ScavengerProjectileThink( centity_t *ent, const weaponInfo_t *wi );
+//void FX_ScavengerAltFireThink( centity_t *ent, const weaponInfo_t *wi );
+//void FX_ScavengerWeaponHitWall( vec3_t origin, vec3_t normal, qboolean fired_by_NPC );
+//void FX_ScavengerWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean fired_by_NPC );
+//void FX_ScavengerAltExplode( vec3_t origin, vec3_t dir );
+//
+// Grenade launcher
+//
+void FX_GrenadeThink( centity_t *cent, const struct weaponInfo_s *weapon );
+void FX_GrenadeHitWall( vec3_t origin, vec3_t normal );
+void FX_GrenadeHitPlayer( vec3_t origin, vec3_t normal );
+void FX_GrenadeExplode( vec3_t origin, vec3_t normal );
+void FX_GrenadeShrapnelExplode( vec3_t origin, vec3_t norm );
+void FX_GrenadeShrapnelBits( vec3_t start);
+void FX_fxfunc_Explosion( vec3_t start, vec3_t origin, vec3_t normal );
+void FX_fxfunc_Shot( vec3_t start, vec3_t dir );
+
+// Borg FX
+//void FX_BorgProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon );
+//void FX_BorgWeaponHitWall( vec3_t origin, vec3_t normal );
+//void FX_BorgTaser( vec3_t start, vec3_t end );
+//void FX_BorgEyeBeam( vec3_t start, vec3_t end, vec3_t normal, qboolean large );
+//void FX_BorgTeleport( vec3_t origin );
+//void FX_BorgTeleportTrails( vec3_t origin );// effect seen by other borg when you are in a mid-teleport
+
+//
+// detpack
+//
+
+void FX_Detpack(vec3_t origin);
+
+
+//
+// Stasis Weapon
+//
+//Disruptor
+void FX_DisruptorBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty );
+
+
+//void FX_StasisProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon );
+void FX_StasisWeaponHitWall( vec3_t origin, vec3_t dir, int size );
+//void FX_StasisWeaponHitPlayer( vec3_t origin, vec3_t dir, int size );
+//void FX_StasisShot( centity_t *cent, vec3_t end, vec3_t start );
+//void FX_StasisShotImpact( vec3_t end, vec3_t dir );
+//void FX_StasisShotMiss( vec3_t end, vec3_t dir );
+
+//
+// Quantum Burst
+//
+
+void FX_QuantumThink( centity_t *cent, const struct weaponInfo_s *weapon );
+void FX_QuantumAltThink( centity_t *cent, const struct weaponInfo_s *weapon );
+void FX_QuantumHitWall( vec3_t origin, vec3_t normal );
+void FX_QuantumAltHitWall( vec3_t origin, vec3_t normal );
+void FX_QuantumColumns( vec3_t origin );
+
+
+//
+// Dreadnought
+//
+
+//void FX_DreadnoughtHitWall( vec3_t origin, vec3_t normal, qboolean spark );
+//void FX_DreadnoughtFire( vec3_t origin, vec3_t end, vec3_t normal, qboolean spark, qboolean impact );
+//void FX_DreadnoughtProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon );
+//void FX_DreadnoughtShotMiss( vec3_t end, vec3_t dir );
+
+
+//
+// transporter
+//
+
+void FX_Transporter(vec3_t origin);
+void FX_TransporterPad( vec3_t origin );
+void FX_SPTransporterLensFlares( centity_t* cent, vec3_t headVector, int startTime );
+
+
+// Holdable, portable shield item
+void FX_DrawPortableShield(centity_t *cent);
+
+
+
+// Shield
+void FX_PlayerShieldHit( centity_t *cent );
+
+
+//
+// Miscellaneous FX
+//
+
+void FX_Disruptor( vec3_t org, float length );
+void FX_ExplodeBits( vec3_t org);
+
+void FX_qFlash( centity_t* cent, vec3_t org, int timeIndex );
+
+//
+// sin table
+//
+
+void fxRandCircumferencePos(vec3_t center, vec3_t normal, float radius, vec3_t out);
diff --git a/cgame/fx_misc.c b/cgame/fx_misc.c
new file mode 100644
index 0000000..6b26afc
--- /dev/null
+++ b/cgame/fx_misc.c
@@ -0,0 +1,286 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+
+/*
+-------------------------
+FX_Disruptor
+-------------------------
+*/
+void DisruptorShards(vec3_t org)
+{
+ vec3_t normal, end;
+
+ // Pick a random endpoint
+ VectorSet( normal, crandom(), crandom(), crandom() );
+ VectorNormalize( normal );
+
+ end[0] = org[0] + ( normal[0] * ( 48 + crandom() * 16 ));
+ end[1] = org[1] + ( normal[1] * ( 48 + crandom() * 16 ));
+ end[2] = org[2] + ( normal[2] * ( 64 + crandom() * 24 ));
+
+ // Draw a light shard, use a couple of different kinds so it doesn't look too homogeneous
+ if( rand() & 1 )
+ {
+ FX_AddLine( org, end, 1.0, random() * 0.5 + 0.5, 12.0, random() * 0.1 + 0.1, 0.0, 200 + random() * 350, cgs.media.orangeParticleShader );
+ }
+ else
+ {
+ FX_AddLine( org, end, 1.0, random() * 0.5 + 0.5, 12.0, random() * 0.1 + 0.1, 0.0, 200 + random() * 350, cgs.media.yellowParticleShader );
+ }
+}
+
+qboolean MakeDisruptorShard( localEntity_t *le )
+{
+ DisruptorShards(le->refEntity.origin);
+ return(qtrue);
+}
+
+// Effect used when scav beams in--this wouldn't work well for a scav on the ground if they were to beam out
+void FX_Disruptor( vec3_t org, float length )
+{//FIXME: make it move with owner?
+ vec3_t org1, org2, normal={0,0,1};
+ int t;
+
+ VectorMA( org, 48, normal, org1 );
+ VectorMA( org, -48, normal, org2 );
+
+ // This is the core
+ FX_AddLine( org1, org2, 1.0, 0.1, 48.0, 1.0, 0.0, length, cgs.media.dkorangeParticleShader );
+
+ // Spawn a bunch to get the effect going
+ for (t=0; t < 12; t++ )
+ {
+ DisruptorShards( org);
+ }
+
+ // Keep spawning the light shards for a while.
+ FX_AddSpawner( org, normal, NULL, NULL, qfalse, 20, 10, length*0.75, MakeDisruptorShard, 0);
+}
+
+
+
+
+void FX_EnergyGibs(vec3_t origin )
+{
+ localEntity_t *le;
+ refEntity_t *re;
+ vec3_t dir;
+ int i, j, k;
+ int chunkModel=0;
+ float baseScale = 0.7f, dist;
+ int numChunks;
+
+ numChunks = irandom( 10, 15 );
+
+ VectorSubtract(cg.snap->ps.origin, origin, dir);
+ dist = VectorLength(dir);
+ if (dist > 512)
+ {
+ numChunks *= 512.0/dist; // 1/2 at 1024, 1/4 at 2048, etc.
+ }
+
+ for ( i = 0; i < numChunks; i++ )
+ {
+ chunkModel = cgs.media.chunkModels[MT_METAL][irandom(0,5)];
+
+ le = CG_AllocLocalEntity();
+ re = &le->refEntity;
+
+ le->leType = LE_FRAGMENT;
+ le->endTime = cg.time + 2000;
+
+ VectorCopy( origin, re->origin );
+
+ for ( j = 0; j < 3; j++ )
+ {
+ re->origin[j] += crandom() * 12;
+ }
+ VectorCopy( re->origin, le->pos.trBase );
+
+ //Velocity
+ VectorSet( dir, crandom(), crandom(), crandom() );
+ VectorScale( dir, flrandom( 300, 500 ), le->pos.trDelta );
+
+ //Angular Velocity
+ VectorSet( le->angles.trBase, crandom() * 360, crandom() * 360, crandom() * 360 );
+ VectorSet( le->angles.trDelta, crandom() * 90, crandom() * 90, crandom() * 90 );
+
+ AxisCopy( axisDefault, re->axis );
+
+ le->data.fragment.radius = flrandom(baseScale * 0.4f, baseScale * 0.8f );
+
+ re->nonNormalizedAxes = qtrue;
+ re->hModel = chunkModel;
+ re->renderfx |= RF_CAP_FRAMES;
+ re->customShader = cgs.media.quantumDisruptorShader;
+ re->shaderTime = cg.time/1000.0f;
+
+ le->pos.trType = TR_GRAVITY;
+ le->pos.trTime = cg.time;
+ le->angles.trType = TR_INTERPOLATE;
+ le->angles.trTime = cg.time;
+ le->bounceFactor = 0.2f + random() * 0.2f;
+ le->leFlags |= LEF_TUMBLE;
+
+ re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 255;
+
+ // Make sure that we have the desired start size set
+ for( k = 0; k < 3; k++)
+ {
+ VectorScale(le->refEntity.axis[k], le->data.fragment.radius, le->refEntity.axis[k]);
+ }
+ }
+}
+
+
+
+void FX_ExplodeBits( vec3_t org)
+{
+ float width, length;
+ vec3_t vel, pos;
+ int i;
+
+ FX_EnergyGibs(org);
+
+ for (i = 0; i < 32; i++)
+ {
+ VectorSet(vel, flrandom(-320,320), flrandom(-320,320), flrandom(-100,320));
+ VectorCopy(org, pos);
+ pos[2] += flrandom(-8, 8);
+ length = flrandom(10,20);
+ width = flrandom(2.0,4.0);
+ FX_AddTrail( pos, vel, qtrue, length, -length, width, -width,
+ 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader);
+ }
+}
+
+#define Q_FLASH_SIZE 110
+
+void FX_qFlash( centity_t* cent, vec3_t org, int timeIndex ) {
+ trace_t tr;
+ refEntity_t flare;
+ float frac;
+
+ if ( cg.predictedPlayerState.clientNum != cent->currentState.clientNum ) {
+ CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL,
+ cent->lerpOrigin, cg.predictedPlayerState.clientNum, CONTENTS_SOLID );
+ if ( tr.fraction != 1 ) {
+ return;
+ }
+ }
+
+ memset( &flare, 0, sizeof( flare ) );
+
+ flare.reType = RT_SPRITE;
+ flare.shaderRGBA[0] = 0xff;
+ flare.shaderRGBA[1] = 0xff;
+ flare.shaderRGBA[2] = 0xff;
+ flare.shaderRGBA[3] = 0xff;
+
+ flare.data.sprite.rotation = 0;
+ flare.nonNormalizedAxes = qtrue; //needed for effective scaling
+
+ flare.customShader = cgs.media.qFlashSprite;
+
+ flare.renderfx |= RF_DEPTHHACK;
+
+ VectorCopy( org, flare.origin );
+
+ //find the basic ratio
+ frac = (float)(cg.time - timeIndex) / (float)( Q_FLASH_TIME );
+ //apply a sine function to it to make it less linear
+ //calculated using the fine graph prog @ http://math.umn.edu/~garrett/a08/Graph.html
+ frac = ( 0.65f * sin( 4.5f * frac - 0.6f ) + 0.35f );
+
+ frac = Com_Clamp( 0.0f, 1.0f, frac );
+
+ //CG_Printf( "%f\n", frac );
+
+ flare.data.sprite.radius = (float)Q_FLASH_SIZE * frac;
+
+ trap_R_AddRefEntityToScene( &flare );
+}
+
+#define PROBE_BEAM_LENGTH 32
+//TiM - Beam FX for the Neutrino Probe weapon
+void FX_ProbeBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire )
+{
+ trace_t tr;
+ refEntity_t beam;
+ vec3_t end;
+ float scale;
+
+ memset( &beam, 0, sizeof( beam ) );
+
+ if ( alt_fire )
+ scale = flrandom(7.0f, 12.0f);
+ else
+ scale = Q_fabs( 12.0f * sin( cg.time * 0.1f ) );
+
+ VectorMA( origin, PROBE_BEAM_LENGTH, dir, end );
+
+ CG_Trace( &tr, origin, NULL, NULL, end, clientNum, CONTENTS_SOLID );
+
+ trap_R_AddLightToScene( origin, 20, 114.0f / 255, 164.0f / 255, 1.0f );
+
+ VectorCopy( origin, beam.origin);
+ VectorCopy( tr.endpos, beam.oldorigin );
+ beam.reType = RT_LINE;
+ beam.customShader = cgs.media.probeBeam;
+ beam.shaderRGBA[0] = 0xff;
+ beam.shaderRGBA[1] = 0xff;
+ beam.shaderRGBA[2] = 0xff;
+ beam.shaderRGBA[3] = 0xff;
+ AxisClear( beam.axis );
+
+ beam.data.line.width = scale*0.1;
+ beam.data.line.width2 = scale;
+ beam.data.line.stscale = 1.0;
+ trap_R_AddRefEntityToScene( &beam );
+
+ if ( tr.fraction != 1.0f )
+ {
+ float radius;
+
+ if ( alt_fire )
+ radius = flrandom(1.5f, 3.0f) * (1.0 - (tr.fraction*0.3));
+ else
+ radius = flrandom(0.5f, 1.5f) * (1.0 - (tr.fraction*0.3));
+
+ if ( !radius )
+ return;
+
+ CG_ImpactMark( cgs.media.probeDecal, tr.endpos, tr.plane.normal, 0, 1, 1, 1, 0.2*(1.0-tr.fraction), qfalse, radius, qtrue );
+ trap_R_AddLightToScene( origin, radius*5, 114.0f / 255, 164.0f / 255, 1.0f );
+ }
+}
+
+#define REGEN_BEAM_LENGTH 64
+void FX_RegenBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire )
+{
+ trace_t tr;
+ vec3_t end;
+
+ VectorMA( origin, REGEN_BEAM_LENGTH, dir, end );
+
+ CG_Trace( &tr, origin, NULL, NULL, end, clientNum, CONTENTS_SOLID );
+
+ trap_R_AddLightToScene( origin, 30, 235.0f / 255, 74.0f / 255, 102.0f / 255 );
+
+ if ( tr.fraction != 1.0f )
+ {
+ float radius;
+
+ if ( alt_fire )
+ radius = flrandom(1.5f, 3.0f) * (1.0 - (tr.fraction*0.3));
+ else
+ radius = flrandom(0.5f, 1.5f) * (1.0 - (tr.fraction*0.3));
+
+ if ( !radius )
+ return;
+
+ CG_ImpactMark( cgs.media.regenDecal, tr.endpos, tr.plane.normal, 0, 1, 1, 1, 0.2*(1.0-tr.fraction), qfalse, radius, qtrue );
+ trap_R_AddLightToScene( origin, radius*5, 235.0f / 255, 74.0f / 255, 102.0f / 255 );
+ }
+}
diff --git a/cgame/fx_phaser.c b/cgame/fx_phaser.c
new file mode 100644
index 0000000..a8b94e0
--- /dev/null
+++ b/cgame/fx_phaser.c
@@ -0,0 +1,357 @@
+//Phaser
+
+#include "cg_local.h"
+#include "fx_local.h"
+
+/*
+-------------------------
+FX_PhaserFire
+-------------------------
+*/
+
+lensFlare_t phaserFlare = { {0.0,0.0,0.0},
+ 20,
+ 20,
+ {1.0, 0.7, 0.13},
+ 1.2,
+ 1.5,
+ 20,
+ 300,
+ {0.0, 0.0, 0.0},
+ 20,
+ 300,
+ 80,
+ 5,
+ qfalse,
+ 5,
+ 40,
+ qfalse,
+ qfalse,
+ qtrue,
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0,
+ qtrue };
+
+void FX_PhaserFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty )
+{
+ refEntity_t beam;
+ sfxHandle_t sfx;
+ float size;
+ vec3_t velocity;
+ int sparks;
+ vec3_t rgb = { 1,0.9,0.6}, rgb2={1,0.3,0};
+
+ //vec3_t rgb3 = { 1.0, 1.0, 1.0 };
+
+ sfx = 0;
+
+ // Draw beam first.
+ memset( &beam, 0, sizeof( beam ) );
+
+ VectorCopy( startpos, beam.origin);
+ VectorCopy( endpos, beam.oldorigin );
+ beam.reType = RT_LINE;
+ if (empty)
+ {
+ beam.customShader = cgs.media.phaserEmptyShader;
+ }
+ else
+ {
+ beam.customShader = cgs.media.phaserShader;
+ }
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff;
+ beam.shaderRGBA[1] = 0xff;
+ beam.shaderRGBA[2] = 0xff;
+ beam.shaderRGBA[3] = 0xff;
+ if (empty)
+ {
+ beam.data.line.width = 1.0f + ( crandom() * 0.6f );
+ }
+ else
+ {
+ beam.data.line.width = 2.0f + ( crandom() * 0.6f );
+ }
+ beam.data.line.stscale = 5.0;
+ trap_R_AddRefEntityToScene( &beam );
+
+ // Now draw the hit graphic
+
+ // no explosion at LG impact, it is added with the beam
+
+ if ( sfx )
+ {
+ Com_Printf("playing %s\n", "phaser sound");
+ trap_S_StartSound( endpos, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
+ }
+
+ //
+ // impact mark
+ //
+ if (impact)
+ {
+ if (!empty)
+ { // normal.
+ CG_ImpactMark( cgs.media.scavMarkShader, endpos, normal, random()*360, 1,1,1,0.2, qfalse,
+ random() + 1, qfalse );
+
+ //VectorCopy( endpos, phaserFlare.worldCoord );
+
+ /*CG_InitLensFlare( endpos,
+ 80,
+ 80,
+ rgb,
+ 1.2,
+ 1.5,
+ 1600,
+ 200,
+ colorTable[CT_BLACK],
+ 1600,
+ 200,
+ 80,
+ 5,
+ qfalse,
+ 5,
+ 40,
+ qfalse,
+ qfalse,
+ qfalse,
+ 1.0,
+ 1.0,
+ 200.0,
+ 200.0,
+ 200.0 );*/
+
+ //CG_InitLensFlare( endpos,
+ // 30, 30,
+ // rgb, 1.2, 2.0, 1600, 200,
+ // colorTable[CT_BLACK], 1600, 200, 410, 15, qfalse,
+ // 0, 0, qfalse, qtrue,
+ // qfalse, 1.0, cg.time, 0, 0, 50);
+
+ //TiM : Add your basic cheesy 'seen-way-too-much-in-movies-these-days' anamorphic lens streak :)
+ //CG_DrawLensFlare( &phaserFlare );
+ //FX_AddSprite( endpos, NULL, qfalse, random() * 1.25 + 5.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 50.0, cgs.media.blueParticleStreakShader ); //1.5f
+
+ //FX_AddQuad2( endpos, normal, random() * 1.25 + 8.0f, 0.0f, 1.0f, 1.0f, rgb3, rgb3, 270, 50.0, cgs.media.blueParticleStreakShader );
+ //eh... looked bad :P
+
+ FX_AddQuad2( endpos, normal, random() * 1.25 + 1.5f, 0.0f, 1.0f, 0.0f, rgb, rgb2, rand() % 360, 500 + random() * 200,
+ cgs.media.sunnyFlareShader );
+ }
+ else
+ { // Wuss hit when empty.
+ FX_AddQuad2( endpos, normal, random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200,
+ cgs.media.sunnyFlareShader );
+ }
+ }
+
+ // "Fun" sparks... Not when empty.
+ if ( spark && !empty)
+ {
+ sparks = rand() & 1 + 1;
+ for(;sparks>0;sparks--)
+ {
+ size = 0.2f + (random() * 0.4);
+ FXE_Spray( normal, 200, 75, 0.8f, velocity);
+ if (rand() & LEF_USE_COLLISION)
+ { // This spark bounces.
+ FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f,
+ size, -size, 1.0f, 0.5f, 0.4f, 500.0f, cgs.media.sparkShader);
+ }
+ else
+ {
+ FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f,
+ size, -size, 1.0f, 0.5f, 0.0, 500.0f, cgs.media.sparkShader);
+ }
+ }
+ }
+}
+
+/*
+-------------------------
+FX_PhaserAltFire
+-------------------------
+*/
+
+#define PHASER_ALT_CONE_LEN 256
+
+void FX_PhaserAltFire( vec3_t start, vec3_t end, vec3_t normal, qboolean spark, qboolean impact, qboolean empty )
+{
+ float scale = flrandom(13.0f, 17.0f), scale2 = flrandom(2.0f, 6.0f);
+ vec3_t vel, diff, end2;
+ int i = 0, sparks = 0;
+ refEntity_t beam;
+ vec3_t rgb = { 1,0.6,0.5}, rgb2={1,0.3,0};
+ float len;
+ int color;
+
+ VectorSubtract(end, start, diff);
+ len = VectorNormalize(diff);
+
+ color = 0xff * flrandom(0.75, 1.0);
+
+ if (empty)
+ { // More faint and shaky line.
+ scale *= flrandom(0.25,0.75);
+ }
+
+ if (len > PHASER_ALT_CONE_LEN)
+ { // Draw beam in two parts...
+
+ // Draw main beam first.
+ VectorMA(start, PHASER_ALT_CONE_LEN, diff, end2);
+
+ // Draw starting cone
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( start, beam.origin);
+ VectorCopy( end2, beam.oldorigin );
+ beam.reType = RT_LINE2;
+ if (empty)
+ {
+ beam.customShader = cgs.media.phaserAltEmptyShader;
+ }
+ else
+ {
+ beam.customShader = cgs.media.phaserAltShader;
+ }
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff;
+ beam.shaderRGBA[1] = 0xff*0.3;
+ beam.shaderRGBA[2] = 0;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.line.width = scale*0.1;
+ beam.data.line.width2 = scale;
+ beam.data.line.stscale = 1.0;
+ trap_R_AddRefEntityToScene( &beam );
+
+ // Draw big thick normal beam for the rest.
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( end2, beam.oldorigin);
+ VectorCopy( end, beam.origin );
+ beam.reType = RT_LINE;
+ if (empty)
+ {
+ beam.customShader = cgs.media.phaserAltEmptyShader;
+ }
+ else
+ {
+ beam.customShader = cgs.media.phaserAltShader;
+ }
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff;
+ beam.shaderRGBA[1] = 0xff*0.3;
+ beam.shaderRGBA[2] = 0;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.line.width = scale;
+ beam.data.line.stscale = 1.0;
+ trap_R_AddRefEntityToScene( &beam );
+
+ // Draw beam core, all one bit.
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( start, beam.origin);
+ VectorCopy( end, beam.oldorigin );
+ beam.reType = RT_LINE2;
+ beam.customShader = cgs.media.phaserShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = color*0.75f;
+ beam.shaderRGBA[1] = 0xff*0.5f;
+ beam.shaderRGBA[2] = 0xff*0.5f;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.line.width = scale2*0.2;
+ beam.data.line.width2 = scale2;
+ beam.data.line.stscale = 1.0;
+ trap_R_AddRefEntityToScene( &beam );
+ }
+ else
+ { // Draw beam in two parts...
+ // Draw beam first.
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( start, beam.origin);
+ VectorCopy( end, beam.oldorigin );
+ beam.reType = RT_LINE2;
+ beam.customShader = cgs.media.phaserAltShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff;
+ beam.shaderRGBA[1] = 0xff*0.3;
+ beam.shaderRGBA[2] = 0;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.line.width = scale*0.1;
+ beam.data.line.width2 = scale;
+ beam.data.line.stscale = 1.0;
+ trap_R_AddRefEntityToScene( &beam );
+
+ // just one beam is never enough
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( start, beam.origin);
+ VectorCopy( end, beam.oldorigin );
+ beam.reType = RT_LINE2;
+ beam.customShader = cgs.media.phaserShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = color*0.75f;
+ beam.shaderRGBA[1] = 0xff*0.5f;
+ beam.shaderRGBA[2] = 0xff*0.5f;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.line.width = scale2*0.2;
+ beam.data.line.width2 = scale2;
+ beam.data.line.stscale = 1.0;
+ trap_R_AddRefEntityToScene( &beam );
+ }
+
+
+ // Phaser beam
+// FX_AddLine( start, end, 1.0f, scale, 0.0f, 0.9f, 0.9f, 2, cgs.media.phaserShader );
+// FX_AddLine( start, end, 1.0f, scale * 0.5f, 0.0f, 0.8f, 0.8f, 2, cgs.media.phaserShader );
+
+ // Per frame impact mark
+ FX_AddQuad( end, normal, random() * 1.5 + 1.75f, 0.0f, 1.0f, 0.0f, 0.0f, 1, cgs.media.sparkShader );
+ FX_AddQuad( end, normal, random() * 5 + 2.75f, 0.0f, 1.0f, 0.0f, 0.0f, 1, cgs.media.yellowParticleShader );
+
+ // Multi frame impacts--never do this when it hits a player because it will just look stupid
+ if ( impact )
+ {
+ FX_AddQuad2( end, normal, random() * 2.0 + 5.0f, 2.5f, 0.6f, 0.0f, rgb, rgb2, 0.0f, 500 + random() * 200,
+ cgs.media.sunnyFlareShader );
+
+ CG_ImpactMark( cgs.media.scavMarkShader, end, normal, random()*360, 1,1,1,0.1, qfalse,
+ random() + 6.0, qfalse );
+ }
+
+ // "Fun" sparks
+ if ( spark )
+ {
+ // kef -- fixme. dunno what the deal is with this velocity vector
+ VectorClear(vel);
+ sparks = rand() & 3 + 1;
+
+ // Set random starting pos...
+ end2[0] = flrandom(-1.0, 1.0) + end[0];
+ end2[1] = flrandom(-1.0, 1.0) + end[1];
+ end2[2] = flrandom(-1.0, 1.0) + end[2];
+ for( i = 0; i < sparks; i++ )
+ {
+ scale = 0.5f + (random() * 0.5);
+ FXE_Spray( normal, 200, 75, 0.8f, /*1024*/vel);
+ FX_AddTrail2( end2, vel, qfalse,
+ 8.0f, -8.0f,
+ scale, -scale, 0.5f, 0.0f, rgb, rgb2, 0.4f, 500.0f, cgs.media.sparkShader );
+ }
+
+ VectorMA(end, -8, diff, end2);
+ // Add a hit sprite over everything...
+ memset( &beam, 0, sizeof( beam ) );
+ VectorCopy( end2, beam.origin);
+ beam.reType = RT_SPRITE;
+ beam.customShader = cgs.media.sunnyFlareShader;
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff*1.0f;
+ beam.shaderRGBA[1] = 0xff*0.9f;
+ beam.shaderRGBA[2] = 0xff*0.8f;
+ beam.shaderRGBA[3] = 0xff;
+ beam.data.sprite.radius = random()*2.0 + 9.0;
+ trap_R_AddRefEntityToScene( &beam );
+ }
+}
diff --git a/cgame/fx_quantum.c b/cgame/fx_quantum.c
new file mode 100644
index 0000000..1979153
--- /dev/null
+++ b/cgame/fx_quantum.c
@@ -0,0 +1,415 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+
+// think function for the quantum explosion particles
+//unused
+/*qboolean explosionTailThink(localEntity_t *le)
+{
+ refEntity_t *re = &le->refEntity;
+ float length = 20;
+
+ // leave a cool tail
+ CG_AddTrail(FX_AddTrail( re->origin,
+ le->data.particle.dir, qtrue,
+ length, 0,
+ le->data.particle.radius*0.8, le->data.particle.dradius,
+ 0.2, 0.0, // alpha, dalpha
+ 0,
+ 1,
+ cgs.media.yellowParticleShader ));
+
+ return qtrue;
+}*/
+
+/*
+-------------------------
+FX_QuantumThink
+-------------------------
+*/
+
+ /*vec3_t worldCoord;
+ int w1;
+ int h1;
+ vec3_t glowColor;
+ float glowOffset;
+ float hazeOffset;
+ int minDist;
+ int maxDist;
+ vec3_t streakColor;
+ int streakDistMin;
+ int streakDistMax;
+ int streakW;
+ int streakH;
+ qboolean whiteStreaks;
+ int reflecDistMin;
+ int reflecDistMax;
+ qboolean reflecAnamorphic;
+ qboolean defReflecs;
+ qboolean clamp;
+ float maxAlpha;
+ int startTime;
+ int upTime;
+ int holdTime;
+ int downTime;
+ qboolean qfull;
+
+static lensFlare_t quantumFlare = { {0.0, 0.0, 0.0}, 100, 100, // worldCoord, w1, h1
+ {0.0, 1.0, 1.0}, 1.1, 1.3, 1000, 40,//glowColor, glowOffset, hazeOffset, minDist, maxDist
+ {0.0, 1.0, 1.0}, 1000, 40, 600, 10, qtrue, //streakColor, sDistMin, sDistMax, sW, sH, whitestreaks
+ 0, 1, qfalse, qfalse, qfalse, //rDistMin, rDistMax, rAna, rDef, clamp
+ 1.0, 0, 0, 0, 0, qtrue }; //maxalpha, start, up, hold, down, full */
+
+void FX_QuantumThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+ vec3_t line1end, line2end, axis[3], rgb;
+
+ AxisClear( axis );
+
+ // convert direction of travel into axis
+ if ( VectorNormalize2( cent->currentState.pos.trDelta, axis[0] ) == 0 ) {
+ axis[0][2] = 1;
+ }
+
+ // spin as it moves
+ RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f );
+
+ VectorMA( cent->lerpOrigin, -24.0f, axis[1], line1end );
+ VectorMA( cent->lerpOrigin, 24.0f, axis[1], line2end );
+ FX_AddLine( line1end, line2end, 1.0f, random() * 6 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader );
+
+ AxisClear( axis );
+
+ // convert direction of travel into axis
+ if ( VectorNormalize2( cent->currentState.pos.trDelta, axis[0] ) == 0 ) {
+ axis[0][2] = 1;
+ }
+
+ // spin as it moves
+ RotateAroundDirection( axis, -cg.time * 0.3f );// * 1.25f );
+
+ VectorMA( cent->lerpOrigin, -48.0f, axis[2], line1end );
+ VectorMA( cent->lerpOrigin, 48.0f, axis[2], line2end );
+ FX_AddLine( line1end, line2end, 1.0f, random() * 5 + 2, 0.0f, 0.1 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader );
+
+ VectorSet( rgb, 1.0f, 0.45f, 0.15f ); // orange
+
+ FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.orangeParticleShader );
+ FX_AddSprite2(cent->lerpOrigin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader );
+ FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 16 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.yellowParticleShader );
+
+ /*
+ VectorCopy( cent->lerpOrigin, quantumFlare.worldCoord );
+ VectorCopy( colorTable[CT_CYAN], quantumFlare.glowColor );
+ VectorCopy( colorTable[CT_CYAN], quantumFlare.streakColor );
+
+ CG_DrawLensFlare( quantumFlare );*/
+}
+
+/*
+-------------------------
+FX_QuantumAltThink
+-------------------------
+*/
+void FX_QuantumAltThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+ vec3_t line1end, line2end, axis[3], vel, rgb;
+ float scale;
+
+ AxisClear( axis );
+
+ // convert direction of travel into axis
+ if ( VectorNormalize2( cent->currentState.pos.trDelta, axis[0] ) == 0 ) {
+ axis[0][2] = 1;
+ }
+
+ // spin as it moves
+ RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f );
+
+ VectorMA( cent->lerpOrigin, -48.0f, axis[1], line1end );
+ VectorMA( cent->lerpOrigin, 48.0f, axis[1], line2end );
+ FX_AddLine( line1end, line2end, 1.0f, random() * 6 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader );
+
+ VectorMA( cent->lerpOrigin, -48.0f, axis[2], line1end );
+ VectorMA( cent->lerpOrigin, 48.0f, axis[2], line2end );
+ FX_AddLine( line1end, line2end, 1.0f, random() * 5 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader );
+
+ VectorSet( rgb, 1.0f, 0.45f, 0.15f ); // orange
+
+ FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.orangeParticleShader );
+ FX_AddSprite2(cent->lerpOrigin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader );
+ FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 16 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.yellowParticleShader );
+
+ scale = ( 2.0f + cos( cg.time * ( M_PI * 0.001f * 4 ))) * 0.5f;
+
+ // Unlike the main fire, I'm leaving around this center core for a moment as a trail...
+ VectorScale( cent->currentState.pos.trDelta, 0.25f, vel );
+ FX_AddSprite( cent->lerpOrigin, NULL,qfalse,scale * 8 + 2, scale * -5.0f, 0.8f, 0.0f, 0, 0, 300.0f, cgs.media.sunnyFlareShader);
+
+ // Tack on a sprite trail so we can see the cool tracking at work.
+ VectorSet( vel, flrandom(-12, 12), flrandom(-12, 12), flrandom(-12, 12));
+ VectorMA( vel, 0.25f, cent->currentState.pos.trDelta, vel);
+
+ if ( rand() & 1 )
+ FX_AddSprite( cent->lerpOrigin, vel,qfalse,random() * 12.0f + scale * 14, -10, 0.2f + random() * 0.2f, 0.0, random()*360, 0, 800 + random() * 200.0f,
+ cgs.media.orangeRingShader );
+ else
+ FX_AddSprite2(cent->lerpOrigin, vel,qfalse,random() * 12.0f + scale * 14, -10, 0.5, 0.0, rgb, rgb, random()*360, 0, 800 + random() * 200.0f,
+ cgs.media.whiteRingShader );
+}
+
+/*
+-------------------------
+FX_QuantumHitWall
+-------------------------
+*/
+void FX_QuantumHitWall( vec3_t origin, vec3_t normal )
+{
+ localEntity_t *le = NULL;
+ vec3_t dir, org;
+ vec3_t vel;
+ float scale;
+ int i;
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_QUANTUM_BURST];
+
+ CG_InitLensFlare( origin,
+ 400, 400,
+ colorTable[CT_YELLOW], 1.2, 2.0, 1600, 200,
+ colorTable[CT_YELLOW], 1600, 200, 800, 35, qtrue,
+ 0, 0, qfalse, qtrue,
+ qfalse, 1.0, cg.time, 0, 0, 200);
+
+ for ( i = 0; i < 12; i++ )
+ {
+ VectorSet( dir, normal[0] + crandom() * 2, normal[1] + crandom() * 2, normal[2] + crandom() );
+ VectorNormalize( dir );
+ scale = random() * 300 + 300;
+ VectorScale( dir, scale, vel );
+ vel[2] += 300;
+ if ( rand() & 1 )
+ {
+ // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 300 + random() * 100, cgs.media.yellowParticleShader, explosionTailThink );
+ scale = random()*14+2;
+ // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile...
+ le=FX_AddSprite(origin, vel, qfalse, scale, -scale, 0.9, 0.5, 0.0, 0.0, 200 + random() * 100, cgs.media.yellowParticleShader);
+ // ...with a trail that overlaps it exactly.
+ FX_AddTrail(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, 0.0, 300, cgs.media.orangeTrailShader);
+ }
+ else
+ {
+ // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 450 + random() * 200, cgs.media.sunnyFlareShader, explosionTailThink );
+ scale = random()*14+6;
+ // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile...
+ le=FX_AddSprite(origin, vel, qfalse, scale, -scale, 0.9, 0.5, 0.0, 0.0, 350 + random() * 150, cgs.media.sunnyFlareShader);
+ // ...with a trail that overlaps it exactly.
+ FX_AddTrail(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, 0.0, 500, cgs.media.orangeTrailShader);
+ }
+ }
+ // Always face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, dir );
+ VectorNormalize( dir );
+
+ // Main explosion, tag with light
+
+ le = CG_MakeExplosion2( origin, normal, (qhandle_t)0, 1, cgs.media.quantumExplosionShader, 600, qtrue, 3 + crandom(), 0 );
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 1.0f, 1.0f, 0.6f );
+
+ // Create sphere
+ CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.quantumFlashShader, 150, qfalse, 4.6f + ( crandom() * 0.3f), 0 );
+
+ // Make an offset explosion
+ for ( i = 0; i < 3; i++ ) {
+ org[i] = origin[i] + crandom() * 4;
+ }
+
+ CG_MakeExplosion( org, dir, 0, cgs.media.quantumExplosionShader, 700, 1, qtrue );
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse,
+ random() * 16 + 48, qfalse );
+
+ CG_ExplosionEffects( origin, 3.0f, 256 );
+
+ // One big bright quick flash
+ FX_AddSprite( origin, NULL, qfalse, 100, -100, 1.0, 1.0, 0, 0, 300, cgs.media.sunnyFlareShader);
+
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound );
+}
+
+/*
+-------------------------
+FX_QuantumAltHitWall
+-------------------------
+*/
+void FX_QuantumAltHitWall( vec3_t origin, vec3_t normal )
+{
+ localEntity_t *le = NULL;
+ vec3_t dir, org;
+ vec3_t vel;
+ float scale;
+ int i;
+ vec3_t RGB={1.0, 0.6, 0.3}, RGB2={1.0, 0.3, 0.0};
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_QUANTUM_BURST];
+
+ CG_InitLensFlare( origin,
+ 500, 500,
+ colorTable[CT_YELLOW], 1.2, 2.0, 1600, 200,
+ colorTable[CT_YELLOW], 1600, 200, 800, 35, qtrue,
+ 1600, 200, qfalse, qfalse,
+ qfalse, 1.0, cg.time, 0, 0, 350);
+
+ for ( i = 0; i < 12; i++ )
+ {
+ VectorSet( dir, normal[0] + crandom() * 2, normal[1] + crandom() * 2, normal[2] + crandom() );
+ VectorNormalize( dir );
+ scale = random() * 500 + 500;
+ VectorScale( dir, scale, vel );
+ vel[2] += 300;
+ if ( rand() & 1 )
+ {
+ // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 300 + random() * 100, cgs.media.yellowParticleShader, explosionTailThink );
+ scale = random()*14+2;
+ // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile...
+ le=FX_AddSprite2(origin, vel, qfalse, scale, -scale, 0.9, 0.5, RGB, RGB2, 0.0, 0.0, 200 + random() * 100, cgs.media.yellowParticleShader);
+ // ...with a trail that overlaps it exactly.
+ FX_AddTrail2(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, RGB, RGB2, 0.0, 300, cgs.media.orangeTrailShader);
+ }
+ else
+ {
+ // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 450 + random() * 200, cgs.media.sunnyFlareShader, explosionTailThink );
+ scale = random()*14+6;
+ // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile...
+ le=FX_AddSprite2(origin, vel, qfalse, scale, -scale, 0.9, 0.5, RGB, RGB2, 0.0, 0.0, 350 + random() * 150, cgs.media.sunnyFlareShader);
+ // ...with a trail that overlaps it exactly.
+ FX_AddTrail2(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, RGB, RGB2, 0.0, 500, cgs.media.orangeTrailShader);
+ }
+ }
+ // Always face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, dir );
+ VectorNormalize( dir );
+
+ // Main explosion, tag with light
+
+ le = CG_MakeExplosion2( origin, normal, (qhandle_t)0, 1, cgs.media.quantumExplosionShader, 600, qtrue, 3 + crandom(), 0 );
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 1.0f, 1.0f, 0.6f );
+
+ // Create sphere
+ CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.quantumFlashShader, 150, qfalse, 5.4f + ( crandom() * 0.3f), 0 );
+
+ // Make an offset explosion
+ for ( i = 0; i < 3; i++ ) {
+ org[i] = origin[i] + crandom() * 4;
+ }
+
+ CG_MakeExplosion( org, dir, 0, cgs.media.quantumExplosionShader, 700, 1, qtrue );
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse,
+ random() * 16 + 48, qfalse );
+
+ CG_ExplosionEffects( origin, 3.0f, 256 );
+
+ // One big bright quick flash
+ FX_AddSprite( origin, NULL, qfalse, 200, -200, 1.0, 1.0, 0, 0, 400, cgs.media.sunnyFlareShader);
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound );
+}
+
+qboolean FX_QuantumSparkle( localEntity_t *le)
+{
+ int t, i;
+ vec3_t org, v;
+
+ for ( i = 0; i < 4; i ++ )
+ {
+ VectorCopy( le->refEntity.origin, org );
+
+ for ( t = 0; t < 3; t++ )
+ {
+ org[t] = le->refEntity.origin[t] + crandom() * 12;
+ v[t] = crandom() * 18.0f;
+ }
+
+ FX_AddSprite( org, v, qfalse, random() * 1 + 1, -4, 0.5f, 1.0f, 0.0f, 0.0f, 125 + random() * 100, cgs.media.yellowParticleShader);
+ }
+ return qtrue;
+}
+
+void FX_QuantumFizzles( vec3_t origin )
+{
+ float v;
+ vec3_t dir, vel, org;
+ int i;
+
+ for ( i = 0; i < 32; i++ )
+ {
+ v = random() * 6.0f + 6.0f;
+
+ VectorSet( dir, crandom(), crandom(), crandom() );
+ VectorNormalize( dir );
+ VectorScale( dir, v, vel );
+
+ org[0] = origin[0] + dir[0] * 48;
+ org[1] = origin[1] + dir[1] * 48;
+ org[2] = origin[2] + dir[2] * 64;
+
+ FX_AddSpawner( org, dir, vel, NULL, qfalse, 125, 10 + random() * 30, 200 + random() * 400, FX_QuantumSparkle, 1024 );
+ }
+}
+
+void FX_QuantumColumns( vec3_t origin )
+{
+ vec3_t dir, bottom, top;
+ vec3_t sizeMin = {-4, -4, -1};
+ vec3_t sizeMax = {-4, -4, 1};
+ trace_t trace;
+ localEntity_t *le;
+
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, dir );
+ VectorNormalize( dir );
+
+ //=== Sound ===
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.quantumBoom );
+
+ //=== columns ===
+ VectorCopy( origin, bottom );
+ bottom[2] -= 256;
+
+ trap_CM_BoxTrace( &trace, origin, bottom, sizeMin, sizeMax, 0, MASK_OPAQUE );
+ VectorCopy( trace.endpos, bottom );
+
+ VectorCopy( origin, top );
+ top[2] += 256;
+
+ trap_CM_BoxTrace( &trace, origin, top, sizeMin, sizeMax, 0, MASK_OPAQUE );
+ VectorCopy( trace.endpos, top );
+
+ //found floor and ceiling, now do columns and ring explosions:
+ //ceiling
+ VectorSet( dir, 0, 0, -1 );
+
+ le = FX_AddCylinder( top, dir, top[2] - origin[2], (origin[2] - top[2]), 40, 100, 20, 50, 1.0, 0.0, 1000, cgs.media.quantumRingShader, 1.5 );
+
+ le->refEntity.data.cylinder.wrap = qtrue;
+ le->refEntity.data.cylinder.stscale = 6;
+
+ //floor
+ VectorSet( dir, 0, 0, 1 );
+
+ le = FX_AddCylinder( bottom, dir, origin[2] - bottom[2], (bottom[2] - origin[2]), 40, 100, 20, 50, 1.0, 0.0, 1000, cgs.media.quantumRingShader, 1.5 );
+ le->refEntity.data.cylinder.wrap = qtrue;
+ le->refEntity.data.cylinder.stscale = 6;
+
+ FX_QuantumFizzles( origin );
+
+ // Main explosion, tag with light
+
+ le = CG_MakeExplosion2( origin, dir, (qhandle_t)0, 1, cgs.media.quantumExplosionShader, 600, qtrue, 3 + crandom(), 0 );
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 1.0f, 1.0f, 0.6f );
+
+
+}
diff --git a/cgame/fx_scavenger.c b/cgame/fx_scavenger.c
new file mode 100644
index 0000000..0b141f1
--- /dev/null
+++ b/cgame/fx_scavenger.c
@@ -0,0 +1,458 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+
+/*
+-------------------------
+FX_HypoSpray
+Redtechie: RPG-X Added
+FIXME! FIXME! FIXME! FIXME! Im not spraying in the direction some one shoots me!
+TiM: Fixed! An improperly formatted directional vector was being sent. it's all good now :)
+-------------------------
+*/
+
+#define NUM_HYPO_PUFFS 20
+
+void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red ) // When not red, it'll be blue
+{
+ vec3_t color, vel, accel, angles, work;
+ float scale, dscale;
+ int i;
+ localEntity_t *le;
+
+ vectoangles( dir, angles );
+
+ for ( i = 0; i < NUM_HYPO_PUFFS; i++ )
+ {
+ if ( red )
+ {
+ VectorSet( color, 1.0f, random() * 0.4f, random() * 0.4f ); // mostly red
+ }
+ else
+ {
+ VectorSet( color, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f ); // mostly blue
+ }
+
+ VectorCopy( angles, work );
+
+ work[0] += crandom() * 12.0f;
+ work[1] += crandom() * 12.0f;
+
+ AngleVectors( work, vel, NULL, NULL );
+
+ scale = random() * 256.0f + 128.0f;
+
+ VectorScale( vel, scale, vel );
+ VectorScale( vel, random() * -0.3f, accel );
+
+ scale = random() * 4.0f + 2.0f;
+ dscale = random() * 64.0f + 24.0f;
+
+ //localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale,
+ // float startalpha, float endalpha, float roll, float elasticity,
+ // float killTime, qhandle_t shader);
+
+ le = FX_AddSprite( origin, vel, qfalse, scale, dscale, 0.8f + random() * 0.2f, 0.0f, crandom() * 50, /*crandom() * 5*/0, 1000, cgs.media.steamShader );
+ VectorSet(le->data.sprite.startRGB, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f );// mostly blue
+ }
+}
+/*
+void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red ) // When not red, it'll be blue
+{
+ localEntity_t *le;
+ vec3_t color, vel, accel, angles, work, forward, right;
+ float scale, dscale;
+ int i;
+ //vectoangles( dir, angles );
+ VectorCopy( dir, angles );
+
+ //RPG-X: RedTechie - Debug print
+ //Com_Printf("dir: %f, %f, %f\nangles: %f, %f, %f\n\n",dir[0],dir[1],dir[2],angles[0],angles[1],angles[2]);
+
+ for ( i = 0; i < NUM_HYPO_PUFFS; i++ )
+ {
+ if ( red )
+ {
+ VectorSet( color, 1.0f, random() * 0.4f, random() * 0.4f ); // mostly red
+ }
+ else
+ {
+ VectorSet( color, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f ); // mostly blue
+ }
+
+ VectorCopy( angles, work );
+
+ work[0] += crandom() * 12.0f;
+ work[1] += crandom() * 12.0f;
+
+
+ scale = random() * 256.0f + 128.0f;
+ VectorScale( vel, scale, vel );
+ VectorScale( vel, random() * -0.3f, accel );
+
+ //scale = 30.0f + random() * 100.0f + 2.0f;
+ //dscale = 30.0f + random() * 400.0f + 24.0f;
+ scale = random() * 4.0f + 2.0f;
+ dscale = random() * 64.0f + 24.0f;
+
+ le = FX_AddSprite( origin, //vec3_t origin,
+ vel,//vel, //vec3_t velocity,
+ qfalse, // qboolean gravity,
+ scale,//scale, //float scale,
+ dscale, //float dscale,
+ 0.8f + random() * 0.2f, //float startalpha
+ 0.0f, //float endalpha
+ crandom() * 120,//crandom() * 120, ///float roll
+ 0.0f, //float elasticity
+ 4000, //float killTime -9999999 -1000
+ cgs.media.steamShader );//qhandle_t shader
+ //le->endTime = 99999999;
+ //le->color = color;
+ VectorSet(le->data.sprite.startRGB, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f );// mostly blue
+ //le->data.sprite.startRGB = color;
+ //le->data.sprite.dRGB = color;
+ //FX_AddSprite( origin, vel, qfalse, scale, dscale, 0.8f + random() * 0.2f, 0.0f, color, color, crandom() * 120, 0.0f, 1000, cgs.media.steamShader );
+ }
+}
+
+*/
+//RPG-X: J2J - Fixed Version (incomplete right now)
+/*void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red ) // When not red, it'll be blue
+{
+ localEntity_t *le;
+ vec3_t muzzle, /*mins, maxs, end, color, forward, right;
+ float scale, dscale;
+ int i;
+
+ // Move out to the end of the nozzle
+ //VectorMA( muzzle, 20, forward, muzzle );
+ //VectorMA( muzzle, 4, vright, muzzle );
+
+ VectorCopy(dir, muzzle);
+
+ AngleVectors( dir, forward, right, NULL );
+
+ for ( i = 0; i < NUM_HYPO_PUFFS; i++ )
+ {
+
+ if ( red )
+ {
+ VectorSet( color, 1.0f, random() * 0.4f, random() * 0.4f ); // mostly red
+ }
+ else
+ {
+ VectorSet( color, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f ); // mostly blue
+ }
+
+
+ VectorMA( muzzle, 24, forward, end );
+ VectorSet( maxs, 6, 6, 6 );
+ VectorScale( maxs, -1, mins );
+ // Create the effect -- thought something was needed here, but apparently not.
+ VectorMA( muzzle, 20, forward, muzzle );w
+ VectorMA( muzzle, 4, right, muzzle );
+
+
+ scale = random() + 2.0f;
+ dscale = random() * 64.0f + 24.0f;
+
+ muzzle[0] += (scale * cos(abs(dir[1]) * 0.017453292222222222222222222222222);
+ muzzle[1] += (scale * sin(abs(dir[1]) * 0.017453292222222222222222222222222);
+ muzzle[2] += (scale * -tan(dir[0] * 0.017453292222222222222222222222222);
+
+ VectorScale( muzzle, scale, muzzle );
+
+ le = FX_AddSprite( origin, //vec3_t origin,
+ muzzle,//vel, //vec3_t velocity,
+ qfalse, // qboolean gravity,
+ scale,//scale, //float scale,
+ dscale, //float dscale,
+ 0.8f + random() * 0.2f, //float startalpha
+ 0.0f, //float endalpha
+ crandom() * 120,//crandom() * 120, ///float roll
+ 0.0f, //float elasticity
+ 4000, //float killTime -9999999 -1000
+ cgs.media.steamShader );//qhandle_t shader
+
+ VectorSet(le->data.sprite.startRGB, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f );// mostly blue
+
+
+ }
+
+ return;
+
+}*/
+
+//#define SCAV_SPIN 0.3
+
+/*void FX_ScavengerProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+ vec3_t forward;
+ qboolean fired_from_NPC = qfalse; // Always
+
+ if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0 )
+ forward[2] = 1;
+
+ // The effect you'd see from first person looks horrible from third person..or when shot by an NPC,
+ // so we'll just tone down the effect so it's not so horrible. :)
+ if ( fired_from_NPC )
+ {
+ // Energy glow
+ /*FX_AddSprite( cent->lerpOrigin,
+ NULL, qfalse,
+ 8.0f + random() * 8.0f, 0.0f,
+ 0.7f, 0.0f,
+ random()*360, 0.0f,
+ 1.0f,
+ cgs.media.tetrionFlareShader );
+
+ // Spinning projectile core
+ FX_AddSprite( cent->lerpOrigin,
+ NULL, qfalse,
+ 8.0f + random() * 10.0f, 0.0f,
+ 1.0f, 0.0f,
+ cg.time * SCAV_SPIN, 0.0f,
+ 1.0f,
+ cgs.media.redFlareShader );
+
+ // leave a cool tail
+ /*FX_AddTrail( cent->lerpOrigin,
+ forward, qfalse,
+ 16, 0,
+ 1.0f, 0.0f,
+ 0.8f, 0.0f,
+ 0,
+ 1,
+ cgs.media.tetrionTrail2Shader );
+ }
+ else
+ {
+ // Energy glow
+ /*FX_AddSprite( cent->lerpOrigin,
+ NULL, qfalse,
+ 16.0f + random() * 16.0f, 0.0f,
+ 0.5f, 0.0f,
+ random()*360, 0.0f,
+ 1.0f,
+ cgs.media.tetrionFlareShader );
+
+ // Spinning projectile
+ FX_AddSprite( cent->lerpOrigin,
+ NULL, qfalse,
+ 4.0f + random() * 10.0f, 0.0f,
+ 0.6f, 0.0f,
+ cg.time * SCAV_SPIN, 0.0f,
+ 1.0f,
+ cgs.media.redFlareShader );
+
+ // leave a cool tail
+ /*FX_AddTrail( cent->lerpOrigin,
+ forward, qfalse,
+ 64, 0,
+ 1.4f, 0.0f,
+ 0.6f, 0.0f,
+ 0,
+ 1,
+ cgs.media.tetrionTrail2Shader );
+ }
+}*/
+
+
+/*
+-------------------------
+FX_ScavengerAltFireThink
+-------------------------
+*/
+//#define SCAV_TRAIL_SPACING 12
+
+/*void FX_ScavengerAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+ vec3_t diff;
+ float len;
+
+ // Make a trail that's reasonably consistent and not so much based on frame rate.
+ if (cent->thinkFlag)
+ {
+ VectorSubtract( cent->lerpOrigin, cent->beamEnd, diff );
+ }
+ else
+ {
+ VectorSubtract( cent->lerpOrigin, cent->currentState.origin2, diff );
+ }
+
+ len = VectorNormalize( diff );
+
+ if ( len > SCAV_TRAIL_SPACING )
+ {
+ vec3_t origin;
+ int i;
+ float scale;
+
+ for ( i = 0 ; i < len; i += SCAV_TRAIL_SPACING )
+ {
+ // Calc the right spot along the trail
+ VectorMA( cent->lerpOrigin, -i, diff, origin );
+ scale = 18.0f + (random()*5.0f);
+ /*FX_AddSprite( origin,
+ NULL, qfalse,
+ scale, -8.75,
+ 0.4f, 0.0f,
+ random() * 360, 0.0f,
+ 250.0f,
+ cgs.media.scavengerAltShader );
+ }
+
+ // Stash the current position
+ VectorCopy( cent->lerpOrigin, cent->beamEnd);
+ cent->thinkFlag = 1;
+ }
+
+ // Glowing bit
+ /*FX_AddSprite( cent->lerpOrigin,
+ NULL, qfalse,
+ 24.0f + ( random() * 16.0f ), 0.0f,
+ 1.0f, 0.0f,
+ 0, 0.0f,
+ 1.0f,
+ cgs.media.tetrionFlareShader );
+}*/
+
+
+/*
+-------------------------
+FX_ScavengerWeaponHitWall
+-------------------------
+*/
+/*void FX_ScavengerWeaponHitWall( vec3_t origin, vec3_t normal, qboolean fired_by_NPC )
+{
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_COFFEE];
+
+ // Tone down when shot by an NPC
+ // FIXME: is this really a good idea?
+ if ( fired_by_NPC )
+ {
+ // Expanding shock ring
+ FX_AddQuad( origin, normal,
+ 0.5f, 6.4f,
+ 0.8, 0.0,
+ random() * 360.0f,
+ 200,
+ cgs.media.redRingShader );
+
+ // Impact core
+ FX_AddQuad( origin, normal,
+ 12.0f + ( random() * 8.0f ), 3.2f,
+ 0.6f, 0.0f,
+ cg.time * SCAV_SPIN,
+ 100,
+ cgs.media.redFlareShader );
+ }
+ else
+ {
+ // Expanding shock ring
+ FX_AddQuad( origin, normal,
+ 8.0f, 12.8f,
+ 1.0, 0.0,
+ random() * 360.0f,
+ 200,
+ cgs.media.redRingShader );
+
+ // Impact core
+ FX_AddQuad( origin, normal,
+ 24.0f + ( random() * 16.0f ), 6.4f,
+ 0.8f, 0.0f,
+ cg.time * SCAV_SPIN,
+ 100,
+ cgs.media.redFlareShader );
+ }
+
+ //Sound
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound);
+
+ CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, random() + 5.5f, qfalse );
+}*/
+
+
+/*
+-------------------------
+FX_ScavengerWeaponHitPlayer
+-------------------------
+*/
+/*void FX_ScavengerWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean fired_by_NPC )
+{
+ if ( fired_by_NPC )
+ {
+ // Smaller expanding shock ring
+ FX_AddQuad( origin, normal,
+ 0.5f, 32.0f,
+ 0.8, 0.0,
+ random() * 360.0f,
+ 125,
+ cgs.media.redRingShader );
+ }
+ else
+ {
+ // Expanding shock ring
+ FX_AddQuad( origin, normal,
+ 1.0f, 64.0f,
+ 0.8, 0.0,
+ random() * 360.0f,
+ 125,
+ cgs.media.redRingShader );
+ }
+
+ //Sound
+// trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cg_weapons[WP_COFFEE].missileHitSound );
+}*/
+
+
+
+/*
+-------------------------
+FX_Scavenger_Alt_Explode
+-------------------------
+*/
+/*void FX_ScavengerAltExplode( vec3_t origin, vec3_t dir )
+{
+// FXCylinder *fx2;
+ localEntity_t *le;
+ vec3_t direction, org;
+ int i;
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_COFFEE];
+
+ //Orient the explosions to face the camera
+ VectorSubtract( cg.refdef.vieworg, origin, direction );
+ VectorNormalize( direction );
+
+ VectorMA( origin, 12, direction, org );
+ // Add an explosion and tag a light to it
+ //le = CG_MakeExplosion2( org, direction, cgs.media.explosionModel, 5, cgs.media.scavExplosionSlowShader, 675, qfalse, 1.0f + (random()*0.5f), LEF_NONE);
+ le->light = 150;
+ le->refEntity.renderfx |= RF_NOSHADOW;
+ VectorSet( le->lightColor, 1.0f, 0.6f, 0.6f );
+
+ VectorSet( org, (org[0] + crandom() * 8), (org[1] + crandom() * 8), (org[2] + crandom() * 8) );
+ //CG_MakeExplosion2( org, direction, cgs.media.explosionModel, 5, cgs.media.scavExplosionFastShader, 375, qfalse, 0.7f + (random()*0.5f), LEF_NONE);
+
+ //Sound
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound );
+
+ CG_ImpactMark( cgs.media.compressionMarkShader, origin, dir, random()*360, 1,1,1,0.2, qfalse,
+ random() * 6 + 20, qfalse );
+
+ // Always orient horizontally
+ VectorSet ( direction, 0,0,1 );
+
+ le = FX_AddCylinder( origin, direction, 4, 0, 20, 210, 14, 140, 1.0, 0.0, 600, cgs.media.redRing2Shader, 1.5 );
+ le->refEntity.data.cylinder.wrap = qtrue;
+ le->refEntity.data.cylinder.stscale = 6;
+
+ for (i = 0; i < 6; i++)
+ {
+ vec3_t velocity;
+
+ FXE_Spray( dir, 300, 175, 0.8f, velocity);
+ /*FX_AddTrail( origin, velocity, qtrue, 12.0f, -12.0f,
+ 2, -2, 1.0f, 1.0f, 0.2f, 1000.0f, cgs.media.tetrionTrail2Shader);*/
+ /*}
+}*/
diff --git a/cgame/fx_stasis.c b/cgame/fx_stasis.c
new file mode 100644
index 0000000..835e93c
--- /dev/null
+++ b/cgame/fx_stasis.c
@@ -0,0 +1,617 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+void FX_StasisDischarge( vec3_t origin, vec3_t normal, int count, float dist_out, float dist_side );
+
+#define FX_STASIS_ALT_RIGHT_OFS 0.10
+#define FX_STASIS_ALT_UP_OFS 0.02
+#define FX_STASIS_ALT_MUZZLE_OFS 1
+#define FX_MAXRANGE_STASIS 4096
+
+/*
+-------------------------
+FX_StasisShot
+
+Alt-fire, beam that shrinks to its impact point
+-------------------------
+*/
+
+/*void FX_SmallStasisBeam(centity_t *cent, vec3_t start, vec3_t dir)
+{
+ vec3_t end, org, vel = { 0,0,-4};
+ trace_t tr;
+ float r;
+ int i, ct, t;
+
+ VectorMA(start, FX_MAXRANGE_STASIS, dir, end);
+ CG_Trace(&tr, start, NULL, NULL, end, cent->currentState.number, MASK_SHOT);
+
+ // Beam
+// FX_AddLine( start, tr.endpos, 1.0f, 3.0f, 4.0f, 0.8f, 0.0f, 400.0f, cgs.media.stasisAltShader);
+
+ // Do a quick LOD for number of decay particles
+ ct = tr.fraction * (FX_MAXRANGE_STASIS * 0.02);
+ if ( ct < 12 )
+ ct = 12;
+ else if ( ct > 24 )
+ ct = 24;
+
+ for ( i = 0; i < ct; i++ )
+ {
+ r = random() * tr.fraction * (FX_MAXRANGE_STASIS * 0.5);
+ VectorMA( start, r, dir, org );
+
+ for ( t = 0; t < 3; t++ )
+ org[t] += crandom();
+
+ if ( rand() & 1 )
+ FX_AddSprite( org, vel, qfalse, random() + 1.5, -3, 1.0, 1.0, 0.0, 0.0, 500, cgs.media.blueParticleShader);
+ else
+ FX_AddSprite( org, vel, qfalse, random() + 1.5, -3, 1.0, 1.0, 0.0, 0.0, 500, cgs.media.purpleParticleShader);
+ }
+
+ // Impact graphic if needed.
+ if (cg_entities[tr.entityNum].currentState.eType == ET_PLAYER)
+ { // Hit an entity.
+ // Expanding rings
+// FX_AddSprite( tr.endpos, NULL, qfalse, 1, 60, 0.8, 0.2, random() * 360, 0, 400, cgs.media.stasisRingShader );
+ // Impact effect
+// FX_AddSprite( tr.endpos, NULL, qfalse, 7, 25, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader );
+ FX_AddSprite( tr.endpos, NULL, qfalse, 5, 18, 1.0, 0.0, random() * 360, 0, 420, cgs.media.ltblueParticleShader );
+ }
+ else if (!(tr.surfaceFlags & SURF_NOIMPACT) )
+ {
+ // Move me away from the wall a bit so that I don't z-buffer into it
+ VectorMA( tr.endpos, 1.5, tr.plane.normal, end);
+
+ // Expanding rings
+// FX_AddQuad( end, tr.plane.normal, 1, 12, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader );
+// FX_AddQuad( end, tr.plane.normal, 1, 30, 0.8, 0.2, random() * 360, 300, cgs.media.stasisRingShader );
+ // Impact effect
+ FX_AddQuad( end, tr.plane.normal, 4, 16, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader );
+ FX_AddQuad( end, tr.plane.normal, 3, 12, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader );
+
+ CG_ImpactMark( cgs.media.scavMarkShader, end, tr.plane.normal, random()*360, 1,1,1,0.6, qfalse,
+ 5 + random() * 2, qfalse );
+ }
+
+ FX_AddSprite( tr.endpos, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader );
+
+ // Pass the end position back to the calling function (yes, I know).
+ VectorCopy(tr.endpos, dir);
+}*/
+
+
+// kef -- fixme. the altfire stuff really wants to use some endcap stuff and some other flags
+/*void FX_StasisShot( centity_t *cent, vec3_t end, vec3_t start )
+{
+ trace_t tr;
+ vec3_t fwd, newdir, org, vel = { 0,0,-4}, newstart, end2;
+ int i, t, ct;
+ float len, r;
+ vec3_t fwd2, right, up;
+ int bolt1, bolt2;
+ vec3_t bolt1vec, bolt2vec;
+ centity_t *traceEnt = NULL;
+ int clientNum = -1;
+
+ // Choose which bolt will have the electricity accent.
+ bolt1 = irandom(0,2);
+ bolt2 = irandom(0,4);
+
+ VectorSubtract( end, start, fwd );
+ len = VectorNormalize( fwd );
+
+ // Beam
+// FX_AddLine( end, start, 1.0f, 4.0f, 6.0f, 0.8f, 0.0f, 500.0f, cgs.media.stasisAltShader);
+
+ // Do a quick LOD for number of decay particles
+ ct = len * 0.03;
+ if ( ct < 16 )
+ ct = 16;
+ else if ( ct > 32 )
+ ct = 32;
+
+ for ( i = 0; i < ct; i++ )
+ {
+ r = random() * len * 0.5;
+ VectorMA( start, r, fwd, org );
+
+ for ( t = 0; t < 3; t++ )
+ org[t] += crandom();
+
+ if ( rand() & 1 )
+ FX_AddSprite( org, vel, qfalse, random() + 2, -4, 1.0, 1.0, 0.0, 0.0, 600, cgs.media.blueParticleShader);
+ else
+ FX_AddSprite( org, vel, qfalse, random() + 2, -4, 1.0, 1.0, 0.0, 0.0, 600, cgs.media.purpleParticleShader);
+ }
+ VectorMA(start, FX_MAXRANGE_STASIS, fwd, end2);
+ CG_Trace(&tr, start, NULL, NULL, end2, cent->currentState.number, MASK_SHOT);
+ if (!( tr.surfaceFlags & SURF_NOIMPACT ))
+ {
+ traceEnt = &cg_entities[tr.entityNum];
+ clientNum = traceEnt->currentState.clientNum;
+ if ( (tr.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) )
+ {
+ // hit a player
+ FX_StasisShotImpact(tr.endpos, tr.plane.normal);
+ }
+ else
+ {
+ // hit the world
+ FX_StasisShotMiss(tr.endpos, tr.plane.normal);
+ }
+ }
+ // cap the impact end of the main beam to hide the nasty end of the line
+ FX_AddSprite( tr.endpos, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader );
+
+ if (bolt1==0)
+ {
+ VectorCopy(end, bolt1vec);
+ }
+ else if (bolt2==0)
+ {
+ VectorCopy(end, bolt2vec);
+ }
+
+ AngleVectors(cent->currentState.angles, fwd2, right, up);
+
+// CrossProduct(fwd, up, right);
+// VectorNormalize(right); // "right" is scaled by the sin of the angle between fwd & up... Ditch that.
+// CrossProduct(right, fwd, up); // Change the "fake up" (0,0,1) to a "real up" (perpendicular to the forward vector).
+ // VectorNormalize(up); // If I cared about how the vertical variance looked when pointing up or down, I'd normalize this.
+
+ // Fire a shot up and to the right.
+ VectorMA(fwd, FX_STASIS_ALT_RIGHT_OFS, right, newdir);
+ VectorMA(newdir, FX_STASIS_ALT_UP_OFS, up, newdir);
+ VectorMA(start, FX_STASIS_ALT_MUZZLE_OFS, right, newstart);
+ FX_SmallStasisBeam(cent, newstart, newdir);
+
+ if (bolt1==1)
+ {
+ VectorCopy(newdir, bolt1vec);
+ }
+ else if (bolt2==1)
+ {
+ VectorCopy(newdir, bolt2vec);
+ }
+
+ // Fire a shot up and to the left.
+ VectorMA(fwd, -FX_STASIS_ALT_RIGHT_OFS, right, newdir);
+ VectorMA(newdir, FX_STASIS_ALT_UP_OFS, up, newdir);
+ VectorMA(start, -FX_STASIS_ALT_MUZZLE_OFS, right, newstart);
+ FX_SmallStasisBeam(cent, newstart, newdir);
+
+ if (bolt1==2)
+ {
+ VectorCopy(newdir, bolt1vec);
+ }
+ else if (bolt2==2)
+ {
+ VectorCopy(newdir, bolt2vec);
+ }
+
+ // Fire a shot a bit down and to the right.
+ VectorMA(fwd, 2.0*FX_STASIS_ALT_RIGHT_OFS, right, newdir);
+ VectorMA(newdir, -0.5*FX_STASIS_ALT_UP_OFS, up, newdir);
+ VectorMA(start, 2.0*FX_STASIS_ALT_MUZZLE_OFS, right, newstart);
+ FX_SmallStasisBeam(cent, newstart, newdir);
+
+ if (bolt1==3)
+ {
+ VectorCopy(newdir, bolt1vec);
+ }
+ else if (bolt2==3)
+ {
+ VectorCopy(newdir, bolt2vec);
+ }
+
+ // Fire a shot up and to the left.
+ VectorMA(fwd, -2.0*FX_STASIS_ALT_RIGHT_OFS, right, newdir);
+ VectorMA(newdir, -0.5*FX_STASIS_ALT_UP_OFS, up, newdir);
+ VectorMA(start, -2.0*FX_STASIS_ALT_MUZZLE_OFS, right, newstart);
+ FX_SmallStasisBeam(cent, newstart, newdir);
+
+ if (bolt1==4)
+ {
+ VectorCopy(newdir, bolt1vec);
+ }
+ else if (bolt2==4)
+ {
+ VectorCopy(newdir, bolt2vec);
+ }
+
+ // Put a big gigant-mo sprite at the muzzle end so people can't see the crappy edges of the line
+ FX_AddSprite( start, NULL, qfalse, random()*3 + 15, -20, 1.0, 0.5, 0.0, 0.0, 600, cgs.media.blueParticleShader);
+
+ // Do an electrical arc to one of the impact points.
+ FX_AddElectricity( start, bolt1vec, 0.2f, 15.0, -15.0, 1.0, 0.5, 100, cgs.media.dnBoltShader, 0.1 );
+
+ if (bolt1!=bolt2)
+ {
+ // ALSO do an electrical arc to another point.
+ FX_AddElectricity( bolt1vec, bolt2vec, 0.2f, 15.0, -15.0, 1.0, 0.5, flrandom(100,200), cgs.media.dnBoltShader, 0.5 );
+ }
+}*/
+
+/*
+-------------------------
+FX_StasisShotImpact
+
+Alt-fire, impact effect
+-------------------------
+*/
+/*void FX_StasisShotImpact( vec3_t end, vec3_t dir )
+{
+ vec3_t org;
+
+ // Move me away from the wall a bit so that I don't z-buffer into it
+ VectorMA( end, 1.5, dir, org );
+
+ // Expanding rings
+// FX_AddQuad( org, dir, 1, 80, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader );
+ // Impact effect
+ FX_AddQuad( org, dir, 7, 35, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader );
+ FX_AddQuad( org, dir, 5, 25, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader );
+
+// CG_ImpactMark( cgs.media.scavMarkShader, org, dir, random()*360, 1,1,1,0.6, qfalse,
+// 8 + random() * 2, qfalse );
+
+// FX_StasisDischarge( org, dir, irandom( 2,4 ), 24 + random() * 12, 64 + random() * 48 );
+}*/
+
+/*
+-------------------------
+FX_StasisShotMiss
+
+Alt-fire, miss effect
+-------------------------
+*/
+/*void FX_StasisShotMiss( vec3_t end, vec3_t dir )
+{
+ vec3_t org;
+
+ // Move me away from the wall a bit so that I don't z-buffer into it
+ VectorMA( end, 0.5, dir, org );
+
+ // Expanding rings
+// FX_AddQuad( org, dir, 1, 16, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader );
+// FX_AddQuad( org, dir, 1, 40, 0.8, 0.2, random() * 360, 300, cgs.media.stasisRingShader );
+ // Impact effect
+ FX_AddQuad( org, dir, 5, 25, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader );
+ FX_AddQuad( org, dir, 4, 17, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader );
+
+ CG_ImpactMark( cgs.media.scavMarkShader, org, dir, random()*360, 1,1,1,0.6, qfalse,
+ 6 + random() * 2, qfalse );
+
+ FX_AddSprite( end, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader );
+
+// FX_StasisDischarge( org, dir, irandom( 2,4 ), 24 + random() * 12, 64 + random() * 48 );
+}*/
+
+/*
+-------------------------
+FX_StasisProjectileThink
+
+Main fire, with crazy bits swirling around main projectile
+Hehe used to :D -TiM
+-------------------------
+*/
+//unused
+/*void FX_StasisProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon )
+{
+ int size = 0;
+ vec3_t forward;
+ //vec3_t right, up;
+ //float radius, temp;
+ vec3_t org;
+
+ if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0 )
+ forward[2] = 1;
+
+ VectorCopy( cent->lerpOrigin, org );
+
+// org[0] -=32;
+
+//FX_AddTrail(
+
+ FX_AddTrail( org, forward, qfalse, 64, 0, 30.4f, 0.0f, 0.6f, 0.0f, 0, 1, cgs.media.disruptorBolt );
+
+ FX_AddSprite( cent->lerpOrigin, NULL, qfalse, 25.0f, 0.0f, 0.7f, 0.0f, 1.0f, 0.0f, 1.0f, cgs.media.disruptorStreak );
+
+
+}*/
+
+/*
+-------------------------
+FX_OrientedBolt
+
+Creates new bolts for a while
+-------------------------
+*/
+
+void FX_OrientedBolt( vec3_t start, vec3_t end, vec3_t dir )
+{
+ vec3_t mid;
+
+ VectorSubtract( end, start, mid );
+ VectorScale( mid, 0.1f + (random() * 0.8), mid );
+ VectorAdd( start, mid, mid );
+ VectorMA(mid, 3.0f + (random() * 10.0f), dir, mid );
+
+ //FX_AddElectricity( mid, start, 0.5, 0.75 + random() * 0.75, 0.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.bolt2Shader, DEFAULT_DEVIATION);
+ //FX_AddElectricity( mid, end, 0.5, 0.75 + random() * 0.75, 1.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.bolt2Shader, DEFAULT_DEVIATION);
+
+ FX_AddElectricity( mid, start, 0.5, 0.75 + random() * 0.75, 0.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.borgLightningShaders[2], DEFAULT_DEVIATION);
+ FX_AddElectricity( mid, end, 0.5, 0.75 + random() * 0.75, 1.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.borgLightningShaders[3], DEFAULT_DEVIATION);
+}
+
+/*
+-------------------------
+FX_StasisDischarge
+
+Fun "crawling" electricity ( credit goes to Josh for this one )
+-------------------------
+*/
+
+void FX_StasisDischarge( vec3_t origin, vec3_t normal, int count, float dist_out, float dist_side )
+{
+ trace_t trace;
+ vec3_t org, dir, dest;
+ vec3_t vr;
+ int i;
+ int discharge = dist_side;
+
+ vectoangles( normal, dir );
+ dir[ROLL] += random() * 360;
+
+ for (i = 0; i < count; i++)
+ {
+ //Move out a set distance
+ VectorMA( origin, dist_out, normal, org );
+
+ //Even out the hits
+ dir[ROLL] += (360 / count) + (rand() & 31);
+ AngleVectors( dir, NULL, vr, NULL );
+
+ //Move to the side in a random direction
+ discharge += (int)( crandom() * 8.0f );
+ VectorMA( org, discharge, vr, org );
+
+ //Trace back to find a surface
+ VectorMA( org, -dist_out * 3, normal, dest );
+
+ CG_Trace( &trace, org, NULL, NULL, dest, 0, MASK_SHOT );
+
+ //No surface found, start over
+ if (trace.fraction == 1)
+ continue;
+
+ //Connect the two points with bolts
+ FX_OrientedBolt( origin, trace.endpos, normal );
+
+ //TiM : Aww screw it. Add a lens flare. ^_^
+ CG_InitLensFlare( trace.endpos,
+ 10, 10,
+ colorTable[CT_GREEN], 1.2, 2.0, 1600, 500,
+ colorTable[CT_GREEN], 1600, 500, 100, 5, qtrue,
+ 0, 0, qfalse, qtrue,
+ qfalse, 1.0, cg.time, 0, 0, 300.0f + random() * 300);
+ }
+}
+
+/*
+-------------------------
+FX_StasisWeaponHitWall
+
+Main fire impact
+-------------------------
+*/
+
+#define NUM_DISCHARGES 6
+#define DISCHARGE_DIST 8
+#define DISCHARGE_SIDE_DIST 24
+
+void FX_StasisWeaponHitWall( vec3_t origin, vec3_t dir, int size )
+{
+ vec3_t vel, /*accel,*/ hitpos, direction, org;
+ //int i, t;
+ weaponInfo_t *weaponInfo = &cg_weapons[WP_DISRUPTOR];
+
+ CG_InitLensFlare( origin,
+ 375, 375,
+ colorTable[CT_GREEN], 1.2, 2.0, 1600, 200,
+ colorTable[CT_GREEN], 1600, 200, 800, 20, qtrue,
+ 0, 0, qfalse, qtrue,
+ qfalse, 1.0, cg.time, 0, 0, 200);
+
+ // Generate "crawling" electricity // eh, don't it doesn't look that great.
+ FX_StasisDischarge( origin, dir, NUM_DISCHARGES, DISCHARGE_DIST, DISCHARGE_SIDE_DIST );
+
+ VectorMA(origin, size, dir, hitpos);
+
+ // Set an oriented residual glow effect
+ FX_AddQuad( hitpos, dir, size * size * 15.0f, -150.0f,
+ 1.0f, 0.0f, 0, 300, cgs.media.greenParticleShader );
+
+ CG_ImpactMark( cgs.media.scavMarkShader, origin, dir, random()*360, 1,1,1,0.6, qfalse,
+ size * 12 + 1, qfalse );
+
+ FX_AddSprite( hitpos, NULL, qfalse, size * size * 15.0f, -150.0f,
+ 1.0f, 0.0f, 360*random(), 0, 400, cgs.media.greenParticleShader );
+
+/* FX_AddSprite( hitpos, NULL, qfalse, size * size * 15.0f, -150.0f,
+ 1.0f, 0.0f, 360*random(), 0, 400, cgs.media.greenParticleStreakShader ); */
+
+ FX_AddSprite( hitpos, NULL, qfalse, size * size * 25.0f, -150.0f,
+ 1.0f, 0.0f, 0.0f, 0, 400, cgs.media.greenParticleStreakShader );
+
+ VectorSubtract( cg.refdef.vieworg, origin, direction );
+ VectorNormalize( direction );
+
+ VectorMA( origin, 12, direction, org );
+ VectorMA( org, 8, dir, direction );
+ VectorSet(vel, 0, 0, 32 ); //8
+
+ FX_AddSprite( origin,
+ vel, qfalse,
+ random() * 4 + 2, 12,
+ 0.6 + random() * 0.4, 0.0,
+ random() * 180,
+ 0.0,
+ random() * 200 + 1200, //300
+ cgs.media.steamShader );
+
+ //FX_AddSprite(
+
+ // Only play the impact sound and throw off the purple particles when it's the main projectile
+/* if ( size < 3 )
+ return;
+
+ for ( i = 0; i < 4; i++ )
+ {
+ for ( t = 0; t < 3; t++ )
+ vel[t] = ( dir[t] + crandom() * 0.9 ) * ( random() * 100 + 250 );
+
+ VectorScale( vel, -2.2, accel );
+ FX_AddSprite( hitpos, vel, qfalse, random() * 8 + 8, 0, 1.0, 0.0, 0.0, 0.0, 200, cgs.media.purpleParticleShader );
+
+ }*/
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound );
+}
+
+void FX_DisruptorBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty )
+{
+ refEntity_t beam;
+ sfxHandle_t sfx;
+ float size;
+ vec3_t velocity;
+ int sparks;
+ vec3_t rgb = { 1,0.9,0.6}, rgb2={1,0.3,0};
+
+ //vec3_t rgb3 = { 1.0, 1.0, 1.0 };
+
+ sfx = 0;
+
+ // Draw beam first.
+ memset( &beam, 0, sizeof( beam ) );
+
+ VectorCopy( startpos, beam.origin);
+ VectorCopy( endpos, beam.oldorigin );
+ beam.reType = RT_LINE;
+ if (empty)
+ {
+ beam.customShader = cgs.media.phaserEmptyShader;
+ }
+ else
+ {
+ beam.customShader = cgs.media.disruptorBeam;
+ }
+ AxisClear( beam.axis );
+ beam.shaderRGBA[0] = 0xff;
+ beam.shaderRGBA[1] = 0xff;
+ beam.shaderRGBA[2] = 0xff;
+ beam.shaderRGBA[3] = 0xff;
+ if (empty)
+ {
+ beam.data.line.width = 1.0f + ( crandom() * 0.6f );
+ }
+ else
+ {
+ beam.data.line.width = 1.5f + ( crandom() * 0.6f );
+ }
+ beam.data.line.stscale = 5.0;
+ trap_R_AddRefEntityToScene( &beam );
+
+ // Now draw the hit graphic
+
+ // no explosion at LG impact, it is added with the beam
+
+ if ( sfx )
+ {
+ Com_Printf("playing %s\n", "phaser sound");
+ trap_S_StartSound( endpos, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
+ }
+
+ //
+ // impact mark
+ //
+ if (impact)
+ {
+ if (!empty)
+ { // normal.
+ CG_ImpactMark( cgs.media.scavMarkShader, endpos, normal, random()*360, 1,1,1,0.2, qfalse,
+ random() + 1, qfalse );
+
+ //VectorCopy( endpos, phaserFlare.worldCoord );
+
+ /*CG_InitLensFlare( endpos,
+ 80,
+ 80,
+ rgb,
+ 1.2,
+ 1.5,
+ 1600,
+ 200,
+ colorTable[CT_BLACK],
+ 1600,
+ 200,
+ 80,
+ 5,
+ qfalse,
+ 5,
+ 40,
+ qfalse,
+ qfalse,
+ qfalse,
+ 1.0,
+ 1.0,
+ 200.0,
+ 200.0,
+ 200.0 );*/
+
+ //CG_InitLensFlare( endpos,
+ // 30, 30,
+ // rgb, 1.2, 2.0, 1600, 200,
+ // colorTable[CT_BLACK], 1600, 200, 410, 15, qfalse,
+ // 0, 0, qfalse, qtrue,
+ // qfalse, 1.0, cg.time, 0, 0, 50);
+
+ //TiM : Add your basic cheesy 'seen-way-too-much-in-movies-these-days' anamorphic lens streak :)
+ //CG_DrawLensFlare( &phaserFlare );
+ //FX_AddSprite( endpos, NULL, qfalse, random() * 1.25 + 5.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 50.0, cgs.media.blueParticleStreakShader ); //1.5f
+
+ //FX_AddQuad2( endpos, normal, random() * 1.25 + 8.0f, 0.0f, 1.0f, 1.0f, rgb3, rgb3, 270, 50.0, cgs.media.blueParticleStreakShader );
+ //eh... looked bad :P
+
+ FX_AddQuad2( endpos, normal, random() * 1.25 + 1.5f, 0.0f, 1.0f, 0.0f, rgb, rgb2, rand() % 360, 500 + random() * 200,
+ cgs.media.sunnyFlareShader );
+ }
+ else
+ { // Wuss hit when empty.
+ FX_AddQuad2( endpos, normal, random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200,
+ cgs.media.sunnyFlareShader );
+ }
+ }
+
+ // "Fun" sparks... Not when empty.
+ if ( spark && !empty)
+ {
+ sparks = rand() & 1 + 1;
+ for(;sparks>0;sparks--)
+ {
+ size = 0.2f + (random() * 0.4);
+ FXE_Spray( normal, 200, 75, 0.8f, velocity);
+ if (rand() & LEF_USE_COLLISION)
+ { // This spark bounces.
+ FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f,
+ size, -size, 1.0f, 0.5f, 0.4f, 500.0f, cgs.media.sparkShader);
+ }
+ else
+ {
+ FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f,
+ size, -size, 1.0f, 0.5f, 0.0, 500.0f, cgs.media.sparkShader);
+ }
+ }
+ }
+}
diff --git a/cgame/fx_tetrion.c b/cgame/fx_tetrion.c
new file mode 100644
index 0000000..177d971
--- /dev/null
+++ b/cgame/fx_tetrion.c
@@ -0,0 +1,240 @@
+#include "cg_local.h"
+#include "fx_local.h"
+
+
+/*
+-------------------------
+FX_TetrionProjectileThink
+-------------------------
+*/
+/*void FX_TetrionProjectileThink( centity_t *cent, const struct weaponInfo_s *wi )
+{
+ vec3_t forward;
+
+ if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0 )
+ forward[2] = 1;
+
+ /*FX_AddSprite( cent->lerpOrigin,
+ NULL, qfalse,
+ 4.0f + random() * 16.0f, 0.0f,
+ 0.4f, 0.0f,
+ random()*360, 0.0f,
+ 1.0f,
+ cgs.media.greenBurstShader );
+ FX_AddSprite( cent->lerpOrigin,
+ NULL, qfalse,
+ 16.0f + random() * 16.0f, 0.0f,
+ 0.6f, 0.0f,
+ random()*360, 0.0f,
+ 1.0f,
+ cgs.media.borgFlareShader );
+ /*FX_AddTrail( cent->lerpOrigin,
+ forward, qfalse,
+ 64, 0,
+ 2.0f, 0,
+ 0.5f, 0,
+ 0,
+ 1,
+ cgs.media.greenTrailShader );
+}*/
+
+
+/*
+-------------------------
+FX_TetrionShot
+-------------------------
+*/
+#define MAXRANGE_TETRION 5000000 //RPG-X: J2J OLD: 8192
+void FX_TetrionShot( vec3_t start, vec3_t forward )
+{
+ trace_t trace;
+ vec3_t end, dir, new_start, new_end, radial, start2, spreadFwd;
+ float off, len, i, numBullets = 3;
+ float firingRadius = 6, minDeviation = 0.95, maxDeviation = 1.1;
+ qboolean render_impact = qtrue;
+ centity_t *traceEnt = NULL;
+ int clientNum = -1;
+
+ for (i = 0; i < numBullets; i++)
+ {
+ render_impact = qtrue;
+ // determine new firing position
+ fxRandCircumferencePos(start, forward, firingRadius, new_start);
+ VectorSubtract(new_start, start, radial);
+ VectorMA(start, 10, forward, start2);
+ VectorMA(start2, flrandom(minDeviation, maxDeviation), radial, start2);
+ VectorSubtract(start2, new_start, spreadFwd);
+ VectorNormalize(spreadFwd);
+ // determine new end position for this bullet. give the endpoint some spread, too.
+ VectorMA(new_start, MAXRANGE_TETRION, spreadFwd, end);
+ CG_Trace( &trace, new_start, NULL, NULL, end, cg_entities[cg.predictedPlayerState.clientNum].currentState.number, MASK_SHOT );
+ // Get the length of the whole shot
+ VectorSubtract( trace.endpos, new_start, dir );
+ len = VectorNormalize( dir );
+ // Don't do tracers when it gets really short
+ if ( len >= 64 )
+ {
+ // Move the end_point in a bit so the tracer doesn't always trace the full line length--this isn't strictly necessary, but it does
+ // add a bit of variance
+ off = flrandom(0.7, 1.0);
+ VectorMA( new_start, len * off, dir, new_end );
+
+ // Draw the tracer
+ FX_AddLine( new_end, new_start, 1.0f, 1.5f + random(), 0.0f, flrandom(0.3,0.6), 0.0,
+ flrandom(300,500), cgs.media.borgFlareShader );
+ }
+ // put the impact effect where this tracer hits
+ if (len >= 32)
+ {
+ // Rendering things like impacts when hitting a sky box would look bad, but you still want to see the tracer
+ if ( trace.surfaceFlags & SURF_NOIMPACT )
+ {
+ render_impact = qfalse;
+ }
+
+ if (render_impact)
+ {
+ traceEnt = &cg_entities[trace.entityNum];
+ clientNum = traceEnt->currentState.clientNum;
+ if ( (trace.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) )
+ {
+ // hit a player. let the shield/pain effects be the indicator for this
+ }
+ else
+ {
+ // hit something else
+ FX_TetrionWeaponHitWall(trace.endpos, trace.plane.normal);
+ }
+ }
+ }
+ }
+}
+
+/*
+-------------------------
+FX_TetrionWeaponHitWall
+-------------------------
+*/
+void FX_TetrionWeaponHitWall( vec3_t origin, vec3_t normal )
+{
+/* vec3_t vel, accel, org;
+ int i = 0, t = 0;
+ float scale = random() * 2.5 + 1.5;
+
+ CG_ImpactMark( cgs.media.bulletmarksShader, origin, normal, random()*360, 1,1,1,0.2, qfalse,
+ scale, qfalse );
+
+ // Move out a hair to avoid z buffer nastiness
+ VectorMA( origin, 0.5, normal, org );
+
+ // Add a bit of variation every now and then
+ if ( rand() & 1 )
+ {
+ FX_AddQuad( org, normal,
+ scale * 2, -4,
+ 0.5, 0.5,
+ 0,
+ 175,
+ cgs.media.sunnyFlareShader );
+ }
+
+ FX_AddQuad( org, normal,
+ scale * 4, -8,
+ 1.0, 1.0,
+ 0,
+ 175,
+ cgs.media.borgFlareShader );
+
+ // Add some smoke puffs
+ for ( i = 0; i < 2; i ++ )
+ {
+ for ( t = 0; t < 3; t++ )
+ {
+ vel[t] = normal[t] + crandom();
+ }
+
+ VectorScale( vel, 12 + random() * 12, vel );
+
+ vel[2] += 16;
+
+ VectorScale( vel, -0.25, accel );
+ FX_AddSprite( origin,
+ vel, qfalse,
+ random() * 4 + 2, 12,
+ 0.6 + random() * 0.4, 0.0,
+ random() * 180,
+ 0.0,
+ random() * 200 + 300,
+ cgs.media.steamShader );
+ }*/
+}
+
+/*
+-------------------------
+FX_TetrionRicochet
+-------------------------
+*/ /*
+void FX_TetrionRicochet( vec3_t origin, vec3_t normal )
+{
+ vec3_t org;
+
+ // Move away from the wall a bit to help avoid z buffer clipping.
+ VectorMA( origin, 0.5, normal, org );
+
+ /*FX_AddQuad( org, normal,
+ 24, -24,
+ 1.0, 0.0,
+ 0,
+ 300,
+ cgs.media.greenBurstShader );
+ FX_AddQuad( org, normal,
+ 48, -48,
+ 0.5, 0.0,
+ 0,
+ 300,
+ cgs.media.borgFlareShader );
+}*/
+
+/*
+-------------------------
+FX_TetrionAltHitWall
+-------------------------
+*/
+/*void FX_TetrionAltHitWall( vec3_t origin, vec3_t normal )
+{
+ vec3_t org;
+ float scale;
+
+ scale = random() * 2.0 + 1.0;
+
+ /*CG_ImpactMark( cgs.media.bulletmarksShader, origin, normal, random()*360, 1,1,1,0.2, qfalse,
+ scale, qfalse );
+
+ // Move out a hair to avoid z buffer nastiness
+ VectorMA( origin, 0.5, normal, org );
+
+ /*FX_AddQuad( origin, normal,
+ 64, -96,
+ 1.0, 0.0,
+ 0,
+ 200,
+ cgs.media.greenBurstShader );
+ FX_AddQuad( origin, normal,
+ 128, -192,
+ 1.0, 0.0,
+ 0,
+ 200,
+ cgs.media.borgFlareShader );
+
+ // kef -- urp.fixme.
+// trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cg_weapons[WP_TR116].altmissileHitSound );
+}*/
+
+/*
+-------------------------
+FX_TetrionAltHitPlayer
+-------------------------
+*/
+void FX_TetrionAltHitPlayer( vec3_t origin, vec3_t normal )
+{
+}
diff --git a/cgame/fx_transporter.c b/cgame/fx_transporter.c
new file mode 100644
index 0000000..3476dac
--- /dev/null
+++ b/cgame/fx_transporter.c
@@ -0,0 +1,293 @@
+
+#include "cg_local.h"
+#include "fx_local.h"
+
+/*
+-------------------------
+SPTransporterLensFlares
+
+TiM: Judging from the title,
+you just KNOW it's mine ;)
+
+Anyway, the point of this
+function is to render 4
+sprites and then scale + move
+them in a way reminisicant
+of ST: Voyager's transporter FX.
+
+I wrote this instead of using the
+already made FX spawning functions
+because they don't let u track an origin.
+To stop the particle
+from rendering within the character, I'm going to
+use the DEPTHHACK renderflag, the flag
+originally used for the weapon models
+in first-person mode. :)
+
+Planned timing:
+
+0 - 500 : Flare spawns, and scales up to it's main size (width parameter)
+500 - 1500 : Flare moves up the height/2 of model (height parameter)
+1500 - 2000 : Flare scales down and disappears ( width * 1.0 - (timeScale) )
+-------------------------
+*/
+
+void FX_SPTransporterLensFlares( centity_t* cent, vec3_t headVector, int startTime ) {
+ refEntity_t flare;
+ trace_t tr;
+ int i;
+ int direction = 1;
+ int timeOffset = 0; //250 - time between first and second flares appearing;
+ float ratio;
+ float dlightRatio;
+
+ vec3_t origin, tempOrigin;
+ int width;
+ int height;
+
+ //Hrmm... we have a glitch lol. Since DEPTHHACK is on, the flare will be drawn
+ //from ANYWHERE IN THE LEVEL! O_o
+ //So.... uh, we'll do a trace between ourselves and the entity this is attached
+ //to, and if they can't see each other, screw it. :P
+ if ( cg.predictedPlayerState.clientNum != cent->currentState.clientNum ) {
+ CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL,
+ cent->lerpOrigin, cg.predictedPlayerState.clientNum, CONTENTS_SOLID );
+ if ( tr.fraction != 1 ) {
+ return;
+ }
+ }
+ /*else {
+ Com_Printf( "Origin: { %f, %f, %f }\n", cent->lerpOrigin[0], cent->lerpOrigin[1], cent->lerpOrigin[2] );
+ Com_Printf( "HeadVector: { %f, %f, %f }\n", headVector[0], headVector[1], headVector[2] );
+ return;
+ }*/
+
+ //calculate the necessary data we need to place the origin in the direct center of the model
+
+ memset( &flare, 0, sizeof( flare ) );
+
+ //Bah. I thought lerpOrigin was at the base of the feet. Turns out it's at the knees O_o
+ //This little hack should help that
+ VectorCopy( cent->lerpOrigin, tempOrigin );
+ tempOrigin[2] -= 24;
+
+ //If the distance means we're not lying down
+ if ( ( headVector[2] - tempOrigin[2] ) > 8 ) {
+ //find the average between our lerp origin and headVector to find the center
+ //VectorAdd( headVector, tempOrigin, origin );
+ //VectorScale( origin, 0.5, origin );
+ VectorAverage( headVector, tempOrigin, origin );
+
+ width = 30;
+ height = (headVector[2] - tempOrigin[2]) / 2;
+ }
+ else {
+ width = 30;
+ height = 4;
+
+ VectorCopy( cent->lerpOrigin, origin);
+ }
+
+ flare.reType = RT_SPRITE;
+ flare.shaderRGBA[0] = 0xff;
+ flare.shaderRGBA[1] = 0xff;
+ flare.shaderRGBA[2] = 0xff;
+ flare.shaderRGBA[3] = 0xff;
+
+ flare.data.sprite.rotation = 0;
+ flare.nonNormalizedAxes = qtrue; //needed for effective scaling
+
+ flare.renderfx |= RF_DEPTHHACK; //DEPTHHACK renders the element over everything else. Useful in this lens flare simulation case :)
+
+ //loop 4 times = 4 flares. :)
+ for (i = 0; i < 4; i++ ) {
+ VectorClear( flare.origin );
+ VectorCopy( origin, flare.origin);
+
+ //the first two flares are the main ones
+ if ( i < 2 ) {
+ flare.customShader = cgs.media.transport1Shader; //1
+ timeOffset = startTime;
+ }
+ else { // the second two spawn a little later
+ flare.customShader = cgs.media.transport2Shader;
+ timeOffset = startTime + 650; //750
+ }
+
+ //the second flare each round goes down instead of up
+ if ( i % 2 == 0)
+ direction = 1;
+ else
+ direction = -1;
+
+ //===========================
+
+ if ( cg.time > timeOffset + 2000 ) {
+ continue;
+ }
+
+ //Phase 1: flares get bigger
+ if ( cg.time < timeOffset + 500 ) {
+ ratio = ((float)(cg.time - timeOffset) * 0.002);
+ if (ratio < 0 )
+ ratio = 0.0f;
+ else if (ratio > 1 )
+ ratio = 1.0f;
+
+ flare.data.sprite.radius = (float)width * ratio;
+ /*if ( i ==0 )
+ Com_Printf( "Phase 1 Radius: %f\n", flare.data.sprite.radius );*/
+ }
+ //Phase 2: flares move up/down character
+ if ( ( cg.time < timeOffset + 1500 ) && ( cg.time >= timeOffset + 500 ) ) {
+ ratio = ( (float)(cg.time - (timeOffset + 500) ) * 0.001 );
+ if (ratio < 0 )
+ ratio = 0.0f;
+ else if (ratio > 1 )
+ ratio = 1.0f;
+
+ flare.data.sprite.radius = (float)width;
+ flare.origin[2] += (float)direction * (float)height * ratio;
+ /*if (i == 0 )
+ Com_Printf( "Phase 2 Location: %f\n", flare.origin[2] );*/
+ }
+ //Phase 3: flares get smaller
+ if ( ( cg.time < timeOffset + 2000 ) && ( cg.time >= timeOffset + 1500 ) ) {
+ ratio = 1.0f - ( (float)(cg.time - ( timeOffset + 1500 ) ) * 0,002 );
+ if (ratio < 0 )
+ ratio = 0.0f;
+ else if (ratio > 1 )
+ ratio = 1.0f;
+
+ flare.origin[2] += ((float)height * (float)direction);
+ flare.data.sprite.radius = (float)width * ratio;
+ /*if ( i == 0 )
+ Com_Printf( "Phase 3 Radius: %f\n", flare.data.sprite.radius );*/
+ }
+
+ trap_R_AddRefEntityToScene( &flare );
+ }
+
+ //dynamic light calculation
+ if ( cg.time < ( startTime + 2000 ) ) {
+ dlightRatio = (float)( cg.time - startTime ) * 0,0005;
+ }
+ else {
+ dlightRatio = 1.0f - ( (float)( cg.time - ( startTime + 2000 ) ) * 0,0005 );
+ }
+
+ //dynamic light FX
+ trap_R_AddLightToScene( origin, 80.0f * dlightRatio, 0.345, 0.624, 0.835 );
+}
+
+/*
+-------------------------
+TransporterParticle
+-------------------------
+*/
+
+qboolean TransporterParticle( localEntity_t *le)
+{
+ vec3_t org, velocity = { 0, 0, 68 };
+ vec3_t accel = { 0, 0, -12 };
+ float scale, dscale;
+ qhandle_t shader;
+
+ VectorCopy( le->refEntity.origin, org );
+ org[2] += 0;//38;
+
+ shader = ( le->data.spawner.dir[0] == 0 ) ? cgs.media.trans1Shader : cgs.media.trans2Shader;
+ scale = ( le->data.spawner.dir[0] == 0 ) ? 2.0 : 4.0;
+ dscale = ( le->data.spawner.dir[0] == 0 ) ? 4.0 : 24.0;
+
+ le->data.spawner.dir[0]++;
+
+ FX_AddSprite( org,
+ velocity,
+ qfalse,
+ scale,
+ dscale,
+ 1.0f,
+ 0.0f,
+ 0,
+ 0.0f,
+ 450.0f,
+ shader );
+
+ VectorScale( velocity, -1, velocity );
+ VectorScale( accel, -1, accel );
+
+ FX_AddSprite( org,
+ velocity,
+ qfalse,
+ scale,
+ dscale,
+ 1.0f,
+ 0.0f,
+ 0,
+ 0.0f,
+ 450.0f,
+ shader );
+
+ return qtrue;
+}
+
+/*
+-------------------------
+TransporterPad
+-------------------------
+*/
+
+qboolean TransporterPad( localEntity_t *le)
+{
+ vec3_t org;
+ vec3_t up = {0,0,1};
+ float scale, dscale;
+ qhandle_t shader;
+
+ VectorCopy( le->refEntity.origin, org );
+ org[2] -= 3;
+
+ shader = cgs.media.trans1Shader;
+ scale = 20.0;
+ dscale = 2.0;
+
+ FX_AddQuad( org,
+ up,
+ scale,
+ dscale,
+ 1.0f,
+ 0.0f,
+ 0,
+ 950.0f,
+ shader );
+ return qtrue;
+}
+
+/*
+-------------------------
+FX_Transporter
+-------------------------
+*/
+
+void FX_Transporter( vec3_t origin )
+{
+ vec3_t up = {0,0,1};
+
+ FX_AddSpawner( origin, up, NULL, NULL, qfalse, 0, 0, 200, TransporterParticle, 0 );
+// trap_S_StartSound( origin, NULL, CHAN_AUTO, cgs.media.teleInSound );
+}
+
+/*
+-------------------------
+FX_TransporterPad
+-------------------------
+*/
+
+void FX_TransporterPad( vec3_t origin )
+{
+ vec3_t up = {0,0,1};
+
+ FX_AddSpawner( origin, up, NULL, NULL, qfalse, 1000, 0, 0, TransporterPad, 0 );
+}
+
diff --git a/cgame/tr_types.h b/cgame/tr_types.h
new file mode 100644
index 0000000..d74ac99
--- /dev/null
+++ b/cgame/tr_types.h
@@ -0,0 +1,256 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+#ifndef __TR_TYPES_H
+#define __TR_TYPES_H
+
+
+#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces
+
+#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing
+
+
+// renderfx flags
+#define RF_LOWLIGHT 0x0001 // subtract ambient to keep it in the dark
+#define RF_THIRD_PERSON 0x0002 // don't draw through eyes, only mirrors (player bodies, chat sprites)
+#define RF_FIRST_PERSON 0x0004 // only draw through eyes (view weapon, damage blood blob)
+#define RF_DEPTHHACK 0x0008 // for view weapon Z crunching
+#define RF_FULLBRIGHT 0x0010 // Render with a bright ambient light.
+#define RF_NOSHADOW 0x0040 // don't add stencil shadows
+
+#define RF_LIGHTING_ORIGIN 0x0080 // use refEntity->lightingOrigin instead of refEntity->origin
+ // for lighting. This allows entities to sink into the floor
+ // with their origin going solid, and allows all parts of a
+ // player to get the same lighting
+#define RF_SHADOW_PLANE 0x0100 // use refEntity->shadowPlane
+#define RF_WRAP_FRAMES 0x0200 // mod the model frames by the maxframes to allow continuous
+ // animation without needing to know the frame count
+#define RF_CAP_FRAMES 0x0400 // cap the model frames by the maxframes for one shot anims
+#define RF_FORCE_ENT_ALPHA 0x0800 // Models should use ent alpha regardless of what the shader says.
+
+
+// refdef flags
+#define RDF_NOWORLDMODEL 1 // used for player configuration screen
+#define RDF_HYPERSPACE 4 // teleportation effect
+
+#ifdef XTRA
+#define RDF_MOTIONBLUR 8
+#endif
+
+typedef struct {
+ vec3_t xyz;
+ float st[2];
+ byte modulate[4];
+} polyVert_t;
+
+typedef struct poly_s {
+ qhandle_t hShader;
+ int numVerts;
+ polyVert_t *verts;
+} poly_t;
+
+typedef enum {
+ RT_MODEL,
+ RT_SPRITE,
+ RT_ORIENTEDSPRITE, // Replaces RT_POLY, which wasn't used. --Pat
+ RT_ALPHAVERTPOLY, // Individual alpha levels on each vertex
+ RT_BEAM,
+ RT_RAIL_CORE,
+ RT_RAIL_RINGS,
+ RT_LIGHTNING,
+ RT_PORTALSURFACE, // doesn't draw anything, just info for portals
+ RT_LINE, // New type for Trek MP --Pat
+ RT_ORIENTEDLINE,
+ RT_LINE2, // New line type for Trek MP, with taper support --Pat
+ RT_BEZIER, // what he said --keith
+ RT_CYLINDER, // Yet another Trek primitive!
+ RT_ELECTRICITY, // Yet another Trek primitive!
+
+ RT_MAX_REF_ENTITY_TYPE
+} refEntityType_t;
+
+
+typedef struct {
+ refEntityType_t reType;
+ int renderfx;
+
+ qhandle_t hModel; // opaque type outside refresh
+
+ // most recent data
+ vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN)
+ float shadowPlane; // projection shadows go here, stencils go slightly lower
+
+ vec3_t axis[3]; // rotation vectors
+ qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale
+ float origin[3]; // also used as MODEL_BEAM's "from"
+ int frame; // also used as MODEL_BEAM's diameter
+
+ // previous data for frame interpolation
+ float oldorigin[3]; // also used as MODEL_BEAM's "to"
+ int oldframe;
+ float backlerp; // 0.0 = current, 1.0 = old
+
+ // texturing
+ int skinNum; // inline skin index
+ qhandle_t customSkin; // NULL for default skin
+ qhandle_t customShader; // use one image for the entire thing
+
+ // misc
+ byte shaderRGBA[4]; // colors used by rgbgen entity shaders
+ float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers
+ float shaderTime; // subtracted from refdef time to control effect start times
+
+ // extra sprite information
+ union {
+ struct
+ {
+ float rotation;
+ float radius;
+ byte vertRGBA[4][4];
+ } sprite;
+ struct
+ {
+ float width;
+ float width2;
+ float stscale;
+ } line;
+ struct // that whole put-the-opening-brace-on-the-same-line-as-the-beginning-of-the-definition coding style is fecal
+ // TiM: You're a fecal. I prefer that style :D
+ // TiM: (c)2008 You're a you're a fecal. ;P I've grown to like the other one now.... it's way easier to read XD
+ {
+ float width;
+ vec3_t control1;
+ vec3_t control2;
+ } bezier;
+ struct
+ {
+ float width;
+ float width2;
+ float stscale;
+ float height;
+ float bias;
+ qboolean wrap;
+ } cylinder;
+ struct
+ {
+ float width;
+ float deviation;
+ float stscale;
+ qboolean wrap;
+ qboolean taper;
+ } electricity;
+ } data;
+} refEntity_t;
+
+
+#define MAX_RENDER_STRINGS 8
+#define MAX_RENDER_STRING_LENGTH 32
+
+typedef struct {
+ int x, y, width, height;
+ float fov_x, fov_y;
+ vec3_t vieworg;
+ vec3_t viewaxis[3]; // transformation matrix
+
+ // time in milliseconds for shader effects and other time dependent rendering issues
+ int time;
+
+ int rdflags; // RDF_NOWORLDMODEL, etc
+
+ // 1 bits will prevent the associated area from rendering at all
+ byte areamask[MAX_MAP_AREA_BYTES];
+
+ // text messages for deform text shaders
+// char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
+} refdef_t;
+
+
+typedef enum {
+ STEREO_CENTER,
+ STEREO_LEFT,
+ STEREO_RIGHT
+} stereoFrame_t;
+
+
+/*
+** glconfig_t
+**
+** Contains variables specific to the OpenGL configuration
+** being run right now. These are constant once the OpenGL
+** subsystem is initialized.
+*/
+typedef enum {
+ TC_NONE,
+ TC_S3TC,
+ TC_S3TC_DXT
+} textureCompression_t;
+
+typedef enum {
+ GLDRV_ICD, // driver is integrated with window system
+ // WARNING: there are tests that check for
+ // > GLDRV_ICD for minidriverness, so this
+ // should always be the lowest value in this
+ // enum set
+ GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver
+ GLDRV_VOODOO // driver is a 3Dfx standalone driver
+} glDriverType_t;
+
+typedef enum {
+ GLHW_GENERIC, // where everthing works the way it should
+ GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is
+ // the hardware type then there can NOT exist a secondary
+ // display adapter
+ GLHW_RIVA128, // where you can't interpolate alpha
+ GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures
+ GLHW_PERMEDIA2 // where you don't have src*dst
+} glHardwareType_t;
+
+typedef struct {
+ char renderer_string[MAX_STRING_CHARS];
+ char vendor_string[MAX_STRING_CHARS];
+ char version_string[MAX_STRING_CHARS];
+ char extensions_string[2*MAX_STRING_CHARS];
+
+ int maxTextureSize; // queried from GL
+ int maxActiveTextures; // multitexture ability
+
+ int colorBits, depthBits, stencilBits;
+
+ glDriverType_t driverType;
+ glHardwareType_t hardwareType;
+
+ qboolean deviceSupportsGamma;
+ textureCompression_t textureCompression;
+ qboolean textureEnvAddAvailable;
+ qboolean textureFilterAnisotropicAvailable;
+
+ int vidWidth, vidHeight;
+ // aspect is the screen's physical width / height, which may be different
+ // than scrWidth / scrHeight if the pixels are non-square
+ // normal screens should be 4/3, but wide aspect monitors may be 16/9
+ float windowAspect;
+
+ int displayFrequency;
+
+ // synonymous with "does rendering consume the entire screen?", therefore
+ // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that
+ // used CDS.
+ qboolean isFullscreen;
+ qboolean stereoEnabled;
+ qboolean smpActive; // dual processor
+} glconfig_t;
+
+
+#if !defined _WIN32
+
+#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so"
+#define OPENGL_DRIVER_NAME "libGL.so"
+
+#else
+
+#define _3DFX_DRIVER_NAME "3dfxvgl"
+#define OPENGL_DRIVER_NAME "opengl32"
+
+#endif // !defined _WIN32
+
+
+#endif // __TR_TYPES_H
diff --git a/codetidbits.txt b/codetidbits.txt
new file mode 100644
index 0000000..d71213e
--- /dev/null
+++ b/codetidbits.txt
@@ -0,0 +1,98 @@
+/* Old Code bits
+ hcolor[0] = 0.0;
+ hcolor[1] = 1.0;
+ hcolor[2] = 0.0;
+ hcolor[3] = 1.0;
+
+ x = 250;
+ y2 = 200;
+ w = 100;
+ h = 100;
+ glowOffset = 60;
+ streakW = 500;
+ streakH = 10;*/
+
+ //reflecAnamorphic lenses make their flares really stretched
+ //Actually, just make this control the reflections.
+ //The user can define the w + h themselves. :P
+ /*if ( reflecAnamorphic ) {
+ w = w * 2;
+ //widthDouble = 2;
+ }*/
+
+ //else
+ /*CG_DrawPic(
+ ( ( xCart/lensReflec[i].offset )+HALF_SCREEN_WIDTH ) - ( reflecAnamorphic ? lensReflec[i].width : lensReflec[i].width/2 ), //X
+ ( ( yCart/lensReflec[i].offset )+HALF_SCREEN_HEIGHT ) - ( lensReflec[i].height/2 ), //Y
+ reflecAnamorphic ? lensReflec[i].width * 2 : lensReflec[i].width, //W
+ lensReflec[i].height, //H
+ lensReflec[i].graphic //pic
+ );*/
+/* float yMid = ymin + ( (ymax - ymin) /2 );
+
+ //if the flare's origin moves to out of range, fade out the alpha on the reflections
+ //this is how it works O_o each side of the screen is a different quadrant controlled
+ //by a dif equation, excluding the corners where they'd overlap. The corners have their own
+ //equations applied later on, hopefully forming a complte circle for the flares to fade out easily. :S
+
+
+ //left bar
+ if ( x > xmin && x < xmax && y < ymin && y > -ymin )
+ reflecAlpha = (float)(( xmax - (float)x ) / xDif);
+ //right bar
+ if ( x < -xmin && x > -xmax && y < ymin && y > -ymin )
+ reflecAlpha = (float)(( xmax + (float)x ) / xDif);
+ //upper bar
+ if ( y > ymin && y < ymax && x < xmin && x > -xmin )
+ reflecAlpha = (float)(( ymax - (float)y ) / yDif);
+ //lower bar
+ if ( y < -ymin && y > -ymax && x < xmin && x > -xmin )
+ reflecAlpha = (float)(( ymax + (float)y ) / yDif);
+
+ //lower right quadrant
+/* if ( x > xmin && x < xmax && y > ymin && y < ymax ) {
+ if ( (x*0.75) > yMid )*/
+
+/*
+ CG_DrawPic( , y - ((h * hazeOffset) * 0.5), (w * hazeOffset), h * hazeOffset, cgs.media.flareHaze ); //Surrounding ambient haze
+
+ trap_R_SetColor( strkColor );
+ CG_DrawPic( streakX , streakY , streakW, streakH, cgs.media.flareStreak ); //Colored portion of the anamorphic streaks
+
+ trap_R_SetColor( color );
+ CG_DrawPic( x - ((w * glowOffset) * 0.5), y - ((h * glowOffset) * 0.5), (w * glowOffset), h * glowOffset, cgs.media.flareCore ); //Main colored glow bit of the main flare
+
+ if ( whiteStreaks ) { //if player wanted white streaks in their streaks
+ strkColor[0] = strkColor[1] = strkColor[2] = 1.0;
+
+ trap_R_SetColor( strkColor ); //White
+ CG_DrawPic( streakX + (streakW*0.2), streakY + (streakH*0.2), streakW*0.6, streakH*0.6, cgs.media.flareStreak ); //White Core of streak is ALWAYS 20% smaller.
+ }
+
+ trap_R_SetColor( NULL );
+ CG_DrawPic( x, y, w, h, cgs.media.flareCore ); //Draw teh main fl4r3 :)*/
+
+ //w = (0.0001 * -(length*length)) + w;
+ //h = (0.0001 * -(length*length)) + h;
+
+ /*if ( !clamp ) {
+ w = w + (int)(-0.1 * length );
+ h = h + (int)(-0.1 * length );
+
+ //clamp it at 10% then fade it out
+ if ( ((float)w/(float)w1) <= 0.2 && ((float)h/(float)h1) <= 0.2 ) {
+ w = w1*0.2f;
+ h = h1*0.2f;
+ }
+
+ //then fade it back in
+ //if ( ((float)w/(float)w1) >= .05 && ((float)h/(float)h1) >= .05 && fadeAlpha == 0.0 )
+
+ if ( ((float)w/(float)w1) <= 0.15 && ((float)h/(float)h1) <= 0.15 ) {
+ w = w1 * 0.15;
+ h = h1 * 0.15;
+ }
+ //CG_Printf( "Distance = %f, w = %i, h = %i\n", length, w, h);
+ }*/
+
+//------------------------------------------------------
\ No newline at end of file
diff --git a/game/Makefile b/game/Makefile
new file mode 100644
index 0000000..7895451
--- /dev/null
+++ b/game/Makefile
@@ -0,0 +1,220 @@
+default: so
+so: build_so
+
+# determine arch and platform
+ARCH=$(shell uname -m | sed -e s/i.86/i386/)
+PLATFORM=$(shell uname|sed -e s/_.*//|tr '[:upper:]' '[:lower:]')
+
+# compiler to use for building shared objects
+CC = gcc
+
+# cflags for the compiler
+ifeq ($(PLATFORM), mingw32)
+SOCFLAGS = $(CFLAGS)
+LIBMYSQL =
+else
+SOCFLAGS = $(CFLAGS) -fPIC
+LIBMYSQL = -mysqlclient
+endif
+
+# set extension
+ifeq ($(PLATFORM), mingw32)
+EXT=dll
+ARCH=x86
+else
+EXT=so
+endif
+
+# game objects
+OBJ = \
+ g_ui.o \
+ g_lua.o \
+ q_shared.o \
+ q_math.o \
+ g_weapon.o \
+ g_utils.o \
+ g_usable.o \
+ g_turrets.o \
+ g_trigger.o \
+ g_team.o \
+ g_target.o \
+ g_svcmds.o \
+ g_spawn.o \
+ g_session.o \
+ g_mover.o \
+ g_missile.o \
+ g_misc.o \
+ g_mem.o \
+ g_main.o \
+ g_log.o \
+ g_items.o \
+ g_fx.o \
+ g_combat.o \
+ g_cmds.o \
+ g_client.o \
+ g_breakable.o \
+ g_bot.o \
+ g_arenas.o \
+ g_active.o \
+ bg_slidemove.o \
+ bg_pmove.o \
+ bg_oums.o \
+ bg_misc.o \
+ ai_team.o \
+ ai_main.o \
+ ai_dmq3.o \
+ ai_dmnet.o \
+ ai_cmd.o \
+ ai_chat.o \
+ lua_game.o \
+ lua_entity.o \
+ lua_vector.o \
+ lua_mover.o \
+ lua_qmath.o
+
+# game object for syscalls to the engine
+SOOBJ = \
+ g_syscalls.o
+
+# objects for lua
+LUAOBJ = \
+ lapi.o \
+ lauxlib.o \
+ lbaselib.o \
+ lbitlib.o \
+ lcode.o \
+ lcorolib.o \
+ lctype.o \
+ ldblib.o \
+ ldebug.o \
+ ldo.o \
+ ldump.o \
+ lfunc.o \
+ lgc.o \
+ linit.o \
+ liolib.o \
+ llex.o \
+ lmathlib.o \
+ lmem.o \
+ loadlib.o \
+ lobject.o \
+ lopcodes.o \
+ loslib.o \
+ lparser.o \
+ lstate.o \
+ lstring.o \
+ lstrlib.o \
+ ltable.o \
+ ltablib.o \
+ ltm.o \
+ lua.o \
+ luac.o \
+ lundump.o \
+ lvm.o \
+ lzio.o
+
+# do cc for shared library
+DO_SOCC = $(CC) $(SOCFLAGS) -o $@ -c $<
+# do cc for lua
+DO_LUACC = $(CC) -O2 -Wall -fPIC -DLUA_COMPAT_ALL -o $@ -c $<
+
+build_so: DO_CC=$(DO_SOCC)
+
+# game
+ai_chat.o : ai_chat.c; $(DO_CC)
+ai_cmd.o : ai_cmd.c; $(DO_CC)
+ai_dmnet.o : ai_dmnet.c; $(DO_CC)
+ai_dmq3.o : ai_dmq3.c; $(DO_CC)
+ai_main.o : ai_main.c; $(DO_CC)
+ai_team.o : ai_team.c; $(DO_CC)
+bg_misc.o : bg_misc.c; $(DO_CC)
+bg_pmove.o : bg_pmove.c; $(DO_CC)
+bg_slidemove.o : bg_slidemove.c; $(DO_CC)
+g_active.o : g_active.c; $(DO_CC)
+g_arenas.o : g_arenas.c; $(DO_CC)
+g_bot.o : g_bot.c; $(DO_CC)
+g_breakable.o : g_breakable.c; $(DO_CC)
+g_client.o : g_client.c; $(DO_CC)
+g_cmds.o : g_cmds.c; $(DO_CC)
+g_combat.o : g_combat.c; $(DO_CC)
+g_fx.o : g_fx.c; $(DO_CC)
+g_items.o : g_items.c; $(DO_CC)
+g_log.o : g_log.c; $(DO_CC)
+g_main.o : g_main.c; $(DO_CC)
+g_mem.o : g_mem.c; $(DO_CC)
+g_misc.o : g_misc.c; $(DO_CC)
+g_missile.o : g_missile.c; $(DO_CC)
+g_mover.o : g_mover.c; $(DO_CC)
+g_session.o : g_session.c; $(DO_CC)
+g_spawn.o : g_spawn.c; $(DO_CC)
+g_svcmds.o : g_svcmds.c; $(DO_CC)
+g_target.o : g_target.c; $(DO_CC)
+g_team.o : g_team.c; $(DO_CC)
+g_trigger.o : g_trigger.c; $(DO_CC)
+g_turrets.o : g_turrets.c; $(DO_CC)
+g_usable.o : g_usable.c; $(DO_CC)
+g_utils.o : g_utils.c; $(DO_CC)
+g_weapon.o : g_weapon.c; $(DO_CC)
+q_math.o : q_math.c; $(DO_CC)
+q_shared.o : q_shared.c; $(DO_CC)
+g_lua.o: g_lua.c; $(DO_CC)
+g_ui.o: g_ui.c; $(DO_CC)
+g_sql.o: g_sql.c; $(DO_CC)
+bg_oums.o : bg_oums.c; $(DO_CC)
+lua_game.o: lua_game.c; $(DO_CC)
+lua_entity.o: lua_entity.c; $(DO_CC)
+lua_mover.o: lua_mover.c; $(DO_CC)
+lua_qmath.o: lua_qmath.c; $(DO_CC)
+lua_vector.o: lua_vector.c; $(DO_CC)
+
+# game syscalls
+g_syscalls.o : g_syscalls.c; $(DO_CC)
+
+# bg_lib
+bg_lib.o : bg_lib.c; $(DO_CC)
+
+# lua
+lapi.o: lapi.c; $(DO_LUACC)
+lauxlib.o: lauxlib.c; $(DO_LUACC)
+lbaselib.o: lbaselib.c; $(DO_LUACC)
+lbitlib.o: lbitlib.c; $(DO_LUACC)
+lcode.o: lcode.c; $(DO_LUACC)
+lcorolib.o: lcorolib.c; $(DO_LUACC)
+lctype.o: lctype.c; $(DO_LUACC)
+ldblib.o: ldblib.c; $(DO_LUACC)
+ldebug.o: ldebug.c; $(DO_LUACC)
+ldo.o: ldo.c; $(DO_LUACC)
+ldump.o: ldump.c; $(DO_LUACC)
+lfunc.o: lfunc.c; $(DO_LUACC)
+lgc.o: lgc.c; $(DO_LUACC)
+linit.o: linit.c; $(DO_LUACC)
+liolib.o: liolib.c; $(DO_LUACC)
+llex.o: llex.c; $(DO_LUACC)
+lmathlib.o: lmathlib.c; $(DO_LUACC)
+lmem.o: lmem.c; $(DO_LUACC)
+loadlib.o: loadlib.c; $(DO_LUACC)
+lobject.o: lobject.c; $(DO_LUACC)
+lopcodes.o: lopcodes.c; $(DO_LUACC)
+loslib.o: loslib.c; $(DO_LUACC)
+lparser.o: lparser.c; $(DO_LUACC)
+lstate.o: lstate.c; $(DO_LUACC)
+lstring.o: lstring.c; $(DO_LUACC)
+lstrlib.o: lstrlib.c; $(DO_LUACC)
+ltable.o: ltable.c; $(DO_LUACC)
+ltablib.o: ltablib.c; $(DO_LUACC)
+ltm.o: ltm.c; $(DO_LUACC)
+lua.o: lua.c; $(DO_LUACC)
+luac.o: luac.c; $(DO_LUACC)
+lundump.o: lundump.c; $(DO_LUACC)
+lvm.o: lvm.c; $(DO_LUACC)
+lzio.o: lzio.c; $(DO_LUACC)
+
+build_so: $(OBJ) $(SOOBJ) $(LUAOBJ)
+ifeq ($(PLATFORM), mingw32)
+ $(CC) -shared -W1,--export-all-symbols,-soname,qqgame$(ARCH).$(EXT) -o qagame$(ARCH).$(EXT) $(OBJ) $(SOOBJ) $(LUAOBJ) ../mysql/libs/mysqlclient.lib -lm
+else
+ $(CC) -shared -Wl,--export-dynamic,-soname,qagame$(ARCH).$(EXT) -o qagame$(ARCH).$(EXT) $(OBJ) $(SOOBJ) $(LUAOBJ) $(LIBMYSQL) -lm
+endif
+
+clean:
+ rm -f *.o *.$(EXT)
diff --git a/game/ai_chat.c b/game/ai_chat.c
new file mode 100644
index 0000000..3ea3160
--- /dev/null
+++ b/game/ai_chat.c
@@ -0,0 +1,1161 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_chat.c
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_chat.c $
+ * $Author: Mgummelt $
+ * $Revision: 7 $
+ * $Modtime: 3/09/01 11:52a $
+ * $Date: 3/09/01 12:02p $
+ *
+ *****************************************************************************/
+
+#include "g_local.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_ea.h"
+#include "be_ai_char.h"
+#include "be_ai_chat.h"
+#include "be_ai_gen.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+#include "be_ai_weap.h"
+//
+#include "ai_main.h"
+#include "ai_dmq3.h"
+#include "ai_chat.h"
+#include "ai_cmd.h"
+#include "ai_dmnet.h"
+//
+#include "chars.h" //characteristics
+#include "inv.h" //indexes into the inventory
+#include "syn.h" //synonyms
+#include "match.h" //string matching types and vars
+
+
+/*
+==================
+BotNumActivePlayers
+==================
+*/
+int BotNumActivePlayers(void) {
+ int i, num;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ num = 0;
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //
+ num++;
+ }
+ return num;
+}
+
+/*
+==================
+BotIsFirstInRankings
+==================
+*/
+int BotIsFirstInRankings(bot_state_t *bs) {
+ int i, score;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+ playerState_t ps;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ score = bs->cur_ps.persistant[PERS_SCORE];
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //
+ BotAI_GetClientState(i, &ps);
+ if (score < ps.persistant[PERS_SCORE]) return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotIsLastInRankings
+==================
+*/
+int BotIsLastInRankings(bot_state_t *bs) {
+ int i, score;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+ playerState_t ps;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ score = bs->cur_ps.persistant[PERS_SCORE];
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //
+ BotAI_GetClientState(i, &ps);
+ if (score > ps.persistant[PERS_SCORE]) return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotFirstClientInRankings
+==================
+*/
+char *BotFirstClientInRankings(void) {
+ int i, bestscore, bestclient;
+ char buf[MAX_INFO_STRING];
+ static char name[32];
+ static int maxclis;
+ playerState_t ps;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ bestscore = -999999;
+ bestclient = 0;
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //
+ BotAI_GetClientState(i, &ps);
+ if (ps.persistant[PERS_SCORE] > bestscore) {
+ bestscore = ps.persistant[PERS_SCORE];
+ bestclient = i;
+ }
+ }
+ EasyClientName(bestclient, name, 32);
+ return name;
+}
+
+/*
+==================
+BotLastClientInRankings
+==================
+*/
+char *BotLastClientInRankings(void) {
+ int i, worstscore, bestclient;
+ char buf[MAX_INFO_STRING];
+ static char name[32];
+ static int maxclis;
+ playerState_t ps;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ worstscore = 999999;
+ bestclient = 0;
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //
+ BotAI_GetClientState(i, &ps);
+ if (ps.persistant[PERS_SCORE] < worstscore) {
+ worstscore = ps.persistant[PERS_SCORE];
+ bestclient = i;
+ }
+ }
+ EasyClientName(bestclient, name, 32);
+ return name;
+}
+
+/*
+==================
+BotRandomOpponentName
+==================
+*/
+char *BotRandomOpponentName(bot_state_t *bs) {
+ int i, count;
+ char buf[MAX_INFO_STRING];
+ int opponents[MAX_CLIENTS], numopponents;
+ static int maxclis;
+ static char name[32];
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ numopponents = 0;
+ opponents[0] = 0;
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ if (i == bs->client) continue;
+ //
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //skip team mates
+ if (BotSameTeam(bs, i)) continue;
+ //
+ opponents[numopponents] = i;
+ numopponents++;
+ }
+ count = random() * numopponents;
+ for (i = 0; i < numopponents; i++) {
+ count--;
+ if (count <= 0) {
+ EasyClientName(opponents[i], name, sizeof(name));
+ return name;
+ }
+ }
+ EasyClientName(opponents[0], name, sizeof(name));
+ return name;
+}
+
+/*
+==================
+BotMapTitle
+==================
+*/
+
+char *BotMapTitle(void) {
+ char info[1024];
+ static char mapname[128];
+
+ trap_GetServerinfo(info, sizeof(info));
+
+ strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
+ mapname[sizeof(mapname)-1] = '\0';
+
+ return mapname;
+}
+
+
+/*
+==================
+BotWeaponNameForMeansOfDeath
+==================
+*/
+
+char *BotWeaponNameForMeansOfDeath(int mod) {
+ switch(mod) {
+ case MOD_PHASER:
+ case MOD_PHASER_ALT: return "Phaser";
+ case MOD_CRIFLE:
+ case MOD_CRIFLE_SPLASH:
+ case MOD_CRIFLE_ALT:
+ case MOD_CRIFLE_ALT_SPLASH: return "Compression Rifle";
+ case MOD_IMOD:
+ case MOD_IMOD_ALT: return "Infinity Modulator";
+ case MOD_SCAVENGER:
+ case MOD_SCAVENGER_ALT:
+ case MOD_SCAVENGER_ALT_SPLASH: return "Scavenger Rifle";
+ case MOD_STASIS:
+ case MOD_STASIS_ALT: return "Stasis Weapon";
+ case MOD_GRENADE:
+ case MOD_GRENADE_SPLASH:
+ case MOD_GRENADE_ALT_SPLASH: return "Grenade Launcher";
+ case MOD_TETRION:
+ case MOD_TETRION_ALT: return "Tetryon Disruptor";
+ case MOD_DREADNOUGHT:
+ case MOD_DREADNOUGHT_ALT: return "Arc Welder";
+ case MOD_QUANTUM:
+ case MOD_QUANTUM_SPLASH:
+ case MOD_QUANTUM_ALT:
+ case MOD_QUANTUM_ALT_SPLASH: return "Photon Burst Cannon";
+ case MOD_KNOCKOUT: return "Hypo";
+
+ default: return "[unknown weapon]";
+ }
+}
+
+/*
+==================
+BotRandomWeaponName
+==================
+*/
+char *BotRandomWeaponName(void) {
+ int rnd;
+
+ rnd = random() * 8.9;
+ switch(rnd) {
+ case 0: return "Phaser";
+ case 1: return "Compression Rifle";
+ case 2: return "Infinity Modulator";
+ case 3: return "Scavenger Rifle";
+ case 4: return "Stasis Weapon";
+ case 5: return "Grenade Launcher";
+ case 6: return "Tetryon Disruptor";
+ case 7: return "Dermal Regenerator";
+ default: return "Photon Burst Cannon";
+ }
+}
+
+/*
+==================
+BotVisibleEnemies
+==================
+*/
+int BotVisibleEnemies(bot_state_t *bs) {
+ float vis, dist;
+ int i;
+ vec3_t dir;
+ aas_entityinfo_t entinfo;
+
+ for (i = 0; i < MAX_CLIENTS; i++) {
+
+ if (i == bs->client) continue;
+ //
+ BotEntityInfo(i, &entinfo);
+ //
+ if (!entinfo.valid) continue;
+ //if the enemy isn't dead and the enemy isn't the bot self
+ if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
+ //if the enemy is invisible and not shooting
+ if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
+ continue;
+ }
+ //calculate the distance towards the enemy
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ dist = VectorLength(dir);
+ //if on the same team
+ if (BotSameTeam(bs, i)) continue;
+ //check if the enemy is visible
+ vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
+ if (vis > 0) return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotValidChatPosition
+==================
+*/
+int BotValidChatPosition(bot_state_t *bs) {
+ vec3_t point, start, end, mins, maxs;
+ bsp_trace_t trace;
+
+ //if the bot is dead all positions are valid
+ if (BotIsDead(bs)) return qtrue;
+ //never start chatting with a powerup
+ if (bs->inventory[INVENTORY_QUAD] ||
+ bs->inventory[INVENTORY_HASTE] ||
+ bs->inventory[INVENTORY_INVISIBILITY] ||
+ bs->inventory[INVENTORY_REGEN] ||
+ bs->inventory[INVENTORY_FLIGHT]) return qfalse;
+ //must be on the ground
+ //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse;
+ //do not chat if in lava or slime
+ VectorCopy(bs->origin, point);
+ point[2] -= 24;
+ if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse;
+ //do not chat if under water
+ VectorCopy(bs->origin, point);
+ point[2] += 32;
+ if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse;
+ //must be standing on the world entity
+ VectorCopy(bs->origin, start);
+ VectorCopy(bs->origin, end);
+ start[2] += 1;
+ end[2] -= 10;
+ trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs);
+ BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID);
+ if (trace.ent != ENTITYNUM_WORLD) return qfalse;
+ //the bot is in a position where it can chat
+ return qtrue;
+}
+
+/*
+==================
+BotChat_EnterGame
+==================
+*/
+int BotChat_EnterGame(bot_state_t *bs) {
+ char name[32];
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ rnd = 0;
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ }
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ if (!BotValidChatPosition(bs)) return qfalse;
+ BotAI_BotInitialChat(bs, "game_enter",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ NULL);
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_ExitGame
+==================
+*/
+int BotChat_ExitGame(bot_state_t *bs) {
+ char name[32];
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ rnd = 0;
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ }
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ //
+ BotAI_BotInitialChat(bs, "game_exit",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ NULL);
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_StartLevel
+==================
+*/
+int BotChat_StartLevel(bot_state_t *bs) {
+ char name[32];
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (BotIsObserver(bs)) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ rnd = 0;
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ }
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ BotAI_BotInitialChat(bs, "level_start",
+ EasyClientName(bs->client, name, 32), // 0
+ NULL);
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_EndLevel
+==================
+*/
+int BotChat_EndLevel(bot_state_t *bs) {
+ char name[32];
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (BotIsObserver(bs)) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ rnd = 0;
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ }
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ //
+ if (BotIsFirstInRankings(bs)) {
+ BotAI_BotInitialChat(bs, "level_end_victory",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ "[invalid var]", // 2
+ BotLastClientInRankings(), // 3
+ BotMapTitle(), // 4
+ NULL);
+ }
+ else if (BotIsLastInRankings(bs)) {
+ BotAI_BotInitialChat(bs, "level_end_lose",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ BotFirstClientInRankings(), // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "level_end",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ BotFirstClientInRankings(), // 2
+ BotLastClientInRankings(), // 3
+ BotMapTitle(), // 4
+ NULL);
+ }
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_Death
+==================
+*/
+int BotChat_Death(bot_state_t *bs) {
+ char name[32];
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ rnd = 0;
+ //if fast chatting is off
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ }
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ //
+ if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS)
+ EasyClientName(bs->lastkilledby, name, 32);
+ else
+ strcpy(name, "[world]");
+ //
+ if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) {
+ if (bs->lastkilledby == bs->client) return qfalse;
+ BotAI_BotInitialChat(bs, "death_teammate", name, NULL);
+ bs->chatto = CHAT_TEAM;
+ }
+ else
+ {
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ //
+ if (bs->botdeathtype == MOD_WATER)
+ BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL);
+ else if (bs->botdeathtype == MOD_SLIME)
+ BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL);
+ else if (bs->botdeathtype == MOD_LAVA)
+ BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL);
+ else if (bs->botdeathtype == MOD_FALLING)
+ BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL);
+ else if (bs->botsuicide || //all other suicides by own weapon
+ bs->botdeathtype == MOD_CRUSH ||
+ bs->botdeathtype == MOD_SUICIDE ||
+ bs->botdeathtype == MOD_RESPAWN ||
+ bs->botdeathtype == MOD_TARGET_LASER ||
+ bs->botdeathtype == MOD_TRIGGER_HURT ||
+ bs->botdeathtype == MOD_UNKNOWN ||
+ bs->botdeathtype == MOD_EXPLOSION)
+ BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL);
+ else if (bs->botdeathtype == MOD_TELEFRAG)
+ BotAI_BotInitialChat(bs, "death_telefrag", name, NULL);
+ else {
+#if 0
+ if ((bs->botdeathtype == MOD_GAUNTLET ||
+ bs->botdeathtype == MOD_RAILGUN ||
+ bs->botdeathtype == MOD_BFG ||
+ bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) {
+
+ if (bs->botdeathtype == MOD_GAUNTLET)
+ BotAI_BotInitialChat(bs, "death_gauntlet",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ else if (bs->botdeathtype == MOD_RAILGUN)
+ BotAI_BotInitialChat(bs, "death_rail",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ else
+ BotAI_BotInitialChat(bs, "death_bfg",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ }
+ //choose between insult and praise
+ else
+#endif //0
+
+ if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) {
+ BotAI_BotInitialChat(bs, "death_insult",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "death_praise",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ }
+ }
+ bs->chatto = CHAT_ALL;
+ }
+ bs->lastchat_time = trap_AAS_Time();
+ return qtrue;
+}
+
+/*
+==================
+BotChat_Kill
+==================
+*/
+int BotChat_Kill(bot_state_t *bs) {
+ char name[32];
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ rnd = 0;
+ //if fast chat is off
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ }
+ if (bs->lastkilledplayer == bs->client) return qfalse;
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ if (!BotValidChatPosition(bs)) return qfalse;
+ //
+ if (BotVisibleEnemies(bs)) return qfalse;
+ //
+ EasyClientName(bs->lastkilledplayer, name, 32);
+ //
+ bs->chatto = CHAT_ALL;
+ if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) {
+ BotAI_BotInitialChat(bs, "kill_teammate", name, NULL);
+ bs->chatto = CHAT_TEAM;
+ }
+ else
+ {
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ //
+#if 0
+ if (bs->enemydeathtype == MOD_GAUNTLET) {
+ BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL);
+ }
+ else if (bs->enemydeathtype == MOD_RAILGUN) {
+ BotAI_BotInitialChat(bs, "kill_rail", name, NULL);
+ }
+#endif // 0
+
+ else if (bs->enemydeathtype == MOD_TELEFRAG) {
+ BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL);
+ }
+ //choose between insult and praise
+ else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) {
+ BotAI_BotInitialChat(bs, "kill_insult", name, NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "kill_praise", name, NULL);
+ }
+ }
+ bs->lastchat_time = trap_AAS_Time();
+ return qtrue;
+}
+
+/*
+==================
+BotChat_EnemySuicide
+==================
+*/
+int BotChat_EnemySuicide(bot_state_t *bs) {
+ char name[32];
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ //
+ rnd = 0;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ //if fast chat is off
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ }
+ if (!BotValidChatPosition(bs)) return qfalse;
+ //
+ if (BotVisibleEnemies(bs)) return qfalse;
+ //
+ if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32);
+ else strcpy(name, "");
+ BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL);
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_HitTalking
+==================
+*/
+int BotChat_HitTalking(bot_state_t *bs) {
+ char name[32], *weap;
+ int lasthurt_client;
+ float rnd;
+
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ lasthurt_client = g_entities[bs->client].client->lasthurt_client;
+ if (!lasthurt_client) return qfalse;
+ if (lasthurt_client == bs->client) return qfalse;
+ //
+ if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse;
+ //
+ rnd = 0;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ //if fast chat is off
+ if (!bot_fastchat.integer) {
+ if (random() > rnd * 0.5) return qfalse;
+ }
+ if (!BotValidChatPosition(bs)) return qfalse;
+ //
+ ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name));
+ weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client);
+ //
+ BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL);
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_HitNoDeath
+==================
+*/
+int BotChat_HitNoDeath(bot_state_t *bs) {
+ char name[32], *weap;
+ float rnd;
+ int lasthurt_client;
+ aas_entityinfo_t entinfo;
+
+ lasthurt_client = g_entities[bs->client].client->lasthurt_client;
+ if (!lasthurt_client) return qfalse;
+ if (lasthurt_client == bs->client) return qfalse;
+ //
+ if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse;
+ //
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ rnd = 0;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ //if fast chat is off
+ if (!bot_fastchat.integer) {
+ if (random() > rnd * 0.5) return qfalse;
+ }
+ if (!BotValidChatPosition(bs)) return qfalse;
+ //
+ if (BotVisibleEnemies(bs)) return qfalse;
+ //
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityIsShooting(&entinfo)) return qfalse;
+ //
+ ClientName(lasthurt_client, name, sizeof(name));
+ weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod);
+ //
+ BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL);
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_HitNoKill
+==================
+*/
+int BotChat_HitNoKill(bot_state_t *bs) {
+ char name[32], *weap;
+ float rnd;
+ aas_entityinfo_t entinfo;
+
+ if (bot_nochat.integer) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ rnd = 0;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ //if fast chat is off
+ if (!bot_fastchat.integer) {
+ if (random() > rnd * 0.5) return qfalse;
+ }
+ if (!BotValidChatPosition(bs)) return qfalse;
+ //
+ if (BotVisibleEnemies(bs)) return qfalse;
+ //
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityIsShooting(&entinfo)) return qfalse;
+ //
+ ClientName(bs->enemy, name, sizeof(name));
+ weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod);
+ //
+ BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL);
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChat_Random
+==================
+*/
+int BotChat_Random(bot_state_t *bs) {
+ float rnd;
+ char name[32];
+
+ if (bot_nochat.integer) return qfalse;
+ if (BotIsObserver(bs)) return qfalse;
+ if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse;
+ //don't chat in teamplay
+ if (TeamPlayIsOn()) return qfalse;
+ //don't chat when doing something important :)
+ if (bs->ltgtype == LTG_TEAMHELP ||
+ bs->ltgtype == LTG_TEAMACCOMPANY ||
+ bs->ltgtype == LTG_RUSHBASE) return qfalse;
+ //
+ rnd = 0;
+ if (random() > bs->thinktime * 0.1) return qfalse;
+ if (!bot_fastchat.integer) {
+ if (random() > rnd) return qfalse;
+ if (random() > 0.25) return qfalse;
+ }
+ if (BotNumActivePlayers() <= 1) return qfalse;
+ if (!BotValidChatPosition(bs)) return qfalse;
+ //
+ if (bs->lastkilledplayer == bs->client) {
+ strcpy(name, BotRandomOpponentName(bs));
+ }
+ else {
+ EasyClientName(bs->lastkilledplayer, name, sizeof(name));
+ }
+ //
+ if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) {
+ BotAI_BotInitialChat(bs, "random_misc",
+ BotRandomOpponentName(bs), // 0
+ name, // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ BotRandomWeaponName(), // 5
+ NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "random_insult",
+ BotRandomOpponentName(bs), // 0
+ name, // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ BotRandomWeaponName(), // 5
+ NULL);
+ }
+ bs->lastchat_time = trap_AAS_Time();
+ bs->chatto = CHAT_ALL;
+ return qtrue;
+}
+
+/*
+==================
+BotChatTime
+==================
+*/
+float BotChatTime(bot_state_t *bs) {
+ int cpm;
+
+ cpm = 4000;
+ return 4000;
+}
+
+/*
+==================
+BotChatTest
+==================
+*/
+void BotChatTest(bot_state_t *bs) {
+
+ char name[32];
+ char *weap;
+ int num, i;
+
+ num = trap_BotNumInitialChats(bs->cs, "game_enter");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "game_enter",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "game_exit");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "game_exit",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "level_start");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "level_start",
+ EasyClientName(bs->client, name, 32), // 0
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "level_end_victory");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "level_end_victory",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ BotFirstClientInRankings(), // 2
+ BotLastClientInRankings(), // 3
+ BotMapTitle(), // 4
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "level_end_lose");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "level_end_lose",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ BotFirstClientInRankings(), // 2
+ BotLastClientInRankings(), // 3
+ BotMapTitle(), // 4
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "level_end");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "level_end",
+ EasyClientName(bs->client, name, 32), // 0
+ BotRandomOpponentName(bs), // 1
+ BotFirstClientInRankings(), // 2
+ BotLastClientInRankings(), // 3
+ BotMapTitle(), // 4
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ EasyClientName(bs->lastkilledby, name, sizeof(name));
+ num = trap_BotNumInitialChats(bs->cs, "death_drown");
+ for (i = 0; i < num; i++)
+ {
+ //
+ BotAI_BotInitialChat(bs, "death_drown", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_slime");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_slime", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_lava");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_lava", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_cratered");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_cratered", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_suicide");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_suicide", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_telefrag");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_telefrag", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_gauntlet");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_gauntlet",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_rail");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_rail",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_bfg");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_bfg",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_insult");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_insult",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "death_praise");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "death_praise",
+ name, // 0
+ BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ //
+ EasyClientName(bs->lastkilledplayer, name, 32);
+ //
+ num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet");
+ for (i = 0; i < num; i++)
+ {
+ //
+ BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "kill_rail");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "kill_rail", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "kill_telefrag");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "kill_insult");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "kill_insult", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "kill_praise");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "kill_praise", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "enemy_suicide");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name));
+ weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client);
+ num = trap_BotNumInitialChats(bs->cs, "hit_talking");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "hit_nodeath");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "hit_nokill");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ //
+ if (bs->lastkilledplayer == bs->client) {
+ strcpy(name, BotRandomOpponentName(bs));
+ }
+ else {
+ EasyClientName(bs->lastkilledplayer, name, sizeof(name));
+ }
+ //
+ num = trap_BotNumInitialChats(bs->cs, "random_misc");
+ for (i = 0; i < num; i++)
+ {
+ //
+ BotAI_BotInitialChat(bs, "random_misc",
+ BotRandomOpponentName(bs), // 0
+ name, // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ BotRandomWeaponName(), // 5
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+ num = trap_BotNumInitialChats(bs->cs, "random_insult");
+ for (i = 0; i < num; i++)
+ {
+ BotAI_BotInitialChat(bs, "random_insult",
+ BotRandomOpponentName(bs), // 0
+ name, // 1
+ "[invalid var]", // 2
+ "[invalid var]", // 3
+ BotMapTitle(), // 4
+ BotRandomWeaponName(), // 5
+ NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+}
diff --git a/game/ai_chat.h b/game/ai_chat.h
new file mode 100644
index 0000000..b94fb77
--- /dev/null
+++ b/game/ai_chat.h
@@ -0,0 +1,45 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_chat.h
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_chat.h $
+ * $Author: Jmonroe $
+ * $Revision: 1 $
+ * $Modtime: 1/21/00 10:12p $
+ * $Date: 1/25/00 6:26p $
+ *
+ *****************************************************************************/
+
+//
+int BotChat_EnterGame(bot_state_t *bs);
+//
+int BotChat_ExitGame(bot_state_t *bs);
+//
+int BotChat_StartLevel(bot_state_t *bs);
+//
+int BotChat_EndLevel(bot_state_t *bs);
+//
+int BotChat_HitTalking(bot_state_t *bs);
+//
+int BotChat_HitNoDeath(bot_state_t *bs);
+//
+int BotChat_HitNoKill(bot_state_t *bs);
+//
+int BotChat_Death(bot_state_t *bs);
+//
+int BotChat_Kill(bot_state_t *bs);
+//
+int BotChat_EnemySuicide(bot_state_t *bs);
+//
+int BotChat_Random(bot_state_t *bs);
+//! time the selected chat takes to type in
+float BotChatTime(bot_state_t *bs);
+//! returns true if the bot can chat at the current position
+int BotValidChatPosition(bot_state_t *bs);
+//! test the initial bot chats
+void BotChatTest(bot_state_t *bs);
+
diff --git a/game/ai_cmd.c b/game/ai_cmd.c
new file mode 100644
index 0000000..1a5a021
--- /dev/null
+++ b/game/ai_cmd.c
@@ -0,0 +1,1560 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_cmd.c
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_cmd.c $
+ * $Author: Dkramer $
+ * $Revision: 3 $
+ * $Modtime: 5/09/00 4:04p $
+ * $Date: 5/09/00 4:14p $
+ *
+ *****************************************************************************/
+
+#include "g_local.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_ea.h"
+#include "be_ai_char.h"
+#include "be_ai_chat.h"
+#include "be_ai_gen.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+#include "be_ai_weap.h"
+//
+#include "ai_main.h"
+#include "ai_dmq3.h"
+#include "ai_chat.h"
+#include "ai_cmd.h"
+#include "ai_dmnet.h"
+#include "ai_team.h"
+//
+#include "chars.h" //characteristics
+#include "inv.h" //indexes into the inventory
+#include "syn.h" //synonyms
+#include "match.h" //string matching types and vars
+
+
+#ifdef DEBUG
+/*
+==================
+BotPrintTeamGoal
+==================
+*/
+void BotPrintTeamGoal(bot_state_t *bs) {
+ char netname[MAX_NETNAME];
+ float t;
+
+ ClientName(bs->client, netname, sizeof(netname));
+ t = bs->teamgoal_time - trap_AAS_Time();
+ switch(bs->ltgtype) {
+ case LTG_TEAMHELP:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_TEAMACCOMPANY:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_GETFLAG:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_RUSHBASE:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_RETURNFLAG:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_DEFENDKEYAREA:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_GETITEM:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_KILL:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_CAMP:
+ case LTG_CAMPORDER:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t);
+ break;
+ }
+ case LTG_PATROL:
+ {
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t);
+ break;
+ }
+ default:
+ {
+ if (bs->ctfroam_time > trap_AAS_Time()) {
+ t = bs->ctfroam_time - trap_AAS_Time();
+ BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t);
+ }
+ else {
+ BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname);
+ }
+ }
+ }
+}
+#endif //DEBUG
+
+/*
+==================
+BotGetItemTeamGoal
+
+FIXME: add stuff like "upper rocket launcher"
+"the rl near the railgun", "lower grenade launcher" etc.
+==================
+*/
+int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) {
+ int i;
+
+ if (!strlen(goalname)) return qfalse;
+ i = -1;
+ do {
+ i = trap_BotGetLevelItemGoal(i, goalname, goal);
+ if (i > 0) {
+ //do NOT defend dropped items
+ if (goal->flags & GFL_DROPPED)
+ continue;
+ return qtrue;
+ }
+ } while(i > 0);
+ return qfalse;
+}
+
+/*
+==================
+BotGetMessageTeamGoal
+==================
+*/
+int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) {
+ bot_waypoint_t *cp;
+
+ if (BotGetItemTeamGoal(goalname, goal)) return qtrue;
+
+ cp = BotFindWayPoint(bs->checkpoints, goalname);
+ if (cp) {
+ memcpy(goal, &cp->goal, sizeof(bot_goal_t));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotGetTime
+==================
+*/
+float BotGetTime(bot_match_t *match) {
+ bot_match_t timematch;
+ char timestring[MAX_MESSAGE_SIZE];
+ float t;
+
+ //if the matched string has a time
+ if (match->subtype & ST_TIME) {
+ //get the time string
+ trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE);
+ //match it to find out if the time is in seconds or minutes
+ if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) {
+ if (timematch.type == MSG_FOREVER) {
+ t = 99999999;
+ }
+ else {
+ trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE);
+ if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60;
+ else if (timematch.type == MSG_SECONDS) t = atof(timestring);
+ else t = 0;
+ }
+ //if there's a valid time
+ if (t > 0) return trap_AAS_Time() + t;
+ }
+ }
+ return 0;
+}
+
+/*
+==================
+FindClientByName
+==================
+*/
+int FindClientByName(char *name) {
+ int i;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ ClientName(i, buf, sizeof(buf));
+ if (!Q_stricmp(buf, name)) return i;
+ }
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ ClientName(i, buf, sizeof(buf));
+ if (stristr(buf, name)) return i;
+ }
+ return -1;
+}
+
+/*
+==================
+FindEnemyByName
+==================
+*/
+int FindEnemyByName(bot_state_t *bs, char *name) {
+ int i;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ if (BotSameTeam(bs, i)) continue;
+ ClientName(i, buf, sizeof(buf));
+ if (!Q_stricmp(buf, name)) return i;
+ }
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ if (BotSameTeam(bs, i)) continue;
+ ClientName(i, buf, sizeof(buf));
+ if (stristr(buf, name)) return i;
+ }
+ return -1;
+}
+
+/*
+==================
+NumPlayersOnSameTeam
+==================
+*/
+int NumPlayersOnSameTeam(bot_state_t *bs) {
+ int i, num;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ num = 0;
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING);
+ if (strlen(buf)) {
+ if (BotSameTeam(bs, i+1)) num++;
+ }
+ }
+ return num;
+}
+
+/*
+==================
+TeamPlayIsOn
+==================
+*/
+int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) {
+ char keyarea[MAX_MESSAGE_SIZE];
+ int patrolflags;
+ bot_waypoint_t *wp, *newwp, *newpatrolpoints;
+ bot_match_t keyareamatch;
+ bot_goal_t goal;
+
+ newpatrolpoints = NULL;
+ patrolflags = 0;
+ //
+ trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE);
+ //
+ while(1) {
+ if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) {
+ trap_EA_SayTeam(bs->client, "what do you say?");
+ BotFreeWaypoints(newpatrolpoints);
+ bs->patrolpoints = NULL;
+ return qfalse;
+ }
+ trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE);
+ if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ BotFreeWaypoints(newpatrolpoints);
+ bs->patrolpoints = NULL;
+ return qfalse;
+ }
+ //create a new waypoint
+ newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum);
+ //add the waypoint to the patrol points
+ newwp->next = NULL;
+ for (wp = newpatrolpoints; wp && wp->next; wp = wp->next);
+ if (!wp) {
+ newpatrolpoints = newwp;
+ newwp->prev = NULL;
+ }
+ else {
+ wp->next = newwp;
+ newwp->prev = wp;
+ }
+ //
+ if (keyareamatch.subtype & ST_BACK) {
+ patrolflags = PATROL_LOOP;
+ break;
+ }
+ else if (keyareamatch.subtype & ST_REVERSE) {
+ patrolflags = PATROL_REVERSE;
+ break;
+ }
+ else if (keyareamatch.subtype & ST_MORE) {
+ trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE);
+ }
+ else {
+ break;
+ }
+ }
+ //
+ if (!newpatrolpoints || !newpatrolpoints->next) {
+ trap_EA_SayTeam(bs->client, "I need more key points to patrol\n");
+ BotFreeWaypoints(newpatrolpoints);
+ newpatrolpoints = NULL;
+ return qfalse;
+ }
+ //
+ BotFreeWaypoints(bs->patrolpoints);
+ bs->patrolpoints = newpatrolpoints;
+ //
+ bs->curpatrolpoint = bs->patrolpoints;
+ bs->patrolflags = patrolflags;
+ //
+ return qtrue;
+}
+
+/*
+==================
+BotAddressedToBot
+==================
+*/
+int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) {
+ char addressedto[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ char name[MAX_MESSAGE_SIZE];
+ char botname[128];
+ int client;
+ bot_match_t addresseematch;
+
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ if (client < 0) return qfalse;
+ if (!BotSameTeam(bs, client)) return qfalse;
+ //if the message is addressed to someone
+ if (match->subtype & ST_ADDRESSED) {
+ trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto));
+ //the name of this bot
+ ClientName(bs->client, botname, 128);
+ //
+ while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) {
+ if (addresseematch.type == MSG_EVERYONE) {
+ return qtrue;
+ }
+ else if (addresseematch.type == MSG_MULTIPLENAMES) {
+ trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name));
+ if (strlen(name)) {
+ if (stristr(botname, name)) return qtrue;
+ if (stristr(bs->subteam, name)) return qtrue;
+ }
+ trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE);
+ }
+ else {
+ trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE);
+ if (strlen(name)) {
+ if (stristr(botname, name)) return qtrue;
+ if (stristr(bs->subteam, name)) return qtrue;
+ }
+ break;
+ }
+ }
+ //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto);
+ //trap_EA_Say(bs->client, buf);
+ return qfalse;
+ }
+ else {
+ //make sure not everyone reacts to this message
+ if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotGPSToPosition
+==================
+*/
+int BotGPSToPosition(char *buf, vec3_t position) {
+ int i, j = 0;
+ int num, sign;
+
+ for (i = 0; i < 3; i++) {
+ num = 0;
+ while(buf[j] == ' ') j++;
+ if (buf[j] == '-') {
+ j++;
+ sign = -1;
+ }
+ else {
+ sign = 1;
+ }
+ while (buf[j]) {
+ if (buf[j] >= '0' && buf[j] <= '9') {
+ num = num * 10 + buf[j] - '0';
+ j++;
+ }
+ else {
+ j++;
+ break;
+ }
+ }
+ BotAI_Print(PRT_MESSAGE, "%d\n", sign * num);
+ position[i] = (float) sign * num;
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotMatch_HelpAccompany
+==================
+*/
+void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) {
+ int client, other, areanum;
+ char teammate[MAX_MESSAGE_SIZE], netname[MAX_MESSAGE_SIZE];
+ char itemname[MAX_MESSAGE_SIZE];
+ bot_match_t teammatematch;
+ aas_entityinfo_t entinfo;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the team mate name
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ //get the client to help
+ if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) &&
+ //if someone asks for him or herself
+ teammatematch.type == MSG_ME) {
+ //get the netname
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ other = qfalse;
+ }
+ else {
+ //asked for someone else
+ client = FindClientByName(teammate);
+ //if this is the bot self
+ if (client == bs->client) {
+ other = qfalse;
+ }
+ else if (!BotSameTeam(bs, client)) {
+ //FIXME: say "I don't help the enemy"
+ return;
+ }
+ else {
+ other = qtrue;
+ }
+ }
+ //if the bot doesn't know who to help (FindClientByName returned -1)
+ if (client < 0) {
+ if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL);
+ else BotAI_BotInitialChat(bs, "whois", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //don't help or accompany yourself
+ if (client == bs->client) {
+ return;
+ }
+ //
+ bs->teamgoal.entitynum = -1;
+ BotEntityInfo(client, &entinfo);
+ //if info is valid (in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ bs->teamgoal.entitynum = client;
+ bs->teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ }
+ }
+ //if no teamgoal yet
+ if (bs->teamgoal.entitynum < 0) {
+ //if near an item
+ if (match->subtype & ST_NEARITEM) {
+ //get the match variable
+ trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname));
+ //
+ if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ }
+ }
+ //
+ if (bs->teamgoal.entitynum < 0) {
+ if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL);
+ else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //the team mate
+ bs->teammate = client;
+ //last time the team mate was assumed visible
+ bs->teammatevisible_time = trap_AAS_Time();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the ltg type
+ if (match->type == MSG_HELP) {
+ bs->ltgtype = LTG_TEAMHELP;
+ if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_HELP_TIME;
+ }
+ else {
+ bs->ltgtype = LTG_TEAMACCOMPANY;
+ if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME;
+ bs->formation_dist = 3.5 * 32; //3.5 meter
+ bs->arrive_time = 0;
+ }
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_DefendKeyArea
+==================
+*/
+void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) {
+ char itemname[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the match variable
+ trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname));
+ //
+ if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_DEFENDKEYAREA;
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the team goal time
+ if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME;
+ //away from defending
+ bs->defendaway_time = 0;
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_GetItem
+==================
+*/
+void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) {
+ char itemname[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the match variable
+ trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname));
+ //
+ if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_GETITEM;
+ //set the team goal time
+ bs->teamgoal_time = trap_AAS_Time() + TEAM_GETITEM_TIME;
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_Camp
+==================
+*/
+void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) {
+ int client, areanum;
+ char netname[MAX_MESSAGE_SIZE];
+ char itemname[MAX_MESSAGE_SIZE];
+ aas_entityinfo_t entinfo;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ //asked for someone else
+ client = FindClientByName(netname);
+ //if there's no valid client with this name
+ if (client < 0) {
+ BotAI_BotInitialChat(bs, "whois", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //get the match variable
+ trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname));
+ //in CTF it could be the base
+ if (match->subtype & ST_THERE) {
+ //camp at the spot the bot is currently standing
+ bs->teamgoal.entitynum = bs->entitynum;
+ bs->teamgoal.areanum = bs->areanum;
+ VectorCopy(bs->origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ }
+ else if (match->subtype & ST_HERE) {
+ //if this is the bot self
+ if (client == bs->client) return;
+ //
+ bs->teamgoal.entitynum = -1;
+ BotEntityInfo(client, &entinfo);
+ //if info is valid (in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ //NOTE: just cheat and assume the bot knows where the person is
+ //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) {
+ bs->teamgoal.entitynum = client;
+ bs->teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ //}
+ }
+ }
+ //if the other is not visible
+ if (bs->teamgoal.entitynum < 0) {
+ BotAI_BotInitialChat(bs, "whereareyou", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ }
+ else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) {
+ //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL);
+ //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_CAMPORDER;
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the team goal time
+ if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_CAMP_TIME;
+ //the teammate that requested the camping
+ bs->teammate = client;
+ //not arrived yet
+ bs->arrive_time = 0;
+ //
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_Patrol
+==================
+*/
+void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) {
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the patrol waypoints
+ if (!BotGetPatrolWaypoints(bs, match)) return;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_PATROL;
+ //get the team goal time
+ bs->teamgoal_time = BotGetTime(match);
+ //set the team goal time if not set already
+ if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_PATROL_TIME;
+ //
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_GetFlag
+==================
+*/
+void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) {
+ //if not in CTF mode
+ if (gametype != GT_CTF || !ctf_redflag.areanum || !ctf_blueflag.areanum) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_GETFLAG;
+ //set the team goal time
+ bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_RushBase
+==================
+*/
+void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) {
+ //if not in CTF mode
+ if (gametype != GT_CTF || !ctf_redflag.areanum || !ctf_blueflag.areanum) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_RUSHBASE;
+ //set the team goal time
+ bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
+ bs->rushbaseaway_time = 0;
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_TaskPreference
+==================
+*/
+void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_NETNAME];
+ char teammatename[MAX_MESSAGE_SIZE];
+ int teammate, preference;
+
+ ClientName(bs->client, netname, sizeof(netname));
+ if (Q_stricmp(netname, bs->teamleader) != 0) return;
+
+ trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename));
+ teammate = ClientFromName(teammatename);
+ if (teammate < 0) return;
+
+ preference = BotGetTeamMateCTFPreference(bs, teammate);
+ switch(match->subtype)
+ {
+ case ST_DEFENDER:
+ {
+ preference &= ~CTFTP_ATTACKER;
+ preference |= CTFTP_DEFENDER;
+ break;
+ }
+ case ST_ATTACKER:
+ {
+ preference &= ~CTFTP_DEFENDER;
+ preference |= CTFTP_ATTACKER;
+ break;
+ }
+ case ST_ROAMER:
+ {
+ preference &= ~(CTFTP_ATTACKER|CTFTP_DEFENDER);
+ break;
+ }
+ }
+ BotSetTeamMateCTFPreference(bs, teammate, preference);
+ //
+ EasyClientName(teammate, teammatename, sizeof(teammatename));
+ BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+}
+
+/*
+==================
+BotMatch_ReturnFlag
+==================
+*/
+void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) {
+ //if not in CTF mode
+ if (gametype != GT_CTF) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_RETURNFLAG;
+ //set the team goal time
+ bs->teamgoal_time = trap_AAS_Time() + CTF_RETURNFLAG_TIME;
+ bs->rushbaseaway_time = 0;
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_JoinSubteam
+==================
+*/
+void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) {
+ char teammate[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //get the sub team name
+ trap_BotMatchVariable(match, TEAMNAME, teammate, MAX_MESSAGE_SIZE);
+ //set the sub team name
+ strncpy(bs->subteam, teammate, 32);
+ bs->subteam[31] = '\0';
+ //
+ BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+}
+
+/*
+==================
+BotMatch_LeaveSubteam
+==================
+*/
+void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) {
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ if (strlen(bs->subteam))
+ {
+ BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL);
+ } //end if
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ strcpy(bs->subteam, "");
+}
+
+/*
+==================
+BotMatch_LeaveSubteam
+==================
+*/
+void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) {
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ if (strlen(bs->subteam)) {
+ BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "noteam", NULL);
+ }
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+}
+
+/*
+==================
+BotMatch_CheckPoint
+==================
+*/
+void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) {
+ int areanum;
+ char buf[MAX_MESSAGE_SIZE];
+ vec3_t position;
+ bot_waypoint_t *cp;
+ int i;
+
+ if (!TeamPlayIsOn()) return;
+ //
+ trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE);
+ VectorClear(position);
+ //BotGPSToPosition(buf, position);
+ i = sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]);
+ position[2] += 0.5;
+ areanum = BotPointAreaNum(position);
+ if (!areanum) {
+ if (BotAddressedToBot(bs, match)) {
+ BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+ return;
+ }
+ //
+ trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE);
+ //check if there already exists a checkpoint with this name
+ cp = BotFindWayPoint(bs->checkpoints, buf);
+ if (cp) {
+ if (cp->next) cp->next->prev = cp->prev;
+ if (cp->prev) cp->prev->next = cp->next;
+ else bs->checkpoints = cp->next;
+ cp->inuse = qfalse;
+ }
+ //create a new check point
+ cp = BotCreateWayPoint(buf, position, areanum);
+ //add the check point to the bot's known chech points
+ cp->next = bs->checkpoints;
+ if (bs->checkpoints) bs->checkpoints->prev = cp;
+ bs->checkpoints = cp;
+ //
+ if (BotAddressedToBot(bs, match)) {
+ Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0],
+ cp->goal.origin[1],
+ cp->goal.origin[2]);
+
+ BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+}
+
+/*
+==================
+BotMatch_FormationSpace
+==================
+*/
+void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) {
+ char buf[MAX_MESSAGE_SIZE];
+ float space;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE);
+ //if it's the distance in feet
+ if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf);
+ //else it's in meters
+ else space = 32 * atof(buf);
+ //check if the formation intervening space is valid
+ if (space < 48 || space > 500) space = 100;
+ bs->formation_dist = space;
+}
+
+/*
+==================
+BotMatch_Dismiss
+==================
+*/
+void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) {
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ bs->ltgtype = 0;
+ bs->lead_time = 0;
+ //
+ BotAI_BotInitialChat(bs, "dismissed", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+}
+
+/*
+==================
+BotMatch_StartTeamLeaderShip
+==================
+*/
+void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) {
+ int client;
+ char teammate[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+ //if chats for him or herself
+ if (match->subtype & ST_I) {
+ //get the team mate that will be the team leader
+ trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate));
+ strncpy(bs->teamleader, teammate, sizeof(bs->teamleader));
+ bs->teamleader[sizeof(bs->teamleader)-1] = '\0';
+ }
+ //chats for someone else
+ else {
+ //get the team mate that will be the team leader
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ client = FindClientByName(teammate);
+ if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader));
+ }
+}
+
+/*
+==================
+BotMatch_StopTeamLeaderShip
+==================
+*/
+void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) {
+ int client;
+ char teammate[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+ //get the team mate that stops being the team leader
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ //if chats for him or herself
+ if (match->subtype & ST_I) {
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = FindClientByName(netname);
+ }
+ //chats for someone else
+ else {
+ client = FindClientByName(teammate);
+ } //end else
+ if (client >= 0) {
+ if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) {
+ bs->teamleader[0] = '\0';
+ }
+ }
+}
+
+/*
+==================
+BotMatch_WhoIsTeamLeader
+==================
+*/
+void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+
+ if (!TeamPlayIsOn()) return;
+
+ ClientName(bs->client, netname, sizeof(netname));
+ //if this bot IS the team leader
+ if (!Q_stricmp(netname, bs->teamleader)) {
+ trap_EA_SayTeam(bs->client, "I'm the team leader\n");
+ }
+}
+
+/*
+==================
+BotMatch_WhatAreYouDoing
+==================
+*/
+void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_MESSAGE_SIZE];
+ char goalname[MAX_MESSAGE_SIZE];
+
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //
+ switch(bs->ltgtype) {
+ case LTG_TEAMHELP:
+ {
+ EasyClientName(bs->teammate, netname, sizeof(netname));
+ BotAI_BotInitialChat(bs, "helping", netname, NULL);
+ break;
+ }
+ case LTG_TEAMACCOMPANY:
+ {
+ EasyClientName(bs->teammate, netname, sizeof(netname));
+ BotAI_BotInitialChat(bs, "accompanying", netname, NULL);
+ break;
+ }
+ case LTG_DEFENDKEYAREA:
+ {
+ trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
+ BotAI_BotInitialChat(bs, "defending", goalname, NULL);
+ break;
+ }
+ case LTG_GETITEM:
+ {
+ trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
+ BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL);
+ break;
+ }
+ case LTG_KILL:
+ {
+ ClientName(bs->teamgoal.entitynum, netname, sizeof(netname));
+ BotAI_BotInitialChat(bs, "killing", netname, NULL);
+ break;
+ }
+ case LTG_CAMP:
+ case LTG_CAMPORDER:
+ {
+ BotAI_BotInitialChat(bs, "camping", NULL);
+ break;
+ }
+ case LTG_PATROL:
+ {
+ BotAI_BotInitialChat(bs, "patrolling", NULL);
+ break;
+ }
+ case LTG_GETFLAG:
+ {
+ BotAI_BotInitialChat(bs, "capturingflag", NULL);
+ break;
+ }
+ case LTG_RUSHBASE:
+ {
+ BotAI_BotInitialChat(bs, "rushingbase", NULL);
+ break;
+ }
+ case LTG_RETURNFLAG:
+ {
+ BotAI_BotInitialChat(bs, "returningflag", NULL);
+ break;
+ }
+ default:
+ {
+ BotAI_BotInitialChat(bs, "roaming", NULL);
+ break;
+ }
+ }
+ //chat what the bot is doing
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+}
+
+/*
+==================
+BotMatch_WhatIsMyCommand
+==================
+*/
+void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) {
+ char netname[MAX_NETNAME];
+
+ ClientName(bs->client, netname, sizeof(netname));
+ if (Q_stricmp(netname, bs->teamleader) != 0) return;
+ bs->forceorders = qtrue;
+}
+
+/*
+==================
+BotNearestVisibleItem
+==================
+*/
+float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) {
+ int i;
+ char name[64];
+ bot_goal_t tmpgoal;
+ float dist, bestdist;
+ vec3_t dir;
+ bsp_trace_t trace;
+
+ bestdist = 999999;
+ i = -1;
+ do {
+ i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal);
+ trap_BotGoalName(tmpgoal.number, name, sizeof(name));
+ if (Q_stricmp(itemname, name) != 0) continue;
+ VectorSubtract(tmpgoal.origin, bs->origin, dir);
+ dist = VectorLength(dir);
+ if (dist < bestdist) {
+ //trace from start to end
+ BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
+ if (trace.fraction >= 1.0) {
+ bestdist = dist;
+ memcpy(goal, &tmpgoal, sizeof(bot_goal_t));
+ }
+ }
+ } while(i > 0);
+ return bestdist;
+}
+
+/*
+==================
+BotMatch_WhereAreYou
+==================
+*/
+void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) {
+ float dist, bestdist;
+ int i, bestitem, redflagtt, blueflagtt, redtobluett;
+ bot_goal_t goal;
+ char *nearbyitems[] = {
+ "Phaser Compression Rifle",
+ "I-MOD",
+ "Scavenger Weapon",
+ "Stasis Weapon",
+ "Compound Grenade Launcher",
+ "Tetryon Pulse Disruptor",
+ "Dermal Regenerator",
+ "Photon Burst",
+ "Quantum Weapon Enhancer",//fixme!
+ "Nano-Regenerative Protoplasmer",
+ "Metaphasic Shielding",
+ "Temporal Accelerator",
+ "Personal Cloaking Device",
+ "Anti-Gravity Pack",
+ "Personal Deflector Screen",
+ "Isokinetic Deflector Screen",
+ "Red Flag",
+ "Blue Flag",
+ NULL
+ };
+ //
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+
+ bestitem = -1;
+ bestdist = 999999;
+ for (i = 0; nearbyitems[i]; i++) {
+ dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal);
+ if (dist < bestdist) {
+ bestdist = dist;
+ bestitem = i;
+ }
+ }
+ if (bestitem != -1) {
+ if (gametype == GT_CTF) {
+ redflagtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT);
+ blueflagtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT);
+ redtobluett = trap_AAS_AreaTravelTimeToGoalArea(ctf_redflag.areanum, ctf_redflag.origin, ctf_blueflag.areanum, TFL_DEFAULT);
+ if (redflagtt < (redflagtt + blueflagtt) * 0.4) {
+ BotAI_BotInitialChat(bs, "ctflocation", nearbyitems[bestitem], "red", NULL);
+ }
+ else if (blueflagtt < (redflagtt + blueflagtt) * 0.4) {
+ BotAI_BotInitialChat(bs, "ctflocation", nearbyitems[bestitem], "blue", NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL);
+ }
+ }
+ else {
+ BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL);
+ }
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+}
+
+/*
+==================
+BotMatch_LeadTheWay
+==================
+*/
+void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) {
+ aas_entityinfo_t entinfo;
+ char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE];
+ int client, areanum, other;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+ //if someone asks for someone else
+ if (match->subtype & ST_SOMEONE) {
+ //get the team mate name
+ trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate));
+ client = FindClientByName(teammate);
+ //if this is the bot self
+ if (client == bs->client) {
+ other = qfalse;
+ }
+ else if (!BotSameTeam(bs, client)) {
+ //FIXME: say "I don't help the enemy"
+ return;
+ }
+ else {
+ other = qtrue;
+ }
+ }
+ else {
+ //get the netname
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ client = ClientFromName(netname);
+ other = qfalse;
+ }
+ //if the bot doesn't know who to help (FindClientByName returned -1)
+ if (client < 0) {
+ BotAI_BotInitialChat(bs, "whois", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ //
+ bs->lead_teamgoal.entitynum = -1;
+ BotEntityInfo(client, &entinfo);
+ //if info is valid (in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ bs->lead_teamgoal.entitynum = client;
+ bs->lead_teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->lead_teamgoal.origin);
+ VectorSet(bs->lead_teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8);
+ }
+ }
+
+ if (bs->teamgoal.entitynum < 0) {
+ if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL);
+ else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ bs->lead_teammate = client;
+ bs->lead_time = trap_AAS_Time() + TEAM_LEAD_TIME;
+ bs->leadvisible_time = 0;
+ bs->leadmessage_time = -(trap_AAS_Time() + 2 * random());
+}
+
+/*
+==================
+BotMatch_Kill
+==================
+*/
+void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) {
+ char enemy[MAX_MESSAGE_SIZE];
+ int client;
+
+ if (!TeamPlayIsOn()) return;
+ //if not addressed to this bot
+ if (!BotAddressedToBot(bs, match)) return;
+
+ trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy));
+ //
+ client = FindEnemyByName(bs, enemy);
+ if (client < 0) {
+ BotAI_BotInitialChat(bs, "whois", enemy, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ return;
+ }
+ bs->teamgoal.entitynum = client;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //set the ltg type
+ bs->ltgtype = LTG_KILL;
+ //set the team goal time
+ bs->teamgoal_time = trap_AAS_Time() + TEAM_KILL_SOMEONE;
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+/*
+==================
+BotMatch_CTF
+==================
+*/
+void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) {
+
+ char flag[128], netname[MAX_NETNAME];
+
+ trap_BotMatchVariable(match, FLAG, flag, sizeof(flag));
+ if (match->subtype & ST_GOTFLAG) {
+ if (!Q_stricmp(flag, "red")) {
+ bs->redflagstatus = 1;
+ if (BotCTFTeam(bs) == CTF_TEAM_BLUE) {
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ bs->flagcarrier = ClientFromName(netname);
+ }
+ }
+ else {
+ bs->blueflagstatus = 1;
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) {
+ trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname));
+ bs->flagcarrier = ClientFromName(netname);
+ }
+ }
+ bs->flagstatuschanged = 1;
+ bs->lastflagcapture_time = trap_AAS_Time();
+ }
+ else if (match->subtype & ST_CAPTUREDFLAG) {
+ bs->redflagstatus = 0;
+ bs->blueflagstatus = 0;
+ bs->flagcarrier = 0;
+ bs->flagstatuschanged = 1;
+ }
+ else if (match->subtype & ST_RETURNEDFLAG) {
+ if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0;
+ else bs->blueflagstatus = 0;
+ bs->flagstatuschanged = 1;
+ }
+}
+
+/*
+==================
+BotMatchMessage
+==================
+*/
+int BotMatchMessage(bot_state_t *bs, char *message) {
+ bot_match_t match;
+
+ match.type = 0;
+ //if it is an unknown message
+ if (!trap_BotFindMatch(message, &match, MTCONTEXT_ENTERGAME
+ |MTCONTEXT_INITIALTEAMCHAT
+ |MTCONTEXT_CTF)) {
+ return qfalse;
+ }
+ //react to the found message
+ switch(match.type)
+ {
+ case MSG_HELP: //someone calling for help
+ case MSG_ACCOMPANY: //someone calling for company
+ {
+ BotMatch_HelpAccompany(bs, &match);
+ break;
+ }
+ case MSG_DEFENDKEYAREA: //teamplay defend a key area
+ {
+ BotMatch_DefendKeyArea(bs, &match);
+ break;
+ }
+ case MSG_CAMP: //camp somewhere
+ {
+ BotMatch_Camp(bs, &match);
+ break;
+ }
+ case MSG_PATROL: //patrol between several key areas
+ {
+ BotMatch_Patrol(bs, &match);
+ break;
+ }
+#ifdef CTF
+ case MSG_GETFLAG: //ctf get the enemy flag
+ {
+ BotMatch_GetFlag(bs, &match);
+ break;
+ }
+ case MSG_RUSHBASE: //ctf rush to the base
+ {
+ BotMatch_RushBase(bs, &match);
+ break;
+ }
+ case MSG_RETURNFLAG:
+ {
+ BotMatch_ReturnFlag(bs, &match);
+ break;
+ }
+ case MSG_CTFTASKPREFERENCE:
+ {
+ BotMatch_TaskPreference(bs, &match);
+ break;
+ }
+ case MSG_CTF:
+ {
+ BotMatch_CTF(bs, &match);
+ break;
+ }
+#endif //CTF
+ case MSG_GETITEM:
+ {
+ BotMatch_GetItem(bs, &match);
+ break;
+ }
+ case MSG_JOINSUBTEAM: //join a sub team
+ {
+ BotMatch_JoinSubteam(bs, &match);
+ break;
+ }
+ case MSG_LEAVESUBTEAM: //leave a sub team
+ {
+ BotMatch_LeaveSubteam(bs, &match);
+ break;
+ }
+ case MSG_WHICHTEAM:
+ {
+ BotMatch_WhichTeam(bs, &match);
+ break;
+ }
+ case MSG_CHECKPOINT: //remember a check point
+ {
+ BotMatch_CheckPoint(bs, &match);
+ break;
+ }
+ case MSG_CREATENEWFORMATION: //start the creation of a new formation
+ {
+ trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged");
+ break;
+ }
+ case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation
+ {
+ trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged");
+ break;
+ }
+ case MSG_FORMATIONSPACE: //set the formation space
+ {
+ BotMatch_FormationSpace(bs, &match);
+ break;
+ }
+ case MSG_DOFORMATION: //form a certain formation
+ {
+ break;
+ }
+ case MSG_DISMISS: //dismiss someone
+ {
+ BotMatch_Dismiss(bs, &match);
+ break;
+ }
+ case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader
+ {
+ BotMatch_StartTeamLeaderShip(bs, &match);
+ break;
+ }
+ case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader
+ {
+ BotMatch_StopTeamLeaderShip(bs, &match);
+ break;
+ }
+ case MSG_WHOISTEAMLAEDER:
+ {
+ BotMatch_WhoIsTeamLeader(bs, &match);
+ break;
+ }
+ case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing
+ {
+ BotMatch_WhatAreYouDoing(bs, &match);
+ break;
+ }
+ case MSG_WHATISMYCOMMAND:
+ {
+ BotMatch_WhatIsMyCommand(bs, &match);
+ break;
+ }
+ case MSG_WHEREAREYOU:
+ {
+ BotMatch_WhereAreYou(bs, &match);
+ break;
+ }
+ case MSG_LEADTHEWAY:
+ {
+ BotMatch_LeadTheWay(bs, &match);
+ break;
+ }
+ case MSG_KILL:
+ {
+ BotMatch_Kill(bs, &match);
+ break;
+ }
+ case MSG_ENTERGAME: //someone entered the game
+ {
+ //NOTE: eliza chats will catch this
+ //BotMatchVariable(&match, NETNAME, netname);
+ //Com_sprintf(buf, sizeof(buf), "heya %s", netname);
+ //EA_Say(bs->client, buf);
+ break;
+ }
+ case MSG_WAIT:
+ {
+ break;
+ }
+ default:
+ {
+ BotAI_Print(PRT_MESSAGE, "unknown match type\n");
+ break;
+ }
+ }
+ return qtrue;
+}
diff --git a/game/ai_cmd.h b/game/ai_cmd.h
new file mode 100644
index 0000000..a4866be
--- /dev/null
+++ b/game/ai_cmd.h
@@ -0,0 +1,19 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_cmd.h
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_cmd.h $
+ * $Author: Jmonroe $
+ * $Revision: 1 $
+ * $Modtime: 1/21/00 10:12p $
+ * $Date: 1/25/00 6:26p $
+ *
+ *****************************************************************************/
+
+int BotMatchMessage(bot_state_t *bs, char *message);
+void BotPrintTeamGoal(bot_state_t *bs);
+
diff --git a/game/ai_dmnet.c b/game/ai_dmnet.c
new file mode 100644
index 0000000..e9fab71
--- /dev/null
+++ b/game/ai_dmnet.c
@@ -0,0 +1,2030 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_dmnet.c
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_dmnet.c $
+ * $Author: Mgummelt $
+ * $Revision: 7 $
+ * $Modtime: 3/28/01 11:12a $
+ * $Date: 3/28/01 11:15a $
+ *
+ *****************************************************************************/
+
+#include "g_local.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_ea.h"
+#include "be_ai_char.h"
+#include "be_ai_chat.h"
+#include "be_ai_gen.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+#include "be_ai_weap.h"
+//
+#include "ai_main.h"
+#include "ai_dmq3.h"
+#include "ai_chat.h"
+#include "ai_cmd.h"
+#include "ai_dmnet.h"
+//data file headers
+#include "chars.h" //characteristics
+#include "inv.h" //indexes into the inventory
+#include "syn.h" //synonyms
+#include "match.h" //string matching types and vars
+
+//goal flag, see be_ai_goal.h for the other GFL_*
+#define GFL_AIR 128
+
+int numnodeswitches;
+char nodeswitch[MAX_NODESWITCHES+1][144];
+
+#define LOOKAHEAD_DISTANCE 300
+
+/*
+==================
+BotResetNodeSwitches
+==================
+*/
+void BotResetNodeSwitches(void) {
+ numnodeswitches = 0;
+}
+
+/*
+==================
+BotDumpNodeSwitches
+==================
+*/
+void BotDumpNodeSwitches(bot_state_t *bs) {
+ int i;
+ char netname[MAX_NETNAME];
+
+ ClientName(bs->client, netname, sizeof(netname));
+ BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, trap_AAS_Time(), MAX_NODESWITCHES);
+ for (i = 0; i < numnodeswitches; i++) {
+ BotAI_Print(PRT_MESSAGE, nodeswitch[i]);
+ }
+ BotAI_Print(PRT_FATAL, "");
+}
+
+/*
+==================
+BotRecordNodeSwitch
+==================
+*/
+void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str) {
+ char netname[MAX_NETNAME];
+
+ ClientName(bs->client, netname, sizeof(netname));
+ Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s\n", netname, trap_AAS_Time(), node, str);
+#ifdef DEBUG
+ if (0) {
+ BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]);
+ }
+#endif //DEBUG
+ numnodeswitches++;
+}
+
+/*
+==================
+BotGetAirGoal
+==================
+*/
+int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) {
+ bsp_trace_t bsptrace;
+ vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2};
+ int areanum;
+
+ //trace up until we hit solid
+ VectorCopy(bs->origin, end);
+ end[2] += 1000;
+ BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
+ //trace down until we hit water
+ VectorCopy(bsptrace.endpos, end);
+ BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA);
+ //if we found the water surface
+ if (bsptrace.fraction > 0) {
+ areanum = BotPointAreaNum(bsptrace.endpos);
+ if (areanum) {
+ VectorCopy(bsptrace.endpos, goal->origin);
+ goal->origin[2] -= 2;
+ goal->areanum = areanum;
+ goal->mins[0] = -15;
+ goal->mins[1] = -15;
+ goal->mins[2] = -1;
+ goal->maxs[0] = 15;
+ goal->maxs[1] = 15;
+ goal->maxs[2] = 1;
+ goal->flags = GFL_AIR;
+ goal->number = 0;
+ goal->iteminfo = 0;
+ goal->entitynum = 0;
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotGoForAir
+==================
+*/
+int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
+ bot_goal_t goal;
+
+ //if the bot needs air
+ if (bs->lastair_time < trap_AAS_Time() - 6) {
+ //
+#ifdef DEBUG
+ //BotAI_Print(PRT_MESSAGE, "going for air\n");
+#endif //DEBUG
+ //if we can find an air goal
+ if (BotGetAirGoal(bs, &goal)) {
+ trap_BotPushGoal(bs->gs, &goal);
+ return qtrue;
+ }
+ else {
+ qboolean botRoamsOnly = qtrue;
+ /*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO )
+ {
+ botRoamsOnly = qtrue;
+ }*/
+ if ( g_pModDisintegration.integer != 0 )
+ {
+ botRoamsOnly = qtrue;
+ }
+ //get a nearby goal outside the water
+ while( trap_BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range, botRoamsOnly ) ) {
+ trap_BotGetTopGoal(bs->gs, &goal);
+ //if the goal is not in water
+ if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) {
+ return qtrue;
+ }
+ trap_BotPopGoal(bs->gs);
+ }
+ trap_BotResetAvoidGoals(bs->gs);
+ }
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotNearbyGoal
+==================
+*/
+int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
+ int ret;
+ qboolean botRoamsOnly = qtrue;
+
+ //check if the bot should go for air
+ if (BotGoForAir(bs, tfl, ltg, range)) return qtrue;
+ //if the bot is carrying the enemy flag
+ if (BotCTFCarryingFlag(bs)) {
+ //if the bot is just a few secs away from the base
+ if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
+ bs->teamgoal.areanum, TFL_DEFAULT) < 300) {
+ //make the range really small
+ range = 50;
+ }
+ }
+ //
+ /*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO )
+ {
+ botRoamsOnly = qtrue;
+ }*/
+ if ( g_pModDisintegration.integer != 0 )
+ {
+ botRoamsOnly = qtrue;
+ }
+ ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range, botRoamsOnly );
+ /*
+ if (ret)
+ {
+ char buf[128];
+ //get the goal at the top of the stack
+ trap_BotGetTopGoal(bs->gs, &goal);
+ trap_BotGoalName(goal.number, buf, sizeof(buf));
+ BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", trap_AAS_Time(), buf);
+ }
+ //*/
+ return ret;
+}
+
+/*
+==================
+BotReachedGoal
+==================
+*/
+int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) {
+ if (goal->flags & GFL_ITEM) {
+ //if touching the goal
+ if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
+ //if the goal isn't there
+ if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) return qtrue;
+ //if in the goal area and below or above the goal and not swimming
+ if (bs->areanum == goal->areanum) {
+ if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) {
+ if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) {
+ if (!trap_AAS_Swimming(bs->origin)) {
+ return qtrue;
+ }
+ }
+ }
+ }
+ }
+ else if (goal->flags & GFL_AIR) {
+ //if touching the goal
+ if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
+ //if the bot got air
+ if (bs->lastair_time > trap_AAS_Time() - 1) return qtrue;
+ }
+ else {
+ //if touching the goal
+ if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotGetItemLongTermGoal
+==================
+*/
+int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) {
+ qboolean botRoamsOnly = qtrue;
+ //if the bot has no goal
+ if (!trap_BotGetTopGoal(bs->gs, goal)) {
+ //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n");
+ bs->ltg_time = 0;
+ }
+ //if the bot touches the current goal
+ else if (BotReachedGoal(bs, goal)) {
+ BotChooseWeapon(bs);
+ bs->ltg_time = 0;
+ }
+ //if it is time to find a new long term goal
+ if (bs->ltg_time < trap_AAS_Time()) {
+ //pop the current goal from the stack
+ trap_BotPopGoal(bs->gs);
+ //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname)));
+ //choose a new goal
+ //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", trap_AAS_Time(), bs->client);
+ /*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO )
+ {
+ botRoamsOnly = qtrue;
+ }*/
+ if ( g_pModDisintegration.integer != 0 )
+ {
+ botRoamsOnly = qtrue;
+ }
+ if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl, botRoamsOnly)) {
+ /*
+ char buf[128];
+ //get the goal at the top of the stack
+ trap_BotGetTopGoal(bs->gs, goal);
+ trap_BotGoalName(goal->number, buf, sizeof(buf));
+ BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", trap_AAS_Time(), buf);
+ //*/
+ bs->ltg_time = trap_AAS_Time() + 20;
+ }
+ else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though
+ //
+#ifdef DEBUG
+ //char netname[128];
+
+ //BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname)));
+#endif
+ //trap_BotDumpAvoidGoals(bs->gs);
+ //reset the avoid goals and the avoid reach
+ trap_BotResetAvoidGoals(bs->gs);
+ trap_BotResetAvoidReach(bs->ms);
+ }
+ //get the goal at the top of the stack
+ return trap_BotGetTopGoal(bs->gs, goal);
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotGetLongTermGoal
+
+we could also create a seperate AI node for every long term goal type
+however this saves us a lot of code
+==================
+*/
+int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) {
+ vec3_t target, dir;
+ char netname[MAX_NETNAME];
+ char buf[MAX_MESSAGE_SIZE];
+ int areanum;
+ float croucher;
+ aas_entityinfo_t entinfo;
+ bot_waypoint_t *wp;
+
+ if (bs->ltgtype == LTG_TEAMHELP && !retreat) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //if trying to help the team mate for more than a minute
+ if (bs->teamgoal_time < trap_AAS_Time())
+ bs->ltgtype = 0;
+ //if the team mate IS visible for quite some time
+ if (bs->teammatevisible_time < trap_AAS_Time() - 10) bs->ltgtype = 0;
+ //get entity information of the companion
+ BotEntityInfo(bs->teammate, &entinfo);
+ //if the team mate is visible
+ if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
+ //if close just stand still there
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ if (VectorLength(dir) < 100) {
+ trap_BotResetAvoidReach(bs->ms);
+ return qfalse;
+ }
+ }
+ else {
+ //last time the bot was NOT visible
+ bs->teammatevisible_time = trap_AAS_Time();
+ }
+ //if the entity information is valid (entity in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ //update team goal
+ bs->teamgoal.entitynum = bs->teammate;
+ bs->teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ }
+ }
+ memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
+ return qtrue;
+ }
+ //if the bot accompanies someone
+ if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //if accompanying the companion for 3 minutes
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+ BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->ltgtype = 0;
+ }
+ //get entity information of the companion
+ BotEntityInfo(bs->teammate, &entinfo);
+ //if the companion is visible
+ if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
+ //update visible time
+ bs->teammatevisible_time = trap_AAS_Time();
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ if (VectorLength(dir) < bs->formation_dist) {
+ //check if the bot wants to crouch
+ //don't crouch if crouched less than 5 seconds ago
+ if (bs->attackcrouch_time < trap_AAS_Time() - 5) {
+ croucher = 1;
+ if (random() < bs->thinktime * croucher) {
+ bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15;
+ }
+ }
+ //don't crouch when swimming
+ if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1;
+ //if not arrived yet or arived some time ago
+ if (bs->arrive_time < trap_AAS_Time() - 2) {
+ //if not arrived yet
+ if (!bs->arrive_time) {
+ trap_EA_Gesture(bs->client);
+ BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->arrive_time = trap_AAS_Time();
+ }
+ //if the bot wants to crouch
+ else if (bs->attackcrouch_time > trap_AAS_Time()) {
+ trap_EA_Crouch(bs->client);
+ }
+ //else do some model taunts
+ else if (random() < bs->thinktime * 0.3) {
+ //do a gesture :)
+ trap_EA_Gesture(bs->client);
+ }
+ }
+ //if just arrived look at the companion
+ if (bs->arrive_time > trap_AAS_Time() - 2) {
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ //else look strategically around for enemies
+ else if (random() < bs->thinktime * 0.8) {
+ BotRoamGoal(bs, target);
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ //check if the bot wants to go for air
+ if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) {
+ trap_BotResetLastAvoidReach(bs->ms);
+ //get the goal at the top of the stack
+ //trap_BotGetTopGoal(bs->gs, &tmpgoal);
+ //trap_BotGoalName(tmpgoal.number, buf, 144);
+ //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
+ //time the bot gets to pick up the nearby goal item
+ bs->nbg_time = trap_AAS_Time() + 8;
+ AIEnter_Seek_NBG(bs);
+ return qfalse;
+ }
+ //
+ trap_BotResetAvoidReach(bs->ms);
+ return qfalse;
+ }
+ }
+ //if the entity information is valid (entity in PVS)
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ //update team goal so bot will accompany
+ bs->teamgoal.entitynum = bs->teammate;
+ bs->teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->teamgoal.origin);
+ VectorSet(bs->teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->teamgoal.maxs, 8, 8, 8);
+ }
+ }
+ //the goal the bot should go for
+ memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
+ //if the companion is NOT visible for too long
+ if (bs->teammatevisible_time < trap_AAS_Time() - 60) {
+ BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->ltgtype = 0;
+ }
+ return qtrue;
+ }
+ //
+ if (bs->ltgtype == LTG_DEFENDKEYAREA) {
+ if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
+ bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) {
+ bs->defendaway_time = 0;
+ }
+ }
+ //if defending a key area
+ if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat &&
+ bs->defendaway_time < trap_AAS_Time()) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "defend_start", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //set the bot goal
+ memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
+ //stop after 2 minutes
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+ trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "defend_stop", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->ltgtype = 0;
+ }
+ //if very close... go away for some time
+ VectorSubtract(goal->origin, bs->origin, dir);
+ if (VectorLength(dir) < 70) {
+ trap_BotResetAvoidReach(bs->ms);
+ bs->defendaway_time = trap_AAS_Time() + 2 + 5 * random();
+ bs->defendaway_range = 250;
+ }
+ return qtrue;
+ }
+ //going to kill someone
+ if (bs->ltgtype == LTG_KILL && !retreat) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "kill_start", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //
+ if (bs->lastkilledplayer == bs->teamgoal.entitynum) {
+ EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "kill_done", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->lastkilledplayer = -1;
+ bs->ltgtype = 0;
+ }
+ //
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+ bs->ltgtype = 0;
+ }
+ //just roam around
+ return BotGetItemLongTermGoal(bs, tfl, goal);
+ }
+ //get an item
+ if (bs->ltgtype == LTG_GETITEM && !retreat) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "getitem_start", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //set the bot goal
+ memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
+ //stop after some time
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+ bs->ltgtype = 0;
+ }
+ //
+ if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) {
+ trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->ltgtype = 0;
+ }
+ else if (BotReachedGoal(bs, goal)) {
+ trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->ltgtype = 0;
+ }
+ return qtrue;
+ }
+ //if camping somewhere
+ if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ if (bs->ltgtype == LTG_CAMPORDER) {
+ BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+ bs->teammessage_time = 0;
+ }
+ //set the bot goal
+ memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
+ //
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+ if (bs->ltgtype == LTG_CAMPORDER) {
+ BotAI_BotInitialChat(bs, "camp_stop", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+ bs->ltgtype = 0;
+ }
+ //if really near the camp spot
+ VectorSubtract(goal->origin, bs->origin, dir);
+ if (VectorLength(dir) < 60)
+ {
+ //if not arrived yet
+ if (!bs->arrive_time) {
+ if (bs->ltgtype == LTG_CAMPORDER) {
+ BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+ bs->arrive_time = trap_AAS_Time();
+ }
+ //look strategically around for enemies
+ if (random() < bs->thinktime * 0.8) {
+ BotRoamGoal(bs, target);
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ //check if the bot wants to crouch
+ //don't crouch if crouched less than 5 seconds ago
+ if (bs->attackcrouch_time < trap_AAS_Time() - 5) {
+ croucher = 1;
+ if (random() < bs->thinktime * croucher) {
+ bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15;
+ }
+ }
+ //if the bot wants to crouch
+ if (bs->attackcrouch_time > trap_AAS_Time()) {
+ trap_EA_Crouch(bs->client);
+ }
+ //don't crouch when swimming
+ if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1;
+ //make sure the bot is not gonna drown
+ if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
+ if (bs->ltgtype == LTG_CAMPORDER) {
+ BotAI_BotInitialChat(bs, "camp_stop", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+ bs->ltgtype = 0;
+ }
+ //
+ if (bs->camp_range > 0) {
+ //FIXME: move around a bit
+ }
+ //
+ trap_BotResetAvoidReach(bs->ms);
+ return qfalse;
+ }
+ return qtrue;
+ }
+ //patrolling along several waypoints
+ if (bs->ltgtype == LTG_PATROL && !retreat) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ strcpy(buf, "");
+ for (wp = bs->patrolpoints; wp; wp = wp->next) {
+ strcat(buf, wp->name);
+ if (wp->next) strcat(buf, " to ");
+ }
+ BotAI_BotInitialChat(bs, "patrol_start", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //
+ if (!bs->curpatrolpoint) {
+ bs->ltgtype = 0;
+ return qfalse;
+ }
+ //if the bot touches the current goal
+ if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) {
+ if (bs->patrolflags & PATROL_BACK) {
+ if (bs->curpatrolpoint->prev) {
+ bs->curpatrolpoint = bs->curpatrolpoint->prev;
+ }
+ else {
+ bs->curpatrolpoint = bs->curpatrolpoint->next;
+ bs->patrolflags &= ~PATROL_BACK;
+ }
+ }
+ else {
+ if (bs->curpatrolpoint->next) {
+ bs->curpatrolpoint = bs->curpatrolpoint->next;
+ }
+ else {
+ bs->curpatrolpoint = bs->curpatrolpoint->prev;
+ bs->patrolflags |= PATROL_BACK;
+ }
+ }
+ }
+ //stop after 5 minutes
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+ BotAI_BotInitialChat(bs, "patrol_stop", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->ltgtype = 0;
+ }
+ if (!bs->curpatrolpoint) {
+ bs->ltgtype = 0;
+ return qfalse;
+ }
+ memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t));
+ return qtrue;
+ }
+#ifdef CTF
+ //if going for enemy flag
+ if (bs->ltgtype == LTG_GETFLAG) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ BotAI_BotInitialChat(bs, "captureflag_start", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //
+ switch(BotCTFTeam(bs)) {
+ case CTF_TEAM_RED: *goal = ctf_blueflag; break;
+ case CTF_TEAM_BLUE: *goal = ctf_redflag; break;
+ default: bs->ltgtype = 0; return qfalse;
+ }
+ //if touching the flag
+ if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0;
+ //stop after 3 minutes
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+#ifdef DEBUG
+ BotAI_Print(PRT_MESSAGE, "%s: I quit getting the flag\n", ClientName(bs->client, netname, sizeof(netname)));
+#endif //DEBUG
+ bs->ltgtype = 0;
+ }
+ return qtrue;
+ }
+ //if rushing to the base
+ if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < trap_AAS_Time()) {
+ switch(BotCTFTeam(bs)) {
+ case CTF_TEAM_RED: *goal = ctf_redflag; break;
+ case CTF_TEAM_BLUE: *goal = ctf_blueflag; break;
+ default: bs->ltgtype = 0; return qfalse;
+ }
+ //if not carrying the flag anymore
+ if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0;
+ //quit rushing after 2 minutes
+ if (bs->teamgoal_time < trap_AAS_Time()) bs->ltgtype = 0;
+ //if touching the base flag the bot should loose the enemy flag
+ if (trap_BotTouchingGoal(bs->origin, goal)) {
+ //if the bot is still carrying the enemy flag then the
+ //base flag is gone, now just walk near the base a bit
+ if (BotCTFCarryingFlag(bs)) {
+ trap_BotResetAvoidReach(bs->ms);
+ bs->rushbaseaway_time = trap_AAS_Time() + 5 + 10 * random();
+ //FIXME: add chat to tell the others to get back the flag
+ //FIXME: Make them camp? Get health? Preserve themselves?
+ }
+ else {
+ bs->ltgtype = 0;
+ }
+ }
+ return qtrue;
+ }
+ //returning flag
+ if (bs->ltgtype == LTG_RETURNFLAG) {
+ //check for bot typing status message
+ if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) {
+ EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
+ BotAI_BotInitialChat(bs, "returnflag_start", buf, NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->teammessage_time = 0;
+ }
+ //
+ if (bs->teamgoal_time < trap_AAS_Time()) {
+ bs->ltgtype = 0;
+ }
+ //FIXME: Uh.... we're trying to retrieve our flag, shouldn't
+ // we set that as our goal somewhere?
+ //ALSO: Can't we also easily implement the ability to pick up
+ // the enemy flag if it's dropped in the field by a teammate?
+
+ //just roam around
+ return BotGetItemLongTermGoal(bs, tfl, goal);
+ }
+#endif //CTF
+ //normal goal stuff
+ return BotGetItemLongTermGoal(bs, tfl, goal);
+}
+
+/*
+==================
+BotLongTermGoal
+==================
+*/
+int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) {
+ aas_entityinfo_t entinfo;
+ char teammate[MAX_MESSAGE_SIZE];
+ float dist;
+ int areanum;
+ vec3_t dir;
+
+ //FIXME: also have air long term goals?
+ //
+ //if the bot is leading someone and not retreating
+ if (bs->lead_time > 0 && !retreat) {
+ if (bs->lead_time < trap_AAS_Time()) {
+ //FIXME: add chat to tell the team mate that he/she's on his/her own
+ bs->lead_time = 0;
+ return BotGetLongTermGoal(bs, tfl, retreat, goal);
+ }
+ //
+ if (bs->leadmessage_time < 0 && -bs->leadmessage_time < trap_AAS_Time()) {
+ BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->leadmessage_time = trap_AAS_Time();
+ }
+ //get entity information of the companion
+ BotEntityInfo(bs->lead_teammate, &entinfo);
+ //
+ if (entinfo.valid) {
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ //update team goal
+ bs->lead_teamgoal.entitynum = bs->lead_teammate;
+ bs->lead_teamgoal.areanum = areanum;
+ VectorCopy(entinfo.origin, bs->lead_teamgoal.origin);
+ VectorSet(bs->lead_teamgoal.mins, -8, -8, -8);
+ VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8);
+ }
+ }
+ //if the team mate is visible
+ if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) {
+ bs->leadvisible_time = trap_AAS_Time();
+ }
+ //if the team mate is not visible for 1 seconds
+ if (bs->leadvisible_time < trap_AAS_Time() - 1) {
+ bs->leadbackup_time = trap_AAS_Time() + 2;
+ }
+ //distance towards the team mate
+ VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir);
+ dist = VectorLength(dir);
+ //if backing up towards the team mate
+ if (bs->leadbackup_time > trap_AAS_Time()) {
+ if (bs->leadmessage_time < trap_AAS_Time() - 20) {
+ BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->leadmessage_time = trap_AAS_Time();
+ }
+ //if very close to the team mate
+ if (dist < 100) {
+ bs->leadbackup_time = 0;
+ }
+ //the bot should go back to the team mate
+ memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t));
+ return qtrue;
+ }
+ else {
+ //if quite distant from the team mate
+ if (dist > 500) {
+ if (bs->leadmessage_time < trap_AAS_Time() - 20) {
+ BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->leadmessage_time = trap_AAS_Time();
+ }
+ //look at the team mate
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ //just wait for the team mate
+ return qfalse;
+ }
+ }
+ }
+ return BotGetLongTermGoal(bs, tfl, retreat, goal);
+}
+
+/*
+==================
+AIEnter_Intermission
+==================
+*/
+void AIEnter_Intermission(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "intermission", "");
+ //reset the bot state
+ BotResetState(bs);
+ //check for end level chat
+ if (BotChat_EndLevel(bs)) {
+ trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
+ }
+ bs->ainode = AINode_Intermission;
+}
+
+/*
+==================
+AINode_Intermission
+==================
+*/
+int AINode_Intermission(bot_state_t *bs) {
+ //if the intermission ended
+ if (!BotIntermission(bs)) {
+ if (BotChat_StartLevel(bs)) {
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ }
+ else {
+ bs->stand_time = trap_AAS_Time() + 2;
+ }
+ AIEnter_Stand(bs);
+ }
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Observer
+==================
+*/
+void AIEnter_Observer(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "observer", "");
+ //reset the bot state
+ BotResetState(bs);
+ bs->ainode = AINode_Observer;
+}
+
+/*
+==================
+AINode_Observer
+==================
+*/
+int AINode_Observer(bot_state_t *bs) {
+ //if the bot left observer mode
+ if (!BotIsObserver(bs)) {
+ AIEnter_Stand(bs);
+ }
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Stand
+==================
+*/
+void AIEnter_Stand(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "stand", "");
+ bs->standfindenemy_time = trap_AAS_Time() + 1;
+ bs->ainode = AINode_Stand;
+}
+
+/*
+==================
+AINode_Stand
+==================
+*/
+int AINode_Stand(bot_state_t *bs) {
+
+ //if the bot's health decreased
+ if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
+ if (BotChat_HitTalking(bs)) {
+ bs->standfindenemy_time = trap_AAS_Time() + BotChatTime(bs) + 0.1;
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs) + 0.1;
+ }
+ }
+ if (bs->standfindenemy_time < trap_AAS_Time()) {
+ if (BotFindEnemy(bs, -1)) {
+ AIEnter_Battle_Fight(bs);
+ return qfalse;
+ }
+ bs->standfindenemy_time = trap_AAS_Time() + 1;
+ }
+ trap_EA_Talk(bs->client);
+ if (bs->stand_time < trap_AAS_Time()) {
+ trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Respawn
+==================
+*/
+void AIEnter_Respawn(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "respawn", "");
+ //reset some states
+ trap_BotResetMoveState(bs->ms);
+ trap_BotResetGoalState(bs->gs);
+ trap_BotResetAvoidGoals(bs->gs);
+ trap_BotResetAvoidReach(bs->ms);
+ //if the bot wants to chat
+ if (BotChat_Death(bs)) {
+ bs->respawn_time = trap_AAS_Time() + BotChatTime(bs);
+ bs->respawnchat_time = trap_AAS_Time();
+ }
+ else {
+ bs->respawn_time = trap_AAS_Time() + 1 + random();
+ bs->respawnchat_time = 0;
+ }
+ //set respawn state
+ bs->respawn_wait = qfalse;
+ bs->ainode = AINode_Respawn;
+}
+
+/*
+==================
+AINode_Respawn
+==================
+*/
+int AINode_Respawn(bot_state_t *bs) {
+ if (bs->respawn_wait) {
+ if (!BotIsDead(bs)) {
+ AIEnter_Seek_LTG(bs);
+ }
+ else {
+ trap_EA_Respawn(bs->client);
+ }
+ }
+ else if (bs->respawn_time < trap_AAS_Time()) {
+ //wait until respawned
+ bs->respawn_wait = qtrue;
+ //elementary action respawn
+ trap_EA_Respawn(bs->client);
+ //
+ if (bs->respawnchat_time) {
+ trap_BotEnterChat(bs->cs, bs->client, bs->chatto);
+ bs->enemy = -1;
+ }
+ }
+ if (bs->respawnchat_time && bs->respawnchat_time < trap_AAS_Time() - 0.5) {
+ trap_EA_Talk(bs->client);
+ }
+ //
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Seek_ActivateEntity
+==================
+*/
+void AIEnter_Seek_ActivateEntity(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "activate entity", "");
+ bs->ainode = AINode_Seek_ActivateEntity;
+}
+
+/*
+==================
+AINode_Seek_Activate_Entity
+==================
+*/
+int AINode_Seek_ActivateEntity(bot_state_t *bs) {
+ bot_goal_t *goal;
+ vec3_t target, dir;
+ bot_moveresult_t moveresult;
+
+ if (BotIsObserver(bs)) {
+ AIEnter_Observer(bs);
+ return qfalse;
+ }
+ //if in the intermission
+ if (BotIntermission(bs)) {
+ AIEnter_Intermission(bs);
+ return qfalse;
+ }
+ //respawn if dead
+ if (BotIsDead(bs)) {
+ AIEnter_Respawn(bs);
+ return qfalse;
+ }
+ //
+ bs->tfl = TFL_DEFAULT;
+ if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
+ //if in lava or slime the bot should be able to get out
+ if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
+ //map specific code
+ BotMapScripts(bs);
+ //no enemy
+ bs->enemy = -1;
+ //
+ goal = &bs->activategoal;
+ //if the bot has no goal
+ if (!goal) bs->activate_time = 0;
+ //if the bot touches the current goal
+ else if (trap_BotTouchingGoal(bs->origin, goal)) {
+ BotChooseWeapon(bs);
+#ifdef DEBUG
+ BotAI_Print(PRT_MESSAGE, "touched button or trigger\n");
+#endif //DEBUG
+ bs->activate_time = 0;
+ }
+ //
+ if (bs->activate_time < trap_AAS_Time()) {
+ AIEnter_Seek_NBG(bs);
+ return qfalse;
+ }
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl);
+ //if the movement failed
+ if (moveresult.failure) {
+ //reset the avoid reach, otherwise bot is stuck in current area
+ trap_BotResetAvoidReach(bs->ms);
+ bs->nbg_time = 0;
+ }
+ //check if the bot is blocked
+ BotAIBlocked(bs, &moveresult, qtrue);
+ //
+ if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
+ VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
+ }
+ //if waiting for something
+ else if (moveresult.flags & MOVERESULT_WAITING) {
+ if (random() < bs->thinktime * 0.8) {
+ BotRoamGoal(bs, target);
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ }
+ else if (!(bs->flags & BFL_IDEALVIEWSET)) {
+ if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) {
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ }
+ else {
+ //vectoangles(moveresult.movedir, bs->ideal_viewangles);
+ }
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ //if the weapon is used for the bot movement
+ if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
+ //if there is an enemy
+ if (BotFindEnemy(bs, -1)) {
+ if (BotWantsToRetreat(bs)) {
+ //keep the current long term goal and retreat
+ AIEnter_Battle_NBG(bs);
+ }
+ else {
+ trap_BotResetLastAvoidReach(bs->ms);
+ //empty the goal stack
+ trap_BotEmptyGoalStack(bs->gs);
+ //go fight
+ AIEnter_Battle_Fight(bs);
+ }
+ }
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Seek_NBG
+==================
+*/
+void AIEnter_Seek_NBG(bot_state_t *bs) {
+ bot_goal_t goal;
+ char buf[144];
+
+ if (trap_BotGetTopGoal(bs->gs, &goal)) {
+ trap_BotGoalName(goal.number, buf, 144);
+ BotRecordNodeSwitch(bs, "seek NBG", buf);
+ }
+ else {
+ BotRecordNodeSwitch(bs, "seek NBG", "no goal");
+ }
+ bs->ainode = AINode_Seek_NBG;
+}
+
+/*
+==================
+AINode_Seek_NBG
+==================
+*/
+int AINode_Seek_NBG(bot_state_t *bs) {
+ bot_goal_t goal;
+ vec3_t target, dir;
+ bot_moveresult_t moveresult;
+
+ if (BotIsObserver(bs)) {
+ AIEnter_Observer(bs);
+ return qfalse;
+ }
+ //if in the intermission
+ if (BotIntermission(bs)) {
+ AIEnter_Intermission(bs);
+ return qfalse;
+ }
+ //respawn if dead
+ if (BotIsDead(bs)) {
+ AIEnter_Respawn(bs);
+ return qfalse;
+ }
+ //
+ bs->tfl = TFL_DEFAULT;
+ if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
+ //if in lava or slime the bot should be able to get out
+ if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
+ //
+ if (BotCanAndWantsToRocketJump(bs)) {
+ bs->tfl |= TFL_ROCKETJUMP;
+ }
+ //map specific code
+ BotMapScripts(bs);
+ //no enemy
+ bs->enemy = -1;
+ //if the bot has no goal
+ if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0;
+ //if the bot touches the current goal
+ else if (BotReachedGoal(bs, &goal)) {
+ BotChooseWeapon(bs);
+ bs->nbg_time = 0;
+ }
+ //
+ if (bs->nbg_time < trap_AAS_Time()) {
+ //pop the current goal from the stack
+ trap_BotPopGoal(bs->gs);
+ //check for new nearby items right away
+ //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches
+ bs->check_time = trap_AAS_Time() + 0.05;
+ //go back to seek ltg
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
+ //if the movement failed
+ if (moveresult.failure) {
+ //reset the avoid reach, otherwise bot is stuck in current area
+ trap_BotResetAvoidReach(bs->ms);
+ bs->nbg_time = 0;
+ }
+ //check if the bot is blocked
+ BotAIBlocked(bs, &moveresult, qtrue);
+ //if the viewangles are used for the movement
+ if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
+ VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
+ }
+ //if waiting for something
+ else if (moveresult.flags & MOVERESULT_WAITING) {
+ if (random() < bs->thinktime * 0.8) {
+ BotRoamGoal(bs, target);
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ }
+ else if (!(bs->flags & BFL_IDEALVIEWSET)) {
+ if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal);
+ if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ }
+ //FIXME: look at cluster portals?
+ else vectoangles(moveresult.movedir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ //if the weapon is used for the bot movement
+ if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
+ //if there is an enemy
+ if (BotFindEnemy(bs, -1)) {
+ if (BotWantsToRetreat(bs)) {
+ //keep the current long term goal and retreat
+ AIEnter_Battle_NBG(bs);
+ }
+ else {
+ trap_BotResetLastAvoidReach(bs->ms);
+ //empty the goal stack
+ trap_BotEmptyGoalStack(bs->gs);
+ //go fight
+ AIEnter_Battle_Fight(bs);
+ }
+ }
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Seek_LTG
+==================
+*/
+void AIEnter_Seek_LTG(bot_state_t *bs) {
+ bot_goal_t goal;
+ char buf[144];
+
+ if (trap_BotGetTopGoal(bs->gs, &goal)) {
+ trap_BotGoalName(goal.number, buf, 144);
+ BotRecordNodeSwitch(bs, "seek LTG", buf);
+ }
+ else {
+ BotRecordNodeSwitch(bs, "seek LTG", "no goal");
+ }
+ bs->ainode = AINode_Seek_LTG;
+}
+
+/*
+==================
+AINode_Seek_LTG
+==================
+*/
+int AINode_Seek_LTG(bot_state_t *bs)
+{
+ bot_goal_t goal;
+ vec3_t target, dir;
+ bot_moveresult_t moveresult;
+ int range;
+ //char buf[128];
+ //bot_goal_t tmpgoal;
+
+ if (BotIsObserver(bs)) {
+ AIEnter_Observer(bs);
+ return qfalse;
+ }
+ //if in the intermission
+ if (BotIntermission(bs)) {
+ AIEnter_Intermission(bs);
+ return qfalse;
+ }
+ //respawn if dead
+ if (BotIsDead(bs)) {
+ AIEnter_Respawn(bs);
+ return qfalse;
+ }
+ //
+ if (BotChat_Random(bs)) {
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ return qfalse;
+ }
+ //
+ bs->tfl = TFL_DEFAULT;
+ if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
+ //if in lava or slime the bot should be able to get out
+ if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
+ //
+ if (BotCanAndWantsToRocketJump(bs)) {
+ bs->tfl |= TFL_ROCKETJUMP;
+ }
+ //map specific code
+ BotMapScripts(bs);
+ //no enemy
+ bs->enemy = -1;
+ //
+ if (bs->killedenemy_time > trap_AAS_Time() - 2) {
+ if (random() < bs->thinktime * 1) {
+ trap_EA_Gesture(bs->client);
+ }
+ }
+ //if there is an enemy
+ if (BotFindEnemy(bs, -1)) {
+ if (BotWantsToRetreat(bs)) {
+ //keep the current long term goal and retreat
+ AIEnter_Battle_Retreat(bs);
+ return qfalse;
+ }
+ else {
+ trap_BotResetLastAvoidReach(bs->ms);
+ //empty the goal stack
+ trap_BotEmptyGoalStack(bs->gs);
+ //go fight
+ AIEnter_Battle_Fight(bs);
+ return qfalse;
+ }
+ }
+#ifdef CTF
+ if (gametype == GT_CTF) {
+ //decide what to do in CTF mode
+ BotCTFSeekGoals(bs);
+ }
+#endif //CTF
+ //get the current long term goal
+ if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) {
+ return qtrue;
+ }
+ //check for nearby goals periodicly
+ if (bs->check_time < trap_AAS_Time()) {
+ bs->check_time = trap_AAS_Time() + 0.5;
+ //check if the bot wants to camp
+ BotWantsToCamp(bs);
+ //
+ if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400;
+ else range = 150;
+ //
+#ifdef CTF
+ //if carrying a flag the bot shouldn't be distracted too much
+ if (BotCTFCarryingFlag(bs)) range = 50;
+#endif //CTF
+ //
+ if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
+ trap_BotResetLastAvoidReach(bs->ms);
+ //get the goal at the top of the stack
+ //trap_BotGetTopGoal(bs->gs, &tmpgoal);
+ //trap_BotGoalName(tmpgoal.number, buf, 144);
+ //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
+ //time the bot gets to pick up the nearby goal item
+ bs->nbg_time = trap_AAS_Time() + 4 + range * 0.01;
+ AIEnter_Seek_NBG(bs);
+ return qfalse;
+ }
+ }
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
+ //if the movement failed
+ if (moveresult.failure) {
+ //reset the avoid reach, otherwise bot is stuck in current area
+ trap_BotResetAvoidReach(bs->ms);
+ //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
+ bs->ltg_time = 0;
+ }
+ //
+ BotAIBlocked(bs, &moveresult, qtrue);
+ //if the viewangles are used for the movement
+ if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
+ VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
+ }
+ //if waiting for something
+ else if (moveresult.flags & MOVERESULT_WAITING) {
+ if (random() < bs->thinktime * 0.8) {
+ BotRoamGoal(bs, target);
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ }
+ else if (!(bs->flags & BFL_IDEALVIEWSET)) {
+ if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ }
+ //FIXME: look at cluster portals?
+ else if (VectorLength(moveresult.movedir)) {
+ vectoangles(moveresult.movedir, bs->ideal_viewangles);
+ }
+ else if (random() < bs->thinktime * 0.8) {
+ BotRoamGoal(bs, target);
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ //if the weapon is used for the bot movement
+ if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
+ //
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Battle_Fight
+==================
+*/
+void AIEnter_Battle_Fight(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "battle fight", "");
+ trap_BotResetLastAvoidReach(bs->ms);
+ bs->ainode = AINode_Battle_Fight;
+}
+
+/*
+==================
+AIEnter_Battle_Fight
+==================
+*/
+void AIEnter_Battle_SuicidalFight(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "battle fight", "");
+ trap_BotResetLastAvoidReach(bs->ms);
+ bs->ainode = AINode_Battle_Fight;
+ bs->flags |= BFL_FIGHTSUICIDAL;
+}
+
+/*
+==================
+AINode_Battle_Fight
+==================
+*/
+int AINode_Battle_Fight(bot_state_t *bs) {
+ int areanum;
+ aas_entityinfo_t entinfo;
+ bot_moveresult_t moveresult;
+
+ if (BotIsObserver(bs)) {
+ AIEnter_Observer(bs);
+ return qfalse;
+ }
+
+ //if in the intermission
+ if (BotIntermission(bs)) {
+ AIEnter_Intermission(bs);
+ return qfalse;
+ }
+ //respawn if dead
+ if (BotIsDead(bs)) {
+ AIEnter_Respawn(bs);
+ return qfalse;
+ }
+ //if there is another better enemy
+ if (BotFindEnemy(bs, bs->enemy)) {
+#ifdef DEBUG
+// BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
+#endif
+ }
+ //if no enemy
+ if (bs->enemy < 0) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //
+ BotEntityInfo(bs->enemy, &entinfo);
+ //if the enemy is dead
+ if (bs->enemydeath_time) {
+ if (bs->enemydeath_time < trap_AAS_Time() - 1.0) {
+ bs->enemydeath_time = 0;
+ if (bs->enemysuicide) {
+ BotChat_EnemySuicide(bs);
+ }
+ if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) {
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ }
+ else {
+ bs->ltg_time = 0;
+ AIEnter_Seek_LTG(bs);
+ }
+ return qfalse;
+ }
+ }
+ else {
+ if (EntityIsDead(&entinfo)) {
+ bs->enemydeath_time = trap_AAS_Time();
+ }
+ }
+ //if the enemy is invisible and not shooting the bot looses track easily
+ if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
+ if (random() < 0.2) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ }
+ //update the reachability area and origin if possible
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ VectorCopy(entinfo.origin, bs->lastenemyorigin);
+ bs->lastenemyareanum = areanum;
+ }
+ //update the attack inventory values
+ BotUpdateBattleInventory(bs, bs->enemy);
+ //if the bot's health decreased
+ if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
+ if (BotChat_HitNoDeath(bs)) {
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ return qfalse;
+ }
+ }
+ //if the bot hit someone
+ if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount)
+ {
+ if (BotChat_HitNoKill(bs))
+ {
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ return qfalse;
+ }
+ }
+ //if the enemy is not visible
+ if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
+ if (BotWantsToChase(bs)) {
+ AIEnter_Battle_Chase(bs);
+ return qfalse;
+ }
+ else {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ }
+ //use holdable items
+ BotBattleUseItems(bs);
+ //
+ bs->tfl = TFL_DEFAULT;
+ if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
+ //if in lava or slime the bot should be able to get out
+ if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
+ //
+ if (BotCanAndWantsToRocketJump(bs)) {
+ bs->tfl |= TFL_ROCKETJUMP;
+ }
+ //choose the best weapon to fight with
+ BotChooseWeapon(bs);
+ //do attack movements
+ moveresult = BotAttackMove(bs, bs->tfl);
+ //if the movement failed
+ if (moveresult.failure) {
+ //reset the avoid reach, otherwise bot is stuck in current area
+ trap_BotResetAvoidReach(bs->ms);
+ //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
+ bs->ltg_time = 0;
+ }
+ //
+ BotAIBlocked(bs, &moveresult, qfalse);
+ //aim at the enemy
+ BotAimAtEnemy(bs);
+ //attack the enemy if possible
+ BotCheckAttack(bs);
+ //if the bot wants to retreat
+ if (!(bs->flags & BFL_FIGHTSUICIDAL)) {
+ if (BotWantsToRetreat(bs)) {
+ AIEnter_Battle_Retreat(bs);
+ return qtrue;
+ }
+ }
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Battle_Chase
+==================
+*/
+void AIEnter_Battle_Chase(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "battle chase", "");
+ bs->chase_time = trap_AAS_Time();
+ bs->ainode = AINode_Battle_Chase;
+}
+
+/*
+==================
+AINode_Battle_Chase
+==================
+*/
+int AINode_Battle_Chase(bot_state_t *bs)
+{
+ bot_goal_t goal;
+ vec3_t target, dir;
+ bot_moveresult_t moveresult;
+ float range;
+
+ if (BotIsObserver(bs)) {
+ AIEnter_Observer(bs);
+ return qfalse;
+ }
+ //if in the intermission
+ if (BotIntermission(bs)) {
+ AIEnter_Intermission(bs);
+ return qfalse;
+ }
+ //respawn if dead
+ if (BotIsDead(bs)) {
+ AIEnter_Respawn(bs);
+ return qfalse;
+ }
+ //if no enemy
+ if (bs->enemy < 0) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //if the enemy is visible
+ if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
+ AIEnter_Battle_Fight(bs);
+ return qfalse;
+ }
+ //if there is another enemy
+ if (BotFindEnemy(bs, -1)) {
+ AIEnter_Battle_Fight(bs);
+ return qfalse;
+ }
+ //there is no last enemy area
+ if (!bs->lastenemyareanum) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //
+ bs->tfl = TFL_DEFAULT;
+ if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
+ //if in lava or slime the bot should be able to get out
+ if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
+ //
+ if (BotCanAndWantsToRocketJump(bs)) {
+ bs->tfl |= TFL_ROCKETJUMP;
+ }
+ //map specific code
+ BotMapScripts(bs);
+ //create the chase goal
+ goal.entitynum = bs->enemy;
+ goal.areanum = bs->lastenemyareanum;
+ VectorCopy(bs->lastenemyorigin, goal.origin);
+ VectorSet(goal.mins, -8, -8, -8);
+ VectorSet(goal.maxs, 8, 8, 8);
+ //if the last seen enemy spot is reached the enemy could not be found
+ if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0;
+ //if there's no chase time left
+ if (!bs->chase_time || bs->chase_time < trap_AAS_Time() - 10) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //check for nearby goals periodicly
+ if (bs->check_time < trap_AAS_Time()) {
+ bs->check_time = trap_AAS_Time() + 1;
+ range = 150;
+ //
+ if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
+ //the bot gets 5 seconds to pick up the nearby goal item
+ bs->nbg_time = trap_AAS_Time() + 0.1 * range + 1;
+ trap_BotResetLastAvoidReach(bs->ms);
+ AIEnter_Battle_NBG(bs);
+ return qfalse;
+ }
+ }
+ //
+ BotUpdateBattleInventory(bs, bs->enemy);
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
+ //if the movement failed
+ if (moveresult.failure) {
+ //reset the avoid reach, otherwise bot is stuck in current area
+ trap_BotResetAvoidReach(bs->ms);
+ //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
+ bs->ltg_time = 0;
+ }
+ //
+ BotAIBlocked(bs, &moveresult, qfalse);
+ //
+ if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
+ VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
+ }
+ else if (!(bs->flags & BFL_IDEALVIEWSET)) {
+ if (bs->chase_time > trap_AAS_Time() - 2) {
+ BotAimAtEnemy(bs);
+ }
+ else {
+ if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ }
+ else {
+ vectoangles(moveresult.movedir, bs->ideal_viewangles);
+ }
+ }
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ //if the weapon is used for the bot movement
+ if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
+ //if the bot is in the area the enemy was last seen in
+ if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0;
+ //if the bot wants to retreat (the bot could have been damage during the chase)
+ if (BotWantsToRetreat(bs)) {
+ AIEnter_Battle_Retreat(bs);
+ return qtrue;
+ }
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Battle_Retreat
+==================
+*/
+void AIEnter_Battle_Retreat(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "battle retreat", "");
+ bs->ainode = AINode_Battle_Retreat;
+}
+
+/*
+==================
+AINode_Battle_Retreat
+==================
+*/
+int AINode_Battle_Retreat(bot_state_t *bs) {
+ bot_goal_t goal;
+ aas_entityinfo_t entinfo;
+ bot_moveresult_t moveresult;
+ vec3_t target, dir;
+ float attack_skill, range;
+ int areanum;
+
+ if (BotIsObserver(bs)) {
+ AIEnter_Observer(bs);
+ return qfalse;
+ }
+ //if in the intermission
+ if (BotIntermission(bs)) {
+ AIEnter_Intermission(bs);
+ return qfalse;
+ }
+ //respawn if dead
+ if (BotIsDead(bs)) {
+ AIEnter_Respawn(bs);
+ return qfalse;
+ }
+ //if no enemy
+ if (bs->enemy < 0) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityIsDead(&entinfo)) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //
+ bs->tfl = TFL_DEFAULT;
+ if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
+ //if in lava or slime the bot should be able to get out
+ if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
+ //map specific code
+ BotMapScripts(bs);
+ //update the attack inventory values
+ BotUpdateBattleInventory(bs, bs->enemy);
+ //if the bot doesn't want to retreat anymore... probably picked up some nice items
+ if (BotWantsToChase(bs)) {
+ //empty the goal stack, when chasing, only the enemy is the goal
+ trap_BotEmptyGoalStack(bs->gs);
+ //go chase the enemy
+ AIEnter_Battle_Chase(bs);
+ return qfalse;
+ }
+ //update the last time the enemy was visible
+ if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
+ bs->enemyvisible_time = trap_AAS_Time();
+ //update the reachability area and origin if possible
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ VectorCopy(entinfo.origin, bs->lastenemyorigin);
+ bs->lastenemyareanum = areanum;
+ }
+ }
+ //if the enemy is NOT visible for 4 seconds
+ if (bs->enemyvisible_time < trap_AAS_Time() - 4) {
+ AIEnter_Seek_LTG(bs);
+ return qfalse;
+ }
+ //else if the enemy is NOT visible
+ else if (bs->enemyvisible_time < trap_AAS_Time()) {
+ //if there is another enemy
+ if (BotFindEnemy(bs, -1)) {
+ AIEnter_Battle_Fight(bs);
+ return qfalse;
+ }
+ }
+ //
+#ifdef CTF
+ if (gametype == GT_CTF) {
+ BotCTFRetreatGoals(bs);
+ }
+#endif //CTF
+ //use holdable items
+ BotBattleUseItems(bs);
+ //get the current long term goal while retreating
+ if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) {
+ AIEnter_Battle_SuicidalFight(bs);
+ return qfalse;
+ }
+ //check for nearby goals periodicly
+ if (bs->check_time < trap_AAS_Time()) {
+ bs->check_time = trap_AAS_Time() + 1;
+ range = 150;
+#ifdef CTF
+ //if carrying a flag the bot shouldn't be distracted too much
+ if (BotCTFCarryingFlag(bs)) range = 100;
+#endif //CTF
+ //
+ if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
+ trap_BotResetLastAvoidReach(bs->ms);
+ //time the bot gets to pick up the nearby goal item
+ bs->nbg_time = trap_AAS_Time() + range / 100 + 1;
+ AIEnter_Battle_NBG(bs);
+ return qfalse;
+ }
+ }
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
+ //if the movement failed
+ if (moveresult.failure) {
+ //reset the avoid reach, otherwise bot is stuck in current area
+ trap_BotResetAvoidReach(bs->ms);
+ //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
+ bs->ltg_time = 0;
+ }
+ //
+ BotAIBlocked(bs, &moveresult, qfalse);
+ //choose the best weapon to fight with
+ BotChooseWeapon(bs);
+ //if the view is fixed for the movement
+ if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
+ VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
+ }
+ else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET) )
+ {
+ attack_skill = 1;
+ //if the bot is skilled anough
+ if (attack_skill > 0.3) {
+ BotAimAtEnemy(bs);
+ }
+ else {
+ if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ }
+ else {
+ vectoangles(moveresult.movedir, bs->ideal_viewangles);
+ }
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ }
+ //if the weapon is used for the bot movement
+ if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
+ //attack the enemy if possible
+ BotCheckAttack(bs);
+ //
+ return qtrue;
+}
+
+/*
+==================
+AIEnter_Battle_NBG
+==================
+*/
+void AIEnter_Battle_NBG(bot_state_t *bs) {
+ BotRecordNodeSwitch(bs, "battle NBG", "");
+ bs->ainode = AINode_Battle_NBG;
+}
+
+/*
+==================
+AINode_Battle_NBG
+==================
+*/
+int AINode_Battle_NBG(bot_state_t *bs) {
+ int areanum;
+ bot_goal_t goal;
+ aas_entityinfo_t entinfo;
+ bot_moveresult_t moveresult;
+ float attack_skill;
+ vec3_t target, dir;
+
+ if (BotIsObserver(bs)) {
+ AIEnter_Observer(bs);
+ return qfalse;
+ }
+ //if in the intermission
+ if (BotIntermission(bs)) {
+ AIEnter_Intermission(bs);
+ return qfalse;
+ }
+ //respawn if dead
+ if (BotIsDead(bs)) {
+ AIEnter_Respawn(bs);
+ return qfalse;
+ }
+ //if no enemy
+ if (bs->enemy < 0) {
+ AIEnter_Seek_NBG(bs);
+ return qfalse;
+ }
+ //
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityIsDead(&entinfo)) {
+ AIEnter_Seek_NBG(bs);
+ return qfalse;
+ }
+ //
+ bs->tfl = TFL_DEFAULT;
+ if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
+ //if in lava or slime the bot should be able to get out
+ if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
+ //
+ if (BotCanAndWantsToRocketJump(bs)) {
+ bs->tfl |= TFL_ROCKETJUMP;
+ }
+ //map specific code
+ BotMapScripts(bs);
+ //update the last time the enemy was visible
+ if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
+ bs->enemyvisible_time = trap_AAS_Time();
+ //update the reachability area and origin if possible
+ areanum = BotPointAreaNum(entinfo.origin);
+ if (areanum && trap_AAS_AreaReachability(areanum)) {
+ VectorCopy(entinfo.origin, bs->lastenemyorigin);
+ bs->lastenemyareanum = areanum;
+ }
+ }
+ //if the bot has no goal or touches the current goal
+ if (!trap_BotGetTopGoal(bs->gs, &goal)) {
+ bs->nbg_time = 0;
+ }
+ else if (trap_BotTouchingGoal(bs->origin, &goal)) {
+ bs->nbg_time = 0;
+ }
+ //
+ if (bs->nbg_time < trap_AAS_Time()) {
+ //pop the current goal from the stack
+ trap_BotPopGoal(bs->gs);
+ //if the bot still has a goal
+ if (trap_BotGetTopGoal(bs->gs, &goal)) AIEnter_Battle_Retreat(bs);
+ else AIEnter_Battle_Fight(bs);
+ //
+ return qfalse;
+ }
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
+ //if the movement failed
+ if (moveresult.failure) {
+ //reset the avoid reach, otherwise bot is stuck in current area
+ trap_BotResetAvoidReach(bs->ms);
+ //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
+ bs->nbg_time = 0;
+ }
+ //
+ BotAIBlocked(bs, &moveresult, qfalse);
+ //update the attack inventory values
+ BotUpdateBattleInventory(bs, bs->enemy);
+ //choose the best weapon to fight with
+ BotChooseWeapon(bs);
+ //if the view is fixed for the movement
+ if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
+ VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
+ }
+ else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET))
+ {
+ attack_skill = 1;
+ //if the bot is skilled anough and the enemy is visible
+ if (attack_skill > 0.3) {
+ //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)
+ BotAimAtEnemy(bs);
+ }
+ else {
+ if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
+ VectorSubtract(target, bs->origin, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ }
+ else {
+ vectoangles(moveresult.movedir, bs->ideal_viewangles);
+ }
+ bs->ideal_viewangles[2] *= 0.5;
+ }
+ }
+ //if the weapon is used for the bot movement
+ if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
+ //attack the enemy if possible
+ BotCheckAttack(bs);
+ //
+ return qtrue;
+}
+
diff --git a/game/ai_dmnet.h b/game/ai_dmnet.h
new file mode 100644
index 0000000..7cabf3a
--- /dev/null
+++ b/game/ai_dmnet.h
@@ -0,0 +1,45 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_dmnet.h
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_dmnet.h $
+ * $Author: Jmonroe $
+ * $Revision: 1 $
+ * $Modtime: 1/21/00 10:12p $
+ * $Date: 1/25/00 6:26p $
+ *
+ *****************************************************************************/
+
+#define MAX_NODESWITCHES 50
+
+void AIEnter_Intermission(bot_state_t *bs);
+void AIEnter_Observer(bot_state_t *bs);
+void AIEnter_Respawn(bot_state_t *bs);
+void AIEnter_Stand(bot_state_t *bs);
+void AIEnter_Seek_ActivateEntity(bot_state_t *bs);
+void AIEnter_Seek_NBG(bot_state_t *bs);
+void AIEnter_Seek_LTG(bot_state_t *bs);
+void AIEnter_Seek_Camp(bot_state_t *bs);
+void AIEnter_Battle_Fight(bot_state_t *bs);
+void AIEnter_Battle_Chase(bot_state_t *bs);
+void AIEnter_Battle_Retreat(bot_state_t *bs);
+void AIEnter_Battle_NBG(bot_state_t *bs);
+int AINode_Intermission(bot_state_t *bs);
+int AINode_Observer(bot_state_t *bs);
+int AINode_Respawn(bot_state_t *bs);
+int AINode_Stand(bot_state_t *bs);
+int AINode_Seek_ActivateEntity(bot_state_t *bs);
+int AINode_Seek_NBG(bot_state_t *bs);
+int AINode_Seek_LTG(bot_state_t *bs);
+int AINode_Battle_Fight(bot_state_t *bs);
+int AINode_Battle_Chase(bot_state_t *bs);
+int AINode_Battle_Retreat(bot_state_t *bs);
+int AINode_Battle_NBG(bot_state_t *bs);
+
+void BotResetNodeSwitches(void);
+void BotDumpNodeSwitches(bot_state_t *bs);
+
diff --git a/game/ai_dmq3.c b/game/ai_dmq3.c
new file mode 100644
index 0000000..a87fd00
--- /dev/null
+++ b/game/ai_dmq3.c
@@ -0,0 +1,2858 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+/*****************************************************************************
+ * name: ai_dmq3.c
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_dmq3.c $
+ * $Author: Mgummelt $
+ * $Revision: 33 $
+ * $Modtime: 4/04/01 5:01p $
+ * $Date: 4/04/01 5:17p $
+ *
+ *****************************************************************************/
+
+
+#include "g_local.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_ea.h"
+#include "be_ai_char.h"
+#include "be_ai_chat.h"
+#include "be_ai_gen.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+#include "be_ai_weap.h"
+//
+#include "ai_main.h"
+#include "ai_dmq3.h"
+#include "ai_chat.h"
+#include "ai_cmd.h"
+#include "ai_dmnet.h"
+#include "ai_team.h"
+//
+#include "chars.h" //characteristics
+#include "inv.h" //indexes into the inventory
+#include "syn.h" //synonyms
+#include "match.h" //string matching types and vars
+
+#define IDEAL_ATTACKDIST 140
+#define WEAPONINDEX_PHASER 2
+
+#define MAX_WAYPOINTS 128
+//
+bot_waypoint_t botai_waypoints[MAX_WAYPOINTS];
+bot_waypoint_t *botai_freewaypoints;
+
+//NOTE: not using a cvars which can be updated because the game should be reloaded anyway
+int gametype; //game type
+int maxclients; //maximum number of clients
+
+vmCvar_t bot_grapple;
+vmCvar_t bot_rocketjump;
+vmCvar_t bot_fastchat;
+vmCvar_t bot_nochat;
+vmCvar_t bot_testrchat;
+vmCvar_t bot_challenge;
+
+vec3_t lastteleport_origin; //last teleport event origin
+float lastteleport_time; //last teleport event time
+int max_bspmodelindex; //maximum BSP model index
+
+//CTF flag goals
+bot_goal_t ctf_redflag;
+bot_goal_t ctf_blueflag;
+
+#ifdef CTF
+/*
+==================
+BotCTFCarryingFlag
+==================
+*/
+int BotCTFCarryingFlag(bot_state_t *bs) {
+ if (gametype != GT_CTF) return CTF_FLAG_NONE;
+
+ if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED;
+ else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE;
+ return CTF_FLAG_NONE;
+}
+
+/*
+==================
+BotCTFTeam
+==================
+*/
+int BotCTFTeam(bot_state_t *bs) {
+ char info[1024];
+
+ if (gametype != GT_CTF) return CTF_TEAM_NONE;
+ if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
+ //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n");
+ return qfalse;
+ }
+ trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info));
+ //
+ if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return CTF_TEAM_RED;
+ else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return CTF_TEAM_BLUE;
+ return CTF_TEAM_NONE;
+}
+
+/*
+==================
+BotCTFRetreatGoals
+==================
+*/
+void BotCTFRetreatGoals(bot_state_t *bs) {
+ //when carrying a flag in ctf the bot should rush to the base
+ if (BotCTFCarryingFlag(bs)) {
+ //if not already rushing to the base
+ if (bs->ltgtype != LTG_RUSHBASE) {
+ bs->ltgtype = LTG_RUSHBASE;
+ bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
+ bs->rushbaseaway_time = 0;
+ }
+ }
+}
+
+/*
+==================
+EntityIsDead
+==================
+*/
+qboolean EntityIsDead(aas_entityinfo_t *entinfo) {
+ playerState_t ps;
+
+ if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) {
+ //retrieve the current client state
+ BotAI_GetClientState( entinfo->number, &ps );
+ if (ps.pm_type != PM_NORMAL) return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityIsInvisible
+==================
+*/
+qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) {
+ if (entinfo->powerups & (1 << PW_GHOST))
+ { // 50% chance of being visible?
+ if (((unsigned int)(level.time)/1024)&0x01) // Every second or so, the bot will see the player, so he doesn't jitter.
+ {
+ return qtrue;
+ }
+ else
+ {
+ return qfalse;
+ }
+ }
+ else if (entinfo->powerups & (1 << PW_INVIS))
+ {
+ return qtrue;
+ }
+ else if ( entinfo->flags & EF_NODRAW )
+ {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityCarriesFlag
+==================
+*/
+qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) {
+ /*if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) return qtrue;*/
+ //if ( entinfo->powerups & ( 1 << PW_BORG_ADAPT ) ) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+EntityIsShooting
+==================
+*/
+qboolean EntityIsShooting(aas_entityinfo_t *entinfo) {
+ if (entinfo->flags & EF_FIRING) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityIsChatting
+==================
+*/
+qboolean EntityIsChatting(aas_entityinfo_t *entinfo) {
+ if (entinfo->flags & EF_TALK) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityHasQuad
+==================
+*/
+qboolean EntityHasQuad(aas_entityinfo_t *entinfo) {
+ if (entinfo->powerups & (1 << PW_QUAD)) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotCTFSeekGoals
+==================
+*/
+void BotCTFSeekGoals(bot_state_t *bs) {
+ float rnd;
+ int flagstatus, c;
+
+ //when carrying a flag in ctf the bot should rush to the base
+ if (BotCTFCarryingFlag(bs)) {
+ //if not already rushing to the base
+ if (bs->ltgtype != LTG_RUSHBASE) {
+ bs->ltgtype = LTG_RUSHBASE;
+ bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
+ bs->rushbaseaway_time = 0;
+ }
+ else if (bs->rushbaseaway_time > trap_AAS_Time()) {
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus;
+ else flagstatus = bs->blueflagstatus;
+ //if the flag is back
+ if (flagstatus == 0) {
+ bs->rushbaseaway_time = 0;
+ }
+ }
+ return;
+ }
+ //
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus;
+ else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;
+ //if the enemy flag is not at it's base
+ if (flagstatus == 1) {
+ //if Not defending the base
+ if (!(bs->ltgtype == LTG_DEFENDKEYAREA &&
+ (bs->teamgoal.number == ctf_redflag.number ||
+ bs->teamgoal.number == ctf_blueflag.number))) {
+ //if not already accompanying someone
+ if (bs->ltgtype != LTG_TEAMACCOMPANY) {
+ //if there is avisible team mate flag carrier
+ c = BotTeamFlagCarrierVisible(bs);
+ if (c >= 0) {
+ //follow the flag carrier
+ //the team mate
+ bs->teammate = c;
+ //last time the team mate was visible
+ bs->teammatevisible_time = trap_AAS_Time();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //get the team goal time
+ bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME;
+ bs->ltgtype = LTG_TEAMACCOMPANY;
+ bs->formation_dist = 3.5 * 32; //3.5 meter
+ bs->arrive_time = 0;
+ return;
+ }
+ }
+ }
+ }
+ //if the base flag is stolen
+ else if (flagstatus == 2) {
+ //if not already going for the enemy flag
+ if (bs->ltgtype != LTG_GETFLAG) {
+ //if there's no bot team leader
+ if (!BotTeamLeader(bs)) {
+ //go for the enemy flag
+ bs->ltgtype = LTG_GETFLAG;
+ //no team message
+ bs->teammessage_time = 1;
+ //set the time the bot will stop getting the flag
+ bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
+ return;
+ }
+ }
+ }
+ //if both flags not at their bases
+ else if (flagstatus == 3) {
+ //
+ if (bs->ltgtype != LTG_GETFLAG &&
+ bs->ltgtype != LTG_TEAMACCOMPANY) {
+ //if there is avisible team mate flag carrier
+ c = BotTeamFlagCarrierVisible(bs);
+ if (c >= 0) {
+ //follow the flag carrier
+ return;
+ }
+ else {
+ //otherwise attack the enemy base
+ }
+ return;
+ }
+ }
+ //if the bot is roaming
+ if (bs->ctfroam_time > trap_AAS_Time()) return;
+ //if already a CTF or team goal
+ if (bs->ltgtype == LTG_TEAMHELP ||
+ bs->ltgtype == LTG_TEAMACCOMPANY ||
+ bs->ltgtype == LTG_DEFENDKEYAREA ||
+ bs->ltgtype == LTG_GETFLAG ||
+ bs->ltgtype == LTG_RUSHBASE ||
+ bs->ltgtype == LTG_RETURNFLAG ||
+ bs->ltgtype == LTG_CAMPORDER ||
+ bs->ltgtype == LTG_PATROL) {
+ return;
+ }
+ //if the bot has anough aggression to decide what to do
+ if (BotAggression(bs) < 50) return;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //get the flag or defend the base
+ rnd = random();
+ if (rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum) {
+ bs->ltgtype = LTG_GETFLAG;
+ //set the time the bot will stop getting the flag
+ bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
+ }
+ else if (rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum) {
+ //
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
+ else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
+ //set the ltg type
+ bs->ltgtype = LTG_DEFENDKEYAREA;
+ //set the time the bot stops defending the base
+ bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME;
+ bs->defendaway_time = 0;
+ }
+ else {
+ bs->ltgtype = 0;
+ //set the time the bot will stop roaming
+ bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME;
+ }
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+#endif //CTF
+
+/*
+==================
+BotPointAreaNum
+==================
+*/
+int BotPointAreaNum(vec3_t origin) {
+ int areanum, numareas, areas[10];
+ vec3_t end;
+
+ areanum = trap_AAS_PointAreaNum(origin);
+ if (areanum) return areanum;
+ VectorCopy(origin, end);
+ end[2] += 10;
+ numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10);
+ if (numareas > 0) return areas[0];
+ return 0;
+}
+
+/*
+==================
+ClientName
+==================
+*/
+char *ClientName(int client, char *name, int size) {
+ char buf[MAX_INFO_STRING];
+
+ if (client < 0 || client >= MAX_CLIENTS) {
+ BotAI_Print(PRT_ERROR, "ClientName: client out of range\n");
+ return "[client out of range]";
+ }
+ trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
+ strncpy(name, Info_ValueForKey(buf, "n"), size-1);
+ name[size-1] = '\0';
+ Q_CleanStr( name );
+ return name;
+}
+
+/*
+==================
+ClientSkin
+==================
+*/
+char *ClientSkin(int client, char *skin, int size) {
+ char buf[MAX_INFO_STRING];
+
+ if (client < 0 || client >= MAX_CLIENTS) {
+ BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n");
+ return "[client out of range]";
+ }
+ trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
+ strncpy(skin, Info_ValueForKey(buf, "model"), size-1);
+ skin[size-1] = '\0';
+ return skin;
+}
+
+/*
+==================
+ClientFromName
+==================
+*/
+int ClientFromName(char *name) {
+ int i;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ Q_CleanStr( buf );
+ if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
+ }
+ return -1;
+}
+
+/*
+==================
+stristr
+==================
+*/
+char *stristr(char *str, char *charset) {
+ int i;
+
+ while(*str) {
+ for (i = 0; charset[i] && str[i]; i++) {
+ if (toupper(charset[i]) != toupper(str[i])) break;
+ }
+ if (!charset[i]) return str;
+ str++;
+ }
+ return NULL;
+}
+
+/*
+==================
+EasyClientName
+==================
+*/
+char *EasyClientName(int client, char *buf, int size) {
+ int i;
+ char *str1, *str2, *ptr, c;
+ char name[128];
+
+ strcpy(name, ClientName(client, name, sizeof(name)));
+ for (i = 0; name[i]; i++) name[i] &= 127;
+ //remove all spaces
+ for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) {
+ memmove(ptr, ptr+1, strlen(ptr+1)+1);
+ }
+ //check for [x] and ]x[ clan names
+ str1 = strstr(name, "[");
+ str2 = strstr(name, "]");
+ if (str1 && str2) {
+ if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1);
+ else memmove(str2, str1+1, strlen(str1+1)+1);
+ }
+ //remove Mr prefix
+ if ((name[0] == 'm' || name[0] == 'M') &&
+ (name[1] == 'r' || name[1] == 'R')) {
+ memmove(name, name+2, strlen(name+2)+1);
+ }
+ //only allow lower case alphabet characters
+ ptr = name;
+ while(*ptr) {
+ c = *ptr;
+ if ((c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || c == '_') {
+ ptr++;
+ }
+ else if (c >= 'A' && c <= 'Z') {
+ *ptr += 'a' - 'A';
+ ptr++;
+ }
+ else {
+ memmove(ptr, ptr+1, strlen(ptr + 1)+1);
+ }
+ }
+ strncpy(buf, name, size-1);
+ buf[size-1] = '\0';
+ return buf;
+}
+
+qboolean BotUseMeleeWeapon(bot_state_t *bs) {
+ if ( bs->inventory[ENEMY_HORIZONTAL_DIST] < 64 )
+ {
+ /*if ( bs->cur_ps.persistant[PERS_CLASS] == PC_BORG || bs->cur_ps.persistant[PERS_CLASS] == PC_MEDIC )
+ {
+ return qtrue;
+ }*/
+ }
+ return qfalse;
+}
+/*
+==================
+BotChooseWeapon
+
+ MCG - FIXME: This should really take into account:
+ Projectile vs. instant?
+ gravity on projectile?
+ Range to enemy vs range of weapon?
+ Some randomness on the weights?
+
+==================
+*/
+void BotChooseWeapon(bot_state_t *bs) {
+ int newweaponnum;
+
+ if (bs->cur_ps.weaponstate == WEAPON_RAISING || bs->cur_ps.weaponstate == WEAPON_DROPPING)
+ {
+ trap_EA_SelectWeapon(bs->client, bs->weaponnum);
+ }
+ else
+ {
+ newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory, BotUseMeleeWeapon(bs));
+ if (bs->weaponnum != newweaponnum)
+ {
+ bs->weaponchange_time = trap_AAS_Time();
+ }
+ bs->weaponnum = newweaponnum;
+ //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
+ trap_EA_SelectWeapon(bs->client, bs->weaponnum);
+ }
+}
+
+/*
+==================
+BotSetupForMovement
+==================
+*/
+void BotSetupForMovement(bot_state_t *bs) {
+ bot_initmove_t initmove;
+
+ memset(&initmove, 0, sizeof(bot_initmove_t));
+ VectorCopy(bs->cur_ps.origin, initmove.origin);
+ VectorCopy(bs->cur_ps.velocity, initmove.velocity);
+ VectorCopy(bs->cur_ps.origin, initmove.viewoffset);
+ initmove.viewoffset[2] += bs->cur_ps.viewheight;
+ initmove.entitynum = bs->entitynum;
+ initmove.client = bs->client;
+ initmove.thinktime = bs->thinktime;
+ //set the onground flag
+ if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND;
+ //set the teleported flag
+ if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) {
+ initmove.or_moveflags |= MFL_TELEPORTED;
+ }
+ //set the waterjump flag
+ if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) {
+ initmove.or_moveflags |= MFL_WATERJUMP;
+ }
+ //set presence type
+ if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH;
+ else initmove.presencetype = PRESENCE_NORMAL;
+ //
+ if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK;
+ //
+ VectorCopy(bs->viewangles, initmove.viewangles);
+ //
+ trap_BotInitMoveState(bs->ms, &initmove);
+}
+
+/*
+==================
+BotUpdateInventory
+==================
+*/
+void BotUpdateInventory(bot_state_t *bs) {
+ //armor
+ bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR];
+
+ //weapons
+ bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0;
+ bs->inventory[INVENTORY_STASIS] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DISRUPTOR)) != 0;
+ bs->inventory[INVENTORY_PHASER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PHASER)) != 0;
+ bs->inventory[INVENTORY_DREADNOUGHT] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DERMAL_REGEN)) != 0;
+ bs->inventory[INVENTORY_IMOD] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NULL_HAND)) != 0;
+ bs->inventory[INVENTORY_COMPRESSION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COMPRESSION_RIFLE)) != 0;
+ bs->inventory[INVENTORY_TETRION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_TR116)) != 0;
+ bs->inventory[INVENTORY_SCAVENGER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COFFEE)) != 0;
+ bs->inventory[INVENTORY_QUANTUM] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_QUANTUM_BURST)) != 0;
+
+ //ammo
+ bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER];
+ bs->inventory[INVENTORY_STASISAMMO] = bs->cur_ps.ammo[WP_DISRUPTOR];
+ bs->inventory[INVENTORY_PHASERAMMO] = bs->cur_ps.ammo[WP_PHASER];
+ bs->inventory[INVENTORY_DREADNOUGHTAMMO] = bs->cur_ps.ammo[WP_DERMAL_REGEN];
+ bs->inventory[INVENTORY_IMODAMMO] = bs->cur_ps.ammo[WP_NULL_HAND];
+ bs->inventory[INVENTORY_COMPRESSIONAMMO] = bs->cur_ps.ammo[WP_COMPRESSION_RIFLE];
+ bs->inventory[INVENTORY_TETRIONAMMO] = bs->cur_ps.ammo[WP_TR116];
+ bs->inventory[INVENTORY_SCAVENGERAMMO] = bs->cur_ps.ammo[WP_COFFEE];
+ bs->inventory[INVENTORY_QUANTUMAMMO] = bs->cur_ps.ammo[WP_QUANTUM_BURST];
+
+ //powerups
+ bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH];
+ bs->inventory[INVENTORY_TRANSPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER;
+ bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT;
+ bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0;
+ bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BOLTON] != 0;
+ bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0;
+ bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0;
+ bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_LASER] != 0;
+ bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0;
+ //bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
+ //bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
+ bs->inventory[INVENTORY_SEEKER] = bs->cur_ps.powerups[PW_FLASHLIGHT] != 0;
+ bs->inventory[INVENTORY_SHIELD] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_SHIELD;
+ bs->inventory[INVENTORY_DETPACK] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_DETPACK;
+ //
+}
+
+/*
+==================
+BotUpdateBattleInventory
+==================
+*/
+void BotUpdateBattleInventory(bot_state_t *bs, int enemy) {
+ vec3_t dir;
+ aas_entityinfo_t entinfo;
+
+ BotEntityInfo(enemy, &entinfo);
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ bs->inventory[ENEMY_HEIGHT] = (int) dir[2];
+ dir[2] = 0;
+ bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir);
+ //FIXME: add num visible enemies and num visible team mates to the inventory
+}
+
+/*
+=========================
+BotShouldDetonateDetPack
+=========================
+*/
+#define DETPACK_RADIUS 500
+
+qboolean BotShouldDetonateDetPack(bot_state_t *bs)
+{
+ int botNum = 0, detWeight = 0;
+ vec3_t packOrg, dir;
+ float dist;
+ aas_entityinfo_t botinfo;
+
+ // find the location of the DetPack
+ gentity_t *detpack = NULL;
+ char *classname = BG_FindClassnameForHoldable(HI_DETPACK);
+
+ if (!classname)
+ {
+ return qfalse;
+ }
+
+ //while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL)
+ while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL)
+ {
+ VectorCopy(detpack->r.currentOrigin, packOrg);
+ }
+
+ // determine who would be killed in the blast radius
+ for (botNum = 0; botNum < MAX_CLIENTS; botNum++)
+ {
+ BotEntityInfo(botNum, &botinfo);
+ if (!botinfo.valid) continue;
+
+ //calculate the distance towards the enemy
+ VectorSubtract(botinfo.origin, packOrg, dir);
+ dist = VectorLength(dir);
+
+ if (dist < DETPACK_RADIUS) // bot would get caught in blast radius
+ {
+ if (BotSameTeam(bs, botNum)) // friendly casualty potential
+ {
+ if (botNum == bs->client) // suicide... bad
+ {
+ detWeight--;
+ }
+ if (EntityCarriesFlag(&botinfo)) // it's my teammate, and he's got the flag!
+ {
+ detWeight -= 11;
+ continue;
+ }
+ detWeight--;
+ }
+ else
+ {
+ if(EntityCarriesFlag(&botinfo)) // mwahaha
+ {
+ detWeight += 14;
+ }
+ detWeight++;
+ }
+ }
+ }
+
+// Com_Printf("detWeight %d\n", detWeight);
+
+ if (detWeight > 0)
+ {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+
+/*
+==================
+BotBattleUseItems
+==================
+*/
+void BotBattleUseItems(bot_state_t *bs) {
+
+
+ if (bs->inventory[INVENTORY_DETPACK] > 0)
+ {
+ // this needs to be in two stages: placement and detonation
+ if (bs->ltgtype == LTG_DEFENDKEYAREA)
+ {
+ if (bs->inventory[INVENTORY_DETPACK_PLACED] == 0) // not placed yet
+ {
+ bs->inventory[INVENTORY_DETPACK_PLACED] = 1;
+ trap_EA_Use(bs->client); // place it
+ return;
+ }
+ }
+
+ if (bs->inventory[INVENTORY_DETPACK_PLACED] == 1) // placed
+ {
+ if (BotShouldDetonateDetPack(bs)) // logic
+ {
+ bs->inventory[INVENTORY_DETPACK_PLACED] = 0;
+ trap_EA_Use(bs->client); // BOOM
+ return;
+ }
+ return;
+ }
+ return;
+ }
+
+ if (bs->inventory[INVENTORY_SHIELD] > 0)
+ {
+ if (BotWantsToRetreat(bs) && (bs->inventory[INVENTORY_HEALTH] < 50))
+ {
+ trap_EA_Use(bs->client);
+ }
+ return;
+ }
+
+ if (bs->inventory[INVENTORY_TRANSPORTER] > 0)
+ {
+ if (!BotCTFCarryingFlag(bs) && (bs->inventory[INVENTORY_HEALTH] < 50))
+ {
+ trap_EA_Use(bs->client);
+ return;
+ }
+ }
+
+ if (bs->inventory[INVENTORY_MEDKIT] > 0)
+ {
+ if (bs->inventory[INVENTORY_HEALTH] < 30)
+ {
+ trap_EA_Use(bs->client);
+ }
+ return;
+ }
+}
+
+/*
+==================
+BotSetTeleportTime
+==================
+*/
+void BotSetTeleportTime(bot_state_t *bs) {
+ if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) {
+ bs->teleport_time = trap_AAS_Time();
+ }
+ bs->last_eFlags = bs->cur_ps.eFlags;
+}
+
+/*
+==================
+BotIsDead
+==================
+*/
+qboolean BotIsDead(bot_state_t *bs) {
+ return (bs->cur_ps.pm_type == PM_DEAD);
+}
+
+/*
+==================
+BotIsObserver
+==================
+*/
+qboolean BotIsObserver(bot_state_t *bs) {
+ char buf[MAX_INFO_STRING];
+ if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue;
+ trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf));
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+BotIntermission
+==================
+*/
+qboolean BotIntermission(bot_state_t *bs) {
+ //NOTE: we shouldn't be looking at the game code...
+ if (level.intermissiontime) return qtrue;
+ return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION);
+}
+
+/*
+==================
+BotInLavaOrSlime
+==================
+*/
+qboolean BotInLavaOrSlime(bot_state_t *bs) {
+ vec3_t feet;
+
+ VectorCopy(bs->origin, feet);
+ feet[2] -= 23;
+ return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME));
+}
+
+/*
+==================
+BotCreateWayPoint
+==================
+*/
+bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) {
+ bot_waypoint_t *wp;
+ vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8};
+
+ wp = botai_freewaypoints;
+ if ( !wp ) {
+ BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" );
+ return NULL;
+ }
+ botai_freewaypoints = botai_freewaypoints->next;
+
+ Q_strncpyz( wp->name, name, sizeof(wp->name) );
+ VectorCopy(origin, wp->goal.origin);
+ VectorCopy(waypointmins, wp->goal.mins);
+ VectorCopy(waypointmaxs, wp->goal.maxs);
+ wp->goal.areanum = areanum;
+ wp->next = NULL;
+ wp->prev = NULL;
+ return wp;
+}
+
+/*
+==================
+BotFindWayPoint
+==================
+*/
+bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) {
+ bot_waypoint_t *wp;
+
+ for (wp = waypoints; wp; wp = wp->next) {
+ if (!Q_stricmp(wp->name, name)) return wp;
+ }
+ return NULL;
+}
+
+/*
+==================
+BotFreeWaypoints
+==================
+*/
+void BotFreeWaypoints(bot_waypoint_t *wp) {
+ bot_waypoint_t *nextwp;
+
+ for (; wp; wp = nextwp) {
+ nextwp = wp->next;
+ wp->next = botai_freewaypoints;
+ botai_freewaypoints = wp;
+ }
+}
+
+/*
+==================
+BotInitWaypoints
+==================
+*/
+void BotInitWaypoints(void) {
+ int i;
+
+ botai_freewaypoints = NULL;
+ for (i = 0; i < MAX_WAYPOINTS; i++) {
+ botai_waypoints[i].next = botai_freewaypoints;
+ botai_freewaypoints = &botai_waypoints[i];
+ }
+}
+
+/*
+==================
+TeamPlayIsOn
+==================
+*/
+int TeamPlayIsOn(void) {
+ return ( gametype == GT_TEAM || gametype == GT_CTF );
+}
+
+/*
+==================
+BotAggression
+==================
+*/
+float BotAggression(bot_state_t *bs) {
+ //if the bot has quad
+ if (bs->inventory[INVENTORY_QUAD]) {
+ //if the bot is not holding the gauntlet or the enemy is really nearby
+ if (bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) {
+ return 70;
+ }
+ }
+ //if the enemy is located way higher than the bot
+ if (bs->inventory[ENEMY_HEIGHT] > 200) return 0;
+ //if the bot is very low on health
+ if (bs->inventory[INVENTORY_HEALTH] < 60) return 0;
+ //if the bot is low on health
+ if (bs->inventory[INVENTORY_HEALTH] < 80) {
+ //if the bot has insufficient armor
+ if (bs->inventory[INVENTORY_ARMOR] < 40) return 0;
+ }
+
+ if (bs->inventory[INVENTORY_DREADNOUGHT] > 0 &&
+ bs->inventory[INVENTORY_DREADNOUGHTAMMO] > 0) return 100;
+
+ if (bs->inventory[INVENTORY_TETRION] > 0 &&
+ bs->inventory[INVENTORY_TETRIONAMMO] > 0) return 95;
+
+ if (bs->inventory[INVENTORY_QUANTUM] > 0 &&
+ bs->inventory[INVENTORY_QUANTUMAMMO] > 0) return 90;
+
+ if (bs->inventory[INVENTORY_STASIS] > 0 &&
+ bs->inventory[INVENTORY_STASISAMMO] > 0) return 85;
+
+ if (bs->inventory[INVENTORY_SCAVENGER] > 0 &&
+ bs->inventory[INVENTORY_SCAVENGERAMMO] > 0) return 80;
+
+ if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
+ bs->inventory[INVENTORY_GRENADES] > 0) return 75;
+
+ if (bs->inventory[INVENTORY_IMOD] > 0 &&
+ bs->inventory[INVENTORY_IMODAMMO] > 0) return 70;
+
+ if (bs->inventory[INVENTORY_COMPRESSION] > 0 &&
+ bs->inventory[INVENTORY_COMPRESSIONAMMO] > 0) return 65;
+
+ //otherwise the bot is not feeling too aggressive
+ return 0;
+}
+
+/*
+==================
+BotWantsToRetreat
+==================
+*/
+int BotWantsToRetreat(bot_state_t *bs) {
+ aas_entityinfo_t entinfo;
+
+ //always retreat when carrying a CTF flag
+ if (BotCTFCarryingFlag(bs)) return qtrue;
+ //
+ if (bs->enemy >= 0) {
+ //if the enemy is carrying a flag
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityCarriesFlag(&entinfo)) return qfalse;
+ }
+ //if the bot is getting the flag
+ if (bs->ltgtype == LTG_GETFLAG) return qtrue;
+ //
+ if (BotAggression(bs) < 50) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+BotWantsToChase
+==================
+*/
+int BotWantsToChase(bot_state_t *bs) {
+ aas_entityinfo_t entinfo;
+
+ //always retreat when carrying a CTF flag
+ if (BotCTFCarryingFlag(bs)) return qfalse;
+ //if the enemy is carrying a flag
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityCarriesFlag(&entinfo)) return qtrue;
+ //if the bot is getting the flag
+ if (bs->ltgtype == LTG_GETFLAG) return qfalse;
+ //
+ if (BotAggression(bs) > 50) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+BotWantsToHelp
+==================
+*/
+int BotWantsToHelp(bot_state_t *bs) {
+ return qtrue;
+}
+
+/*
+==================
+BotCanAndWantsToRocketJump
+==================
+*/
+int BotCanAndWantsToRocketJump(bot_state_t *bs) {
+ float rocketjumper;
+
+ //if rocket jumping is disabled
+ if (!bot_rocketjump.integer) return qfalse;
+ //if no rocket launcher
+ if (bs->inventory[INVENTORY_QUANTUM] <= 0) return qfalse;
+ //if low on rockets
+ if (bs->inventory[INVENTORY_QUANTUMAMMO] < 1) return qfalse;
+ //never rocket jump with the Quad
+ if (bs->inventory[INVENTORY_QUAD])
+ {
+ if ( rpg_selfdamage.integer != 0 )
+ {
+ return qfalse;
+ }
+ }
+ //if low on health
+ if (bs->inventory[INVENTORY_HEALTH] < 50)
+ { //if not full health
+ if ( rpg_selfdamage.integer != 0 )
+ {
+ return qfalse;
+ }
+ }
+ if (bs->inventory[INVENTORY_HEALTH] < 60)
+ { //if the bot has insufficient armor
+ if (bs->inventory[INVENTORY_ARMOR] < 20)
+ {
+ if ( rpg_selfdamage.integer != 0 )
+ {
+ return qfalse;
+ }
+ }
+ }
+ rocketjumper = 1;
+ return qtrue;
+}
+
+/*
+==================
+BotGoCamp
+==================
+*/
+void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) {
+ float camper;
+
+ //set message time to zero so bot will NOT show any message
+ bs->teammessage_time = 0;
+ //set the ltg type
+ bs->ltgtype = LTG_CAMP;
+ //set the team goal
+ memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t));
+ //get the team goal time
+ camper = 0;
+ if (camper > 0.99) bs->teamgoal_time = 99999;
+ else bs->teamgoal_time = 120 + 180 * camper + random() * 15;
+ //set the last time the bot started camping
+ bs->camp_time = trap_AAS_Time();
+ //the teammate that requested the camping
+ bs->teammate = 0;
+ //do NOT type arrive message
+ bs->arrive_time = 1;
+}
+
+/*
+==================
+BotWantsToCamp
+==================
+*/
+int BotWantsToCamp(bot_state_t *bs) {
+ float camper;
+ camper = 0;
+ return qfalse;
+}
+
+/*
+==================
+BotDontAvoid
+==================
+*/
+void BotDontAvoid(bot_state_t *bs, char *itemname) {
+ bot_goal_t goal;
+ int num;
+
+ num = trap_BotGetLevelItemGoal(-1, itemname, &goal);
+ while(num >= 0) {
+ trap_BotRemoveFromAvoidGoals(bs->gs, goal.number);
+ num = trap_BotGetLevelItemGoal(num, itemname, &goal);
+ }
+}
+
+/*
+==================
+BotGoForPowerups
+==================
+*/
+void BotGoForPowerups(bot_state_t *bs) {
+
+ //don't avoid any of the powerups anymore
+ BotDontAvoid(bs, "Quantum Weapon Enhancer");
+ BotDontAvoid(bs, "Nano-Regenerative Protoplasmer");
+ BotDontAvoid(bs, "Metaphasic Shielding");
+ BotDontAvoid(bs, "Temporal Accelerator");
+ BotDontAvoid(bs, "Personal Cloaking Device");
+ BotDontAvoid(bs, "Seeker Drone");
+ BotDontAvoid(bs, "Anti-Gravity Pack");
+ //reset the long term goal time so the bot will go for the powerup
+ //NOTE: the long term goal type doesn't change
+ bs->ltg_time = 0;
+}
+
+/*
+==================
+BotRoamGoal
+==================
+*/
+void BotRoamGoal(bot_state_t *bs, vec3_t goal) {
+ int pc, i;
+ float len, rnd;
+ vec3_t dir, bestorg, belowbestorg;
+ bsp_trace_t trace;
+
+ for (i = 0; i < 10; i++) {
+ //start at the bot origin
+ VectorCopy(bs->origin, bestorg);
+ rnd = random();
+ if (rnd > 0.25) {
+ //add a random value to the x-coordinate
+ if (random() < 0.5) bestorg[0] -= 800 * random() + 100;
+ else bestorg[0] += 800 * random() + 100;
+ }
+ if (rnd < 0.75) {
+ //add a random value to the y-coordinate
+ if (random() < 0.5) bestorg[1] -= 800 * random() + 100;
+ else bestorg[1] += 800 * random() + 100;
+ }
+ //add a random value to the z-coordinate (NOTE: 48 = maxjump?)
+ bestorg[2] += 2 * 48 * crandom();
+ //trace a line from the origin to the roam target
+ BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID);
+ //direction and length towards the roam target
+ VectorSubtract(trace.endpos, bs->origin, dir);
+ len = VectorNormalize(dir);
+ //if the roam target is far away anough
+ if (len > 200) {
+ //the roam target is in the given direction before walls
+ VectorScale(dir, len * trace.fraction - 40, dir);
+ VectorAdd(bs->origin, dir, bestorg);
+ //get the coordinates of the floor below the roam target
+ belowbestorg[0] = bestorg[0];
+ belowbestorg[1] = bestorg[1];
+ belowbestorg[2] = bestorg[2] - 800;
+ BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID);
+ //
+ if (!trace.startsolid) {
+ trace.endpos[2]++;
+ pc = trap_PointContents(trace.endpos, bs->entitynum);
+ if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) {
+ VectorCopy(bestorg, goal);
+ return;
+ }
+ }
+ }
+ }
+ VectorCopy(bestorg, goal);
+}
+
+/*
+==================
+BotAttackMove
+==================
+*/
+bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) {
+ int movetype, i;
+ float attack_skill, jumper, croucher, dist, strafechange_time;
+ float attack_dist, attack_range;
+ vec3_t forward, backward, sideward, hordir, up = {0, 0, 1};
+ aas_entityinfo_t entinfo;
+ bot_moveresult_t moveresult;
+ bot_goal_t goal;
+
+ if (bs->attackchase_time > trap_AAS_Time()) {
+ //create the chase goal
+ goal.entitynum = bs->enemy;
+ goal.areanum = bs->lastenemyareanum;
+ VectorCopy(bs->lastenemyorigin, goal.origin);
+ VectorSet(goal.mins, -8, -8, -8);
+ VectorSet(goal.maxs, 8, 8, 8);
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl);
+ return moveresult;
+ }
+ //
+ memset(&moveresult, 0, sizeof(bot_moveresult_t));
+ //
+ attack_skill = 1;
+ jumper = 1;
+ croucher = 1;
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //get the enemy entity info
+ BotEntityInfo(bs->enemy, &entinfo);
+ //direction towards the enemy
+ VectorSubtract(entinfo.origin, bs->origin, forward);
+ //the distance towards the enemy
+ dist = VectorNormalize(forward);
+ VectorNegate(forward, backward);
+ //walk, crouch or jump
+ movetype = MOVE_WALK;
+ //
+ if (bs->attackcrouch_time < trap_AAS_Time() - 1) {
+ if (random() < jumper) {
+ movetype = MOVE_JUMP;
+ }
+ //wait at least one second before crouching again
+ else if (bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher) {
+ bs->attackcrouch_time = trap_AAS_Time() + croucher * 5;
+ }
+ }
+ if (bs->attackcrouch_time > trap_AAS_Time()) movetype = MOVE_CROUCH;
+ //if the bot should jump
+ if (movetype == MOVE_JUMP) {
+ //if jumped last frame
+ if (bs->attackjump_time > trap_AAS_Time()) {
+ movetype = MOVE_WALK;
+ }
+ else {
+ bs->attackjump_time = trap_AAS_Time() + 1;
+ }
+ }
+
+ //if using assimilator or alt-fire hypo...
+ if ( bs->weaponnum == WP_TOOLKIT || bs->weaponnum == (WP_VOYAGER_HYPO+WP_NUM_WEAPONS) )
+ {//get real close
+ attack_dist = 16;
+ attack_range = 16;
+ }
+ else
+ {
+ attack_dist = IDEAL_ATTACKDIST;
+ attack_range = 40;
+ }
+
+ //increase the strafe time
+ bs->attackstrafe_time += bs->thinktime;
+ //get the strafe change time
+ strafechange_time = 0.4 + (1 - attack_skill) * 0.2;
+ if (attack_skill > 0.7) strafechange_time += crandom() * 0.2;
+ //if the strafe direction should be changed
+ if (bs->attackstrafe_time > strafechange_time) {
+ //some magic number :)
+ if (random() > 0.935) {
+ //flip the strafe direction
+ bs->flags ^= BFL_STRAFERIGHT;
+ bs->attackstrafe_time = 0;
+ }
+ }
+ //
+ for (i = 0; i < 2; i++) {
+ hordir[0] = forward[0];
+ hordir[1] = forward[1];
+ hordir[2] = 0;
+ VectorNormalize(hordir);
+ //get the sideward vector
+ CrossProduct(hordir, up, sideward);
+ //reverse the vector depending on the strafe direction
+ if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward);
+ //randomly go back a little
+ if (random() > 0.9) {
+ VectorAdd(sideward, backward, sideward);
+ }
+ else {
+ //walk forward or backward to get at the ideal attack distance
+ if (dist > attack_dist + attack_range) VectorAdd(sideward, forward, sideward);
+ else if (dist < attack_dist - attack_range) VectorAdd(sideward, backward, sideward);
+ }
+ //perform the movement
+ if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) return moveresult;
+ //movement failed, flip the strafe direction
+ bs->flags ^= BFL_STRAFERIGHT;
+ bs->attackstrafe_time = 0;
+ }
+ //bot couldn't do any usefull movement
+// bs->attackchase_time = AAS_Time() + 6;
+ return moveresult;
+}
+
+/*
+==================
+BotSameTeam
+==================
+*/
+int BotSameTeam(bot_state_t *bs, int entnum) {
+ char info1[1024], info2[1024];
+
+ if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
+ //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
+ return qfalse;
+ }
+ if (entnum < 0 || entnum >= MAX_CLIENTS) {
+ //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
+ return qfalse;
+ }
+ if (gametype == GT_TEAM || gametype == GT_CTF) {
+ trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1));
+ trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2));
+ //
+ if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+InFieldOfVision
+==================
+*/
+qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
+{
+ int i;
+ float diff, angle;
+
+ for (i = 0; i < 2; i++) {
+ angle = AngleMod(viewangles[i]);
+ angles[i] = AngleMod(angles[i]);
+ diff = angles[i] - angle;
+ if (angles[i] > angle) {
+ if (diff > 180.0) diff -= 360.0;
+ }
+ else {
+ if (diff < -180.0) diff += 360.0;
+ }
+ if (diff > 0) {
+ if (diff > fov * 0.5) return qfalse;
+ }
+ else {
+ if (diff < -fov * 0.5) return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotEntityVisible
+
+returns visibility in the range [0, 1] taking fog and water surfaces into account
+==================
+*/
+float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) {
+ int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
+ float fogdist, waterfactor, vis, bestvis;
+ bsp_trace_t trace;
+ aas_entityinfo_t entinfo;
+ vec3_t dir, entangles, start, end, middle;
+
+ //calculate middle of bounding box
+ BotEntityInfo(ent, &entinfo);
+ VectorAdd(entinfo.mins, entinfo.maxs, middle);
+ VectorScale(middle, 0.5, middle);
+ VectorAdd(entinfo.origin, middle, middle);
+ //check if entity is within field of vision
+ VectorSubtract(middle, eye, dir);
+ vectoangles(dir, entangles);
+ if (!InFieldOfVision(viewangles, fov, entangles)) return 0;
+ //
+ pc = trap_AAS_PointContents(eye);
+ infog = (pc & CONTENTS_SOLID);
+ inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER));
+ //
+ bestvis = 0;
+ for (i = 0; i < 3; i++) {
+ //if the point is not in potential visible sight
+ //if (!AAS_inPVS(eye, middle)) continue;
+ //
+ contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP;
+ passent = viewer;
+ hitent = ent;
+ VectorCopy(eye, start);
+ VectorCopy(middle, end);
+ //if the entity is in water, lava or slime
+ if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
+ contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
+ }
+ //if eye is in water, lava or slime
+ if (inwater) {
+ if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) {
+ passent = ent;
+ hitent = viewer;
+ VectorCopy(middle, start);
+ VectorCopy(eye, end);
+ }
+ contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
+ }
+ //trace from start to end
+ BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask);
+ //if water was hit
+ waterfactor = 1.0;
+ if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
+ //if the water surface is translucent
+ if (1) {
+ //trace through the water
+ contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
+ BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
+ waterfactor = 0.5;
+ }
+ }
+ //if a full trace or the hitent was hit
+ if (trace.fraction >= 1 || trace.ent == hitent) {
+ //check for fog, assuming there's only one fog brush where
+ //either the viewer or the entity is in or both are in
+ otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG);
+ if (infog && otherinfog) {
+ VectorSubtract(trace.endpos, eye, dir);
+ fogdist = VectorLength(dir);
+ }
+ else if (infog) {
+ VectorCopy(trace.endpos, start);
+ BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
+ VectorSubtract(eye, trace.endpos, dir);
+ fogdist = VectorLength(dir);
+ }
+ else if (otherinfog) {
+ VectorCopy(trace.endpos, end);
+ BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
+ VectorSubtract(end, trace.endpos, dir);
+ fogdist = VectorLength(dir);
+ }
+ else {
+ //if the entity and the viewer are not in fog assume there's no fog in between
+ fogdist = 0;
+ }
+ //decrease visibility with the view distance through fog
+ vis = 1 / ((fogdist * fogdist * 0.001) < 1 ? 1 : (fogdist * fogdist * 0.001));
+ //if entering water visibility is reduced
+ vis *= waterfactor;
+ //
+ if (vis > bestvis) bestvis = vis;
+ //if pretty much no fog
+ if (bestvis >= 0.95) return bestvis;
+ }
+ //check bottom and top of bounding box as well
+ if (i == 0) middle[2] += entinfo.mins[2];
+ else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2];
+ }
+ return bestvis;
+}
+
+/*
+==================
+BotFindEnemy
+==================
+*/
+int BotFindEnemy(bot_state_t *bs, int curenemy) {
+ int i, healthdecrease;
+ float f, dist, curdist, alertness, easyfragger, vis;
+ aas_entityinfo_t entinfo, curenemyinfo;
+ vec3_t dir, angles;
+
+ alertness = 1;
+ easyfragger = 1;
+ //check if the health decreased
+ healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
+ //remember the current health value
+ bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
+ //
+ if (curenemy >= 0) {
+ BotEntityInfo(curenemy, &curenemyinfo);
+ if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
+ VectorSubtract(curenemyinfo.origin, bs->origin, dir);
+ curdist = VectorLength(dir);
+ }
+ else {
+ curdist = 0;
+ }
+ //
+
+ //FIXME: This only finds lowest numbered enemy, not the closest or best!!!
+ // 6/15/00 dpk changed this
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+
+ if (i == bs->client) continue;
+ //if it's the current enemy
+ if (i == curenemy) continue;
+ //
+ BotEntityInfo(i, &entinfo);
+ //
+ if (!entinfo.valid) continue;
+ //if on the same team
+ if (BotSameTeam(bs, i)) continue;
+ //if the enemy isn't dead and the enemy isn't the bot self
+ if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
+ //if the enemy is invisible and not shooting
+ if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
+ continue;
+ }
+ //if not an easy fragger don't shoot at chatting players
+ if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue;
+ //
+ if (lastteleport_time > trap_AAS_Time() - 3) {
+ VectorSubtract(entinfo.origin, lastteleport_origin, dir);
+ if (VectorLength(dir) < 70) continue;
+ }
+ //calculate the distance towards the enemy
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ dist = VectorLength(dir);
+
+
+ //if this entity is not carrying a flag
+ if (!EntityCarriesFlag(&entinfo))
+// if (EntityCarriesFlag(&entinfo))
+/* {
+ // pick this one!
+ }
+ else
+*/ {
+ //if this enemy is further away than the current one
+ if (curenemy >= 0 && dist > curdist) continue;
+ }
+
+ //if the bot in too far away for me to notice
+ if (dist > 900 + alertness * 4000) continue;
+
+
+ //if the bot's health decreased or the enemy is shooting
+ if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) f = 360;
+ else f = 90 + 90 - (90 - (dist > 810 ? 810 : dist) / 9);
+ //check if the enemy is visible
+ vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i);
+ if (vis <= 0) continue;
+ //if the enemy is quite far away, not shooting and the bot is not damaged
+ if (curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting(&entinfo))
+ {
+ //check if we can avoid this enemy
+ VectorSubtract(bs->origin, entinfo.origin, dir);
+ vectoangles(dir, angles);
+ //if the bot isn't in the fov of the enemy
+ if (!InFieldOfVision(entinfo.angles, 120, angles)) {
+ //update some stuff for this enemy
+ BotUpdateBattleInventory(bs, i);
+ //if the bot doesn't really want to fight
+ if (BotWantsToRetreat(bs)) continue;
+ }
+ }
+// }
+ //found an enemy
+ bs->enemy = entinfo.number;
+ if (curenemy >= 0) bs->enemysight_time = trap_AAS_Time() - 2;
+ else bs->enemysight_time = trap_AAS_Time();
+ bs->enemysuicide = qfalse;
+ bs->enemydeath_time = 0;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotTeamFlagCarrierVisible
+==================
+*/
+int BotTeamFlagCarrierVisible(bot_state_t *bs) {
+ int i;
+ float vis;
+ aas_entityinfo_t entinfo;
+
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ if (i == bs->client) continue;
+ //
+ BotEntityInfo(i, &entinfo);
+ //if this player is active
+ if (!entinfo.valid) continue;
+ //if this player is carrying a flag
+ if (!EntityCarriesFlag(&entinfo)) continue;
+ //if the flag carrier is not on the same team
+ if (!BotSameTeam(bs, i)) continue;
+ //if the flag carrier is not visible
+ vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
+ if (vis <= 0) continue;
+ //
+ return i;
+ }
+ return -1;
+}
+
+/*
+==================
+BotAimAtEnemy
+
+ MCG - FIXME: This is not set up at all correctly in the following areas:
+ Needs to consider if weapon is an alt weapon for aim and accuracy as well as functionality
+ Need to consider range?
+ Grenade Primary and alt as well as Scavenger alt need to take into account gravity
+ Needs to pick our new weapons correctly to determine which ones they should:
+ lead with (projectiles have speed)
+ decay aim purposely (instant hit weaps)
+ shoot "around corners" (bouncers, mines, splashdamage?)
+
+==================
+*/
+void BotAimAtEnemy(bot_state_t *bs) {
+ int i, enemyvisible;
+ float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
+ vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
+ vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
+ weaponinfo_t wi;
+ aas_entityinfo_t entinfo;
+ bot_goal_t goal;
+ bsp_trace_t trace;
+ vec3_t target;
+
+ //if the bot has no enemy
+ if (bs->enemy < 0) return;
+ //
+ //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
+ //
+ aim_skill = 1;
+ aim_accuracy = 1;
+ //
+ if (aim_skill > 0.95)
+ {
+ //don't aim too early
+ reactiontime = 0.5;
+ if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
+ if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
+ }
+
+ //get the weapon information
+ trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
+ //get the weapon specific aim accuracy and or aim skill
+
+ if (wi.number == WP_GRENADE_LAUNCHER) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
+ aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
+ }
+ if (wi.number == WP_DISRUPTOR) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_STASIS, 0, 1);
+ aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_STASIS, 0, 1);
+ }
+ if (wi.number == WP_PHASER) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PHASER, 0, 1);
+ }
+ if (wi.number == WP_NULL_HAND) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_IMOD, 0, 1);
+ }
+ if (wi.number == WP_COMPRESSION_RIFLE) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_COMPRESSION, 0, 1);
+ }
+ if (wi.number == WP_TR116) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_TETRION, 0, 1);
+ }
+ if (wi.number == WP_DERMAL_REGEN) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_DREADNOUGHT, 0, 1);
+ }
+ if (wi.number == WP_QUANTUM_BURST) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_QUANTUM, 0, 1);
+ aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_QUANTUM, 0, 1);
+ }
+ if (wi.number == WP_COFFEE) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SCAVENGER, 0, 1);
+ }
+
+ //
+ if (aim_accuracy <= 0) aim_accuracy = 0.0001;
+ //get the enemy entity information
+ BotEntityInfo(bs->enemy, &entinfo);
+ //if the enemy is invisible then shoot crappy most of the time
+ if (EntityIsInvisible(&entinfo)) {
+ if (random() > 0.1) aim_accuracy *= 0.4;
+ }
+ //
+ VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity);
+ VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
+ //enemy origin and velocity is remembered every 0.5 seconds
+ if (bs->enemyposition_time < trap_AAS_Time()) {
+ //
+ bs->enemyposition_time = trap_AAS_Time() + 0.5;
+ VectorCopy(enemyvelocity, bs->enemyvelocity);
+ VectorCopy(entinfo.origin, bs->enemyorigin);
+ }
+ //if not extremely skilled
+ if (aim_skill < 0.9) {
+ VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
+ //if the enemy moved a bit
+ if (VectorLength(dir) > 48) {
+ //if the enemy changed direction
+ if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
+ //aim accuracy should be worse now
+ aim_accuracy *= 0.7;
+ }
+ }
+ }
+ //check visibility of enemy
+ enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy);
+ //if the enemy is visible
+ if (enemyvisible) {
+ //
+ VectorCopy(entinfo.origin, bestorigin);
+ bestorigin[2] += 8;
+ //get the start point shooting from
+ //NOTE: the x and y projectile start offsets are ignored
+ VectorCopy(bs->origin, start);
+ start[2] += bs->cur_ps.viewheight;
+ start[2] += wi.offset[2];
+ //
+ BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
+ //if the enemy is NOT hit
+ if (trace.fraction <= 1 && trace.ent != entinfo.number) {
+ bestorigin[2] += 16;
+ }
+ //if it is not an instant hit weapon the bot might want to predict the enemy
+ if (wi.speed) {
+ //
+ VectorSubtract(bestorigin, bs->origin, dir);
+ dist = VectorLength(dir);
+ VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
+ //if the enemy is NOT pretty far away and strafing just small steps left and right
+ if (!(dist > 100 && VectorLength(dir) < 32)) {
+ //if skilled anough do exact prediction
+ if (aim_skill > 0.8 &&
+ //if the weapon is ready to fire
+ bs->cur_ps.weaponstate == WEAPON_READY) {
+ aas_clientmove_t move;
+ vec3_t origin;
+
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ //distance towards the enemy
+ dist = VectorLength(dir);
+ //direction the enemy is moving in
+ VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
+ //
+ VectorScale(dir, 1 / entinfo.update_time, dir);
+ //
+ VectorCopy(entinfo.origin, origin);
+ origin[2] += 1;
+ //
+ VectorClear(cmdmove);
+ //AAS_ClearShownDebugLines();
+ trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
+ PRESENCE_CROUCH, qfalse,
+ dir, cmdmove, 0,
+ dist * 10 / wi.speed, 0.1, 0, 0, qfalse);
+ VectorCopy(move.endpos, bestorigin);
+ //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed);
+ }
+ //if not that skilled do linear prediction
+ else if (aim_skill > 0.4) {
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ //distance towards the enemy
+ dist = VectorLength(dir);
+ //direction the enemy is moving in
+ VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
+ dir[2] = 0;
+ //
+ speed = VectorNormalize(dir) / entinfo.update_time;
+ //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
+ //best spot to aim at
+ VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
+ }
+ }
+ }
+ //if the projectile does radial damage
+ if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) {
+ //if the enemy isn't standing significantly higher than the bot
+ if (entinfo.origin[2] < bs->origin[2] + 16) {
+ //try to aim at the ground in front of the enemy
+ VectorCopy(entinfo.origin, end);
+ end[2] -= 64;
+ BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT);
+ //
+ VectorCopy(bestorigin, groundtarget);
+ if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16;
+ else groundtarget[2] = trace.endpos[2] - 8;
+ //trace a line from projectile start to ground target
+ BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
+ //if hitpoint is not vertically too far from the ground target
+ if (fabs(trace.endpos[2] - groundtarget[2]) < 50) {
+ VectorSubtract(trace.endpos, groundtarget, dir);
+ //if the hitpoint is near anough the ground target
+ if (VectorLength(dir) < 60) {
+ VectorSubtract(trace.endpos, start, dir);
+ //if the hitpoint is far anough from the bot
+ if (VectorLength(dir) > 100) {
+ //check if the bot is visible from the ground target
+ trace.endpos[2] += 1;
+ BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT);
+ if (trace.fraction >= 1) {
+ //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
+ VectorCopy(groundtarget, bestorigin);
+ }
+ }
+ }
+ }
+ }
+ }
+ bestorigin[0] += 20 * crandom() * (1 - aim_accuracy);
+ bestorigin[1] += 20 * crandom() * (1 - aim_accuracy);
+ bestorigin[2] += 10 * crandom() * (1 - aim_accuracy);
+ }
+ else {
+ //
+ VectorCopy(bs->lastenemyorigin, bestorigin);
+ bestorigin[2] += 8;
+ //if the bot is skilled anough
+ if (aim_skill > 0.5) {
+ //do prediction shots around corners
+ if (wi.number == WP_DISRUPTOR ||
+ wi.number == WP_GRENADE_LAUNCHER)
+ {
+ //create the chase goal
+ goal.entitynum = bs->client;
+ goal.areanum = bs->areanum;
+ VectorCopy(bs->eye, goal.origin);
+ VectorSet(goal.mins, -8, -8, -8);
+ VectorSet(goal.maxs, 8, 8, 8);
+ //
+ if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
+ VectorSubtract(target, bs->eye, dir);
+ if (VectorLength(dir) > 80) {
+ VectorCopy(target, bestorigin);
+ bestorigin[2] -= 20;
+ }
+ }
+ aim_accuracy = 1;
+ }
+ }
+ }
+ //
+ if (enemyvisible) {
+ BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
+ VectorCopy(trace.endpos, bs->aimtarget);
+ }
+ else {
+ VectorCopy(bestorigin, bs->aimtarget);
+ }
+ //get aim direction
+ VectorSubtract(bestorigin, bs->eye, dir);
+ // kef -- fixme. i'm guessing this is listing all of the instant-hit weapons?
+ if (wi.number == WP_PHASER ||
+ wi.number == WP_NULL_HAND) {
+ //distance towards the enemy
+ dist = VectorLength(dir);
+ if (dist > 150) dist = 150;
+ f = 0.6 + dist / 150 * 0.4;
+ aim_accuracy *= f;
+ }
+ //add some random stuff to the aim direction depending on the aim accuracy
+ if (aim_accuracy < 0.8) {
+ VectorNormalize(dir);
+ for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy);
+ }
+ //set the ideal view angles
+ vectoangles(dir, bs->ideal_viewangles);
+ //take the weapon spread into account for lower skilled bots
+ bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
+ bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
+ //if the bots should be really challenging
+ //if the bot is really accurate and has the enemy in view for some time
+ if (aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1)
+ {
+ //set the view angles directly
+ if (bs->ideal_viewangles[PITCH] > 180)
+ {
+ bs->ideal_viewangles[PITCH] -= 360;
+ }
+ VectorCopy(bs->ideal_viewangles, bs->viewangles);
+ trap_EA_View(bs->client, bs->viewangles);
+ }
+}
+
+/*
+==================
+BotCheckAttack
+==================
+*/
+void BotCheckAttack(bot_state_t *bs) {
+ float points, reactiontime, fov, firethrottle;
+ bsp_trace_t bsptrace;
+ //float selfpreservation;
+ vec3_t forward, right, start, end, dir, angles;
+ weaponinfo_t wi;
+ bsp_trace_t trace;
+ aas_entityinfo_t entinfo;
+ vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
+
+ if (bs->enemy < 0) return;
+ //
+ reactiontime = 0;
+ if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
+ if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
+ //if changing weapons
+ if (bs->weaponchange_time > trap_AAS_Time() - 0.1) return;
+ //check fire throttle characteristic
+ if (bs->firethrottlewait_time > trap_AAS_Time()) return;
+ firethrottle = 1;
+ if (bs->firethrottleshoot_time < trap_AAS_Time()) {
+ if (random() > firethrottle) {
+ bs->firethrottlewait_time = trap_AAS_Time() + firethrottle;
+ bs->firethrottleshoot_time = 0;
+ }
+ else {
+ bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle;
+ bs->firethrottlewait_time = 0;
+ }
+ }
+ //
+ BotEntityInfo(bs->enemy, &entinfo);
+ VectorSubtract(entinfo.origin, bs->eye, dir);
+ //
+ if (VectorLength(dir) < 100) fov = 120;
+ else fov = 50;
+ /*
+ //if the enemy isn't visible
+ if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) {
+ //botimport.Print(PRT_MESSAGE, "enemy not visible\n");
+ return;
+ }*/
+ vectoangles(dir, angles);
+ if (!InFieldOfVision(bs->viewangles, fov, angles)) return;
+ BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
+ if (bsptrace.fraction < 1 && bsptrace.ent != bs->enemy) return;
+
+ //get the weapon info
+ trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
+ //get the start point shooting from
+ VectorCopy(bs->origin, start);
+ start[2] += bs->cur_ps.viewheight;
+ AngleVectors(bs->viewangles, forward, right, NULL);
+ start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
+ start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
+ start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
+ //end point aiming at
+ VectorMA(start, 1000, forward, end);
+ //a little back to make sure not inside a very close enemy
+ VectorMA(start, -12, forward, start);
+ BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
+ //if won't hit the enemy
+ if (trace.ent != bs->enemy) {
+ //if the entity is a client
+ if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) {
+ //if a teammate is hit
+ if (BotSameTeam(bs, trace.ent)) return;
+ }
+ //if the projectile does a radial damage
+ if (wi.proj.damagetype & DAMAGETYPE_RADIAL) {
+ if (trace.fraction * 1000 < wi.proj.radius) {
+ points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5;
+ if (points > 0) {
+// selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1);
+// if (random() < selfpreservation) return;
+ return;
+ }
+ }
+ //FIXME: check if a teammate gets radial damage
+ }
+ }
+ //if fire has to be release to activate weapon
+ if (wi.flags & WFL_FIRERELEASED) {
+ if (bs->flags & BFL_ATTACKED)
+ {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery?
+ if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire!
+ {
+ bs->weaponnum -= WP_NUM_WEAPONS;
+ trap_EA_Alt_Attack(bs->client);
+ }
+ else
+ {
+ trap_EA_Attack(bs->client);
+ }
+ }
+ }
+ else
+ { // check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery?
+ if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire!
+ {
+ bs->weaponnum -= WP_NUM_WEAPONS;
+ trap_EA_Alt_Attack(bs->client);
+ }
+ else
+ {
+ trap_EA_Attack(bs->client);
+ }
+ }
+ bs->flags ^= BFL_ATTACKED;
+}
+
+/*
+==================
+BotMapScripts
+==================
+*/
+void BotMapScripts(bot_state_t *bs) {
+ char info[1024];
+ char mapname[128];
+ int i, shootbutton;
+ float aim_accuracy;
+ aas_entityinfo_t entinfo;
+ vec3_t dir;
+
+ trap_GetServerinfo(info, sizeof(info));
+
+ strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
+ mapname[sizeof(mapname)-1] = '\0';
+
+ if (!Q_stricmp(mapname, "q3tourney6")) {
+ vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680};
+ vec3_t buttonorg = {304, 352, 920};
+ //NOTE: NEVER use the func_bobbing in q3tourney6
+ bs->tfl &= ~TFL_FUNCBOB;
+ //if the bot is below the bounding box
+ if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) {
+ if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) {
+ if (bs->origin[2] < mins[2]) {
+ return;
+ }
+ }
+ }
+ shootbutton = qfalse;
+ //if an enemy is below this bounding box then shoot the button
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+
+ if (i == bs->client) continue;
+ //
+ BotEntityInfo(i, &entinfo);
+ //
+ if (!entinfo.valid) continue;
+ //if the enemy isn't dead and the enemy isn't the bot self
+ if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
+ //
+ if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) {
+ if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) {
+ if (entinfo.origin[2] < mins[2]) {
+ //if there's a team mate below the crusher
+ if (BotSameTeam(bs, i)) {
+ shootbutton = qfalse;
+ break;
+ }
+ else {
+ shootbutton = qtrue;
+ }
+ }
+ }
+ }
+ }
+ if (shootbutton) {
+ bs->flags |= BFL_IDEALVIEWSET;
+ VectorSubtract(buttonorg, bs->eye, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ aim_accuracy = 1;
+ bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
+ bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
+ //
+ if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles))
+ {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery?
+ if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire!
+ {
+ bs->weaponnum -= WP_NUM_WEAPONS;
+ trap_EA_Alt_Attack(bs->client);
+ }
+ else
+ {
+ trap_EA_Attack(bs->client);
+ }
+ }
+ }
+ }
+}
+
+/*
+==================
+BotEntityToActivate
+==================
+*/
+//#define OBSTACLEDEBUG
+
+int BotEntityToActivate(int entitynum) {
+ int i, ent, cur_entities[10];
+ char model[MAX_INFO_STRING], tmpmodel[128];
+ char target[128], classname[128];
+ float health;
+ char targetname[10][128];
+ aas_entityinfo_t entinfo;
+
+ BotEntityInfo(entitynum, &entinfo);
+ Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex);
+ for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue;
+ if (!strcmp(model, tmpmodel)) break;
+ }
+ if (!ent) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model);
+ return 0;
+ }
+ trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
+ if (!classname) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model);
+ return 0;
+ }
+ //if it is a door
+ if (!strcmp(classname, "func_door")) {
+ if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) {
+ //if health the door must be shot to open
+ if (health) return ent;
+ }
+ }
+ //get the targetname so we can find an entity with a matching target
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) {
+#ifdef OBSTACLEDEBUG
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model);
+#endif //OBSTACLEDEBUG
+ return 0;
+ }
+ //only allows single activation chains, tree-like activation can be added back in
+ cur_entities[0] = trap_AAS_NextBSPEntity(0);
+ for (i = 0; i >= 0 && i < 10;) {
+ for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) {
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue;
+ if (!strcmp(targetname[i], target)) {
+ cur_entities[i] = trap_AAS_NextBSPEntity(ent);
+ break;
+ }
+ }
+ if (!ent) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i]);
+ i--;
+ continue;
+ }
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i]);
+ continue;
+ }
+ if (!strcmp(classname, "func_button")) {
+ //BSP button model
+ return ent;
+ }
+ else if (!strcmp(classname, "trigger_multiple")) {
+ //invisible trigger multiple box
+ return ent;
+ }
+ else {
+ i--;
+ }
+ }
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname);
+ return 0;
+}
+
+/*
+==================
+BotSetMovedir
+==================
+*/
+vec3_t VEC_UP = {0, -1, 0};
+vec3_t MOVEDIR_UP = {0, 0, 1};
+vec3_t VEC_DOWN = {0, -2, 0};
+vec3_t MOVEDIR_DOWN = {0, 0, -1};
+
+void BotSetMovedir(vec3_t angles, vec3_t movedir) {
+ if (VectorCompare(angles, VEC_UP)) {
+ VectorCopy(MOVEDIR_UP, movedir);
+ }
+ else if (VectorCompare(angles, VEC_DOWN)) {
+ VectorCopy(MOVEDIR_DOWN, movedir);
+ }
+ else {
+ AngleVectors(angles, movedir, NULL, NULL);
+ }
+}
+
+/*
+==================
+BotModelMinsMaxs
+
+this is ugly
+==================
+*/
+void BotModelMinsMaxs(int modelindex, int eType, vec3_t mins, vec3_t maxs) {
+ gentity_t *ent;
+ int i;
+
+ ent = &g_entities[0];
+ for (i = 0; i < level.num_entities; i++, ent++) {
+ if ( !ent->inuse ) {
+ continue;
+ }
+ if ( ent->s.eType != eType) {
+ continue;
+ }
+ if (ent->s.modelindex == modelindex) {
+ VectorAdd(ent->r.currentOrigin, ent->r.mins, mins);
+ VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs);
+ return;
+ }
+ }
+ VectorClear(mins);
+ VectorClear(maxs);
+}
+
+/*
+==================
+BotAIBlocked
+
+very basic handling of bots being blocked by other entities
+check what kind of entity is blocking the bot and try to activate
+it otherwise try to walk around the entity
+before the bot ends in this part of the AI it should predict which doors to open,
+which buttons to activate etc.
+==================
+*/
+void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) {
+ int movetype, ent, i, areas[10], numareas, modelindex;
+ char classname[128], model[128];
+ float lip, dist, health, angle;
+ vec3_t hordir, size, start, end, mins, maxs, sideward, angles;
+ vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs;
+ vec3_t up = {0, 0, 1}, extramins = {1, 1, 1}, extramaxs = {-1, -1, -1};
+ aas_entityinfo_t entinfo;
+ //bsp_trace_t bsptrace;
+#ifdef OBSTACLEDEBUG
+ char netname[MAX_NETNAME];
+ char buf[128];
+#endif
+
+ if (!moveresult->blocked) {
+ bs->notblocked_time = trap_AAS_Time();
+ return;
+ }
+ //
+ BotEntityInfo(moveresult->blockentity, &entinfo);
+#ifdef OBSTACLEDEBUG
+ ClientName(bs->client, netname, sizeof(netname));
+ BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex);
+#endif
+ //if blocked by a bsp model and the bot wants to activate it if possible
+ if (entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate) {
+ //find the bsp entity which should be activated in order to remove
+ //the blocking entity
+ ent = BotEntityToActivate(entinfo.number);
+ if (!ent) {
+ strcpy(classname, "");
+#ifdef OBSTACLEDEBUG
+ BotAI_Print(PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName(bs->client, netname, sizeof(netname)));
+#endif
+ }
+ else {
+ trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
+#ifdef OBSTACLEDEBUG
+ ClientName(bs->client, netname, sizeof(netname));
+ BotAI_Print(PRT_MESSAGE, "%s: I should activate %s\n", netname, classname);
+#endif
+ }
+ if (!strcmp(classname, "func_button")) {
+ //create a bot goal towards the button
+ trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
+ modelindex = atoi(model+1);
+ //if the model is not loaded
+ if (!modelindex) return;
+ VectorClear(angles);
+ BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
+ //get the lip of the button
+ trap_AAS_FloatForBSPEpairKey(ent, "lip", &lip);
+ if (!lip) lip = 4;
+ //get the move direction from the angle
+ trap_AAS_FloatForBSPEpairKey(ent, "angle", &angle);
+ VectorSet(angles, 0, angle, 0);
+ BotSetMovedir(angles, movedir);
+ //button size
+ VectorSubtract(maxs, mins, size);
+ //button origin
+ VectorAdd(mins, maxs, origin);
+ VectorScale(origin, 0.5, origin);
+ //touch distance of the button
+ dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];
+ dist *= 0.5;
+ //
+ trap_AAS_FloatForBSPEpairKey(ent, "health", &health);
+ //if the button is shootable
+ if (health) {
+ //FIXME: walk to a point where the button is visible and shoot at the button
+ //calculate the goal origin
+ VectorMA(origin, -dist, movedir, goalorigin);
+ //
+ VectorSubtract(goalorigin, bs->origin, movedir);
+ vectoangles(movedir, moveresult->ideal_viewangles);
+ moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
+ moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
+ //select the machinegun and shoot
+ trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER);
+ if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) {
+ trap_EA_Attack(bs->client);
+ }
+ return;
+ }
+ else {
+ //add bounding box size to the dist
+ trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
+ for (i = 0; i < 3; i++) {
+ if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
+ else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
+ }
+ //calculate the goal origin
+ VectorMA(origin, -dist, movedir, goalorigin);
+ //
+ VectorCopy(goalorigin, start);
+ start[2] += 24;
+ VectorCopy(start, end);
+ end[2] -= 100;
+ numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
+ //
+ for (i = 0; i < numareas; i++) {
+ if (trap_AAS_AreaReachability(areas[i])) {
+ break;
+ }
+ }
+ if (i < numareas) {
+ //
+#ifdef OBSTACLEDEBUG
+ if (bs->activatemessage_time < trap_AAS_Time()) {
+ Com_sprintf(buf, sizeof(buf), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n",
+ goalorigin[0], goalorigin[1], goalorigin[2], areas[i]);
+ trap_EA_Say(bs->client, buf);
+ bs->activatemessage_time = trap_AAS_Time() + 5;
+ }
+#endif //OBSTACLEDEBUG
+ //
+ VectorCopy(origin, bs->activategoal.origin);
+ bs->activategoal.areanum = areas[i];
+ VectorSubtract(mins, origin, bs->activategoal.mins);
+ VectorSubtract(maxs, origin, bs->activategoal.maxs);
+ //
+ for (i = 0; i < 3; i++)
+ {
+ if (movedir[i] < 0) bs->activategoal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
+ else bs->activategoal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
+ } //end for
+ //
+ bs->activategoal.entitynum = entinfo.number;
+ bs->activategoal.number = 0;
+ bs->activategoal.flags = 0;
+ bs->activate_time = trap_AAS_Time() + 10;
+ AIEnter_Seek_ActivateEntity(bs);
+ return;
+ }
+ else {
+#ifdef OBSTACLEDEBUG
+ if (!numareas) BotAI_Print(PRT_MESSAGE, "button not in an area\n");
+ else BotAI_Print(PRT_MESSAGE, "button area has no reachabilities\n");
+#endif //OBSTACLEDEBUG
+ if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
+ else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
+ }
+ }
+ }
+ else if (!strcmp(classname, "func_door")) {
+ //shoot at the shootable door
+ trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
+ modelindex = atoi(model+1);
+ //if the model is not loaded
+ if (!modelindex) return;
+ VectorClear(angles);
+ BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
+ //door origin
+ VectorAdd(mins, maxs, origin);
+ VectorScale(origin, 0.5, origin);
+ //
+ VectorSubtract(origin, bs->eye, movedir);
+ vectoangles(movedir, moveresult->ideal_viewangles);
+ moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
+ moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
+ //select the machinegun and shoot
+ trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER);
+ if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) {
+ trap_EA_Attack(bs->client);
+ }
+ return;
+ }
+ }
+ //just some basic dynamic obstacle avoidance code
+ hordir[0] = moveresult->movedir[0];
+ hordir[1] = moveresult->movedir[1];
+ hordir[2] = 0;
+ //if no direction just take a random direction
+ if (VectorNormalize(hordir) < 0.1) {
+ VectorSet(angles, 0, 360 * random(), 0);
+ AngleVectors(angles, hordir, NULL, NULL);
+ }
+ //
+ //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP;
+ //else
+ movetype = MOVE_WALK;
+ //if there's an obstacle at the bot's feet and head then
+ //the bot might be able to crouch through
+ VectorCopy(bs->origin, start);
+ start[2] += 18;
+ VectorMA(start, 5, hordir, end);
+ VectorSet(mins, -16, -16, -24);
+ VectorSet(maxs, 16, 16, 4);
+ //
+ //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID);
+ //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH;
+ //get the sideward vector
+ CrossProduct(hordir, up, sideward);
+ //
+ if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward);
+ //try to crouch straight forward?
+ if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) {
+ //perform the movement
+ if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) {
+ //flip the avoid direction flag
+ bs->flags ^= BFL_AVOIDRIGHT;
+ //flip the direction
+ VectorNegate(sideward, sideward);
+ //move in the other direction
+ trap_BotMoveInDirection(bs->ms, sideward, 400, movetype);
+ }
+ }
+ //
+ if (bs->notblocked_time < trap_AAS_Time() - 0.4) {
+ //just reset goals and hope the bot will go into another direction
+ //is this still needed??
+ if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
+ else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
+ }
+}
+
+/*
+==================
+BotCheckConsoleMessages
+==================
+*/
+void BotCheckConsoleMessages(bot_state_t *bs) {
+ char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr;
+ float chat_reply;
+ int context, handle;
+ bot_consolemessage_t m;
+ bot_match_t match;
+
+ //the name of this bot
+ ClientName(bs->client, botname, sizeof(botname));
+ //
+ while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) {
+ //if the chat state is flooded with messages the bot will read them quickly
+ if (trap_BotNumConsoleMessages(bs->cs) < 10) {
+ //if it is a chat message the bot needs some time to read it
+ if (m.type == CMS_CHAT && m.time > trap_AAS_Time() - (1 + random())) break;
+ }
+ //
+ ptr = m.message;
+ //if it is a chat message then don't unify white spaces and don't
+ //replace synonyms in the netname
+ if (m.type == CMS_CHAT) {
+ //
+ if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
+ ptr = m.message + match.variables[MESSAGE].offset;
+ }
+ }
+ //unify the white spaces in the message
+ trap_UnifyWhiteSpaces(ptr);
+ //replace synonyms in the right context
+ context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES;
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) context |= CONTEXT_CTFREDTEAM;
+ else context |= CONTEXT_CTFBLUETEAM;
+ trap_BotReplaceSynonyms(ptr, context);
+ //if there's no match
+ if (!BotMatchMessage(bs, m.message)) {
+ //if it is a chat message
+ if (m.type == CMS_CHAT && !bot_nochat.integer) {
+ //
+ if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ continue;
+ }
+ //don't use eliza chats with team messages
+ if (match.subtype & ST_TEAM) {
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ continue;
+ }
+ //
+ trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname));
+ trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message));
+ //if this is a message from the bot self
+ if (!Q_stricmp(netname, botname)) {
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ continue;
+ }
+ //unify the message
+ trap_UnifyWhiteSpaces(message);
+ //
+ trap_Cvar_Update(&bot_testrchat);
+ if (bot_testrchat.integer) {
+ //
+ trap_BotLibVarSet("bot_testrchat", "1");
+ //if bot replies with a chat message
+ if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ botname, netname)) {
+ BotAI_Print(PRT_MESSAGE, "------------------------\n");
+ }
+ else {
+ BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n");
+ }
+ }
+ //if at a valid chat position and not chatting already
+ else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs)) {
+ chat_reply = 0;
+ if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) {
+ //if bot replies with a chat message
+ if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ botname, netname)) {
+ //remove the console message
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ //EA_Say(bs->client, bs->cs.chatmessage);
+ break;
+ }
+ }
+ }
+ }
+ }
+ //remove the console message
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ }
+}
+
+/*
+==================
+BotCheckEvents
+==================
+*/
+void BotCheckEvents(bot_state_t *bs, entityState_t *state) {
+ int event;
+ char buf[128];
+ //
+ //NOTE: this sucks, we're accessing the gentity_t directly
+ //but there's no other fast way to do it right now
+ if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) {
+ return;
+ }
+ bs->entityeventTime[state->number] = g_entities[state->number].eventTime;
+ //if it's an event only entity
+ if (state->eType > ET_EVENTS) {
+ event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS;
+ }
+ else {
+ event = state->event & ~EV_EVENT_BITS;
+ }
+ //
+ switch(event) {
+ //client obituary event
+ case EV_OBITUARY:
+ {
+ int target, attacker, mod;
+
+ target = state->otherEntityNum;
+ attacker = state->otherEntityNum2;
+ mod = state->eventParm;
+ //
+ if (target == bs->client) {
+ bs->botdeathtype = mod;
+ bs->lastkilledby = attacker;
+ //
+ if (target == attacker) bs->botsuicide = qtrue;
+ else bs->botsuicide = qfalse;
+ //
+ bs->num_deaths++;
+ }
+ //else if this client was killed by the bot
+ else if (attacker == bs->client) {
+ bs->enemydeathtype = mod;
+ bs->lastkilledplayer = target;
+ bs->killedenemy_time = trap_AAS_Time();
+ //
+ bs->num_kills++;
+ }
+ else if (attacker == bs->enemy && target == attacker) {
+ bs->enemysuicide = qtrue;
+ }
+ break;
+ }
+
+ case EV_GLOBAL_SOUND:
+ {
+ if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
+ BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
+ break;
+ }
+ else {
+ trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
+ if (!strcmp(buf, "sound/items/poweruprespawn.wav")) {
+ //powerup respawned... go get it
+ BotGoForPowerups(bs);
+ }
+ }
+ break;
+ }
+
+ case EV_TEAM_SOUND:
+ {
+ if (state->eventParm < 0 || state->eventParm > MAX_TEAM_SOUNDS) {
+ BotAI_Print(PRT_ERROR, "EV_TEAM_SOUND: eventParm (%d) out of range\n", state->eventParm);
+ break;
+ }
+
+ if (state->eventParm == RETURN_FLAG_SOUND)
+ {
+ if (state->otherEntityNum == TEAM_RED)
+ {
+ //red flag is returned
+ bs->redflagstatus = 0;
+ bs->flagstatuschanged = qtrue;
+ }
+ else
+ {
+ //blue flag is returned
+ bs->blueflagstatus = 0;
+ bs->flagstatuschanged = qtrue;
+ }
+ }
+ break;
+ }
+
+
+
+ case EV_PLAYER_TELEPORT_IN:
+ {
+ VectorCopy(state->origin, lastteleport_origin);
+ lastteleport_time = trap_AAS_Time();
+ break;
+ }
+ case EV_GENERAL_SOUND:
+ {
+ //if this sound is played on the bot
+ if (state->number == bs->client) {
+ if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
+ BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
+ break;
+ }
+ //check out the sound
+ trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
+ //if falling into a death pit
+ if (!strcmp(buf, "*falling1.wav")) {
+ //if the bot has a personal teleporter
+ if (bs->inventory[INVENTORY_TRANSPORTER] > 0) {
+ //use the holdable item
+ trap_EA_Use(bs->client);
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+/*
+==================
+BotCheckSnapshot
+==================
+*/
+void BotCheckSnapshot(bot_state_t *bs) {
+ int ent;
+ entityState_t state;
+
+ //
+ ent = 0;
+ while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) {
+ //check the entity state for events
+ BotCheckEvents(bs, &state);
+ }
+ //check the player state for events
+ BotAI_GetEntityState(bs->client, &state);
+ //copy the player state events to the entity state
+ state.event = bs->cur_ps.externalEvent;
+ state.eventParm = bs->cur_ps.externalEventParm;
+ //
+ BotCheckEvents(bs, &state);
+}
+
+/*
+==================
+BotCheckAir
+==================
+*/
+void BotCheckAir(bot_state_t *bs) {
+ if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) {
+ if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
+ return;
+ }
+ }
+ bs->lastair_time = trap_AAS_Time();
+}
+
+/*
+==================
+BotDeathmatchAI
+==================
+*/
+void BotDeathmatchAI(bot_state_t *bs, float thinktime) {
+ char gender[144], name[144], buf[144];
+ char userinfo[MAX_INFO_STRING];
+ int i;
+
+ //if the bot has just been setup
+ if (bs->setupcount > 0) {
+ bs->setupcount--;
+ if (bs->setupcount > 0) return;
+ //get the gender characteristic
+ trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender));
+ //set the bot gender
+ trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
+ Info_SetValueForKey(userinfo, "sex", gender);
+ trap_SetUserinfo(bs->client, userinfo);
+ //set the team
+ if ( g_gametype.integer != GT_TOURNAMENT ) {
+ Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team);
+ trap_EA_Command(bs->client, buf);
+ }
+ if ( g_pModSpecialties.integer ) {
+ Com_sprintf(buf, sizeof(buf), "class %s", bs->settings.pclass);
+ trap_EA_Command(bs->client, buf);
+ }
+ //set the chat gender
+ if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
+ else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
+ else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
+ //set the chat name
+ ClientName(bs->client, name, sizeof(name));
+ trap_BotSetChatName(bs->cs, name);
+ //
+ bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
+ bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
+ //
+ bs->setupcount = 0;
+ }
+ //no ideal view set
+ bs->flags &= ~BFL_IDEALVIEWSET;
+ //set the teleport time
+ BotSetTeleportTime(bs);
+ //update some inventory values
+ BotUpdateInventory(bs);
+ //check the console messages
+ BotCheckConsoleMessages(bs);
+ //check out the snapshot
+ BotCheckSnapshot(bs);
+ //check for air
+ BotCheckAir(bs);
+ //if not in the intermission and not in observer mode
+ if (!BotIntermission(bs) && !BotIsObserver(bs)) {
+ //do team AI
+ BotTeamAI(bs);
+ }
+ //if the bot has no ai node
+ if (!bs->ainode) {
+ AIEnter_Seek_LTG(bs);
+ }
+ //if the bot entered the game less than 8 seconds ago
+ if (!bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8) {
+ if (BotChat_EnterGame(bs)) {
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ }
+ bs->entergamechat = qtrue;
+ }
+ //reset the node switches from the previous frame
+ BotResetNodeSwitches();
+ //execute AI nodes
+ for (i = 0; i < MAX_NODESWITCHES; i++) {
+ if (bs->ainode(bs)) break;
+ }
+ //if the bot removed itself :)
+ if (!bs->inuse) return;
+ //if the bot executed too many AI nodes
+ if (i >= MAX_NODESWITCHES) {
+ trap_BotDumpGoalStack(bs->gs);
+ trap_BotDumpAvoidGoals(bs->gs);
+ BotDumpNodeSwitches(bs);
+ ClientName(bs->client, name, sizeof(name));
+ BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES);
+ }
+ //
+ bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
+ bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
+}
+
+/*
+==================
+BotSetupDeathmatchAI
+==================
+*/
+void BotSetupDeathmatchAI(void) {
+ int ent, modelnum;
+ char model[128];
+
+ gametype = trap_Cvar_VariableIntegerValue("g_gametype");
+ maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0);
+ trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0);
+ trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0);
+ trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0);
+ trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0);
+ trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0);
+ //
+ if (gametype == GT_CTF) {
+ if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
+ BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
+ if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
+ BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
+ }
+
+ max_bspmodelindex = 0;
+ for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue;
+ if (model[0] == '*') {
+ modelnum = atoi(model+1);
+ if (modelnum > max_bspmodelindex)
+ max_bspmodelindex = modelnum;
+ }
+ }
+ //initialize the waypoint heap
+ BotInitWaypoints();
+}
+
+/*
+==================
+BotShutdownDeathmatchAI
+==================
+*/
+void BotShutdownDeathmatchAI(void) {
+}
+
diff --git a/game/ai_dmq3.c.orig b/game/ai_dmq3.c.orig
new file mode 100644
index 0000000..3bc9787
--- /dev/null
+++ b/game/ai_dmq3.c.orig
@@ -0,0 +1,2857 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+/*****************************************************************************
+ * name: ai_dmq3.c
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_dmq3.c $
+ * $Author: Mgummelt $
+ * $Revision: 33 $
+ * $Modtime: 4/04/01 5:01p $
+ * $Date: 4/04/01 5:17p $
+ *
+ *****************************************************************************/
+
+
+#include "g_local.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_ea.h"
+#include "be_ai_char.h"
+#include "be_ai_chat.h"
+#include "be_ai_gen.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+#include "be_ai_weap.h"
+//
+#include "ai_main.h"
+#include "ai_dmq3.h"
+#include "ai_chat.h"
+#include "ai_cmd.h"
+#include "ai_dmnet.h"
+#include "ai_team.h"
+//
+#include "chars.h" //characteristics
+#include "inv.h" //indexes into the inventory
+#include "syn.h" //synonyms
+#include "match.h" //string matching types and vars
+
+#define IDEAL_ATTACKDIST 140
+#define WEAPONINDEX_PHASER 2
+
+#define MAX_WAYPOINTS 128
+//
+bot_waypoint_t botai_waypoints[MAX_WAYPOINTS];
+bot_waypoint_t *botai_freewaypoints;
+
+//NOTE: not using a cvars which can be updated because the game should be reloaded anyway
+int gametype; //game type
+int maxclients; //maximum number of clients
+
+vmCvar_t bot_grapple;
+vmCvar_t bot_rocketjump;
+vmCvar_t bot_fastchat;
+vmCvar_t bot_nochat;
+vmCvar_t bot_testrchat;
+vmCvar_t bot_challenge;
+
+vec3_t lastteleport_origin; //last teleport event origin
+float lastteleport_time; //last teleport event time
+int max_bspmodelindex; //maximum BSP model index
+
+//CTF flag goals
+bot_goal_t ctf_redflag;
+bot_goal_t ctf_blueflag;
+
+#ifdef CTF
+/*
+==================
+BotCTFCarryingFlag
+==================
+*/
+int BotCTFCarryingFlag(bot_state_t *bs) {
+ if (gametype != GT_CTF) return CTF_FLAG_NONE;
+
+ if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED;
+ else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE;
+ return CTF_FLAG_NONE;
+}
+
+/*
+==================
+BotCTFTeam
+==================
+*/
+int BotCTFTeam(bot_state_t *bs) {
+ char info[1024];
+
+ if (gametype != GT_CTF) return CTF_TEAM_NONE;
+ if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
+ //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n");
+ return qfalse;
+ }
+ trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info));
+ //
+ if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return CTF_TEAM_RED;
+ else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return CTF_TEAM_BLUE;
+ return CTF_TEAM_NONE;
+}
+
+/*
+==================
+BotCTFRetreatGoals
+==================
+*/
+void BotCTFRetreatGoals(bot_state_t *bs) {
+ //when carrying a flag in ctf the bot should rush to the base
+ if (BotCTFCarryingFlag(bs)) {
+ //if not already rushing to the base
+ if (bs->ltgtype != LTG_RUSHBASE) {
+ bs->ltgtype = LTG_RUSHBASE;
+ bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
+ bs->rushbaseaway_time = 0;
+ }
+ }
+}
+
+/*
+==================
+EntityIsDead
+==================
+*/
+qboolean EntityIsDead(aas_entityinfo_t *entinfo) {
+ playerState_t ps;
+
+ if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) {
+ //retrieve the current client state
+ BotAI_GetClientState( entinfo->number, &ps );
+ if (ps.pm_type != PM_NORMAL) return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityIsInvisible
+==================
+*/
+qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) {
+ if (entinfo->powerups & (1 << PW_GHOST))
+ { // 50% chance of being visible?
+ if (((unsigned int)(level.time)/1024)&0x01) // Every second or so, the bot will see the player, so he doesn't jitter.
+ {
+ return qtrue;
+ }
+ else
+ {
+ return qfalse;
+ }
+ }
+ else if (entinfo->powerups & (1 << PW_INVIS))
+ {
+ return qtrue;
+ }
+ else if ( entinfo->flags & EF_NODRAW )
+ {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityCarriesFlag
+==================
+*/
+qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) {
+ if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) return qtrue;
+ if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+EntityIsShooting
+==================
+*/
+qboolean EntityIsShooting(aas_entityinfo_t *entinfo) {
+ if (entinfo->flags & EF_FIRING) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityIsChatting
+==================
+*/
+qboolean EntityIsChatting(aas_entityinfo_t *entinfo) {
+ if (entinfo->flags & EF_TALK) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+EntityHasQuad
+==================
+*/
+qboolean EntityHasQuad(aas_entityinfo_t *entinfo) {
+ if (entinfo->powerups & (1 << PW_QUAD)) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotCTFSeekGoals
+==================
+*/
+void BotCTFSeekGoals(bot_state_t *bs) {
+ float rnd;
+ int flagstatus, c;
+
+ //when carrying a flag in ctf the bot should rush to the base
+ if (BotCTFCarryingFlag(bs)) {
+ //if not already rushing to the base
+ if (bs->ltgtype != LTG_RUSHBASE) {
+ bs->ltgtype = LTG_RUSHBASE;
+ bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
+ bs->rushbaseaway_time = 0;
+ }
+ else if (bs->rushbaseaway_time > trap_AAS_Time()) {
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus;
+ else flagstatus = bs->blueflagstatus;
+ //if the flag is back
+ if (flagstatus == 0) {
+ bs->rushbaseaway_time = 0;
+ }
+ }
+ return;
+ }
+ //
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus;
+ else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;
+ //if the enemy flag is not at it's base
+ if (flagstatus == 1) {
+ //if Not defending the base
+ if (!(bs->ltgtype == LTG_DEFENDKEYAREA &&
+ (bs->teamgoal.number == ctf_redflag.number ||
+ bs->teamgoal.number == ctf_blueflag.number))) {
+ //if not already accompanying someone
+ if (bs->ltgtype != LTG_TEAMACCOMPANY) {
+ //if there is avisible team mate flag carrier
+ c = BotTeamFlagCarrierVisible(bs);
+ if (c >= 0) {
+ //follow the flag carrier
+ //the team mate
+ bs->teammate = c;
+ //last time the team mate was visible
+ bs->teammatevisible_time = trap_AAS_Time();
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //get the team goal time
+ bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME;
+ bs->ltgtype = LTG_TEAMACCOMPANY;
+ bs->formation_dist = 3.5 * 32; //3.5 meter
+ bs->arrive_time = 0;
+ return;
+ }
+ }
+ }
+ }
+ //if the base flag is stolen
+ else if (flagstatus == 2) {
+ //if not already going for the enemy flag
+ if (bs->ltgtype != LTG_GETFLAG) {
+ //if there's no bot team leader
+ if (!BotTeamLeader(bs)) {
+ //go for the enemy flag
+ bs->ltgtype = LTG_GETFLAG;
+ //no team message
+ bs->teammessage_time = 1;
+ //set the time the bot will stop getting the flag
+ bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
+ return;
+ }
+ }
+ }
+ //if both flags not at their bases
+ else if (flagstatus == 3) {
+ //
+ if (bs->ltgtype != LTG_GETFLAG &&
+ bs->ltgtype != LTG_TEAMACCOMPANY) {
+ //if there is avisible team mate flag carrier
+ c = BotTeamFlagCarrierVisible(bs);
+ if (c >= 0) {
+ //follow the flag carrier
+ return;
+ }
+ else {
+ //otherwise attack the enemy base
+ }
+ return;
+ }
+ }
+ //if the bot is roaming
+ if (bs->ctfroam_time > trap_AAS_Time()) return;
+ //if already a CTF or team goal
+ if (bs->ltgtype == LTG_TEAMHELP ||
+ bs->ltgtype == LTG_TEAMACCOMPANY ||
+ bs->ltgtype == LTG_DEFENDKEYAREA ||
+ bs->ltgtype == LTG_GETFLAG ||
+ bs->ltgtype == LTG_RUSHBASE ||
+ bs->ltgtype == LTG_RETURNFLAG ||
+ bs->ltgtype == LTG_CAMPORDER ||
+ bs->ltgtype == LTG_PATROL) {
+ return;
+ }
+ //if the bot has anough aggression to decide what to do
+ if (BotAggression(bs) < 50) return;
+ //set the time to send a message to the team mates
+ bs->teammessage_time = trap_AAS_Time() + 2 * random();
+ //get the flag or defend the base
+ rnd = random();
+ if (rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum) {
+ bs->ltgtype = LTG_GETFLAG;
+ //set the time the bot will stop getting the flag
+ bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
+ }
+ else if (rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum) {
+ //
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
+ else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
+ //set the ltg type
+ bs->ltgtype = LTG_DEFENDKEYAREA;
+ //set the time the bot stops defending the base
+ bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME;
+ bs->defendaway_time = 0;
+ }
+ else {
+ bs->ltgtype = 0;
+ //set the time the bot will stop roaming
+ bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME;
+ }
+#ifdef DEBUG
+ BotPrintTeamGoal(bs);
+#endif //DEBUG
+}
+
+#endif //CTF
+
+/*
+==================
+BotPointAreaNum
+==================
+*/
+int BotPointAreaNum(vec3_t origin) {
+ int areanum, numareas, areas[10];
+ vec3_t end;
+
+ areanum = trap_AAS_PointAreaNum(origin);
+ if (areanum) return areanum;
+ VectorCopy(origin, end);
+ end[2] += 10;
+ numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10);
+ if (numareas > 0) return areas[0];
+ return 0;
+}
+
+/*
+==================
+ClientName
+==================
+*/
+char *ClientName(int client, char *name, int size) {
+ char buf[MAX_INFO_STRING];
+
+ if (client < 0 || client >= MAX_CLIENTS) {
+ BotAI_Print(PRT_ERROR, "ClientName: client out of range\n");
+ return "[client out of range]";
+ }
+ trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
+ strncpy(name, Info_ValueForKey(buf, "n"), size-1);
+ name[size-1] = '\0';
+ Q_CleanStr( name );
+ return name;
+}
+
+/*
+==================
+ClientSkin
+==================
+*/
+char *ClientSkin(int client, char *skin, int size) {
+ char buf[MAX_INFO_STRING];
+
+ if (client < 0 || client >= MAX_CLIENTS) {
+ BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n");
+ return "[client out of range]";
+ }
+ trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
+ strncpy(skin, Info_ValueForKey(buf, "model"), size-1);
+ skin[size-1] = '\0';
+ return skin;
+}
+
+/*
+==================
+ClientFromName
+==================
+*/
+int ClientFromName(char *name) {
+ int i;
+ char buf[MAX_INFO_STRING];
+ static int maxclients;
+
+ if (!maxclients)
+ maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ Q_CleanStr( buf );
+ if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
+ }
+ return -1;
+}
+
+/*
+==================
+stristr
+==================
+*/
+char *stristr(char *str, char *charset) {
+ int i;
+
+ while(*str) {
+ for (i = 0; charset[i] && str[i]; i++) {
+ if (toupper(charset[i]) != toupper(str[i])) break;
+ }
+ if (!charset[i]) return str;
+ str++;
+ }
+ return NULL;
+}
+
+/*
+==================
+EasyClientName
+==================
+*/
+char *EasyClientName(int client, char *buf, int size) {
+ int i;
+ char *str1, *str2, *ptr, c;
+ char name[128];
+
+ strcpy(name, ClientName(client, name, sizeof(name)));
+ for (i = 0; name[i]; i++) name[i] &= 127;
+ //remove all spaces
+ for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) {
+ memmove(ptr, ptr+1, strlen(ptr+1)+1);
+ }
+ //check for [x] and ]x[ clan names
+ str1 = strstr(name, "[");
+ str2 = strstr(name, "]");
+ if (str1 && str2) {
+ if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1);
+ else memmove(str2, str1+1, strlen(str1+1)+1);
+ }
+ //remove Mr prefix
+ if ((name[0] == 'm' || name[0] == 'M') &&
+ (name[1] == 'r' || name[1] == 'R')) {
+ memmove(name, name+2, strlen(name+2)+1);
+ }
+ //only allow lower case alphabet characters
+ ptr = name;
+ while(*ptr) {
+ c = *ptr;
+ if ((c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || c == '_') {
+ ptr++;
+ }
+ else if (c >= 'A' && c <= 'Z') {
+ *ptr += 'a' - 'A';
+ ptr++;
+ }
+ else {
+ memmove(ptr, ptr+1, strlen(ptr + 1)+1);
+ }
+ }
+ strncpy(buf, name, size-1);
+ buf[size-1] = '\0';
+ return buf;
+}
+
+qboolean BotUseMeleeWeapon(bot_state_t *bs) {
+ if ( bs->inventory[ENEMY_HORIZONTAL_DIST] < 64 )
+ {
+ if ( bs->cur_ps.persistant[PERS_CLASS] == PC_BORG || bs->cur_ps.persistant[PERS_CLASS] == PC_MEDIC )
+ {
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+/*
+==================
+BotChooseWeapon
+
+ MCG - FIXME: This should really take into account:
+ Projectile vs. instant?
+ gravity on projectile?
+ Range to enemy vs range of weapon?
+ Some randomness on the weights?
+
+==================
+*/
+void BotChooseWeapon(bot_state_t *bs) {
+ int newweaponnum;
+
+ if (bs->cur_ps.weaponstate == WEAPON_RAISING || bs->cur_ps.weaponstate == WEAPON_DROPPING)
+ {
+ trap_EA_SelectWeapon(bs->client, bs->weaponnum);
+ }
+ else
+ {
+ newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory, BotUseMeleeWeapon(bs));
+ if (bs->weaponnum != newweaponnum)
+ {
+ bs->weaponchange_time = trap_AAS_Time();
+ }
+ bs->weaponnum = newweaponnum;
+ //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
+ trap_EA_SelectWeapon(bs->client, bs->weaponnum);
+ }
+}
+
+/*
+==================
+BotSetupForMovement
+==================
+*/
+void BotSetupForMovement(bot_state_t *bs) {
+ bot_initmove_t initmove;
+
+ memset(&initmove, 0, sizeof(bot_initmove_t));
+ VectorCopy(bs->cur_ps.origin, initmove.origin);
+ VectorCopy(bs->cur_ps.velocity, initmove.velocity);
+ VectorCopy(bs->cur_ps.origin, initmove.viewoffset);
+ initmove.viewoffset[2] += bs->cur_ps.viewheight;
+ initmove.entitynum = bs->entitynum;
+ initmove.client = bs->client;
+ initmove.thinktime = bs->thinktime;
+ //set the onground flag
+ if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND;
+ //set the teleported flag
+ if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) {
+ initmove.or_moveflags |= MFL_TELEPORTED;
+ }
+ //set the waterjump flag
+ if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) {
+ initmove.or_moveflags |= MFL_WATERJUMP;
+ }
+ //set presence type
+ if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH;
+ else initmove.presencetype = PRESENCE_NORMAL;
+ //
+ if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK;
+ //
+ VectorCopy(bs->viewangles, initmove.viewangles);
+ //
+ trap_BotInitMoveState(bs->ms, &initmove);
+}
+
+/*
+==================
+BotUpdateInventory
+==================
+*/
+void BotUpdateInventory(bot_state_t *bs) {
+ //armor
+ bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR];
+
+ //weapons
+ bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0;
+ bs->inventory[INVENTORY_STASIS] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DISRUPTOR)) != 0;
+ bs->inventory[INVENTORY_PHASER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PHASER)) != 0;
+ bs->inventory[INVENTORY_DREADNOUGHT] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DERMAL_REGEN)) != 0;
+ bs->inventory[INVENTORY_IMOD] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NULL_HAND)) != 0;
+ bs->inventory[INVENTORY_COMPRESSION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COMPRESSION_RIFLE)) != 0;
+ bs->inventory[INVENTORY_TETRION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_TR116)) != 0;
+ bs->inventory[INVENTORY_SCAVENGER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COFFEE)) != 0;
+ bs->inventory[INVENTORY_QUANTUM] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_QUANTUM_BURST)) != 0;
+
+ //ammo
+ bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER];
+ bs->inventory[INVENTORY_STASISAMMO] = bs->cur_ps.ammo[WP_DISRUPTOR];
+ bs->inventory[INVENTORY_PHASERAMMO] = bs->cur_ps.ammo[WP_PHASER];
+ bs->inventory[INVENTORY_DREADNOUGHTAMMO] = bs->cur_ps.ammo[WP_DERMAL_REGEN];
+ bs->inventory[INVENTORY_IMODAMMO] = bs->cur_ps.ammo[WP_NULL_HAND];
+ bs->inventory[INVENTORY_COMPRESSIONAMMO] = bs->cur_ps.ammo[WP_COMPRESSION_RIFLE];
+ bs->inventory[INVENTORY_TETRIONAMMO] = bs->cur_ps.ammo[WP_TR116];
+ bs->inventory[INVENTORY_SCAVENGERAMMO] = bs->cur_ps.ammo[WP_COFFEE];
+ bs->inventory[INVENTORY_QUANTUMAMMO] = bs->cur_ps.ammo[WP_QUANTUM_BURST];
+
+ //powerups
+ bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH];
+ bs->inventory[INVENTORY_TRANSPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER;
+ bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT;
+ bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0;
+ bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BOLTON] != 0;
+ bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0;
+ bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0;
+ bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0;
+ bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0;
+ bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
+ bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0;
+ bs->inventory[INVENTORY_SEEKER] = bs->cur_ps.powerups[PW_SEEKER] != 0;
+ bs->inventory[INVENTORY_SHIELD] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_SHIELD;
+ bs->inventory[INVENTORY_DETPACK] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_DETPACK;
+ //
+}
+
+/*
+==================
+BotUpdateBattleInventory
+==================
+*/
+void BotUpdateBattleInventory(bot_state_t *bs, int enemy) {
+ vec3_t dir;
+ aas_entityinfo_t entinfo;
+
+ BotEntityInfo(enemy, &entinfo);
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ bs->inventory[ENEMY_HEIGHT] = (int) dir[2];
+ dir[2] = 0;
+ bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir);
+ //FIXME: add num visible enemies and num visible team mates to the inventory
+}
+
+/*
+=========================
+BotShouldDetonateDetPack
+=========================
+*/
+#define DETPACK_RADIUS 500
+
+qboolean BotShouldDetonateDetPack(bot_state_t *bs)
+{
+ int botNum = 0, detWeight = 0;
+ vec3_t packOrg, dir;
+ float dist;
+ aas_entityinfo_t botinfo;
+
+ // find the location of the DetPack
+ gentity_t *detpack = NULL;
+ char *classname = BG_FindClassnameForHoldable(HI_DETPACK);
+
+ if (!classname)
+ {
+ return qfalse;
+ }
+
+ while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL)
+ {
+ VectorCopy(detpack->r.currentOrigin, packOrg);
+ }
+
+ // determine who would be killed in the blast radius
+ for (botNum = 0; botNum < MAX_CLIENTS; botNum++)
+ {
+ BotEntityInfo(botNum, &botinfo);
+ if (!botinfo.valid) continue;
+
+ //calculate the distance towards the enemy
+ VectorSubtract(botinfo.origin, packOrg, dir);
+ dist = VectorLength(dir);
+
+ if (dist < DETPACK_RADIUS) // bot would get caught in blast radius
+ {
+ if (BotSameTeam(bs, botNum)) // friendly casualty potential
+ {
+ if (botNum == bs->client) // suicide... bad
+ {
+ detWeight--;
+ }
+ if (EntityCarriesFlag(&botinfo)) // it's my teammate, and he's got the flag!
+ {
+ detWeight -= 11;
+ continue;
+ }
+ detWeight--;
+ }
+ else
+ {
+ if(EntityCarriesFlag(&botinfo)) // mwahaha
+ {
+ detWeight += 14;
+ }
+ detWeight++;
+ }
+ }
+ }
+
+// Com_Printf("detWeight %d\n", detWeight);
+
+ if (detWeight > 0)
+ {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+
+/*
+==================
+BotBattleUseItems
+==================
+*/
+void BotBattleUseItems(bot_state_t *bs) {
+
+
+ if (bs->inventory[INVENTORY_DETPACK] > 0)
+ {
+ // this needs to be in two stages: placement and detonation
+ if (bs->ltgtype == LTG_DEFENDKEYAREA)
+ {
+ if (bs->inventory[INVENTORY_DETPACK_PLACED] == 0) // not placed yet
+ {
+ bs->inventory[INVENTORY_DETPACK_PLACED] = 1;
+ trap_EA_Use(bs->client); // place it
+ return;
+ }
+ }
+
+ if (bs->inventory[INVENTORY_DETPACK_PLACED] == 1) // placed
+ {
+ if (BotShouldDetonateDetPack(bs)) // logic
+ {
+ bs->inventory[INVENTORY_DETPACK_PLACED] = 0;
+ trap_EA_Use(bs->client); // BOOM
+ return;
+ }
+ return;
+ }
+ return;
+ }
+
+ if (bs->inventory[INVENTORY_SHIELD] > 0)
+ {
+ if (BotWantsToRetreat(bs) && (bs->inventory[INVENTORY_HEALTH] < 50))
+ {
+ trap_EA_Use(bs->client);
+ }
+ return;
+ }
+
+ if (bs->inventory[INVENTORY_TRANSPORTER] > 0)
+ {
+ if (!BotCTFCarryingFlag(bs) && (bs->inventory[INVENTORY_HEALTH] < 50))
+ {
+ trap_EA_Use(bs->client);
+ return;
+ }
+ }
+
+ if (bs->inventory[INVENTORY_MEDKIT] > 0)
+ {
+ if (bs->inventory[INVENTORY_HEALTH] < 30)
+ {
+ trap_EA_Use(bs->client);
+ }
+ return;
+ }
+}
+
+/*
+==================
+BotSetTeleportTime
+==================
+*/
+void BotSetTeleportTime(bot_state_t *bs) {
+ if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) {
+ bs->teleport_time = trap_AAS_Time();
+ }
+ bs->last_eFlags = bs->cur_ps.eFlags;
+}
+
+/*
+==================
+BotIsDead
+==================
+*/
+qboolean BotIsDead(bot_state_t *bs) {
+ return (bs->cur_ps.pm_type == PM_DEAD);
+}
+
+/*
+==================
+BotIsObserver
+==================
+*/
+qboolean BotIsObserver(bot_state_t *bs) {
+ char buf[MAX_INFO_STRING];
+ if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue;
+ trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf));
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+BotIntermission
+==================
+*/
+qboolean BotIntermission(bot_state_t *bs) {
+ //NOTE: we shouldn't be looking at the game code...
+ if (level.intermissiontime) return qtrue;
+ return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION);
+}
+
+/*
+==================
+BotInLavaOrSlime
+==================
+*/
+qboolean BotInLavaOrSlime(bot_state_t *bs) {
+ vec3_t feet;
+
+ VectorCopy(bs->origin, feet);
+ feet[2] -= 23;
+ return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME));
+}
+
+/*
+==================
+BotCreateWayPoint
+==================
+*/
+bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) {
+ bot_waypoint_t *wp;
+ vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8};
+
+ wp = botai_freewaypoints;
+ if ( !wp ) {
+ BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" );
+ return NULL;
+ }
+ botai_freewaypoints = botai_freewaypoints->next;
+
+ Q_strncpyz( wp->name, name, sizeof(wp->name) );
+ VectorCopy(origin, wp->goal.origin);
+ VectorCopy(waypointmins, wp->goal.mins);
+ VectorCopy(waypointmaxs, wp->goal.maxs);
+ wp->goal.areanum = areanum;
+ wp->next = NULL;
+ wp->prev = NULL;
+ return wp;
+}
+
+/*
+==================
+BotFindWayPoint
+==================
+*/
+bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) {
+ bot_waypoint_t *wp;
+
+ for (wp = waypoints; wp; wp = wp->next) {
+ if (!Q_stricmp(wp->name, name)) return wp;
+ }
+ return NULL;
+}
+
+/*
+==================
+BotFreeWaypoints
+==================
+*/
+void BotFreeWaypoints(bot_waypoint_t *wp) {
+ bot_waypoint_t *nextwp;
+
+ for (; wp; wp = nextwp) {
+ nextwp = wp->next;
+ wp->next = botai_freewaypoints;
+ botai_freewaypoints = wp;
+ }
+}
+
+/*
+==================
+BotInitWaypoints
+==================
+*/
+void BotInitWaypoints(void) {
+ int i;
+
+ botai_freewaypoints = NULL;
+ for (i = 0; i < MAX_WAYPOINTS; i++) {
+ botai_waypoints[i].next = botai_freewaypoints;
+ botai_freewaypoints = &botai_waypoints[i];
+ }
+}
+
+/*
+==================
+TeamPlayIsOn
+==================
+*/
+int TeamPlayIsOn(void) {
+ return ( gametype == GT_TEAM || gametype == GT_CTF );
+}
+
+/*
+==================
+BotAggression
+==================
+*/
+float BotAggression(bot_state_t *bs) {
+ //if the bot has quad
+ if (bs->inventory[INVENTORY_QUAD]) {
+ //if the bot is not holding the gauntlet or the enemy is really nearby
+ if (bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) {
+ return 70;
+ }
+ }
+ //if the enemy is located way higher than the bot
+ if (bs->inventory[ENEMY_HEIGHT] > 200) return 0;
+ //if the bot is very low on health
+ if (bs->inventory[INVENTORY_HEALTH] < 60) return 0;
+ //if the bot is low on health
+ if (bs->inventory[INVENTORY_HEALTH] < 80) {
+ //if the bot has insufficient armor
+ if (bs->inventory[INVENTORY_ARMOR] < 40) return 0;
+ }
+
+ if (bs->inventory[INVENTORY_DREADNOUGHT] > 0 &&
+ bs->inventory[INVENTORY_DREADNOUGHTAMMO] > 0) return 100;
+
+ if (bs->inventory[INVENTORY_TETRION] > 0 &&
+ bs->inventory[INVENTORY_TETRIONAMMO] > 0) return 95;
+
+ if (bs->inventory[INVENTORY_QUANTUM] > 0 &&
+ bs->inventory[INVENTORY_QUANTUMAMMO] > 0) return 90;
+
+ if (bs->inventory[INVENTORY_STASIS] > 0 &&
+ bs->inventory[INVENTORY_STASISAMMO] > 0) return 85;
+
+ if (bs->inventory[INVENTORY_SCAVENGER] > 0 &&
+ bs->inventory[INVENTORY_SCAVENGERAMMO] > 0) return 80;
+
+ if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
+ bs->inventory[INVENTORY_GRENADES] > 0) return 75;
+
+ if (bs->inventory[INVENTORY_IMOD] > 0 &&
+ bs->inventory[INVENTORY_IMODAMMO] > 0) return 70;
+
+ if (bs->inventory[INVENTORY_COMPRESSION] > 0 &&
+ bs->inventory[INVENTORY_COMPRESSIONAMMO] > 0) return 65;
+
+ //otherwise the bot is not feeling too aggressive
+ return 0;
+}
+
+/*
+==================
+BotWantsToRetreat
+==================
+*/
+int BotWantsToRetreat(bot_state_t *bs) {
+ aas_entityinfo_t entinfo;
+
+ //always retreat when carrying a CTF flag
+ if (BotCTFCarryingFlag(bs)) return qtrue;
+ //
+ if (bs->enemy >= 0) {
+ //if the enemy is carrying a flag
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityCarriesFlag(&entinfo)) return qfalse;
+ }
+ //if the bot is getting the flag
+ if (bs->ltgtype == LTG_GETFLAG) return qtrue;
+ //
+ if (BotAggression(bs) < 50) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+BotWantsToChase
+==================
+*/
+int BotWantsToChase(bot_state_t *bs) {
+ aas_entityinfo_t entinfo;
+
+ //always retreat when carrying a CTF flag
+ if (BotCTFCarryingFlag(bs)) return qfalse;
+ //if the enemy is carrying a flag
+ BotEntityInfo(bs->enemy, &entinfo);
+ if (EntityCarriesFlag(&entinfo)) return qtrue;
+ //if the bot is getting the flag
+ if (bs->ltgtype == LTG_GETFLAG) return qfalse;
+ //
+ if (BotAggression(bs) > 50) return qtrue;
+ return qfalse;
+}
+
+/*
+==================
+BotWantsToHelp
+==================
+*/
+int BotWantsToHelp(bot_state_t *bs) {
+ return qtrue;
+}
+
+/*
+==================
+BotCanAndWantsToRocketJump
+==================
+*/
+int BotCanAndWantsToRocketJump(bot_state_t *bs) {
+ float rocketjumper;
+
+ //if rocket jumping is disabled
+ if (!bot_rocketjump.integer) return qfalse;
+ //if no rocket launcher
+ if (bs->inventory[INVENTORY_QUANTUM] <= 0) return qfalse;
+ //if low on rockets
+ if (bs->inventory[INVENTORY_QUANTUMAMMO] < 1) return qfalse;
+ //never rocket jump with the Quad
+ if (bs->inventory[INVENTORY_QUAD])
+ {
+ if ( rpg_selfdamage.integer != 0 )
+ {
+ return qfalse;
+ }
+ }
+ //if low on health
+ if (bs->inventory[INVENTORY_HEALTH] < 50)
+ { //if not full health
+ if ( rpg_selfdamage.integer != 0 )
+ {
+ return qfalse;
+ }
+ }
+ if (bs->inventory[INVENTORY_HEALTH] < 60)
+ { //if the bot has insufficient armor
+ if (bs->inventory[INVENTORY_ARMOR] < 20)
+ {
+ if ( rpg_selfdamage.integer != 0 )
+ {
+ return qfalse;
+ }
+ }
+ }
+ rocketjumper = 1;
+ return qtrue;
+}
+
+/*
+==================
+BotGoCamp
+==================
+*/
+void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) {
+ float camper;
+
+ //set message time to zero so bot will NOT show any message
+ bs->teammessage_time = 0;
+ //set the ltg type
+ bs->ltgtype = LTG_CAMP;
+ //set the team goal
+ memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t));
+ //get the team goal time
+ camper = 0;
+ if (camper > 0.99) bs->teamgoal_time = 99999;
+ else bs->teamgoal_time = 120 + 180 * camper + random() * 15;
+ //set the last time the bot started camping
+ bs->camp_time = trap_AAS_Time();
+ //the teammate that requested the camping
+ bs->teammate = 0;
+ //do NOT type arrive message
+ bs->arrive_time = 1;
+}
+
+/*
+==================
+BotWantsToCamp
+==================
+*/
+int BotWantsToCamp(bot_state_t *bs) {
+ float camper;
+ camper = 0;
+ return qfalse;
+}
+
+/*
+==================
+BotDontAvoid
+==================
+*/
+void BotDontAvoid(bot_state_t *bs, char *itemname) {
+ bot_goal_t goal;
+ int num;
+
+ num = trap_BotGetLevelItemGoal(-1, itemname, &goal);
+ while(num >= 0) {
+ trap_BotRemoveFromAvoidGoals(bs->gs, goal.number);
+ num = trap_BotGetLevelItemGoal(num, itemname, &goal);
+ }
+}
+
+/*
+==================
+BotGoForPowerups
+==================
+*/
+void BotGoForPowerups(bot_state_t *bs) {
+
+ //don't avoid any of the powerups anymore
+ BotDontAvoid(bs, "Quantum Weapon Enhancer");
+ BotDontAvoid(bs, "Nano-Regenerative Protoplasmer");
+ BotDontAvoid(bs, "Metaphasic Shielding");
+ BotDontAvoid(bs, "Temporal Accelerator");
+ BotDontAvoid(bs, "Personal Cloaking Device");
+ BotDontAvoid(bs, "Seeker Drone");
+ BotDontAvoid(bs, "Anti-Gravity Pack");
+ //reset the long term goal time so the bot will go for the powerup
+ //NOTE: the long term goal type doesn't change
+ bs->ltg_time = 0;
+}
+
+/*
+==================
+BotRoamGoal
+==================
+*/
+void BotRoamGoal(bot_state_t *bs, vec3_t goal) {
+ int pc, i;
+ float len, rnd;
+ vec3_t dir, bestorg, belowbestorg;
+ bsp_trace_t trace;
+
+ for (i = 0; i < 10; i++) {
+ //start at the bot origin
+ VectorCopy(bs->origin, bestorg);
+ rnd = random();
+ if (rnd > 0.25) {
+ //add a random value to the x-coordinate
+ if (random() < 0.5) bestorg[0] -= 800 * random() + 100;
+ else bestorg[0] += 800 * random() + 100;
+ }
+ if (rnd < 0.75) {
+ //add a random value to the y-coordinate
+ if (random() < 0.5) bestorg[1] -= 800 * random() + 100;
+ else bestorg[1] += 800 * random() + 100;
+ }
+ //add a random value to the z-coordinate (NOTE: 48 = maxjump?)
+ bestorg[2] += 2 * 48 * crandom();
+ //trace a line from the origin to the roam target
+ BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID);
+ //direction and length towards the roam target
+ VectorSubtract(trace.endpos, bs->origin, dir);
+ len = VectorNormalize(dir);
+ //if the roam target is far away anough
+ if (len > 200) {
+ //the roam target is in the given direction before walls
+ VectorScale(dir, len * trace.fraction - 40, dir);
+ VectorAdd(bs->origin, dir, bestorg);
+ //get the coordinates of the floor below the roam target
+ belowbestorg[0] = bestorg[0];
+ belowbestorg[1] = bestorg[1];
+ belowbestorg[2] = bestorg[2] - 800;
+ BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID);
+ //
+ if (!trace.startsolid) {
+ trace.endpos[2]++;
+ pc = trap_PointContents(trace.endpos, bs->entitynum);
+ if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) {
+ VectorCopy(bestorg, goal);
+ return;
+ }
+ }
+ }
+ }
+ VectorCopy(bestorg, goal);
+}
+
+/*
+==================
+BotAttackMove
+==================
+*/
+bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) {
+ int movetype, i;
+ float attack_skill, jumper, croucher, dist, strafechange_time;
+ float attack_dist, attack_range;
+ vec3_t forward, backward, sideward, hordir, up = {0, 0, 1};
+ aas_entityinfo_t entinfo;
+ bot_moveresult_t moveresult;
+ bot_goal_t goal;
+
+ if (bs->attackchase_time > trap_AAS_Time()) {
+ //create the chase goal
+ goal.entitynum = bs->enemy;
+ goal.areanum = bs->lastenemyareanum;
+ VectorCopy(bs->lastenemyorigin, goal.origin);
+ VectorSet(goal.mins, -8, -8, -8);
+ VectorSet(goal.maxs, 8, 8, 8);
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //move towards the goal
+ trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl);
+ return moveresult;
+ }
+ //
+ memset(&moveresult, 0, sizeof(bot_moveresult_t));
+ //
+ attack_skill = 1;
+ jumper = 1;
+ croucher = 1;
+ //initialize the movement state
+ BotSetupForMovement(bs);
+ //get the enemy entity info
+ BotEntityInfo(bs->enemy, &entinfo);
+ //direction towards the enemy
+ VectorSubtract(entinfo.origin, bs->origin, forward);
+ //the distance towards the enemy
+ dist = VectorNormalize(forward);
+ VectorNegate(forward, backward);
+ //walk, crouch or jump
+ movetype = MOVE_WALK;
+ //
+ if (bs->attackcrouch_time < trap_AAS_Time() - 1) {
+ if (random() < jumper) {
+ movetype = MOVE_JUMP;
+ }
+ //wait at least one second before crouching again
+ else if (bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher) {
+ bs->attackcrouch_time = trap_AAS_Time() + croucher * 5;
+ }
+ }
+ if (bs->attackcrouch_time > trap_AAS_Time()) movetype = MOVE_CROUCH;
+ //if the bot should jump
+ if (movetype == MOVE_JUMP) {
+ //if jumped last frame
+ if (bs->attackjump_time > trap_AAS_Time()) {
+ movetype = MOVE_WALK;
+ }
+ else {
+ bs->attackjump_time = trap_AAS_Time() + 1;
+ }
+ }
+
+ //if using assimilator or alt-fire hypo...
+ if ( bs->weaponnum == WP_TOOLKIT || bs->weaponnum == (WP_VOYAGER_HYPO+WP_NUM_WEAPONS) )
+ {//get real close
+ attack_dist = 16;
+ attack_range = 16;
+ }
+ else
+ {
+ attack_dist = IDEAL_ATTACKDIST;
+ attack_range = 40;
+ }
+
+ //increase the strafe time
+ bs->attackstrafe_time += bs->thinktime;
+ //get the strafe change time
+ strafechange_time = 0.4 + (1 - attack_skill) * 0.2;
+ if (attack_skill > 0.7) strafechange_time += crandom() * 0.2;
+ //if the strafe direction should be changed
+ if (bs->attackstrafe_time > strafechange_time) {
+ //some magic number :)
+ if (random() > 0.935) {
+ //flip the strafe direction
+ bs->flags ^= BFL_STRAFERIGHT;
+ bs->attackstrafe_time = 0;
+ }
+ }
+ //
+ for (i = 0; i < 2; i++) {
+ hordir[0] = forward[0];
+ hordir[1] = forward[1];
+ hordir[2] = 0;
+ VectorNormalize(hordir);
+ //get the sideward vector
+ CrossProduct(hordir, up, sideward);
+ //reverse the vector depending on the strafe direction
+ if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward);
+ //randomly go back a little
+ if (random() > 0.9) {
+ VectorAdd(sideward, backward, sideward);
+ }
+ else {
+ //walk forward or backward to get at the ideal attack distance
+ if (dist > attack_dist + attack_range) VectorAdd(sideward, forward, sideward);
+ else if (dist < attack_dist - attack_range) VectorAdd(sideward, backward, sideward);
+ }
+ //perform the movement
+ if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) return moveresult;
+ //movement failed, flip the strafe direction
+ bs->flags ^= BFL_STRAFERIGHT;
+ bs->attackstrafe_time = 0;
+ }
+ //bot couldn't do any usefull movement
+// bs->attackchase_time = AAS_Time() + 6;
+ return moveresult;
+}
+
+/*
+==================
+BotSameTeam
+==================
+*/
+int BotSameTeam(bot_state_t *bs, int entnum) {
+ char info1[1024], info2[1024];
+
+ if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
+ //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
+ return qfalse;
+ }
+ if (entnum < 0 || entnum >= MAX_CLIENTS) {
+ //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
+ return qfalse;
+ }
+ if (gametype == GT_TEAM || gametype == GT_CTF) {
+ trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1));
+ trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2));
+ //
+ if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+InFieldOfVision
+==================
+*/
+qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
+{
+ int i;
+ float diff, angle;
+
+ for (i = 0; i < 2; i++) {
+ angle = AngleMod(viewangles[i]);
+ angles[i] = AngleMod(angles[i]);
+ diff = angles[i] - angle;
+ if (angles[i] > angle) {
+ if (diff > 180.0) diff -= 360.0;
+ }
+ else {
+ if (diff < -180.0) diff += 360.0;
+ }
+ if (diff > 0) {
+ if (diff > fov * 0.5) return qfalse;
+ }
+ else {
+ if (diff < -fov * 0.5) return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+/*
+==================
+BotEntityVisible
+
+returns visibility in the range [0, 1] taking fog and water surfaces into account
+==================
+*/
+float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) {
+ int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
+ float fogdist, waterfactor, vis, bestvis;
+ bsp_trace_t trace;
+ aas_entityinfo_t entinfo;
+ vec3_t dir, entangles, start, end, middle;
+
+ //calculate middle of bounding box
+ BotEntityInfo(ent, &entinfo);
+ VectorAdd(entinfo.mins, entinfo.maxs, middle);
+ VectorScale(middle, 0.5, middle);
+ VectorAdd(entinfo.origin, middle, middle);
+ //check if entity is within field of vision
+ VectorSubtract(middle, eye, dir);
+ vectoangles(dir, entangles);
+ if (!InFieldOfVision(viewangles, fov, entangles)) return 0;
+ //
+ pc = trap_AAS_PointContents(eye);
+ infog = (pc & CONTENTS_SOLID);
+ inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER));
+ //
+ bestvis = 0;
+ for (i = 0; i < 3; i++) {
+ //if the point is not in potential visible sight
+ //if (!AAS_inPVS(eye, middle)) continue;
+ //
+ contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP;
+ passent = viewer;
+ hitent = ent;
+ VectorCopy(eye, start);
+ VectorCopy(middle, end);
+ //if the entity is in water, lava or slime
+ if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
+ contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
+ }
+ //if eye is in water, lava or slime
+ if (inwater) {
+ if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) {
+ passent = ent;
+ hitent = viewer;
+ VectorCopy(middle, start);
+ VectorCopy(eye, end);
+ }
+ contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
+ }
+ //trace from start to end
+ BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask);
+ //if water was hit
+ waterfactor = 1.0;
+ if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
+ //if the water surface is translucent
+ if (1) {
+ //trace through the water
+ contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
+ BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
+ waterfactor = 0.5;
+ }
+ }
+ //if a full trace or the hitent was hit
+ if (trace.fraction >= 1 || trace.ent == hitent) {
+ //check for fog, assuming there's only one fog brush where
+ //either the viewer or the entity is in or both are in
+ otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG);
+ if (infog && otherinfog) {
+ VectorSubtract(trace.endpos, eye, dir);
+ fogdist = VectorLength(dir);
+ }
+ else if (infog) {
+ VectorCopy(trace.endpos, start);
+ BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
+ VectorSubtract(eye, trace.endpos, dir);
+ fogdist = VectorLength(dir);
+ }
+ else if (otherinfog) {
+ VectorCopy(trace.endpos, end);
+ BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
+ VectorSubtract(end, trace.endpos, dir);
+ fogdist = VectorLength(dir);
+ }
+ else {
+ //if the entity and the viewer are not in fog assume there's no fog in between
+ fogdist = 0;
+ }
+ //decrease visibility with the view distance through fog
+ vis = 1 / ((fogdist * fogdist * 0.001) < 1 ? 1 : (fogdist * fogdist * 0.001));
+ //if entering water visibility is reduced
+ vis *= waterfactor;
+ //
+ if (vis > bestvis) bestvis = vis;
+ //if pretty much no fog
+ if (bestvis >= 0.95) return bestvis;
+ }
+ //check bottom and top of bounding box as well
+ if (i == 0) middle[2] += entinfo.mins[2];
+ else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2];
+ }
+ return bestvis;
+}
+
+/*
+==================
+BotFindEnemy
+==================
+*/
+int BotFindEnemy(bot_state_t *bs, int curenemy) {
+ int i, healthdecrease;
+ float f, dist, curdist, alertness, easyfragger, vis;
+ aas_entityinfo_t entinfo, curenemyinfo;
+ vec3_t dir, angles;
+
+ alertness = 1;
+ easyfragger = 1;
+ //check if the health decreased
+ healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
+ //remember the current health value
+ bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
+ //
+ if (curenemy >= 0) {
+ BotEntityInfo(curenemy, &curenemyinfo);
+ if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
+ VectorSubtract(curenemyinfo.origin, bs->origin, dir);
+ curdist = VectorLength(dir);
+ }
+ else {
+ curdist = 0;
+ }
+ //
+
+ //FIXME: This only finds lowest numbered enemy, not the closest or best!!!
+ // 6/15/00 dpk changed this
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+
+ if (i == bs->client) continue;
+ //if it's the current enemy
+ if (i == curenemy) continue;
+ //
+ BotEntityInfo(i, &entinfo);
+ //
+ if (!entinfo.valid) continue;
+ //if on the same team
+ if (BotSameTeam(bs, i)) continue;
+ //if the enemy isn't dead and the enemy isn't the bot self
+ if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
+ //if the enemy is invisible and not shooting
+ if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
+ continue;
+ }
+ //if not an easy fragger don't shoot at chatting players
+ if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue;
+ //
+ if (lastteleport_time > trap_AAS_Time() - 3) {
+ VectorSubtract(entinfo.origin, lastteleport_origin, dir);
+ if (VectorLength(dir) < 70) continue;
+ }
+ //calculate the distance towards the enemy
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ dist = VectorLength(dir);
+
+
+ //if this entity is not carrying a flag
+ if (!EntityCarriesFlag(&entinfo))
+// if (EntityCarriesFlag(&entinfo))
+/* {
+ // pick this one!
+ }
+ else
+*/ {
+ //if this enemy is further away than the current one
+ if (curenemy >= 0 && dist > curdist) continue;
+ }
+
+ //if the bot in too far away for me to notice
+ if (dist > 900 + alertness * 4000) continue;
+
+
+ //if the bot's health decreased or the enemy is shooting
+ if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) f = 360;
+ else f = 90 + 90 - (90 - (dist > 810 ? 810 : dist) / 9);
+ //check if the enemy is visible
+ vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i);
+ if (vis <= 0) continue;
+ //if the enemy is quite far away, not shooting and the bot is not damaged
+ if (curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting(&entinfo))
+ {
+ //check if we can avoid this enemy
+ VectorSubtract(bs->origin, entinfo.origin, dir);
+ vectoangles(dir, angles);
+ //if the bot isn't in the fov of the enemy
+ if (!InFieldOfVision(entinfo.angles, 120, angles)) {
+ //update some stuff for this enemy
+ BotUpdateBattleInventory(bs, i);
+ //if the bot doesn't really want to fight
+ if (BotWantsToRetreat(bs)) continue;
+ }
+ }
+// }
+ //found an enemy
+ bs->enemy = entinfo.number;
+ if (curenemy >= 0) bs->enemysight_time = trap_AAS_Time() - 2;
+ else bs->enemysight_time = trap_AAS_Time();
+ bs->enemysuicide = qfalse;
+ bs->enemydeath_time = 0;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+BotTeamFlagCarrierVisible
+==================
+*/
+int BotTeamFlagCarrierVisible(bot_state_t *bs) {
+ int i;
+ float vis;
+ aas_entityinfo_t entinfo;
+
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ if (i == bs->client) continue;
+ //
+ BotEntityInfo(i, &entinfo);
+ //if this player is active
+ if (!entinfo.valid) continue;
+ //if this player is carrying a flag
+ if (!EntityCarriesFlag(&entinfo)) continue;
+ //if the flag carrier is not on the same team
+ if (!BotSameTeam(bs, i)) continue;
+ //if the flag carrier is not visible
+ vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
+ if (vis <= 0) continue;
+ //
+ return i;
+ }
+ return -1;
+}
+
+/*
+==================
+BotAimAtEnemy
+
+ MCG - FIXME: This is not set up at all correctly in the following areas:
+ Needs to consider if weapon is an alt weapon for aim and accuracy as well as functionality
+ Need to consider range?
+ Grenade Primary and alt as well as Scavenger alt need to take into account gravity
+ Needs to pick our new weapons correctly to determine which ones they should:
+ lead with (projectiles have speed)
+ decay aim purposely (instant hit weaps)
+ shoot "around corners" (bouncers, mines, splashdamage?)
+
+==================
+*/
+void BotAimAtEnemy(bot_state_t *bs) {
+ int i, enemyvisible;
+ float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
+ vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
+ vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
+ weaponinfo_t wi;
+ aas_entityinfo_t entinfo;
+ bot_goal_t goal;
+ bsp_trace_t trace;
+ vec3_t target;
+
+ //if the bot has no enemy
+ if (bs->enemy < 0) return;
+ //
+ //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
+ //
+ aim_skill = 1;
+ aim_accuracy = 1;
+ //
+ if (aim_skill > 0.95)
+ {
+ //don't aim too early
+ reactiontime = 0.5;
+ if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
+ if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
+ }
+
+ //get the weapon information
+ trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
+ //get the weapon specific aim accuracy and or aim skill
+
+ if (wi.number == WP_GRENADE_LAUNCHER) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
+ aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
+ }
+ if (wi.number == WP_DISRUPTOR) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_STASIS, 0, 1);
+ aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_STASIS, 0, 1);
+ }
+ if (wi.number == WP_PHASER) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PHASER, 0, 1);
+ }
+ if (wi.number == WP_NULL_HAND) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_IMOD, 0, 1);
+ }
+ if (wi.number == WP_COMPRESSION_RIFLE) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_COMPRESSION, 0, 1);
+ }
+ if (wi.number == WP_TR116) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_TETRION, 0, 1);
+ }
+ if (wi.number == WP_DERMAL_REGEN) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_DREADNOUGHT, 0, 1);
+ }
+ if (wi.number == WP_QUANTUM_BURST) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_QUANTUM, 0, 1);
+ aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_QUANTUM, 0, 1);
+ }
+ if (wi.number == WP_COFFEE) {
+ aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SCAVENGER, 0, 1);
+ }
+
+ //
+ if (aim_accuracy <= 0) aim_accuracy = 0.0001;
+ //get the enemy entity information
+ BotEntityInfo(bs->enemy, &entinfo);
+ //if the enemy is invisible then shoot crappy most of the time
+ if (EntityIsInvisible(&entinfo)) {
+ if (random() > 0.1) aim_accuracy *= 0.4;
+ }
+ //
+ VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity);
+ VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
+ //enemy origin and velocity is remembered every 0.5 seconds
+ if (bs->enemyposition_time < trap_AAS_Time()) {
+ //
+ bs->enemyposition_time = trap_AAS_Time() + 0.5;
+ VectorCopy(enemyvelocity, bs->enemyvelocity);
+ VectorCopy(entinfo.origin, bs->enemyorigin);
+ }
+ //if not extremely skilled
+ if (aim_skill < 0.9) {
+ VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
+ //if the enemy moved a bit
+ if (VectorLength(dir) > 48) {
+ //if the enemy changed direction
+ if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
+ //aim accuracy should be worse now
+ aim_accuracy *= 0.7;
+ }
+ }
+ }
+ //check visibility of enemy
+ enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy);
+ //if the enemy is visible
+ if (enemyvisible) {
+ //
+ VectorCopy(entinfo.origin, bestorigin);
+ bestorigin[2] += 8;
+ //get the start point shooting from
+ //NOTE: the x and y projectile start offsets are ignored
+ VectorCopy(bs->origin, start);
+ start[2] += bs->cur_ps.viewheight;
+ start[2] += wi.offset[2];
+ //
+ BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
+ //if the enemy is NOT hit
+ if (trace.fraction <= 1 && trace.ent != entinfo.number) {
+ bestorigin[2] += 16;
+ }
+ //if it is not an instant hit weapon the bot might want to predict the enemy
+ if (wi.speed) {
+ //
+ VectorSubtract(bestorigin, bs->origin, dir);
+ dist = VectorLength(dir);
+ VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
+ //if the enemy is NOT pretty far away and strafing just small steps left and right
+ if (!(dist > 100 && VectorLength(dir) < 32)) {
+ //if skilled anough do exact prediction
+ if (aim_skill > 0.8 &&
+ //if the weapon is ready to fire
+ bs->cur_ps.weaponstate == WEAPON_READY) {
+ aas_clientmove_t move;
+ vec3_t origin;
+
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ //distance towards the enemy
+ dist = VectorLength(dir);
+ //direction the enemy is moving in
+ VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
+ //
+ VectorScale(dir, 1 / entinfo.update_time, dir);
+ //
+ VectorCopy(entinfo.origin, origin);
+ origin[2] += 1;
+ //
+ VectorClear(cmdmove);
+ //AAS_ClearShownDebugLines();
+ trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
+ PRESENCE_CROUCH, qfalse,
+ dir, cmdmove, 0,
+ dist * 10 / wi.speed, 0.1, 0, 0, qfalse);
+ VectorCopy(move.endpos, bestorigin);
+ //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed);
+ }
+ //if not that skilled do linear prediction
+ else if (aim_skill > 0.4) {
+ VectorSubtract(entinfo.origin, bs->origin, dir);
+ //distance towards the enemy
+ dist = VectorLength(dir);
+ //direction the enemy is moving in
+ VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
+ dir[2] = 0;
+ //
+ speed = VectorNormalize(dir) / entinfo.update_time;
+ //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
+ //best spot to aim at
+ VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
+ }
+ }
+ }
+ //if the projectile does radial damage
+ if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) {
+ //if the enemy isn't standing significantly higher than the bot
+ if (entinfo.origin[2] < bs->origin[2] + 16) {
+ //try to aim at the ground in front of the enemy
+ VectorCopy(entinfo.origin, end);
+ end[2] -= 64;
+ BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT);
+ //
+ VectorCopy(bestorigin, groundtarget);
+ if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16;
+ else groundtarget[2] = trace.endpos[2] - 8;
+ //trace a line from projectile start to ground target
+ BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
+ //if hitpoint is not vertically too far from the ground target
+ if (fabs(trace.endpos[2] - groundtarget[2]) < 50) {
+ VectorSubtract(trace.endpos, groundtarget, dir);
+ //if the hitpoint is near anough the ground target
+ if (VectorLength(dir) < 60) {
+ VectorSubtract(trace.endpos, start, dir);
+ //if the hitpoint is far anough from the bot
+ if (VectorLength(dir) > 100) {
+ //check if the bot is visible from the ground target
+ trace.endpos[2] += 1;
+ BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT);
+ if (trace.fraction >= 1) {
+ //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
+ VectorCopy(groundtarget, bestorigin);
+ }
+ }
+ }
+ }
+ }
+ }
+ bestorigin[0] += 20 * crandom() * (1 - aim_accuracy);
+ bestorigin[1] += 20 * crandom() * (1 - aim_accuracy);
+ bestorigin[2] += 10 * crandom() * (1 - aim_accuracy);
+ }
+ else {
+ //
+ VectorCopy(bs->lastenemyorigin, bestorigin);
+ bestorigin[2] += 8;
+ //if the bot is skilled anough
+ if (aim_skill > 0.5) {
+ //do prediction shots around corners
+ if (wi.number == WP_DISRUPTOR ||
+ wi.number == WP_GRENADE_LAUNCHER)
+ {
+ //create the chase goal
+ goal.entitynum = bs->client;
+ goal.areanum = bs->areanum;
+ VectorCopy(bs->eye, goal.origin);
+ VectorSet(goal.mins, -8, -8, -8);
+ VectorSet(goal.maxs, 8, 8, 8);
+ //
+ if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
+ VectorSubtract(target, bs->eye, dir);
+ if (VectorLength(dir) > 80) {
+ VectorCopy(target, bestorigin);
+ bestorigin[2] -= 20;
+ }
+ }
+ aim_accuracy = 1;
+ }
+ }
+ }
+ //
+ if (enemyvisible) {
+ BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
+ VectorCopy(trace.endpos, bs->aimtarget);
+ }
+ else {
+ VectorCopy(bestorigin, bs->aimtarget);
+ }
+ //get aim direction
+ VectorSubtract(bestorigin, bs->eye, dir);
+ // kef -- fixme. i'm guessing this is listing all of the instant-hit weapons?
+ if (wi.number == WP_PHASER ||
+ wi.number == WP_NULL_HAND) {
+ //distance towards the enemy
+ dist = VectorLength(dir);
+ if (dist > 150) dist = 150;
+ f = 0.6 + dist / 150 * 0.4;
+ aim_accuracy *= f;
+ }
+ //add some random stuff to the aim direction depending on the aim accuracy
+ if (aim_accuracy < 0.8) {
+ VectorNormalize(dir);
+ for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy);
+ }
+ //set the ideal view angles
+ vectoangles(dir, bs->ideal_viewangles);
+ //take the weapon spread into account for lower skilled bots
+ bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
+ bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
+ //if the bots should be really challenging
+ //if the bot is really accurate and has the enemy in view for some time
+ if (aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1)
+ {
+ //set the view angles directly
+ if (bs->ideal_viewangles[PITCH] > 180)
+ {
+ bs->ideal_viewangles[PITCH] -= 360;
+ }
+ VectorCopy(bs->ideal_viewangles, bs->viewangles);
+ trap_EA_View(bs->client, bs->viewangles);
+ }
+}
+
+/*
+==================
+BotCheckAttack
+==================
+*/
+void BotCheckAttack(bot_state_t *bs) {
+ float points, reactiontime, fov, firethrottle;
+ bsp_trace_t bsptrace;
+ //float selfpreservation;
+ vec3_t forward, right, start, end, dir, angles;
+ weaponinfo_t wi;
+ bsp_trace_t trace;
+ aas_entityinfo_t entinfo;
+ vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
+
+ if (bs->enemy < 0) return;
+ //
+ reactiontime = 0;
+ if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
+ if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
+ //if changing weapons
+ if (bs->weaponchange_time > trap_AAS_Time() - 0.1) return;
+ //check fire throttle characteristic
+ if (bs->firethrottlewait_time > trap_AAS_Time()) return;
+ firethrottle = 1;
+ if (bs->firethrottleshoot_time < trap_AAS_Time()) {
+ if (random() > firethrottle) {
+ bs->firethrottlewait_time = trap_AAS_Time() + firethrottle;
+ bs->firethrottleshoot_time = 0;
+ }
+ else {
+ bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle;
+ bs->firethrottlewait_time = 0;
+ }
+ }
+ //
+ BotEntityInfo(bs->enemy, &entinfo);
+ VectorSubtract(entinfo.origin, bs->eye, dir);
+ //
+ if (VectorLength(dir) < 100) fov = 120;
+ else fov = 50;
+ /*
+ //if the enemy isn't visible
+ if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) {
+ //botimport.Print(PRT_MESSAGE, "enemy not visible\n");
+ return;
+ }*/
+ vectoangles(dir, angles);
+ if (!InFieldOfVision(bs->viewangles, fov, angles)) return;
+ BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
+ if (bsptrace.fraction < 1 && bsptrace.ent != bs->enemy) return;
+
+ //get the weapon info
+ trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
+ //get the start point shooting from
+ VectorCopy(bs->origin, start);
+ start[2] += bs->cur_ps.viewheight;
+ AngleVectors(bs->viewangles, forward, right, NULL);
+ start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
+ start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
+ start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
+ //end point aiming at
+ VectorMA(start, 1000, forward, end);
+ //a little back to make sure not inside a very close enemy
+ VectorMA(start, -12, forward, start);
+ BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
+ //if won't hit the enemy
+ if (trace.ent != bs->enemy) {
+ //if the entity is a client
+ if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) {
+ //if a teammate is hit
+ if (BotSameTeam(bs, trace.ent)) return;
+ }
+ //if the projectile does a radial damage
+ if (wi.proj.damagetype & DAMAGETYPE_RADIAL) {
+ if (trace.fraction * 1000 < wi.proj.radius) {
+ points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5;
+ if (points > 0) {
+// selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1);
+// if (random() < selfpreservation) return;
+ return;
+ }
+ }
+ //FIXME: check if a teammate gets radial damage
+ }
+ }
+ //if fire has to be release to activate weapon
+ if (wi.flags & WFL_FIRERELEASED) {
+ if (bs->flags & BFL_ATTACKED)
+ {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery?
+ if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire!
+ {
+ bs->weaponnum -= WP_NUM_WEAPONS;
+ trap_EA_Alt_Attack(bs->client);
+ }
+ else
+ {
+ trap_EA_Attack(bs->client);
+ }
+ }
+ }
+ else
+ { // check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery?
+ if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire!
+ {
+ bs->weaponnum -= WP_NUM_WEAPONS;
+ trap_EA_Alt_Attack(bs->client);
+ }
+ else
+ {
+ trap_EA_Attack(bs->client);
+ }
+ }
+ bs->flags ^= BFL_ATTACKED;
+}
+
+/*
+==================
+BotMapScripts
+==================
+*/
+void BotMapScripts(bot_state_t *bs) {
+ char info[1024];
+ char mapname[128];
+ int i, shootbutton;
+ float aim_accuracy;
+ aas_entityinfo_t entinfo;
+ vec3_t dir;
+
+ trap_GetServerinfo(info, sizeof(info));
+
+ strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
+ mapname[sizeof(mapname)-1] = '\0';
+
+ if (!Q_stricmp(mapname, "q3tourney6")) {
+ vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680};
+ vec3_t buttonorg = {304, 352, 920};
+ //NOTE: NEVER use the func_bobbing in q3tourney6
+ bs->tfl &= ~TFL_FUNCBOB;
+ //if the bot is below the bounding box
+ if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) {
+ if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) {
+ if (bs->origin[2] < mins[2]) {
+ return;
+ }
+ }
+ }
+ shootbutton = qfalse;
+ //if an enemy is below this bounding box then shoot the button
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+
+ if (i == bs->client) continue;
+ //
+ BotEntityInfo(i, &entinfo);
+ //
+ if (!entinfo.valid) continue;
+ //if the enemy isn't dead and the enemy isn't the bot self
+ if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
+ //
+ if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) {
+ if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) {
+ if (entinfo.origin[2] < mins[2]) {
+ //if there's a team mate below the crusher
+ if (BotSameTeam(bs, i)) {
+ shootbutton = qfalse;
+ break;
+ }
+ else {
+ shootbutton = qtrue;
+ }
+ }
+ }
+ }
+ }
+ if (shootbutton) {
+ bs->flags |= BFL_IDEALVIEWSET;
+ VectorSubtract(buttonorg, bs->eye, dir);
+ vectoangles(dir, bs->ideal_viewangles);
+ aim_accuracy = 1;
+ bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
+ bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy);
+ bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
+ //
+ if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles))
+ {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery?
+ if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire!
+ {
+ bs->weaponnum -= WP_NUM_WEAPONS;
+ trap_EA_Alt_Attack(bs->client);
+ }
+ else
+ {
+ trap_EA_Attack(bs->client);
+ }
+ }
+ }
+ }
+}
+
+/*
+==================
+BotEntityToActivate
+==================
+*/
+//#define OBSTACLEDEBUG
+
+int BotEntityToActivate(int entitynum) {
+ int i, ent, cur_entities[10];
+ char model[MAX_INFO_STRING], tmpmodel[128];
+ char target[128], classname[128];
+ float health;
+ char targetname[10][128];
+ aas_entityinfo_t entinfo;
+
+ BotEntityInfo(entitynum, &entinfo);
+ Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex);
+ for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue;
+ if (!strcmp(model, tmpmodel)) break;
+ }
+ if (!ent) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model);
+ return 0;
+ }
+ trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
+ if (!classname) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model);
+ return 0;
+ }
+ //if it is a door
+ if (!strcmp(classname, "func_door")) {
+ if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) {
+ //if health the door must be shot to open
+ if (health) return ent;
+ }
+ }
+ //get the targetname so we can find an entity with a matching target
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) {
+#ifdef OBSTACLEDEBUG
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model);
+#endif //OBSTACLEDEBUG
+ return 0;
+ }
+ //only allows single activation chains, tree-like activation can be added back in
+ cur_entities[0] = trap_AAS_NextBSPEntity(0);
+ for (i = 0; i >= 0 && i < 10;) {
+ for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) {
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue;
+ if (!strcmp(targetname[i], target)) {
+ cur_entities[i] = trap_AAS_NextBSPEntity(ent);
+ break;
+ }
+ }
+ if (!ent) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i]);
+ i--;
+ continue;
+ }
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) {
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i]);
+ continue;
+ }
+ if (!strcmp(classname, "func_button")) {
+ //BSP button model
+ return ent;
+ }
+ else if (!strcmp(classname, "trigger_multiple")) {
+ //invisible trigger multiple box
+ return ent;
+ }
+ else {
+ i--;
+ }
+ }
+ BotAI_Print(PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname);
+ return 0;
+}
+
+/*
+==================
+BotSetMovedir
+==================
+*/
+vec3_t VEC_UP = {0, -1, 0};
+vec3_t MOVEDIR_UP = {0, 0, 1};
+vec3_t VEC_DOWN = {0, -2, 0};
+vec3_t MOVEDIR_DOWN = {0, 0, -1};
+
+void BotSetMovedir(vec3_t angles, vec3_t movedir) {
+ if (VectorCompare(angles, VEC_UP)) {
+ VectorCopy(MOVEDIR_UP, movedir);
+ }
+ else if (VectorCompare(angles, VEC_DOWN)) {
+ VectorCopy(MOVEDIR_DOWN, movedir);
+ }
+ else {
+ AngleVectors(angles, movedir, NULL, NULL);
+ }
+}
+
+/*
+==================
+BotModelMinsMaxs
+
+this is ugly
+==================
+*/
+void BotModelMinsMaxs(int modelindex, int eType, vec3_t mins, vec3_t maxs) {
+ gentity_t *ent;
+ int i;
+
+ ent = &g_entities[0];
+ for (i = 0; i < level.num_entities; i++, ent++) {
+ if ( !ent->inuse ) {
+ continue;
+ }
+ if ( ent->s.eType != eType) {
+ continue;
+ }
+ if (ent->s.modelindex == modelindex) {
+ VectorAdd(ent->r.currentOrigin, ent->r.mins, mins);
+ VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs);
+ return;
+ }
+ }
+ VectorClear(mins);
+ VectorClear(maxs);
+}
+
+/*
+==================
+BotAIBlocked
+
+very basic handling of bots being blocked by other entities
+check what kind of entity is blocking the bot and try to activate
+it otherwise try to walk around the entity
+before the bot ends in this part of the AI it should predict which doors to open,
+which buttons to activate etc.
+==================
+*/
+void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) {
+ int movetype, ent, i, areas[10], numareas, modelindex;
+ char classname[128], model[128];
+ float lip, dist, health, angle;
+ vec3_t hordir, size, start, end, mins, maxs, sideward, angles;
+ vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs;
+ vec3_t up = {0, 0, 1}, extramins = {1, 1, 1}, extramaxs = {-1, -1, -1};
+ aas_entityinfo_t entinfo;
+ //bsp_trace_t bsptrace;
+#ifdef OBSTACLEDEBUG
+ char netname[MAX_NETNAME];
+ char buf[128];
+#endif
+
+ if (!moveresult->blocked) {
+ bs->notblocked_time = trap_AAS_Time();
+ return;
+ }
+ //
+ BotEntityInfo(moveresult->blockentity, &entinfo);
+#ifdef OBSTACLEDEBUG
+ ClientName(bs->client, netname, sizeof(netname));
+ BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex);
+#endif OBSTACLEDEBUG
+ //if blocked by a bsp model and the bot wants to activate it if possible
+ if (entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate) {
+ //find the bsp entity which should be activated in order to remove
+ //the blocking entity
+ ent = BotEntityToActivate(entinfo.number);
+ if (!ent) {
+ strcpy(classname, "");
+#ifdef OBSTACLEDEBUG
+ BotAI_Print(PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName(bs->client, netname, sizeof(netname)));
+#endif //OBSTACLEDEBUG
+ }
+ else {
+ trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
+#ifdef OBSTACLEDEBUG
+ ClientName(bs->client, netname, sizeof(netname));
+ BotAI_Print(PRT_MESSAGE, "%s: I should activate %s\n", netname, classname);
+#endif OBSTACLEDEBUG
+ }
+ if (!strcmp(classname, "func_button")) {
+ //create a bot goal towards the button
+ trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
+ modelindex = atoi(model+1);
+ //if the model is not loaded
+ if (!modelindex) return;
+ VectorClear(angles);
+ BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
+ //get the lip of the button
+ trap_AAS_FloatForBSPEpairKey(ent, "lip", &lip);
+ if (!lip) lip = 4;
+ //get the move direction from the angle
+ trap_AAS_FloatForBSPEpairKey(ent, "angle", &angle);
+ VectorSet(angles, 0, angle, 0);
+ BotSetMovedir(angles, movedir);
+ //button size
+ VectorSubtract(maxs, mins, size);
+ //button origin
+ VectorAdd(mins, maxs, origin);
+ VectorScale(origin, 0.5, origin);
+ //touch distance of the button
+ dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];
+ dist *= 0.5;
+ //
+ trap_AAS_FloatForBSPEpairKey(ent, "health", &health);
+ //if the button is shootable
+ if (health) {
+ //FIXME: walk to a point where the button is visible and shoot at the button
+ //calculate the goal origin
+ VectorMA(origin, -dist, movedir, goalorigin);
+ //
+ VectorSubtract(goalorigin, bs->origin, movedir);
+ vectoangles(movedir, moveresult->ideal_viewangles);
+ moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
+ moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
+ //select the machinegun and shoot
+ trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER);
+ if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) {
+ trap_EA_Attack(bs->client);
+ }
+ return;
+ }
+ else {
+ //add bounding box size to the dist
+ trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
+ for (i = 0; i < 3; i++) {
+ if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
+ else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
+ }
+ //calculate the goal origin
+ VectorMA(origin, -dist, movedir, goalorigin);
+ //
+ VectorCopy(goalorigin, start);
+ start[2] += 24;
+ VectorCopy(start, end);
+ end[2] -= 100;
+ numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
+ //
+ for (i = 0; i < numareas; i++) {
+ if (trap_AAS_AreaReachability(areas[i])) {
+ break;
+ }
+ }
+ if (i < numareas) {
+ //
+#ifdef OBSTACLEDEBUG
+ if (bs->activatemessage_time < trap_AAS_Time()) {
+ Com_sprintf(buf, sizeof(buf), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n",
+ goalorigin[0], goalorigin[1], goalorigin[2], areas[i]);
+ trap_EA_Say(bs->client, buf);
+ bs->activatemessage_time = trap_AAS_Time() + 5;
+ }
+#endif //OBSTACLEDEBUG
+ //
+ VectorCopy(origin, bs->activategoal.origin);
+ bs->activategoal.areanum = areas[i];
+ VectorSubtract(mins, origin, bs->activategoal.mins);
+ VectorSubtract(maxs, origin, bs->activategoal.maxs);
+ //
+ for (i = 0; i < 3; i++)
+ {
+ if (movedir[i] < 0) bs->activategoal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
+ else bs->activategoal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
+ } //end for
+ //
+ bs->activategoal.entitynum = entinfo.number;
+ bs->activategoal.number = 0;
+ bs->activategoal.flags = 0;
+ bs->activate_time = trap_AAS_Time() + 10;
+ AIEnter_Seek_ActivateEntity(bs);
+ return;
+ }
+ else {
+#ifdef OBSTACLEDEBUG
+ if (!numareas) BotAI_Print(PRT_MESSAGE, "button not in an area\n");
+ else BotAI_Print(PRT_MESSAGE, "button area has no reachabilities\n");
+#endif //OBSTACLEDEBUG
+ if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
+ else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
+ }
+ }
+ }
+ else if (!strcmp(classname, "func_door")) {
+ //shoot at the shootable door
+ trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
+ modelindex = atoi(model+1);
+ //if the model is not loaded
+ if (!modelindex) return;
+ VectorClear(angles);
+ BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
+ //door origin
+ VectorAdd(mins, maxs, origin);
+ VectorScale(origin, 0.5, origin);
+ //
+ VectorSubtract(origin, bs->eye, movedir);
+ vectoangles(movedir, moveresult->ideal_viewangles);
+ moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
+ moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
+ //select the machinegun and shoot
+ trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER);
+ if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) {
+ trap_EA_Attack(bs->client);
+ }
+ return;
+ }
+ }
+ //just some basic dynamic obstacle avoidance code
+ hordir[0] = moveresult->movedir[0];
+ hordir[1] = moveresult->movedir[1];
+ hordir[2] = 0;
+ //if no direction just take a random direction
+ if (VectorNormalize(hordir) < 0.1) {
+ VectorSet(angles, 0, 360 * random(), 0);
+ AngleVectors(angles, hordir, NULL, NULL);
+ }
+ //
+ //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP;
+ //else
+ movetype = MOVE_WALK;
+ //if there's an obstacle at the bot's feet and head then
+ //the bot might be able to crouch through
+ VectorCopy(bs->origin, start);
+ start[2] += 18;
+ VectorMA(start, 5, hordir, end);
+ VectorSet(mins, -16, -16, -24);
+ VectorSet(maxs, 16, 16, 4);
+ //
+ //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID);
+ //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH;
+ //get the sideward vector
+ CrossProduct(hordir, up, sideward);
+ //
+ if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward);
+ //try to crouch straight forward?
+ if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) {
+ //perform the movement
+ if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) {
+ //flip the avoid direction flag
+ bs->flags ^= BFL_AVOIDRIGHT;
+ //flip the direction
+ VectorNegate(sideward, sideward);
+ //move in the other direction
+ trap_BotMoveInDirection(bs->ms, sideward, 400, movetype);
+ }
+ }
+ //
+ if (bs->notblocked_time < trap_AAS_Time() - 0.4) {
+ //just reset goals and hope the bot will go into another direction
+ //is this still needed??
+ if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
+ else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
+ }
+}
+
+/*
+==================
+BotCheckConsoleMessages
+==================
+*/
+void BotCheckConsoleMessages(bot_state_t *bs) {
+ char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr;
+ float chat_reply;
+ int context, handle;
+ bot_consolemessage_t m;
+ bot_match_t match;
+
+ //the name of this bot
+ ClientName(bs->client, botname, sizeof(botname));
+ //
+ while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) {
+ //if the chat state is flooded with messages the bot will read them quickly
+ if (trap_BotNumConsoleMessages(bs->cs) < 10) {
+ //if it is a chat message the bot needs some time to read it
+ if (m.type == CMS_CHAT && m.time > trap_AAS_Time() - (1 + random())) break;
+ }
+ //
+ ptr = m.message;
+ //if it is a chat message then don't unify white spaces and don't
+ //replace synonyms in the netname
+ if (m.type == CMS_CHAT) {
+ //
+ if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
+ ptr = m.message + match.variables[MESSAGE].offset;
+ }
+ }
+ //unify the white spaces in the message
+ trap_UnifyWhiteSpaces(ptr);
+ //replace synonyms in the right context
+ context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES;
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) context |= CONTEXT_CTFREDTEAM;
+ else context |= CONTEXT_CTFBLUETEAM;
+ trap_BotReplaceSynonyms(ptr, context);
+ //if there's no match
+ if (!BotMatchMessage(bs, m.message)) {
+ //if it is a chat message
+ if (m.type == CMS_CHAT && !bot_nochat.integer) {
+ //
+ if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ continue;
+ }
+ //don't use eliza chats with team messages
+ if (match.subtype & ST_TEAM) {
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ continue;
+ }
+ //
+ trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname));
+ trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message));
+ //if this is a message from the bot self
+ if (!Q_stricmp(netname, botname)) {
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ continue;
+ }
+ //unify the message
+ trap_UnifyWhiteSpaces(message);
+ //
+ trap_Cvar_Update(&bot_testrchat);
+ if (bot_testrchat.integer) {
+ //
+ trap_BotLibVarSet("bot_testrchat", "1");
+ //if bot replies with a chat message
+ if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ botname, netname)) {
+ BotAI_Print(PRT_MESSAGE, "------------------------\n");
+ }
+ else {
+ BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n");
+ }
+ }
+ //if at a valid chat position and not chatting already
+ else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs)) {
+ chat_reply = 0;
+ if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) {
+ //if bot replies with a chat message
+ if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ botname, netname)) {
+ //remove the console message
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ //EA_Say(bs->client, bs->cs.chatmessage);
+ break;
+ }
+ }
+ }
+ }
+ }
+ //remove the console message
+ trap_BotRemoveConsoleMessage(bs->cs, handle);
+ }
+}
+
+/*
+==================
+BotCheckEvents
+==================
+*/
+void BotCheckEvents(bot_state_t *bs, entityState_t *state) {
+ int event;
+ char buf[128];
+ //
+ //NOTE: this sucks, we're accessing the gentity_t directly
+ //but there's no other fast way to do it right now
+ if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) {
+ return;
+ }
+ bs->entityeventTime[state->number] = g_entities[state->number].eventTime;
+ //if it's an event only entity
+ if (state->eType > ET_EVENTS) {
+ event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS;
+ }
+ else {
+ event = state->event & ~EV_EVENT_BITS;
+ }
+ //
+ switch(event) {
+ //client obituary event
+ case EV_OBITUARY:
+ {
+ int target, attacker, mod;
+
+ target = state->otherEntityNum;
+ attacker = state->otherEntityNum2;
+ mod = state->eventParm;
+ //
+ if (target == bs->client) {
+ bs->botdeathtype = mod;
+ bs->lastkilledby = attacker;
+ //
+ if (target == attacker) bs->botsuicide = qtrue;
+ else bs->botsuicide = qfalse;
+ //
+ bs->num_deaths++;
+ }
+ //else if this client was killed by the bot
+ else if (attacker == bs->client) {
+ bs->enemydeathtype = mod;
+ bs->lastkilledplayer = target;
+ bs->killedenemy_time = trap_AAS_Time();
+ //
+ bs->num_kills++;
+ }
+ else if (attacker == bs->enemy && target == attacker) {
+ bs->enemysuicide = qtrue;
+ }
+ break;
+ }
+
+ case EV_GLOBAL_SOUND:
+ {
+ if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
+ BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
+ break;
+ }
+ else {
+ trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
+ if (!strcmp(buf, "sound/items/poweruprespawn.wav")) {
+ //powerup respawned... go get it
+ BotGoForPowerups(bs);
+ }
+ }
+ break;
+ }
+
+ case EV_TEAM_SOUND:
+ {
+ if (state->eventParm < 0 || state->eventParm > MAX_TEAM_SOUNDS) {
+ BotAI_Print(PRT_ERROR, "EV_TEAM_SOUND: eventParm (%d) out of range\n", state->eventParm);
+ break;
+ }
+
+ if (state->eventParm == RETURN_FLAG_SOUND)
+ {
+ if (state->otherEntityNum == TEAM_RED)
+ {
+ //red flag is returned
+ bs->redflagstatus = 0;
+ bs->flagstatuschanged = qtrue;
+ }
+ else
+ {
+ //blue flag is returned
+ bs->blueflagstatus = 0;
+ bs->flagstatuschanged = qtrue;
+ }
+ }
+ break;
+ }
+
+
+
+ case EV_PLAYER_TELEPORT_IN:
+ {
+ VectorCopy(state->origin, lastteleport_origin);
+ lastteleport_time = trap_AAS_Time();
+ break;
+ }
+ case EV_GENERAL_SOUND:
+ {
+ //if this sound is played on the bot
+ if (state->number == bs->client) {
+ if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
+ BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
+ break;
+ }
+ //check out the sound
+ trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
+ //if falling into a death pit
+ if (!strcmp(buf, "*falling1.wav")) {
+ //if the bot has a personal teleporter
+ if (bs->inventory[INVENTORY_TRANSPORTER] > 0) {
+ //use the holdable item
+ trap_EA_Use(bs->client);
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+/*
+==================
+BotCheckSnapshot
+==================
+*/
+void BotCheckSnapshot(bot_state_t *bs) {
+ int ent;
+ entityState_t state;
+
+ //
+ ent = 0;
+ while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) {
+ //check the entity state for events
+ BotCheckEvents(bs, &state);
+ }
+ //check the player state for events
+ BotAI_GetEntityState(bs->client, &state);
+ //copy the player state events to the entity state
+ state.event = bs->cur_ps.externalEvent;
+ state.eventParm = bs->cur_ps.externalEventParm;
+ //
+ BotCheckEvents(bs, &state);
+}
+
+/*
+==================
+BotCheckAir
+==================
+*/
+void BotCheckAir(bot_state_t *bs) {
+ if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) {
+ if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
+ return;
+ }
+ }
+ bs->lastair_time = trap_AAS_Time();
+}
+
+/*
+==================
+BotDeathmatchAI
+==================
+*/
+void BotDeathmatchAI(bot_state_t *bs, float thinktime) {
+ char gender[144], name[144], buf[144];
+ char userinfo[MAX_INFO_STRING];
+ int i;
+
+ //if the bot has just been setup
+ if (bs->setupcount > 0) {
+ bs->setupcount--;
+ if (bs->setupcount > 0) return;
+ //get the gender characteristic
+ trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender));
+ //set the bot gender
+ trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
+ Info_SetValueForKey(userinfo, "sex", gender);
+ trap_SetUserinfo(bs->client, userinfo);
+ //set the team
+ if ( g_gametype.integer != GT_TOURNAMENT ) {
+ Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team);
+ trap_EA_Command(bs->client, buf);
+ }
+ if ( g_pModSpecialties.integer ) {
+ Com_sprintf(buf, sizeof(buf), "class %s", bs->settings.pclass);
+ trap_EA_Command(bs->client, buf);
+ }
+ //set the chat gender
+ if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
+ else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
+ else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
+ //set the chat name
+ ClientName(bs->client, name, sizeof(name));
+ trap_BotSetChatName(bs->cs, name);
+ //
+ bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
+ bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
+ //
+ bs->setupcount = 0;
+ }
+ //no ideal view set
+ bs->flags &= ~BFL_IDEALVIEWSET;
+ //set the teleport time
+ BotSetTeleportTime(bs);
+ //update some inventory values
+ BotUpdateInventory(bs);
+ //check the console messages
+ BotCheckConsoleMessages(bs);
+ //check out the snapshot
+ BotCheckSnapshot(bs);
+ //check for air
+ BotCheckAir(bs);
+ //if not in the intermission and not in observer mode
+ if (!BotIntermission(bs) && !BotIsObserver(bs)) {
+ //do team AI
+ BotTeamAI(bs);
+ }
+ //if the bot has no ai node
+ if (!bs->ainode) {
+ AIEnter_Seek_LTG(bs);
+ }
+ //if the bot entered the game less than 8 seconds ago
+ if (!bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8) {
+ if (BotChat_EnterGame(bs)) {
+ bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
+ AIEnter_Stand(bs);
+ }
+ bs->entergamechat = qtrue;
+ }
+ //reset the node switches from the previous frame
+ BotResetNodeSwitches();
+ //execute AI nodes
+ for (i = 0; i < MAX_NODESWITCHES; i++) {
+ if (bs->ainode(bs)) break;
+ }
+ //if the bot removed itself :)
+ if (!bs->inuse) return;
+ //if the bot executed too many AI nodes
+ if (i >= MAX_NODESWITCHES) {
+ trap_BotDumpGoalStack(bs->gs);
+ trap_BotDumpAvoidGoals(bs->gs);
+ BotDumpNodeSwitches(bs);
+ ClientName(bs->client, name, sizeof(name));
+ BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES);
+ }
+ //
+ bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
+ bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
+}
+
+/*
+==================
+BotSetupDeathmatchAI
+==================
+*/
+void BotSetupDeathmatchAI(void) {
+ int ent, modelnum;
+ char model[128];
+
+ gametype = trap_Cvar_VariableIntegerValue("g_gametype");
+ maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0);
+ trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0);
+ trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0);
+ trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0);
+ trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0);
+ trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0);
+ //
+ if (gametype == GT_CTF) {
+ if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
+ BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
+ if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
+ BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
+ }
+
+ max_bspmodelindex = 0;
+ for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
+ if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue;
+ if (model[0] == '*') {
+ modelnum = atoi(model+1);
+ if (modelnum > max_bspmodelindex)
+ max_bspmodelindex = modelnum;
+ }
+ }
+ //initialize the waypoint heap
+ BotInitWaypoints();
+}
+
+/*
+==================
+BotShutdownDeathmatchAI
+==================
+*/
+void BotShutdownDeathmatchAI(void) {
+}
+
diff --git a/game/ai_dmq3.h b/game/ai_dmq3.h
new file mode 100644
index 0000000..4769b4a
--- /dev/null
+++ b/game/ai_dmq3.h
@@ -0,0 +1,134 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_dmq3.h
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_dmq3.h $
+ * $Author: Dkramer $
+ * $Revision: 2 $
+ * $Modtime: 6/21/00 4:35p $
+ * $Date: 6/27/00 10:06a $
+ *
+ *****************************************************************************/
+
+//!setup the deathmatch AI
+void BotSetupDeathmatchAI(void);
+//!shutdown the deathmatch AI
+void BotShutdownDeathmatchAI(void);
+//!let the bot live within it's deathmatch AI net
+void BotDeathmatchAI(bot_state_t *bs, float thinktime);
+//!free waypoints
+void BotFreeWaypoints(bot_waypoint_t *wp);
+//!choose a weapon
+void BotChooseWeapon(bot_state_t *bs);
+//!setup movement stuff
+void BotSetupForMovement(bot_state_t *bs);
+//!update the inventory
+void BotUpdateInventory(bot_state_t *bs);
+//!update the inventory during battle
+void BotUpdateBattleInventory(bot_state_t *bs, int enemy);
+//! should I detonate the detpack now
+qboolean BotShouldDetonateDetPack(bot_state_t *bs);
+//!use holdable items during battle
+void BotBattleUseItems(bot_state_t *bs);
+//!return true if the bot is dead
+qboolean BotIsDead(bot_state_t *bs);
+//!returns true if the bot is in observer mode
+qboolean BotIsObserver(bot_state_t *bs);
+//!returns true if the bot is in the intermission
+qboolean BotIntermission(bot_state_t *bs);
+//!returns true if the bot is in lava or slime
+qboolean BotInLavaOrSlime(bot_state_t *bs);
+//!returns true if the entity is dead
+qboolean EntityIsDead(aas_entityinfo_t *entinfo);
+//!returns true if the entity is invisible
+qboolean EntityIsInvisible(aas_entityinfo_t *entinfo);
+//!returns true if the entity is shooting
+qboolean EntityIsShooting(aas_entityinfo_t *entinfo);
+//!returns the name of the client
+char *ClientName(int client, char *name, int size);
+//!returns an simplyfied client name
+char *EasyClientName(int client, char *name, int size);
+//!returns the skin used by the client
+char *ClientSkin(int client, char *skin, int size);
+//!returns the aggression of the bot in the range [0, 100]
+float BotAggression(bot_state_t *bs);
+//!returns true if the bot wants to retreat
+int BotWantsToRetreat(bot_state_t *bs);
+//!returns true if the bot wants to chase
+int BotWantsToChase(bot_state_t *bs);
+//!returns true if the bot wants to help
+int BotWantsToHelp(bot_state_t *bs);
+//!returns true if the bot can and wants to rocketjump
+int BotCanAndWantsToRocketJump(bot_state_t *bs);
+//!returns true if the bot wants to and goes camping
+int BotWantsToCamp(bot_state_t *bs);
+//!the bot will perform attack movements
+bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl);
+//!returns true if the bot and the entity are in the same team
+int BotSameTeam(bot_state_t *bs, int entnum);
+//!returns true if teamplay is on
+int TeamPlayIsOn(void);
+//!returns visible team mate flag carrier if available
+int BotTeamFlagCarrierVisible(bot_state_t *bs);
+//!returns true and sets the .enemy field when an enemy is found
+int BotFindEnemy(bot_state_t *bs, int curenemy);
+//!returns a roam goal
+void BotRoamGoal(bot_state_t *bs, vec3_t goal);
+//!returns entity visibility in the range [0, 1]
+float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent);
+//!the bot will aim at the current enemy
+void BotAimAtEnemy(bot_state_t *bs);
+//!check if the bot should attack
+void BotCheckAttack(bot_state_t *bs);
+//!AI when the bot is blocked
+void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate);
+//!returns the CTF team the bot is in
+int BotCTFTeam(bot_state_t *bs);
+//!returns the flag the bot is carrying (CTFFLAG_?)
+int BotCTFCarryingFlag(bot_state_t *bs);
+//!set ctf goals (defend base, get enemy flag) during seek
+void BotCTFSeekGoals(bot_state_t *bs);
+//!set ctf goals (defend base, get enemy flag) during retreat
+void BotCTFRetreatGoals(bot_state_t *bs);
+//!create a new waypoint
+bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum);
+//!find a waypoint with the given name
+bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name);
+//!strstr but case insensitive
+char *stristr(char *str, char *charset);
+//!returns the number of the client with the given name
+int ClientFromName(char *name);
+//
+int BotPointAreaNum(vec3_t origin);
+//
+void BotMapScripts(bot_state_t *bs);
+
+//ctf flags
+#define CTF_FLAG_NONE 0
+#define CTF_FLAG_RED 1
+#define CTF_FLAG_BLUE 2
+//CTF skins
+#define CTF_SKIN_REDTEAM "red"
+#define CTF_SKIN_BLUETEAM "blue"
+//CTF teams
+#define CTF_TEAM_NONE 0
+#define CTF_TEAM_RED 1
+#define CTF_TEAM_BLUE 2
+
+extern int gametype; //!allsolid = trace.allsolid;
+ bsptrace->startsolid = trace.startsolid;
+ bsptrace->fraction = trace.fraction;
+ VectorCopy(trace.endpos, bsptrace->endpos);
+ bsptrace->plane.dist = trace.plane.dist;
+ VectorCopy(trace.plane.normal, bsptrace->plane.normal);
+ bsptrace->plane.signbits = trace.plane.signbits;
+ bsptrace->plane.type = trace.plane.type;
+ bsptrace->surface.value = trace.surfaceFlags;
+ bsptrace->ent = trace.entityNum;
+ bsptrace->exp_dist = 0;
+ bsptrace->sidenum = 0;
+ bsptrace->contents = 0;
+}
+
+/*
+==================
+BotAI_GetClientState
+==================
+*/
+int BotAI_GetClientState( int clientNum, playerState_t *state ) {
+ gentity_t *ent;
+
+ ent = &g_entities[clientNum];
+ if ( !ent->inuse ) {
+ return qfalse;
+ }
+ if ( !ent->client ) {
+ return qfalse;
+ }
+
+ memcpy( state, &ent->client->ps, sizeof(playerState_t) );
+ return qtrue;
+}
+
+/*
+==================
+BotAI_GetEntityState
+==================
+*/
+int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
+ gentity_t *ent;
+
+ ent = &g_entities[entityNum];
+ memset( state, 0, sizeof(entityState_t) );
+ if (!ent->inuse) return qfalse;
+ if (!ent->r.linked) return qfalse;
+ if (ent->r.svFlags & SVF_NOCLIENT) return qfalse;
+ memcpy( state, &ent->s, sizeof(entityState_t) );
+ return qtrue;
+}
+
+/*
+==================
+BotAI_GetSnapshotEntity
+==================
+*/
+int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) {
+ int entNum;
+
+ entNum = trap_BotGetSnapshotEntity( clientNum, sequence );
+ if ( entNum == -1 ) {
+ memset(state, 0, sizeof(entityState_t));
+ return -1;
+ }
+
+ BotAI_GetEntityState( entNum, state );
+
+ return sequence + 1;
+}
+
+/*
+==================
+BotAI_BotInitialChat
+==================
+*/
+void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) {
+ int i, mcontext;
+ va_list ap;
+ char *p;
+ char *vars[MAX_MATCHVARIABLES];
+
+ memset(vars, 0, sizeof(vars));
+ va_start(ap, type);
+ p = va_arg(ap, char *);
+ for (i = 0; i < MAX_MATCHVARIABLES; i++) {
+ if( !p ) {
+ break;
+ }
+ vars[i] = p;
+ p = va_arg(ap, char *);
+ }
+ va_end(ap);
+
+ mcontext = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES;
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) mcontext |= CONTEXT_CTFREDTEAM;
+ else mcontext |= CONTEXT_CTFBLUETEAM;
+
+ trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] );
+}
+
+
+/*
+==================
+BotTestSolid
+==================
+*/
+void BotTestSolid(vec3_t origin) {
+ int areanum;
+
+ if( !bot_setupComplete ) {
+ return;
+ }
+
+ trap_Cvar_Update(&bot_testsolid);
+ if (bot_testsolid.integer) {
+ if (!trap_AAS_Initialized()) return;
+ areanum = BotPointAreaNum(origin);
+ if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area");
+ else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area");
+ }
+}
+
+/*
+==================
+BotReportStatus
+==================
+*/
+void BotReportStatus(bot_state_t *bs) {
+ char goalname[MAX_MESSAGE_SIZE];
+ char netname[MAX_MESSAGE_SIZE];
+ char *leader, *flagstatus;
+ //
+ ClientName(bs->client, netname, sizeof(netname));
+ if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L";
+ else leader = " ";
+ if (BotCTFCarryingFlag(bs)) {
+ if (BotCTFTeam(bs) == TEAM_RED) flagstatus = S_COLOR_RED"F";
+ else flagstatus = S_COLOR_BLUE"F";
+ }
+ else {
+ flagstatus = " ";
+ }
+ switch(bs->ltgtype) {
+ case LTG_TEAMHELP:
+ {
+ EasyClientName(bs->teammate, goalname, sizeof(goalname));
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname);
+ break;
+ }
+ case LTG_TEAMACCOMPANY:
+ {
+ EasyClientName(bs->teammate, goalname, sizeof(goalname));
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname);
+ break;
+ }
+ case LTG_DEFENDKEYAREA:
+ {
+ trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname);
+ break;
+ }
+ case LTG_GETITEM:
+ {
+ trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname);
+ break;
+ }
+ case LTG_KILL:
+ {
+ ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname));
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname);
+ break;
+ }
+ case LTG_CAMP:
+ case LTG_CAMPORDER:
+ {
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus);
+ break;
+ }
+ case LTG_PATROL:
+ {
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus);
+ break;
+ }
+ case LTG_GETFLAG:
+ {
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus);
+ break;
+ }
+ case LTG_RUSHBASE:
+ {
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus);
+ break;
+ }
+ case LTG_RETURNFLAG:
+ {
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus);
+ break;
+ }
+ default:
+ {
+ BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus);
+ break;
+ }
+ }
+}
+
+/*
+==================
+BotTeamplayReport
+==================
+*/
+void BotTeamplayReport(void) {
+ int i;
+ char buf[MAX_INFO_STRING];
+
+ BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n");
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ //
+ if ( !botstates[i] || !botstates[i]->inuse ) continue;
+ //
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) {
+ BotReportStatus(botstates[i]);
+ }
+ }
+ BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n");
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ //
+ if ( !botstates[i] || !botstates[i]->inuse ) continue;
+ //
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) {
+ BotReportStatus(botstates[i]);
+ }
+ }
+}
+
+/*
+==============
+BotInterbreedBots
+==============
+*/
+void BotInterbreedBots(void) {
+ float ranks[MAX_CLIENTS];
+ int parent1, parent2, child;
+ int i;
+
+ // get rankings for all the bots
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if ( botstates[i] && botstates[i]->inuse ) {
+ ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths;
+ }
+ else {
+ ranks[i] = -1;
+ }
+ }
+
+ if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) {
+ trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs);
+ trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1);
+ }
+ // reset the kills and deaths
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if (botstates[i] && botstates[i]->inuse) {
+ botstates[i]->num_kills = 0;
+ botstates[i]->num_deaths = 0;
+ }
+ }
+}
+
+/*
+==============
+BotWriteInterbreeded
+==============
+*/
+void BotWriteInterbreeded(char *filename) {
+ float rank, bestrank;
+ int i, bestbot;
+
+ bestrank = 0;
+ bestbot = -1;
+ // get the best bot
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if ( botstates[i] && botstates[i]->inuse ) {
+ rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths;
+ }
+ else {
+ rank = -1;
+ }
+ if (rank > bestrank) {
+ bestrank = rank;
+ bestbot = i;
+ }
+ }
+ if (bestbot >= 0) {
+ //write out the new goal fuzzy logic
+ trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename);
+ }
+}
+
+/*
+==============
+BotInterbreedEndMatch
+
+add link back into ExitLevel?
+==============
+*/
+void BotInterbreedEndMatch(void) {
+
+ if (!bot_interbreed) return;
+ bot_interbreedmatchcount++;
+ if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) {
+ bot_interbreedmatchcount = 0;
+ //
+ trap_Cvar_Update(&bot_interbreedwrite);
+ if (strlen(bot_interbreedwrite.string)) {
+ BotWriteInterbreeded(bot_interbreedwrite.string);
+ trap_Cvar_Set("bot_interbreedwrite", "");
+ }
+ BotInterbreedBots();
+ }
+}
+
+/*
+==============
+BotInterbreeding
+==============
+*/
+void BotInterbreeding(void) {
+ int i;
+
+ trap_Cvar_Update(&bot_interbreedchar);
+ if (!strlen(bot_interbreedchar.string)) return;
+ //make sure we are in tournament mode
+ if (gametype != GT_TOURNAMENT) {
+ trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT));
+ ExitLevel();
+ return;
+ }
+ //shutdown all the bots
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if (botstates[i] && botstates[i]->inuse) {
+ BotAIShutdownClient(botstates[i]->client);
+ }
+ }
+ //make sure all item weight configs are reloaded and Not shared
+ trap_BotLibVarSet("bot_reloadcharacters", "1");
+ //add a number of bots using the desired bot character
+ for (i = 0; i < bot_interbreedbots.integer; i++) {
+ trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n",
+ bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) );
+ }
+ //
+ trap_Cvar_Set("bot_interbreedchar", "");
+ bot_interbreed = qtrue;
+}
+
+/*
+==============
+BotEntityInfo
+==============
+*/
+void BotEntityInfo(int entnum, aas_entityinfo_t *info) {
+ trap_AAS_EntityInfo(entnum, info);
+}
+
+/*
+==============
+NumBots
+==============
+*/
+int NumBots(void) {
+ return numbots;
+}
+
+/*
+==============
+BotTeamLeader
+==============
+*/
+int BotTeamLeader(bot_state_t *bs) {
+ int leader;
+
+ leader = ClientFromName(bs->teamleader);
+ if (leader < 0) return qfalse;
+ if (!botstates[leader] || !botstates[leader]->inuse) return qfalse;
+ return qtrue;
+}
+
+/*
+==============
+AngleDifference
+==============
+*/
+float AngleDifference(float ang1, float ang2) {
+ float diff;
+
+ diff = ang1 - ang2;
+ if (ang1 > ang2) {
+ if (diff > 180.0) diff -= 360.0;
+ }
+ else {
+ if (diff < -180.0) diff += 360.0;
+ }
+ return diff;
+}
+
+/*
+==============
+BotChangeViewAngle
+==============
+*/
+float BotChangeViewAngle(float angle, float ideal_angle, float speed) {
+ float move;
+
+ angle = AngleMod(angle);
+ ideal_angle = AngleMod(ideal_angle);
+ if (angle == ideal_angle) return angle;
+ move = ideal_angle - angle;
+ if (ideal_angle > angle) {
+ if (move > 180.0) move -= 360.0;
+ }
+ else {
+ if (move < -180.0) move += 360.0;
+ }
+ if (move > 0) {
+ if (move > speed) move = speed;
+ }
+ else {
+ if (move < -speed) move = -speed;
+ }
+ return AngleMod(angle + move);
+}
+
+/*
+==============
+BotChangeViewAngles
+==============
+*/
+void BotChangeViewAngles(bot_state_t *bs, float thinktime) {
+ float diff, factor, maxchange, anglespeed;
+ int i;
+
+ if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
+
+ factor = 1;
+ maxchange = 1800;
+
+ maxchange *= thinktime;
+ for (i = 0; i < 2; i++)
+ {
+ //smooth slowdown view model
+ diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]));
+ anglespeed = diff * factor;
+ if (anglespeed > maxchange)
+ {
+ anglespeed = maxchange;
+ }
+ bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], bs->ideal_viewangles[i], anglespeed);
+ //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);`
+ //bs->viewangles[i] = bs->ideal_viewangles[i];
+ }
+ //bs->viewangles[PITCH] = 0;
+ if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360;
+ //elementary action: view
+ trap_EA_View(bs->client, bs->viewangles);
+}
+
+/*
+==============
+BotInputToUserCommand
+==============
+*/
+void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) {
+ vec3_t angles, forward, right;
+ short temp;
+ int j;
+
+ //clear the whole structure
+ memset(ucmd, 0, sizeof(usercmd_t));
+ //
+ //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
+ //the duration for the user command in milli seconds
+ ucmd->serverTime = time;
+ //
+ if (bi->actionflags & ACTION_DELAYEDJUMP) {
+ bi->actionflags |= ACTION_JUMP;
+ bi->actionflags &= ~ACTION_DELAYEDJUMP;
+ }
+ //set the buttons
+ if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK;
+ if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK;
+ if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
+ if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
+ if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
+ if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING;
+ if (bi->actionflags & ACTION_ALT_ATTACK)
+ {
+ ucmd->buttons |= BUTTON_ALT_ATTACK;
+ }
+
+ ucmd->weapon = bi->weapon;
+ //set the view angles
+ //NOTE: the ucmd->angles are the angles WITHOUT the delta angles
+ ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]);
+ ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]);
+ ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]);
+ //subtract the delta angles
+ for (j = 0; j < 3; j++) {
+ temp = ucmd->angles[j] - delta_angles[j];
+ /*NOTE: disabled because temp should be mod first
+ if ( j == PITCH ) {
+ // don't let the player look up or down more than 90 degrees
+ if ( temp > 16000 ) temp = 16000;
+ else if ( temp < -16000 ) temp = -16000;
+ }
+ */
+ ucmd->angles[j] = temp;
+ }
+ //NOTE: movement is relative to the REAL view angles
+ //get the horizontal forward and right vector
+ //get the pitch in the range [-180, 180]
+ if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH];
+ else angles[PITCH] = 0;
+ angles[YAW] = bi->viewangles[YAW];
+ angles[ROLL] = 0;
+ AngleVectors(angles, forward, right, NULL);
+ //bot input speed is in the range [0, 400]
+ bi->speed = bi->speed * 127 / 400;
+ //set the view independent movement
+ ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed;
+ ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed;
+ ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
+ //normal keyboard movement
+ if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127;
+ if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127;
+ if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127;
+ if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127;
+ //jump/moveup
+ if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127;
+ //crouch/movedown
+ if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127;
+ //
+ //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
+ //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
+}
+
+/*
+==============
+BotUpdateInput
+==============
+*/
+void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) {
+ bot_input_t bi;
+ int j;
+
+ //add the delta angles to the bot's current view angles
+ for (j = 0; j < 3; j++) {
+ bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
+ }
+ //change the bot view angles
+ BotChangeViewAngles(bs, (float) elapsed_time / 1000);
+ //retrieve the bot input
+ trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
+ //respawn hack
+ if (bi.actionflags & ACTION_RESPAWN) {
+ if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK);
+ }
+ //convert the bot input to a usercmd
+ BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time);
+ //subtract the delta angles
+ for (j = 0; j < 3; j++) {
+ bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
+ }
+}
+
+/*
+==============
+BotAIRegularUpdate
+==============
+*/
+void BotAIRegularUpdate(void) {
+ if (regularupdate_time < trap_AAS_Time()) {
+ trap_BotUpdateEntityItems();
+ regularupdate_time = trap_AAS_Time() + 0.3;
+ }
+}
+
+/*
+==============
+BotAI
+==============
+*/
+int BotAI(int client, float thinktime) {
+ bot_state_t *bs;
+ char buf[1024], *args;
+ int j;
+
+ trap_EA_ResetInput(client);
+ //
+ bs = botstates[client];
+ if (!bs || !bs->inuse) {
+ BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client);
+ return qfalse;
+ }
+
+ //retrieve the current client state
+ BotAI_GetClientState( client, &bs->cur_ps );
+
+ //retrieve any waiting console messages
+ while( trap_BotGetConsoleMessage(client, buf, sizeof(buf)) ) {
+ //have buf point to the command and args to the command arguments
+ args = strchr( buf, ' ');
+ if (!args) continue;
+ *args++ = '\0';
+
+ //remove color espace sequences from the arguments
+ Q_CleanStr( args );
+
+ if (!Q_stricmp(buf, "cp "))
+ { /*CenterPrintf*/ }
+ else if (!Q_stricmp(buf, "cs"))
+ { /*ConfigStringModified*/ }
+ else if (!Q_stricmp(buf, "print")) {
+ //remove first and last quote from the chat message
+ memmove(args, args+1, strlen(args));
+ args[strlen(args)-1] = '\0';
+ trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args);
+ }
+ else if (!Q_stricmp(buf, "chat")) {
+ //remove first and last quote from the chat message
+ memmove(args, args+1, strlen(args));
+ args[strlen(args)-1] = '\0';
+ trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args);
+ }
+ else if (!Q_stricmp(buf, "tchat")) {
+ //remove first and last quote from the chat message
+ memmove(args, args+1, strlen(args));
+ args[strlen(args)-1] = '\0';
+ trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args);
+ }
+ else if (!Q_stricmp(buf, "scores"))
+ { /*FIXME: parse scores?*/ }
+ else if (!Q_stricmp(buf, "clientLevelShot"))
+ { /*ignore*/ }
+ }
+ //add the delta angles to the bot's current view angles
+ for (j = 0; j < 3; j++) {
+ bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
+ }
+ //increase the local time of the bot
+ bs->ltime += thinktime;
+ //
+ bs->thinktime = thinktime;
+ //origin of the bot
+ VectorCopy(bs->cur_ps.origin, bs->origin);
+ //eye coordinates of the bot
+ VectorCopy(bs->cur_ps.origin, bs->eye);
+ bs->eye[2] += bs->cur_ps.viewheight;
+ //get the area the bot is in
+ bs->areanum = BotPointAreaNum(bs->origin);
+ //the real AI
+ BotDeathmatchAI(bs, thinktime);
+ //set the weapon selection every AI frame
+ trap_EA_SelectWeapon(bs->client, bs->weaponnum);
+ //subtract the delta angles
+ for (j = 0; j < 3; j++) {
+ bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
+ }
+ //everything was ok
+ return qtrue;
+}
+
+/*
+==================
+BotScheduleBotThink
+==================
+*/
+void BotScheduleBotThink(void) {
+ int i, botnum;
+
+ botnum = 0;
+
+ for( i = 0; i < MAX_CLIENTS; i++ ) {
+ if( !botstates[i] || !botstates[i]->inuse ) {
+ continue;
+ }
+ //initialize the bot think residual time
+ botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots;
+ botnum++;
+ }
+}
+
+/*
+==============
+BotAISetupClient
+==============
+*/
+int BotAISetupClient(int client, struct bot_settings_s *settings) {
+ char filename[AI_MAX_PATH], name[AI_MAX_PATH], gender[AI_MAX_PATH];
+ bot_state_t *bs;
+ int errnum;
+
+ if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t));
+ bs = botstates[client];
+
+ if(!bs) return qfalse;
+
+ if (bs && bs->inuse) {
+ BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client);
+ return qfalse;
+ }
+
+ if (!trap_AAS_Initialized()) {
+ BotAI_Print(PRT_FATAL, "AAS not initialized\n");
+ return qfalse;
+ }
+
+ //load the bot character
+ bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill);
+ if (!bs->character) {
+ BotAI_Print(PRT_FATAL, "couldn't load skill %d from %s\n", settings->skill, settings->characterfile);
+ return qfalse;
+ }
+ //copy the settings
+ memcpy(&bs->settings, settings, sizeof(bot_settings_t));
+ //allocate a goal state
+ bs->gs = trap_BotAllocGoalState(client);
+ //load the item weights
+ trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, AI_MAX_PATH);
+ errnum = trap_BotLoadItemWeights(bs->gs, filename);
+ if (errnum != BLERR_NOERROR) {
+ trap_BotFreeGoalState(bs->gs);
+ return qfalse;
+ }
+ //allocate a weapon state
+ bs->ws = trap_BotAllocWeaponState();
+ //load the weapon weights
+ trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, AI_MAX_PATH);
+ errnum = trap_BotLoadWeaponWeights(bs->ws, filename);
+ if (errnum != BLERR_NOERROR) {
+ trap_BotFreeGoalState(bs->gs);
+ trap_BotFreeWeaponState(bs->ws);
+ return qfalse;
+ }
+ //allocate a chat state
+ bs->cs = trap_BotAllocChatState();
+ //load the chat file
+ trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, AI_MAX_PATH);
+ trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, AI_MAX_PATH);
+ errnum = trap_BotLoadChatFile(bs->cs, filename, name);
+ if (errnum != BLERR_NOERROR) {
+ trap_BotFreeChatState(bs->cs);
+ trap_BotFreeGoalState(bs->gs);
+ trap_BotFreeWeaponState(bs->ws);
+ return qfalse;
+ }
+ //get the gender characteristic
+ trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, AI_MAX_PATH);
+ //set the chat gender
+ if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
+ else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
+ else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
+
+ bs->inuse = qtrue;
+ bs->client = client;
+ bs->entitynum = client;
+ bs->setupcount = 4;
+ bs->entergame_time = trap_AAS_Time();
+ bs->ms = trap_BotAllocMoveState();
+ bs->walker = 0;
+ numbots++;
+
+ if (trap_Cvar_VariableIntegerValue("bot_testichat")) {
+ trap_BotLibVarSet("bot_testichat", "1");
+ BotChatTest(bs);
+ }
+ //NOTE: reschedule the bot thinking
+ BotScheduleBotThink();
+ //if interbreeding start with a mutation
+ if (bot_interbreed) {
+ trap_BotMutateGoalFuzzyLogic(bs->gs, 1);
+ }
+ //bot has been setup succesfully
+ return qtrue;
+}
+
+/*
+==============
+BotAIShutdownClient
+==============
+*/
+int BotAIShutdownClient(int client) {
+ bot_state_t *bs;
+
+ bs = botstates[client];
+ if (!bs || !bs->inuse) {
+ //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client);
+ return qfalse;
+ }
+
+ if (BotChat_ExitGame(bs)) {
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
+ }
+
+ trap_BotFreeMoveState(bs->ms);
+ //free the goal state`
+ trap_BotFreeGoalState(bs->gs);
+ //free the chat file
+ trap_BotFreeChatState(bs->cs);
+ //free the weapon weights
+ trap_BotFreeWeaponState(bs->ws);
+ //free the bot character
+ trap_BotFreeCharacter(bs->character);
+ //
+ BotFreeWaypoints(bs->checkpoints);
+ BotFreeWaypoints(bs->patrolpoints);
+ //clear the bot state
+ memset(bs, 0, sizeof(bot_state_t));
+ //set the inuse flag to qfalse
+ bs->inuse = qfalse;
+ //there's one bot less
+ numbots--;
+ //everything went ok
+ return qtrue;
+}
+
+/*
+==============
+BotResetState
+
+called when a bot enters the intermission or observer mode and
+when the level is changed
+==============
+*/
+void BotResetState(bot_state_t *bs) {
+ int client, entitynum, inuse;
+ int movestate, goalstate, chatstate, weaponstate;
+ bot_settings_t settings;
+ int character;
+ playerState_t ps; //current player state
+ float entergame_time;
+
+ //save some things that should not be reset here
+ memcpy(&settings, &bs->settings, sizeof(bot_settings_t));
+ memcpy(&ps, &bs->cur_ps, sizeof(playerState_t));
+ inuse = bs->inuse;
+ client = bs->client;
+ entitynum = bs->entitynum;
+ character = bs->character;
+ movestate = bs->ms;
+ goalstate = bs->gs;
+ chatstate = bs->cs;
+ weaponstate = bs->ws;
+ entergame_time = bs->entergame_time;
+ //free checkpoints and patrol points
+ BotFreeWaypoints(bs->checkpoints);
+ BotFreeWaypoints(bs->patrolpoints);
+ //reset the whole state
+ memset(bs, 0, sizeof(bot_state_t));
+ //copy back some state stuff that should not be reset
+ bs->ms = movestate;
+ bs->gs = goalstate;
+ bs->cs = chatstate;
+ bs->ws = weaponstate;
+ memcpy(&bs->cur_ps, &ps, sizeof(playerState_t));
+ memcpy(&bs->settings, &settings, sizeof(bot_settings_t));
+ bs->inuse = inuse;
+ bs->client = client;
+ bs->entitynum = entitynum;
+ bs->character = character;
+ bs->entergame_time = entergame_time;
+ //reset several states
+ if (bs->ms) trap_BotResetMoveState(bs->ms);
+ if (bs->gs) trap_BotResetGoalState(bs->gs);
+ if (bs->ws) trap_BotResetWeaponState(bs->ws);
+ if (bs->gs) trap_BotResetAvoidGoals(bs->gs);
+ if (bs->ms) trap_BotResetAvoidReach(bs->ms);
+}
+
+/*
+==============
+BotAILoadMap
+==============
+*/
+int BotAILoadMap( int restart ) {
+ int i;
+ vmCvar_t mapname;
+
+ if (!restart) {
+ trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
+ trap_BotLibLoadMap( mapname.string );
+ }
+
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if (botstates[i] && botstates[i]->inuse) {
+ BotResetState( botstates[i] );
+ botstates[i]->setupcount = 4;
+ }
+ }
+
+ BotSetupDeathmatchAI();
+
+ return qtrue;
+}
+
+/*
+==================
+BotAIStartFrame
+==================
+*/
+int BotAIStartFrame(int time) {
+ int i;
+ gentity_t *ent;
+ bot_entitystate_t state;
+ int elapsed_time, thinktime;
+ static int local_time;
+ static int botlib_residual;
+ static int lastbotthink_time;
+
+ G_CheckBotSpawn();
+
+ trap_Cvar_Update(&bot_rocketjump);
+ trap_Cvar_Update(&bot_grapple);
+ trap_Cvar_Update(&bot_fastchat);
+ trap_Cvar_Update(&bot_nochat);
+ trap_Cvar_Update(&bot_testrchat);
+ trap_Cvar_Update(&bot_thinktime);
+ trap_Cvar_Update(&bot_memorydump);
+ trap_Cvar_Update(&bot_pause);
+ trap_Cvar_Update(&bot_report);
+
+ if (bot_report.integer) {
+ BotTeamplayReport();
+ trap_Cvar_Set("bot_report", "0");
+ }
+
+ if (bot_pause.integer) {
+ // execute bot user commands every frame
+ for( i = 0; i < MAX_CLIENTS; i++ ) {
+ if( !botstates[i] || !botstates[i]->inuse ) {
+ continue;
+ }
+ if( g_entities[i].client->pers.connected != CON_CONNECTED ) {
+ continue;
+ }
+ botstates[i]->lastucmd.forwardmove = 0;
+ botstates[i]->lastucmd.rightmove = 0;
+ botstates[i]->lastucmd.upmove = 0;
+ botstates[i]->lastucmd.buttons = 0;
+ botstates[i]->lastucmd.serverTime = time;
+ trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd);
+ }
+ return qtrue;
+ }
+
+ if (bot_memorydump.integer) {
+ trap_BotLibVarSet("memorydump", "1");
+ trap_Cvar_Set("bot_memorydump", "0");
+ }
+ //check if bot interbreeding is activated
+ BotInterbreeding();
+ //cap the bot think time
+ if (bot_thinktime.integer > 200) {
+ trap_Cvar_Set("bot_thinktime", "200");
+ }
+ //if the bot think time changed we should reschedule the bots
+ if (bot_thinktime.integer != lastbotthink_time) {
+ lastbotthink_time = bot_thinktime.integer;
+ BotScheduleBotThink();
+ }
+
+ elapsed_time = time - local_time;
+ local_time = time;
+
+ botlib_residual += elapsed_time;
+
+ if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time;
+ else thinktime = bot_thinktime.integer;
+
+ // update the bot library
+ if ( botlib_residual >= thinktime ) {
+ botlib_residual -= thinktime;
+
+ trap_BotLibStartFrame((float) time / 1000);
+
+ if (!trap_AAS_Initialized()) return qfalse;
+
+ //update entities in the botlib
+ for (i = 0; i < MAX_GENTITIES; i++) {
+ ent = &g_entities[i];
+ if (!ent->inuse) {
+ trap_BotLibUpdateEntity(i, NULL);
+ continue;
+ }
+ if (!ent->r.linked) {
+ trap_BotLibUpdateEntity(i, NULL);
+ continue;
+ }
+ if (ent->r.svFlags & SVF_NOCLIENT) {
+ trap_BotLibUpdateEntity(i, NULL);
+ continue;
+ }
+ // do not update missiles
+ if (ent->s.eType == ET_MISSILE) {
+ trap_BotLibUpdateEntity(i, NULL);
+ continue;
+ }
+ // do not update event only entities
+ if (ent->s.eType > ET_EVENTS) {
+ trap_BotLibUpdateEntity(i, NULL);
+ continue;
+ }
+ //
+ memset(&state, 0, sizeof(bot_entitystate_t));
+ //
+ VectorCopy(ent->r.currentOrigin, state.origin);
+ if (i < MAX_CLIENTS) {
+ VectorCopy(ent->s.apos.trBase, state.angles);
+ } else {
+ VectorCopy(ent->r.currentAngles, state.angles);
+ }
+ VectorCopy(ent->s.origin2, state.old_origin);
+ VectorCopy(ent->r.mins, state.mins);
+ VectorCopy(ent->r.maxs, state.maxs);
+ state.type = ent->s.eType;
+ state.flags = ent->s.eFlags;
+ if (ent->r.bmodel) state.solid = SOLID_BSP;
+ else state.solid = SOLID_BBOX;
+ state.groundent = ent->s.groundEntityNum;
+ state.modelindex = ent->s.modelindex;
+ state.modelindex2 = ent->s.modelindex2;
+ state.frame = ent->s.frame;
+ state.event = ent->s.event;
+ state.eventParm = ent->s.eventParm;
+ state.powerups = ent->s.powerups;
+ state.legsAnim = ent->s.legsAnim;
+ state.torsoAnim = ent->s.torsoAnim;
+ state.weapon = ent->s.weapon;
+ //
+ trap_BotLibUpdateEntity(i, &state);
+ }
+
+ BotAIRegularUpdate();
+ }
+
+ // execute scheduled bot AI
+ for( i = 0; i < MAX_CLIENTS; i++ ) {
+ if( !botstates[i] || !botstates[i]->inuse ) {
+ continue;
+ }
+ //
+ botstates[i]->botthink_residual += elapsed_time;
+ //
+ if ( botstates[i]->botthink_residual >= thinktime ) {
+ botstates[i]->botthink_residual -= thinktime;
+
+ if (!trap_AAS_Initialized()) return qfalse;
+
+ if (g_entities[i].client->pers.connected == CON_CONNECTED) {
+ BotAI(i, (float) thinktime / 1000);
+ }
+ }
+ }
+
+
+ // execute bot user commands every frame
+ for( i = 0; i < MAX_CLIENTS; i++ ) {
+ if( !botstates[i] || !botstates[i]->inuse ) {
+ continue;
+ }
+ if( g_entities[i].client->pers.connected != CON_CONNECTED ) {
+ continue;
+ }
+
+ BotUpdateInput(botstates[i], time, elapsed_time);
+ trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd);
+ }
+
+ return qtrue;
+}
+
+/*
+==============
+BotInitLibrary
+==============
+*/
+int BotInitLibrary(void) {
+ int gt;
+ char buf[144];
+
+ //set the maxclients and maxentities library variables before calling BotSetupLibrary
+ trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf));
+ if (!strlen(buf)) strcpy(buf, "8");
+ trap_BotLibVarSet("maxclients", buf);
+ Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES);
+ trap_BotLibVarSet("maxentities", buf);
+ //bsp checksum
+ trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf);
+ //maximum number of aas links
+ trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf);
+ //maximum number of items in a level
+ trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf);
+ //game type
+ gt = trap_Cvar_VariableIntegerValue("g_gametype");
+ if( gt == GT_SINGLE_PLAYER ) {
+ gt = AIGT_SINGLE_PLAYER;
+ }
+ else if( gt >= GT_TEAM ) {
+ gt = AIGT_TEAM;
+ }
+ else {
+ gt = AIGT_OTHER;
+ }
+ trap_BotLibVarSet("ai_gametype", va("%i", gt));
+ //bot developer mode and log file
+ trap_Cvar_VariableStringBuffer("bot_developer", buf, sizeof(buf));
+ if (!strlen(buf)) strcpy(buf, "0");
+ trap_BotLibVarSet("bot_developer", buf);
+ trap_BotLibVarSet("log", buf);
+ //no chatting
+ trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("nochat", "0");
+ //visualize jump pads
+ trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf);
+ //forced clustering calculations
+ trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf);
+ //forced reachability calculations
+ trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf);
+ //force writing of AAS to file
+ trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf);
+ //no AAS optimization
+ trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf);
+ //reload instead of cache bot character files
+ trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf));
+ if (!strlen(buf)) strcpy(buf, "0");
+ trap_BotLibVarSet("bot_reloadcharacters", buf);
+ //base directory
+ trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("basedir", buf);
+ //game directory
+ trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("gamedir", buf);
+ //cd directory
+ trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf));
+ if (strlen(buf)) trap_BotLibVarSet("cddir", buf);
+ //setup the bot library
+ return trap_BotLibSetup();
+}
+
+/*
+==============
+BotAISetup
+==============
+*/
+int BotAISetup( int restart ) {
+ int errnum;
+
+#ifdef RANDOMIZE
+ srand((unsigned)time(NULL));
+#endif //RANDOMIZE
+
+ bot_setupComplete = qtrue;
+
+ trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT);
+ trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT);
+ trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT);
+ trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT);
+ trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT);
+ trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0);
+ trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0);
+ trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0);
+ trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0);
+
+ //if the game is restarted for a tournament
+ if (restart) {
+ return qtrue;
+ }
+
+ //initialize the bot states
+ memset( botstates, 0, sizeof(botstates) );
+
+ errnum = BotInitLibrary();
+ if (errnum != BLERR_NOERROR) return qfalse;
+ return qtrue;
+}
+
+/*
+==============
+BotAIShutdown
+==============
+*/
+int BotAIShutdown( int restart ) {
+
+ int i;
+
+ //if the game is restarted for a tournament
+ if ( restart ) {
+ //shutdown all the bots in the botlib
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if (botstates[i] && botstates[i]->inuse) {
+ BotAIShutdownClient(botstates[i]->client);
+ }
+ }
+ //don't shutdown the bot library
+ }
+ else {
+ trap_BotLibShutdown();
+ }
+ return qtrue;
+}
+
diff --git a/game/ai_main.h b/game/ai_main.h
new file mode 100644
index 0000000..f682376
--- /dev/null
+++ b/game/ai_main.h
@@ -0,0 +1,226 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_main.h
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_main.h $
+ * $Author: Jmonroe $
+ * $Revision: 1 $
+ * $Modtime: 1/21/00 10:12p $
+ * $Date: 1/25/00 6:26p $
+ *
+ *****************************************************************************/
+
+//#define DEBUG
+#define CTF
+
+#define MAX_ITEMS 256
+//bot flags
+#define BFL_STRAFERIGHT 1 //!teamleader)) return qfalse;
+ if (ClientFromName(bs->teamleader) == -1) return qfalse;
+ return qtrue;
+}
+
+/*
+==================
+BotNumTeamMates
+==================
+*/
+int BotNumTeamMates(bot_state_t *bs) {
+ int i, numplayers;
+ char buf[MAX_INFO_STRING];
+ static int maxclis;
+
+ if (!maxclis)
+ maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ numplayers = 0;
+ for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //
+ if (BotSameTeam(bs, i)) {
+ numplayers++;
+ }
+ }
+ return numplayers;
+}
+
+/*
+==================
+BotClientTravelTimeToGoal
+==================
+*/
+int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) {
+ playerState_t ps;
+ int areanum;
+
+ BotAI_GetClientState(client, &ps);
+ areanum = BotPointAreaNum(ps.origin);
+ if (!areanum) return 1;
+ return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT);
+}
+
+/*
+==================
+BotSortTeamMatesByBaseTravelTime
+==================
+*/
+int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) {
+
+ int i, j, k, numteammates, traveltime;
+ char buf[MAX_INFO_STRING];
+ static int maxclients;
+ int traveltimes[MAX_CLIENTS];
+ bot_goal_t *goal;
+
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) goal = &ctf_redflag;
+ else goal = &ctf_blueflag;
+
+ if (!maxclients)
+ maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
+
+ numteammates = 0;
+ for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
+ trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
+ //if no config string or no name
+ if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
+ //skip spectators
+ if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue;
+ //
+ if (BotSameTeam(bs, i)) {
+ //
+ traveltime = BotClientTravelTimeToGoal(i, goal);
+ //
+ for (j = 0; j < numteammates; j++) {
+ if (traveltime < traveltimes[j]) {
+ for (k = numteammates; k > j; k--) {
+ traveltimes[k] = traveltimes[k-1];
+ teammates[k] = teammates[k-1];
+ }
+ traveltimes[j] = traveltime;
+ teammates[j] = i;
+ break;
+ }
+ }
+ if (j >= numteammates) {
+ traveltimes[j] = traveltime;
+ teammates[j] = i;
+ }
+ numteammates++;
+ if (numteammates >= maxteammates) break;
+ }
+ }
+ return numteammates;
+}
+
+/*
+==================
+BotGetTeamMateCTFPreference
+==================
+*/
+void BotSetTeamMateCTFPreference(bot_state_t *bs, int teammate, int preference) {
+ char teammatename[MAX_NETNAME];
+
+ ctftaskpreferences[teammate].preference = preference;
+ ClientName(teammate, teammatename, sizeof(teammatename));
+ strcpy(ctftaskpreferences[teammate].name, teammatename);
+}
+
+/*
+==================
+BotGetTeamMateCTFPreference
+==================
+*/
+int BotGetTeamMateCTFPreference(bot_state_t *bs, int teammate) {
+ char teammatename[MAX_NETNAME];
+
+ if (!ctftaskpreferences[teammate].preference) return 0;
+ ClientName(teammate, teammatename, sizeof(teammatename));
+ if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0;
+ return ctftaskpreferences[teammate].preference;
+}
+
+/*
+==================
+BotSortTeamMatesByCTFPreference
+==================
+*/
+int BotSortTeamMatesByCTFPreference(bot_state_t *bs, int *teammates, int numteammates) {
+ int defenders[MAX_CLIENTS], numdefenders;
+ int attackers[MAX_CLIENTS], numattackers;
+ int roamers[MAX_CLIENTS], numroamers;
+ int i, preference;
+
+ numdefenders = numattackers = numroamers = 0;
+ for (i = 0; i < numteammates; i++) {
+ preference = BotGetTeamMateCTFPreference(bs, teammates[i]);
+ if (preference & CTFTP_DEFENDER) {
+ defenders[numdefenders++] = teammates[i];
+ }
+ else if (preference & CTFTP_ATTACKER) {
+ attackers[numattackers++] = teammates[i];
+ }
+ else {
+ roamers[numroamers++] = teammates[i];
+ }
+ }
+ numteammates = 0;
+ //defenders at the front of the list
+ memcpy(&teammates[numteammates], defenders, numdefenders);
+ numteammates += numdefenders;
+ //roamers in the middle
+ memcpy(&teammates[numteammates], roamers, numroamers);
+ numteammates += numroamers;
+ //attacker in the back of the list
+ memcpy(&teammates[numteammates], attackers, numattackers);
+ numteammates += numattackers;
+
+ return numteammates;
+}
+
+/*
+==================
+BotSayTeamOrders
+==================
+*/
+void BotSayTeamOrder(bot_state_t *bs, int toclient) {
+ char teamchat[MAX_MESSAGE_SIZE];
+ char buf[MAX_MESSAGE_SIZE];
+ char name[MAX_NETNAME];
+
+ //if the bot is talking to itself
+ if (bs->client == toclient) {
+ //don't show the message just put it in the console message queue
+ trap_BotGetChatMessage(bs->cs, buf, sizeof(buf));
+ ClientName(bs->client, name, sizeof(name));
+ Com_sprintf(teamchat, sizeof(teamchat), "(%s): %s", name, buf);
+ trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat);
+ }
+ else {
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ }
+}
+
+/*
+==================
+BotCTFOrders
+==================
+*/
+void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) {
+ int numteammates, defenders, attackers, i, other;
+ int teammates[MAX_CLIENTS];
+ char name[MAX_NETNAME], carriername[MAX_NETNAME];
+
+ numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates));
+ BotSortTeamMatesByCTFPreference(bs, teammates, numteammates);
+ //different orders based on the number of team mates
+ switch(bs->numteammates) {
+ case 1: break;
+ case 2:
+ {
+ //tell the one not carrying the flag to attack the enemy base
+ if (teammates[0] != bs->flagcarrier) other = teammates[0];
+ else other = teammates[1];
+ ClientName(other, name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, other);
+ break;
+ }
+ case 3:
+ {
+ //tell the one closest to the base not carrying the flag to accompany the flag carrier
+ if (teammates[0] != bs->flagcarrier) other = teammates[0];
+ else other = teammates[1];
+ ClientName(other, name, sizeof(name));
+ ClientName(bs->flagcarrier, carriername, sizeof(carriername));
+ if (bs->flagcarrier == bs->client) {
+ BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL);
+ }
+ BotSayTeamOrder(bs, other);
+ //tell the one furthest from the the base not carrying the flag to get the enemy flag
+ if (teammates[2] != bs->flagcarrier) other = teammates[2];
+ else other = teammates[1];
+ ClientName(other, name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, other);
+ break;
+ }
+ default:
+ {
+ defenders = (int) (float) numteammates * 0.4 + 0.5;
+ if (defenders > 1) defenders = 1;
+ attackers = (int) (float) numteammates * 0.5 + 0.5;
+ ClientName(bs->flagcarrier, carriername, sizeof(carriername));
+ for (i = 0; i < defenders; i++) {
+ //
+ if (teammates[i] == bs->flagcarrier) {
+ continue;
+ }
+ //
+ ClientName(teammates[i], name, sizeof(name));
+ if (bs->flagcarrier == bs->client) {
+ BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL);
+ }
+ BotSayTeamOrder(bs, teammates[i]);
+ }
+ for (i = 0; i < attackers; i++) {
+ //
+ if (teammates[numteammates - i - 1] == bs->flagcarrier) {
+ continue;
+ }
+ //
+ ClientName(teammates[numteammates - i - 1], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[numteammates - i - 1]);
+ }
+ //
+ break;
+ }
+ }
+}
+
+/*
+==================
+BotCTFOrders
+==================
+*/
+void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) {
+ int numteammates, defenders, attackers, i;
+ int teammates[MAX_CLIENTS];
+ char name[MAX_NETNAME];
+
+ numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates));
+ BotSortTeamMatesByCTFPreference(bs, teammates, numteammates);
+ //agressive
+ //different orders based on the number of team mates
+ switch(bs->numteammates)
+ {
+ case 1: break;
+ case 2:
+ {
+ //both will go for the enemy flag
+ ClientName(teammates[0], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[0]);
+ //
+ ClientName(teammates[1], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[1]);
+ break;
+ }
+ case 3:
+ {
+ //everyone go for the flag
+ ClientName(teammates[0], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[0]);
+ //
+ ClientName(teammates[1], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[1]);
+ //
+ ClientName(teammates[2], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[2]);
+ break;
+ }
+ default:
+ {
+ //keep some people near the base for when the flag is returned
+ defenders = (int) (float) numteammates * 0.2 + 0.5;
+ if (defenders > 1) defenders = 1;
+ attackers = (int) (float) numteammates * 0.7 + 0.5;
+ for (i = 0; i < defenders; i++) {
+ //
+ ClientName(teammates[i], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL);
+ BotSayTeamOrder(bs, teammates[i]);
+ }
+ for (i = 0; i < attackers; i++) {
+ //
+ ClientName(teammates[numteammates - i - 1], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[numteammates - i - 1]);
+ }
+ //
+ break;
+ }
+ }
+}
+
+/*
+==================
+BotCTFOrders
+==================
+*/
+void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) {
+ int numteammates, defenders, attackers, i, other;
+ int teammates[MAX_CLIENTS];
+ char name[MAX_NETNAME], carriername[MAX_NETNAME];
+
+ numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates));
+ BotSortTeamMatesByCTFPreference(bs, teammates, numteammates);
+ //different orders based on the number of team mates
+ switch(numteammates) {
+ case 1: break;
+ case 2:
+ {
+ //tell the one not carrying the flag to defend the base
+ if (teammates[0] == bs->flagcarrier) other = teammates[1];
+ else other = teammates[0];
+ ClientName(other, name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL);
+ BotSayTeamOrder(bs, other);
+ break;
+ }
+ case 3:
+ {
+ //tell the one closest to the base not carrying the flag to defend the base
+ if (teammates[0] != bs->flagcarrier) other = teammates[0];
+ else other = teammates[1];
+ ClientName(other, name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL);
+ BotSayTeamOrder(bs, other);
+ //tell the one furthest from the base not carrying the flag to accompany the flag carrier
+ if (teammates[2] != bs->flagcarrier) other = teammates[2];
+ else other = teammates[1];
+ ClientName(other, name, sizeof(name));
+ ClientName(bs->flagcarrier, carriername, sizeof(carriername));
+ if (bs->flagcarrier == bs->client) {
+ BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL);
+ }
+ BotSayTeamOrder(bs, other);
+ break;
+ }
+ default:
+ {
+ //40% will defend the base
+ defenders = (int) (float) numteammates * 0.4 + 0.5;
+ if (defenders > 1) defenders = 1;
+ //50% accompanies the flag carrier
+ attackers = (int) (float) numteammates * 0.5 + 0.5;
+ for (i = 0; i < defenders; i++) {
+ //
+ if (teammates[i] == bs->flagcarrier) {
+ continue;
+ }
+ ClientName(teammates[i], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL);
+ BotSayTeamOrder(bs, teammates[i]);
+ }
+ ClientName(bs->flagcarrier, carriername, sizeof(carriername));
+ for (i = 0; i < attackers; i++) {
+ //
+ if (teammates[numteammates - i - 1] == bs->flagcarrier) {
+ continue;
+ }
+ //
+ ClientName(teammates[numteammates - i - 1], name, sizeof(name));
+ if (bs->flagcarrier == bs->client) {
+ BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL);
+ }
+ else {
+ BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL);
+ }
+ BotSayTeamOrder(bs, teammates[numteammates - i - 1]);
+ }
+ //
+ break;
+ }
+ }
+}
+
+
+/*
+==================
+BotCTFOrders
+==================
+*/
+void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) {
+ int numteammates, defenders, attackers, i;
+ int teammates[MAX_CLIENTS];
+ char name[MAX_NETNAME];
+
+ //sort team mates by travel time to base
+ numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates));
+ //sort team mates by CTF preference
+ BotSortTeamMatesByCTFPreference(bs, teammates, numteammates);
+ //agressive
+ //different orders based on the number of team mates
+ switch(numteammates)
+ {
+ case 1: break;
+ case 2:
+ {
+ //the one closest to the base will defend the base
+ ClientName(teammates[0], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL);
+ BotSayTeamOrder(bs, teammates[0]);
+ //the other will get the flag
+ ClientName(teammates[1], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[1]);
+ break;
+ }
+ case 3:
+ {
+ //the one closest to the base will defend the base
+ ClientName(teammates[0], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL);
+ BotSayTeamOrder(bs, teammates[0]);
+ //the others should go for the enemy flag
+ ClientName(teammates[1], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[1]);
+ //
+ ClientName(teammates[2], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[2]);
+ break;
+ }
+ default:
+ {
+ defenders = (int) (float) numteammates * 0.4 + 0.5;
+ if (defenders > 1) defenders = 1;
+ attackers = (int) (float) numteammates * 0.5 + 0.5;
+ for (i = 0; i < defenders; i++) {
+ //
+ ClientName(teammates[i], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL);
+ BotSayTeamOrder(bs, teammates[i]);
+ }
+ for (i = 0; i < attackers; i++) {
+ //
+ ClientName(teammates[numteammates - i - 1], name, sizeof(name));
+ BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL);
+ BotSayTeamOrder(bs, teammates[numteammates - i - 1]);
+ }
+ //
+ break;
+ }
+ }
+}
+
+
+/*
+==================
+BotTeamOrders
+==================
+*/
+void BotTeamOrders(bot_state_t *bs) {
+ //no teamplay orders at this time
+}
+
+
+/*
+==================
+BotTeamAI
+==================
+*/
+void BotTeamAI(bot_state_t *bs) {
+ int numteammates, flagstatus;
+ char netname[MAX_NETNAME];
+
+ if(!bs) return;
+
+ //
+ if (gametype != GT_TEAM && gametype != GT_CTF) return;
+ //make sure we've got a valid team leader
+ if (!BotValidTeamLeader(bs)) {
+ //
+ if (!bs->askteamleader_time && !bs->becometeamleader_time) {
+ if (bs->entergame_time + 10 > trap_AAS_Time()) {
+ bs->askteamleader_time = trap_AAS_Time() + 5 + random() * 10;
+ }
+ else {
+ bs->becometeamleader_time = trap_AAS_Time() + 5 + random() * 10;
+ }
+ }
+ if (bs->askteamleader_time && bs->askteamleader_time < trap_AAS_Time()) {
+ //if asked for a team leader and no repsonse
+ BotAI_BotInitialChat(bs, "whoisteamleader", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ bs->askteamleader_time = 0;
+ bs->becometeamleader_time = trap_AAS_Time() + 15 + random() * 10;
+ }
+ if (bs->becometeamleader_time && bs->becometeamleader_time < trap_AAS_Time()) {
+ BotAI_BotInitialChat(bs, "iamteamleader", NULL);
+ trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM);
+ ClientName(bs->client, netname, sizeof(netname));
+ strncpy(bs->teamleader, netname, sizeof(bs->teamleader));
+ bs->teamleader[sizeof(bs->teamleader)-1] = '\0';
+ bs->becometeamleader_time = 0;
+ }
+ return;
+ }
+ bs->askteamleader_time = 0;
+ bs->becometeamleader_time = 0;
+
+ //return if this bot is NOT the team leader
+ ClientName(bs->client, netname, sizeof(netname));
+ if (Q_stricmp(netname, bs->teamleader) != 0) return;
+ //
+ //if the game starts OR a new player comes onto the team OR a player leaves the team
+ //
+ numteammates = BotNumTeamMates(bs);
+ //give orders
+ switch(gametype) {
+ case GT_TEAM:
+ {
+ if (bs->numteammates != numteammates || bs->forceorders) {
+ bs->teamgiveorders_time = trap_AAS_Time();
+ bs->numteammates = numteammates;
+ bs->forceorders = qfalse;
+ }
+ //if it's time to give orders
+ if (bs->teamgiveorders_time < trap_AAS_Time() - 5) {
+ BotTeamOrders(bs);
+ //
+ bs->teamgiveorders_time = 0;
+ }
+ break;
+ }
+ case GT_CTF:
+ {
+ //if the number of team mates changed or the flag status changed
+ //or someone wants to know what to do
+ if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) {
+ bs->teamgiveorders_time = trap_AAS_Time();
+ bs->numteammates = numteammates;
+ bs->flagstatuschanged = qfalse;
+ bs->forceorders = qfalse;
+ }
+ //if there were no flag captures the last 3 minutes
+ if (bs->lastflagcapture_time < trap_AAS_Time() - 240) {
+ bs->lastflagcapture_time = trap_AAS_Time();
+ //randomly change the CTF strategy
+ if (random() < 0.4) {
+ bs->ctfstrategy ^= CTFS_PASSIVE;
+ bs->teamgiveorders_time = trap_AAS_Time();
+ }
+ }
+ //if it's time to give orders
+ if (bs->teamgiveorders_time && bs->teamgiveorders_time < trap_AAS_Time() - 3) {
+ //
+ if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus;
+ else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;
+ //
+ switch(flagstatus) {
+ case 0: BotCTFOrders_BothFlagsAtBase(bs); break;
+ case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break;
+ case 2: BotCTFOrders_FlagNotAtBase(bs); break;
+ case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break;
+ }
+ //
+ bs->teamgiveorders_time = 0;
+ }
+ break;
+ }
+ }
+}
+
diff --git a/game/ai_team.h b/game/ai_team.h
new file mode 100644
index 0000000..519642e
--- /dev/null
+++ b/game/ai_team.h
@@ -0,0 +1,19 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: ai_team.h
+ *
+ * desc: Quake3 bot AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/ai_team.h $
+ * $Author: Jmonroe $
+ * $Revision: 1 $
+ * $Modtime: 1/21/00 10:12p $
+ * $Date: 1/25/00 6:26p $
+ *
+ *****************************************************************************/
+
+void BotTeamAI(bot_state_t *bs);
+int BotGetTeamMateCTFPreference(bot_state_t *bs, int teammate);
+void BotSetTeamMateCTFPreference(bot_state_t *bs, int teammate, int preference);
diff --git a/game/be_aas.h b/game/be_aas.h
new file mode 100644
index 0000000..66b2efb
--- /dev/null
+++ b/game/be_aas.h
@@ -0,0 +1,165 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*****************************************************************************
+ * name: be_aas.h
+ *
+ * desc: Area Awareness System, stuff exported to the AI
+ *
+ * $Archive: /StarTrek/Code-DM/game/be_aas.h $
+ * $Author: Jmonroe $
+ * $Revision: 1 $
+ * $Modtime: 1/21/00 10:12p $
+ * $Date: 1/25/00 6:26p $
+ *
+ *****************************************************************************/
+
+#ifndef MAX_STRINGFIELD
+#define MAX_STRINGFIELD 80
+#endif
+
+//travel flags
+#define TFL_INVALID 0x0000001 //! 0); \
+}
+
+#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
+ es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
+
+static void
+swapfunc(a, b, n, swaptype)
+ char *a, *b;
+ int n, swaptype;
+{
+ if(swaptype <= 1)
+ swapcode(long, a, b, n)
+ else
+ swapcode(char, a, b, n)
+}
+
+#define swap(a, b) \
+ if (swaptype == 0) { \
+ long t = *(long *)(a); \
+ *(long *)(a) = *(long *)(b); \
+ *(long *)(b) = t; \
+ } else \
+ swapfunc(a, b, es, swaptype)
+
+#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype)
+
+static char *
+med3(a, b, c, cmp)
+ char *a, *b, *c;
+ cmp_t *cmp;
+{
+ return cmp(a, b) < 0 ?
+ (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a ))
+ :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c ));
+}
+
+void
+qsort(a, n, es, cmp)
+ void *a;
+ size_t n, es;
+ cmp_t *cmp;
+{
+ char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
+ int d, r, swaptype, swap_cnt;
+
+loop: SWAPINIT(a, es);
+ swap_cnt = 0;
+ if (n < 7) {
+ for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
+ for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0;
+ pl -= es)
+ swap(pl, pl - es);
+ return;
+ }
+ pm = (char *)a + (n / 2) * es;
+ if (n > 7) {
+ pl = a;
+ pn = (char *)a + (n - 1) * es;
+ if (n > 40) {
+ d = (n / 8) * es;
+ pl = med3(pl, pl + d, pl + 2 * d, cmp);
+ pm = med3(pm - d, pm, pm + d, cmp);
+ pn = med3(pn - 2 * d, pn - d, pn, cmp);
+ }
+ pm = med3(pl, pm, pn, cmp);
+ }
+ swap(a, pm);
+ pa = pb = (char *)a + es;
+
+ pc = pd = (char *)a + (n - 1) * es;
+ for (;;) {
+ while (pb <= pc && (r = cmp(pb, a)) <= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ swap(pa, pb);
+ pa += es;
+ }
+ pb += es;
+ }
+ while (pb <= pc && (r = cmp(pc, a)) >= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ swap(pc, pd);
+ pd -= es;
+ }
+ pc -= es;
+ }
+ if (pb > pc)
+ break;
+ swap(pb, pc);
+ swap_cnt = 1;
+ pb += es;
+ pc -= es;
+ }
+ if (swap_cnt == 0) { /* Switch to insertion sort */
+ for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
+ for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0;
+ pl -= es)
+ swap(pl, pl - es);
+ return;
+ }
+
+ pn = (char *)a + n * es;
+ r = min(pa - (char *)a, pb - pa);
+ vecswap(a, pb - r, r);
+ r = min(pd - pc, pn - pd - es);
+ vecswap(pb, pn - r, r);
+ if ((r = pb - pa) > es)
+ qsort(a, r / es, es, cmp);
+ if ((r = pd - pc) > es) {
+ /* Iterate rather than recurse to save stack space */
+ a = pn - r;
+ n = r / es;
+ goto loop;
+ }
+/* qsort(pn - r, r / es, es, cmp);*/
+}
+
+//==================================================================================
+
+
+// this file is excluded from release builds because of intrinsics
+
+size_t strlen( const char *string ) {
+ const char *s;
+
+ s = string;
+ while ( *s ) {
+ s++;
+ }
+ return s - string;
+}
+
+
+char *strcat( char *strDestination, const char *strSource ) {
+ char *s;
+
+ s = strDestination;
+ while ( *s ) {
+ s++;
+ }
+ while ( *strSource ) {
+ *s++ = *strSource++;
+ }
+ *s = 0;
+ return strDestination;
+}
+
+char *strcpy( char *strDestination, const char *strSource ) {
+ char *s;
+
+ s = strDestination;
+ while ( *strSource ) {
+ *s++ = *strSource++;
+ }
+ *s = 0;
+ return strDestination;
+}
+
+
+int strcmp( const char *string1, const char *string2 ) {
+ while ( *string1 == *string2 && *string1 && *string2 ) {
+ string1++;
+ string2++;
+ }
+ return *string1 - *string2;
+}
+
+
+char *strchr( const char *string, int c ) {
+ while ( *string ) {
+ if ( *string == c ) {
+ return ( char * )string;
+ }
+ string++;
+ }
+ return (char *)0;
+}
+
+char *strstr( const char *string, const char *strCharSet ) {
+ while ( *string ) {
+ int i;
+
+ for ( i = 0 ; strCharSet[i] ; i++ ) {
+ if ( string[i] != strCharSet[i] ) {
+ break;
+ }
+ }
+ if ( !strCharSet[i] ) {
+ return (char *)string;
+ }
+ string++;
+ }
+ return (char *)0;
+}
+
+#if !defined ( _MSC_VER ) && ! defined ( __linux__ )
+
+int tolower( int c ) {
+ if ( c >= 'A' && c <= 'Z' ) {
+ c += 'a' - 'A';
+ }
+ return c;
+}
+
+
+int toupper( int c ) {
+ if ( c >= 'a' && c <= 'z' ) {
+ c += 'A' - 'a';
+ }
+ return c;
+}
+
+#endif
+//#ifndef _MSC_VER
+
+void *memmove( void *dest, const void *src, size_t count ) {
+ int i;
+
+ if ( dest > src ) {
+ for ( i = count-1 ; i >= 0 ; i-- ) {
+ ((char *)dest)[i] = ((char *)src)[i];
+ }
+ } else {
+ for ( i = 0 ; i < count ; i++ ) {
+ ((char *)dest)[i] = ((char *)src)[i];
+ }
+ }
+ return dest;
+}
+
+
+#if 0
+
+double floor( double x ) {
+ return (int)(x + 0x40000000) - 0x40000000;
+}
+
+void *memset( void *dest, int c, size_t count ) {
+ while ( count-- ) {
+ ((char *)dest)[count] = c;
+ }
+ return dest;
+}
+
+void *memcpy( void *dest, const void *src, size_t count ) {
+ while ( count-- ) {
+ ((char *)dest)[count] = ((char *)src)[count];
+ }
+ return dest;
+}
+
+char *strncpy( char *strDest, const char *strSource, size_t count ) {
+ char *s;
+
+ s = strDest;
+ while ( *strSource && count ) {
+ *s++ = *strSource++;
+ count--;
+ }
+ while ( count-- ) {
+ *s++ = 0;
+ }
+ return strDest;
+}
+
+double sqrt( double x ) {
+ float y;
+ float delta;
+ float maxError;
+
+ if ( x <= 0 ) {
+ return 0;
+ }
+
+ // initial guess
+ y = x / 2;
+
+ // refine
+ maxError = x * 0.001;
+
+ do {
+ delta = ( y * y ) - x;
+ y -= delta / ( 2 * y );
+ } while ( delta > maxError || delta < -maxError );
+
+ return y;
+}
+
+
+float sintable[1024] = {
+0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738,
+0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008,
+0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274,
+0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535,
+0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790,
+0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035,
+0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269,
+0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490,
+0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697,
+0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888,
+0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061,
+0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213,
+0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343,
+0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450,
+0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532,
+0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586,
+0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610,
+0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604,
+0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565,
+0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492,
+0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382,
+0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234,
+0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046,
+0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816,
+0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543,
+0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225,
+0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859,
+0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445,
+0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980,
+0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463,
+0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892,
+0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266,
+0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582,
+0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838,
+0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034,
+0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168,
+0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237,
+0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241,
+0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177,
+0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043,
+0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839,
+0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563,
+0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212,
+0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786,
+0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283,
+0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701,
+0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039,
+0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294,
+0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466,
+0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553,
+0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554,
+0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466,
+0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290,
+0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022,
+0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661,
+0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207,
+0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657,
+0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011,
+0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266,
+0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422,
+0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476,
+0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429,
+0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278,
+0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021,
+0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659,
+0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188,
+0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609,
+0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920,
+0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119,
+0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206,
+0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179,
+0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036,
+0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778,
+0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402,
+0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907,
+0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293,
+0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558,
+0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701,
+0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721,
+0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616,
+0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387,
+0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032,
+0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549,
+0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939,
+0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199,
+0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330,
+0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329,
+0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197,
+0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932,
+0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534,
+0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001,
+0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332,
+0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528,
+0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587,
+0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508,
+0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291,
+0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935,
+0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440,
+0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803,
+0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026,
+0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107,
+0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046,
+0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842,
+0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494,
+0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002,
+0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366,
+0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584,
+0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657,
+0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584,
+0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365,
+0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999,
+0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485,
+0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824,
+0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014,
+0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057,
+0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950,
+0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695,
+0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291,
+0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737,
+0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033,
+0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180,
+0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176,
+0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023,
+0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719,
+0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265,
+0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660,
+0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905,
+0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999
+};
+
+double sin( double x ) {
+ int index;
+ int quad;
+
+ index = 1024 * x / (M_PI * 0.5);
+ quad = ( index >> 10 ) & 3;
+ index &= 1023;
+ switch ( quad ) {
+ case 0:
+ return sintable[index];
+ case 1:
+ return sintable[1023-index];
+ case 2:
+ return -sintable[index];
+ case 3:
+ return -sintable[1023-index];
+ }
+ return 0;
+}
+
+
+double cos( double x ) {
+ int index;
+ int quad;
+
+ index = 1024 * x / (M_PI * 0.5);
+ quad = ( index >> 10 ) & 3;
+ index &= 1023;
+ switch ( quad ) {
+ case 3:
+ return sintable[index];
+ case 0:
+ return sintable[1023-index];
+ case 1:
+ return -sintable[index];
+ case 2:
+ return -sintable[1023-index];
+ }
+ return 0;
+}
+
+
+double atan2( double y, double x ) {
+ float base;
+ float temp;
+ float dir;
+ float test;
+ int i;
+
+ if ( x < 0 ) {
+ if ( y >= 0 ) {
+ // quad 1
+ base = M_PI / 2;
+ temp = x;
+ x = y;
+ y = -temp;
+ } else {
+ // quad 2
+ base = M_PI;
+ x = -x;
+ y = -y;
+ }
+ } else {
+ if ( y < 0 ) {
+ // quad 3
+ base = 3 * M_PI / 2;
+ temp = x;
+ x = -y;
+ y = temp;
+ }
+ }
+
+ if ( y > x ) {
+ base += M_PI/2;
+ temp = x;
+ x = y;
+ y = temp;
+ dir = -1;
+ } else {
+ dir = 1;
+ }
+
+ // calcualte angle in octant 0
+ if ( x == 0 ) {
+ return base;
+ }
+ y /= x;
+
+ for ( i = 0 ; i < 512 ; i++ ) {
+ test = sintable[i] / sintable[1023-i];
+ if ( test > y ) {
+ break;
+ }
+ }
+
+ return base + dir * i * ( M_PI/2048);
+}
+
+
+#endif
+
+double tan( double x ) {
+ return sin(x) / cos(x);
+}
+
+
+static int randSeed = 0;
+
+void srand( unsigned seed ) {
+ randSeed = seed;
+}
+
+int rand( void ) {
+ randSeed = (69069 * randSeed + 1);
+ return randSeed & 0x7fff;
+}
+
+double atof( const char *string ) {
+ float sign;
+ float value;
+ int c;
+
+
+ // skip whitespace
+ while ( *string <= ' ' ) {
+ if ( !*string ) {
+ return 0;
+ }
+ string++;
+ }
+
+ // check sign
+ switch ( *string ) {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+ case '-':
+ string++;
+ sign = -1;
+ break;
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ c = string[0];
+ if ( c != '.' ) {
+ do {
+ c = *string++;
+ if ( c < '0' || c > '9' ) {
+ break;
+ }
+ c -= '0';
+ value = value * 10 + c;
+ } while ( 1 );
+ } else {
+ string++;
+ }
+
+ // check for decimal point
+ if ( c == '.' ) {
+ double fraction;
+
+ fraction = 0.1;
+ do {
+ c = *string++;
+ if ( c < '0' || c > '9' ) {
+ break;
+ }
+ c -= '0';
+ value += c * fraction;
+ fraction *= 0.1;
+ } while ( 1 );
+
+ }
+
+ // not handling 10e10 notation...
+
+ return value * sign;
+}
+
+double _atof( const char **stringPtr ) {
+ const char *string;
+ float sign;
+ float value;
+ int c;
+
+ string = *stringPtr;
+
+ // skip whitespace
+ while ( *string <= ' ' ) {
+ if ( !*string ) {
+ *stringPtr = string;
+ return 0;
+ }
+ string++;
+ }
+
+ // check sign
+ switch ( *string ) {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+ case '-':
+ string++;
+ sign = -1;
+ break;
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ if ( string[0] != '.' ) {
+ do {
+ c = *string++;
+ if ( c < '0' || c > '9' ) {
+ break;
+ }
+ c -= '0';
+ value = value * 10 + c;
+ } while ( 1 );
+ }
+
+ // check for decimal point
+ if ( c == '.' ) {
+ double fraction;
+
+ fraction = 0.1;
+ do {
+ c = *string++;
+ if ( c < '0' || c > '9' ) {
+ break;
+ }
+ c -= '0';
+ value += c * fraction;
+ fraction *= 0.1;
+ } while ( 1 );
+
+ }
+
+ // not handling 10e10 notation...
+ *stringPtr = string;
+
+ return value * sign;
+}
+
+
+#if !defined( _MSC_VER ) && !defined( __linux__ )
+
+int atoi( const char *string ) {
+ int sign;
+ int value;
+ int c;
+
+
+ // skip whitespace
+ while ( *string <= ' ' ) {
+ if ( !*string ) {
+ return 0;
+ }
+ string++;
+ }
+
+ // check sign
+ switch ( *string ) {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+ case '-':
+ string++;
+ sign = -1;
+ break;
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ do {
+ c = *string++;
+ if ( c < '0' || c > '9' ) {
+ break;
+ }
+ c -= '0';
+ value = value * 10 + c;
+ } while ( 1 );
+
+ // not handling 10e10 notation...
+
+ return value * sign;
+}
+
+
+int _atoi( const char **stringPtr ) {
+ int sign;
+ int value;
+ int c;
+ const char *string;
+
+ string = *stringPtr;
+
+ // skip whitespace
+ while ( *string <= ' ' ) {
+ if ( !*string ) {
+ return 0;
+ }
+ string++;
+ }
+
+ // check sign
+ switch ( *string ) {
+ case '+':
+ string++;
+ sign = 1;
+ break;
+ case '-':
+ string++;
+ sign = -1;
+ break;
+ default:
+ sign = 1;
+ break;
+ }
+
+ // read digits
+ value = 0;
+ do {
+ c = *string++;
+ if ( c < '0' || c > '9' ) {
+ break;
+ }
+ c -= '0';
+ value = value * 10 + c;
+ } while ( 1 );
+
+ // not handling 10e10 notation...
+
+ *stringPtr = string;
+
+ return value * sign;
+}
+
+int abs( int n ) {
+ return n < 0 ? -n : n;
+}
+
+double fabs( double x ) {
+ return x < 0 ? -x : x;
+}
+
+
+
+//=========================================================
+
+
+#define ALT 0x00000001 /* alternate form */
+#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */
+#define LADJUST 0x00000004 /* left adjustment */
+#define LONGDBL 0x00000008 /* long double */
+#define LONGINT 0x00000010 /* long integer */
+#define QUADINT 0x00000020 /* quad integer */
+#define SHORTINT 0x00000040 /* short integer */
+#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */
+#define FPT 0x00000100 /* floating point number */
+
+#define to_digit(c) ((c) - '0')
+#define is_digit(c) ((unsigned)to_digit(c) <= 9)
+#define to_char(n) ((n) + '0')
+
+void AddInt( char **buf_p, int val, int width, int flags ) {
+ char text[32];
+ int digits;
+ int signedVal;
+ char *buf;
+
+ digits = 0;
+ signedVal = val;
+ if ( val < 0 ) {
+ val = -val;
+ }
+ do {
+ text[digits++] = '0' + val % 10;
+ val /= 10;
+ } while ( val );
+
+ if ( signedVal < 0 ) {
+ text[digits++] = '-';
+ }
+
+ buf = *buf_p;
+
+ if( !( flags & LADJUST ) ) {
+ while ( digits < width ) {
+ *buf++ = ( flags & ZEROPAD ) ? '0' : ' ';
+ width--;
+ }
+ }
+
+ while ( digits-- ) {
+ *buf++ = text[digits];
+ width--;
+ }
+
+ if( flags & LADJUST ) {
+ while ( width-- ) {
+ *buf++ = ( flags & ZEROPAD ) ? '0' : ' ';
+ }
+ }
+
+ *buf_p = buf;
+}
+
+//TiM - Required since the native QVM code can't handle UInts... >_<
+//This is AddInt horribly mutilated. ;P
+void AddULong( char **buf_p, unsigned long val, int width, int flags ) {
+ char text[32];
+ int digits;
+ //int signedVal;
+ char *buf;
+
+ digits = 0;
+ //signedVal = val;
+ //if ( val < 0 ) {
+ // val = -val;
+ //}
+ do {
+ text[digits++] = '0' + val % 10;
+ val /= 10;
+ } while ( val );
+
+ //if ( signedVal < 0 ) {
+ // text[digits++] = '-';
+ //}
+
+ buf = *buf_p;
+
+ if( !( flags & LADJUST ) ) {
+ while ( digits < width ) {
+ *buf++ = ( flags & ZEROPAD ) ? '0' : ' ';
+ width--;
+ }
+ }
+
+ while ( digits-- ) {
+ *buf++ = text[digits];
+ width--;
+ }
+
+ if( flags & LADJUST ) {
+ while ( width-- ) {
+ *buf++ = ( flags & ZEROPAD ) ? '0' : ' ';
+ }
+ }
+
+ *buf_p = buf;
+}
+
+//TiM - 'borrowed' from Q3 1.32 and modified so it can handle the
+//rounding up system that EF has.
+//I'm going to bet it was because of this id Software chose to
+//round down floats from then on
+void AddFloat( char **buf_p, float fval, int width, int prec ) {
+ char text[32];
+ int digits;
+ float signedVal;
+ char *buf;
+ int val;
+
+ // get the sign
+ signedVal = fval;
+ if ( fval < 0 ) {
+ fval = -fval;
+ }
+
+ //Com_Printf( "wholeNumber = %i, roundsUp = %i\n", fval, wholeNumber, roundsUp );
+
+ // write the float number
+ digits = 0;
+ val = (int)fval;
+ if ( fval - val < 0.0f )
+ val--;
+
+ do {
+ text[digits++] = '0' + (val % 10);
+ val /= 10;
+ } while ( val );
+
+ if ( signedVal < 0 ) {
+ text[digits++] = '-';
+ }
+
+ buf = *buf_p;
+
+ while ( digits < width ) {
+ *buf++ = ' ';
+ width--;
+ }
+
+ while ( digits-- ) {
+ *buf++ = text[digits];
+ }
+
+ *buf_p = buf;
+
+ if (prec < 0)
+ prec = 6;
+ // write the fraction
+ digits = 0;
+ while (digits < prec) {
+ fval -= (int)fval;
+ fval = Q_fabs(fval); //if it gets rounded up, it becomes neg, so just abs it
+ fval *= 10.0;
+ val = (int)fval;
+ text[digits++] = '0' + (val % 10);
+ }
+
+ if (digits > 0) {
+ buf = *buf_p;
+ *buf++ = '.';
+ for (prec = 0; prec < digits; prec++) {
+ *buf++ = text[prec];
+ }
+ *buf_p = buf;
+ }
+}
+
+
+/*void AddFloat( char **buf_p, float fval, int width, int prec ) {
+ char text[32];
+ int digits;
+ float signedVal;
+ char *buf;
+ int val;
+ //decimal points
+ float decVal;
+ int decScaled;
+ //flipping loop
+ char temp;
+ int i;
+ //rounding check
+ qboolean wholeNumber = qfalse;
+ qboolean roundsUp = qfalse;
+ float decimal = 0.9f;
+
+ // FIXME!!!! handle fractions
+ //TiM - WHAT THE HELL!? YOU LEFT OUT FRACTIONS!?! WTF MAN WTF?!?! >_ 2 )
+ //ioEF casts by rounding down. ( 1.5 -> 1 )
+ //as this can create a sizeable variance, we have to account for it
+ roundsUp = (int)decimal;
+
+ digits = 0;
+ signedVal = fval;
+ if ( fval < 0 ) {
+ fval = -fval;
+ }
+
+ //determine if it's a whole number or not (ie = x.000 )
+ wholeNumber = (fval - (float)((int)fval) == 0);
+
+ //isolate the whole number portion of it, rounding down to maintain precision
+ val = (int)fval - (!wholeNumber && roundsUp ? 1 : 0); //3.452 : 4 - 1 = 3
+
+ //isolate the decimal number portion
+ decVal = (fval - (float)val); //3.452 - 3 = 0.452
+ do {
+ decScaled = 0;
+ decVal *= 10.0f; //scale it up one int at a time - 0.452*10 = 4.52
+ decScaled = ((int)decVal - (!wholeNumber && roundsUp ? 1 : 0)); // = 4
+ //Com_Printf( "decScaled = %i\n", decScaled );
+ text[digits++] = '0' + decScaled % 10; //'0' + 4 = '4'
+ decVal = decVal - (float)decScaled; //4.52 - 4 = 0.52
+ } while ( (prec >= 0) ? digits < prec : digits < 6 );
+
+ //now swap it around so it's backwards (I brought it in forwards, but the whole section is done backwards)
+ for ( i=0; i < digits>>1; i++ )
+ {
+ temp = text[i];
+ text[i] = text[(digits-1)-i];
+ text[(digits-1)-i] = temp;
+ }
+
+ //add a dot
+ text[digits++] = '.';
+
+ //handle the whole number part
+ do {
+ text[digits++] = '0' + val % 10;
+ val /= 10;
+ } while ( val );
+
+ if ( signedVal < 0 ) {
+ text[digits++] = '-';
+ }
+
+ buf = *buf_p;
+
+ while ( digits < width ) {
+ *buf++ = ' ';
+ width--;
+ }
+
+ while ( digits-- ) {
+ *buf++ = text[digits];
+ }
+
+ *buf_p = buf;
+}*/
+
+
+void AddString( char **buf_p, char *string, int width, int prec ) {
+ int size;
+ char *buf;
+
+ buf = *buf_p;
+
+ if ( string == NULL ) {
+ string = "(null)";
+ prec = -1;
+ }
+
+ if ( prec >= 0 ) {
+ for( size = 0; size < prec; size++ ) {
+ if( string[size] == '\0' ) {
+ break;
+ }
+ }
+ }
+ else {
+ size = strlen( string );
+ }
+
+ width -= size;
+
+ while( size-- ) {
+ *buf++ = *string++;
+ }
+
+ while( width-- > 0 ) {
+ *buf++ = ' ';
+ }
+
+ *buf_p = buf;
+}
+
+/*
+vsprintf
+
+I'm not going to support a bunch of the more arcane stuff in here
+just to keep it simpler. For example, the '*' and '$' are not
+currently supported. I've tried to make it so that it will just
+parse and ignore formats we don't support.
+*/
+int vsprintf( char *buffer, const char *fmt, va_list argptr ) {
+ int *arg;
+ char *buf_p;
+ char ch;
+ int flags;
+ int width;
+ int prec;
+ int n;
+ char sign;
+
+ buf_p = buffer;
+ arg = (int *)argptr;
+
+ while( qtrue ) {
+ // run through the format string until we hit a '%' or '\0'
+ for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) {
+ *buf_p++ = ch;
+ }
+ if ( ch == '\0' ) {
+ goto done;
+ }
+
+ // skip over the '%'
+ fmt++;
+
+ // reset formatting state
+ flags = 0;
+ width = 0;
+ prec = -1;
+ sign = '\0';
+
+rflag:
+ ch = *fmt++;
+reswitch:
+ switch( ch ) {
+ case '-':
+ flags |= LADJUST;
+ goto rflag;
+ case '.':
+ n = 0;
+ while( is_digit( ( ch = *fmt++ ) ) ) {
+ n = 10 * n + ( ch - '0' );
+ }
+ prec = n < 0 ? -1 : n;
+ goto reswitch;
+ case '0':
+ flags |= ZEROPAD;
+ goto rflag;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ n = 0;
+ do {
+ n = 10 * n + ( ch - '0' );
+ ch = *fmt++;
+ } while( is_digit( ch ) );
+ width = n;
+ goto reswitch;
+ case 'c':
+ *buf_p++ = (char)*arg;
+ arg++;
+ break;
+ case 'd':
+ case 'i':
+ AddInt( &buf_p, *arg, width, flags );
+ arg++;
+ break;
+ case 'f':
+ AddFloat( &buf_p, *(double *)arg, width, prec );
+#ifdef __LCC__
+ arg += 1; // everything is 32 bit in my compiler
+#else
+ arg += 2;
+#endif
+ break;
+ case 's':
+ AddString( &buf_p, (char *)*arg, width, prec );
+ arg++;
+ break;
+ //TiM
+ case 'u':
+ AddULong( &buf_p, *(unsigned long *)arg, width, flags );
+ arg++;
+ break;
+ case '%':
+ *buf_p++ = ch;
+ break;
+ default:
+ *buf_p++ = (char)*arg;
+ arg++;
+ break;
+ }
+ }
+
+done:
+ *buf_p = 0;
+ return buf_p - buffer;
+}
+
+
+/* this is really crappy */
+int sscanf( const char *buffer, const char *fmt, ... ) {
+ int cmd;
+ int **arg;
+ int count;
+
+ arg = (int **)&fmt + 1;
+ count = 0;
+
+ while ( *fmt ) {
+ if ( fmt[0] != '%' ) {
+ fmt++;
+ continue;
+ }
+
+ cmd = fmt[1];
+ fmt += 2;
+
+ switch ( cmd ) {
+ case 'i':
+ case 'd':
+ case 'u':
+ **arg = _atoi( &buffer );
+ break;
+ case 'f':
+ *(float *)*arg = _atof( &buffer );
+ break;
+ }
+ arg++;
+ }
+
+ return count;
+}
+
+#endif
diff --git a/game/bg_lib.h b/game/bg_lib.h
new file mode 100644
index 0000000..12187ac
--- /dev/null
+++ b/game/bg_lib.h
@@ -0,0 +1,72 @@
+// bg_lib.h -- standard C library replacement routines used by code
+// compiled for the virtual machine
+
+// This file is NOT included on native builds
+
+#ifdef Q3_VM
+
+typedef int size_t;
+
+typedef char * va_list;
+#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
+#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
+#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
+#define va_end(ap) ( ap = (va_list)0 )
+
+#define CHAR_BIT 8 /* number of bits in a char */
+#define SCHAR_MIN (-128) /* minimum signed char value */
+#define SCHAR_MAX 127 /* maximum signed char value */
+#define UCHAR_MAX 0xff /* maximum unsigned char value */
+
+#define SHRT_MIN (-32768) /* minimum (signed) short value */
+#define SHRT_MAX 32767 /* maximum (signed) short value */
+#define USHRT_MAX 0xffff /* maximum unsigned short value */
+#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */
+#define INT_MAX 2147483647 /* maximum (signed) int value */
+#define UINT_MAX 0xffffffff /* maximum unsigned int value */
+#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */
+#define LONG_MAX 2147483647L /* maximum (signed) long value */
+#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */
+
+// Misc functions
+typedef int cmp_t(const void *, const void *);
+void qsort(void *a, size_t n, size_t es, cmp_t *cmp);
+void srand( unsigned seed );
+int rand( void );
+
+// String functions
+size_t strlen( const char *string );
+char *strcat( char *strDestination, const char *strSource );
+char *strcpy( char *strDestination, const char *strSource );
+int strcmp( const char *string1, const char *string2 );
+char *strchr( const char *string, int c );
+char *strstr( const char *string, const char *strCharSet );
+char *strncpy( char *strDest, const char *strSource, size_t count );
+int tolower( int c );
+int toupper( int c );
+
+double atof( const char *string );
+double _atof( const char **stringPtr );
+int atoi( const char *string );
+int _atoi( const char **stringPtr );
+
+int vsprintf( char *buffer, const char *fmt, va_list argptr );
+int sscanf( const char *buffer, const char *fmt, ... );
+
+// Memory functions
+void *memmove( void *dest, const void *src, size_t count );
+void *memset( void *dest, int c, size_t count );
+void *memcpy( void *dest, const void *src, size_t count );
+
+// Math functions
+double ceil( double x );
+double floor( double x );
+double sqrt( double x );
+double sin( double x );
+double cos( double x );
+double atan2( double y, double x );
+double tan( double x );
+int abs( int n );
+double fabs( double x );
+
+#endif // Q3_VM
diff --git a/game/bg_local.h b/game/bg_local.h
new file mode 100644
index 0000000..f2b7396
--- /dev/null
+++ b/game/bg_local.h
@@ -0,0 +1,53 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+// bg_local.h -- local definitions for the bg (both games) files
+
+#define MIN_WALK_NORMAL 0.7 // can't walk on very steep slopes
+
+#define STEPSIZE 18
+
+#define JUMP_VELOCITY 270
+
+#define TIMER_LAND 130
+#define TIMER_GESTURE (34*66+50)
+
+
+#define OVERCLIP 1.001
+
+// all of the locals will be zeroed before each
+// pmove, just to make damn sure we don't have
+// any differences when running on client or server
+typedef struct {
+ vec3_t forward, right, up;
+ float frametime;
+
+ int msec;
+
+ qboolean walking;
+ qboolean groundPlane;
+ trace_t groundTrace;
+
+ float impactSpeed;
+
+ vec3_t previous_origin;
+ vec3_t previous_velocity;
+ int previous_waterlevel;
+} pml_t;
+
+extern pmove_t *pm;
+extern pml_t pml;
+
+extern int c_pmove;
+
+void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce );
+void PM_AddTouchEnt( int entityNum );
+void PM_AddEvent( int newEvent );
+
+static void PM_StartLegsAnim( int anim );
+static void PM_StartTorsoAnim( int anim, qboolean overrideEmotes );
+
+qboolean PM_SlideMove( qboolean gravity );
+void PM_StepSlideMove( qboolean gravity );
+
+
+
diff --git a/game/bg_misc.c b/game/bg_misc.c
new file mode 100644
index 0000000..01c38bc
--- /dev/null
+++ b/game/bg_misc.c
@@ -0,0 +1,2331 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+// bg_misc.c -- both games misc functions, all completely stateless
+
+#include "q_shared.h"
+#include "bg_public.h"
+
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+void trap_FS_FCloseFile( fileHandle_t f );
+void trap_FS_Read( void *buffer, int len, fileHandle_t f );
+
+// If you change these: PLEASE CHANGE THE COMMENTS ON THE AMMO PICKUPS, WHICH DETAIL THE QUANTITY IN THE CLIP
+#define AMMO_PHASER_CLIP 50
+#define AMMO_COMPRESSION_CLIP 32
+#define AMMO_IMOD_CLIP 15
+#define AMMO_SCAVENGER_CLIP 30
+#define AMMO_STASIS_CLIP 15
+#define AMMO_GRENADE_CLIP 10
+#define AMMO_TETRION_CLIP 40
+#define AMMO_QUANTUM_CLIP 6
+#define AMMO_DREADNOUGHT_CLIP 40
+
+char races[256];
+
+//TiM : Tidied up for programmer easability... O_o
+//Marcin: not used since 30/12/2008
+/**
+* Max ammo for each weapon. Unused.
+*/
+int Max_Ammo[WP_NUM_WEAPONS] =
+{
+ 0, // WP_NONE,
+
+ 5, // WP_NULL_HAND
+
+ 5, // WP_TRICORDER,
+ 5, // WP_PADD,
+ 5, // WP_COFFEE,
+
+ 5, // WP_PHASER, !! this should match PHASER_AMMO_MAX defined in bg_public
+ 5, // WP_COMPRESSION_RIFLE,
+ 5, // WP_TR116,
+ 5, // WP_GRENADE_LAUNCHER,
+ 5, // WP_QUANTUM_BURST,
+ 5, // WP_DISRUPTOR,
+
+ 5, // WP_MEDKIT,
+ 5, // WP_VOYAGER_HYPO,
+ 5, // WP_DERMAL_REGEN
+
+ 5, // WP_TOOLKIT,
+ 5, // WP_NEUTRINO_PROBE,
+
+ //64 // WP_TR116
+};
+
+/*int Max_Ammo[WP_NUM_WEAPONS] =
+{
+ 0, // WP_NONE,
+ 50, // WP_PHASER, !! this should match PHASER_AMMO_MAX defined in bg_public
+ 128, // WP_COMPRESSION_RIFLE,
+ 60, // WP_NULL_HAND,
+ 100, // WP_COFFEE,
+ 50, // WP_DISRUPTOR,
+ 30, // WP_GRENADE_LAUNCHER,
+ 120, // WP_TR116,
+ 20, // WP_QUANTUM_BURST,
+ 50, // WP_DERMAL_REGEN,
+ 50, // WP_VOYAGER_HYPO,
+ 50, // WP_TOOLKIT,
+ 100, // WP_MEDKIT,
+ 50, // WP_TRICORDER,
+ 50, // WP_PADD,
+ 50, // WP_NEUTRINO_PROBE,
+ 64 // WP_TR116
+
+};*/
+
+/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended
+DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION.
+The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface.
+
+If an item is the target of another entity, it will not spawn in until fired.
+
+An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired.
+
+"notfree" if set to 1, don't spawn in free for all games
+"notteam" if set to 1, don't spawn in team games
+"notsingle" if set to 1, don't spawn in single player games
+"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning.
+"random" random number of plus or minus seconds varied from the respawn time
+"count" override quantity or duration on most items.
+*/
+
+gitem_t bg_itemlist[] =
+{
+ {
+ NULL, //char *classname; // spawning name
+ NULL, //char *pickup_sound;
+ NULL, //char *world_model;
+ NULL, //char *view_model;
+/* icon */ NULL, //char *icon;
+/* pickup */ NULL, //char *pickup_name; // for printing on pickup
+ 0, //int quantity; // for ammo how much, or duration of powerup
+ 0, //itemType_t giType; // IT_* flags
+ 0, //int giTag;
+/* precache */ "", //char *precaches; // string of all models and images this item will use
+/* sounds */ "" //char *sounds; // string of all sounds this item will use
+ }, // leave index 0 alone
+
+ //
+ // WEAPONS
+ //
+
+//WP_NULL_HAND
+/*QUAKED weapon_imod (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_null_hand",
+ "sound/silence.wav", //"sound/weapons/w_pkup.wav",
+ "models/weapons2/hand/hand_w.md3", //"models/weapons2/imod/imod2_w.md3",//world
+ "models/weapons2/hand/hand.md3", //"models/weapons2/imod/imod2.md3", //view
+/* icon */ "icons/w_icon_hand",
+/* pickup */ " ",
+ AMMO_IMOD_CLIP,
+ IT_WEAPON,
+ WP_NULL_HAND,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+
+/*QUAKED weapon_tricorder (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_tricorder",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/tricorder/tricorder_w.md3", //world
+ "models/weapons2/tricorder/tricorder.md3", //view
+/* icon */ "icons/w_icon_tricorder",
+/* pickup */ "Tricorder",
+ AMMO_PHASER_CLIP,
+ IT_WEAPON,
+ WP_TRICORDER,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_padd (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_padd",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/padd/padd_w.md3", //world
+ "models/weapons2/padd/padd.md3", //view
+/* icon */ "icons/w_icon_padd",
+/* pickup */ "Padd",
+ AMMO_PHASER_CLIP,
+ IT_WEAPON,
+ WP_PADD,
+/* precache */ "",
+/* sounds */ "",
+ },
+
+/*QUAKED weapon_scavenger (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_coffee",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/coffeecup/coffee_cup_w.md3", //world
+ "models/weapons2/coffeecup/coffee_cup.md3", //view
+/* icon */ "icons/w_icon_coffee",
+/* pickup */ "Coffee, Black",
+ AMMO_SCAVENGER_CLIP,
+ IT_WEAPON,
+ WP_COFFEE,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_phaser (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_phaser",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/phaser/phaser_w.md3", //world
+ "models/weapons2/phaser/phaser.md3", //view
+/* icon */ "icons/w_icon_phaser",
+/* pickup */ "Phaser",
+ AMMO_PHASER_CLIP,
+ IT_WEAPON,
+ WP_PHASER,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_compressionrifle (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_compressionrifle",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/prifle/prifle_w.md3", //world
+ "models/weapons2/prifle/prifle.md3", //view
+/* icon */ "icons/w_icon_rifle",
+/* pickup */ "Phaser Compression Rifle",
+ AMMO_COMPRESSION_CLIP,
+ IT_WEAPON,
+ WP_COMPRESSION_RIFLE,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_tetriondisruptor (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_tr116",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/tr116/tr-116_w.md3",//world
+ "models/weapons2/tr116/tr-116.md3", //view
+/* icon */ "icons/w_icon_tr116",
+/* pickup */ "TR-116",
+ AMMO_TETRION_CLIP,
+ IT_WEAPON,
+ WP_TR116,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_grenadelauncher",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/launcher/launcher_w.md3", //world
+ "models/weapons2/launcher/launcher.md3", //view
+/* icon */ "icons/w_icon_grenade",
+/* pickup */ "Compound Grenade Launcher",
+ AMMO_GRENADE_CLIP,
+ IT_WEAPON,
+ WP_GRENADE_LAUNCHER,
+/* precache */ "",
+/* sounds */ "sound/weapons/glauncher/bounce1.wav sound/weapons/glauncher/bounce2.wav"
+ },
+
+/*QUAKED weapon_quantumburst (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_quantumburst",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/q_burst/q_burst_w.md3", //world
+ "models/weapons2/q_burst/q_burst.md3", //view
+/* icon */ "icons/w_icon_quantum",
+/* pickup */ "Photon Burst",
+ AMMO_QUANTUM_CLIP,
+ IT_WEAPON,
+ WP_QUANTUM_BURST,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_stasisweapon (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_disruptor",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/alien_disruptor/disruptor_w.md3", //world
+ "models/weapons2/alien_disruptor/disruptor.md3", //view
+/* icon */ "icons/w_icon_disruptor",
+/* pickup */ "Disruptor",
+ AMMO_STASIS_CLIP,
+ IT_WEAPON,
+ WP_DISRUPTOR,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_borg_weapon (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_medkit",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/medkit/medkit_w.md3", //world
+ "models/weapons2/medkit/medkit.md3", //view
+/* icon */ "icons/w_icon_medkit",
+/* pickup */ "Medkit",
+ AMMO_PHASER_CLIP,
+ IT_WEAPON,
+ WP_MEDKIT,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_voyager_hypo (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_voyager_hypo",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/hypospray/hypospray_w.md3", //world
+ "models/weapons2/hypospray/hypospray.md3", //view
+/* icon */ "icons/w_icon_hypo",
+/* pickup */ "Hypo",
+ AMMO_PHASER_CLIP,
+ IT_WEAPON,
+ WP_VOYAGER_HYPO,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_dreadnought (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_dermal_regen",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/dermal_regen/dermal_regen_w.md3",
+ "models/weapons2/dermal_regen/dermal_regen.md3",
+/* icon */ "icons/w_icon_dermalregen",
+/* pickup */ "Dermal Regenerator",
+ AMMO_DREADNOUGHT_CLIP,
+ IT_WEAPON,
+ WP_DERMAL_REGEN,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_borg_assimilator (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_toolkit",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/toolkit/toolkit_w.md3", //world
+ "models/weapons2/toolkit/toolkit.md3", //view
+/* icon */ "icons/w_icon_toolkit",
+/* pickup */ "Toolkit",
+ AMMO_PHASER_CLIP,
+ IT_WEAPON,
+ WP_TOOLKIT,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED weapon_engtool (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+ {
+ "weapon_hyperspanner",
+ "sound/weapons/w_pkup.wav",
+ "models/weapons2/hyperspanner/hyperspanner_w.md3", //world
+ "models/weapons2/hyperspanner/hyperspanner.md3", //view
+/* icon */ "icons/w_icon_hyperspanner",
+/* pickup */ "Hyperspanner",
+ AMMO_PHASER_CLIP,
+ IT_WEAPON,
+ WP_HYPERSPANNER,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+ //
+ // AMMO ITEMS
+ //
+
+/*QUAKED ammo_compressionrifle (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+32 ammo for the compression rifle
+*/
+ {
+ "ammo_compressionrifle",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/prifle_ammo.md3", //world
+ NULL,
+/* icon */ "icons/dm_phaser_sm",
+/* pickup */ "Phaser Compression Rifle Ammo",
+ AMMO_COMPRESSION_CLIP,
+ IT_AMMO,
+ WP_COMPRESSION_RIFLE,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED ammo_imod (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+15 ammo for the I-MOD
+*/
+ {
+ "ammo_imod",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/imod_ammo.md3", //world
+ NULL,
+/* icon */ "icons/dm_imod",
+/* pickup */ "I-MOD Ammo",
+ AMMO_IMOD_CLIP,
+ IT_AMMO,
+ WP_NULL_HAND,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED ammo_scavenger (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+30 ammo for the scavenger rifle
+*/
+ {
+ "ammo_scavenger",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/scavenger_ammo.md3", //world
+ NULL,
+/* icon */ "icons/dm_scav",
+/* pickup */ "Scavenger Weapon Ammo",
+ AMMO_SCAVENGER_CLIP,
+ IT_AMMO,
+ WP_COFFEE,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED ammo_stasis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+15 ammo for the stasis weapon
+*/
+ {
+ "ammo_stasis",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/stasis_ammo.md3", //world
+ NULL,
+/* icon */ "icons/dm_stasis_sm",
+/* pickup */ "Stasis Weapon Ammo",
+ AMMO_STASIS_CLIP,
+ IT_AMMO,
+ WP_DISRUPTOR,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+10 ammo for the grenade launcher
+*/
+ {
+ "ammo_grenades",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/glauncher_ammo.md3", //world
+ NULL,
+/* icon */ "icons/dm_glauncher_sm",
+/* pickup */ "Compound Grenade Launcher Ammo",
+ AMMO_GRENADE_CLIP,
+ IT_AMMO,
+ WP_GRENADE_LAUNCHER,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED ammo_tetriondisruptor (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+40 ammo for the tetrYon disruptor
+*/
+ {
+ "ammo_tetriondisruptor",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/tetrion_ammo.md3", //world
+ NULL,
+/* icon */ "icons/dm_tetrion_sm",
+/* pickup */ "Tetryon Pulse Disruptor Ammo",
+ AMMO_TETRION_CLIP,
+ IT_AMMO,
+ WP_TR116,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED ammo_quantumburst (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+6 ammo for the quantum burst weapon
+*/
+ {
+ "ammo_quantumburst",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/torpedo.md3", //world
+ NULL,
+/* icon */ "icons/dm_torpedo_sm",
+/* pickup */ "Photon Burst Ammo",
+ AMMO_QUANTUM_CLIP,
+ IT_AMMO,
+ WP_QUANTUM_BURST,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED ammo_dreadnought (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+40 ammo for the dreadnought/arc welder
+*/
+ {
+ "ammo_dreadnought",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/arc_ammo.md3", //world
+ NULL,
+/* icon */ "icons/dm_a_arc_sm",
+/* pickup */ "Dermal Regenerator Ammo",
+ AMMO_DREADNOUGHT_CLIP,
+ IT_AMMO,
+ WP_DERMAL_REGEN,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+ //
+ // ARMOR
+ //
+
+/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+5 points of shields
+*/
+ {
+ "item_armor_shard",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/armor_shard.md3", //world
+ NULL,
+/* icon */ "icons/icon_shards",
+/* pickup */ "Incremental Shield Boost",
+ 5,
+ IT_ARMOR,
+ 0,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+50 points of shields
+*/
+ {
+ "item_armor_combat",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/armor.md3", //world
+ NULL,
+/* icon */ "icons/dm_armor_sm",
+/* pickup */ "Personal Deflector Screen",
+ 50,
+ IT_ARMOR,
+ 0,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+100 points of shields
+*/
+ {
+ "item_armor_body",
+ "sound/player/suitenergy.wav",
+ "models/powerups/trek/armor2.md3", //world
+ NULL,
+/* icon */ "icons/dm_superarmor_sm",
+/* pickup */ "Isokinetic Deflector Screen",
+ 100,
+ IT_ARMOR,
+ 0,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+ //
+ // HEALTH
+ //
+
+/*QUAKED item_hypo_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+5 points of health, max of 200
+*/
+ {
+ "item_hypo_small",
+ "sound/player/pickuphealth.wav",
+ "models/powerups/trek/hypo_single.md3", //world
+ NULL,
+/* icon */ "icons/dm_health_sm",
+/* pickup */ "Booster Hypospray",
+ 5,
+ IT_HEALTH,
+ 0,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED item_hypo (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+25 points of health, max of 100
+*/
+ {
+ "item_hypo",
+ "sound/player/suithealth.wav",
+ "models/powerups/trek/hypo_double.md3", //world
+ NULL,
+/* icon */ "icons/dm_health2_sm",
+/* pickup */ "Emergency Hypospray",
+ 25,
+ IT_HEALTH,
+ 0,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+ //
+ // HOLDABLE ITEMS
+ //
+
+/*QUAKED holdable_transporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+pick it up and it stays in your inventory until used, at which time you drop it in front of you and it still
+kind of resides in your inventory. when you use it _again_ it activates and anyone can walk through the transporter.
+*/
+ {
+ "holdable_transporter",
+ "sound/items/holdable.wav",
+ "models/powerups/trek/transporter.md3", //world
+ NULL,
+/* icon */ "icons/dm_transport_sm",
+/* pickup */ "Personal Transporter Device",
+ 60,
+ IT_HOLDABLE,
+ HI_TRANSPORTER,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+pick it up and it stays in your inventory until used, at which time it sets your health to 100
+*/
+ {
+ "holdable_medkit",
+ "sound/items/holdable.wav",
+ "models/powerups/trek/med_kit.md3", //world
+ NULL,
+/* icon */ "icons/dm_health3_sm",
+/* pickup */ "Portable Medkit",
+ 60,
+ IT_HOLDABLE,
+ HI_MEDKIT,
+/* precache */ "",
+/* sounds */ "sound/items/use_medkit.wav"
+ },
+
+ //
+ // POWERUP ITEMS
+ //
+
+
+//Transporter ent - quad no more
+/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+multiplies your weapon's damage for 30 seconds
+*/
+// {
+// "item_quad",
+// "sound/items/quaddamage.wav",
+// "models/powerups/trek/quad_damage.md3", //world
+// NULL,
+///* icon */ "icons/dm_quad",
+///* pickup */ "Quantum Weapon Enhancer",
+// 30,
+// IT_POWERUP,
+// PW_QUAD,
+///* precache */ "",
+///* sounds */ "sound/items/damage3.wav"
+// },
+
+/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+20 seconds of invulnerability
+*/
+// {
+// "item_enviro",
+// "sound/items/protect.wav",
+// "models/powerups/trek/armor3.md3", //world
+// NULL,
+///* icon */ "icons/envirosuit",
+///* pickup */ "Metaphasic Shielding",
+// 20,
+// IT_POWERUP,
+// PW_BOLTON,
+///* precache */ "",
+///* sounds */ "sound/items/protect3.wav"
+// },
+
+/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+for 30 seconds you run at %150 of your normal speed and your firing delays are 3/4 as long
+*/
+ {
+ "item_haste",
+ "sound/items/haste.wav",
+ "models/powerups/trek/haste.md3", //world
+ NULL,
+/* icon */ "icons/dm_haste",
+/* pickup */ "Temporal Accelerator",
+ 30,
+ IT_POWERUP,
+ PW_HASTE,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+/*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+20 seconds of invisibility
+*/
+ {
+ "item_invis",
+ "sound/items/invisibility.wav",
+ "models/powerups/trek/invisible.md3", //world
+ NULL,
+/* icon */ "icons/dm_invisibility",
+/* pickup */ "Personal Cloaking Device",
+ 20,
+ IT_POWERUP,
+ PW_INVIS,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+//TiM : No regen. it's a laser now
+/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+for 30 seconds you get 5 health every second, up to 200 health
+*/
+// {
+// "item_regen",
+// "sound/items/regeneration.wav",
+// "models/powerups/trek/regen.md3", //world
+// NULL,
+///* icon */ "icons/regen",
+///* pickup */ "Nano-Regenerative Protoplasmer",
+// 30,
+// IT_POWERUP,
+// PW_LASER,
+///* precache */ "",
+///* sounds */ "sound/items/regen.wav"
+// },
+
+/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+30 seconds of flight
+*/
+ {
+ "item_flight",
+ "sound/items/flight.wav",
+ "models/powerups/trek/flight.md3", //world
+ NULL,
+/* icon */ "icons/dm_flight",
+/* pickup */ "Anti-Gravity Pack",
+ 30,
+ IT_POWERUP,
+ PW_FLIGHT,
+/* precache */ "",
+/* sounds */ "sound/items/flight.wav"
+ },
+
+/*QUAKED team_CTF_redflag (1 0 0) (-24 -24 -16) (24 24 32)
+Only in CTF games
+*/
+ //{
+ // "team_CTF_redflag",
+ // "sound/voice/computer/misc/haveflag.wav",
+ // "models/flags/flag_red.md3", //world !! must match cg_main media and botfiles/items.c !!
+ // NULL,
+///* icon */ "icons/iconf_red",
+///* pickup */ "Red Flag",
+// 0,
+// IT_TEAM,
+// PW_REDFLAG,
+///* precache */ "",
+///* sounds */ "sound/voice/computer/misc/stolen.wav sound/voice/computer/misc/stolen_e.wav sound/voice/computer/misc/returned.wav sound/voice/computer/misc/returned_e.wav"
+// },
+
+/*QUAKED team_CTF_blueflag (0 0 1) (-24 -24 -16) (24 24 32)
+Only in CTF games
+*/
+// {
+// "team_CTF_blueflag",
+// "sound/voice/computer/misc/haveflag.wav",
+ // "models/flags/flag_blue.md3",//must match cg_main media and botfiles/items.c
+// NULL,
+///* icon */ "icons/iconf_blu",
+///* pickup */ "Blue Flag",
+// 0,
+// IT_TEAM,
+// PW_REDFLAG,
+///* precache */ "",
+///* sounds */ "sound/voice/computer/misc/dropped.wav sound/voice/computer/misc/dropped_e.wav sound/voice/computer/misc/scored.wav sound/voice/computer/misc/scored_e.wav"
+// },
+
+/*QUAKED holdable_detpack (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+BLAMMO!
+*/
+ {
+ "holdable_detpack",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/detpak.md3", //world
+ NULL,
+/* icon */ "icons/icon_detpack",
+/* pickup */ "Ultritium Explosive Charge",
+ 1, // 5,
+ IT_HOLDABLE,
+ HI_DETPACK,
+/* precache */ "",
+/* sounds */ "sound/weapons/detpacklatch.wav sound/weapons/explosions/detpakexplode.wav"
+ },
+
+//TiM No more seeker - flashlight now
+/*QUAKED item_seeker (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+30 seconds of seeker drone
+*/
+// {
+// "item_seeker",
+// "sound/player/pickupenergy.wav",
+// "models/powerups/trek/flyer.md3", //world
+// NULL,
+///* icon */ "icons/icon_seeker",
+///* pickup */ "Seeker Drone",
+// 30,
+// IT_POWERUP,
+// PW_FLASHLIGHT,
+///* precache */ "",
+///* sounds */ ""
+// },
+
+/*QUAKED holdable_shield (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+About 25 seconds or 250 hit points of a portashield.
+*/
+ {
+ "holdable_shield",
+ "sound/player/pickupenergy.wav",
+ "models/powerups/trek/shield_gen.md3", //world
+ NULL,
+/* icon */ "icons/icon_shield",
+/* pickup */ "Portable Force Field",
+ 1,
+ IT_HOLDABLE,
+ HI_SHIELD,
+/* precache */ "",
+/* sounds */ "sound/weapons/detpacklatch.wav sound/movers/forceup.wav sound/ambience/spark5.wav"
+ },
+
+
+/*QUAKED Holographic_decoy (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+About 1 minute of a holographic decoy.
+*/
+ {
+ "Holographic_decoy",
+ "sound/items/holdable.wav",
+ "models/powerups/trek/decoy.md3", //world
+ NULL,
+/* icon */ "icons/icon_decoy",
+/* pickup */ "Holographic Decoy",
+ 1,
+ IT_HOLDABLE,
+ HI_DECOY,
+/* precache */ "",
+/* sounds */ ""
+ },
+
+ //
+ // New Weapons
+ //
+
+/*QUAKED weapon_tr116 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
+*/
+// {
+// "weapon_tr116",
+// "sound/weapons/w_pkup.wav",
+// "models/weapons2/tr116/tr116_w.md3", //world
+// "models/weapons2/tr116/tr116.md3", //view
+///* icon */ "icons/w_icon_tr116",
+///* pickup */ "TR-116",
+// AMMO_PHASER_CLIP,
+// IT_WEAPON,
+// WP_TR116,
+///* precache */ "",
+///* sounds */ ""
+// },
+
+ // end of list marker
+ {NULL}
+};
+
+int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1;
+
+#define STAND_VIEWHEIGHT (DEFAULT_VIEWHEIGHT)
+#define SITTING_VIEWHEIGHT (22) //TiM: To reduce redundancy here
+#define CROUCHING_VIEWHEIGHT (CROUCH_VIEWHEIGHT)
+#define HITBOX_DEFAULT 32
+#define HITBOX_CROUCH 16
+#define HITBOX_NULL -23 //0 //-24 doesn't work on patch meshes apparently. That might be more the mapper's fault than mine tho lol
+#define NULL_ANIM -1
+
+//!Main emotes definition arrayzor
+//Suffice it to say... my hands hurt after writing this thing >.<
+emoteList_t bg_emoteList[] = {
+ //name //type //enumName //enumLoop //viewHeight //hitBox Height //bodyFlags //animFlags
+ { "alert", TYPE_MISC, -1, NULL_ANIM, 0, 0, 0, 0, },
+ { "alert2", TYPE_MISC, -1, NULL_ANIM, 0, 0, 0, 0, },
+ { "assimilated", TYPE_FULLBODY, BOTH_ASSIMILATED1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_ALL | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "", TYPE_NONE, -1, NULL_ANIM, 0, 0, 0, 0, },
+ { "benchsit1_2stand", TYPE_SITTING, BOTH_BENCHSIT1_2STAND, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "benchsit1_fixboot", TYPE_SITTING, BOTH_BENCHSIT1_FIXBOOT, BOTH_BENCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_REVERTLOOP_BOTH },
+ { "benchsit1_idle", TYPE_SITTING, BOTH_BENCHSIT1_IDLE, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH | EMOTE_OVERRIDE_BOTH },
+ { "benchsit1to2", TYPE_SITTING, BOTH_BENCHSIT1TO2, BOTH_BENCHSIT2_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "benchsit2_idle", TYPE_SITTING, BOTH_BENCHSIT2_IDLE, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH | EMOTE_OVERRIDE_BOTH },
+ { "benchsit2to1", TYPE_SITTING, BOTH_BENCHSIT2TO1, BOTH_BENCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "catch1", TYPE_FULLBODY, BOTH_CATCH1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "console1", TYPE_CONSOLE, BOTH_CONSOLE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console1_idle", TYPE_CONSOLE, BOTH_CONSOLE1IDLE, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "console1_left", TYPE_CONSOLE, BOTH_CONSOLE1LEFT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console1_right", TYPE_CONSOLE, BOTH_CONSOLE1RIGHT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console2", TYPE_CONSOLE, BOTH_CONSOLE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "console3", TYPE_CONSOLE, BOTH_CONSOLE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console3_idle", TYPE_CONSOLE, BOTH_CONSOLE3IDLE, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console3_left", TYPE_CONSOLE, BOTH_CONSOLE3LEFT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console3_right", TYPE_CONSOLE, BOTH_CONSOLE3RIGHT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console4", TYPE_CONSOLE, BOTH_CONSOLE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "console5", TYPE_CONSOLE, BOTH_CONSOLE5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "couchsit1_2stand", TYPE_SITTING, BOTH_COUCHSIT1_2STAND1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "couchsit1_left", TYPE_SITTING, BOTH_COUCHSIT1_GESTURELEFT, BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "couchsit1_right", TYPE_SITTING, BOTH_COUCHSIT1_GESTURERIGHT,BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "couchsit1_idle", TYPE_SITTING, BOTH_COUCHSIT1_IDLE, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "couchsit1_talk", TYPE_SITTING, BOTH_COUCHSIT1_TALKGESTURE, BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "couchsit2to1", TYPE_SITTING, BOTH_COUCHSIT1_TO2, BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_REVERTLOOP_UPPER },
+ { "couchsit2_idle", TYPE_SITTING, BOTH_COUCHSIT2, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "coverup1_end", TYPE_FULLBODY, BOTH_COVERUP1_END, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "coverup1_loop", TYPE_FULLBODY, BOTH_COVERUP1_LOOP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "coverup1_start", TYPE_FULLBODY, BOTH_COVERUP1_START, BOTH_COVERUP1_LOOP, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "cowar1", TYPE_FULLBODY, BOTH_COWAR1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "crowdlook1", TYPE_FULLBODY, BOTH_CROWDLOOK1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "crowdlook2", TYPE_FULLBODY, BOTH_CROWDLOOK2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "crowdlook3", TYPE_MISC, BOTH_CROWDLOOK3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH | EMOTE_LOOP_BOTH },
+ { "crowdlook4", TYPE_MISC, BOTH_CROWDLOOK4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "dive1", TYPE_FULLBODY, BOTH_DIVE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_ALL | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "eyes_shut", TYPE_MISC, -1, 0, 0, 0, 0, 0, },
+ { "eyes_angry", TYPE_MISC, -1, 0, 0, 0, 0, 0, },
+ { "gesture2", TYPE_GESTURE, BOTH_GESTURE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "gesture3", TYPE_GESTURE, BOTH_GESTURE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "get_up1", TYPE_FULLBODY, BOTH_GET_UP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "grab1", TYPE_FULLBODY, BOTH_GRAB1, BOTH_GRAB2, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "grab2", TYPE_FULLBODY, BOTH_GRAB2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "grab3", TYPE_FULLBODY, BOTH_GRAB3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "grab4", TYPE_FULLBODY, BOTH_GRAB4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "grabbed1", TYPE_FULLBODY, BOTH_GRABBED1, BOTH_GRABBED2, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "grabbed2", TYPE_FULLBODY, BOTH_GRABBED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "groundshake1", TYPE_FULLBODY, BOTH_GROUNDSHAKE1, BOTH_GROUNDSHAKE1LOOP, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "groundshake1loop", TYPE_FULLBODY, BOTH_GROUNDSHAKE1LOOP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "groundshake2", TYPE_FULLBODY, BOTH_GROUNDSHAKE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "guard_idle1", TYPE_MISC, BOTH_GUARD_IDLE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "guard_lkrt1", TYPE_MISC, BOTH_GUARD_LKRT1, BOTH_GUARD_IDLE1, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "guard_lookaround1", TYPE_MISC, BOTH_GUARD_LOOKAROUND1, BOTH_GUARD_IDLE1, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "guilt1", TYPE_FULLBODY, BOTH_GUILT1, NULL_ANIM, CROUCHING_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "hitwall1", TYPE_FULLBODY, BOTH_HITWALL1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "help1", TYPE_FULLBODY, BOTH_HELP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "injured1", TYPE_INJURED, BOTH_INJURED1, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, },
+ { "injured2", TYPE_INJURED, BOTH_INJURED2, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, },
+ { "injured3", TYPE_INJURED, BOTH_INJURED3, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, },
+ { "injured4", TYPE_INJURED, BOTH_INJURED4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, },
+ { "injured4to5", TYPE_INJURED, BOTH_INJURED4TO5, BOTH_INJURED5, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "injured5", TYPE_INJURED, BOTH_INJURED5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "injured6", TYPE_INJURED, BOTH_INJURED6, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "injured6_combadge", TYPE_INJURED, BOTH_INJURED6COMBADGE, BOTH_INJURED6, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "injured6_point", TYPE_INJURED, BOTH_INJURED6POINT, BOTH_INJURED6, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "kneel_hand1", TYPE_MISC, BOTH_KNEELHAND1, NULL_ANIM, CROUCHING_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "laugh1", TYPE_FULLBODY, BOTH_LAUGH2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "laugh2", TYPE_FULLBODY, BOTH_LAUGH1, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "lean1", TYPE_MISC, BOTH_LEAN1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "pain2writhe1", TYPE_FULLBODY, BOTH_PAIN2WRITHE1, BOTH_WRITHING1, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "possessed1", TYPE_FULLBODY, BOTH_POSSESSED1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "possessed2", TYPE_FULLBODY, BOTH_POSSESSED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "psychicshock1", TYPE_FULLBODY, BOTH_PSYCHICSHOCK1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "psychicshock2", TYPE_FULLBODY, BOTH_PSYCHICSHOCK2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "scared2", TYPE_FULLBODY, BOTH_SCARED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "shield1", TYPE_FULLBODY, BOTH_SHIELD1, BOTH_SHIELD2, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "shield2", TYPE_FULLBODY, BOTH_SHIELD2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH },
+ { "sit1stand", TYPE_SITTING, BOTH_SIT1STAND, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_BOTH },
+ { "sit1to2", TYPE_SITTING, BOTH_SIT1TO2, BOTH_SIT2, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit1to3", TYPE_SITTING, BOTH_SIT1TO3, BOTH_SIT3, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit1", TYPE_SITTING, BOTH_SIT1, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sit2to1", TYPE_SITTING, BOTH_SIT2TO1, BOTH_SIT1, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit2to3", TYPE_SITTING, BOTH_SIT2TO3, BOTH_SIT3, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit2", TYPE_SITTING, BOTH_SIT2, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sit3to1", TYPE_SITTING, BOTH_SIT3TO1, BOTH_SIT1, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit3to2", TYPE_SITTING, BOTH_SIT3TO2, BOTH_SIT2, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit3", TYPE_SITTING, BOTH_SIT3, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sit4to5", TYPE_SITTING, BOTH_SIT4TO5, BOTH_SIT5, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit4to6", TYPE_SITTING, BOTH_SIT4TO6, BOTH_SIT6, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit4", TYPE_SITTING, BOTH_SIT4, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sit5to4", TYPE_SITTING, BOTH_SIT5TO4, BOTH_SIT4, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit5to6", TYPE_SITTING, BOTH_SIT5TO6, BOTH_SIT6, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit5", TYPE_SITTING, BOTH_SIT5, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sit6to4", TYPE_SITTING, BOTH_SIT6TO4, BOTH_SIT4, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit6to5", TYPE_SITTING, BOTH_SIT6TO5, BOTH_SIT5, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sit6", TYPE_SITTING, BOTH_SIT6, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sit7", TYPE_SITTING, BOTH_SIT7, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH, },
+ { "sit7tostand1", TYPE_SITTING, BOTH_SIT7TOSTAND1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sleep1", TYPE_MISC, BOTH_SLEEP1, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sleep1_nose", TYPE_MISC, BOTH_SLEEP1_NOSE, BOTH_SLEEP1, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sleep1_getup", TYPE_MISC, BOTH_SLEEP1GETUP, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sleep2", TYPE_MISC, BOTH_SLEEP2, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sleep2_shift", TYPE_MISC, BOTH_SLEEP2_SHIFT, BOTH_SLEEP2, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sleep2_getup", TYPE_MISC, BOTH_SLEEP2GETUP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "sleep3", TYPE_MISC, BOTH_SLEEP3, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "sleep3getup", TYPE_MISC, BOTH_SLEEP3GETUP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "snapto1", TYPE_FULLBODY, BOTH_SNAPTO1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "snapto2", TYPE_FULLBODY, BOTH_SNAPTO2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random1", TYPE_GESTURE, BOTH_STAND1_RANDOM2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random2", TYPE_GESTURE, BOTH_STAND1_RANDOM3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random3", TYPE_GESTURE, BOTH_STAND1_RANDOM4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random4", TYPE_GESTURE, BOTH_STAND1_RANDOM5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random5", TYPE_GESTURE, BOTH_STAND1_RANDOM6, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random6", TYPE_GESTURE, BOTH_STAND1_RANDOM7, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random7", TYPE_GESTURE, BOTH_STAND1_RANDOM8, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random8", TYPE_GESTURE, BOTH_STAND1_RANDOM9, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random9", TYPE_GESTURE, BOTH_STAND1_RANDOM10, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand1_random10", TYPE_GESTURE, BOTH_STAND1_RANDOM11, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random1", TYPE_GESTURE, BOTH_STAND2_RANDOM1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random2", TYPE_GESTURE, BOTH_STAND2_RANDOM2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random3", TYPE_GESTURE, BOTH_STAND2_RANDOM3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random4", TYPE_GESTURE, BOTH_STAND2_RANDOM4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random5", TYPE_GESTURE, BOTH_STAND2_RANDOM5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random6", TYPE_GESTURE, BOTH_STAND2_RANDOM6, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random7", TYPE_GESTURE, BOTH_STAND2_RANDOM7, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random8", TYPE_GESTURE, BOTH_STAND2_RANDOM8, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random9", TYPE_GESTURE, BOTH_STAND2_RANDOM9, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random10", TYPE_GESTURE, BOTH_STAND2_RANDOM10, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random11", TYPE_GESTURE, BOTH_STAND2_RANDOM11, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand2_random12", TYPE_GESTURE, BOTH_STAND2_RANDOM12, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ { "stand3", TYPE_MISC, BOTH_STAND3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "standup1", TYPE_FULLBODY, BOTH_STANDUP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "surprised1", TYPE_FULLBODY, BOTH_SURPRISED1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER },
+ { "surprised2", TYPE_FULLBODY, BOTH_SURPRISED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER },
+ { "surprised3", TYPE_FULLBODY, BOTH_SURPRISED3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER },
+ { "surprised4", TYPE_FULLBODY, BOTH_SURPRISED4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH },
+ { "surprised5", TYPE_FULLBODY, BOTH_SURPRISED5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER },
+ { "table_eat1", TYPE_MISC, BOTH_TABLE_EAT1, BOTH_TABLE_IDLE1, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "table_getup1", TYPE_MISC, BOTH_TABLE_GETUP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "table_idle1", TYPE_MISC, BOTH_TABLE_IDLE1, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "table_talkgesture1", TYPE_MISC, BOTH_TABLE_TALKGESTURE1, BOTH_TABLE_IDLE1, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER },
+ { "talkgesture1", TYPE_GESTURE, BOTH_TALKGESTURE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "talkgesture2", TYPE_GESTURE, BOTH_TALKGESTURE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "talkgesture3", TYPE_GESTURE, TORSO_TALKGESTURE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "talkgesture4", TYPE_GESTURE, TORSO_TALKGESTURE5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "writhing2", TYPE_FULLBODY, BOTH_WRITHING2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH },
+ { "combadge1", TYPE_GESTURE, TORSO_COMBADGE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "combadge2", TYPE_GESTURE, TORSO_COMBADGE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "combadge3", TYPE_GESTURE, TORSO_COMBADGE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "combadge4", TYPE_GESTURE, TORSO_COMBADGE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "equipment1", TYPE_GESTURE, TORSO_EQUIPMENT1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "equipment2", TYPE_GESTURE, TORSO_EQUIPMENT2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "equipment3", TYPE_GESTURE, TORSO_EQUIPMENT3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "grablbackl", TYPE_GESTURE, TORSO_GRABLBACKL, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "hand1", TYPE_GESTURE, TORSO_HAND1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "hand2", TYPE_GESTURE, TORSO_HAND2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture1", TYPE_GESTURE, TORSO_HANDGESTURE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture2", TYPE_GESTURE, TORSO_HANDGESTURE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture3", TYPE_GESTURE, TORSO_HANDGESTURE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture4", TYPE_GESTURE, TORSO_HANDGESTURE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture5", TYPE_GESTURE, TORSO_HANDGESTURE5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture6", TYPE_GESTURE, TORSO_HANDGESTURE6, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture7", TYPE_GESTURE, TORSO_HANDGESTURE7, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture8", TYPE_GESTURE, TORSO_HANDGESTURE8, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture9", TYPE_GESTURE, TORSO_HANDGESTURE9, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture10", TYPE_GESTURE, TORSO_HANDGESTURE10, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture11", TYPE_GESTURE, TORSO_HANDGESTURE11, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture12", TYPE_GESTURE, TORSO_HANDGESTURE12, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "handgesture13", TYPE_GESTURE, TORSO_HANDGESTURE13, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "hypospray", TYPE_GESTURE, TORSO_HYPOSPRAY1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "pokeridle1", TYPE_GESTURE, TORSO_POKERIDLE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER },
+ { "pokeridle2", TYPE_GESTURE, TORSO_POKERIDLE2, TORSO_POKERIDLE1, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER },
+ { "pokeridle3", TYPE_GESTURE, TORSO_POKERIDLE3, TORSO_POKERIDLE1, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "shout1", TYPE_GESTURE, TORSO_SHOUT1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER },
+ { "speechless1", TYPE_GESTURE, TORSO_SPEECHLESS1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER },
+ { "speechless2", TYPE_GESTURE, TORSO_SPEECHLESS2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER },
+ { "taunt", TYPE_GESTURE, TORSO_GESTURE, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ { "wrist1", TYPE_GESTURE, TORSO_WRIST1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+};
+
+ //{ "gesture1", BOTH_GESTURE1, NULL_ANIM, 0, 0, MOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ //{ "stand1_random1", BOTH_STAND1_RANDOM1, NULL_ANIM, 0, 0, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH },
+ //{ "talkgesture5", TORSO_TALKGESTURE6, NULL_ANIM, 0, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+ //{ "headshake1", TORSO_HEADSHAKE1, NULL_ANIM, 0, 0, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER },
+
+
+//TiM : Hrmm... this may have been why it was crashing on some people's PCs...
+//Let's try the old fashioned way...
+//int bg_numEmotes = sizeof( emoteList ) / sizeof ( emoteList[0] );
+int bg_numEmotes = 180;
+
+//TiM:
+//!In order to set up a list of items we can use in the 'give' command.
+giveItem_t bg_giveItem[] = {
+ //consoleName giveType giveValue
+ { "all", TYPE_ALL, 0 },
+ { "health", TYPE_HEALTH, 0 },
+ { "weapons", TYPE_WEAPONS, 0 },
+ { "ammo", TYPE_AMMO, 0 },
+ { "transporter", TYPE_HOLDABLE, HI_TRANSPORTER },
+ { "forcefield", TYPE_HOLDABLE, HI_SHIELD },
+ { "phaser", TYPE_WEAPON, WP_PHASER },
+ { "phaser_rifle", TYPE_WEAPON, WP_COMPRESSION_RIFLE },
+ { "coffee", TYPE_WEAPON, WP_COFFEE },
+ { "disruptor", TYPE_WEAPON, WP_DISRUPTOR },
+ { "coffee", TYPE_WEAPON, WP_COFFEE },
+ { "admin_gun", TYPE_WEAPON, WP_GRENADE_LAUNCHER },
+ { "tr-116", TYPE_WEAPON, WP_TR116 },
+ { "photon_burst", TYPE_WEAPON, WP_QUANTUM_BURST },
+ { "dermal_regen", TYPE_WEAPON, WP_DERMAL_REGEN },
+ { "hypospray", TYPE_WEAPON, WP_VOYAGER_HYPO },
+ { "toolkit", TYPE_WEAPON, WP_TOOLKIT },
+ { "medkit", TYPE_WEAPON, WP_MEDKIT },
+ { "tricorder", TYPE_WEAPON, WP_TRICORDER },
+ { "padd", TYPE_WEAPON, WP_PADD },
+ { "hyperspanner", TYPE_WEAPON, WP_HYPERSPANNER },
+ { "cloak", TYPE_POWERUP, 0 },
+ { "flight", TYPE_POWERUP, 0 },
+ { "god", TYPE_POWERUP, 0 }
+};
+
+//TiM - Meh... just define the number... doing fancy array stuff seems to make PCs 'splode O_o
+int bg_numGiveItems = 24;
+
+/**
+* \brief Finds an item by it's classname
+*
+* \return the item
+*/
+gitem_t *BG_FindItemWithClassname(const char *name)
+{
+ int i = 0;
+ gitem_t *item = NULL;
+
+ if ( (NULL == name) || (0 == name[0]) )
+ {
+ return NULL;
+ }
+ for (i = 0; i < bg_numItems; i++)
+ {
+ item = &bg_itemlist[i];
+ if (!strcmp(name, item->classname))
+ {
+ return item;
+ }
+ }
+ return NULL;
+}
+
+/**
+* Finds the classname for a holdable.
+* \return classname for holdable
+*/
+char *BG_FindClassnameForHoldable(holdable_t pw)
+{
+ gitem_t *item = BG_FindItemForHoldable(pw);
+
+ if (item)
+ {
+ return item->classname;
+ }
+ return NULL;
+}
+
+/**
+* Finds item for a powerup.
+* \return the item
+*/
+gitem_t *BG_FindItemForPowerup( powerup_t pw ) {
+ int i;
+
+ for ( i = 0 ; i < bg_numItems ; i++ ) {
+ if ( (bg_itemlist[i].giType == IT_POWERUP ||
+ bg_itemlist[i].giType == IT_TEAM) &&
+ bg_itemlist[i].giTag == pw ) {
+ return &bg_itemlist[i];
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+* Finds item for a holdable.
+* \return the item
+*/
+gitem_t *BG_FindItemForHoldable( holdable_t pw ) {
+ int i;
+
+ for ( i = 0 ; i < bg_numItems ; i++ ) {
+ if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) {
+ return &bg_itemlist[i];
+ }
+ }
+
+ Com_Error( ERR_DROP, "HoldableItem not found" );
+
+ return NULL;
+}
+
+
+/**
+* Finds item for a weapon.
+* \return the item
+*/
+gitem_t *BG_FindItemForWeapon( weapon_t weapon ) {
+ gitem_t *it;
+ //if(ent->client->sess.sessionClass){
+ //}
+ for ( it = bg_itemlist + 1 ; it->classname ; it++) {
+ if ( it->giType == IT_WEAPON && it->giTag == weapon ) {
+ return it;
+ }
+ }
+
+ Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon);
+ return NULL;
+}
+
+/**
+* Finds item for ammo.
+* \return the item
+*/
+gitem_t *BG_FindItemForAmmo( weapon_t weapon ) {
+ gitem_t *it;
+
+ for ( it = bg_itemlist + 1 ; it->classname ; it++) {
+ if ( it->giType == IT_AMMO && it->giTag == weapon ) {
+ return it;
+ }
+ }
+
+ Com_Error( ERR_DROP, "Couldn't find item for ammo %i", weapon);
+ return NULL;
+}
+
+/**
+* Find a tiem by pickupName.
+* \return the item
+*/
+gitem_t *BG_FindItem( const char *pickupName/*const char *classname*/ ) {
+ gitem_t *it;
+
+ for ( it = bg_itemlist + 1 ; it->classname ; it++ ) {
+ if ( !Q_stricmp( it->pickup_name, pickupName )/*!Q_stricmp( it->classname, classname)*/ )//RPG-X: RedTechie - Trying to fix give cmd
+ return it;
+ }
+
+ return NULL;
+}
+
+/**
+* \brief Checks if player is touching an item.
+*
+* Items can be picked up without actually touching their physical bounds to make
+* grabbing them easier
+*/
+qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) {
+ vec3_t origin;
+
+ BG_EvaluateTrajectory( &item->pos, atTime, origin );
+
+ // we are ignoring ducked differences here
+ if ( ps->origin[0] - origin[0] > 44
+ || ps->origin[0] - origin[0] < -50
+ || ps->origin[1] - origin[1] > 36
+ || ps->origin[1] - origin[1] < -36
+ || ps->origin[2] - origin[2] > 36
+ || ps->origin[2] - origin[2] < -36 ) {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+
+/**
+* \brief Check if item can be grabbed.
+*
+* Returns false if the item should not be picked up.
+* This needs to be the same for client side prediction and server use.
+*/
+
+qboolean BG_CanItemBeGrabbed( const entityState_t *ent, const playerState_t *ps, int maxWeap ) {
+ gitem_t *item;
+
+//_______________________________________________________________
+/* SYNC with g_items global var
+int Max_Ammo[WP_NUM_WEAPONS] =
+{
+ 0, // WP_NONE,
+ 50, // WP_PHASER, !! this should match PHASER_AMMO_MAX defined in bg_public
+ 128, // WP_COMPRESSION_RIFLE,
+ 60, // WP_NULL_HAND,
+ 100, // WP_COFFEE,
+ 50, // WP_DISRUPTOR,
+ 30, // WP_GRENADE_LAUNCHER,
+ 120, // WP_TR116,
+ 20, // WP_QUANTUM_BURST,
+ 120, // WP_DERMAL_REGEN,
+ 100, // WP_VOYAGER_HYPO,
+ 100, // WP_TOOLKIT,
+ 100 // WP_MEDKIT
+
+};
+//_______________________________________________________________
+*/
+
+ if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) {
+ //Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" );
+ //Com_Printf ("BG_CanItemBeGrabbed: index out of range\n");
+ return qfalse;
+ }
+
+ // The player used to not be able to pickup stuff when ghosted. Now it automatically unghosts them.
+ //RPG-X: RedTechie - No we want to keep ghost >:)
+ /*if (ps->powerups[PW_GHOST])
+ {
+ return qfalse;
+ }*/
+
+ item = &bg_itemlist[ent->modelindex];
+
+ //if ( ps->persistant[PERS_CLASS] == PC_BORG && item->giType != IT_TEAM )//FIXME: check for pModAssimilation mode being on
+ //{//borg cannot pick up anything but flags
+ // return qfalse;
+ //}
+
+ // Marcin| 30/12/2008
+ if (ps->ammo[item->giTag] >= maxWeap) {
+ return qfalse;
+ } else {
+ return qtrue;
+ }
+
+ //unused: --> so why it is not commented out?
+
+/* switch( item->giType )
+ {
+ case IT_WEAPON:
+ if ( ( ps->stats[STAT_WEAPONS] & (1 << item->giTag) ) ) // RPG-X | Marcin | 04/12/2008
+ {
+ return qfalse;
+ }
+ return qtrue;
+
+ case IT_AMMO:
+ if ( ps->ammo[ item->giTag ] >= Max_Ammo[ item->giTag ]) {
+ return qfalse; // can't hold any more
+ }
+ //if ( ps->persistant[PERS_CLASS] != PC_NOCLASS )//FIXME: should be checking g_pModSpecialties->integer != 0 )
+ //{
+ // if ( ps->persistant[PERS_CLASS] != PC_ACTIONHERO )
+ // {
+ // //only pick it up if you have the weapon
+ // if ( !(ps->stats[STAT_WEAPONS]&(1<giTag)) )
+ // {//don't have the weapon that uses this ammo
+ // return qfalse;
+ // }
+ // }
+ //}
+ return qtrue;
+
+ case IT_ARMOR:
+ // we also clamp armor to the maxhealth for handicapping
+ if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) {
+ return qfalse;
+ }
+ return qtrue;
+
+ case IT_HEALTH:
+ // small and mega healths will go over the max, otherwise
+ // don't pick up if already at max
+ if ( item->quantity == 5 || item->quantity == 100 ) {
+ if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) {
+ return qfalse;
+ }
+ return qtrue;
+ }
+
+ if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) {
+ return qfalse;
+ }
+ return qtrue;
+
+ case IT_POWERUP:
+ //if ( ps->persistant[PERS_CLASS] == PC_ACTIONHERO )
+ //{
+ // if ( item->giTag == PW_LASER )
+ // {
+ // return qfalse;
+ // }
+ //}
+ //else if ( ps->persistant[PERS_CLASS] != PC_NOCLASS )//FIXME: should be checking g_pModSpecialties->integer != 0 )
+ //{//in specialty mode, only certain classes can use certain powerups
+ // //FIXME: breaks bots!
+ // switch( item->giTag )
+ // {
+ // case PW_QUAD:
+ // if ( ps->persistant[PERS_CLASS] != PC_HEAVY )
+ // {
+ // return qfalse;
+ // }
+ // break;
+ // case PW_BOLTON:
+ // if ( ps->persistant[PERS_CLASS] != PC_MEDIC )
+ // {
+ // return qfalse;
+ // }
+ // break;
+ // case PW_FLIGHT:
+ // if ( ps->persistant[PERS_CLASS] != PC_INFILTRATOR )
+ // {
+ // return qfalse;
+ // }
+ // break;
+ // }
+ //}
+ return qtrue; // powerups are always picked up
+
+ case IT_TEAM: // team items, such as flags
+ // ent->modelindex2 is non-zero on items if they are dropped
+ // we need to know this because we can pick up our dropped flag (and return it)
+ // but we can't pick up our flag at base
+ if (ps->persistant[PERS_TEAM] == TEAM_RED) {
+ //if (item->giTag == PW_BORG_ADAPT ||
+ // (item->giTag == PW_REDFLAG && ent->modelindex2) ||
+ // (item->giTag == PW_REDFLAG && ps->powerups[PW_BORG_ADAPT]))
+ // return qtrue;
+ } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) {
+ //if (item->giTag == PW_REDFLAG ||
+ // (item->giTag == PW_BORG_ADAPT && ent->modelindex2) ||
+ // (item->giTag == PW_BORG_ADAPT && ps->powerups[PW_REDFLAG]))
+ // return qtrue;
+ }
+ return qfalse;
+
+ case IT_HOLDABLE:
+ // can only hold one item at a time
+ if ( ps->stats[STAT_HOLDABLE_ITEM] ) {
+ return qfalse;
+ }
+ return qtrue;
+
+ case IT_BAD:
+ Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" );
+ }
+
+ return qfalse; */
+}
+
+//======================================================================
+
+/**
+* Evaluates a trajectory.
+*/
+void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) {
+ float deltaTime;
+ float phase;
+
+ switch( tr->trType ) {
+ case TR_STATIONARY:
+ case TR_INTERPOLATE:
+ VectorCopy( tr->trBase, result );
+ break;
+ case TR_LINEAR:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorMA( tr->trBase, deltaTime, tr->trDelta, result );
+ break;
+ case TR_SINE:
+ deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration;
+ phase = sin( deltaTime * M_PI * 2 );
+ VectorMA( tr->trBase, phase, tr->trDelta, result );
+ break;
+ case TR_LINEAR_STOP:
+ if ( atTime > tr->trTime + tr->trDuration ) {
+ atTime = tr->trTime + tr->trDuration;
+ }
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ if ( deltaTime < 0 ) {
+ deltaTime = 0;
+ }
+ VectorMA( tr->trBase, deltaTime, tr->trDelta, result );
+ break;
+ case TR_GRAVITY:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorMA( tr->trBase, deltaTime, tr->trDelta, result );
+ result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity...
+ break;
+ default:
+ Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime );
+ break;
+ }
+}
+
+/**
+* Determining velocity at a given time
+*/
+void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) {
+ float deltaTime;
+ float phase;
+
+ switch( tr->trType ) {
+ case TR_STATIONARY:
+ case TR_INTERPOLATE:
+ VectorClear( result );
+ break;
+ case TR_LINEAR:
+ VectorCopy( tr->trDelta, result );
+ break;
+ case TR_SINE:
+ deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration;
+ phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos
+ phase *= 0.5;
+ VectorScale( tr->trDelta, phase, result );
+ break;
+ case TR_LINEAR_STOP:
+ if ( atTime > tr->trTime + tr->trDuration ) {
+ VectorClear( result );
+ return;
+ }
+ VectorCopy( tr->trDelta, result );
+ break;
+ case TR_GRAVITY:
+ deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds
+ VectorCopy( tr->trDelta, result );
+ result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity...
+ break;
+ default:
+ Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime );
+ break;
+ }
+}
+
+/**
+* Handles the sequence numbers
+*/
+void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) {
+ ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent;
+ ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm;
+ ps->eventSequence++;
+}
+
+
+/**
+* \brief Playerstate to entitystate
+*
+* This is done after each set of usercmd_t on the server,
+* and after local prediction on the client
+*/
+void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) {
+ int i;
+ char medicrevive[32];
+ int medicrevive_int;
+
+ //RPG-X: RedTechie - Attempted to fix player going invisible now they just dont go invisible (me being picky) a player is never going to notice this
+ trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 );
+ medicrevive_int = atoi(medicrevive);
+ if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) {
+ s->eType = ET_INVISIBLE;
+ } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) {
+ if(medicrevive_int == 1){
+ s->eType = ET_PLAYER; //RPG-X: RedTechie - No gibbing! Before it was s->eType = ET_INVISIBLE;
+ }else{
+ s->eType = ET_INVISIBLE;
+ }
+ } else {
+ s->eType = ET_PLAYER;
+ }
+
+ s->number = ps->clientNum;
+
+ s->pos.trType = TR_INTERPOLATE;
+ VectorCopy( ps->origin, s->pos.trBase );
+ if ( snap ) {
+ SnapVector( s->pos.trBase );
+ }
+
+ //TiM - Get velocity as well
+ VectorCopy( ps->velocity, s->pos.trDelta );
+ if ( snap )
+ {
+ SnapVector( s->pos.trDelta );
+ }
+
+ s->apos.trType = TR_INTERPOLATE;
+ VectorCopy( ps->viewangles, s->apos.trBase );
+ if ( snap ) {
+ SnapVector( s->apos.trBase );
+ }
+
+ s->angles2[YAW] = ps->movementDir;
+
+ //TiM
+ s->torsoAnim = ps->stats[TORSOANIM];
+ s->legsAnim = ps->stats[LEGSANIM];
+
+ //s->legsAnim = ps->legsAnim;
+ //s->torsoAnim = ps->torsoAnim;
+
+ //TiM : Mental note : DON'T FREEAKIN ACCIDENTLY COMMENT THIS OUT AGAIN! IT'S KINDA IMPORTANT!!!!!!!!!!!!!!!!
+ s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number
+ // so corpses can also reference the proper config
+
+ s->eFlags = ps->eFlags;
+ if ( ( !medicrevive_int && ps->stats[STAT_HEALTH] <= 0 ) ||
+ ( medicrevive_int > 0 && ps->stats[STAT_HEALTH] <= 1 ) )
+ { //RPG-X: TiM: Bah Red... u gotta account for these flags with ur system
+ s->eFlags |= EF_DEAD; //or it screws up the model system
+ } else {
+ s->eFlags &= ~EF_DEAD;
+ }
+
+ //==========================================================================
+ //TiM: ^&$*#^^.... T_T
+ //Okay it's official. eFlags is buggy. Turns out the Ravensoft programmers were
+ //wreaking grief with it too. :P
+ //Although hacky, transposing these flags here is the only way I know to get this data from G to CG.
+
+ //Clamp body (not head) flag
+ if ( ps->stats[EMOTES] & EMOTE_CLAMP_BODY ) {
+ s->eFlags |= EF_CLAMP_BODY;
+ }
+ else {
+ s->eFlags &= ~EF_CLAMP_BODY;
+ }
+
+ //Clamp whole body flags
+ if ( ps->stats[EMOTES] & EMOTE_CLAMP_ALL ) {
+ s->eFlags |= EF_CLAMP_ALL;
+ }
+ else {
+ s->eFlags &= ~EF_CLAMP_ALL;
+ }
+
+ if ( ps->stats[EMOTES] & EMOTE_EYES_SHUT )
+ {
+ s->eFlags |= EF_EYES_SHUT;
+ //Com_Printf( "Eyes were shutted.\n" );
+ }
+ else {
+ s->eFlags &= ~EF_EYES_SHUT;
+ }
+
+ if ( ps->stats[EMOTES] & EMOTE_EYES_PISSED ) {
+ s->eFlags |= EF_EYES_ANGRY;
+ }
+ else {
+ s->eFlags &= ~EF_EYES_ANGRY;
+ }
+
+ //==========================================================================
+
+ if ( ps->externalEvent ) {
+ s->event = ps->externalEvent;
+ s->eventParm = ps->externalEventParm;
+ }
+ else if ( ps->entityEventSequence < ps->eventSequence )
+ {
+ int seq;
+
+ if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) {
+ ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS;
+ }
+ seq = ps->entityEventSequence & (MAX_PS_EVENTS-1);
+ s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
+ s->eventParm = ps->eventParms[ seq ];
+ ps->entityEventSequence++;
+ }
+
+ s->weapon = ps->weapon;
+ s->groundEntityNum = ps->groundEntityNum;
+
+ s->powerups = 0;
+ for ( i = 0 ; i < MAX_POWERUPS ; i++ )
+ {
+ if ( ps->powerups[ i ] ) {
+ s->powerups |= 1 << i;
+ }
+ }
+
+ if ( s->powerups & (1 << PW_FLIGHT) || (ps->powerups[PW_EVOSUIT] && ps->gravity == 0 ) )
+ {
+ s->eFlags |= EF_FULL_ROTATE;
+ }
+ else
+ {
+ s->eFlags &= ~EF_FULL_ROTATE;
+ }
+
+ //TiM: Extra - Transmit the weapons stats as a flag for the 'equip' command
+ s->time2 = ps->stats[STAT_WEAPONS];
+}
+
+#define MAX_ITEMNAMES 45
+
+const char *itemnames[MAX_ITEMNAMES] =
+{
+
+"nothing",
+"WEAPON_NULL_HAND",
+"WEAPON_TRICORDER",
+"WEAPON_PADD",
+"WEAPON_COFFEE",
+"WEAPON_PHASER",
+"WEAPON_COMPRESSIONRIFLE",
+"WEAPON_TR116",
+"WEAPON_GRENADELAUNCHER",
+"WEAPON_QUANTUM",
+"WEAPON_DISRUPTOR",
+"WEAPON_MEDKIT",
+"WEAPON_VOYAGER_HYPO",
+"WEAPON_DERMAL_REGEN",
+"WEAPON_TOOLKIT",
+"WEAPON_NEUTRINO_PROBE",
+
+"AMMO_COMPRESSIONRIFLE",
+"AMMO_IMOD",
+"AMMO_SCAVENGERRIFLE",
+"AMMO_STASIS",
+"AMMO_GRENADELAUNCHER",
+"AMMO_DISRUPTOR",
+"AMMO_QUANTUM",
+"AMMO_DREADNOUGHT",
+
+"ITEM_ARMOR_SHARD",
+"ITEM_ARMOR",
+"ITEM_HEAVY_ARMOR",
+"ITEM_HYPO_SMALL",
+"ITEM_HYPO",
+
+"HOLDABLE_TRANSPORTER",
+"HOLDABLE_MEDKIT",
+"HOLDABLE_QUADDAMAGE",
+"HOLDABLE_BATTLESUIT",
+"HOLDABLE_SPEED",
+"HOLDABLE_INVISIBILITY",
+"HOLDABLE_REGENERATION",
+"HOLDABLE_FLIGHT",
+"HOLDABLE_REDFLAG",
+"HOLDABLE_BLUEFLAG",
+"HOLDABLE_DETPACK",
+"ITEM_SEEKER",
+"HOLDABLE_SHIELD",
+"HOLOGRAPHIC_DECOY", // decoy temp
+
+"WEAPON_TR116",
+
+NULL
+};
+
+
+#define MAX_ITEMNAMEFILE 10000 //5000 har har
+char itemNameBuffer[MAX_ITEMNAMEFILE];
+
+/**
+* Explains itself.
+*/
+void BG_ParseItemsText(char *buff)
+{
+ char *token;
+ char *buffer;
+ int i,len;
+
+ COM_BeginParseSession();
+
+ buffer = buff;
+ while ( buffer )
+ {
+ token = COM_ParseExt( &buffer, qtrue );
+
+ i=0;
+ while (itemnames[i])
+ {
+ if (Q_stricmp(token, itemnames[i])==0)
+ {
+ token = COM_ParseExt( &buffer, qtrue );
+ if (token[0])
+ {
+ len = strlen(token);
+ if (len)
+ {
+ bg_itemlist[i].pickup_name = (buffer - (len + 1)); // The +1 is to get rid of the " at the beginning of the sting.
+ *(buffer - 1) = '\0'; // Place an string end where is belongs.
+ }
+ }
+
+ break;
+ }
+ i++;
+ }
+
+ }
+}
+
+/*
+* Creates a filename with an extension based on the value in g_language
+*/
+void BG_LanguageFilename(char *baseName,char *baseExtension,char *finalName)
+{
+ char language[32];
+ fileHandle_t file;
+
+ trap_Cvar_VariableStringBuffer( "g_language", language, 32 );
+
+ // If it's English then no extension
+ if (language[0]=='\0' || Q_stricmp ("ENGLISH",language)==0)
+ {
+ Com_sprintf(finalName,MAX_QPATH,"%s.%s",baseName,baseExtension);
+ }
+ else
+ {
+ Com_sprintf(finalName,MAX_QPATH,"%s_%s.%s",baseName,language,baseExtension);
+
+ //Attempt to load the file
+ trap_FS_FOpenFile( finalName, &file, FS_READ );
+
+ if ( file == 0 ) // This extension doesn't exist, go English.
+ {
+ Com_sprintf(finalName,MAX_QPATH,"%s.%s",baseName,baseExtension); //the caller will give the error if this isn't there
+ }
+ else
+ {
+ trap_FS_FCloseFile( file );
+ }
+ }
+}
+
+/**
+* Loads item names.
+*/
+void BG_LoadItemNames(void)
+{
+ char fileName[MAX_QPATH];
+ int len;
+ fileHandle_t f;
+
+ BG_LanguageFilename("ext_data/mp_itemnames","dat",fileName);
+
+ len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+
+ if ( !f )
+ {
+ Com_Printf( S_COLOR_RED "BG_LoadItemNames : MP_ITEMNAMES.DAT file not found!\n");
+ return;
+ }
+
+ if ( len > MAX_ITEMNAMEFILE )
+ {
+ Com_Printf( S_COLOR_RED "BG_LoadItemNames : MP_ITEMNAMES.DAT too big!\n");
+ return;
+ }
+
+ // initialise the data area
+ memset(itemNameBuffer, 0, sizeof(itemNameBuffer));
+
+ trap_FS_Read( itemNameBuffer, len, f );
+
+ trap_FS_FCloseFile( f );
+
+ BG_ParseItemsText(itemNameBuffer);
+
+}
+
+/**
+* Read a configuration file to get the sex
+* models/players_rpgx/munro/animation.cfg
+*/
+#ifdef Q3_VM
+static gender_t G_ParseAnimationFileSex( const char *filename) {
+ char *text_p;
+ int len;
+ char *token;
+ char text[20000];
+ fileHandle_t f;
+ char animfile[MAX_QPATH];
+
+ //strcpy(animfile, filename);
+ Q_strncpyz(animfile, filename, sizeof(animfile));
+ len = strlen(animfile);
+ strcpy(&animfile[len-strlen("groups.cfg")], "animation.cfg");
+
+ // load the file
+ len = trap_FS_FOpenFile( animfile, &f, FS_READ );
+ if ( len <= 0 ) {
+ return GENDER_NEUTER;
+ }
+ if ( len >= sizeof( text ) - 1 ) {
+ Com_Printf( "File %s too long\n", animfile );
+ trap_FS_FCloseFile( f );
+ return GENDER_NEUTER;
+ }
+ trap_FS_Read( text, len, f );
+ text[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while ( 1 ) {
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( !Q_stricmp( token, "sex" ) ) {
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( token[0] == 'f' || token[0] == 'F' ) {
+ return GENDER_FEMALE;
+ } else if ( token[0] == 'n' || token[0] == 'N' ) {
+ return GENDER_NEUTER;
+ } else {
+ return GENDER_MALE;
+ }
+ }
+ }
+ return GENDER_MALE;
+}
+#else
+static gender_t G_ParseAnimationFileSex( const char *filename) {
+ char *text_p;
+ int len;
+ char *token;
+ char *text;
+ fileHandle_t f;
+ char animfile[MAX_QPATH];
+
+ //strcpy(animfile, filename);
+ Q_strncpyz(animfile, filename, sizeof(animfile));
+ len = strlen(animfile);
+ strcpy(&animfile[len-strlen("groups.cfg")], "animation.cfg");
+
+ // load the file
+ len = trap_FS_FOpenFile( animfile, &f, FS_READ );
+ if ( len <= 0 ) {
+ return GENDER_NEUTER;
+ }
+
+ text = (char *)malloc(20000 * sizeof(char));
+ if(!text) {
+ trap_FS_FCloseFile(f);
+ Com_Printf( "Was unable to allocate %i bytes.\n", 20000 * sizeof(char) );
+ return GENDER_NEUTER;
+ }
+
+ if ( len >= 20000 - 1 ) {
+ Com_Printf( "File %s too long\n", animfile );
+ trap_FS_FCloseFile( f );
+ return GENDER_NEUTER;
+ }
+ trap_FS_Read( text, len, f );
+ text[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while ( 1 ) {
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( !Q_stricmp( token, "sex" ) ) {
+ token = COM_Parse( &text_p );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( token[0] == 'f' || token[0] == 'F' ) {
+ free(text);
+ return GENDER_FEMALE;
+ } else if ( token[0] == 'n' || token[0] == 'N' ) {
+ free(text);
+ return GENDER_NEUTER;
+ } else {
+ free(text);
+ return GENDER_MALE;
+ }
+ }
+ }
+ free(text);
+ return GENDER_MALE;
+}
+#endif
+
+/**
+* Registers an item.
+*/
+#define MAX_GROUP_FILE_SIZE 5000
+char* BG_RegisterRace( const char *name ) {
+ char *text_p;
+ char *token;
+ int len;
+ fileHandle_t f;
+ char text[MAX_GROUP_FILE_SIZE];
+ gender_t theSex;
+
+ memset (races, 0, sizeof(races));
+ memset (text, 0, sizeof(text));
+
+ // load and parse the skin file
+ len = trap_FS_FOpenFile( name, &f, FS_READ );
+ if ( !f ) {
+ // if we didn't get a races file, use an empty one.
+ Com_sprintf(races, sizeof(races), "unknown");
+ return races;
+ }
+ if ( len >= sizeof( text ) - 1)
+ {
+ Com_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", name, len, sizeof( text ) );
+ trap_FS_FCloseFile( f );
+ return races;
+ }
+
+ trap_FS_Read( text, len, f );
+ trap_FS_FCloseFile( f );
+
+ theSex = G_ParseAnimationFileSex(name);
+ if (theSex == GENDER_MALE) {
+ strcat(races, "Male,");
+ } else if (theSex == GENDER_FEMALE) {
+ strcat(races, "Female,");
+ }
+
+ text_p = text;
+ while ( *text_p ) {
+ // get surface name
+ token = COM_Parse( &text_p );
+
+ if ( !token[0] ) {
+ break;
+ }
+
+ // if we about to break the races size list then dump us out
+ if (strlen(races) + strlen(token) > 256) {
+ break;
+ }
+
+ // add it into the race list
+ strcat(races, token);
+ // put a comma between the names
+ strcat(races, ",");
+
+ if ( *text_p == ',' ) {
+ text_p++;
+ }
+
+ if (!Q_stricmp ("borg", token) ) {
+ if (theSex == GENDER_MALE) {
+ // add it into the race list
+ strcat(races, "BorgMale,");
+ } else if (theSex == GENDER_FEMALE) {
+ strcat(races, "BorgFemale,");
+ } else {
+ }
+ }
+
+ }
+
+ // just in case
+ if (!races[0])
+ {
+ Com_sprintf(races, sizeof(races), "unknown");
+ }
+ else
+ { //lose the last comma
+ races[strlen(races)-1] = 0;
+ }
+
+ return races;
+}
+
+/**
+* Parses the rank names.
+*/
+#ifdef Q3_VM
+qboolean BG_ParseRankNames( char* fileName, rankNames_t rankNames[] ) {
+ fileHandle_t f;
+ int file_len;
+ char charText[20000];
+ char* textPtr;
+ char* token;
+ int i = 0;
+
+ file_len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+
+ if ( file_len<= 0 ) {
+ return qfalse;
+ }
+
+ if ( file_len >= ( sizeof(charText) - 1) ) {
+ Com_Printf( S_COLOR_RED "File length of %s is too long.\n", fileName );
+ return qfalse;
+ }
+
+ memset( &charText, 0, sizeof( charText ) );
+ memset( rankNames, 0, sizeof( rankNames ) );
+
+ trap_FS_Read( charText, file_len, f );
+
+ charText[file_len] = 0;
+
+ trap_FS_FCloseFile( f );
+
+ COM_BeginParseSession();
+
+ textPtr = charText;
+
+ token = COM_Parse( &textPtr );
+
+ if ( !token[0] ) {
+ Com_Printf( S_COLOR_RED "No data found in buffer: %s\n", fileName );
+ return qfalse;
+ }
+
+ if ( Q_stricmpn( token, "{", 1 ) ) {
+ Com_Printf( S_COLOR_RED "No beginning { found in %s\n", fileName );
+ return qfalse;
+ }
+
+ //Parse out the default cell. Default has no names anyway,
+ //but in case a n00bie modder put names in anyway.
+ SkipBracedSection( &textPtr );
+
+ while( 1 ) {
+ //lastPtr = textPtr;
+ token = COM_Parse( &textPtr );
+ if( !token[0] ) {
+ break;
+ }
+
+ if ( i >= MAX_RANKS ) {
+ break;
+ }
+
+ //If we hit an open brace (ie, assuming we hit the start of a new rank cell)
+ if ( !Q_stricmpn( token, "{", 1 ) ) {
+ while ( 1 ) {
+ token = COM_Parse( &textPtr );
+ if( !token[0] ) {
+ break;
+ }
+
+ //We hit a MenuTexture entry, since this uses { symbols, we'll skip these to stop errors.
+ if ( !Q_stricmpn( token, "MenuTexture", 11 ) ) {
+ SkipRestOfLine( &textPtr );
+ continue;
+ }
+
+ if ( !Q_stricmpn( token, "ConsoleName", 11) ) {
+ if ( COM_ParseString( &textPtr, &token ) ) {
+ continue;
+ }
+
+ Q_strncpyz( rankNames[i].consoleName, token, sizeof( rankNames[i].consoleName ) );
+
+ continue;
+ }
+ else if ( !Q_stricmpn( token, "FormalName", 10) ) {
+ if ( COM_ParseString( &textPtr, &token ) ) {
+ continue;
+ }
+
+ Q_strncpyz( rankNames[i].formalName, token, sizeof( rankNames[i].formalName ) );
+
+ continue;
+ }
+ //We hit the end of the cell.
+ else if ( !Q_stricmpn( token, "}", 1 ) ) {
+ break;
+ }
+ }
+
+ //Error check. If we didn't get both a formal and console name, pwn the caller. ;P
+ if ( !rankNames[i].consoleName[0] || !rankNames[i].formalName[0] ) {
+ Com_Printf( S_COLOR_RED "One or more rank names were not found in rank#: %i\n", i );
+ return qfalse;
+ }
+ else {
+ i++;
+ }
+ }
+ }
+ return qtrue;
+}
+#else
+qboolean BG_ParseRankNames( char* fileName, rankNames_t rankNames[] ) {
+ fileHandle_t f;
+ int file_len;
+ char *charText;
+ char* textPtr;
+ char* token;
+ int i = 0;
+
+ file_len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+
+ if ( file_len<= 0 ) {
+ return qfalse;
+ }
+
+ charText = (char *)malloc(20000 * sizeof(char));
+ if(!charText) {
+ Com_Printf( S_COLOR_RED "Was unable to allocate %i bytes.\n", 20000 * sizeof(char) );
+ trap_FS_FCloseFile(f);
+ return qfalse;
+ }
+
+ if ( file_len >= ( 20000 - 1) ) {
+ Com_Printf( S_COLOR_RED "File length of %s is too long.\n", fileName );
+ trap_FS_FCloseFile(f);
+ free(charText);
+ return qfalse;
+ }
+
+ memset( rankNames, 0, sizeof( rankNames ) );
+
+ trap_FS_Read( charText, file_len, f );
+
+ charText[file_len] = 0;
+
+ trap_FS_FCloseFile( f );
+
+ COM_BeginParseSession();
+
+ textPtr = charText;
+
+ token = COM_Parse( &textPtr );
+
+ if ( !token[0] ) {
+ Com_Printf( S_COLOR_RED "No data found in buffer: %s\n", fileName );
+ free(charText);
+ return qfalse;
+ }
+
+ if ( Q_stricmpn( token, "{", 1 ) ) {
+ Com_Printf( S_COLOR_RED "No beginning { found in %s\n", fileName );
+ free(charText);
+ return qfalse;
+ }
+
+ //Parse out the default cell. Default has no names anyway,
+ //but in case a n00bie modder put names in anyway.
+ SkipBracedSection( &textPtr );
+
+ while( 1 ) {
+ //lastPtr = textPtr;
+ token = COM_Parse( &textPtr );
+ if( !token[0] ) {
+ break;
+ }
+
+ if ( i >= MAX_RANKS ) {
+ break;
+ }
+
+ //If we hit an open brace (ie, assuming we hit the start of a new rank cell)
+ if ( !Q_stricmpn( token, "{", 1 ) ) {
+ while ( 1 ) {
+ token = COM_Parse( &textPtr );
+ if( !token[0] ) {
+ break;
+ }
+
+ //We hit a MenuTexture entry, since this uses { symbols, we'll skip these to stop errors.
+ if ( !Q_stricmpn( token, "MenuTexture", 11 ) ) {
+ SkipRestOfLine( &textPtr );
+ continue;
+ }
+
+ if ( !Q_stricmpn( token, "ConsoleName", 11) ) {
+ if ( COM_ParseString( &textPtr, &token ) ) {
+ continue;
+ }
+
+ Q_strncpyz( rankNames[i].consoleName, token, sizeof( rankNames[i].consoleName ) );
+
+ continue;
+ }
+ else if ( !Q_stricmpn( token, "FormalName", 10) ) {
+ if ( COM_ParseString( &textPtr, &token ) ) {
+ continue;
+ }
+
+ Q_strncpyz( rankNames[i].formalName, token, sizeof( rankNames[i].formalName ) );
+
+ continue;
+ }
+ //We hit the end of the cell.
+ else if ( !Q_stricmpn( token, "}", 1 ) ) {
+ break;
+ }
+ }
+
+ //Error check. If we didn't get both a formal and console name, pwn the caller. ;P
+ if ( !rankNames[i].consoleName[0] || !rankNames[i].formalName[0] ) {
+ Com_Printf( S_COLOR_RED "One or more rank names were not found in rank#: %i\n", i );
+ return qfalse;
+ }
+ else {
+ i++;
+ }
+ }
+ }
+ free(charText);
+ return qtrue;
+}
+#endif
+
+/*
+===========
+NextWordEndsHere
+===========
+*/
+char *NextWordEndsHere(char *p)
+{
+ if (*p != ' ') {
+ return p;
+ }
+
+ while (*p && *p == ' ') { // first pass
+ ++p;
+ }
+
+ while (*p && *p != ' ') { // second pass
+ ++p;
+ }
+
+ return p;
+}
+
+/*
+===========
+EndWord
+===========
+Returns a pointer to the position of the next space, null, or newline.
+*/
+char *EndWord(char *pos)
+{
+ while (!*pos && *pos != ' ' && *pos != '\n') {
+ ++pos;
+ }
+
+ return pos;
+}
diff --git a/game/bg_oums.c b/game/bg_oums.c
new file mode 100644
index 0000000..139597f
--- /dev/null
+++ b/game/bg_oums.c
@@ -0,0 +1,2 @@
+
+
diff --git a/game/bg_oums.h b/game/bg_oums.h
new file mode 100644
index 0000000..2529dfb
--- /dev/null
+++ b/game/bg_oums.h
@@ -0,0 +1,4 @@
+/*
+bg_OUMS.h
+ONLINE USER MANAGEMENT SYSTEM
+*/
diff --git a/game/bg_panimate.c b/game/bg_panimate.c
new file mode 100644
index 0000000..7ebc184
--- /dev/null
+++ b/game/bg_panimate.c
@@ -0,0 +1,12 @@
+//===========================================================
+// Name: bg_panimate.c
+// Developer: Timothy 'TiM' Oliver
+// Date: 16/2/2006
+// Function: Global character animation functions. Utilised
+// in CG and G for animation loading + handling.
+// Based off of logic from EF SP.
+//===========================================================
+
+#include "q_shared.h"
+#include "bg_public.h"
+
diff --git a/game/bg_pmove.c b/game/bg_pmove.c
new file mode 100644
index 0000000..4c07004
--- /dev/null
+++ b/game/bg_pmove.c
@@ -0,0 +1,3848 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+// bg_pmove.c -- both games player movement code
+// takes a playerstate and a usercmd as input and returns a modifed playerstate
+
+#include "q_shared.h"
+#include "bg_public.h"
+#include "bg_local.h"
+
+pmove_t *pm;
+pml_t pml;
+
+// movement parameters
+const float pm_stopspeed = 100;
+const float pm_duckScale = 0.50;
+const float pm_swimScale = 0.50;
+const float pm_ladderScale = 0.7f;
+
+const float pm_accelerate = 10;
+const float pm_airaccelerate = 1;
+const float pm_wateraccelerate = 4;
+const float pm_flyaccelerate = 8;
+
+const float pm_friction = 6;
+const float pm_waterfriction = 1;
+const float pm_flightfriction = 3;
+const float pm_evosuitfriction = 0.25; //RPG-X | Phenix | 8/8/2004
+
+int c_pmove = 0;
+
+#define PHASER_RECHARGE_TIME 100
+
+//RPG-X | Marcin | Big hack but it appears to work | 06/12/2008
+#ifdef QAGAME
+extern vmCvar_t rpg_rifleDelay;
+extern vmCvar_t rpg_disruptorDelay;
+extern vmCvar_t rpg_photonDelay;
+extern vmCvar_t rpg_altPhotonDelay;
+extern vmCvar_t rpg_TR116Delay;
+extern vmCvar_t rpg_altTricorderDelay;
+#define RIFLE_DELAY rpg_rifleDelay.integer
+#define DISRUPTOR_DELAY rpg_disruptorDelay.integer
+#define PHOTON_DELAY rpg_photonDelay.integer
+#define ALT_PHOTON_DELAY rpg_altPhotonDelay.integer
+#define TR116_DELAY rpg_TR116Delay.integer
+#define ALT_TRICORDER_DELAY rpg_altTricorderDelay.integer
+#else
+#define RIFLE_DELAY 250
+#define DISRUPTOR_DELAY 700
+#define PHOTON_DELAY 1600
+#define ALT_PHOTON_DELAY 1600
+#define TR116_DELAY 500
+#define ALT_TRICORDER_DELAY 1000
+#endif
+
+//qboolean BG_BorgTransporting( playerState_t *ps )
+//{
+// if ( ps->persistant[PERS_CLASS] == PC_BORG && bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_TRANSPORTER && ps->stats[STAT_USEABLE_PLACED] == 2 )
+// {//A player who has an item and it's set to 2 - meaning flight
+// return qtrue;
+// }
+// return qfalse;
+//}
+
+/*typedef enum {
+ ANIM_CROUCH,
+ ANIM_DOCROUCH,
+ ANIM_UNCROUCH,
+ ANIM_STAND,
+ ANIM_FIRE,
+ ANIM_JUMP,
+ ANIM_JUMPB,
+ ANIM_RUN,
+ ANIM_RUNB,
+ ANIM_WALK,
+ ANIM_WALKB,
+ ANIM_CROUCHWALK,
+ ANIM_CROUCHWALKB,
+ ANIM_SWIM,
+ ANIM_TURN,
+ ANIM_FLY,
+} animList_t;*/
+
+//TiM - Copied from the UI module.
+//My aim here is to adapt the checks code here so the function can be used both for
+//ingame animation, as well as UI animation
+
+// RPG-X UI Required Ones
+#define ANIM_IDLE 0
+#define ANIM_RUN 1
+#define ANIM_WALK 2
+#define ANIM_BACK 3
+#define ANIM_JUMP 4
+#define ANIM_CROUCH 5
+#define ANIM_ATTACK 22
+//Ingame required ones
+#define ANIM_JUMPB 6
+#define ANIM_RUNB 7
+#define ANIM_WALKB 8
+#define ANIM_CROUCHWALK 9
+#define ANIM_CROUCHWALKB 10
+#define ANIM_SWIM 11
+#define ANIM_FLY 12
+#define ANIM_TURN 13
+
+/**
+* Checks if the player holding a two handed weapon.
+*/
+qboolean PM_Holding2HandedWeapon ( void ) {
+ switch (pm->ps->weapon) {
+ case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_COMPRESSION_RIFLE:
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/**
+* Checks if the player is crouching.
+*/
+qboolean PM_PlayerCrouching ( int legsAnim ) {
+ //switch( pm->ps->legsAnim ) {
+ switch( ( legsAnim & ~ANIM_TOGGLEBIT) ) {
+ //case BOTH_CROUCH1:
+ case BOTH_CROUCH1IDLE:
+ case BOTH_CROUCH1WALK:
+ case BOTH_CROUCH2IDLE:
+ //case BOTH_CROUCH2TOSTAND1:
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/**
+* Check if player is idling.
+*/
+qboolean PM_PlayerIdling ( int torsoAnim, int legsAnim ) {
+ //switch( pm->ps->legsAnim ) {
+ //TiM : Cool hacky way to make sure both upper and lower anims are the same
+ switch( ( legsAnim & ~ANIM_TOGGLEBIT) ) //+ ( torsoAnim & ~ANIM_TOGGLEBIT)) >> 1
+ {
+ case BOTH_STAND1:
+ case BOTH_STAND2:
+ case BOTH_STAND3:
+ case BOTH_STAND4:
+ case BOTH_CROWDLOOK3:
+ {
+ switch ( ( torsoAnim & ~ANIM_TOGGLEBIT) )
+ {
+ case BOTH_STAND1:
+ case BOTH_STAND2:
+ case BOTH_STAND3:
+ case BOTH_STAND4:
+ case BOTH_CROWDLOOK3:
+ case TORSO_TRICORDER1:
+ case TORSO_HYPOSPRAY1:
+ case TORSO_HYPO1:
+ case TORSO_DROPWEAP1:
+ case TORSO_RAISEWEAP1:
+ case TORSO_PADD1:
+ case TORSO_COFFEE:
+ return qtrue;
+ }
+ }
+ }
+ return qfalse;
+}
+
+
+/**
+* \brief Checks if player is holding a loppable weapon.
+*
+* A weapon that can have its
+* firing animation looped,
+* like the PADD or tricorder, etc
+*/
+qboolean PM_HoldingLoopableWeapon ( void ) {
+ switch (pm->ps->weapon) {
+ case WP_DERMAL_REGEN:
+ case WP_TRICORDER:
+ case WP_PADD:
+ case WP_MEDKIT:
+ case WP_COFFEE:
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/**
+* \brief Checks if player is holding a spillable weapon.
+*
+* Player is holding a weapon that
+* shouldn't let players do the
+* 'slowing down' anim
+*/
+qboolean PM_HoldingSpillableWeapon( void ) {
+ switch ( pm->ps->weapon ) {
+ case WP_COFFEE:
+ case WP_COMPRESSION_RIFLE:
+ case WP_QUANTUM_BURST:
+ case WP_GRENADE_LAUNCHER:
+ case WP_TR116:
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+/**
+* Check to see if the player is moving at all
+*/
+qboolean PM_PlayerWalking( int anim )
+{
+ switch( anim & ~ANIM_TOGGLEBIT )
+ {
+ case BOTH_WALK1:
+ case BOTH_WALK2:
+ case BOTH_WALK3:
+ case BOTH_WALK4:
+ case BOTH_WALK7:
+ case LEGS_WALKBACK1:
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/**
+* Check to see if the player is running
+*/
+qboolean PM_PlayerRunning( int anim )
+{
+ switch( anim & ~ANIM_TOGGLEBIT )
+ {
+ case BOTH_RUN1:
+ case BOTH_RUN2:
+ case LEGS_RUNBACK2:
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/**
+* Check to see if the player is moving while crouching
+*/
+qboolean PM_PlayerCrouchWalking( int anim )
+{
+ switch( anim & ~ANIM_TOGGLEBIT )
+ {
+ case BOTH_CROUCH1WALK:
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/**
+* TiM: An index is defined, and depending
+* on which weapon is active, a specific
+* animation is returned.
+* I could have used pm->ps->weapon instead
+* of manually defining it as an paramter,
+* but I'm going to use this in the UI module,
+* which is out of pm's scope.
+*/
+int PM_GetAnim ( int anim, int weapon, qboolean injured, qboolean upper )
+{
+ playerState_t *ps = pm->ps;
+ // Called when player is in idle crouching
+ switch ( anim ) {
+ case ANIM_CROUCH:
+ //2 handed weapon - "heavy"
+ switch (weapon) {
+ case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ if ( ps->pm_flags & ANIM_ALERT2 && upper )
+ return TORSO_WEAPONREADY2;
+ else if (upper)
+ return BOTH_STAND2;
+ else
+ return LEGS_KNEEL1;
+ break;
+ //2 handed weapon - "light"
+ case WP_COMPRESSION_RIFLE:
+ //case WP_TR116:
+ if ( ps->pm_flags & ANIM_ALERT && upper )
+ return BOTH_STAND2;
+ else if (upper)
+ return TORSO_WEAPONREADY2;
+ else
+ return LEGS_KNEEL1;
+ break;
+ //1 handed weapon - "phaser"
+ case WP_PHASER:
+ case WP_DISRUPTOR:
+ if ( upper )
+ return TORSO_WEAPONPOSE1;
+ else
+ return BOTH_CROUCH1IDLE;
+ break;
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ //break;
+ //Generic tools - "everything else"
+ default:
+ return BOTH_CROUCH2IDLE;
+ break;
+ }
+ break;
+
+ //Called when player is in idle standing
+ case ANIM_IDLE:
+ //2 handed weapon - "heavy"
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_TR116:
+ if (injured)
+ return BOTH_INJURED4;
+ else
+ {
+ if ( ps->pm_flags & ANIM_ALERT )
+ return BOTH_STAND2;
+ else if ( ps->pm_flags & ANIM_ALERT2 )
+ {
+ if ( upper )
+ return TORSO_WEAPONREADY2;
+ else
+ return BOTH_STAND2;
+ }
+ else
+ return BOTH_STAND4;
+ }
+ break;
+ //2 handed weapon - "light"
+ case WP_COMPRESSION_RIFLE:
+ if (injured)
+ return BOTH_INJURED4;
+ else
+ {
+ if ( ps->pm_flags & ANIM_ALERT )
+ return BOTH_STAND2;
+ else if ( ps->pm_flags & ANIM_ALERT2 )
+ {
+ if ( upper )
+ return TORSO_WEAPONREADY2;
+ else
+ return BOTH_STAND2;
+ }
+ else
+ return BOTH_STAND4;
+ }
+ break;
+ //1 handed weapon - "phaser"
+ case WP_PHASER:
+ case WP_DISRUPTOR:
+ if (injured)
+ return BOTH_INJURED4;
+ else {
+ if ( ps->pm_flags & ANIM_ALERT && upper )
+ return TORSO_WEAPONIDLE1;
+ else if ( ps->pm_flags & ANIM_ALERT2 && upper )
+ return TORSO_WEAPONREADY1;
+ else
+ return BOTH_STAND1;
+ }
+ break;
+ //Generic tools - "everything else"
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ else
+ return BOTH_STAND1;
+ break;
+ default:
+ if (injured)
+ return BOTH_INJURED4;
+ else
+ return BOTH_STAND1;
+ break;
+ }
+ break;
+
+ //Called when player fires their weapon
+ case ANIM_ATTACK:
+ //2 handed weapon - "heavy"
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_TR116:
+ if ( ps->pm_flags & ANIM_ALERT2 )
+ return BOTH_ATTACK2;
+ else
+ return BOTH_ATTACK3;
+ break;
+ //2 handed weapon - "light"
+ case WP_COMPRESSION_RIFLE:
+ if ( ps->pm_flags & ANIM_ALERT2 )
+ return BOTH_ATTACK2;
+ else
+ {
+ if (upper)
+ return BOTH_ATTACK3;
+ else
+ return BOTH_ATTACK3;
+ }
+ break;
+ //1 handed weapon - "phaser"
+ case WP_PHASER:
+ case WP_DISRUPTOR:
+ case WP_HYPERSPANNER:
+ case WP_DERMAL_REGEN:
+ if (upper)
+ return TORSO_WEAPONREADY1;
+ else
+ return BOTH_STAND1;
+ break;
+ //Other Tools "padd"
+ case WP_PADD:
+ if (upper)
+ return TORSO_PADD1;
+ else
+ return BOTH_STAND1;
+ break;
+ //Other Tools "tricorder"
+ case WP_TRICORDER:
+ if (upper)
+ {
+ if ( !pm->medic )
+ return TORSO_TRICORDER1;
+ else
+ return TORSO_MEDICORDER1;
+ }
+ else
+ return BOTH_STAND1;
+ break;
+ //Other: "Medkit"
+ case WP_MEDKIT:
+ if (upper)
+ return TORSO_ACTIVATEMEDKIT1;
+ else
+ return BOTH_STAND1;
+ break;
+ //Other: "Hypo
+ case WP_VOYAGER_HYPO:
+ if (upper)
+ //return TORSO_HYPOSPRAY1;
+ return TORSO_HYPO1;
+ else
+ return BOTH_STAND1;
+ //Other: "Toolkit"
+ /*case WP_TOOLKIT:
+ //Return nothing.
+ //A bit hacky, but the engine accepts it :P
+ break;*/
+ //Other Tools "everything else"
+ /*case WP_NULL_HAND:
+ switch(rand()%13)
+ {
+ case 0: return TORSO_HANDGESTURE1;
+ case 1: return TORSO_HANDGESTURE2;
+ case 2: return TORSO_HANDGESTURE3;
+ case 3: return TORSO_HANDGESTURE4;
+ case 4: //PM_StartTorsoAnim( TORSO_HANDGESTURE5 ); break;
+ case 5: return TORSO_HANDGESTURE6;
+ case 6: return TORSO_HANDGESTURE7;
+ case 7: return TORSO_HANDGESTURE8;
+ case 8: return TORSO_HANDGESTURE9;
+ case 9: return TORSO_HANDGESTURE10;
+ case 10: return TORSO_HANDGESTURE11;
+ case 11: return TORSO_HANDGESTURE12;
+ case 12: return TORSO_HANDGESTURE13;
+ }
+ break;*/
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ //break;
+ default:
+ if (upper)
+ return TORSO_WEAPONREADY1;
+ else
+ return BOTH_STAND1;
+ break;
+ }
+ break;
+
+ //When the player jumps
+ case ANIM_JUMP:
+ return BOTH_JUMP1;
+ //Wen the player jumps backwards
+ case ANIM_JUMPB:
+ return BOTH_JUMPBACK1;
+
+ //When the player runs
+ case ANIM_RUN:
+ if (injured) {
+ return BOTH_RUNINJURED1;
+ }
+
+ //2 handed weapons
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_COMPRESSION_RIFLE:
+ case WP_TR116:
+ if (upper)
+ return BOTH_RUN2;
+ else
+ return BOTH_RUN1;
+ break;
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ //break;
+ //EVERYTHING ELSE
+ default:
+ return BOTH_RUN1;
+ }
+ break;
+
+ //When the player runs back
+ case ANIM_RUNB:
+ //2 handed weapons
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_COMPRESSION_RIFLE:
+ case WP_TR116:
+ if (upper)
+ return BOTH_WALK2;
+ else
+ if ( injured )
+ return LEGS_WALKBACK1;
+ else
+ return LEGS_RUNBACK2;
+ break;
+ //EVERYTHING ELSE
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ //break;
+ default:
+ if (upper)
+ return BOTH_WALK1;
+ else
+ if ( injured )
+ return LEGS_WALKBACK1;
+ else
+ return LEGS_RUNBACK2;
+ break;
+ }
+ break;
+
+ //When the player walks
+ case ANIM_WALK:
+ if ( ps->legsTimer > 0 && bg_emoteList[ps->legsTimer].enumName == BOTH_STAND3 )
+ return BOTH_WALK3;
+
+ //2 handed weapons
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_COMPRESSION_RIFLE:
+ case WP_TR116:
+ if ( ps->pm_flags & ANIM_ALERT )
+ return BOTH_WALK2;
+ else if ( ps->pm_flags & ANIM_ALERT2 )
+ {
+ if ( upper )
+ return TORSO_WEAPONREADY2;
+ else
+ return BOTH_WALK2;
+ }
+ else
+ return BOTH_WALK4;
+ break;
+ //Other Tools "everything else"
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ case WP_PHASER:
+ case WP_DISRUPTOR:
+ if ( ps->pm_flags & ANIM_ALERT )
+ {
+ if ( upper )
+ return TORSO_WEAPONIDLE1;
+ }
+ else if ( ps->pm_flags & ANIM_ALERT2 )
+ {
+ if ( upper )
+ return TORSO_WEAPONREADY1;
+ }
+
+ default:
+ return BOTH_WALK1;
+ break;
+ }
+ break;
+
+ //When the player walks baaaack
+ case ANIM_WALKB:
+ //2 handed weapons
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_COMPRESSION_RIFLE:
+ case WP_TR116:
+ if ( ps->pm_flags & ANIM_ALERT )
+ {
+ if ( upper )
+ return BOTH_WALK2;
+ else
+ return LEGS_WALKBACK1;
+ }
+ else if ( ps->pm_flags & ANIM_ALERT2 )
+ {
+ if ( upper )
+ return TORSO_WEAPONREADY2;
+ else
+ return LEGS_WALKBACK1;
+ }
+ else
+ {
+ if ( upper )
+ return BOTH_WALK4;
+ else
+ return LEGS_WALKBACK1;
+ }
+ break;
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ //break;
+ case WP_PHASER:
+ case WP_DISRUPTOR:
+ if ( ps->pm_flags & ANIM_ALERT && upper)
+ return TORSO_WEAPONIDLE1;
+ else if ( ps->pm_flags & ANIM_ALERT2 && upper )
+ return TORSO_WEAPONREADY1;
+
+ //Other Tools "everything else"
+ default:
+ if ( upper )
+ return BOTH_WALK1;
+ else
+ return LEGS_WALKBACK1;
+ break;
+ }
+ break;
+
+ //When the player crouch walks
+ case ANIM_CROUCHWALK:
+ //2 handed weapons
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_COMPRESSION_RIFLE:
+ case WP_TR116:
+ if ( upper )
+ return TORSO_WEAPONREADY2;
+ else
+ return BOTH_CROUCH1WALK;
+ break;
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ if ( ps->pm_flags & ANIM_ALERT2 && upper )
+ return TORSO_WEAPONREADY2;
+ else if ( upper )
+ return BOTH_WALK2;
+ else
+ return BOTH_CROUCH1WALK;
+ break;
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ //break;
+ case WP_PHASER:
+ case WP_DISRUPTOR:
+ if ( ps->pm_flags & ANIM_ALERT && upper )
+ return TORSO_WEAPONIDLE1;
+ else if ( ps->pm_flags & ANIM_ALERT2 && upper )
+ return TORSO_WEAPONREADY1;
+ //Other Tools "everything else"
+ default:
+ return BOTH_CROUCH1WALK;
+ break;
+ }
+ break;
+
+ //When the player crouch walks bak
+ case ANIM_CROUCHWALKB:
+ //2 handed weapons
+ switch (weapon) {
+ //case WP_TR116:
+ case WP_GRENADE_LAUNCHER:
+ case WP_QUANTUM_BURST:
+ case WP_COMPRESSION_RIFLE:
+ case WP_TR116:
+ if ( ps->pm_flags & ANIM_ALERT2 )
+ return TORSO_WEAPONREADY2;
+ else if ( upper )
+ return BOTH_WALK2;
+ else
+ return BOTH_CROUCH1WALK;
+ break;
+ case WP_COFFEE:
+ if (upper)
+ return TORSO_COFFEE;
+ //break;
+ case WP_PHASER:
+ case WP_DISRUPTOR:
+ if ( ps->pm_flags & ANIM_ALERT && upper )
+ return TORSO_WEAPONIDLE1;
+ else if ( ps->pm_flags & ANIM_ALERT2 && upper )
+ return TORSO_WEAPONREADY1;
+
+ //Other Tools "everything else"
+ default:
+ return BOTH_CROUCH1WALK;
+ break;
+ }
+ break;
+
+ case ANIM_SWIM:
+ if ( !upper ) {
+ if ( pm->cmd.forwardmove
+ || pm->cmd.rightmove
+ || pm->cmd.upmove )
+ {
+ return LEGS_SWIM;
+ }
+ }
+
+ return BOTH_FLOAT1;
+
+ /*if ( ps->velocity[2] >= 0 ) {
+ return BOTH_FLOAT1;
+ }
+ else {
+ return BOTH_FLOAT2;
+ }*/
+ case ANIM_FLY:
+ return BOTH_FLOAT1;
+ }
+
+ return BOTH_STAND1;
+}
+
+
+
+/**
+* Adds a predictable event to playerstate
+*/
+void PM_AddEvent( int newEvent ) {
+ BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps );
+}
+
+/*
+==============
+PM_Use
+
+Generates a use event
+==============
+*/
+#define USE_DELAY 2000
+
+/**
+* Generates a use event
+*/
+void PM_Use( void )
+{
+ playerState_t *ps = pm->ps;
+
+ if ( ps->useTime > 0 )
+ ps->useTime -= 100;//pm->cmd.msec;
+
+ if ( ps->useTime > 0 ) {
+ return;
+ }
+
+ if ( ! (pm->cmd.buttons & BUTTON_USE ) )
+ {
+ pm->useEvent = 0;
+ ps->useTime = 0;
+ //PM_StartTorsoAnim( BOTH_CONSOLE1 );
+
+ return;
+ }
+
+ pm->useEvent = EV_USE;
+ ps->useTime = USE_DELAY;
+}
+
+/*
+===============
+PM_AddTouchEnt
+===============
+*/
+/**
+* Adds a touchEnt event.
+*/
+void PM_AddTouchEnt( int entityNum ) {
+ int i;
+
+ if ( entityNum == ENTITYNUM_WORLD ) {
+ return;
+ }
+ if ( pm->numtouch == MAXTOUCH ) {
+ return;
+ }
+
+ // see if it is already added
+ for ( i = 0 ; i < pm->numtouch ; i++ ) {
+ if ( pm->touchents[ i ] == entityNum ) {
+ return;
+ }
+ }
+
+ // add it
+ pm->touchents[pm->numtouch] = entityNum;
+ pm->numtouch++;
+}
+
+/**
+* Start torso animation
+*/
+
+static void PM_StartTorsoAnim( int anim, qboolean overrideEmotes ) {
+ playerState_t *ps = pm->ps;
+ if ( ps->stats[EMOTES] & EMOTE_UPPER && !overrideEmotes ) {
+ return;
+ }
+
+ if ( ps->pm_type >= PM_DEAD &&
+ anim != BOTH_FALLDEATH1INAIR &&
+ anim != BOTH_FALLDEATH1LAND )
+ { //TiM: UberHack :P
+ return;
+ }
+
+ //if ( ps->torsoTimer > 0 ) {
+ if ( ps->stats[TORSOTIMER] > 0 ) {
+ return; // a high priority animation is running
+ }
+
+ //ps->torsoAnim
+ ps->stats[TORSOANIM] = ( ( ps->stats[TORSOANIM] & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
+ | anim;
+}
+
+/**
+* Start leg animation
+*/
+static void PM_StartLegsAnim( int anim ) {
+ playerState_t *ps = pm->ps;
+
+ /*if ( ps->stats[EMOTES] & EMOTE_CLAMP ) {
+ return;
+ }*/
+
+ if ( ps->pm_type >= PM_DEAD &&
+ anim != BOTH_FALLDEATH1INAIR &&
+ anim != BOTH_FALLDEATH1LAND )
+ {
+ return;
+ }
+
+ //if ( ps->introTime > 0 ) { //legsTimer
+ /*if ( ps->stats[LEGSTIMER] > 0 ) { //legsTimer
+ return; // a high priority animation is running
+ }*/
+
+ //ps->legsAnim
+ ps->stats[LEGSANIM] = ( ( ps->stats[LEGSANIM] & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
+ | anim;
+}
+
+/**
+* Continues the legs animation.
+*/
+static void PM_ContinueLegsAnim( int anim, qboolean overrideEmote ) {
+ playerState_t *ps = pm->ps;
+
+ //override to return to idle after moving in an emote
+ if ( (ps->stats[EMOTES] & EMOTE_LOWER ) && ( !( ps->stats[EMOTES] & EMOTE_CLAMP_BODY) && !( ps->stats[EMOTES] & EMOTE_CLAMP_ALL) ) && !overrideEmote ) {
+ if ( ps->legsTimer>0 && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) != bg_emoteList[ ps->legsTimer ].enumName && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) != BOTH_GET_UP1 ) {
+ int anim2 = PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse );
+
+ //Com_Printf( "KILL!\n");
+ ps->stats[LEGSANIM] = ( ( ps->stats[LEGSANIM] & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim2;
+ }
+ }
+
+ if ( (ps->stats[EMOTES] & EMOTE_CLAMP_BODY || ps->stats[EMOTES] & EMOTE_CLAMP_ALL ) && !overrideEmote ) { //EMOTE_LOWER
+ return;
+ }
+
+ //if ( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) {
+ if ( ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) == anim ) {
+ return;
+ }
+ //if ( ps->introTime > 0 ) { //legsTimer
+ if ( ps->stats[LEGSTIMER] > 0 && !overrideEmote ) { //legsTimer
+ return; // a high priority animation is running
+ }
+ PM_StartLegsAnim( anim );
+}
+
+/**
+* Continues the torso animation
+*/
+static void PM_ContinueTorsoAnim( int anim, qboolean overrideEmote ) {
+ playerState_t *ps = pm->ps;
+
+ if ( ps->stats[EMOTES] & EMOTE_UPPER && !overrideEmote ) {
+ return;
+ }
+
+ //if ( ( ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) {
+ if ( ( ps->stats[TORSOANIM] & ~ANIM_TOGGLEBIT ) == anim ) {
+ return;
+ }
+
+ //if ( ps->torsoTimer > 0 ) {
+ if ( ps->stats[TORSOTIMER] > 0 ) {
+ return; // a high priority animation is running
+ }
+ PM_StartTorsoAnim( anim, overrideEmote );
+}
+
+/**
+* Force a legs animation
+*/
+static void PM_ForceLegsAnim( int anim ) {
+ playerState_t *ps = pm->ps;
+
+ //OMFG UBERHACK
+ //I'm lazy... client revive spawns players 1 unit over the ground.
+ //THat small fall enacts this, and subsequently screws up client revive animations
+ if ( ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) {
+ ps->stats[EMOTES] &= ~EMOTE_MASK_LOWER;
+ ps->stats[LEGSTIMER] = 0; //legsTimer
+ }
+
+ PM_StartLegsAnim( anim );
+}
+
+/**
+* Force a torso animation
+*/
+static void PM_ForceTorsoAnim( int anim, qboolean overrideEmotes ) {
+ playerState_t *ps = pm->ps;
+
+ if ( overrideEmotes && ( ps->stats[TORSOANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) {
+ ps->stats[EMOTES] &= ~EMOTE_MASK_UPPER;
+ ps->stats[TORSOTIMER] = 0;
+ }
+ //ps->stats[TORSOTIMER] = 0;
+
+ PM_StartTorsoAnim( anim, overrideEmotes );
+}
+
+/*
+================
+PM_Animate (RPG-X:J2J)
+
+TiM: Bookmark... this shows promise :)
+LATER: Nope... scratch that.
+Although it's good in the fact it assigns
+the anim right, it doesn't take the timers
+into account, meaning this would play for a
+total of one clock cycle :P
+================
+*/
+/*static void PM_DoEmote( void )
+{
+ if ( pm->ps->stats[EMOTES] & EMOTE_LOWER ) {
+ pm->ps->viewheight = emoteList[pm->ps->legsAnim].viewHeight;
+ }
+}*/
+
+/*static void PM_DoEmote( void )
+{
+ int EmoteType = 0; //0 = legs, 1 = torso, 2 = both
+
+ //Bail out if no new emote or invalid.
+ if(CurrentEmote[pm->ps->clientNum] < 0 || CurrentEmote[pm->ps->clientNum] >= MAX_ANIMATIONS)
+ return;
+
+ //Get animation type
+ if(CurrentEmote[pm->ps->clientNum] >= LEGS_WALKBACK1)
+ EmoteType = 0;
+ if(CurrentEmote[pm->ps->clientNum] >= TORSO_DROPWEAP1 && CurrentEmote[pm->ps->clientNum] <= TORSO_CARRY1)
+ EmoteType = 1;
+ if(CurrentEmote[pm->ps->clientNum] >= BOTH_DEATH1 && CurrentEmote[pm->ps->clientNum] <= BOTH_POWERUP1)
+ EmoteType = 2;
+
+//Check for higher priority animations
+ if(EmoteType == 0)
+ {
+ //ToDo: insert checking for high priority anims
+ }
+ else if(EmoteType == 1)
+ {
+ //ToDo: insert checking for high priority anims
+ }
+ else //Implies EmoteType == 2
+ {
+ //ToDo: insert checking for high priority anims
+ }
+
+ //Do the emote (play the anim)
+ if(EmoteType == 0 || EmoteType == 2)
+ {
+ PM_ForceLegsAnim(CurrentEmote[pm->ps->clientNum]); //CurrentEmote[pm->ps->clientNum]
+ //pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
+ //| CurrentEmote[pm->ps->clientNum];
+ }
+ if(EmoteType == 1 || EmoteType == 2)
+ {
+ PM_ForceTorsoAnim(CurrentEmote[pm->ps->clientNum]);
+ //pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
+ //| CurrentEmote[pm->ps->clientNum];
+ }
+
+ //Make -1 so it doesnt start the anim again.
+ CurrentEmote[pm->ps->clientNum] = -1;
+}*/
+
+
+/*
+==================
+PM_ClipVelocity
+==================
+*/
+/**
+* Slide off of the impacting surface
+*/
+void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) {
+ float backoff;
+ float change;
+ int i;
+
+ backoff = DotProduct (in, normal);
+
+ if ( backoff < 0 ) {
+ backoff *= overbounce;
+ } else {
+ backoff /= overbounce;
+ }
+
+ for ( i=0 ; i<3 ; i++ ) {
+ change = normal[i]*backoff;
+ out[i] = in[i] - change;
+ }
+}
+
+
+/*
+==================
+PM_Friction
+==================
+*/
+/**
+* Handles both ground friction and water friction
+*/
+static void PM_Friction( void ) {
+ vec3_t vec;
+ float *vel;
+ float speed, newspeed, control;
+ float drop;
+ playerState_t *ps = pm->ps;
+
+ vel = ps->velocity;
+
+ VectorCopy( vel, vec );
+ if ( pml.walking ) {
+ vec[2] = 0; // ignore slope movement
+ }
+
+ speed = VectorLength(vec);
+ if (speed < 1) {
+ vel[0] = 0;
+ vel[1] = 0; // allow sinking underwater
+ // FIXME: still have z friction underwater?
+ return;
+ }
+
+ drop = 0;
+
+ // apply ground friction
+ if ( (pm->watertype & CONTENTS_LADDER) || pm->waterlevel <= 1 ) {
+ if ( (pm->watertype & CONTENTS_LADDER) || (pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK)) ) {
+ // if getting knocked back, no friction
+ if ( ! (ps->pm_flags & PMF_TIME_KNOCKBACK) ) {
+ control = speed < pm_stopspeed ? pm_stopspeed : speed;
+ drop += control*pm_friction*pml.frametime;
+ }
+ }
+ }
+
+ // apply water friction even if just wading
+ if ( pm->waterlevel && !(pm->watertype & CONTENTS_LADDER) ) {
+ drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime;
+ }
+
+ //RPG-X | Phenix | 8/8/2004
+ //Apply EVOSUIT friction (small)
+ if ( ps->powerups[PW_EVOSUIT] )
+ {
+ drop += speed*pm_evosuitfriction*pml.frametime;
+ }
+
+ // apply flying friction
+ if ( ps->powerups[PW_FLIGHT] || ps->pm_type == PM_SPECTATOR ) {
+ drop += speed*pm_flightfriction*pml.frametime;
+ }
+
+ // scale the velocity
+ newspeed = speed - drop;
+ if (newspeed < 0) {
+ newspeed = 0;
+ }
+ newspeed /= speed;
+
+ vel[0] = vel[0] * newspeed;
+ vel[1] = vel[1] * newspeed;
+ vel[2] = vel[2] * newspeed;
+}
+
+
+/*
+==============
+PM_Accelerate
+==============
+*/
+/**
+* Handles user intended acceleration
+*/
+static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) {
+#if 1
+ // q2 style
+ int i;
+ float addspeed, accelspeed, currentspeed;
+
+ currentspeed = DotProduct (pm->ps->velocity, wishdir);
+ addspeed = wishspeed - currentspeed;
+ if (addspeed <= 0) {
+ return;
+ }
+ accelspeed = accel*pml.frametime*wishspeed;
+ if (accelspeed > addspeed) {
+ accelspeed = addspeed;
+ }
+
+ for (i=0 ; i<3 ; i++) {
+ pm->ps->velocity[i] += accelspeed*wishdir[i];
+ }
+#else
+ // proper way (avoids strafe jump maxspeed bug), but feels bad
+ vec3_t wishVelocity;
+ vec3_t pushDir;
+ float pushLen;
+ float canPush;
+
+ VectorScale( wishdir, wishspeed, wishVelocity );
+ VectorSubtract( wishVelocity, pm->ps->velocity, pushDir );
+ pushLen = VectorNormalize( pushDir );
+
+ canPush = accel*pml.frametime*wishspeed;
+ if (canPush > pushLen) {
+ canPush = pushLen;
+ }
+
+ VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity );
+#endif
+}
+
+
+
+/*
+============
+PM_CmdScale
+============
+*/
+/**
+* \return the scale factor to apply to cmd movements
+*
+* This allows the clients to use axial -127 to 127 values for all directions
+* without getting a sqrt(2) distortion in speed.
+*/
+static float PM_CmdScale( usercmd_t *cmd ) {
+ int max;
+ float total;
+ float scale;
+
+ max = abs( cmd->forwardmove );
+ if ( abs( cmd->rightmove ) > max ) {
+ max = abs( cmd->rightmove );
+ }
+ if ( abs( cmd->upmove ) > max ) {
+ max = abs( cmd->upmove );
+ }
+ if ( !max ) {
+ return 0;
+ }
+
+ total = sqrt( cmd->forwardmove * cmd->forwardmove
+ + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove );
+ scale = (float)pm->ps->speed * max / ( 127.0 * total );
+
+ return scale;
+}
+
+
+/*
+================
+PM_SetMovementDir
+================
+*/
+/**
+* Determines the rotation of the legs reletive
+* to the facing dir
+*/
+static void PM_SetMovementDir( void ) {
+ playerState_t *ps = pm->ps;
+ usercmd_t *cmd = &pm->cmd;
+
+ if ( cmd->forwardmove || cmd->rightmove ) {
+ if ( cmd->rightmove == 0 && cmd->forwardmove > 0 ) {
+ ps->movementDir = 0;
+ } else if ( cmd->rightmove < 0 && cmd->forwardmove > 0 ) {
+ ps->movementDir = 1;
+ } else if ( cmd->rightmove < 0 && cmd->forwardmove == 0 ) {
+ ps->movementDir = 2;
+ } else if ( cmd->rightmove < 0 && cmd->forwardmove < 0 ) {
+ ps->movementDir = 3;
+ } else if ( cmd->rightmove == 0 && cmd->forwardmove < 0 ) {
+ ps->movementDir = 4;
+ } else if ( cmd->rightmove > 0 && cmd->forwardmove < 0 ) {
+ ps->movementDir = 5;
+ } else if ( cmd->rightmove > 0 && cmd->forwardmove == 0 ) {
+ ps->movementDir = 6;
+ } else if ( cmd->rightmove > 0 && cmd->forwardmove > 0 ) {
+ ps->movementDir = 7;
+ }
+ } else {
+ // if they aren't actively going directly sideways,
+ // change the animation to the diagonal so they
+ // don't stop too crooked
+ if ( ps->movementDir == 2 ) {
+ ps->movementDir = 1;
+ } else if ( ps->movementDir == 6 ) {
+ ps->movementDir = 7;
+ }
+ }
+}
+
+
+/*
+=============
+PM_CheckJump
+=============
+*/
+/**
+* Checks if jumping is allowed
+*/
+static qboolean PM_CheckJump( void ) {
+ playerState_t *ps = pm->ps;
+
+ if ( ps->pm_flags & PMF_RESPAWNED ) {
+ return qfalse; // don't allow jump until all buttons are up
+ }
+
+ if ( pm->cmd.upmove < 10 ) {
+ // not holding jump
+ return qfalse;
+ }
+
+ // must wait for jump to be released
+ if ( ps->pm_flags & PMF_JUMP_HELD ) {
+ // clear upmove so cmdscale doesn't lower running speed
+ pm->cmd.upmove = 0;
+ return qfalse;
+ }
+
+ pml.groundPlane = qfalse; // jumping away
+ pml.walking = qfalse;
+ ps->pm_flags |= PMF_JUMP_HELD;
+
+ ps->groundEntityNum = ENTITYNUM_NONE;
+ ps->velocity[2] = JUMP_VELOCITY;
+
+ PM_AddEvent( EV_JUMP );
+
+ if ( pm->cmd.forwardmove >= 0 ) {
+ PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) ); //BOTH_JUMP
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue );
+ ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ } else {
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue );
+ PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ) ); //LEGS_JUMPB
+ ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+
+
+ return qtrue;
+}
+
+/*
+=============
+PM_CheckWaterJump
+=============
+*/
+/**
+* Checks if jumping out of water is allowed
+*/
+static qboolean PM_CheckWaterJump( void ) {
+ vec3_t spot;
+ int cont;
+ vec3_t flatforward;
+ playerState_t *ps = pm->ps;
+
+ if (ps->pm_time) {
+ return qfalse;
+ }
+
+ // check for water jump
+ if ( pm->waterlevel != 2 ) {
+ return qfalse;
+ }
+
+ if ( pm->watertype & CONTENTS_LADDER ) {
+ if (ps->velocity[2] <= 0)
+ return qfalse;
+ }
+
+ flatforward[0] = pml.forward[0];
+ flatforward[1] = pml.forward[1];
+ flatforward[2] = 0;
+ VectorNormalize (flatforward);
+
+ VectorMA (ps->origin, 30, flatforward, spot);
+ spot[2] += 4;
+ cont = pm->pointcontents (spot, ps->clientNum );
+ if ( !(cont & CONTENTS_SOLID) ) {
+ return qfalse;
+ }
+
+ spot[2] += 16;
+ cont = pm->pointcontents (spot, ps->clientNum );
+ if ( cont ) {
+ return qfalse;
+ }
+
+ // jump out of water
+ VectorScale (pml.forward, 200, ps->velocity);
+ ps->velocity[2] = 350;
+
+ ps->pm_flags |= PMF_TIME_WATERJUMP;
+ ps->pm_time = 2000;
+
+ return qtrue;
+}
+
+//============================================================================
+
+
+/*
+===================
+PM_WaterJumpMove
+===================
+*/
+/**
+* Flying out of the water
+*/
+static void PM_WaterJumpMove( void ) {
+ playerState_t *ps = pm->ps;
+ // waterjump has no control, but falls
+
+ PM_StepSlideMove( qtrue );
+
+ ps->velocity[2] -= ps->gravity * pml.frametime;
+ if (ps->velocity[2] < 0) {
+ // cancel as soon as we are falling down again
+ ps->pm_flags &= ~PMF_ALL_TIMES;
+ ps->pm_time = 0;
+ }
+}
+
+/*
+===================
+PM_WaterMove
+
+===================
+*/
+/**
+* Handles movement in water
+*/
+static void PM_WaterMove( void ) {
+ int i;
+ vec3_t wishvel;
+ float wishspeed;
+ vec3_t wishdir;
+ float scale;
+ float vel;
+ playerState_t *ps = pm->ps;
+
+ if ( PM_CheckWaterJump() ) {
+ PM_WaterJumpMove();
+ return;
+ }
+#if 0
+ // jump = head for surface
+ if ( pm->cmd.upmove >= 10 ) {
+ if (ps->velocity[2] > -300) {
+ if ( pm->watertype == CONTENTS_WATER ) {
+ ps->velocity[2] = 100;
+ } else if (pm->watertype == CONTENTS_SLIME) {
+ ps->velocity[2] = 80;
+ } else {
+ ps->velocity[2] = 50;
+ }
+ }
+ }
+#endif
+ PM_Friction ();
+
+ scale = PM_CmdScale( &pm->cmd );
+ //
+ // user intentions
+ //
+ if ( !scale ) {
+ wishvel[0] = 0;
+ wishvel[1] = 0;
+ if ( pm->watertype & CONTENTS_LADDER ) {
+ wishvel[2] = 0;
+ } else {
+ wishvel[2] = -60; // sink towards bottom
+ }
+ } else {
+ for (i=0 ; i<3 ; i++) {
+ wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove;
+ }
+ wishvel[2] += scale * pm->cmd.upmove;
+ }
+
+ VectorCopy (wishvel, wishdir);
+ wishspeed = VectorNormalize(wishdir);
+
+ if ( pm->watertype & CONTENTS_LADDER ) //ladder
+ {
+ if ( wishspeed > ps->speed * pm_ladderScale ) {
+ wishspeed = ps->speed * pm_ladderScale;
+ }
+ PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate );
+ } else {
+ if ( wishspeed > ps->speed * pm_swimScale ) {
+ wishspeed = ps->speed * pm_swimScale;
+ }
+ PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate );
+ }
+
+ // make sure we can go up slopes easily under water
+ if ( pml.groundPlane && DotProduct( ps->velocity, pml.groundTrace.plane.normal ) < 0 ) {
+ vel = VectorLength(ps->velocity);
+ // slide along the ground plane
+ PM_ClipVelocity (ps->velocity, pml.groundTrace.plane.normal,
+ ps->velocity, OVERCLIP );
+
+ VectorNormalize(ps->velocity);
+ VectorScale(ps->velocity, vel, ps->velocity);
+ }
+
+ PM_SlideMove( qfalse );
+}
+
+
+
+/*
+===================
+PM_FlyMove
+
+Only with the flight powerup
+TiM: Good... if this handles
+spectators too, I'm sunk >.<
+Oh crap... it does lol
+===================
+*/
+/**
+* Handles fly movement (e.g. flight powerup or spectator movement)
+*/
+static void PM_FlyMove( void ) {
+ int i;
+ vec3_t wishvel;
+ float wishspeed;
+ vec3_t wishdir;
+ float scale;
+ playerState_t *ps = pm->ps;
+
+ // normal slowdown
+ PM_Friction ();
+
+ scale = PM_CmdScale( &pm->cmd );
+ //
+ // user intentions
+ //
+ if ( !scale ) {
+ wishvel[0] = 0;
+ wishvel[1] = 0;
+ wishvel[2] = 0;
+ } else {
+ for (i=0 ; i<3 ; i++) {
+ if ( ps->pm_type == PM_SPECTATOR ) {
+ wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove;
+ }
+ else {
+ //TiM - Vertical is no longer a hardcoded constant direction
+ wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove +
+ scale * pml.right[i]*pm->cmd.rightmove +
+ scale * pml.up[i]* pm->cmd.upmove;
+
+ //TiM - Set directions
+ PM_SetMovementDir();
+ }
+ }
+
+ if ( ps->pm_type == PM_SPECTATOR )
+ wishvel[2] += scale * pm->cmd.upmove;
+ }
+
+ VectorCopy (wishvel, wishdir);
+ wishspeed = VectorNormalize(wishdir);
+
+ PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate);
+
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_FLY, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_FLY, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue );
+
+ PM_StepSlideMove( qfalse );
+}
+
+
+/*
+===================
+PM_AirMove
+
+===================
+*/
+/**
+* Handles movement during air time (e.g. falling)
+*/
+static void PM_AirMove( void ) {
+ int i;
+ vec3_t wishvel;
+ float fmove, smove;
+ vec3_t wishdir;
+ float wishspeed;
+ float scale;
+ usercmd_t cmd;
+
+ PM_Friction();
+
+ fmove = pm->cmd.forwardmove;
+ smove = pm->cmd.rightmove;
+
+ cmd = pm->cmd;
+ scale = PM_CmdScale( &cmd );
+
+ // set the movementDir so clients can rotate the legs for strafing
+ PM_SetMovementDir();
+
+ // project moves down to flat plane
+ pml.forward[2] = 0;
+ pml.right[2] = 0;
+ VectorNormalize (pml.forward);
+ VectorNormalize (pml.right);
+
+ for ( i = 0 ; i < 2 ; i++ ) {
+ wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
+ }
+ wishvel[2] = 0;
+
+ VectorCopy (wishvel, wishdir);
+ wishspeed = VectorNormalize(wishdir);
+ wishspeed *= scale;
+
+ // not on ground, so little effect on velocity
+ PM_Accelerate (wishdir, wishspeed, pm_airaccelerate);
+
+ // we may have a ground plane that is very steep, even
+ // though we don't have a groundentity
+ // slide along the steep plane
+ if ( pml.groundPlane ) {
+ PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal,
+ pm->ps->velocity, OVERCLIP );
+ }
+
+ PM_StepSlideMove ( qtrue );
+}
+
+/*
+===================
+PM_WalkMove
+===================
+*/
+/**
+* Handles walk movement
+*/
+static void PM_WalkMove( void ) {
+ int i;
+ vec3_t wishvel;
+ float fmove, smove;
+ vec3_t wishdir;
+ float wishspeed;
+ float scale;
+ usercmd_t cmd;
+ float accelerate;
+ float vel;
+ playerState_t *ps = pm->ps;
+
+ if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) {
+ // begin swimming
+ PM_WaterMove();
+ return;
+ }
+
+
+ if ( PM_CheckJump () ) {
+ // jumped away
+ if ( pm->waterlevel > 1 ) {
+ PM_WaterMove();
+ } else {
+ PM_AirMove();
+ }
+ return;
+ }
+
+ PM_Friction ();
+
+ fmove = pm->cmd.forwardmove;
+ smove = pm->cmd.rightmove;
+
+ cmd = pm->cmd;
+ scale = PM_CmdScale( &cmd );
+ //scale = 0.4; //TiM
+
+ // set the movementDir so clients can rotate the legs for strafing
+ PM_SetMovementDir();
+
+ // project moves down to flat plane
+ pml.forward[2] = 0;
+ pml.right[2] = 0;
+
+ // project the forward and right directions onto the ground plane
+ PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP );
+ PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP );
+ //
+ VectorNormalize (pml.forward);
+ VectorNormalize (pml.right);
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
+ }
+ // when going up or down slopes the wish velocity should Not be zero
+// wishvel[2] = 0;
+
+ VectorCopy (wishvel, wishdir);
+ wishspeed = VectorNormalize(wishdir);
+ wishspeed *= scale;
+
+ // clamp the speed lower if ducking
+ if ( ps->pm_flags & PMF_DUCKED ) {
+ if ( wishspeed > ps->speed * pm_duckScale ) {
+ wishspeed = ps->speed * pm_duckScale;
+ }
+ }
+
+ // clamp the speed lower if wading or walking on the bottom
+ if ( pm->waterlevel ) {
+ float waterScale;
+
+ waterScale = pm->waterlevel / 3.0;
+ waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale;
+ if ( wishspeed > ps->speed * waterScale ) {
+ wishspeed = ps->speed * waterScale;
+ }
+ }
+
+ // when a player gets hit, they temporarily lose
+ // full control, which allows them to be moved a bit
+ if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || ps->pm_flags & PMF_TIME_KNOCKBACK ) {
+ accelerate = pm_airaccelerate;
+ } else {
+ accelerate = pm_accelerate;
+ }
+
+ PM_Accelerate (wishdir, wishspeed, accelerate);
+
+ //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", ps->velocity[0], ps->velocity[1], ps->velocity[2]);
+ //Com_Printf("velocity1 = %1.1f\n", VectorLength(ps->velocity));
+
+ if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || ps->pm_flags & PMF_TIME_KNOCKBACK ) {
+ ps->velocity[2] -= ps->gravity * pml.frametime;
+ } else {
+ // don't reset the z velocity for slopes
+// ps->velocity[2] = 0;
+ }
+
+ vel = VectorLength(ps->velocity);
+
+ // slide along the ground plane
+ PM_ClipVelocity (ps->velocity, pml.groundTrace.plane.normal,
+ ps->velocity, OVERCLIP );
+
+ // don't decrease velocity when going up or down a slope
+ VectorNormalize(ps->velocity);
+ VectorScale(ps->velocity, vel, ps->velocity);
+
+ // don't do anything if standing still
+ if (!ps->velocity[0] && !ps->velocity[1]) {
+ return;
+ }
+
+ PM_StepSlideMove( qfalse );
+
+ //Com_Printf("velocity2 = %1.1f\n", VectorLength(ps->velocity));
+
+}
+
+
+/*
+==============
+PM_DeadMove
+==============
+*/
+/**
+* Handles movement while dead
+*/
+static void PM_DeadMove( void ) {
+ float forward;
+ playerState_t *ps = pm->ps;
+
+ if ( !pml.walking ) {
+ return;
+ }
+
+ // extra friction
+
+ forward = VectorLength (ps->velocity);
+ forward -= 20;
+ if ( forward <= 0 ) {
+ VectorClear (ps->velocity);
+ } else {
+ VectorNormalize (ps->velocity);
+ VectorScale (ps->velocity, forward, ps->velocity);
+ }
+}
+
+
+/*
+===============
+PM_NoclipMove
+===============
+*/
+/**
+* Handles noclip movement
+*/
+static void PM_NoclipMove( void ) {
+ float speed, drop, friction, control, newspeed;
+ int i;
+ vec3_t wishvel;
+ float fmove, smove;
+ vec3_t wishdir;
+ float wishspeed;
+ float scale;
+ playerState_t *ps = pm->ps;
+
+ ps->viewheight = DEFAULT_VIEWHEIGHT;
+
+ // friction
+
+ speed = VectorLength (ps->velocity);
+ if (speed < 1)
+ {
+ VectorCopy (vec3_origin, ps->velocity);
+ }
+ else
+ {
+ drop = 0;
+
+ friction = pm_friction*1.5; // extra friction
+ control = speed < pm_stopspeed ? pm_stopspeed : speed;
+ drop += control*friction*pml.frametime;
+
+ // scale the velocity
+ newspeed = speed - drop;
+ if (newspeed < 0)
+ newspeed = 0;
+ newspeed /= speed;
+
+ VectorScale (ps->velocity, newspeed, ps->velocity);
+ }
+
+ // accelerate
+ scale = PM_CmdScale( &pm->cmd );
+
+ fmove = pm->cmd.forwardmove;
+ smove = pm->cmd.rightmove;
+
+ for (i=0 ; i<3 ; i++)
+ wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
+ wishvel[2] += pm->cmd.upmove;
+
+ VectorCopy (wishvel, wishdir);
+ wishspeed = VectorNormalize(wishdir);
+ wishspeed *= scale;
+
+ PM_Accelerate( wishdir, wishspeed, pm_accelerate );
+
+ // move
+ VectorMA (ps->origin, pml.frametime, ps->velocity, ps->origin);
+}
+
+
+/*
+===================
+PM_FreezeMove
+===================
+*/
+/**
+* Handles movement whole freezed
+*/
+static void PM_FreezeMove( void )
+{
+ trace_t trace;
+ short temp, i;
+ vec3_t moveto;
+ playerState_t *ps = pm->ps;
+
+ pm->mins[0] = DEFAULT_MINS_0; //-15
+ pm->mins[1] = DEFAULT_MINS_1;
+
+ pm->maxs[0] = DEFAULT_MAXS_0; //15
+ pm->maxs[1] = DEFAULT_MAXS_1;
+
+ pm->mins[2] = MINS_Z;
+ // stand up if possible
+ if (ps->pm_flags & PMF_DUCKED)
+ {
+ // try to stand up
+ pm->maxs[2] = 36; //32
+ pm->trace (&trace, ps->origin, pm->mins, pm->maxs, ps->origin, ps->clientNum, pm->tracemask );
+ if (!trace.allsolid)
+ ps->pm_flags &= ~PMF_DUCKED;
+ }
+
+ if (ps->pm_flags & PMF_DUCKED && ( !(ps->stats[EMOTES] & EMOTE_LOWER) && !ps->powerups[PW_FLIGHT] && !( ( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 ) ) ) )
+ {
+ pm->maxs[2] = 16;
+ ps->viewheight = CROUCH_VIEWHEIGHT;
+ }
+ else
+ {
+ if ( ps->stats[EMOTES] & EMOTE_LOWER && ps->legsTimer > 0 ) {
+ pm->maxs[2] = bg_emoteList[ps->legsTimer].hitBoxHeight;
+ ps->viewheight = bg_emoteList[ps->legsTimer].viewHeight;
+ //Com_Printf( S_COLOR_RED "%f", bg_emoteList[ps->legsTimer].viewHeight );
+ }
+ else {
+ //TiM - essentially flip the bounding box
+ /*if ( ps->powerups[PW_FLIGHT] && Q_fabs( ps->viewangles[PITCH] ) > 89.0f ) {
+ pm->maxs[2] = 92;
+ pm->mins[2] = 32;
+ ps->viewheight = DEFAULT_VIEWHEIGHT;
+ }*/
+ //else {
+ //TiM - Copied above
+ /*pm->maxs[2] = 36;
+ pm->mins[2] = MINS_Z;
+ ps->viewheight = DEFAULT_VIEWHEIGHT;*/
+ //}
+ ps->viewheight = DEFAULT_VIEWHEIGHT;
+ }
+ }
+
+ // circularly clamp the angles with deltas
+ for (i=0 ; i<3 ; i++) {
+ temp = pm->cmd.angles[i] + ps->delta_angles[i];
+ if ( i == PITCH ) {
+ // don't let the player look up or down more than 90 degrees
+ if ( temp > 16000 ) {
+ ps->delta_angles[i] = 16000 - pm->cmd.angles[i];
+ temp = 16000;
+ } else if ( temp < -16000 ) {
+ ps->delta_angles[i] = -16000 - pm->cmd.angles[i];
+ temp = -16000;
+ }
+ }
+// ps->viewangles[i] = SHORT2ANGLE(temp); // Clear the view angles, but don't set them.
+ }
+
+ VectorCopy (ps->origin, moveto);
+ moveto[2] -= 16;
+
+ // test the player position if they were a stepheight higher
+ pm->trace (&trace, ps->origin, pm->mins, pm->maxs, moveto, ps->clientNum, pm->tracemask);
+ if ( trace.fraction < 1.0)
+ { // Something just below, snap to it, to prevent a little "hop" after the holodeck fades in.
+ VectorCopy (trace.endpos, ps->origin);
+ ps->groundEntityNum = trace.entityNum;
+
+ // Touch it.
+ PM_AddTouchEnt( trace.entityNum );
+
+ }
+}
+
+
+//============================================================================
+
+//RPG-X | GSIO01 | 20/05/2009:
+/**
+* Get the corresponding landing sound for each surface type
+*/
+static int PM_LandsoundForSurface( int fallType ) {
+ if( pm->ps->stats[PW_INVIS] )
+ return 0;
+
+ switch(fallType) {
+ case 1:
+ if(pml.groundTrace.surfaceFlags & SURF_GRASS)
+ return EV_FALL_MEDIUM_GRASS;
+ else if(pml.groundTrace.surfaceFlags & SURF_GRAVEL)
+ return EV_FALL_MEDIUM_GRAVEL;
+ else if(pml.groundTrace.surfaceFlags & SURF_SNOW)
+ return EV_FALL_MEDIUM_SNOW;
+ else if(pml.groundTrace.surfaceFlags & SURF_WOOD)
+ return EV_FALL_MEDIUM_WOOD;
+ else
+ return EV_FALL_MEDIUM;
+ break;
+ case 2:
+ if(pml.groundTrace.surfaceFlags & SURF_GRASS)
+ return EV_FALL_FAR_GRASS;
+ else if(pml.groundTrace.surfaceFlags & SURF_GRAVEL)
+ return EV_FALL_FAR_GRAVEL;
+ else if(pml.groundTrace.surfaceFlags & SURF_SNOW)
+ return EV_FALL_FAR_SNOW;
+ else if(pml.groundTrace.surfaceFlags & SURF_WOOD)
+ return EV_FALL_FAR_WOOD;
+ else
+ return EV_FALL_FAR;
+ break;
+ default:
+ if(pml.groundTrace.surfaceFlags & SURF_GRASS)
+ return EV_FALL_SHORT_GRASS;
+ else if(pml.groundTrace.surfaceFlags & SURF_GRAVEL)
+ return EV_FALL_SHORT_GRAVEL;
+ else if(pml.groundTrace.surfaceFlags & SURF_SNOW)
+ return EV_FALL_SHORT_SNOW;
+ else if(pml.groundTrace.surfaceFlags & SURF_WOOD)
+ return EV_FALL_SHORT_WOOD;
+ else
+ return EV_FALL_SHORT;
+ break;
+ }
+}
+
+/*
+================
+PM_FootstepForSurface
+================
+*/
+/**
+* \return an event number apropriate for the groundsurface
+*/
+static int PM_FootstepForSurface( void ) {
+ //cloaked people make no noise
+ if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS || pm->ps->stats[PW_INVIS] ) {
+ return 0;
+ }
+ if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) {
+ return EV_FOOTSTEP_METAL;
+ }
+ //RPG-X | GSIO01 | 20.05.2009 | START MOD
+ if ( pml.groundTrace.surfaceFlags & SURF_GRASS ) {
+ return EV_FOOTSTEP_GRASS;
+ }
+ if ( pml.groundTrace.surfaceFlags & SURF_GRAVEL ) {
+ return EV_FOOTSTEP_GRAVEL;
+ }
+ if ( pml.groundTrace.surfaceFlags & SURF_SNOW ) {
+ return EV_FOOTSTEP_SNOW;
+ }
+ if ( pml.groundTrace.surfaceFlags & SURF_WOOD ) {
+ return EV_FOOTSTEP_WOOD;
+ }
+ //RPG-X | GSIO01 | 20.05.2009 | END MOD
+ return EV_FOOTSTEP;
+}
+
+
+/*
+=================
+PM_CrashLand
+=================
+*/
+/**
+* Check for hard landings that generate sound events
+*/
+static void PM_CrashLand( void ) {
+ float delta;
+ float dist;
+ float vel, acc;
+ float t;
+ float a, b, c, den;
+ playerState_t *ps = pm->ps;
+
+ // decide which landing animation to use
+ if ( ps->pm_flags & PMF_BACKWARDS_JUMP ) {
+ // PM_ForceLegsAnim( LEGS_LANDB );
+ } else {
+ // PM_ForceLegsAnim( LEGS_LAND );
+ }
+
+// ps->legsTimer = 0; //TIMER_LAND
+
+ // calculate the exact velocity on landing
+ dist = ps->origin[2] - pml.previous_origin[2];
+ vel = pml.previous_velocity[2];
+ acc = -ps->gravity;
+
+ a = acc / 2;
+ b = vel;
+ c = -dist;
+
+ den = b * b - 4 * a * c;
+ if ( den < 0 ) {
+ return;
+ }
+ t = (-b - sqrt( den ) ) / ( 2 * a );
+
+ delta = vel + t * acc;
+ delta = delta*delta * 0.0001;
+
+ // ducking while falling doubles damage
+ if ( ps->pm_flags & PMF_DUCKED ) {
+ delta *= 2;
+ }
+
+ // never take falling damage if completely underwater
+ if ( pm->waterlevel == 3 ) {
+ return;
+ }
+
+ // reduce falling damage if there is standing water
+ if ( pm->waterlevel == 2 ) {
+ delta *= 0.25;
+ }
+ if ( pm->waterlevel == 1 ) {
+ delta *= 0.5;
+ }
+
+ if ( delta < 1 ) {
+ return;
+ }
+
+ // create a local entity event to play the sound
+
+ // SURF_NODAMAGE is used for bounce pads where you don't ever
+ // want to take damage or play a crunch sound
+ if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) {
+ if ( delta > 55 || ps->stats[STAT_HEALTH] <= 1 ) { //60 //TiM a bit hacky, but I want this to play any time we fall when dead
+ PM_AddEvent( /*EV_FALL_FAR*/PM_LandsoundForSurface(2) ); //GSIO01 | 20/05/2009
+ } else if ( delta > 35 ) { //40
+ // this is a pain grunt, so don't play it if dead
+ if ( ps->stats[STAT_HEALTH] > 1 ) { //0
+ PM_AddEvent( /*EV_FALL_MEDIUM*/PM_LandsoundForSurface(1) ); //GSIO01 | 20/05/2009
+ }
+ } else if ( delta > 5 ) { //7
+ PM_AddEvent( /*EV_FALL_SHORT*/ PM_LandsoundForSurface(0) ); //GSIO01 | 20/05/2009
+ } else {
+ PM_AddEvent( PM_FootstepForSurface() );
+ }
+ }
+
+ // start footstep cycle over
+ ps->bobCycle = 0; //TiM: was commented out... :P
+}
+
+
+
+/*
+=============
+PM_CorrectAllSolid
+=============
+*/
+static void PM_CorrectAllSolid( void ) {
+ if ( pm->debugLevel ) {
+ Com_Printf("%i:allsolid\n", c_pmove);
+ }
+
+ // FIXME: jitter around
+
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+}
+
+
+/*
+=============
+PM_GroundTraceMissed
+=============
+*/
+/**
+* The ground trace didn't hit a surface, so we are in freefall
+*/
+static void PM_GroundTraceMissed( void ) {
+ trace_t trace;
+ vec3_t point;
+ playerState_t *ps = pm->ps;
+
+ if ( ps->groundEntityNum != ENTITYNUM_NONE ) {
+ // we just transitioned into freefall
+ if ( pm->debugLevel ) {
+ Com_Printf("%i:lift\n", c_pmove);
+ }
+
+ // if they aren't in a jumping animation and the ground is a ways away, force into it
+ // if we didn't do the trace, the player would be backflipping down staircases
+ VectorCopy( ps->origin, point );
+ point[2] -= 64;
+
+ pm->trace (&trace, ps->origin, pm->mins, pm->maxs, point, ps->clientNum, pm->tracemask);
+ if ( trace.fraction == 1.0 && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) {
+ if ( pm->cmd.forwardmove >= 0 ) {
+ PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) );
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue );
+ ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ } else {
+ PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) );
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue );
+ ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+
+ }
+ }
+
+ ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+}
+
+
+/*
+=============
+PM_GroundTrace
+=============
+*/
+/**
+* Does ad trace to the ground
+*/
+static void PM_GroundTrace( void ) {
+ vec3_t point;
+ trace_t trace;
+ playerState_t *ps = pm->ps;
+
+ point[0] = ps->origin[0];
+ point[1] = ps->origin[1];
+ point[2] = ps->origin[2] - 0.25;
+
+ pm->trace (&trace, ps->origin, pm->mins, pm->maxs, point, ps->clientNum, pm->tracemask);
+ pml.groundTrace = trace;
+
+ // do something corrective if the trace starts in a solid...
+ if ( trace.allsolid ) {
+ PM_CorrectAllSolid();
+ return;
+ }
+
+ // if the trace didn't hit anything, we are in free fall
+ if ( trace.fraction == 1.0 ) {
+ PM_GroundTraceMissed();
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+ return;
+ }
+
+ // check if getting thrown off the ground
+ if ( ps->velocity[2] > 0 && DotProduct( ps->velocity, trace.plane.normal ) > 10 && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) {
+ if ( pm->debugLevel ) {
+ Com_Printf("%i:kickoff\n", c_pmove);
+ }
+ // go into jump animation
+ if ( pm->cmd.forwardmove >= 0 ) {
+ PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) );
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue );
+ ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
+ } else {
+ PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) );
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue );
+ ps->pm_flags |= PMF_BACKWARDS_JUMP;
+ }
+
+ ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+ return;
+ }
+
+ // slopes that are too steep will not be considered onground
+ if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) {
+ if ( pm->debugLevel ) {
+ Com_Printf("%i:steep\n", c_pmove);
+ }
+ // FIXME: if they can't slide down the slope, let them
+ // walk (sharp crevices)
+ ps->groundEntityNum = ENTITYNUM_NONE;
+ pml.groundPlane = qtrue;
+ pml.walking = qfalse;
+ return;
+ }
+
+ pml.groundPlane = qtrue;
+ pml.walking = qtrue;
+
+ // hitting solid ground will end a waterjump
+ if (ps->pm_flags & PMF_TIME_WATERJUMP)
+ {
+ ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND);
+ ps->pm_time = 0;
+ }
+
+ if ( ps->groundEntityNum == ENTITYNUM_NONE ) {
+ // just hit the ground
+ if ( pm->debugLevel ) {
+ Com_Printf("%i:Land\n", c_pmove);
+ }
+
+ PM_CrashLand();
+
+ // don't do landing time if we were just going down a slope
+ if ( pml.previous_velocity[2] < -200 ) {
+ // don't allow another jump for a little while
+ ps->pm_flags |= PMF_TIME_LAND;
+ ps->pm_time = 250;
+ }
+ }
+
+ ps->groundEntityNum = trace.entityNum;
+
+ // don't reset the z velocity for slopes
+// pm->ps->velocity[2] = 0;
+
+ PM_AddTouchEnt( trace.entityNum );
+}
+
+
+/*
+=============
+PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving
+=============
+*/
+/**
+* Set water level
+*/
+static void PM_SetWaterLevel( void ) {
+ vec3_t point;
+ int cont;
+ int sample1;
+ int sample2;
+ playerState_t *ps = pm->ps;
+
+ //
+ // get waterlevel, accounting for ducking
+ //
+ pm->waterlevel = 0;
+ pm->watertype = 0;
+
+ point[0] = ps->origin[0];
+ point[1] = ps->origin[1];
+ point[2] = ps->origin[2] + MINS_Z + 1;
+ cont = pm->pointcontents( point, ps->clientNum );
+
+ if ( cont & (MASK_WATER|CONTENTS_LADDER) ) {
+ sample2 = ps->viewheight - MINS_Z;
+ sample1 = sample2 / 2;
+
+ pm->watertype = cont;
+ pm->waterlevel = 1;
+ point[2] = ps->origin[2] + MINS_Z + sample1;
+ cont = pm->pointcontents (point, ps->clientNum );
+ if ( cont & (MASK_WATER|CONTENTS_LADDER) ) {
+ pm->waterlevel = 2;
+ point[2] = ps->origin[2] + MINS_Z + sample2;
+ cont = pm->pointcontents (point, ps->clientNum );
+ if ( cont & (MASK_WATER|CONTENTS_LADDER) ){
+ pm->waterlevel = 3;
+ }
+ }
+ }
+
+}
+
+
+
+/*
+==============
+PM_CheckDuck
+==============
+*/
+/**
+* Sets mins, maxs, and pm->ps->viewheight
+*/
+static void PM_CheckDuck (void)
+{
+ trace_t trace;
+ playerState_t *ps = pm->ps;
+
+ pm->mins[0] = DEFAULT_MINS_0; //-15
+ pm->mins[1] = DEFAULT_MINS_1;
+
+ pm->maxs[0] = DEFAULT_MAXS_0;
+ pm->maxs[1] = DEFAULT_MAXS_1;
+
+ pm->mins[2] = MINS_Z;
+
+ if (ps->pm_type == PM_DEAD)
+ {
+ pm->maxs[2] = -8;
+ ps->viewheight = DEAD_VIEWHEIGHT;
+ return;
+ }
+
+ if (pm->cmd.upmove < 0)
+ { // duck
+ ps->pm_flags |= PMF_DUCKED;
+ }
+ else
+ { // stand up if possible
+ if (ps->pm_flags & PMF_DUCKED)
+ {
+ // try to stand up
+ pm->maxs[2] = 36; //32
+ pm->trace (&trace, ps->origin, pm->mins, pm->maxs, ps->origin, ps->clientNum, pm->tracemask );
+ if (!trace.allsolid)
+ ps->pm_flags &= ~PMF_DUCKED;
+ }
+ }
+
+ if ( ps->pm_flags & PMF_DUCKED && !( (ps->stats[EMOTES] & EMOTE_LOWER) || ps->powerups[PW_FLIGHT] || ( ps->powerups[PW_EVOSUIT] && ps->gravity == 0 ) ) )
+ {
+ pm->maxs[2] = 16;
+ ps->viewheight = CROUCH_VIEWHEIGHT;
+ }
+ else
+ {
+ if ( ps->stats[EMOTES] & EMOTE_LOWER && ps->legsTimer > 0 ) {
+ pm->maxs[2] = bg_emoteList[ps->legsTimer].hitBoxHeight;
+ ps->viewheight = bg_emoteList[ps->legsTimer].viewHeight;
+
+ //Com_Printf( S_COLOR_RED "legsTimer = %i, viewHeight = %i\n", ps->legsTimer, ps->viewheight );
+ }
+ else {
+ //TiM - essentially flip the bounding box
+ /*if ( ps->powerups[PW_FLIGHT] && Q_fabs( ps->viewangles[PITCH] ) > 89.0f ) {
+ pm->maxs[2] = 92;
+ pm->mins[2] = 32;
+ ps->viewheight = DEFAULT_VIEWHEIGHT;
+ }*/
+ //else {
+ pm->maxs[2] = 36;
+ pm->mins[2] = MINS_Z;
+ ps->viewheight = DEFAULT_VIEWHEIGHT;
+ //}
+ }
+ }
+
+ //Com_Printf( "Viewheight is %i\n", ps->viewheight );
+}
+
+
+
+//===================================================================
+
+//static qboolean ps->didFly;
+//static
+
+/*
+===============
+PM_Footsteps
+===============
+*/
+/**
+* Does what it name suggests it handles footsteps.
+*/
+static void PM_Footsteps( void ) {
+ float bobmove;
+ int old;
+ qboolean footstep;
+ playerState_t *ps = pm->ps;
+ //qboolean didFly;
+
+ //
+ // calculate speed and cycle to be used for
+ // all cyclic walking effects
+ //
+ pm->xyspeed = sqrt( ps->velocity[0] * ps->velocity[0]
+ + ps->velocity[1] * ps->velocity[1] );
+
+ pm->xyzspeed = sqrt( ps->velocity[0] * ps->velocity[0] //XVel - left + right
+ + ps->velocity[1] * ps->velocity[1] //YVel - forward + back
+ + ps->velocity[2] * ps->velocity[2] ); //ZVel - up + down
+
+ //RPG-X : TiM *****************************************************
+ //Cheesy Halo style death flying!
+ if ( ps->stats[STAT_HEALTH] <= 1 && !ps->powerups[PW_QUAD] && !ps->powerups[PW_BEAM_OUT] ) { //if dead
+ /*TiM: clip brushes register as ENTITYNUM_NONE
+ (So if they landed on a shuttle model for instance, the fall anim would still loop O_o )
+ so they gotta be moving as well to trigger this*/
+
+ //Com_Printf("groundEnt = %i, speed = %f, animState = %i\n",ps->groundEntityNum, pm->xyzspeed, ps->pm_flags);
+
+ if ( ps->groundEntityNum == ENTITYNUM_NONE && pm->xyzspeed && pm->waterlevel < 2 ) {
+
+ ps->pm_flags |= ANIM_DIDFLY;
+
+ PM_ContinueLegsAnim( BOTH_FALLDEATH1INAIR, qtrue );
+
+ if ( ps->weaponstate == WEAPON_READY )
+ {
+ PM_ContinueTorsoAnim( BOTH_FALLDEATH1INAIR, qtrue );
+ }
+ }
+
+ else {
+ if ( ps->pm_flags & ANIM_DIDFLY ) {
+ //TiM: Save flags. Use anim nums if possible
+ //if (ps->torsoAnim == BOTH_FALLDEATH1INAIR && ps->legsAnim == BOTH_FALLDEATH1INAIR ) {
+ PM_ContinueLegsAnim( BOTH_FALLDEATH1LAND, qtrue );
+
+
+ if ( ps->weaponstate == WEAPON_READY )
+ {
+ PM_ContinueTorsoAnim( BOTH_FALLDEATH1LAND, qtrue );
+ }
+
+ }
+ }
+ return;
+ }
+ else { //Reset splat boolean
+ if ( ps->pm_flags & ANIM_DIDFLY && ps->pm_type != PM_DEAD ) {
+ ps->pm_flags &= ~ANIM_DIDFLY;
+ }
+ }
+
+ //RPG-X : TiM *****************************************************
+ //Ladder Animations
+
+ //If not on ladder, reset
+ if ( !(pm->watertype & CONTENTS_LADDER) )
+ {
+ if ( ps->pm_flags & ANIM_ONLADDER ) {
+ ps->pm_flags &= ~( ANIM_ONLADDER );
+ }
+
+ if ( ( !(ps->stats[EMOTES] & EMOTE_UPPER ) || !(ps->stats[EMOTES] & EMOTE_LOWER ) )
+ && ps->stats[EMOTES] & EMOTE_CLAMP_BODY ) {
+ ps->stats[EMOTES] &= ~EMOTE_CLAMP_BODY;
+ }
+ //ps->pm_flags &= ~( ANIM_OFFLADDER );
+ }
+
+ //If on ladder, but not touching the ground
+ if ( (pm->watertype & CONTENTS_LADDER) && ( ps->groundEntityNum == ENTITYNUM_NONE ) )
+ {
+ if ( !(ps->pm_flags & ANIM_ONLADDER) ) {
+ ps->pm_flags |= ANIM_ONLADDER ;
+ ps->stats[EMOTES] |= EMOTE_CLAMP_BODY;
+ }
+ //ps->pm_flags &= ~( ANIM_OFFLADDER );
+ }
+
+ //If was going down the ladder, and hit the floor
+/* if ( !(ps->pm_flags & (ANIM_ONLADDER) ) &&
+ !(ps->pm_flags & (ANIM_OFFLADDER) ) &&
+ ( pm->watertype & CONTENTS_LADDER) &&
+ ( ps->groundEntityNum != ENTITYNUM_NONE ) )
+ {
+ ps->pm_flags |= (ANIM_ONLADDER);
+ ps->pm_flags &= ~(ANIM_OFFLADDER);
+
+ if ( ps->legsAnim == BOTH_LADDER_DWN1 || ps->legsAnim == BOTH_LADDER_IDLE) { //Going DOWN!
+ ps->pm_flags &= ~(ANIM_ONLADDER);
+ ps->pm_flags |= (ANIM_OFFLADDER);
+ }
+ }
+*/
+ //Com_Printf("pm->onLadder = %i, pm->offLadder = %i, vel = %i\n", pm->onLadder, pm->offLadder, ps->velocity[2] );
+
+ //Transition anim to get off ladder
+ if ( ( pm->watertype & CONTENTS_LADDER) && ( ps->groundEntityNum != ENTITYNUM_NONE ) && (ps->pm_flags & ANIM_ONLADDER) ) {//We JUST hit a ladder on the ground
+ PM_ContinueLegsAnim( BOTH_OFFLADDER_BOT1, qtrue );
+ //ps->legsTimer = TIMER_GESTURE;
+
+ if ( ps->weaponstate == WEAPON_READY )
+ {
+ PM_ContinueTorsoAnim( BOTH_OFFLADDER_BOT1, qtrue );
+ //ps->torsoTimer = TIMER_GESTURE;
+ }
+ //pm->offLadder = qtrue;
+ //if ( !(ps->pm_flags & ANIM_UPPER_LOOPING) ) {
+ ps->stats[EMOTES] &= ~EMOTE_CLAMP_BODY;
+ //}
+
+ return;
+ }
+
+ //Transition anim to get on ladder
+ if ( ( pm->watertype & CONTENTS_LADDER) && ( ps->groundEntityNum != ENTITYNUM_NONE ) && !(ps->pm_flags & ANIM_ONLADDER) ) {//We JUST hit a ladder on the ground
+ PM_ContinueLegsAnim( BOTH_ONLADDER_BOT1, qtrue );
+ //ps->legsTimer = TIMER_GESTURE;
+
+ if ( ps->weaponstate == WEAPON_READY )
+ {
+ PM_ContinueTorsoAnim( BOTH_ONLADDER_BOT1, qtrue );
+ //ps->torsoTimer = TIMER_GESTURE;
+ }
+ //pm->onLadder = qfalse;
+
+ return;
+ }
+
+ if ( ps->groundEntityNum == ENTITYNUM_NONE ) {
+ if (pm->watertype & CONTENTS_LADDER)
+ {//FIXME: check for watertype, save waterlevel for whether to play
+ //the get off ladder transition anim
+
+ if ( ps->velocity[2] )
+ {//going up or down it
+ int anim;
+ if ( ps->velocity[2] > 0 )
+ {
+ anim = BOTH_LADDER_UP1;
+ }
+ else
+ {
+ anim = BOTH_LADDER_DWN1;
+ }
+ PM_ContinueLegsAnim( anim, qtrue );
+ //if ( pm->waterlevel >= 2 ) //arms on ladder
+ if ( ps->weaponstate == WEAPON_READY )
+ {
+ PM_ContinueTorsoAnim( anim, qtrue );
+ }
+ if (fabs(ps->velocity[2]) >5) {
+ bobmove = 0.005 * fabs(ps->velocity[2]); // climbing bobs slow
+ if (bobmove > 0.3)
+ bobmove = 0.3F;
+ }
+ }
+ else
+ {
+ PM_ContinueLegsAnim( BOTH_LADDER_IDLE, qtrue );
+ //ps->legsTimer += 300;
+ //if ( pm->waterlevel >= 2 ) //arms on ladder
+ if ( ps->weaponstate == WEAPON_READY )
+ {
+ PM_ContinueTorsoAnim( BOTH_LADDER_IDLE, qtrue );
+ //ps->torsoTimer += 300;
+ }
+ }
+ return;
+ }//******************************************************************
+ else {
+ // airborne leaves position in cycle intact, but doesn't advance
+ if ( pm->waterlevel > 2 ) { //TiM: swimming is more hardcore now //1
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_SWIM, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue );
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_SWIM, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue );
+ } /*else if ( ps->pm_flags & PMF_DUCKED ) {
+ PM_ContinueLegsAnim( BOTH_CROUCH2IDLE ); //BOTH_CROUCH1IDLE
+ }*/
+
+ return;
+ }
+ }
+
+
+ //Com_Printf( "Speed: %f\n", pm->xyspeed );
+
+ // if not trying to move
+ if ( ( !ps->speed || pm->xyspeed < 1.0f || !(pm->cmd.forwardmove || pm->cmd.rightmove) )
+ && pm->waterlevel < 3
+ && !ps->powerups[PW_FLIGHT] && !(( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 ))
+ /*&& !( pm->watertype & MASK_WATER )*/ )
+ {
+ //Com_Printf("Truuue\n" );
+
+ if ( pm->xyspeed > 1.0f && !( ps->pm_flags & PMF_DUCKED ) && !( ps->stats[EMOTES] & EMOTE_LOWER ) )
+ { //TiM: When you want to duck, you will duck. no delays
+ if ( !( pm->cmd.buttons & BUTTON_WALKING ) && !(ps->pm_flags & PMF_DUCKED) ) {
+ if ( ps->weaponstate == WEAPON_READY && !PM_HoldingSpillableWeapon() ) {
+ PM_ContinueTorsoAnim( BOTH_RUN1STOP, qtrue ); //BOTH_RUN1STOP
+ }
+ PM_ContinueLegsAnim( BOTH_RUN1STOP, qtrue );
+ }
+
+ return;
+ }
+ else { // <5
+ ps->bobCycle = 0; // start at beginning of cycle again
+ if ( ps->pm_flags & PMF_DUCKED && !(ps->stats[EMOTES] & EMOTE_LOWER ) ) {
+
+ if ( ps->weaponstate == WEAPON_READY ) {
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_CROUCH, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ }
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_CROUCH, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue );
+
+ /*if ( !(ps->pm_flags & ANIM_CROUCHING) ) { //okay, we've obviously JUST crouched...
+ ps->pm_flags |= ANIM_CROUCHING;
+ /*if ( ps->weaponstate == WEAPON_READY ) {
+ PM_StartTorsoAnim( PM_GetAnim( "docrouch", qtrue ) );
+ ps->torsoTimer += 1200;
+ }
+
+ PM_StartLegsAnim( PM_GetAnim( "docrouch", qfalse ) );
+ ps->legsTimer += 1200;*/
+ //}
+
+ } else {
+
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qfalse );
+
+ /*if ( (ps->pm_flags & ANIM_CROUCHING) ) { //okay, we've obviously JUST uncrouched...
+ ps->pm_flags &= ~ANIM_CROUCHING;
+
+ if ( ps->weaponstate == WEAPON_READY ) {
+ PM_StartTorsoAnim( PM_GetAnim( ANIM_UNCROUCH, qtrue ) );
+ ps->torsoTimer = 1100;
+ //Com_Printf("Anim: %i, timer: %i\n", ps->torsoAnim, ps->torsoTimer);
+ }
+
+ PM_StartLegsAnim( PM_GetAnim( ANIM_UNCROUCH, qfalse ) );
+ ps->introTime = 1100; //legsTimer
+ }*/
+
+ }
+ }
+ return;
+ }
+
+ footstep = qfalse;
+
+ //TiM : in case we're part-way thru a transition anim,
+ //reset the anim timer
+ //ps->torsoTimer = 0;
+ //ps->legsTimer = 0;
+
+ //TiM : Kill this when swimming as it screws up animations
+ //Also... kill when speed is 0.. running on the spot is silly lol
+ //Also, disable when flying. It looks ludricrous if we run upside down lol
+ if ( pm->waterlevel == 3 || ps->speed == 0 || ps->powerups[PW_FLIGHT] > 0 || pm->xyspeed < 1.0f || (( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 )) )
+ {
+ return;
+ }
+
+ if ( ps->pm_flags & PMF_DUCKED ) {
+ bobmove = 0.5; // ducked characters bob much faster
+ //HACK coz this damn thing screws up crouch firing anims otherwise T_T
+ if ( ps->weaponstate == WEAPON_READY ) {
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_CROUCHWALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ }
+
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_CROUCHWALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue );
+ // ducked characters never play footsteps
+ }
+ else if ( ps->pm_flags & PMF_BACKWARDS_RUN )
+ {
+ if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) {
+ bobmove = 0.4; // faster speeds bob faster
+ footstep = qtrue;
+
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_RUNB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_RUNB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_BACK
+ } else {
+ bobmove = 0.3;
+
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_WALKB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_WALKB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_BACK
+ }
+
+ } else {
+
+ if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) {
+ bobmove = 0.4; // faster speeds bob faster
+ footstep = qtrue;
+
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_RUN, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_RUN, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_RUN
+
+ } else {
+ bobmove = 0.3; // walking bobs slow //0.3
+ if ( ps->weaponstate == WEAPON_READY )
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_WALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+
+ PM_ContinueLegsAnim( PM_GetAnim( ANIM_WALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_WALK
+ }
+ }
+
+ // check for footstep / splash sounds
+ old = ps->bobCycle;
+ ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255;
+
+ // if we just crossed a cycle boundary, play an apropriate footstep event
+ if ( ( ( old + 64 ) ^ ( ps->bobCycle + 64 ) ) & 128 && !ps->stats[PW_INVIS] ) {
+ if ( pm->watertype & CONTENTS_LADDER ) {// on ladder
+ if ( !pm->noFootsteps ) {
+ PM_AddEvent( EV_FOOTSTEP_METAL );
+ }
+ } else if ( pm->waterlevel == 0 ) {
+ // on ground will only play sounds if running
+ if ( footstep && !pm->noFootsteps ) {
+ PM_AddEvent( PM_FootstepForSurface() );
+ }
+ } else if ( pm->waterlevel == 1 ) {
+ // splashing
+ PM_AddEvent( EV_FOOTSPLASH );
+ } else if ( pm->waterlevel == 2 ) {
+ // wading / swimming at surface
+ PM_AddEvent( EV_SWIM );
+ } else if ( pm->waterlevel == 3 ) {
+ // no sound when completely underwater
+
+ }
+ }
+}
+
+/*
+==============
+PM_WaterEvents
+==============
+*/
+/**
+* Generate sound events for entering and leaving water
+*/
+static void PM_WaterEvents( void ) { // FIXME?
+ if ( pm->watertype & CONTENTS_LADDER || pm->ps->stats[PW_INVIS] ) //fake water for ladder
+ {
+ return;
+ }
+ //
+ // if just entered a water volume, play a sound
+ //
+ if (!pml.previous_waterlevel && pm->waterlevel) {
+ PM_AddEvent( EV_WATER_TOUCH );
+ }
+
+ //
+ // if just completely exited a water volume, play a sound
+ //
+ if (pml.previous_waterlevel && !pm->waterlevel) {
+ PM_AddEvent( EV_WATER_LEAVE );
+ }
+
+ //
+ // check for head just going under water
+ //
+ if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) {
+ PM_AddEvent( EV_WATER_UNDER );
+ }
+
+ //
+ // check for head just coming out of water
+ //
+ if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) {
+ PM_AddEvent( EV_WATER_CLEAR );
+ }
+}
+
+
+/*
+===============
+PM_BeginWeaponChange
+===============
+*/
+/**
+* Begins weapon change
+*/
+static void PM_BeginWeaponChange( int weapon ) {
+ playerState_t *ps = pm->ps;
+
+ if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) {
+ return;
+ }
+
+ if ( !( ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
+ return;
+ }
+
+ if ( ps->weaponstate == WEAPON_DROPPING ) {
+ return;
+ }
+
+ PM_AddEvent( EV_CHANGE_WEAPON );
+ ps->weaponstate = WEAPON_DROPPING;
+ ps->weaponTime += 200;
+ PM_ForceTorsoAnim( TORSO_DROPWEAP1, qfalse );
+}
+
+
+/*
+===============
+PM_FinishWeaponChange
+===============
+*/
+/**
+* Finishs weapon change
+*/
+static void PM_FinishWeaponChange( void ) {
+ int weapon;
+ playerState_t *ps = pm->ps;
+
+ weapon = pm->cmd.weapon;
+ if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) {
+ weapon = WP_NONE;
+ }
+
+ if ( !( ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
+ weapon = WP_NONE;
+ }
+
+ ps->weapon = weapon;
+ ps->weaponstate = WEAPON_RAISING;
+ ps->weaponTime += 250;
+ PM_ForceTorsoAnim( TORSO_RAISEWEAP1, qfalse );
+}
+
+
+/*
+==============
+PM_TorsoAnimation
+
+==============
+*/
+/**
+* Once handled torso animation
+*/
+static void PM_TorsoAnimation( void )
+{
+
+ if ( pm->ps->weaponstate == WEAPON_READY )
+ {
+ /*if ( pm->ps->weapon == WP_PHASER || //RPG-X - TiM: Making the default pose anim better
+ pm->ps->weapon == WP_DISRUPTOR )
+ {
+ PM_ContinueTorsoAnim( TORSO_WEAPONIDLE1 );
+ }
+ else
+ {
+ PM_ContinueTorsoAnim( TORSO_WEAPONREADY2 );
+ }
+ if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) {
+ //PM_ContinueTorsoAnim( BOTH_RUN1 );
+ }
+ else {
+ PM_ContinueTorsoAnim( BOTH_WALK1 );
+ }*/
+ }
+
+ //PM_ContinueTorsoAnim( TORSO_STAND2 );
+ return;
+}
+
+
+#define PHASER_AMMO_PER_SHOT 1
+#define PHASER_ALT_AMMO_PER_SHOT 2
+
+//! alt ammo usage
+int altAmmoUsage[WP_NUM_WEAPONS] =
+{
+ 0, //!ps;
+
+ // don't allow attack until all buttons are up
+ if ( ps->pm_flags & PMF_RESPAWNED ) {
+ return;
+ }
+
+ // ignore if spectator
+ if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
+ return;
+ }
+
+ //Check for phaser ammo recharge
+ //RPG-X: Marcin: don't! - 30/12/2008
+ if ( 0 ) //( (ps->rechargeTime <= 0) && ( ps->ammo[WP_PHASER] < PHASER_AMMO_MAX) )
+ {
+ ps->rechargeTime = PHASER_RECHARGE_TIME;
+ ps->ammo[WP_PHASER]++;
+ }
+ else
+ {
+ ps->rechargeTime -= pml.msec;
+ }
+
+ // check for dead player
+ if ( ps->stats[STAT_HEALTH] <= 0 ) {
+ ps->weapon = WP_NONE;
+ return;
+ }
+
+ // check for item using
+ if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE )
+ {
+ if ( ! ( ps->pm_flags
+& PMF_USE_ITEM_HELD ) )
+ {
+ // I have commented out this code because, we want the medkit to ALWAYS be used.
+
+// if ( bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT
+// && ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] )
+// {
+// // don't use medkit if at max health
+// }
+// else
+ {
+ int tag = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag;
+ ps->pm_flags |= PMF_USE_ITEM_HELD;
+ PM_AddEvent( EV_USE_ITEM0 + tag );
+ // if we're placing the detpack, don't remove it from our "inventory"
+ if ( (HI_DETPACK == tag) /* || (HI_TRANSPORTER == tag)) */ &&
+ (IT_HOLDABLE == bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giType) )
+ {
+ // are we placing it?
+ if (2 == ps->stats[STAT_USEABLE_PLACED])
+ {
+ // we've placed the first stage of a 2-stage transporter
+ }
+ else if (ps->stats[STAT_USEABLE_PLACED])
+ {
+ // we already placed it, we're activating it.
+ ps->stats[STAT_HOLDABLE_ITEM] = 0;
+ }
+ }
+ else
+ {
+ ps->stats[STAT_HOLDABLE_ITEM] = 0;
+ }
+ }
+ return;
+ }
+ }
+ else
+ {
+ ps->pm_flags &= ~PMF_USE_ITEM_HELD;
+ }
+
+
+ // make weapon function
+ if ( ps->weaponTime > 0 ) {
+ ps->weaponTime -= pml.msec;
+ }
+
+ // check for weapon change
+ // can't change if weapon is firing, but can change
+ // again if lowering or raising
+ if ( ps->weaponTime <= 0 || ps->weaponstate != WEAPON_FIRING ) {
+ if ( ps->weapon != pm->cmd.weapon ) {
+ PM_BeginWeaponChange( pm->cmd.weapon );
+ }
+ }
+
+ if ( ps->weaponTime > 0 ) {
+ return;
+ }
+
+ // change weapon if time
+ if ( ps->weaponstate == WEAPON_DROPPING ) {
+ PM_FinishWeaponChange();
+ return;
+ }
+
+ if ( ps->weaponstate == WEAPON_RAISING )
+ {
+ ps->weaponstate = WEAPON_READY;
+ /*if ( ps->weapon == WP_PHASER || //RPG-X - TiM: Making the default pose anim better
+ ps->weapon == WP_DISRUPTOR )
+ {
+ PM_StartTorsoAnim( TORSO_WEAPONREADY2 );
+ }
+ else
+ {
+ PM_StartTorsoAnim( TORSO_WEAPONIDLE1 );
+ }*/
+
+ PM_StartTorsoAnim( PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+
+ return;
+ }
+
+ // check for fire
+ if ( !(pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_ALT_ATTACK) ) )
+ {
+ ps->weaponTime = 0;
+ ps->weaponstate = WEAPON_READY;
+
+ return;
+ }
+
+ // take an ammo away if not infinite
+ if ( ps->ammo[ ps->weapon ] != -1 )
+ {
+ if (pm->cmd.buttons & BUTTON_ALT_ATTACK)
+ {
+ // alt fire
+ // check for out of ammo
+ if ( ps->ammo[ps->weapon] < altAmmoUsage[ps->weapon])
+ {
+ //FIXME: flash a message and sound that indicates not enough ammo
+// PM_AddEvent( EV_NOAMMO_ALT );
+// ps->weaponTime += 500;
+// ps->eFlags &= ~EF_ALT_FIRING;
+// ps->eFlags &= ~EF_FIRING;
+/// ps->weaponstate = WEAPON_READY;
+
+ /*
+ RPG-X | Phenix | 27/02/2005
+ if ( ps->weapon == WP_PHASER ) // phaser out of ammo is special case
+ {
+ ps->ammo[ps->weapon] = 0;
+ }
+ else
+ {
+ PM_AddEvent( EV_NOAMMO_ALT ); // Just try to switch weapons like any other.
+
+ // check out the EV_NOAMMO_ALT event
+
+ ps->weaponTime += 500;
+ return;
+ }*/
+ }
+ else
+ {
+ //ps->ammo[ps->weapon] -= altAmmoUsage[ps->weapon];
+ //RPG-X | Phenix | 27/02/2005
+ //altfired = qtrue;
+ }
+ altfired = qtrue;
+ }
+ else
+ {
+ // check for out of ammo
+ if ( ! ps->ammo[ ps->weapon ] )
+ {
+ if ( ps->weapon == WP_PHASER ) // phaser out of ammo is special case
+ {
+ ps->ammo[ps->weapon] = 0;
+ }
+ //else //TiM - No ammo no more... this is lagging us up
+ //{
+ // PM_AddEvent( EV_NOAMMO );
+ // ps->weaponTime += 500;
+ // return;
+ //}
+ }
+ else
+ {
+ // main fire (always uses 1 ammo)
+ //ps->ammo[ps->weapon]--;
+ //RPG-X | Phenix | 27/02/2005
+ }
+ }
+ }
+
+ // *don't* start the animation if out of ammo
+ /*if ( ps->weapon == WP_PHASER || //RPG-X - TiM: Making the default pose anim better
+ ps->weapon == WP_DISRUPTOR )
+ {
+ PM_StartTorsoAnim( TORSO_ATTACK2 );
+ }
+ else
+ {
+ PM_StartTorsoAnim( BOTH_ATTACK1 );
+ }*/
+
+ if ( ps->weapon != WP_TOOLKIT && ps->weapon != WP_COFFEE && ps->weapon != WP_NULL_HAND )
+ {
+ //Little hack. I like the idle poses for these when it crouches :)
+ if ( ( ( ps->weapon == WP_PHASER )
+ || ( ps->weapon == WP_COMPRESSION_RIFLE )
+ || ( ps->weapon == WP_DISRUPTOR )
+ || ( ps->weapon == WP_TR116 ) )
+ && ( ps->pm_flags & PMF_DUCKED /*&& !pm->xyspeed*/ ) )
+ {
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_CROUCH, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ /*BWEEE*/
+ //PM_ContinueTorsoAnim( PM_GetAnim( ANIM_ATTACK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ }
+ else
+ {
+ /*if ( !PM_HoldingLoopableWeapon() ) {
+ PM_ForceTorsoAnim( PM_GetAnim( ANIM_FIRE, qtrue ), qtrue );
+ }
+ else {*/
+ //PM_ContinueLegsAnim( PM_GetAnim( ANIM_ATTACK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qfalse );
+ PM_ContinueTorsoAnim( PM_GetAnim( ANIM_ATTACK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse );
+ //}
+ }
+ /*else
+ PM_StartTorsoAnim( PM_GetAnim( "fire", qtrue ) );*/
+
+ //Put in this scope, so holding down the trigger on these 'no-anim' weapons won't lock
+ //other animations.
+ //Bots were locked in the jumping anim >.<
+
+ ps->weaponstate = WEAPON_FIRING;
+ }
+ else
+ {
+ ps->weaponstate = WEAPON_READY;
+
+ }
+
+ // fire weapon
+ if ( pm->cmd.buttons & BUTTON_ALT_ATTACK )
+ {
+ if (altfired) // it's either a legally altfirable non-phaser, or it's a phaser with ammo left
+ {
+ PM_AddEvent( EV_ALT_FIRE ); // fixme, because I'm deducting ammo earlier, the last alt-fire shot turns into a main fire
+ }
+ else
+ {
+ PM_AddEvent( EV_FIRE_EMPTY_PHASER );
+ }
+ switch( ps->weapon ) {
+ default:
+ case WP_PHASER:
+ addTime = 100;
+ //If the phaser has been fired, delay the next recharge time
+ ps->rechargeTime = PHASER_RECHARGE_TIME;
+ break;
+ case WP_DERMAL_REGEN:
+ addTime = 0; //500
+ break;
+ case WP_GRENADE_LAUNCHER:
+ addTime = 600;//RPG-X: RedTechie use to be 700
+ break;
+ case WP_DISRUPTOR:
+ addTime = DISRUPTOR_DELAY;
+ break;
+ case WP_COFFEE:
+ addTime = 0; //700
+ break;
+ case WP_QUANTUM_BURST:
+ addTime = ALT_PHOTON_DELAY;
+ break;
+ case WP_NULL_HAND:
+ addTime = 460; //700
+ break;
+ case WP_COMPRESSION_RIFLE:
+ addTime = 100;
+ break;
+ case WP_TR116:
+ addTime = 500; //RPG-X: RedTechie - Use to be 1200
+ break;
+ case WP_VOYAGER_HYPO:
+ //RPG-X: RedTechie - Admins get faster alt fire for steam effects
+ if(/*ps->persistant[PERS_CLASS] == PC_ADMIN*/ pm->admin){
+ addTime = 80;
+ }else{
+ addTime = 1000;
+ }
+ break;
+ case WP_TOOLKIT:
+ addTime = 2000;
+ break;
+ case WP_MEDKIT:
+ addTime = 0; //1000
+ break;
+ case WP_TRICORDER:
+ if(pm->admin /*ps->persistant[PERS_CLASS] == PC_ADMIN*/){
+ addTime = ALT_TRICORDER_DELAY;
+ }else{
+ addTime = 0;
+ }
+ break;
+ case WP_PADD:
+ addTime = 0; //500
+ break;
+ case WP_HYPERSPANNER:
+ addTime = 0; //1000
+ break;
+/* case WP_TR116:
+ addTime = 500; //RPG-X: RedTechie - Use to be 1200
+ break;*/
+ }
+ }
+ else
+ {
+ if (ps->ammo[ps->weapon])
+ {
+ PM_AddEvent( EV_FIRE_WEAPON );
+ }
+ else
+ {
+ PM_AddEvent( EV_FIRE_EMPTY_PHASER );
+ }
+ switch( ps->weapon ) {
+ default:
+ case WP_PHASER:
+ addTime = 100;
+ //If the phaser has been fired, delay the next recharge time
+ ps->rechargeTime = PHASER_RECHARGE_TIME;
+ break;
+ case WP_DERMAL_REGEN:
+ addTime = 1000; //1000
+ break;
+ case WP_GRENADE_LAUNCHER:
+ addTime = 460;//RPG-X: RedTechie use to be 700
+ break;
+ case WP_NULL_HAND:
+ addTime = 460;
+ break;
+ case WP_DISRUPTOR:
+ addTime = 100;
+ break;
+ case WP_COFFEE:
+ addTime = 0; //100
+ break;
+ case WP_QUANTUM_BURST:
+ addTime = PHOTON_DELAY;
+ break;
+ case WP_COMPRESSION_RIFLE:
+ addTime = RIFLE_DELAY;
+ break;
+ case WP_TR116:
+ addTime = TR116_DELAY; //RPG-X: RedTechie - Use to be 1200
+ break;
+ case WP_VOYAGER_HYPO:
+ addTime = 1000;
+ break;
+ case WP_TOOLKIT:
+ addTime = 2000; //1000
+ break;
+ case WP_MEDKIT:
+ addTime = 0; //1000
+ break;
+ case WP_TRICORDER:
+ addTime = ALT_TRICORDER_DELAY; //1000
+ break;
+ case WP_PADD:
+ addTime = 0; //500
+ break;
+ case WP_HYPERSPANNER:
+ addTime = 0; //1000
+ break;
+/* case WP_TR116:
+ addTime = 500; //RPG-X: RedTechie - Use to be 1200
+ break;*/
+ }
+ }
+
+ if ( ps->powerups[PW_HASTE] ) {
+ addTime /= 1.3;
+ }
+
+ ps->weaponTime += addTime;
+}
+
+/*
+================
+PM_Animate
+================
+*/
+static void PM_Animate( void ) {
+ playerState_t *ps = pm->ps;
+
+ if ( pm->cmd.buttons & BUTTON_GESTURE ) {
+ if (ps->pm_type < PM_DEAD ) {
+ //if ( ps->torsoTimer == 0 ) {
+ //PM_StartTorsoAnim( BOTH_CONSOLE1 );
+ //PM_StartLegsAnim( BOTH_CONSOLE1 );
+ //ps->introTime = ps->torsoTimer = 4000; //TIMER_GESTURE //legsTimer
+ //PM_AddEvent( EV_TAUNT );
+
+ if ( !( ps->eFlags & EF_TALKING ) ) {
+ ps->eFlags |= EF_TALKING;
+ }
+ }
+ } else {
+ if ( ( ps->eFlags & EF_TALKING ) ) {
+ ps->eFlags &= ~EF_TALKING;
+ }
+ }
+}
+
+
+/*
+================
+PM_DropTimers
+================
+*/
+static void PM_DropTimers( void ) {
+ int newFlags;
+ playerState_t *ps = pm->ps;
+
+ // drop misc timing counter
+ if ( ps->pm_time ) {
+ if ( pml.msec >= ps->pm_time )
+ {
+ ps->pm_flags &= ~PMF_ALL_TIMES;
+ ps->pm_time = 0;
+ }
+ else
+ {
+ ps->pm_time -= pml.msec;
+ }
+ }
+
+ //Count down the legs anim timer
+ if ( ps->stats[LEGSTIMER] > 0 ) { //legsTimer
+ ps->stats[LEGSTIMER] -= pml.msec;
+ if ( ps->stats[LEGSTIMER] < 0 )
+ {
+ ps->stats[LEGSTIMER] = 0;
+ }
+ }
+
+ //if legs anim timer hit 0
+ if ( ps->stats[LEGSTIMER] == 0 ) {
+ if ( (ps->stats[EMOTES] & EMOTE_LOWER ) &&
+ !(ps->stats[EMOTES] & EMOTE_LOOP_LOWER ) )
+ {
+ ps->stats[EMOTES] &= ~EMOTE_MASK_LOWER;
+
+ if ( ps->legsAnim > 0 )
+ {
+ PM_ForceLegsAnim( bg_emoteList[ps->legsAnim].enumName );
+
+ //TiM - Remove any data about torsos here. it's not necessary and it invalidates the check below
+ newFlags = ( bg_emoteList[ps->legsAnim].animFlags | bg_emoteList[ps->legsAnim].bodyFlags );
+ newFlags &= ~EMOTE_MASK_UPPER;
+ ps->stats[EMOTES] |= newFlags;
+ //Com_Printf( S_COLOR_RED "Set anim to %i\n", ps->legsAnim );
+
+ ps->legsTimer = ps->legsAnim;
+ //ps->torsoTimer = emoteList[ps->torsoAnim].hitBoxHeight;
+ }
+ else
+ {
+ ps->legsTimer = 0;
+ ps->legsAnim = 0;
+ }
+ //Com_Printf(S_COLOR_RED "Acknowledge Lower change!!\n");
+ }
+ }
+
+ if ( ps->stats[TORSOTIMER] > 0 ) { //torsoTimer
+ ps->stats[TORSOTIMER] -= pml.msec;
+ if ( ps->stats[TORSOTIMER] < 0 )
+ {
+ ps->stats[TORSOTIMER] = 0;
+
+ /*if (ps->eFlags & EF_EMOTING ) {
+ ps->eFlags &= ~EF_EMOTING;
+ }*/
+ }
+ }
+
+ if ( ps->stats[TORSOTIMER] == 0 ) {
+ if ( (ps->stats[EMOTES] & EMOTE_UPPER) &&
+ !(ps->stats[EMOTES] & EMOTE_LOOP_UPPER ) )
+ {
+ ps->stats[EMOTES] &= ~EMOTE_MASK_UPPER;
+
+ if ( ps->torsoAnim > 0 )
+ {
+ PM_ForceTorsoAnim( bg_emoteList[ps->torsoAnim].enumName, qtrue );
+
+ //TiM - Remove any data about legs here. it's not necessary and it invalidates any subsequent checks
+ newFlags = ( bg_emoteList[ps->torsoAnim].animFlags | bg_emoteList[ps->torsoAnim].bodyFlags );
+ newFlags &= ~EMOTE_MASK_LOWER;
+ ps->stats[EMOTES] |= newFlags;
+ //ps->stats[EMOTES] |= ( emoteList[ps->torsoAnim].animFlags | emoteList[ps->torsoAnim].bodyFlags );
+
+ }
+ else
+ {
+ ps->torsoTimer = 0;
+ ps->torsoAnim = 0;
+ }
+ }
+ }
+
+ // drop animation counter
+/* if ( ps->introTime > 0 ) { //legsTimer
+ ps->introTime -= pml.msec;
+ if ( ps->introTime < 0 ) {
+ ps->introTime = 0;
+
+ if (ps->eFlags & EF_EMOTING ) {
+ ps->eFlags &= ~EF_EMOTING;
+ }
+ }
+ }
+
+ if ( ps->torsoTimer > 0 ) {
+ ps->torsoTimer -= pml.msec;
+ if ( ps->torsoTimer < 0 ) {
+ ps->torsoTimer = 0;
+ }
+ }*/
+}
+
+/*
+================
+PM_UpdateViewAngles
+================
+*/
+/**
+* This can be used as another entry point when only the viewangles
+* are being updated isntead of a full move
+*/
+void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) {
+ short temp;
+ int i;
+
+ if ( ps->pm_type == PM_INTERMISSION ) {
+ return; // no view changes at all
+ }
+
+ if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 1 ) { //RPG-X: RedTechie - This use to be 0 but in rpg-x with or without medics revive 1 health means you die!
+ return; // no view changes at all
+ }
+
+
+ //TiM - Bookmark
+ //With a flag here, a change to the client side player rotation code there,
+ //we could actually make it EVA suit users could rotate fully in all directions
+ //when in space. :)
+
+ //Com_Printf( "Before: %f %f %f\n", ps->viewangles[0], ps->viewangles[1], ps->viewangles[2]);
+
+ // circularly clamp the angles with deltas
+ for (i=0 ; i<3 ; i++) {
+ temp = cmd->angles[i] + ps->delta_angles[i];
+ if ( i == PITCH && !pm->ps->powerups[PW_FLIGHT] && !(( pm->ps->powerups[PW_EVOSUIT] ) && ( pm->ps->gravity == 0 )) ) {
+ // don't let the player look up or down more than 90 degrees
+ if ( temp > 16000 ) {
+ ps->delta_angles[i] = 16000 - cmd->angles[i];
+ temp = 16000;
+ } else if ( temp < -16000 ) {
+ ps->delta_angles[i] = -16000 - cmd->angles[i];
+ temp = -16000;
+ }
+ }
+
+ ps->viewangles[i] = SHORT2ANGLE(temp);
+ }
+}
+
+
+/*
+================
+PmoveSingle
+
+================
+*/
+void PmoveSingle (pmove_t *pmove) {
+ playerState_t *ps = pmove->ps;
+
+ pm = pmove;
+
+
+ // this counter lets us debug movement problems with a journal
+ // by setting a conditional breakpoint fot the previous frame
+ c_pmove++;
+
+ // clear results
+ pm->numtouch = 0;
+ pm->watertype = 0;
+ pm->waterlevel = 0;
+
+ //if(ps->legsTimer > 0 ) {
+ // Com_Printf("%i, %i\n", ps->legsTimer, ps->torsoTimer);
+ //}
+
+ if ( ps->stats[STAT_HEALTH] <= 0 ) {
+ pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies
+ }
+
+ // make sure walking button is clear if they are running, to avoid
+ // proxy no-footsteps cheats
+ if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) {
+ pm->cmd.buttons &= ~BUTTON_WALKING;
+ }
+
+ // set the talk balloon flag
+ if ( pm->cmd.buttons & BUTTON_TALK ) {
+ ps->eFlags |= EF_TALK;
+ } else {
+ ps->eFlags &= ~EF_TALK;
+ }
+
+ // set the firing flag for continuous beam weapons
+ if ( !(ps->pm_flags & PMF_RESPAWNED) &&
+ ps->pm_type != PM_INTERMISSION &&
+ ( (pm->cmd.buttons & BUTTON_ATTACK) || (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) &&
+ (ps->ammo[ ps->weapon ] || ps->weapon == WP_PHASER))
+ {
+ if (((ps->weapon == WP_PHASER) && (!ps->ammo[ ps->weapon ])) || (!(pm->cmd.buttons & BUTTON_ALT_ATTACK)))
+ {
+ ps->eFlags &= ~EF_ALT_FIRING;
+ }
+ else // if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) <-- implied
+ {
+ ps->eFlags |= EF_ALT_FIRING;
+ }
+
+ /*if ( ps->weapon == WP_DISRUPTOR )
+ {//tech can't use alt attack
+ pm->cmd.buttons &=~BUTTON_ALT_ATTACK;
+ pm->cmd.buttons |= BUTTON_ATTACK;
+ }*/
+ //TiM - good riddance to bad coding
+
+ // This flag should always get set, even when alt-firing
+ ps->eFlags |= EF_FIRING;
+ }
+ else
+ {
+ ps->eFlags &= ~EF_FIRING;
+ ps->eFlags &= ~EF_ALT_FIRING;
+ }
+
+ // clear the respawned flag if attack and use are cleared
+ if ( ps->stats[STAT_HEALTH] > 0 &&
+ !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) {
+ ps->pm_flags &= ~PMF_RESPAWNED;
+ }
+
+ // if talk button is down, dissallow all other input
+ // this is to prevent any possible intercept proxy from
+ // adding fake talk balloons
+ if ( pmove->cmd.buttons & BUTTON_TALK )
+ {
+ pmove->cmd.buttons = 0;
+ pmove->cmd.forwardmove = 0;
+ pmove->cmd.rightmove = 0;
+ if (pmove->cmd.upmove)
+ {
+ if (pmove->cmd.upmove > 0)
+ {
+ pmove->cmd.upmove = 1;
+ }
+ else
+ {
+ pmove->cmd.upmove = -1;//allow a tiny bit to keep the duck anim
+ }
+ }
+ }
+
+ //ph34r teh cheezy hack
+ //Disable the ability to jump when getting up animating.
+ //This is for the purporse of the medics revive animation
+ if ( ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) == BOTH_GET_UP1 ) {
+ pm->cmd.upmove = 0;
+ }
+
+ //If the legs are playing a non infinite loop, disable the movement keys.
+ //Else the player slides on their feet :S
+ if ( ps->stats[EMOTES] & EMOTE_LOWER &&( ps->stats[EMOTES] & EMOTE_CLAMP_BODY ) || ( ps->stats[EMOTES] & EMOTE_CLAMP_ALL ) ) { //EMOTE_LOWER
+ pmove->cmd.forwardmove = 0;
+ pmove->cmd.rightmove = 0;
+ //pmove->cmd.upmove = Q_fabs( pmove->cmd.upmove );
+ }
+
+ if ( ps->stats[EMOTES] & ( EMOTE_LOWER | EMOTE_UPPER ) ) {
+ if (pmove->cmd.buttons & MASK_KILL_EMOTES || pmove->cmd.upmove != 0 ) {
+ ps->stats[EMOTES] &= ~EMOTE_MASK_BOTH;
+
+ ps->legsTimer = 0;
+ ps->stats[LEGSTIMER] = 0;
+ }
+ }
+
+ //TiM: Injured system
+ //Disable the ability to toggle walking,
+ //and slow player down. :)
+ if ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH ) {
+ pm->cmd.buttons &= ~BUTTON_WALKING;
+ }
+
+ //TiM : Override for looping animation emotes
+ //If player touches move keys, cancel emotes
+ /*if ( pmove->cmd.upmove || pmove->cmd.rightmove || pmove->cmd.forwardmove || ( pmove->cmd.buttons & MASK_KILL_EMOTES ) ) {
+ //if ( ( ps->pm_flags & ANIM_LOWER_LOOPING ) || ( ps->pm_flags & ANIM_UPPER_LOOPING ) ) {
+ if ( ps->stats[EMOTES] & EMOTE_LOWER || ps->stats[EMOTES] & EMOTE_UPPER ) {
+ ps->stats[EMOTES] &= ~EMOTE_MASK_BOTH;
+
+ ps->legsTimer = -1;
+ //ps->torsoTimer = 0;
+ }
+ }*/
+
+ // clear all pmove local vars
+ memset (&pml, 0, sizeof(pml));
+
+ // determine the time
+ pml.msec = pmove->cmd.serverTime - ps->commandTime;
+ if ( pml.msec < 1 ) {
+ pml.msec = 1;
+ } else if ( pml.msec > 200 ) {
+ pml.msec = 200;
+ }
+ ps->commandTime = pmove->cmd.serverTime;
+
+ // save old org in case we get stuck
+ VectorCopy (ps->origin, pml.previous_origin);
+
+ // save old velocity for crashlanding
+ VectorCopy (ps->velocity, pml.previous_velocity);
+
+ pml.frametime = pml.msec * 0.001;
+
+ if (ps->pm_type == PM_FREEZE /*|| ps->introTime > pm->cmd.serverTime*/ )
+ {
+ PM_FreezeMove();
+ return; // no movement at all
+ }
+
+ // update the viewangles
+ PM_UpdateViewAngles( ps, &pm->cmd );
+
+ AngleVectors (ps->viewangles, pml.forward, pml.right, pml.up);
+
+ if ( pm->cmd.upmove < 10 ) {
+ // not holding jump
+ ps->pm_flags &= ~PMF_JUMP_HELD;
+ }
+
+ // decide if backpedaling animations should be used
+ if ( pm->cmd.forwardmove < 0 ) {
+ ps->pm_flags |= PMF_BACKWARDS_RUN;
+ } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) {
+ ps->pm_flags &= ~PMF_BACKWARDS_RUN;
+ }
+
+ if ( ps->pm_type >= PM_DEAD ) {
+ pm->cmd.forwardmove = 0;
+ pm->cmd.rightmove = 0;
+ pm->cmd.upmove = 0;
+ }
+
+
+ if ( ps->pm_type == PM_SPECTATOR ) {
+ PM_CheckDuck ();
+ PM_FlyMove ();
+ PM_DropTimers ();
+ return;
+ }
+
+
+ if ( ps->pm_type == PM_SPECTATOR )
+ {
+ //spectator or an eliminated player
+
+ //RPG-X J2J - NoClip Spectation:
+ //Change to allow noclip in spectator if enabled on server
+ //And if the client wants to noclip
+ /*if(rpg_noclipspectating.integer == 1 && ClientNCSpec == qtrue)
+ {
+ PM_NoclipMove();
+ PM_DropTimers ();
+ return;
+ }*/
+ //Else allow flight.
+ //else
+ {
+ PM_CheckDuck ();
+ PM_FlyMove();
+ PM_DropTimers ();
+ return;
+ }
+ ////////////////////////////////
+ }
+
+ if ( ps->pm_type == PM_NOCLIP ) {
+ PM_NoclipMove ();
+ PM_DropTimers ();
+ return;
+ }
+
+ if ( ps->pm_type == PM_INTERMISSION ) {
+ return; // no movement at all
+ }
+
+ // set watertype, and waterlevel
+ PM_SetWaterLevel();
+ if ( !(pm->watertype & CONTENTS_LADDER) )
+ {//Don't want to remember this for ladders, is only for waterlevel change events (sounds)
+ pml.previous_waterlevel = pmove->waterlevel;
+ }
+
+ // set mins, maxs, and viewheight
+ PM_CheckDuck ();
+
+ // set groundentity
+ PM_GroundTrace();
+
+ if ( ps->pm_type == PM_DEAD ) {
+ PM_DeadMove ();
+ }
+
+ if ( ps->powerups[PW_FLIGHT] ) {
+ // flight powerup doesn't allow jump and has different friction
+ PM_FlyMove();
+ } else if ((( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 ))) {
+ //RPG-X | Phenix | 8/8/2004
+ //The player is in 0 G and is wearing an evo suit...
+ PM_FlyMove();
+ } else if (ps->pm_flags & PMF_TIME_WATERJUMP) {
+ PM_WaterJumpMove();
+ } else if ( pm->waterlevel > 1 ) {
+ // swimming
+ PM_WaterMove();
+ } else if ( pml.walking ) {
+ // walking on ground
+ PM_WalkMove();
+ } else {
+ // airborne
+ PM_AirMove();
+ }
+
+ PM_Animate();
+
+ // set groundentity, watertype, and waterlevel
+ PM_GroundTrace();
+ PM_SetWaterLevel();
+
+ // weapons
+ PM_Weapon();
+
+ PM_Use();
+
+ // torso animation
+ PM_TorsoAnimation();
+
+ // footstep events / legs animations
+ PM_Footsteps();
+
+ // entering / leaving water splashes
+ PM_WaterEvents();
+
+ //PM_DoEmote();
+
+ PM_DropTimers();
+
+ // snap some parts of playerstate to save network bandwidth
+ SnapVector( ps->velocity );
+}
+
+
+/*
+================
+Pmove
+================
+*/
+/**
+* Can be called by either the server or the client
+*/
+void Pmove (pmove_t *pmove) {
+ int finalTime;
+
+ finalTime = pmove->cmd.serverTime;
+
+ if ( finalTime < pmove->ps->commandTime ) {
+ return; // should not happen
+ }
+
+ if ( finalTime > pmove->ps->commandTime + 1000 ) {
+ pmove->ps->commandTime = finalTime - 1000;
+ }
+
+ // chop the move up if it is too long, to prevent framerate
+ // dependent behavior
+ while ( pmove->ps->commandTime != finalTime ) {
+ int msec;
+
+ msec = finalTime - pmove->ps->commandTime;
+
+ if ( msec > 66 ) {
+ msec = 66;
+ }
+ pmove->cmd.serverTime = pmove->ps->commandTime + msec;
+ PmoveSingle( pmove );
+ }
+
+}
diff --git a/game/bg_public.h b/game/bg_public.h
new file mode 100644
index 0000000..1c03175
--- /dev/null
+++ b/game/bg_public.h
@@ -0,0 +1,1169 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+// bg_public.h -- definitions shared by both the server game and client game modules
+
+// are we compiling for rpgxEF?
+#define XTRA 1
+// meh somehow preprocessor G_LUA won't work for me
+#define G_LUA 1
+#define CG_LUA 1
+
+// because games can change separately from the main system version, we need a
+// second version that must match between game and cgame
+#define RPGX_VERSION "2.2 Beta 8.4.6"
+#define RPGX_COMPILEDATE "20/05/11"
+#define RPGX_COMPILEDBY "GSIO01"
+//const char GAME_VERSION[] = strcat("RPG-X v",RPGX_VERSION);
+//ehem why not:
+#define GAME_VERSION "RPG-X v" RPGX_VERSION
+
+#define INJURED_MODE_HEALTH 20 //! MAX_CONFIGSTRINGS
+#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS
+#endif
+
+typedef enum {
+ GT_FFA, //!< free for all
+ GT_TOURNAMENT, //!< one on one tournament
+ GT_SINGLE_PLAYER, //!< single player tournament
+
+ //-- team games go after this --
+
+ GT_TEAM, //!< team deathmatch
+ GT_CTF, //!< capture the flag
+
+ GT_MAX_GAME_TYPE
+} gametype_t;
+
+typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t;
+
+//TiM - Global variables for the player weight/height system
+#define BASE_HEIGHT 185.0
+#define BASE_WEIGHT 90.0
+
+#define FEMALE_OFFSET 0.73
+
+#define MAX_HEIGHT 1.15
+#define MIN_HEIGHT 0.9
+
+#define MAX_WEIGHT 1.1
+#define MIN_WEIGHT 0.9
+
+#define HEIGHT_UNIT "cm"
+#define WEIGHT_UNIT "kg"
+
+/*
+===================================================================================
+
+PMOVE MODULE
+
+The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t
+and some other output data. Used for local prediction on the client game and true
+movement on the server game.
+===================================================================================
+*/
+
+//RPG-X - Player Model System. Anim on or off states
+/*typedef enum {
+ ANIM_ONLADDER,
+ ANIM_OFFLADDER,
+ ANIM_DIDFLY
+} animtype_t;*/
+
+/** \enum pmtype_t
+*
+*/
+typedef enum {
+ PM_NORMAL, //!< can accelerate and turn
+ PM_NOCLIP, //!< noclip movement
+ PM_SPECTATOR, //!< still run into walls
+ PM_DEAD, //!< no acceleration or turning, but free falling
+ PM_FREEZE, //!< stuck in place with no control
+ PM_INTERMISSION //!< no movement or status bar
+} pmtype_t;
+
+/** \enum weaponstate_t
+*
+*/
+typedef enum {
+ WEAPON_READY,
+ WEAPON_RAISING,
+ WEAPON_DROPPING,
+ WEAPON_FIRING
+} weaponstate_t;
+
+// pmove->pm_flags
+
+#define PMF_DUCKED 1 //1 //2
+#define PMF_JUMP_HELD 2 //2 //4
+#define PMF_BACKWARDS_JUMP 8 //!< go into backwards land
+#define PMF_BACKWARDS_RUN 16 //!< coast down to backwards run
+#define PMF_TIME_LAND 32 //!< pm_time is time before rejump
+#define PMF_TIME_KNOCKBACK 64 //!< pm_time is an air-accelerate only time
+#define PMF_TIME_WATERJUMP 256 //!< pm_time is waterjump
+#define PMF_RESPAWNED 512 //!< clear after attack and jump buttons come up
+#define PMF_USE_ITEM_HELD 1024
+//#define PMF_GRAPPLE_PULL 2048 //!< pull towards grapple location
+#define PMF_FOLLOW 4096 //!< spectate following another player
+#define PMF_SCOREBOARD 8192 //!< spectate as a scoreboard
+
+#define ANIM_ONLADDER 16384
+#define ANIM_DIDFLY 32768
+#define ANIM_ALERT 128 //TiM: How was this missed!?
+#define ANIM_ALERT2 2048 //TiM: This makes it full I think
+//#define ANIM_LOWER_LOOPING 2048
+
+#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK)
+
+#define MAXTOUCH 32
+
+/**
+* \struct pmove_t
+*/
+typedef struct {
+ // state (in / out)
+ playerState_t *ps;
+
+ // command (in)
+ usercmd_t cmd;
+ int tracemask; //!< collide against these types of surfaces
+ int debugLevel; //!< if set, diagnostic output will be printed
+ qboolean noFootsteps; //!< if the game is setup for no footsteps by the server
+ qboolean pModDisintegration; //!< true if the Disintegration playerMod is on
+
+ // results (out)
+ int numtouch;
+ int touchents[MAXTOUCH];
+
+ int useEvent;
+
+ vec3_t mins, maxs; //!< bounding box size
+
+ int watertype;
+ int waterlevel;
+
+ float xyspeed;
+ float xyzspeed; //TiM : in case body is falling as well
+
+ qboolean admin;
+ qboolean medic;
+ qboolean borg;
+
+ //RPG-X - J2J - Adding a last fire timer for weapon animations
+ ///int LastFireTime;
+
+ // callbacks to test the world
+ // these will be different functions during game and cgame
+ void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask );
+ int (*pointcontents)( const vec3_t point, int passEntityNum );
+
+} pmove_t;
+
+// if a full pmove isn't done on the client, you can just update the angles
+void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd );
+void Pmove (pmove_t *pmove);
+
+//RPG-X: J2J - Emote Vars (shared for server and client)
+//int CurrentEmote[MAX_CLIENTS];
+//QVM HACK!
+
+//===================================================================================
+
+
+// player_state->stats[] indexes
+//
+// maximum of MAX_STATS...currently 16
+//TiM: Ooohhh! Usable space!
+/** \enum statIndex_t
+* Each of these array cells can store data up to 2^16 bits of data!
+* This is good in the fact that we are capable of using this place to store data
+* that is larger for its previous cells, for example, animation data.
+*/
+typedef enum {
+ STAT_HEALTH,
+ STAT_HOLDABLE_ITEM,
+ STAT_WEAPONS, //!< 16 bit fields
+ STAT_ARMOR,
+ STAT_DEAD_YAW, //!< look this direction when dead (FIXME: get rid of?)
+ STAT_CLIENTS_READY, //!< bit mask of clients wishing to exit the intermission (FIXME: configstring?)
+ STAT_MAX_HEALTH, //!< health / armor limit, changable by handicap
+ STAT_USEABLE_PLACED, //!< have we placed the detpack yet?
+
+ //TiM : Placeholder for emotes data and anim holding
+ TORSOTIMER, //!