Initial Prey Game integration support.

This commit is contained in:
Justin Marshall
2026-05-05 15:01:01 -07:00
parent a6c6f1ed26
commit 8d5f678b25
552 changed files with 265551 additions and 209 deletions
+47
View File
@@ -0,0 +1,47 @@
#ifdef _WIN32
#define _BOOL
#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MFileIO.h>
#include <maya/MLibrary.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MEulerRotation.h>
#include <maya/MObject.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MTime.h>
#include <maya/MAnimControl.h>
#include <maya/MFnGeometryFilter.h>
#include <maya/MFnSet.h>
#include <maya/MSelectionList.h>
#include <maya/MFloatArray.h>
#include <maya/MFnWeightGeometryFilter.h>
#include <maya/MFnSkinCluster.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MFnMesh.h>
#include <maya/MDagPathArray.h>
#include <maya/MItGeometry.h>
#include <maya/MPlugArray.h>
#include <maya/MPlug.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFnAttribute.h>
#include <maya/MFnMatrixData.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MFnTransform.h>
#include <maya/MQuaternion.h>
#include <maya/MFnCamera.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFnEnumAttribute.h>
#undef _BOOL
#endif // _WIN32
+47
View File
@@ -0,0 +1,47 @@
#ifdef _WIN32
#define _BOOL
#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MFileIO.h>
#include <maya/MLibrary.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MEulerRotation.h>
#include <maya/MObject.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MTime.h>
#include <maya/MAnimControl.h>
#include <maya/MFnGeometryFilter.h>
#include <maya/MFnSet.h>
#include <maya/MSelectionList.h>
#include <maya/MFloatArray.h>
#include <maya/MFnWeightGeometryFilter.h>
#include <maya/MFnSkinCluster.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MFnMesh.h>
#include <maya/MDagPathArray.h>
#include <maya/MItGeometry.h>
#include <maya/MPlugArray.h>
#include <maya/MPlug.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFnAttribute.h>
#include <maya/MFnMatrixData.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MFnTransform.h>
#include <maya/MQuaternion.h>
#include <maya/MFnCamera.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFnEnumAttribute.h>
#undef _BOOL
#endif // _WIN32
+434
View File
@@ -0,0 +1,434 @@
#define MAYA_DEFAULT_CAMERA "camera1"
#define ANIM_TX BIT( 0 )
#define ANIM_TY BIT( 1 )
#define ANIM_TZ BIT( 2 )
#define ANIM_QX BIT( 3 )
#define ANIM_QY BIT( 4 )
#define ANIM_QZ BIT( 5 )
typedef enum {
WRITE_MESH,
WRITE_ANIM,
WRITE_CAMERA
} exportType_t;
typedef struct {
idCQuat q;
idVec3 t;
} jointFrame_t;
typedef struct {
idCQuat q;
idVec3 t;
float fov;
} cameraFrame_t;
/*
==============================================================================================
idTokenizer
==============================================================================================
*/
class idTokenizer {
private:
int currentToken;
idStrList tokens;
public:
idTokenizer() { Clear(); };
void Clear( void ) { currentToken = 0; tokens.Clear(); };
int SetTokens( const char *buffer );
const char *NextToken( const char *errorstring = NULL );
bool TokenAvailable( void ) { return currentToken < tokens.Num(); };
int Num( void ) { return tokens.Num(); };
void UnGetToken( void ) { if ( currentToken > 0 ) { currentToken--; } };
const char *GetToken( int index ) { if ( ( index >= 0 ) && ( index < tokens.Num() ) ) { return tokens[ index ]; } else { return NULL; } };
const char *CurrentToken( void ) { return GetToken( currentToken ); };
};
/*
==============================================================================================
idExportOptions
==============================================================================================
*/
class idNamePair {
public:
idStr from;
idStr to;
};
class idAnimGroup {
public:
idStr name;
idStrList joints;
};
class idExportOptions {
private:
idTokenizer tokens;
void Reset( const char *commandline );
public:
idStr commandLine;
idStr src;
idStr dest;
idStr game;
idStr prefix;
float scale;
exportType_t type;
bool ignoreMeshes;
bool clearOrigin;
bool clearOriginAxis;
bool ignoreScale;
int startframe;
int endframe;
int framerate;
float xyzPrecision;
float quatPrecision;
idStr align;
idList<idNamePair> renamejoints;
idList<idNamePair> remapjoints;
idStrList keepjoints;
idStrList skipmeshes;
idStrList keepmeshes;
idList<idAnimGroup *> exportgroups;
idList<idAnimGroup> groups;
float rotate;
float jointThreshold;
int cycleStart;
// HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop)
float boundsExpansion;
// HUMANHEAD END
idExportOptions( const char *commandline, const char *ospath );
bool jointInExportGroup( const char *jointname );
};
/*
==============================================================================
idExportJoint
==============================================================================
*/
class idExportJoint {
public:
idStr name;
idStr realname;
idStr longname;
int index;
int exportNum;
bool keep;
float scale;
float invscale;
MFnDagNode *dagnode;
idHierarchy<idExportJoint> mayaNode;
idHierarchy<idExportJoint> exportNode;
idVec3 t;
idMat3 wm;
idVec3 idt;
idMat3 idwm;
idVec3 bindpos;
idMat3 bindmat;
int animBits;
int firstComponent;
jointFrame_t baseFrame;
int depth;
idExportJoint();
idExportJoint &operator=( const idExportJoint &other );
};
/*
==============================================================================
misc structures
==============================================================================
*/
typedef struct {
idExportJoint *joint;
float jointWeight;
idVec3 offset;
} exportWeight_t;
typedef struct {
idVec3 pos;
idVec2 texCoords;
int startweight;
int numWeights;
} exportVertex_t;
typedef struct {
int indexes[ 3 ];
} exportTriangle_t;
typedef struct {
idVec2 uv[ 3 ];
} exportUV_t;
ID_INLINE int operator==( exportVertex_t a, exportVertex_t b ) {
if ( a.pos != b.pos ) {
return false;
}
if ( ( a.texCoords[ 0 ] != b.texCoords[ 0 ] ) || ( a.texCoords[ 1 ] != b.texCoords[ 1 ] ) ) {
return false;
}
if ( ( a.startweight != b.startweight ) || ( a.numWeights != b.numWeights ) ) {
return false;
}
return true;
}
/*
========================================================================
.MD3 triangle model file format
========================================================================
*/
#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I')
#define MD3_VERSION 15
// limits
#define MD3_MAX_LODS 4
#define MD3_MAX_TRIANGLES 8192 // per surface
#define MD3_MAX_VERTS 4096 // per surface
#define MD3_MAX_SHADERS 256 // per surface
#define MD3_MAX_FRAMES 1024 // per model
#define MD3_MAX_SURFACES 32 // per model
#define MD3_MAX_TAGS 16 // per frame
// vertex scales
#define MD3_XYZ_SCALE (1.0/64)
// surface geometry should not exceed these limits
#define SHADER_MAX_VERTEXES 1000
#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES)
// the maximum size of game reletive pathnames
#define MAX_Q3PATH 64
typedef struct md3Frame_s {
idVec3 bounds[2];
idVec3 localOrigin;
float radius;
char name[16];
} md3Frame_t;
typedef struct md3Tag_s {
char name[MAX_Q3PATH]; // tag name
idVec3 origin;
idVec3 axis[3];
} md3Tag_t;
/*
** md3Surface_t
**
** CHUNK SIZE
** header sizeof( md3Surface_t )
** shaders sizeof( md3Shader_t ) * numShaders
** triangles[0] sizeof( md3Triangle_t ) * numTriangles
** st sizeof( md3St_t ) * numVerts
** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames
*/
typedef struct {
int ident; //
char name[MAX_Q3PATH]; // polyset name
int flags;
int numFrames; // all surfaces in a model should have the same
int numShaders; // all surfaces in a model should have the same
int numVerts;
int numTriangles;
int ofsTriangles;
int ofsShaders; // offset from start of md3Surface_t
int ofsSt; // texture coords are common for all frames
int ofsXyzNormals; // numVerts * numFrames
int ofsEnd; // next surface follows
} md3Surface_t;
typedef struct {
char name[MAX_Q3PATH];
int shaderIndex; // for in-game use
} md3Shader_t;
typedef struct {
int indexes[3];
} md3Triangle_t;
typedef struct {
float st[2];
} md3St_t;
typedef struct {
short xyz[3];
short normal;
} md3XyzNormal_t;
typedef struct {
int ident;
int version;
char name[MAX_Q3PATH]; // model name
int flags;
int numFrames;
int numTags;
int numSurfaces;
int numSkins;
int ofsFrames; // offset for first frame
int ofsTags; // numFrames * numTags
int ofsSurfaces; // first surface, others follow
int ofsEnd; // end of file
} md3Header_t;
/*
==============================================================================
idExportMesh
==============================================================================
*/
class idExportMesh {
public:
idStr name;
idStr shader;
bool keep;
idList<exportVertex_t> verts;
idList<exportTriangle_t> tris;
idList<exportWeight_t> weights;
idList<exportUV_t> uv;
idExportMesh() { keep = true; };
void ShareVerts( void );
void GetBounds( idBounds &bounds ) const;
void Merge( idExportMesh *mesh );
};
/*
==============================================================================
idExportModel
==============================================================================
*/
class idExportModel {
public:
idExportJoint *exportOrigin;
idList<idExportJoint> joints;
idHierarchy<idExportJoint> mayaHead;
idHierarchy<idExportJoint> exportHead;
idList<int> cameraCuts;
idList<cameraFrame_t> camera;
idList<idBounds> bounds;
idList<jointFrame_t> jointFrames;
idList<jointFrame_t *> frames;
int frameRate;
int numFrames;
int skipjoints;
int export_joints;
idList<idExportMesh *> meshes;
idExportModel();
~idExportModel();
idExportJoint *FindJointReal( const char *name );
idExportJoint *FindJoint( const char *name );
bool WriteMesh( const char *filename, idExportOptions &options );
bool WriteAnim( const char *filename, idExportOptions &options );
bool WriteCamera( const char *filename, idExportOptions &options );
};
/*
==============================================================================
Maya
==============================================================================
*/
class idMayaExport {
private:
idExportModel model;
idExportOptions &options;
void FreeDagNodes( void );
float TimeForFrame( int num ) const;
int GetMayaFrameNum( int num ) const;
void SetFrame( int num );
void GetBindPose( MObject &jointNode, idExportJoint *joint, float scale );
void GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat );
void GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale );
void CreateJoints( float scale );
void PruneJoints( idStrList &keepjoints, idStr &prefix );
void RenameJoints( idList<idNamePair> &renamejoints, idStr &prefix );
bool RemapParents( idList<idNamePair> &remapjoints );
MObject FindShader( MObject& setNode );
void GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode );
idExportMesh *CopyMesh( MFnSkinCluster &skinCluster, float scale );
void CreateMesh( float scale );
void CombineMeshes( void );
void GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe );
const char *GetObjectType( MObject object );
float GetCameraFov( idExportJoint *joint );
void GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam );
void CreateCameraAnim( idMat3 &align );
void GetDefaultPose( idMat3 &align );
void CreateAnimation( idMat3 &align );
public:
idMayaExport( idExportOptions &exportOptions ) : options( exportOptions ) { };
~idMayaExport();
void ConvertModel( void );
void ConvertToMD3( void );
};
+47
View File
@@ -0,0 +1,47 @@
#ifdef _WIN32
#define _BOOL
#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MFileIO.h>
#include <maya/MLibrary.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MEulerRotation.h>
#include <maya/MObject.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MTime.h>
#include <maya/MAnimControl.h>
#include <maya\MFnGeometryFilter.h>
#include <maya\MFnSet.h>
#include <maya\MSelectionList.h>
#include <maya\MFloatArray.h>
#include <maya\MFnWeightGeometryFilter.h>
#include <maya\MFnSkinCluster.h>
#include <maya\MItDependencyNodes.h>
#include <maya\MFnMesh.h>
#include <maya\MDagPathArray.h>
#include <maya\MItGeometry.h>
#include <maya\MPlugArray.h>
#include <maya\MPlug.h>
#include <maya\MFloatPointArray.h>
#include <maya\MFnAttribute.h>
#include <maya\MFnMatrixData.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MFnTransform.h>
#include <maya/MQuaternion.h>
#include <maya/MFnCamera.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFnEnumAttribute.h>
#undef _BOOL
#endif // _WIN32
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
#ifndef __MAYA_MAIN_H__
#define __MAYA_MAIN_H__
/*
==============================================================
Maya Import
==============================================================
*/
typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys );
typedef const char *( *exporterInterface_t )( const char *ospath, const char *commandline );
typedef void ( *exporterShutdown_t )( void );
#endif /* !__MAYA_MAIN_H__ */
+4
View File
@@ -0,0 +1,4 @@
EXPORTS
dllEntry
Maya_ConvertModel
Maya_Shutdown
+268
View File
@@ -0,0 +1,268 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
/*
=================
hhNavigator::hhNavigator()
=================
*/
hhNavigator::hhNavigator(void) {
hhSelf = NULL;
// followStateFunction = FollowAllyStay;
} //. hhNavigator::hhNavigator(void)
/*
========================
hhNavigator::Spawn(void)
========================
*/
// Required for CLASS_DECLARATION
void hhNavigator::Spawn(void) {
} //. hhNavigator::hhNavigator(void)
/*
=======================================
hhNavigator::SetOwner(idAI *owner)
=======================================
*/
void hhNavigator::SetOwner(idAI *owner) {
idNavigator::SetOwner(owner);
if (owner->IsType(hhAI::Type)) {
hhSelf = static_cast<hhAI *>(owner);
}
} //. hhNavigator::SetOwner(idAI *)
/* JRM- Removed because brain stuff handles this logic now
//
//===============================
//hhNavigator::SetAlly(idActor *)
//===============================
//
void hhNavigator::SetAlly(idActor *ally) {
if (ally != NULL) {
FollowAlly(ally);
}
} //. hhNavigator::SetAlly(idActor *)
//
//=============================
//hhNavigator::FollowAlly(ally)
//=============================
//
void hhNavigator::FollowAlly(idActor *ally) {
// If we aren't on an hhAI, we can't follow, so leave
if (hhSelf == NULL) {
return;
}
(this->*followStateFunction)(ally);
} //. hhNavigator::FollowAlly(idActor *ally)
*/
/* HUMANHEAD JRM - REMOVED
//
//===================================
//hhNavigator::FollowAllyStay(void)
//===================================
//
void hhNavigator::FollowAllyStay(idActor *ally) {
static bool first = true;
if (first) {
if (ai_debug->integer > 0) {
gameLocal.Printf("in FollowAllyStay\n");
}
first = false;
}
if (hhSelf->AI_ALLY_FAR ||
!hhSelf->AI_ALLY_VISIBLE) {
followStateFunction = FollowAllyFollow;
first = true;
}
else if (hhSelf->AI_ALLY_TOUCHED) {
followStateFunction = FollowAllyLead;
first = true;
}
else { // Just stay still
moveType = MOVE_NONE;
}
} //. hhNavigator::FollowAllyStay(void)
//
//===================================
//hhNavigator::FollowAllyFollow(void)
//===================================
//
void hhNavigator::FollowAllyFollow(idActor *ally) {
static bool first = true;
hhSelf->AI_FOLLOW_ALLY = true;
if (first) {
if (ai_debug->integer > 0) {
gameLocal.Printf("in FollowAllyFollow\n");
}
first = false;
}
if (hhSelf->AI_ALLY_NEAR) {
StopMove();
followStateFunction = FollowAllyStay;
hhSelf->AI_FOLLOW_ALLY = false;
first = true;
}
// In case the min_dist is too close
else if (hhSelf->AI_ALLY_TOUCHED) {
followStateFunction = FollowAllyLead;
hhSelf->AI_FOLLOW_ALLY = false;
first = true;
}
else {
moveDest = ally->GetFloorPos();
goal = ally;
moveType = MOVE_TO_ALLY;
if (aas) {
toAreaNum = aas->PointReachableAreaNum( moveDest );
toAreaEnemy = false;
}
} //. Follow the player
} //. hhNavigator::FollowAllyFollow(void)
//
//===================================
//hhNavigator::FollowAllyLead(void)
//===================================
//
void hhNavigator::FollowAllyLead(idActor *ally) {
static bool first = true;
static int nextUpdateTime;
idVec3 leadPosition;
hhSelf->AI_LEAD_ALLY = true;
if (first) {
if (ai_debug->integer > 0) {
gameLocal.Printf("in FollowAllyLead\n");
}
first = false;
}
// Reset this variable, as it was used to get here.
if (hhSelf->AI_ALLY_TOUCHED) {
hhSelf->AI_ALLY_TOUCHED = false;
nextUpdateTime = 0;
}
if (gameLocal.time * 1000.0f >= nextUpdateTime) {
//if (nextUpdateTime == 0) {
leadPosition = FindNewLeadPosition(ally);
if (leadPosition != vec3_origin) {
moveDest = leadPosition;
moveType = MOVE_TO_ALLY;
goal = NULL;
if (aas) {
toAreaNum = aas->PointReachableAreaNum(moveDest);
toAreaEnemy = false;
}
nextUpdateTime = gameLocal.time * 1000.0f +
hhSelf->follow_lead_update_time;
}
} //. Time to update position
if (!hhSelf->AI_ALLY_NEAR) {
followStateFunction = FollowAllyStay;
hhSelf->AI_LEAD_ALLY = false;
first = true;
}
} //. hhNavigator::FollowAllyLead(void)
//
//================================
//hhNavigator::FindNewLeadPosition
//================================
//
idVec3 hhNavigator::FindNewLeadPosition(idActor *ally) {
idVec3 direction;
aasTrace_t trace;
direction = hhSelf->GetFloorPos() - ally->GetFloorPos();
direction.Normalize();
direction *= hhSelf->follow_min_dist;
if (aas) {
idVec3 bestPos;
bestPos = aas->FindNearestPoint(ally->GetFloorPos(),
hhSelf->GetFloorPos(),
hhSelf->follow_min_dist);
if (bestPos != vec3_origin) {
return(bestPos);
}
// Didn't find a good point
// Just move as far as we can in the direction nudged
aas->Trace(trace, hhSelf->GetPhysics()->GetOrigin(),
hhSelf->GetPhysics()->GetOrigin() + direction);
//? How does this work on slopes?
//! Do we want to use the floor position?
return(ally->GetFloorPos() + direction * trace.fraction);
}
return(ally->GetFloorPos() + direction);
} //. hhNavigator::FindNewLeadPosition(idActor *)
*/
+41
View File
@@ -0,0 +1,41 @@
#ifndef __PREY_AI_NAVIGATOR_H__
#define __PREY_AI_NAVIGATOR_H__
// Forward declaration for hhAI
class hhAI;
class hhNavigator : public idNavigator {
public:
hhNavigator(void);
void Spawn(void);
//virtual void SetAlly(idActor *ally); JRM removed
virtual void SetOwner(idAI *owner);
virtual boolean IsNearDest( void );
protected:
hhAI * hhSelf;
//virtual void FollowAlly(idActor *ally); JRM removed
/* JRM removed
void FollowAllyStay(idActor *ally);
void FollowAllyFollow(idActor *ally);
void FollowAllyLead(idActor *ally);
*/
// void (hhNavigator::* followStateFunction) (idActor *ally); JRM removed
//idVec3 FindNewLeadPosition(idActor *ally); JRM removed
};
#endif /* __PREY_AI_NAVIGATOR_H__ */
+529
View File
@@ -0,0 +1,529 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef AI_CenturionLaunch("centurionFire", "d"); //called from anims to handle firing for centurion
const idEventDef AI_CenturionRoar("centurionRoar"); //called from level-script to cause centurion to roar
const idEventDef AI_CenturionArmChop("centurionArmChop"); //called from level-script to actually cause centurion to loose arm
const idEventDef AI_CenturionLooseArm("centurionLooseArm"); //internal: used to actually 'loose' arm
const idEventDef AI_CenturionForceFieldNotify("preForcefieldNotify");
const idEventDef AI_CenturionForceFieldToggle("forcefieldToggle", "d");
const idEventDef AI_CenturionInTunnel("playerInBox", "dE");
const idEventDef AI_CenturionReachedTunnel("reachedTunnel");
const idEventDef AI_CenturionMoveToTunnel("moveToTunnel", NULL, 'd');
const idEventDef AI_CheckForObstructions("checkForObstructions", "d", 'd');
const idEventDef AI_DestroyObstruction("destroyObstruction");
const idEventDef AI_MoveToObstruction("moveToObstruction");
const idEventDef AI_ReachedObstruction("reachedObstruction");
const idEventDef AI_CloseToObstruction("closeToObstruction", 0, 'd');
const idEventDef AI_BackhandImpulse("<backhandImpulse>", "e");
const idEventDef AI_EnemyCloseToObstruction("enemyCloseToObstruction", NULL, 'd');
const idEventDef AI_TakeDamage("takeDamage", "d");
const idEventDef AI_FindNearbyEnemy("findNearbyEnemy", "f", 'e');
CLASS_DECLARATION( hhMonsterAI, hhCenturion )
EVENT( AI_CenturionLaunch, hhCenturion::Event_CenturionLaunch )
EVENT( AI_CenturionRoar, hhCenturion::Event_ScriptedRoar )
EVENT( AI_CenturionArmChop, hhCenturion::Event_ScriptedArmChop )
EVENT( AI_CenturionLooseArm, hhCenturion::Event_CenturionLooseArm )
EVENT( AI_CenturionInTunnel, hhCenturion::Event_PlayerInTunnel )
EVENT( AI_CenturionReachedTunnel, hhCenturion::Event_ReachedTunnel )
EVENT( AI_CenturionMoveToTunnel, hhCenturion::Event_MoveToTunnel )
EVENT( AI_CenturionForceFieldNotify, hhCenturion::Event_ForceFieldNotify )
EVENT( AI_CenturionForceFieldToggle, hhCenturion::Event_ForceFieldToggle )
EVENT( AI_CheckForObstructions, hhCenturion::Event_CheckForObstruction )
EVENT( AI_MoveToObstruction, hhCenturion::Event_MoveToObstruction )
EVENT( AI_DestroyObstruction, hhCenturion::Event_DestroyObstruction )
EVENT( AI_ReachedObstruction, hhCenturion::Event_ReachedObstruction )
EVENT( AI_CloseToObstruction, hhCenturion::Event_CloseToObstruction )
EVENT( AI_BackhandImpulse, hhCenturion::Event_BackhandImpulse )
EVENT( AI_EnemyCloseToObstruction, hhCenturion::Event_EnemyCloseToObstruction )
EVENT( AI_TakeDamage, hhCenturion::Event_TakeDamage )
EVENT( AI_FindNearbyEnemy, hhCenturion::Event_FindNearbyEnemy )
END_CLASS
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void hhCenturion::Spawn() {
AI_CENTURION_ARM_MISSING = false;
AI_CENTURION_REQUIRE_ROAR = false;
AI_CENTURION_ARM_TUNNEL = false;
AI_CENTURION_SCRIPTED_ROAR = 0;
AI_CENTURION_FORCEFIELD_WAIT = false;
AI_CENTURION_SCRIPTED_TUNNEL = 0;
}
void hhCenturion::Event_PostSpawn( void ) {
hhMonsterAI::Event_PostSpawn();
const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" );
idEntityPtr<idEntity> ent;
while ( kv ) {
ent = gameLocal.FindEntity( kv->GetValue() );
if ( ent.IsValid() ) {
obstacles.AddUnique( ent );
}
kv = spawnArgs.MatchPrefix( "aasObstacle", kv );
}
}
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
void hhCenturion::LinkScriptVariables( void ) {
hhMonsterAI::LinkScriptVariables();
LinkScriptVariable(AI_CENTURION_ARM_MISSING);
LinkScriptVariable(AI_CENTURION_REQUIRE_ROAR);
LinkScriptVariable(AI_CENTURION_ARM_TUNNEL);
LinkScriptVariable(AI_CENTURION_SCRIPTED_ROAR);
LinkScriptVariable(AI_CENTURION_SCRIPTED_TUNNEL);
LinkScriptVariable(AI_CENTURION_FORCEFIELD_WAIT);
}
void hhCenturion::Event_ForceFieldNotify() {
if( !armchop_Target.GetEntity() ) {
return; //must not be in arm-chop mode, so we don't care yet
}
AI_CENTURION_REQUIRE_ROAR = true;
AI_CENTURION_FORCEFIELD_WAIT = true;
}
void hhCenturion::Event_ForceFieldToggle( int toggle ) {
if( !armchop_Target.GetEntity() ) {
return;
}
AI_CENTURION_FORCEFIELD_WAIT = false;
if (!toggle) {
AI_CENTURION_REQUIRE_ROAR = false;
} else if( AI_CENTURION_ARM_TUNNEL ) {
Event_ScriptedArmChop();
return;
}
}
void hhCenturion::Event_PlayerInTunnel( int toggle, idEntity* ent ) {
if( AI_CENTURION_ARM_MISSING ) { //don't care if the player goes back in tunnel once we already lost our arm
return;
}
if( toggle ) {
if( !ent ) {
gameLocal.DWarning( "entity needs to be present for tunnel event" );
return;
}
armchop_Target = ent;
AI_CENTURION_SCRIPTED_TUNNEL = 1;
}
else {
armchop_Target = NULL;
AI_CENTURION_SCRIPTED_TUNNEL = 0;
}
}
void hhCenturion::Event_MoveToTunnel() {
if (!armchop_Target.IsValid()) {
idThread::ReturnInt(0);
return;
}
StopMove(MOVE_STATUS_DONE);
MoveToPosition(armchop_Target->GetOrigin());
idThread::ReturnInt(1);
}
void hhCenturion::Event_ReachedTunnel() {
if( armchop_Target.IsValid() ) {
idAngles faceAngles = armchop_Target->GetAxis()[0].ToAngles();
ideal_yaw = faceAngles.yaw;
current_yaw = faceAngles.yaw;
SetAxis( armchop_Target->GetAxis() );
}
}
void hhCenturion::Event_ScriptedRoar() {
if ( AI_CENTURION_SCRIPTED_ROAR > 0 ) {
gameLocal.Warning( "centurionRoar() called more than once!\n");
} else {
AI_CENTURION_SCRIPTED_ROAR = 1;
}
}
void hhCenturion::Event_ScriptedArmChop() {
const function_t* newstate = NULL;
newstate = GetScriptFunction( "state_ScriptedArmChop" );
if( newstate ) {
SetState( newstate );
}
else {
gameLocal.Warning( "Unable to find 'state_ScriptedArmChop' on centurion" );
}
}
void hhCenturion::Event_CenturionLooseArm() {
idDict args;
idEntity* ent = NULL;
idVec3 jointLoc;
idMat3 jointAxis;
animator.GetJointTransform( spawnArgs.GetString("arm_severjoint", ""), gameLocal.time, jointLoc, jointAxis );
jointLoc = renderEntity.origin + jointLoc * renderEntity.axis;
args.SetVector( "origin", jointLoc );
args.SetMatrix( "rotation", renderEntity.axis );
args.SetBool( "spin", 0 );
args.SetFloat( "triggersize", 48.f );
args.SetBool( "enablePickup", true );
args.SetFloat( "respawn", 0.f );
ent = gameLocal.SpawnObject( spawnArgs.GetString("def_arm_weaponclass", ""), &args );
SetSkinByName( spawnArgs.GetString( "skin_arm_gone" ) );
hhFxInfo fx;
fx.SetEntity( this );
fx.RemoveWhenDone( true );
SpawnFxLocal( spawnArgs.GetString( "fx_armchop" ), jointLoc, mat3_identity, &fx );
//drop any stuck arrows
idEntity *next;
for( ent = teamChain; ent != NULL; ent = next ) {
next = ent->GetTeamChain();
if ( ent && ent->IsType( hhProjectile::Type ) ) {
ent->Unbind();
ent->Hide();
ent->PostEventSec( &EV_Remove, 5 );
next = teamChain;
}
}
AI_CENTURION_ARM_MISSING = true;
}
void hhCenturion::Event_CenturionLaunch( const idList<idStr>* parmList ) {
const idDict* projDef = NULL;
const idSoundShader* soundShader = NULL;
// parms: joint, projectileDef, sound, fx
if( !parmList || parmList->Num() != 5 ) {
gameLocal.Warning( "Incorrect paramater number" );
return;
}
//Rbarrel_A projectile_centurion_autocannon snd_fire fx_muzzleFlash
const char* jointName = (*parmList)[ 0 ].c_str();
const char* projectileDefName = (*parmList)[ 1 ].c_str();
const char* soundName = (*parmList)[ 2 ].c_str();
const char* fxName = (*parmList)[ 3 ].c_str();
int autoAim = atoi( (*parmList)[ 4 ].c_str() );
if( AI_CENTURION_ARM_MISSING ) { //skip firing if joint is on the severed arm
if( !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointA", "")) || !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointB", "")) ) {
return;
}
}
projDef = gameLocal.FindEntityDefDict( projectileDefName, false );
HH_ASSERT( !shootTarget.IsValid() );
// If autoAim is true and we're not blending, and we're facing the enemy, auto-aim
if ( ( autoAim == 1 || ( autoAim == 2 && gameLocal.random.RandomInt(100) < 50 ) ) && torsoAnim.animBlendFrames == 0 && FacingEnemy( 5.0f ) ) {
AimedAttackMissile( jointName, projDef );
} else { // Otherwise do a normal missile attack
Event_AttackMissile( jointName, projDef, 1 );
}
if(idStr::Cmpn( soundName, "snd_", 4)) {
soundShader = declManager->FindSound( soundName );
if( soundShader->GetState() == DS_DEFAULTED ) {
gameLocal.Warning( "Sound '%s' not found", soundName );
}
StartSoundShader( soundShader, SND_CHANNEL_WEAPON, 0, false, NULL );
}
else {
if( !StartSound(soundName, SND_CHANNEL_WEAPON, 0, false, NULL) ) {
gameLocal.Warning( "Framecommand 'centurionFire' on entity '%s' could not find sound '%s'", GetName(), soundName );
}
}
BroadcastFxInfoAlongBone( spawnArgs.GetString(fxName), jointName );
}
void hhCenturion::Event_DestroyObstruction() {
if( pillarEntity.IsValid() ) {
pillarEntity->PostEventMS( &EV_Activate, 0.f, this );
}
}
void hhCenturion::Event_CheckForObstruction( int checkPathToPillar ) {
bool obstacle = false;
predictedPath_t path;
if( enemy.IsValid() ) {
idVec3 end = enemy->GetPhysics()->GetOrigin();
if ( !checkPathToPillar ) {
trace_t tr;
idVec3 toPos, eye = GetEyePosition();
if ( enemy->IsType( idActor::Type ) ) {
toPos = ( ( idActor * )enemy.GetEntity() )->GetEyePosition();
} else {
toPos = enemy->GetPhysics()->GetOrigin();
}
gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this );
idEntity *traceEnt = gameLocal.GetTraceEntity( tr );
if ( traceEnt && ( tr.fraction < 1.0f || traceEnt != enemy.GetEntity() ) ) {
//check to see if the other object is an pillar...
if( traceEnt->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) {
pillarEntity = traceEnt;
obstacle = true;
}
}
} else if ( pillarEntity.IsValid() ) {
idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), enemy->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
if( path.endEvent != 0 && path.blockingEntity ) {
//check to see if the other object is an pillar...
if( path.blockingEntity->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) {
//check to see if we can path to the obstacle clearly
pillarEntity = path.blockingEntity;
//idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), pillarEntity->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA), path );
//if( path.endEvent != 0 && path.blockingEntity == pillarEntity.GetEntity() ) {
obstacle = true;
//}
}
}
}
}
idThread::ReturnInt( (int)obstacle );
}
void hhCenturion::Event_CloseToObstruction() {
if( physicsObj.GetAbsBounds().IntersectsBounds(pillarEntity->GetPhysics()->GetAbsBounds()) ) {
idThread::ReturnInt( 1 );
return;
}
idThread::ReturnInt( 0 );
}
void hhCenturion::Event_ReachedObstruction() {
FaceEntity( pillarEntity.GetEntity() );
}
void hhCenturion::Event_MoveToObstruction() {
idVec3 temp;
if ( aas ) {
int toAreaNum = PointReachableAreaNum( pillarEntity.GetEntity()->GetOrigin() );
temp = pillarEntity.GetEntity()->GetOrigin();
aas->PushPointIntoAreaNum( toAreaNum, temp );
MoveToPosition( temp );
} else {
gameLocal.Warning( "Centurion has no aas for MoveToObstruction\n" );
}
}
void hhCenturion::Think() {
PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
if (ai_skipThink.GetBool()) {
return;
}
hhMonsterAI::Think();
if(ai_debugBrain.GetInteger() > 0 && state) {
if ( enemy.IsValid() && enemy->GetHealth() > 0 ) {
float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast();
gameRenderWorld->DrawText( va("%f", dist), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
}
gameRenderWorld->DrawText(state->Name(), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
gameRenderWorld->DrawText(torsoAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
gameRenderWorld->DrawText(legsAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
}
}
bool hhCenturion::AttackMelee( const char *meleeDefName ) {
const idDict *meleeDef;
idActor *enemyEnt = enemy.GetEntity();
const char *p;
const idSoundShader *shader;
meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
if ( !meleeDef ) {
gameLocal.Error( "Unknown melee '%s'", meleeDefName );
}
if ( !enemyEnt ) {
p = meleeDef->GetString( "snd_miss" );
if ( p && *p ) {
shader = declManager->FindSound( p );
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
}
return false;
}
// make sure the trace can actually hit the enemy
if ( !TestMelee() ) {
// missed
p = meleeDef->GetString( "snd_miss" );
if ( p && *p ) {
shader = declManager->FindSound( p );
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
}
return false;
}
//
// do the damage
//
p = meleeDef->GetString( "snd_hit" );
if ( p && *p ) {
shader = declManager->FindSound( p );
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
}
idVec3 kickDir;
meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
idVec3 globalKickDir;
globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
if ( enemyEnt->IsActiveAF() && !enemyEnt->IsType( idPlayer::Type ) ) {
PostEventMS( &AI_BackhandImpulse, 0, enemyEnt );
}
lastAttackTime = gameLocal.time;
return true;
}
void hhCenturion::Event_BackhandImpulse( idEntity* ent ) {
const idDict *meleeDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_impulse_damage"), false );
if ( !meleeDef || !ent ) {
return;
}
idVec3 kickDir;
meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
idVec3 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
globalKickDir *= ent->spawnArgs.GetFloat( "kick_scale", "1.0" );
ent->ApplyImpulse( this, 0, ent->GetOrigin(), meleeDef->GetFloat( "push_ragdoll" ) * globalKickDir );
}
/*
=====================
hhCenturion::Save
=====================
*/
void hhCenturion::Save( idSaveGame *savefile ) const {
armchop_Target.Save( savefile );
pillarEntity.Save( savefile );
}
/*
=====================
hhCenturion::Restore
=====================
*/
void hhCenturion::Restore( idRestoreGame *savefile ) {
armchop_Target.Restore( savefile );
pillarEntity.Restore( savefile );
// Restore the obstacle list
const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" );
idEntityPtr<idEntity> ent;
while ( kv ) {
ent = gameLocal.FindEntity( kv->GetValue() );
if ( ent.IsValid() ) {
obstacles.AddUnique( ent );
}
kv = spawnArgs.MatchPrefix( "aasObstacle", kv );
}
}
void hhCenturion::Event_EnemyCloseToObstruction(void) {
bool close = false;
if ( pillarEntity.IsValid() && enemy.IsValid() ) {
float obs_dist = ( pillarEntity->GetOrigin() - GetOrigin() ).Length();
float dist = ( enemy->GetOrigin() - GetOrigin() ).Length();
// If the enemy is further from me than the pillar...
if ( dist > obs_dist) {
dist = ( pillarEntity->GetOrigin() - enemy->GetOrigin() ).Length();
// If the enemy is less than 400 units from the pillar, then he's 'close'
if ( dist < 400.0f ) {
close = true;
}
}
}
idThread::ReturnInt( (int) close );
}
void hhCenturion::AimedAttackMissile( const char *jointname, const idDict *projDef) {
idProjectile *proj;
idVec3 target, origin = GetOrigin();
bool inShuttle = false;
if ( shootTarget.IsValid() ) {
target = shootTarget->GetOrigin();
} else if ( enemy.IsValid() ) {
target = enemy->GetOrigin();
if ( enemy->IsType( idActor::Type ) ) {
target.z += enemy->EyeHeight() / 4.0f;
}
} else {
// No target? Do the default attack
Event_AttackMissile( jointname, projDef, 1 );
return;
}
// If target is too close do a non-aimed attack
if ( fabsf( origin.x - target.x ) < 256 &&
fabsf( origin.y - target.y ) < 256 ) {
Event_AttackMissile( jointname, projDef, 1 );
return;
}
idVec3 dist = origin - target;
if ( shootTarget.IsValid() ) {
proj = LaunchProjectile( jointname, shootTarget.GetEntity(), true, projDef );
} else {
proj = LaunchProjectile( jointname, enemy.GetEntity(), true, projDef );
}
}
void hhCenturion::Event_TakeDamage(int takeDamage) {
// Set the takedamage flag
fl.takedamage = (takeDamage != 0);
}
void hhCenturion::Event_FindNearbyEnemy( float distance ) {
// Search for the monster nearest to us
idAI *nearest = NULL;
float dist, nearDist = idMath::INFINITY;
idAI *ai = reinterpret_cast<idAI *> (gameLocal.FindEntityOfType( idAI::Type, NULL ));
while (ai) {
if (ai != this) { // Don't target yourself
dist = (ai->GetOrigin() - GetOrigin()).Length();
if (dist < nearDist) {
nearDist = dist;
nearest = ai;
}
}
ai = reinterpret_cast<idAI *> (gameLocal.FindEntityOfType( idAI::Type, ai ));
}
// If we found one near us and it's near enough, return it
if (nearest && nearDist < distance) {
idThread::ReturnEntity(nearest);
} else {
idThread::ReturnEntity(NULL);
}
}
void hhCenturion::Event_Touch( idEntity *other, trace_t *trace ) {
if ( (!enemy.GetEntity() || other->IsType( hhPlayer::Type )) && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) {
Activate( other );
SetEnemy( static_cast<idActor *> ( other ) );
}
AI_PUSHED = true;
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+84
View File
@@ -0,0 +1,84 @@
#ifndef __PREY_AI_CENTURION_H__
#define __PREY_AI_CENTURION_H__
/***********************************************************************
hhCenturion.
Centurion AI.
***********************************************************************/
class hhCenturion : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhCenturion);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void Event_CenturionLaunch( const idList<idStr>* parmList ) {};
void Event_ScriptedRoar() {};
void Event_ScriptedArmChop() {};
void Event_CenturionLooseArm() {};
void Event_ForceFieldNotify() {};
void Event_ForceFieldToggle( int toggle ) {};
void Event_PlayerInTunnel( int toggle, idEntity* ent ) {};
void Event_ReachedTunnel() {};
void Event_MoveToTunnel() {};
bool AttackMelee( const char *meleeDefName ) { return true; };
void Event_CheckForObstruction( int checkPathToPillar ) {};
void Event_MoveToObstruction() {};
void Event_DestroyObstruction() {};
void Event_ReachedObstruction() {};
void Event_CloseToObstruction() {};
void Event_EnemyCloseToObstruction() {};
void Event_BackhandImpulse( idEntity* ent ) {};
virtual void Event_PostSpawn( void ) {};
void Event_TakeDamage( int takeDamage ) {};
void Event_FindNearbyEnemy( float distance ) {};
virtual void Event_Touch( idEntity *other, trace_t *trace ) {};
#else
public:
void Spawn();
void Think();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
protected:
void Event_CenturionLaunch( const idList<idStr>* parmList );
void Event_ScriptedRoar();
void Event_ScriptedArmChop();
void Event_CenturionLooseArm();
void Event_ForceFieldNotify();
void Event_ForceFieldToggle( int toggle );
void Event_PlayerInTunnel( int toggle, idEntity* ent );
void Event_ReachedTunnel();
void Event_MoveToTunnel();
void LinkScriptVariables( void );
bool AttackMelee( const char *meleeDefName );
void Event_CheckForObstruction( int checkPathToPillar );
void Event_MoveToObstruction();
void Event_DestroyObstruction();
void Event_ReachedObstruction();
void Event_CloseToObstruction();
void Event_EnemyCloseToObstruction();
void Event_BackhandImpulse( idEntity* ent );
virtual void Event_PostSpawn( void );
void Event_TakeDamage( int takeDamage );
void Event_FindNearbyEnemy( float distance );
virtual void Event_Touch( idEntity *other, trace_t *trace );
void AimedAttackMissile( const char *jointname, const idDict *projDef );
idEntityPtr<idEntity> armchop_Target;
idEntityPtr<idEntity> pillarEntity;
idScriptBool AI_CENTURION_ARM_MISSING;
idScriptBool AI_CENTURION_REQUIRE_ROAR;
idScriptBool AI_CENTURION_ARM_TUNNEL;
idScriptBool AI_CENTURION_FORCEFIELD_WAIT;
idScriptFloat AI_CENTURION_SCRIPTED_ROAR;
idScriptFloat AI_CENTURION_SCRIPTED_TUNNEL;
idList< idEntityPtr<idEntity> > obstacles;
#endif
};
#endif //__PREY_AI_CENTURION_H__
+115
View File
@@ -0,0 +1,115 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
CLASS_DECLARATION( hhMonsterAI, hhCrawler )
EVENT( EV_Touch, hhCrawler::Event_Touch )
END_CLASS
void hhCrawler::Spawn() {
if( spawnArgs.FindKey("def_pickup") ) {
if ( gameLocal.isMultiplayer ) {
GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_MONSTERCLIP );
} else {
GetPhysics()->SetContents( CONTENTS_TRIGGER );
}
}
}
void hhCrawler::Event_Touch( idEntity *other, trace_t *trace ) {
hhPlayer *player;
if( !spawnArgs.FindKey("def_pickup") || !other->IsType(idPlayer::Type) ) {
return;
}
player = static_cast<hhPlayer *>(other);
Pickup( player );
}
void hhCrawler::Pickup( hhPlayer *player ) {
if( !GiveToPlayer( player ) ) {
return;
}
// play pickup sound
StartSoundShader( refSound.shader, SND_CHANNEL_ITEM, false ); // play what's defined in the entity
// trigger our targets
ActivateTargets( player );
// clear our contents so the object isn't picked up twice
GetPhysics()->SetContents( 0 );
// hide the model
Hide();
if (player->hud) {
player->hud->SetStateInt("item", 1);
player->hud->SetStateString("itemtext", spawnArgs.GetString("inv_name"));
player->hud->SetStateString("itemicon", spawnArgs.GetString("inv_icon"));
}
idStr str;
spawnArgs.GetString("inv_name", "Item", str);
if (player == gameLocal.GetClientByNum(gameLocal.localClientNum)) {
gameLocal.Printf("Picked up a %s\n", str.c_str());
}
PostEventMS( &EV_Remove, 2000 );
}
bool hhCrawler::GiveToPlayer( hhPlayer* player ) {
const char *pickupName = spawnArgs.GetString("def_pickup", NULL);
bool pickedUp = false;
if ( player && pickupName ) {
idEntity *ent = gameLocal.SpawnObject(pickupName, NULL);
if (ent->IsType(hhItem::Type)) {
pickedUp = player->GiveItem( static_cast<hhItem*>(ent) );
}
ent->Hide();
ent->PostEventMS(&EV_Remove, 2000);
}
return pickedUp;
}
void hhCrawler::Think( void ) {
PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
if (ai_skipThink.GetBool()) { //HUMANHEAD rww
return;
}
if ( thinkFlags & TH_THINK ) {
current_yaw += deltaViewAngles.yaw;
ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw );
deltaViewAngles.Zero();
viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
// HUMANHEAD NLA
physicsObj.ResetNumTouchEnt(0);
// HUMANHEAD END
// animation based movement
UpdateAIScript();
AnimMove();
} else if ( thinkFlags & TH_PHYSICS ) {
RunPhysics();
}
UpdateAnimation();
Present();
LinkCombat();
}
bool hhCrawler::UpdateAnimationControllers() {
//do nothing
return false;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef __PREY_AI_CRAWLER_H__
#define __PREY_AI_CRAWLER_H__
/***********************************************************************
hhCrawler.
Crawler AI.
***********************************************************************/
class hhCrawler : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhCrawler);
public:
void Spawn();
protected:
void Event_Touch( idEntity *other, trace_t *trace );
void Pickup( hhPlayer *player );
bool GiveToPlayer( hhPlayer* player );
void Think();
bool UpdateAnimationControllers();
};
#endif //__PREY_AI_CRAWLER_H__
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
#ifndef __PREY_AI_CREATURE_H__
#define __PREY_AI_CREATURE_H__
class hhCreatureX : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhCreatureX);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void Event_GunRecharge( int onOff ) {};
void Event_LaserOn() {};
void Event_LaserOff() {};
void Event_UpdateLasers() {};
void Event_SetGunOffset( const idAngles &ang ) {};
void Event_EndLeftBeams() {};
void Event_EndRightBeams() {};
void Event_AssignRightMuzzleFx( hhEntityFx* fx ) {};
void Event_AssignLeftMuzzleFx( hhEntityFx* fx ) {};
void Event_AssignRightImpactFx( hhEntityFx* fx ) {};
void Event_AssignLeftImpactFx( hhEntityFx* fx ) {};
void Event_AssignLeftRechargeFx( hhEntityFx* fx ) {};
void Event_AssignRightRechargeFx( hhEntityFx* fx ) {};
void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) {};
void Event_HudEvent( const char *eventName ) {};
void Event_RechargeHealth() {};
void Event_EndRecharge() {};
void Event_ResetRechargeBeam() {};
void Event_SparkLeft() {};
void Event_SparkRight() {};
void Event_LeftGunDeath() {};
void Event_RightGunDeath() {};
void Event_StartRechargeBeams() {};
#else
~hhCreatureX();
void Spawn();
void LinkScriptVariables( void );
void Think();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
idScriptBool AI_GUN_TRACKING;
idScriptBool AI_RECHARGING;
idScriptBool AI_LEFT_FIRE;
idScriptBool AI_RIGHT_FIRE;
idScriptBool AI_LEFT_DAMAGED;
idScriptBool AI_RIGHT_DAMAGED;
idScriptBool AI_FIRING_LASER;
idScriptBool AI_HEALTH_TICK;
idScriptBool AI_GUN_EXPLODE;
protected:
void Event_GunRecharge( int onOff );
void Event_LaserOn();
void Event_LaserOff();
void Event_UpdateLasers();
void Event_SetGunOffset( const idAngles &ang );
void Event_EndLeftBeams();
void Event_EndRightBeams();
void Event_AssignRightMuzzleFx( hhEntityFx* fx );
void Event_AssignLeftMuzzleFx( hhEntityFx* fx );
void Event_AssignRightImpactFx( hhEntityFx* fx );
void Event_AssignLeftImpactFx( hhEntityFx* fx );
void Event_AssignLeftRechargeFx( hhEntityFx* fx );
void Event_AssignRightRechargeFx( hhEntityFx* fx );
void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir );
void Event_HudEvent( const char *eventName );
void Event_RechargeHealth();
void Event_EndRecharge();
void Event_ResetRechargeBeam();
void Event_SparkLeft();
void Event_SparkRight();
void Event_LeftGunDeath();
void Event_RightGunDeath();
void Event_StartRechargeBeams();
void MuzzleLeftOn();
void MuzzleRightOn();
void MuzzleLeftOff();
void MuzzleRightOff();
bool UpdateAnimationControllers( void );
virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
void Show();
void Activate( idEntity *activator );
idEntityPtr<hhBeamSystem> laserRight;
idEntityPtr<hhBeamSystem> laserLeft;
idEntityPtr<hhBeamSystem> preLaserRight;
idEntityPtr<hhBeamSystem> preLaserLeft;
idList< idEntityPtr<hhBeamSystem> > leftBeamList;
idList< idEntityPtr<hhBeamSystem> > rightBeamList;
idEntityPtr<hhBeamSystem> leftRechargeBeam;
idEntityPtr<hhBeamSystem> rightRechargeBeam;
idEntityPtr<hhBeamSystem> leftDamageBeam;
idEntityPtr<hhBeamSystem> rightDamageBeam;
idEntityPtr<hhBeamSystem> leftRetractBeam;
idEntityPtr<hhBeamSystem> rightRetractBeam;
idEntityPtr<hhEntityFx> muzzleLeftFx;
idEntityPtr<hhEntityFx> muzzleRightFx;
idEntityPtr<hhEntityFx> impactLeftFx;
idEntityPtr<hhEntityFx> impactRightFx;
idEntityPtr<hhEntityFx> rechargeLeftFx;
idEntityPtr<hhEntityFx> rechargeRightFx;
idEntityPtr<hhEntityFx> retractLeftFx;
idEntityPtr<hhEntityFx> retractRightFx;
int numBurstBeams;
bool bLaserLeftActive;
bool bLaserRightActive;
idVec3 targetStart_L;
idVec3 targetEnd_L;
idVec3 targetCurrent_L;
idVec3 targetStart_R;
idVec3 targetEnd_R;
idVec3 targetCurrent_R;
float targetAlpha_L;
float targetAlpha_R;
int leftGunHealth;
int rightGunHealth;
int leftGunLives;
int rightGunLives;
idAngles gunShake;
int nextBeamTime;
int nextLeftZapTime;
int nextRightZapTime;
idVec3 laserEndLeft;
idVec3 laserEndRight;
int nextLaserLeft;
int nextLaserRight;
int nextHealthTick;
int leftRechargerHealth;
int rightRechargerHealth;
idEntityPtr<hhMonsterAI> leftRecharger;
idEntityPtr<hhMonsterAI> rightRecharger;
bool bScripted;
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif /* __PREY_AI_CREATURE_H__ */
+660
View File
@@ -0,0 +1,660 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_StartBurst("startBurst");
const idEventDef EV_EndBurst("endBurst");
const idEventDef EV_StartStaticBeam("startStaticBeam");
const idEventDef EV_EndStaticBeam("endStaticBeam");
const idEventDef EV_StartChargeShot("startChargeShot");
const idEventDef EV_EndChargeShot("endChargeShot");
const idEventDef EV_StartPathing("startPathing");
const idEventDef EV_EndPathing("endPathing");
const idEventDef EV_EndZipBeams("<endZipBeams>");
const idEventDef EV_HealerReset("<healerReset>");
CLASS_DECLARATION(hhMonsterAI, hhDroid)
EVENT(EV_StartStaticBeam, hhDroid::Event_StartStaticBeam)
EVENT(EV_EndStaticBeam, hhDroid::Event_EndStaticBeam)
EVENT(EV_StartBurst, hhDroid::Event_StartBurst)
EVENT(EV_EndBurst, hhDroid::Event_EndBurst)
EVENT(EV_StartChargeShot, hhDroid::Event_StartChargeShot)
EVENT(EV_EndChargeShot, hhDroid::Event_EndChargeShot)
EVENT(EV_StartPathing, hhDroid::Event_StartPathing)
EVENT(EV_EndPathing, hhDroid::Event_EndPathing)
EVENT(EV_EndZipBeams, hhDroid::Event_EndZipBeams)
EVENT(EV_HealerReset, hhDroid::Event_HealerReset)
END_CLASS
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
hhDroid::hhDroid() {
burstLength = 0.0;
burstSpread = 0.0;
burstDuration = 0.0;
staticDuration = 0.0;
staticRange = 0.0;
numBurstBeams = 0;
beamOffset = vec3_zero;
chargeShotSize = 0.0;
old_fly_bob_strength = 0.0;
spinAngle = 0.0f;
}
hhDroid::~hhDroid() {
for ( int i = 0; i < numBurstBeams; i ++ ) {
SAFE_REMOVE( beamBurstList[i] );
}
}
void hhDroid::Show( void ) {
hhMonsterAI::Show();
PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) );
}
void hhDroid::Spawn(void)
{
Event_SetMoveType(MOVETYPE_FLY);
lives = spawnArgs.GetInt( "lives", "3" );
numBurstBeams = spawnArgs.GetInt( "numBurstBeams", "5" );
burstLength = spawnArgs.GetFloat( "burstLength", "250" );
staticDuration = spawnArgs.GetFloat( "staticDuration", "2.0" );
burstDuration = spawnArgs.GetFloat( "burstDuration", "2.0" );
beamOffset = spawnArgs.GetVector( "beamOffset", "0 0 0" );
burstSpread = spawnArgs.GetFloat( "burstSpread", "1.0" );
enemyRange = spawnArgs.GetFloat( "enemy_range", "3000" );
flyDampening = spawnArgs.GetFloat( "fly_dampening", "0.01" );
bHealer = spawnArgs.GetBool( "healer" );
beamZip = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamZip" ) );
if( beamZip.IsValid() ) {
beamZip->Activate( false );
beamZip->SetOrigin( GetPhysics()->GetOrigin() + beamOffset );
beamZip->Bind( this, false );
}
for ( int i = 0; i < numBurstBeams; i ++ ) {
if ( gameLocal.random.RandomFloat() > 0.5f ) {
beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst1" ) ) );
} else {
beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst2" ) ) );
}
if( beamBurstList[i].IsValid() ) {
beamBurstList[i]->Activate( false );
beamBurstList[i]->SetOrigin( GetPhysics()->GetOrigin() + beamOffset );
beamBurstList[i]->Bind( this, false );
}
}
PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) );
}
bool hhDroid::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
bool bPain = hhMonsterAI::Pain( inflictor, attacker, damage, dir, location );
if ( !bPain ) {
return bPain;
}
//activate some beams for damage fx
for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
if ( beamBurstList[i].IsValid() ) {
beamBurstList[i]->Activate( true );
beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) );
PostEventSec(&EV_EndBurst, staticDuration);
}
}
return bPain;
}
void hhDroid::Think() {
PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
if (ai_skipThink.GetBool()) {
return;
}
hhMonsterAI::Think();
//update burst beams
idMat3 dir;
if ( enemy.IsValid() ) {
dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3();
} else {
dir = GetPhysics()->GetAxis();
}
for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
if ( beamBurstList[i].IsValid() && beamBurstList[i]->IsActivated() ) {
beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) );
}
}
if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) {
beamBurstList[0]->SetTargetLocation( staticPoint->GetPhysics()->GetOrigin() );
}
if ( chargeShot.IsValid() ) {
chargeShot->SetShaderParm( 9, 1 );
chargeShot->SetShaderParm( 10, chargeShotSize );
chargeShotSize += spawnArgs.GetFloat( "chargeSizeDelta" );
chargeShot->GetPhysics()->DisableClip();
}
}
void hhDroid::Event_StartChargeShot(void) {
//spawn chargeshot projectile
const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chargeshot") );
if ( projectileDef ) {
chargeShot = hhProjectile::SpawnProjectile( projectileDef );
if ( chargeShot.IsValid() ) {
chargeShotSize = spawnArgs.GetFloat( "chargeSizeStart", "1.0" );
idVec3 launchStart = GetPhysics()->GetOrigin() + spawnArgs.GetVector( "chargeOffset", "15 0 0" ) * GetRenderEntity()->axis;
chargeShot->Create(this, launchStart, GetRenderEntity()->axis);
chargeShot->Launch(chargeShot->GetPhysics()->GetOrigin(), chargeShot->GetPhysics()->GetAxis(), vec3_zero );
chargeShot->Bind(this, true);
}
}
}
void hhDroid::Event_EndChargeShot(void) {
//launch projectile
if ( chargeShot.IsValid() ) {
chargeShot->Unbind();
chargeShot->StartTracking();
chargeShot.Clear();
}
}
void hhDroid::Event_StartBurst(void) {
gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_burstdamage") );
//burst effect
idMat3 dir;
if ( enemy.IsValid() ) {
dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3();
} else {
dir = GetPhysics()->GetAxis();
}
for ( int i = 0; i < beamBurstList.Num(); i++ ) {
if ( beamBurstList[i].IsValid() ) {
beamBurstList[i]->Activate( true );
beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) );
}
}
PostEventSec(&EV_EndBurst, burstDuration);
}
void hhDroid::Event_EndBurst(void) {
for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
if ( beamBurstList[i].IsValid() ) {
beamBurstList[i]->Activate( false );
}
}
}
void hhDroid::Event_EndStaticBeam(void) {
if ( beamBurstList[0].IsValid() ) {
beamBurstList[0]->Activate( false );
}
if ( staticPoint.IsValid() ) {
staticPoint.Clear();
}
//wait random time until next static beam
if ( !IsHidden() && !AI_DEAD ) {
PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) );
}
}
void hhDroid::Event_StartStaticBeam(void) {
idVec3 targetPoint;
idEntity *entityList[ MAX_GENTITIES ];
if ( IsHidden() || AI_DEAD ) {
return;
}
//find nearest staticPoint
float radius = spawnArgs.GetFloat( "staticRange" );
int listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES );
for( int i = 0; i < listedEntities; i++ ) {
if ( entityList[ i ] && entityList[ i ]->spawnArgs.GetInt( "droidBeam", "0" ) ) {
staticPoint = entityList[ i ];
break;
}
}
//activate one beam
if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) {
StartSound( "snd_staticBeam", SND_CHANNEL_BODY, 0, true, NULL );
beamBurstList[0]->Activate( true );
beamBurstList[0]->SetTargetLocation( beamBurstList[0]->GetOrigin() + burstLength * hhUtils::RandomVector() );
staticPoint->ActivateTargets( this );
PostEventSec(&EV_EndStaticBeam, staticDuration);
}
}
void hhDroid::FlyTurn( void ) {
if ( AI_PATHING ) {
hhMonsterAI::FlyTurn();
return;
}
if ( AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY ) {
TurnToward( lastVisibleEnemyPos );
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
} else if ( move.speed > 0.0f ) {
const idVec3 &vel = physicsObj.GetLinearVelocity();
if ( vel.ToVec2().LengthSqr() > 0.1f ) {
TurnToward( vel.ToYaw() );
}
}
Turn();
}
void hhDroid::Event_StartPathing(void) {
AI_PATHING = true;
old_fly_bob_strength = fly_bob_strength;
fly_bob_strength = 0.0;
ignore_obstacles = true;
}
void hhDroid::Event_EndPathing(void) {
AI_PATHING = false;
fly_bob_strength = old_fly_bob_strength;
ignore_obstacles = false;
}
void hhDroid::FlyMove( void ) {
idVec3 goalPos;
idVec3 oldorigin;
idVec3 newDest;
AI_BLOCKED = false;
if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
if ( AI_PATHING ) {
physicsObj.SetLinearVelocity( idVec3(0,0,0) );
}
StopMove( MOVE_STATUS_DONE );
}
if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
idVec3 vel = physicsObj.GetLinearVelocity();
if ( GetMovePos( goalPos ) ) {
move.obstacle = NULL;
}
if ( move.speed ) {
FlySeekGoal( vel, goalPos );
}
// add in bobbing
AddFlyBob( vel );
if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) {
float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast();
if ( dist < enemyRange && enemyRange > 0 ) {
AdjustFlyHeight( vel, goalPos );
}
}
AdjustFlySpeed( vel );
physicsObj.SetLinearVelocity( vel );
}
// turn
FlyTurn();
// run the physics for this frame
oldorigin = physicsObj.GetOrigin();
physicsObj.UseFlyMove( true );
physicsObj.UseVelocityMove( false );
physicsObj.SetDelta( vec3_zero );
physicsObj.ForceDeltaMove( disableGravity );
RunPhysics();
monsterMoveResult_t moveResult = physicsObj.GetMoveResult();
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
DirectDamage( attack, enemy.GetEntity() );
} else {
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
} else if ( moveResult == MM_BLOCKED ) {
move.blockTime = gameLocal.time + 500;
AI_BLOCKED = true;
}
}
idVec3 org = physicsObj.GetOrigin();
if ( oldorigin != org ) {
TouchTriggers();
}
if ( ai_debugMove.GetBool() ) {
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 );
gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec );
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true );
gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true );
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
DrawRoute();
}
}
idProjectile *hhDroid::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) {
//jsh overridden to allow per-projectile accuracy
idVec3 muzzle;
idVec3 dir;
idVec3 start;
trace_t tr;
idBounds projBounds;
float distance;
const idClipModel *projClip;
float attack_accuracy;
float attack_cone;
float projectile_spread;
float diff;
float angle;
float spin;
idAngles ang;
int num_projectiles;
int i;
idMat3 axis;
idVec3 tmp;
idProjectile *lastProjectile;
//HUMANHEAD mdc - added to support multiple projectiles
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
int projIndex = FindProjectileInfo( desiredProjectileDef );
if( projIndex >= 0 ) {
SetCurrentProjectile( projIndex );
}
}
//HUMANHEAD END
if ( !projectileDef ) {
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
return NULL;
}
if ( projectileDef->GetFloat( "attack_accuracy" ) ) {
attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" );
} else {
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
}
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
if ( projectileDef->GetFloat( "projectile_spread" ) ) {
projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" );
} else {
projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
}
if ( projectileDef->GetFloat( "num_projectiles" ) ) {
num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" );
} else {
num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" );
}
GetMuzzle( jointname, muzzle, axis );
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, axis[ 0 ] );
}
lastProjectile = projectile.GetEntity();
if ( target != NULL ) {
tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
tmp.Normalize();
axis = tmp.ToMat3();
} else {
axis = viewAxis;
}
// rotate it because the cone points up by default
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
// make sure the projectile starts inside the monster bounding box
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
projClip = lastProjectile->GetPhysics()->GetClipModel();
projBounds = projClip->GetBounds().Rotate( axis );
// check if the owner bounds is bigger than the projectile bounds
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
start = muzzle + distance * viewAxis[ 0 ];
} else {
start = ownerBounds.GetCenter();
}
} else {
// projectile bounds bigger than the owner bounds, so just start it from the center
start = ownerBounds.GetCenter();
}
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
muzzle = tr.endpos;
// set aiming direction
GetAimDir( muzzle, target, this, dir );
ang = dir.ToAngles();
// adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
if ( !AI_WALLWALK && clampToAttackCone ) {
// clamp the attack direction to be within monster's attack cone so he doesn't do
// things like throw the missile backwards if you're behind him
diff = idMath::AngleDelta( ang.yaw, current_yaw );
if ( diff > attack_cone ) {
ang.yaw = current_yaw + attack_cone;
} else if ( diff < -attack_cone ) {
ang.yaw = current_yaw - attack_cone;
}
}
axis = ang.ToMat3();
float spreadRad = DEG2RAD( projectile_spread );
for( i = 0; i < num_projectiles; i++ ) {
// spread the projectiles out
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir.Normalize();
// launch the projectile
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, dir );
}
lastProjectile = projectile.GetEntity();
lastProjectile->Launch( muzzle, dir, vec3_origin );
projectile = NULL;
}
TriggerWeaponEffects( muzzle, axis );
lastAttackTime = gameLocal.time;
//HUMANHEAD mdc - added to support multiple projectiles
projectile = NULL;
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
//HUMANHEAD END
return lastProjectile;
}
void hhDroid::Event_FlyZip() {
idVec3 old_origin = GetOrigin();
hhMonsterAI::Event_FlyZip();
if ( old_origin != GetOrigin() ) {
if ( beamZip.IsValid() ) {
beamZip->SetTargetLocation( old_origin );
}
if ( beamZip.IsValid() ) {
beamZip->Activate( true );
}
PostEventSec( &EV_EndZipBeams, spawnArgs.GetFloat( "zip_beam_duration", "1.0" ) );
}
}
void hhDroid::Event_EndZipBeams() {
if ( beamZip.IsValid() ) {
beamZip->Activate( false );
}
}
void hhDroid::Event_HealerReset() {
fl.takedamage = true;
}
void hhDroid::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if ( bHealer ) {
lives--;
if ( lives <= 0 ) {
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
} else {
const char *defName = spawnArgs.GetString( "fx_pain" );
if (defName && defName[0]) {
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone( true );
idEntityFx *painFx = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient );
if ( painFx ) {
painFx->Bind( this, true );
}
}
health = spawnArgs.GetInt( "health" );
fl.takedamage = false;
PostEventSec( &EV_HealerReset, spawnArgs.GetFloat( "reset_delay", "0.8" ) );
}
return;
}
if ( AI_DEAD ) {
AI_DAMAGE = true;
return;
}
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
if ( beamBurstList[i].IsValid() ) {
beamBurstList[i]->Activate( true );
beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) );
PostEventSec(&EV_EndBurst, staticDuration);
}
}
}
void hhDroid::AdjustFlySpeed( idVec3 &vel ) {
float speed;
// apply dampening
vel -= vel * flyDampening * MS2SEC( gameLocal.msec );
// gradually speed up/slow down to desired speed
speed = vel.Normalize();
speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec );
if ( speed < 0.0f ) {
speed = 0.0f;
} else if ( move.speed && ( speed > move.speed ) ) {
speed = move.speed;
}
vel *= speed;
}
void hhDroid::UpdateModelTransform( void ) {
idVec3 origin;
idMat3 axis;
if ( GetPhysicsToVisualTransform( origin, axis ) ) {
renderEntity.axis = axis * GetPhysics()->GetAxis();
renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis;
if ( bHealer ) {
renderEntity.axis = idAngles( 0, 0, spinAngle ).ToMat3() * renderEntity.axis;
spinAngle += 10;
if ( spinAngle > 360.0f ) {
spinAngle = 0.0f;
}
}
} else {
renderEntity.axis = GetPhysics()->GetAxis();
renderEntity.origin = GetPhysics()->GetOrigin();
}
}
/*
=====================
hhDroid::Save
=====================
*/
void hhDroid::Save( idSaveGame *savefile ) const {
int num = beamBurstList.Num();
savefile->WriteInt( num );
for ( int i = 0; i < num; i++ ) {
beamBurstList[i].Save( savefile );
}
staticPoint.Save( savefile );
chargeShot.Save( savefile );
beamZip.Save( savefile );
savefile->WriteVec3( savedGravity );
savefile->WriteFloat( chargeShotSize );
savefile->WriteFloat( burstLength );
savefile->WriteFloat( burstSpread );
savefile->WriteFloat( burstDuration );
savefile->WriteFloat( staticDuration );
savefile->WriteFloat( staticRange );
savefile->WriteInt( numBurstBeams );
savefile->WriteVec3( beamOffset );
savefile->WriteFloat( old_fly_bob_strength );
savefile->WriteFloat( enemyRange );
savefile->WriteFloat( flyDampening );
savefile->WriteFloat( spinAngle );
savefile->WriteBool( bHealer );
savefile->WriteInt( lives );
}
/*
=====================
hhDroid::Restore
=====================
*/
void hhDroid::Restore( idRestoreGame *savefile ) {
int num;
savefile->ReadInt( num );
beamBurstList.SetNum( num );
for ( int i = 0; i < num; i++ ) {
beamBurstList[i].Restore( savefile );
}
staticPoint.Restore( savefile );
chargeShot.Restore( savefile );
beamZip.Restore( savefile );
savefile->ReadVec3( savedGravity );
savefile->ReadFloat( chargeShotSize );
savefile->ReadFloat( burstLength );
savefile->ReadFloat( burstSpread );
savefile->ReadFloat( burstDuration );
savefile->ReadFloat( staticDuration );
savefile->ReadFloat( staticRange );
savefile->ReadInt( numBurstBeams );
savefile->ReadVec3( beamOffset );
savefile->ReadFloat( old_fly_bob_strength );
savefile->ReadFloat( enemyRange );
savefile->ReadFloat( flyDampening );
savefile->ReadFloat( spinAngle );
savefile->ReadBool( bHealer );
savefile->ReadInt( lives );
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+73
View File
@@ -0,0 +1,73 @@
#ifndef __PREY_AI_DROID_H__
#define __PREY_AI_DROID_H__
class hhDroid : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhDroid);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void Event_StartBurst(void) {};
void Event_EndBurst(void) {};
void Event_StartStaticBeam(void) {};
void Event_EndStaticBeam(void) {};
void Event_StartChargeShot(void) {};
void Event_EndChargeShot(void) {};
void Event_StartPathing(void) {};
void Event_EndPathing(void) {};
void Event_FlyZip() {};
void Event_EndZipBeams() {};
void Event_HealerReset() {}
#else
hhDroid();
~hhDroid();
void Spawn( void );
virtual void Think( void );
void FlyTurn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void AdjustFlySpeed( idVec3 &vel );
protected:
idList< idEntityPtr<hhBeamSystem> > beamBurstList;
idEntityPtr<hhBeamSystem> beamZip;
idEntityPtr<idEntity> staticPoint;
idEntityPtr<hhProjectileTracking> chargeShot;
void Event_StartBurst(void);
void Event_EndBurst(void);
void Event_StartStaticBeam(void);
void Event_EndStaticBeam(void);
void Event_StartChargeShot(void);
void Event_EndChargeShot(void);
void Event_StartPathing(void);
void Event_EndPathing(void);
void Event_FlyZip();
void Event_EndZipBeams();
void Event_HealerReset();
idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef );
virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
virtual void Show( void );
void FlyMove( void );
void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
void UpdateModelTransform();
idVec3 savedGravity;
float chargeShotSize;
float burstLength;
float burstSpread;
float burstDuration;
float staticDuration; //how long staticbeam lasts
float staticRange; //distance to check for staticbeam points
int numBurstBeams;
idVec3 beamOffset;
float old_fly_bob_strength;
float enemyRange;
float flyDampening;
int lives;
float spinAngle;
bool bHealer;
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif
+801
View File
@@ -0,0 +1,801 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_AcidBlast("acidBlast");
const idEventDef EV_AcidDrip("<acidDrip>");
const idEventDef EV_DeathCloud("<deathCloud>");
const idEventDef EV_LaunchPod("launchPod");
const idEventDef EV_NewPod("<newPod>", "e");
const idEventDef EV_SpawnBlastDebris("<spawnBlastDebris>");
const idEventDef EV_ChargeEnemy("chargeEnemy");
const idEventDef EV_GrabEnemy("grabEnemy", NULL, 'f');
const idEventDef EV_BiteEnemy("biteEnemy");
const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v");
const idEventDef EV_BindUnfroze("<bindUnfroze>", "e");
const idEventDef EV_GrabCheck("grabCheck", "", 'd');
const idEventDef EV_MoveToGrabPosition("moveToGrabPosition");
const idEventDef EV_CheckRange("checkRange", "", 'd');
const idEventDef EV_EnemyRangeZ("enemyRangeZ", "", 'f');
CLASS_DECLARATION(hhMonsterAI, hhGasbagSimple)
EVENT(EV_AcidBlast, hhGasbagSimple::Event_AcidBlast)
EVENT(EV_AcidDrip, hhGasbagSimple::Event_AcidDrip)
EVENT(EV_DeathCloud, hhGasbagSimple::Event_DeathCloud)
EVENT(EV_LaunchPod, hhGasbagSimple::Event_LaunchPod)
EVENT(EV_NewPod, hhGasbagSimple::Event_NewPod)
EVENT(EV_SpawnBlastDebris, hhGasbagSimple::Event_SpawnBlastDebris)
EVENT(EV_ChargeEnemy, hhGasbagSimple::Event_ChargeEnemy)
EVENT(EV_GrabEnemy, hhGasbagSimple::Event_GrabEnemy)
EVENT(EV_BiteEnemy, hhGasbagSimple::Event_BiteEnemy)
EVENT(EV_DirectMoveToPosition, hhGasbagSimple::Event_DirectMoveToPosition)
EVENT(EV_BindUnfroze, hhGasbagSimple::Event_BindUnfroze)
EVENT(EV_GrabCheck, hhGasbagSimple::Event_GrabCheck)
EVENT(EV_MoveToGrabPosition, hhGasbagSimple::Event_MoveToGrabPosition)
EVENT(EV_CheckRange, hhGasbagSimple::Event_CheckRange)
EVENT(EV_EnemyRangeZ, hhGasbagSimple::Event_EnemyRangeZ)
END_CLASS
static const idEventDef EV_Unfreeze( "<unfreeze>" );
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
//
// ~hhGasbagSimple()
//
hhGasbagSimple::~hhGasbagSimple(void) {
// Make sure to unbind the player if we've grabbed him to prevent deleting him
if (enemy.IsValid() && enemy->IsBoundTo(this) && enemy->IsType(hhPlayer::Type)) {
enemy->Unbind();
enemy->PostEventMS(&EV_Unfreeze, 0);
}
}
//
// Spawn()
//
void hhGasbagSimple::Spawn(void) {
Event_SetMoveType(MOVETYPE_FLY); // So we ignore gravity zones from the beginning
podOffset = spawnArgs.GetVector("pod_offset", "0 0 64");
podRange = spawnArgs.GetFloat("podRange", "250");
dripCount = 0;
nextWoundTime = 0;
bindController = static_cast<hhBindController *>( gameLocal.SpawnClientObject(spawnArgs.GetString("def_bindController"), NULL) );
HH_ASSERT( bindController.IsValid() );
bindController->fl.networkSync = false;
float yawLimit = spawnArgs.GetFloat("yawlimit", "180");
const char *handName = spawnArgs.GetString("def_tractorhand");
const char *animName = spawnArgs.GetString("boundanim");
bindController->SetRiderParameters(animName, handName, yawLimit, 0.0f);
bindController->Bind(this, true);
bindController->SetOrigin(spawnArgs.GetVector("grab_offset"));
bindController->fl.neverDormant = true;
}
//
// Ticker()
//
void hhGasbagSimple::Ticker(void) {
for (int i = 0; i < podList.Num(); i++) {
if (!podList[i].IsValid()) {
podList.RemoveIndex(i);
i--; // This index is gone, so don't increment
continue;
}
}
// Update the podcount
AI_PODCOUNT = podList.Num();
if (podList.Num() == 0) {
BecomeInactive(TH_TICKER);
}
}
//
// Save()
//
void hhGasbagSimple::Save(idSaveGame *savefile) const {
savefile->WriteInt(dripCount);
savefile->WriteInt(podList.Num());
for (int i = 0; i < podList.Num(); i++) {
podList[i].Save(savefile);
}
bindController.Save(savefile);
}
//
// Restore()
//
void hhGasbagSimple::Restore(idRestoreGame *savefile) {
Spawn();
savefile->ReadInt(dripCount);
int num;
savefile->ReadInt(num);
podList.SetNum(num);
for (int i = 0; i < num; i++) {
podList[i].Restore(savefile);
}
bindController.Restore(savefile);
nextWoundTime = 0;
}
//
// Event_AcidBlast()
//
void hhGasbagSimple::Event_AcidBlast(void) {
if (health <= 0) { // Don't launch a pod if we're dead
return;
}
if (!enemy.IsValid()) {
return;
}
idEntity *target = enemy.GetEntity();
idVec3 targetOrigin = target->GetOrigin();
float len, nearest = podRange;
// See if we have a pod near the enemy. If we do, target it instead.
idEntity *entList[100];
int num = gameLocal.EntitiesWithinRadius(targetOrigin, podRange, entList, 100);
for (int i = 0; i < num; i++) {
if (!entList[i]->IsType(hhPod::Type)) {
continue;
}
len = (entList[i]->GetOrigin() - targetOrigin).Length();
// Always target the pod nearest to the enemy
if (len < nearest) {
target = entList[i];
nearest = len;
}
}
// Spawn a "muzzle flash"-like fx
hhFxInfo fx;
fx.SetEntity(this);
fx.RemoveWhenDone(true);
SpawnFxLocal(spawnArgs.GetString("fx_fire"), GetOrigin() + idVec3(0, 0, -400), enemy->GetPhysics()->GetGravityNormal().ToMat3(), &fx);
// Spawn an acid blob in the player's general direction
const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_projectile"), false);
LaunchProjectile(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef);
PostEventMS(&EV_SpawnBlastDebris, 100 + gameLocal.random.RandomInt(100));
dripCount = gameLocal.random.RandomInt(3) + 1;
PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Drip acid after firing
}
//
// Event_SpawnBlastDebris()
//
void hhGasbagSimple::Event_SpawnBlastDebris(void) {
idDict args;
args.Clear();
args.Set("origin", (GetPhysics()->GetOrigin() + spawnArgs.GetVector("blast_debris_offset", "0 0 128")).ToString());
gameLocal.SpawnObject("debrisSpawner_gasbag_fire", &args);
}
//
// Event_AcidDrip()
//
void hhGasbagSimple::Event_AcidDrip(void) {
assert(dripCount > 0);
dripCount--;
idVec3 target = GetPhysics()->GetOrigin();
target.z -= 128;
idVec3 rndVec = hhUtils::RandomVector() * gameLocal.random.RandomInt(20);
const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_drip_projectile", "projectile_acidspray_gasbag"), false);
LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target + rndVec, false, projDef);
if (dripCount > 0) {
PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Fire again in 200-300ms
}
}
//
// Event_DeathCloud()
//
void hhGasbagSimple::Event_DeathCloud(void) {
const char *fx = spawnArgs.GetString("fx_death");
if (!fx || !fx[0]) {
return;
}
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone(true);
BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo);
}
//
// Event_LaunchPod()
//
void hhGasbagSimple::Event_LaunchPod() {
if (health <= 0) { // Don't launch a pod if we're dying
return;
}
idVec3 target, origin, offset = vec3_origin;
if (enemy.IsValid()) {
// Target a random area around the enemy
origin = enemy->GetEyePosition();
if (gameLocal.random.RandomInt(100) > 30) { // Possibly target the enemy exactly
offset.x = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f;
offset.y = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f;
}
} else {
// Target a random area beneath us
origin = GetOrigin();
offset.x = gameLocal.random.RandomFloat() * 10.0f;
offset.y = gameLocal.random.RandomFloat() * 10.0f;
offset.z = -10.0f;
}
target = origin + offset;
const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_pod_projectile", "projectile_gasbag_pod"), false);
LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef);
}
//
// Event_NewPod
//
void hhGasbagSimple::Event_NewPod(hhPod *pod) {
HH_ASSERT(pod && pod->IsType(hhPod::Type));
podList.Append(idEntityPtr<hhPod>(pod));
// Update the podcount
AI_PODCOUNT = podList.Num();
BecomeActive(TH_TICKER);
}
//
// Killed
//
void hhGasbagSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
bool wasDead = (AI_DEAD != 0);
hhMonsterAI::Killed(inflictor, attacker, damage, dir, location);
if (wasDead) {
return;
}
CancelEvents(&EV_AcidDrip);
CancelEvents(&EV_AcidBlast);
CancelEvents(&EV_LaunchPod);
dripCount = 0;
// Release any grabbed enemy
idEntity *rider;
if ((rider = bindController->GetRider()) != NULL) {
bindController->Detach();
if (rider->IsType(hhPlayer::Type)) {
rider->ProcessEvent(&EV_Unfreeze);
}
}
int deathAnim = GetAnimator()->GetAnim("death");
GetAnimator()->PlayAnim(ANIMCHANNEL_TORSO, deathAnim, gameLocal.time, 300.0f);
int ttl = GetAnimator()->AnimLength(deathAnim);
// Freeze
physicsObj.EnableGravity(false);
physicsObj.PutToRest();
SetSkinByName( spawnArgs.GetString( "skin_death" ) );
SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 2500, 8000); // starttime, duration
PostEventSec( &EV_StartSound, 1.5f, "snd_acid", SND_CHANNEL_ANY, 1 );
// Spawn gas cloud
PostEventMS(&EV_DeathCloud, ttl);
PostEventMS(&EV_Dispose, 0);
}
//
// LaunchProjectile
//
idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
idVec3 muzzle;
idVec3 dir;
idVec3 start;
trace_t tr;
idBounds projBounds;
float distance;
const idClipModel *projClip;
float attack_accuracy;
float attack_cone;
float projectile_spread;
float diff;
float angle;
float spin;
idAngles ang;
int num_projectiles;
int i;
idMat3 axis;
idVec3 tmp;
idProjectile *lastProjectile;
//HUMANHEAD mdc - added to support multiple projectiles
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
int projIndex = FindProjectileInfo( desiredProjectileDef );
if( projIndex >= 0 ) {
SetCurrentProjectile( projIndex );
}
}
//HUMANHEAD END
if ( !projectileDef ) {
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
return NULL;
}
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
GetMuzzle( jointname, muzzle, axis );
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, axis[ 0 ] );
}
lastProjectile = projectile.GetEntity();
tmp = target - muzzle;
tmp.Normalize();
axis = tmp.ToMat3();
// rotate it because the cone points up by default
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
// make sure the projectile starts inside the monster bounding box
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
projClip = lastProjectile->GetPhysics()->GetClipModel();
projBounds = projClip->GetBounds().Rotate( axis );
// check if the owner bounds is bigger than the projectile bounds
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
start = muzzle + distance * viewAxis[ 0 ];
} else {
start = ownerBounds.GetCenter();
}
} else {
// projectile bounds bigger than the owner bounds, so just start it from the center
start = ownerBounds.GetCenter();
}
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
muzzle = tr.endpos;
// set aiming direction
//GetAimDir( muzzle, target, this, dir );
dir = target - muzzle;
ang = dir.ToAngles();
// adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
if ( clampToAttackCone ) {
// clamp the attack direction to be within monster's attack cone so he doesn't do
// things like throw the missile backwards if you're behind him
diff = idMath::AngleDelta( ang.yaw, current_yaw );
if ( diff > attack_cone ) {
ang.yaw = current_yaw + attack_cone;
} else if ( diff < -attack_cone ) {
ang.yaw = current_yaw - attack_cone;
}
}
axis = ang.ToMat3();
float spreadRad = DEG2RAD( projectile_spread );
for( i = 0; i < num_projectiles; i++ ) {
// spread the projectiles out
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir.Normalize();
// launch the projectile
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, dir );
}
lastProjectile = projectile.GetEntity();
lastProjectile->Launch( muzzle, dir, vec3_origin );
projectile = NULL;
}
TriggerWeaponEffects( muzzle, axis );
lastAttackTime = gameLocal.time;
//HUMANHEAD mdc - added to support multiple projectiles
projectile = NULL;
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
//HUMANHEAD END
return lastProjectile;
}
//
// LaunchProjectile
//
idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
idVec3 muzzle;
idVec3 dir;
idVec3 start;
trace_t tr;
idBounds projBounds;
float distance;
const idClipModel *projClip;
//float attack_accuracy;
//float attack_cone;
float projectile_spread;
float angle;
float spin;
idAngles ang;
int num_projectiles;
int i;
idMat3 axis;
idVec3 tmp;
idProjectile *lastProjectile;
//HUMANHEAD mdc - added to support multiple projectiles
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
int projIndex = FindProjectileInfo( desiredProjectileDef );
if( projIndex >= 0 ) {
SetCurrentProjectile( projIndex );
}
}
//HUMANHEAD END
if ( !projectileDef ) {
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
return NULL;
}
//attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
//attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
muzzle = startOrigin;
axis = startAxis;
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, axis[ 0 ] );
}
lastProjectile = projectile.GetEntity();
// make sure the projectile starts inside the monster bounding box
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
projClip = lastProjectile->GetPhysics()->GetClipModel();
projBounds = projClip->GetBounds().Rotate( axis );
// check if the owner bounds is bigger than the projectile bounds
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
start = muzzle + distance * viewAxis[ 0 ];
} else {
start = ownerBounds.GetCenter();
}
} else {
// projectile bounds bigger than the owner bounds, so just start it from the center
start = ownerBounds.GetCenter();
}
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
muzzle = tr.endpos;
float spreadRad = DEG2RAD( projectile_spread );
for( i = 0; i < num_projectiles; i++ ) {
// spread the projectiles out
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir.Normalize();
// launch the projectile
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, dir );
}
lastProjectile = projectile.GetEntity();
lastProjectile->Launch( muzzle, dir, vec3_origin );
projectile = NULL;
}
TriggerWeaponEffects( muzzle, axis );
lastAttackTime = gameLocal.time;
//HUMANHEAD mdc - added to support multiple projectiles
projectile = NULL;
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
//HUMANHEAD END
return lastProjectile;
}
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
void hhGasbagSimple::LinkScriptVariables(void) {
hhMonsterAI::LinkScriptVariables();
LinkScriptVariable(AI_PODCOUNT);
LinkScriptVariable(AI_CHARGEDONE);
LinkScriptVariable(AI_SWOOP);
LinkScriptVariable(AI_DODGEDAMAGE);
}
//
// Event_GrabEnemy()
//
void hhGasbagSimple::Event_GrabEnemy(void) {
idThread::ReturnFloat( GrabEnemy() );
}
//
// GrabEnemy()
//
bool hhGasbagSimple::GrabEnemy(void) {
if (!enemy.IsValid() || enemy->IsBound() || bindController->GetRider()) {
return false;
}
// Don't grab Talon
if (enemy->IsType(hhTalon::Type)) {
return false;
}
if (enemy->IsType(hhPlayer::Type)) {
hhPlayer *player = reinterpret_cast<hhPlayer *> (enemy.GetEntity());
if (player->IsSpiritOrDeathwalking() || player->InGravityZone()) { // Refuse to grab spirit players, or players affected by a gravity zone
return false;
}
player->Freeze();
}
bindController->Attach(enemy.GetEntity());
enemy->SetOrigin(vec3_origin);
return true;
}
//
// Event_BiteEnemy()
//
void hhGasbagSimple::Event_BiteEnemy(void) {
if (!enemy.IsValid() || bindController->GetRider() != enemy.GetEntity()) {
return;
}
if (enemy->IsType(hhSpiritProxy::Type)) {
float diff = gameLocal.GetTime() - reinterpret_cast<hhSpiritProxy *> (enemy.GetEntity())->GetActivationTime();
if (diff < 1150.0f) {
// Don't allow the spirit proxy to ignore damage
scriptThread->WaitMS(diff + 250.0f);
CancelEvents(&EV_BiteEnemy);
PostEventMS(&EV_BiteEnemy, diff + 100.0f);
return;
}
}
// Direct damage
bindController->Detach();
enemy->Damage(this, this, vec3_origin, spawnArgs.GetString("def_damage_bite"), 1.0f, INVALID_JOINT);
}
//
// Damage()
//
void hhGasbagSimple::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) {
int curHealth = health;
idEntity *enemyEnt = enemy.GetEntity();
hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, location);
if (health < curHealth) {
if (gameLocal.time > nextWoundTime && location != INVALID_JOINT) {
hhFxInfo fx;
fx.SetEntity(this);
fx.RemoveWhenDone(true);
SpawnFxLocal(spawnArgs.GetString("fx_damage"), inflictor->GetOrigin(), dir.ToMat3(), &fx);
nextWoundTime = gameLocal.time + 250;
}
// Only release enemy if we actually took damage
idEntity *rider;
if ((rider = bindController->GetRider()) != NULL) {
bindController->Detach();
if (rider->IsType(hhPlayer::Type)) {
rider->PostEventMS(&EV_Unfreeze, 0);
}
}
AI_DODGEDAMAGE = AI_DODGEDAMAGE + (curHealth - health);
}
}
//
// Event_DirectMoveToPosition()
//
void hhGasbagSimple::Event_DirectMoveToPosition(const idVec3 &pos) {
StopMove(MOVE_STATUS_DONE);
DirectMoveToPosition(pos);
}
//
// Event_ChargeEnemy
//
void hhGasbagSimple::Event_ChargeEnemy(void) {
AI_CHARGEDONE = true;
StopMove(MOVE_STATUS_DEST_NOT_FOUND);
if (!enemy.IsValid() || enemy->IsBound()) {
return;
}
idVec3 enemyOrg;
fly_offset = 0.0f;
// position destination so that we're in the enemy's view
enemyOrg = enemy->GetEyePosition();
enemyOrg -= enemy->GetPhysics()->GetGravityNormal() * fly_offset;
DirectMoveToPosition(enemyOrg);
HH_ASSERT(move.bEnemyBlocks == false);
}
void hhGasbagSimple::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {
hhMonsterAI::Event_EnemyIsSpirit(player, proxy);
if (bindController->GetRider() == player) {
bindController->Detach();
player->Unfreeze();
GrabEnemy();
}
}
void hhGasbagSimple::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) {
hhMonsterAI::Event_EnemyIsPhysical(player, proxy);
if (bindController->GetRider() == player) {
player->Freeze();
}
}
//
// Event_BindUnfroze
//
void hhGasbagSimple::Event_BindUnfroze(idEntity *unfrozenBind) {
if (!enemy.IsValid() || unfrozenBind != enemy.GetEntity() || bindController->GetRider() != enemy.GetEntity()) {
gameLocal.Warning("Gasbag received BindUnfroze event but enemy is not bound to it.\n");
return;
}
bindController->Detach();
}
//
// FlyTurn
//
void hhGasbagSimple::FlyTurn(void) {
if (!AI_SWOOP && (AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY)) {
TurnToward( lastVisibleEnemyPos );
} else if ((move.moveCommand == MOVE_FACE_ENTITY) && move.goalEntity.GetEntity()) {
TurnToward(move.goalEntity.GetEntity()->GetPhysics()->GetOrigin());
} else if (move.speed > 0.1f) {
const idVec3 &vel = physicsObj.GetLinearVelocity();
if (vel.ToVec2().LengthSqr() > 0.1f) {
TurnToward(vel.ToYaw());
}
}
Turn();
}
//
// Event_GrabCheck()
//
void hhGasbagSimple::Event_GrabCheck(void) {
idThread::ReturnInt(enemy.IsValid() && enemy->IsBoundTo(this));
}
void hhGasbagSimple::Event_MoveToGrabPosition(void) {
StopMove(MOVE_STATUS_DEST_NOT_FOUND);
if (!enemy.IsValid() || enemy->IsBound()) {
return;
}
fly_offset = 0.0f;
// position destination so that we're right on the enemy when we grab him
DirectMoveToPosition(enemy->GetOrigin() - spawnArgs.GetVector( "grab_offset" ));
HH_ASSERT(move.bEnemyBlocks == false);
}
void hhGasbagSimple::SetEnemy(idActor *newEnemy) {
idEntity *rider;
if ((rider = bindController->GetRider())) { // This is usually caused by Talon
if (rider->IsType(hhPlayer::Type)) {
rider->PostEventMS(&EV_Unfreeze, 0);
}
bindController->Detach();
}
hhMonsterAI::SetEnemy(newEnemy);
}
void hhGasbagSimple::Event_CheckRange(void) {
if (!enemy.IsValid()) {
idThread::ReturnInt(0);
return;
}
int retval;
float dist = ( enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length();
if (enemy->IsType(hhPlayer::Type)) {
float min = spawnArgs.GetFloat( "dda_range_min" );
float max = spawnArgs.GetFloat( "dda_range_max" );
float player_dist = min + (max - min) * gameLocal.GetDDAValue();
retval = (int) (dist <= player_dist);
} else {
retval = (int) (dist <= 100.0f);
}
idThread::ReturnInt(retval);
}
void hhGasbagSimple::Gib( const idVec3 &dir, const char *damageDefName ) {
// Bypass idActor::Gib()
idAFEntity_Gibbable::Gib( dir, damageDefName );
}
int hhGasbagSimple::ReactionTo( const idEntity *ent ) {
//gasbags skip the spirit stuff in hhMonsterAI::ReactionTo()
if ( bNoCombat ) {
return ATTACK_IGNORE;
}
const idActor *actor = static_cast<const idActor *>( ent );
if( actor && actor->IsType(hhDeathProxy::Type) ) {
return ATTACK_IGNORE;
}
if ( ent->IsType( hhMonsterAI::Type ) ) {
const hhMonsterAI *entAI = static_cast<const hhMonsterAI *>( ent );
if ( entAI && entAI->bNeverTarget ) {
return ATTACK_IGNORE;
}
}
return idAI::ReactionTo( ent );
}
void hhGasbagSimple::Event_EnemyRangeZ( void ) {
idThread::ReturnFloat( lastVisibleEnemyPos.z - GetOrigin().z );
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+90
View File
@@ -0,0 +1,90 @@
#ifndef __PREY_AI_GASBAG_SIMPLE_H__
#define __PREY_AI_GASBAG_SIMPLE_H__
extern const idEventDef EV_NewPod;
class hhPod;
class hhGasbagSimple : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhGasbagSimple);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void Event_AcidBlast(void) {};
void Event_AcidDrip(void) {};
void Event_DeathCloud(void) {};
void Event_NewPod(hhPod *pod) {};
void Event_LaunchPod(void) {};
void Event_SpawnBlastDebris(void) {};
void Event_ChargeEnemy(void) {};
void Event_GrabEnemy(void) {};
void Event_BiteEnemy(void) {};
void Event_DirectMoveToPosition(const idVec3 &pos) {};
void Event_BindUnfroze(idEntity *unfrozenBind) {};
virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {};
virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) {};
void Event_GrabCheck(void) {};
void Event_MoveToGrabPosition(void) {};
void Event_CheckRange(void) {};
void Event_EnemyRangeZ(void) {};
#else
virtual ~hhGasbagSimple(void);
void Spawn(void);
void Save(idSaveGame *savefile) const;
void Restore(idRestoreGame *savefile);
virtual void FlyTurn(void);
virtual void Gib(const idVec3 &dir, const char *damageDefName);
int ReactionTo( const idEntity *ent );
protected:
void Event_AcidBlast(void);
void Event_AcidDrip(void);
void Event_DeathCloud(void);
void Event_NewPod(hhPod *pod);
void Event_LaunchPod(void);
void Event_SpawnBlastDebris(void);
void Event_ChargeEnemy(void);
void Event_GrabEnemy(void);
void Event_BiteEnemy(void);
void Event_DirectMoveToPosition(const idVec3 &pos);
void Event_BindUnfroze(idEntity *unfrozenBind);
virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy );
virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy );
void Event_GrabCheck(void);
void Event_MoveToGrabPosition(void);
void Event_CheckRange(void);
void Event_EnemyRangeZ(void);
bool GrabEnemy(void);
virtual void LinkScriptVariables(void);
virtual void Ticker(void);
virtual idProjectile *LaunchProjectileAtVec(const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef);
virtual idProjectile *LaunchProjectileAtVec(const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef);
virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location);
virtual void Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location);
virtual void SetEnemy(idActor *newEnemy);
idVec3 podOffset;
float podRange;
int dripCount;
idList< idEntityPtr<hhPod> > podList;
idScriptInt AI_PODCOUNT;
idScriptBool AI_CHARGEDONE;
idScriptBool AI_SWOOP;
idScriptInt AI_DODGEDAMAGE;
int nextWoundTime;
idEntityPtr<hhBindController> bindController; // Bind controller for tractor beam
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif
+838
View File
@@ -0,0 +1,838 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef MA_AntiProjectileAttack("<antiProjectileAttack>", "e");
const idEventDef MA_StartPreDeath("<startPreDeath>" );
const idEventDef MA_HandlePassageway("<handlePassageway>", NULL);
const idEventDef MA_EnterPassageway("<enterPassageway>", "e",NULL);
const idEventDef MA_ExitPassageway("<exitPassageway>", "e",NULL);
const idEventDef MA_UseThisPassageway("useThisPassageway", "ed",NULL);
const idEventDef MA_GibOnDeath("gibOnDeath", "d");
CLASS_DECLARATION( hhMonsterAI, hhHarvesterSimple )
EVENT( MA_OnProjectileLaunch, hhHarvesterSimple::Event_OnProjectileLaunch )
EVENT( MA_AntiProjectileAttack, hhHarvesterSimple::Event_AntiProjectileAttack )
EVENT( MA_StartPreDeath, hhHarvesterSimple::Event_StartPreDeath )
EVENT( MA_HandlePassageway, hhHarvesterSimple::Event_HandlePassageway )
EVENT( MA_EnterPassageway, hhHarvesterSimple::Event_EnterPassageway )
EVENT( MA_ExitPassageway, hhHarvesterSimple::Event_ExitPassageway )
EVENT( MA_UseThisPassageway, hhHarvesterSimple::Event_UseThisPassageway )
EVENT( EV_Broadcast_AppendFxToList, hhHarvesterSimple::Event_AppendFxToList )
EVENT( MA_GibOnDeath, hhHarvesterSimple::Event_GibOnDeath )
END_CLASS
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
//
// Spawn()
//
void hhHarvesterSimple::Spawn(void) {
allowPreDeath = true;
lastAntiProjectileAttack = 0;
lastPassagewayTime = 0;
passageCount = spawnArgs.GetInt("passage_count", "0"); // Used for passing to and from torso
bSmokes = spawnArgs.GetBool("smokes", "0");
bGibOnDeath = false;
}
//
// Event_OnProjectileLaunch()
//
void hhHarvesterSimple::Event_OnProjectileLaunch(hhProjectile *proj) {
// Can't launch again yet
float min = spawnArgs.GetFloat( "dda_delay_min" );
float max = spawnArgs.GetFloat( "dda_delay_max" );
float delay = min + (max - min) * (1.0f - gameLocal.GetDDAValue());
if(gameLocal.GetTime() - lastAntiProjectileAttack < delay)
return;
// The person who launched this projectile wasn't someone to worry about
if(proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)))
return;
// TODO: more intelligent checks for if we should launch the anti-projectile chaff
idVec3 fw = viewAxis[0];
idVec3 projFw = proj->GetAxis()[0];
if(proj->GetOwner())
projFw = proj->GetOwner()->GetAxis()[0];
float dot = fw * projFw;
if(dot > -.7f)
return;
ProcessEvent(&MA_AntiProjectileAttack, proj);
}
//
// Event_AntiProjectileAttack()
//
void hhHarvesterSimple::Event_AntiProjectileAttack(hhProjectile *proj) {
if(!spawnArgs.GetBool("block_projectiles", "1")) {
return;
}
const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chaff") );
if ( !projectileDef ) {
gameLocal.Error( "Unknown def_chaff: %s\n", spawnArgs.GetString("def_chaff") );
}
hhProjectile* projectile = NULL;
//bjk: new parms
int numProj = spawnArgs.GetInt("shieldNumProj", "10");
float spread = DEG2RAD( spawnArgs.GetFloat("shieldSpread", "10") );
float yaw = DEG2RAD( spawnArgs.GetFloat("shieldYawSpread", "10") );
//StartSound("snd_fire_chaff", SND_CHANNEL_ANY, 0, true, NULL);
torsoAnim.PlayAnim( GetAnimator()->GetAnim( "antiprojectile_attack" ) );
idVec3 dir = proj->GetOrigin() - GetOrigin();
dir.Normalize();
dir = dir+idVec3(0.f, 0.f, -.3f); // bjk: bias towards ground
dir.Normalize();
idMat3 muzzleAxis = dir.ToMat3();
// todo: read this in from a bone
idVec3 launchPos = GetOrigin() + idVec3(0.0f, 0.0f, 64.0f);
for(int i=0; i<numProj; i++) {
projectile = hhProjectile::SpawnProjectile( projectileDef );
HH_ASSERT( projectile );
//bjk: uses random now
float ang = spread * hhMath::Sqrt( gameLocal.random.RandomFloat() );
float spin = hhMath::TWO_PI * gameLocal.random.RandomFloat();
dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( hhMath::Sin(ang) * hhMath::Sin(spin) ) - muzzleAxis[ 1 ] * ( hhMath::Sin(ang+yaw) * hhMath::Cos(spin) );
dir.Normalize();
projectile->Create(this, launchPos, dir);
projectile->Launch(launchPos, dir, idVec3(0.0f, 0.0f, 0.0f));
}
lastAntiProjectileAttack = gameLocal.GetTime();
}
bool hhHarvesterSimple::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
int preDeathThresh = spawnArgs.GetInt("predeath_thresh", "-1");
if(allowPreDeath && !bGibOnDeath && preDeathThresh > 0 && health < preDeathThresh) {
allowPreDeath = false; // We've tried, cannot try anymore
float preDeathFreq = spawnArgs.GetFloat("predeath_freq", "1");
if(gameLocal.random.RandomFloat() < preDeathFreq) {
ProcessEvent(&MA_StartPreDeath);
}
}
if ( bSmokes ) {
if ( !fxSmoke[0].IsValid() && health <= AI_PASSAGEWAY_HEALTH ) {
SpawnSmoke();
} else if (fxSmoke[0].IsValid() && health > AI_PASSAGEWAY_HEALTH) {
ClearSmoke();
}
}
return( idAI::Pain(inflictor, attacker, damage, dir, location) );
}
void hhHarvesterSimple::Event_StartPreDeath(void) {
int i;
// No torso? Just kill myself .....
if(!spawnArgs.GetString("def_torso")) {
ProcessEvent(&AI_Kill);
return;
}
// Spawn death fx, if any
const char *fx = spawnArgs.GetString("fx_death");
if (fx && fx[0]) {
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone(true);
BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo);
}
// do blood splats
float size = spawnArgs.GetFloat("decal_torso_pop_size","96");
// copy my target keys to the keys that will spawn the torso
idDict dict;
const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_torso"));
if ( !torsoDict ) {
gameLocal.Error("Unknown def_torso: %s\n", spawnArgs.GetString("def_torso"));
}
dict.Copy(*torsoDict);
for(i=0;i<targets.Num();i++)
{
if(!targets[i].GetEntity()) {
continue;
}
idStr key("target");
if(i > 0) {
sprintf(key, "target%i", i);
}
dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name);
}
// Passageway variables
dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH);
dict.SetInt("passage_count", passageCount);
dict.SetVector("origin", GetOrigin());
dict.SetMatrix("rotation", GetAxis());
// remove my target keys (so they don't get fired)
targets.Clear();
// Spawn the torso
idEntity *e;
hhMonsterAI *aiTorso;
if(!gameLocal.SpawnEntityDef(dict, &e))
gameLocal.Error("Failed to spawn ai torso.");
HH_ASSERT(e && e->IsType(hhMonsterAI::Type));
aiTorso = static_cast<hhMonsterAI*>(e);
HH_ASSERT(aiTorso != NULL);
// Throw gibs
idStr debrisDef = spawnArgs.GetString("def_gibDebrisSpawnerPreDeath");
if(debrisDef.Length() > 0) {
hhUtils::SpawnDebrisMass(debrisDef.c_str(), this, 1.0f);
}
health = 0;
// remove myself
Hide();
PostEventMS(&EV_Remove, 3000);
}
void hhHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
if ( attacker == this ) {
return;
}
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
if ( !AI_DEAD ) {
if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) {
spawnArgs.Set( "bAlwaysGib", "0" );
allowPreDeath = false;
} else {
allowPreDeath = true;
}
if ( damageDef && damageDef->GetBool( "no_special_death" ) ) {
allowPreDeath = false;
}
}
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
int hhHarvesterSimple::EvaluateReaction( const hhReaction *react ) {
// Let the script limit the distance of Climb reactions we consider
if ( react->desc->effect == hhReactionDesc::Effect_Climb && AI_CLIMB_RANGE >= 0.0f && ( react->causeEntity->GetOrigin() - GetOrigin()).LengthSqr() > AI_CLIMB_RANGE ) {
return 0;
}
// If we're supposed to use a specific passageway, only accept it and reject the others
if ( nextPassageway.IsValid() && react->desc->effect == hhReactionDesc::Effect_Passageway ) {
if (react->causeEntity.GetEntity() == nextPassageway.GetEntity()) {
return 100;
} else {
return 0;
}
}
int rank = hhMonsterAI::EvaluateReaction( react );
if ( react->desc->effect == hhReactionDesc::Effect_Passageway &&
react->causeEntity.GetEntity() == lastPassageway.GetEntity() ) {
if ( lastPassagewayTime + 10000 > gameLocal.time ) {
if (rank > 10) {
// Discourage entering the same passageway we just left
return 10;
}
} else {
// Ignore previous passageway for 10 seconds
return 0;
}
}
return rank;
}
#define CheckPassageFreqInitial 6000
void hhHarvesterSimple::Event_EnterPassageway(hhAIPassageway *pn) {
CancelEvents( &MA_OnProjectileLaunch ); // Clear any anti-projectile attacks
CancelEvents( &MA_AntiProjectileAttack ); // Clear any anti-projectile attacks
if (nextPassageway.IsValid()) {
if (nextPassageway.GetEntity() != pn) {
gameLocal.Warning("hhAIPassageway entered was not the specified one!\n");
}
nextPassageway.Clear();
}
if(currPassageway != NULL) {
gameLocal.Error("%s tried to ENTER a passageway but already had a passageway!", (const char*)name);
}
HH_ASSERT(currPassageway == NULL);
Hide(); // Should already be hidden but just to make sure
// Only consider passageCount if damaged (ignores scripted passage sequences) or if it's the torso
if (health < spawnHealth || !idStr::Icmp(spawnArgs.GetString("classname"), "monster_harvester_torso") ) {
passageCount++;
if (passageCount > 1) {
//TODO make this affected by the DDA system
AI_PASSAGEWAY_HEALTH = AI_PASSAGEWAY_HEALTH * 0.75f; // Reduce passageway seeking health level to 3/4 of it's original value
}
}
GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0);
Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Idle", (int)0);
Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Idle", (int)0);
torsoAnim.UpdateState();
legsAnim.UpdateState();
StartSound("snd_inside_passage", SND_CHANNEL_VOICE, SSF_LOOPING, true, NULL);
currPassageway = pn;
if(GetPhysics())
GetPhysics()->SetContents(0);
// Does this passage way give us health?
int h = 0;
pn->spawnArgs.GetInt("give_health", "0", h);
health += h;
health = idMath::ClampInt(0, spawnArgs.GetInt("health"), health);
// Should we transform to another monster when entering?
idStr transTo;
if(spawnArgs.GetString("passage_transform_to", "", transTo)) {
const idDict *def = gameLocal.FindEntityDefDict( transTo );
if(!def) {
gameLocal.Error("Unknown def : %s", (const char*)transTo);
}
// copy my target keys to the keys of the transformed-to entity
idDict dict;
dict.Copy(*def);
for(int i=0;i<targets.Num();i++)
{
if(!targets[i].GetEntity())
continue;
idStr key("target");
if(i > 0)
sprintf(key, "target%i", i);
dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name);
}
// Passageway variables
dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH);
dict.SetInt("passage_count", passageCount);
idEntity *e = NULL;
gameLocal.SpawnEntityDef(dict, &e);
HH_ASSERT( e->IsType( hhHarvesterSimple::Type ) );
hhHarvesterSimple *ai = static_cast<hhHarvesterSimple*>(e);
ai->SetOrigin(GetOrigin());
ai->SetAxis(GetAxis());
ai->Event_EnterPassageway(pn);
targets.Clear();
PostEventSec(&EV_Remove, 0.1f);
return;
}
PostEventMS(&MA_HandlePassageway, CheckPassageFreqInitial);
}
void hhHarvesterSimple::Event_ExitPassageway(hhAIPassageway *pn) {
if(currPassageway == NULL) {
gameLocal.Error("%s tried to EXIT a passageway but did NOT have a curr passageway!", (const char*)name);
}
HH_ASSERT(currPassageway != NULL);
// Store this passageway so we don't jump right back into it.
lastPassageway = idEntityPtr<idEntity> (pn);
lastPassagewayTime = gameLocal.time;
if(GetPhysics())
GetPhysics()->SetContents(CONTENTS_BODY);
currPassageway = NULL;
AI_ACTIVATED = true; // Sometimes this is NOT true?!?!
StopSound(SND_CHANNEL_VOICE, true);
// Teleport to new pos
idAngles angs = pn->GetAxis().ToAngles();
idVec3 pos = pn->GetExitPos();
Teleport( pos, angs, pn );
Show();
current_yaw = angs.yaw;
ideal_yaw = angs.yaw;
turnVel = 0.0f;
HH_ASSERT(FacingIdeal());
// Exit anim to play?
idStr exitAnim = pn->spawnArgs.GetString("exit_anim");
if(exitAnim.Length() && GetAnimator()->HasAnim(exitAnim)) {
GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0);
torsoAnim.UpdateState();
legsAnim.UpdateState();
torsoAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) );
legsAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) );
}
}
//
// Event_HandlePassageway()
//
#define MaxExits 32
#define CheckPassageFreq 3000
void hhHarvesterSimple::Event_HandlePassageway(void) {
if(currPassageway == NULL) {
gameLocal.Error("%s tried to handle a passageway but did NOT have a passageway!", (const char*)name);
}
HH_ASSERT(currPassageway != NULL);
hhAIPassageway *exitNode = NULL;
// CATCH 'went inside hole - never came out' bug
if(currPassageway.GetEntity()->lastEntered == this && gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > 30000) {
gameLocal.Warning("%s has been stuck in a passageway for >30 secs, killing it off.", (const char*)name);
fl.takedamage = true;
// Use hhMonsterAI::Damage because our Damage returns if we're the attacker
hhMonsterAI::Damage(this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT);
return;
}
// No other way out - gotta go out the way we came in
if(currPassageway->targets.Num() <= 0) {
exitNode = currPassageway.GetEntity();
}
// We can go out another way
else {
hhAIPassageway *possibleExits[MaxExits];
hhAIPassageway *tmp;
int count = 0;
bool isRandom;
if (gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > SEC2MS(10)) {
// Try to prevent us from being stuck after 10 seconds
isRandom = true;
} else {
// Choose possibleExits[] randomly if we have no enemy or based on chance. Otherwise choose based on line-of-sight to the player
isRandom = !enemy.IsValid() || gameLocal.random.RandomInt(100) > 50;
}
trace_t tr;
idVec3 toPos;
if (!isRandom) {
toPos = enemy->GetOrigin();
}
// We've tried once, now we can use our entrance as an exit
if(gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > CheckPassageFreqInitial + 1000) {
count = 1;
possibleExits[0] = currPassageway.GetEntity(); // We can always go out the same way we came in
}
for(int i=0;i<currPassageway->targets.Num();i++) {
if(currPassageway->targets[i] != NULL && currPassageway->targets[i].GetEntity()->IsType(hhAIPassageway::Type)) {
if (isRandom) {
possibleExits[count++] = static_cast<hhAIPassageway*>(currPassageway->targets[i].GetEntity());
} else {
tmp = static_cast<hhAIPassageway*>(currPassageway->targets[i].GetEntity());
// Choose based on visibility to our enemy
gameLocal.clip.TracePoint( tr, tmp->GetOrigin(), toPos, MASK_SHOT_BOUNDINGBOX, tmp );
if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemy.GetEntity() ) ) {
lastVisibleEnemyPos = toPos; // Can now see enemy
possibleExits[count++] = tmp;
}
}
}
// We've got enough
if(count >= MaxExits)
break;
}
if(count == 0) {
exitNode = currPassageway.GetEntity();
} else if ( enemy.IsValid() && gameLocal.random.RandomInt(100) > 70 ) {
// Find the one closest to the last known enemy pos
if (!exitNode) {
float bestDistance = -1.0f, distSq;
for (int i = 0; i < count; i++) {
distSq = ( possibleExits[i]->GetOrigin() - lastVisibleEnemyPos ).LengthSqr();
if ( bestDistance == -1.0f || distSq < bestDistance ) {
exitNode = possibleExits[i];
bestDistance = distSq;
}
}
}
} else {
// Pick a random one
int randExit = gameLocal.random.RandomInt(count); // TODO: Randomness isn't very random! Fix!
HH_ASSERT(randExit >= 0 && randExit < count);
exitNode = possibleExits[randExit];
}
}
if(!exitNode)
exitNode = currPassageway.GetEntity();
// The one we picked is disabled, so go out where we came in
// TODO: make this time based, try a few more times before going back out
if(!exitNode->IsPassagewayEnabled()) {
exitNode = currPassageway.GetEntity();
}
// Special case: The passageway is never meant to be exited from, use it's specified exit point instead
const char *altPassage = exitNode->spawnArgs.GetString("force_passageway", "");
if (altPassage && altPassage[0]) {
idEntity *node = gameLocal.FindEntity(altPassage);
if (node && node->IsType(hhAIPassageway::Type)) {
exitNode = reinterpret_cast<hhAIPassageway *> (node);
}
}
// Make sure no one is blocking where we want to go out
idBounds myBnds = GetPhysics()->GetBounds();
myBnds.Expand(12.0f);
myBnds.TranslateSelf(exitNode->GetExitPos());
idEntity *ents[MAX_GENTITIES];
int count = gameLocal.clip.EntitiesTouchingBounds(myBnds, -1, ents, MAX_GENTITIES);
for(int i=0;i<count;i++) {
// We'll have to try again later when the coast is clear
if(ents[i] && ents[i] != this && !ents[i]->IsHidden() && (ents[i]->IsType(idActor::Type) || ents[i]->IsType(hhMoveable::Type))) {
// Chunk moveables
if ( ents[i]->IsType(idMoveable::Type) ) {
ents[i]->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT );
ents[i]->SquishedByDoor(this);
continue;
}
gameLocal.Warning("Passageway exit blocked.\n");
PostEventMS(&MA_HandlePassageway, CheckPassageFreq);
if(ai_debugBrain.GetInteger() != 0) {
gameRenderWorld->DebugBounds(colorYellow, myBnds, vec3_origin, 2000);
}
return;
}
}
// Else we can just exit now - its safe
exitNode->ProcessEvent(&EV_Activate, this);
}
#define LinkScriptVariable( name ) name.LinkTo(scriptObject, #name)
void hhHarvesterSimple::LinkScriptVariables() {
hhMonsterAI::LinkScriptVariables();
LinkScriptVariable(AI_CLIMB_RANGE);
LinkScriptVariable(AI_PASSAGEWAY_HEALTH);
}
void hhHarvesterSimple::Event_UseThisPassageway(hhAIPassageway *pn, bool force) {
nextPassageway.Assign(pn);
if (force) { // Snap to position
Event_FindReaction("passageway");
hhReaction *reaction = targetReaction.GetReaction();
if (!reaction) {
gameLocal.Warning("useThisPassageway could not find a valid reaction!\n");
} else {
idVec3 tp = GetTouchPos( reaction->causeEntity.GetEntity(), reaction->desc );
SetOrigin(tp);
}
}
}
/*
=====================
hhHarvesterSimple::Save
=====================
*/
void hhHarvesterSimple::Save( idSaveGame *savefile ) const {
savefile->WriteBool( allowPreDeath );
savefile->WriteInt( lastAntiProjectileAttack );
savefile->WriteInt( passageCount );
savefile->WriteBool( bGibOnDeath );
savefile->WriteInt( lastPassagewayTime );
lastPassageway.Save( savefile );
nextPassageway.Save( savefile );
}
/*
=====================
hhHarvesterSimple::Restore
=====================
*/
void hhHarvesterSimple::Restore( idRestoreGame *savefile ) {
savefile->ReadBool( allowPreDeath );
savefile->ReadInt( lastAntiProjectileAttack );
savefile->ReadInt( passageCount );
savefile->ReadBool( bGibOnDeath );
savefile->ReadInt( lastPassagewayTime );
lastPassageway.Restore( savefile );
nextPassageway.Restore( savefile );
bSmokes = spawnArgs.GetBool("smokes", "0");
}
void hhHarvesterSimple::SpawnSmoke(void) {
const char *bones[MAX_HARVESTER_LEGS];
bones[0] = spawnArgs.GetString( "bone_smoke_front_left" );
bones[1] = spawnArgs.GetString( "bone_smoke_front_right" );
bones[2] = spawnArgs.GetString( "bone_smoke_rear_left" );
bones[3] = spawnArgs.GetString( "bone_smoke_rear_right" );
const char *psystem = spawnArgs.GetString( "fx_smoke" );
if ( psystem && *psystem ) {
idVec3 bonePos;
idMat3 boneAxis;
for(int i = 0; i < MAX_HARVESTER_LEGS; i++) {
if( fxSmoke[i] != NULL ) {
gameLocal.Warning("Harvester already had smoke particles for leg %i.\n", i);
continue;
}
hhFxInfo fxInfo;
this->GetJointWorldTransform( bones[i], bonePos, boneAxis );
fxInfo.SetEntity( this );
fxInfo.SetBindBone( bones[i] );
fxInfo.Toggle();
fxInfo.RemoveWhenDone( false );
BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList );
}
}
}
void hhHarvesterSimple::ClearSmoke(void) {
for(int i=0; i < MAX_HARVESTER_LEGS; i++) {
if(fxSmoke[i].IsValid()) {
fxSmoke[i]->Nozzle(false);
}
}
for(int i = 0; i < MAX_HARVESTER_LEGS; i++) {
SAFE_REMOVE(fxSmoke[MAX_HARVESTER_LEGS]);
}
}
void hhHarvesterSimple::Event_AppendFxToList(hhEntityFx *fx) {
for(int i = 0; i < MAX_HARVESTER_LEGS; ++i) {
if(fxSmoke[i] != NULL) {
continue;
}
fxSmoke[i] = fx;
return;
}
}
void hhHarvesterSimple::Event_GibOnDeath(const idList<idStr>* parmList) {
bGibOnDeath = (atoi((*parmList)[0].c_str()) != 0);
}
void hhHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
ClearSmoke();
hhMonsterAI::Killed(inflictor, attacker, damage, dir, location);
if (bGibOnDeath) {
Gib(vec3_origin, "damage_gib");
}
}
void hhHarvesterSimple::Event_Activate(idEntity *activator) {
if (fl.hidden) {
// If we're hidden, we're probably birthing out of passageway. Figure out which one.
hhAIPassageway *nearest = NULL;
float dist, nearDist = idMath::INFINITY;
hhAIPassageway *passage = reinterpret_cast<hhAIPassageway *> (gameLocal.FindEntityOfType( hhAIPassageway::Type, NULL ));
while (passage) {
dist = (passage->GetOrigin() - GetOrigin()).Length();
if (dist < nearDist) {
nearDist = dist;
nearest = passage;
}
passage = reinterpret_cast<hhAIPassageway *> (gameLocal.FindEntityOfType( hhAIPassageway::Type, passage ));
}
// If we found one near us and it's near enough, mark it as our last passageway
if (nearest && nearDist < 250.0f) {
lastPassageway = nearest;
lastPassagewayTime = gameLocal.time;
}
}
hhMonsterAI::Event_Activate(activator);
}
void hhHarvesterSimple::AnimMove( void ) {
idVec3 goalPos;
idVec3 delta;
idVec3 goalDelta;
float goalDist;
monsterMoveResult_t moveResult;
idVec3 newDest;
// Turn on physics to prevent flying harvesters
BecomeActive(TH_PHYSICS);
idVec3 oldorigin = physicsObj.GetOrigin();
#ifdef HUMANHEAD //jsh wallwalk
idMat3 oldaxis = GetGravViewAxis();
#else
idMat3 oldaxis = viewAxis;
#endif
AI_BLOCKED = false;
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
move.lastMoveOrigin.Zero();
move.lastMoveTime = gameLocal.time;
}
move.obstacle = NULL;
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
TurnToward( lastVisibleEnemyPos );
goalPos = oldorigin;
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
goalPos = oldorigin;
} else if ( GetMovePos( goalPos ) ) {
if ( move.moveCommand != MOVE_WANDER ) {
CheckObstacleAvoidance( goalPos, newDest );
TurnToward( newDest );
} else {
TurnToward( goalPos );
}
}
Turn();
if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
if ( gameLocal.time < move.startTime + move.duration ) {
goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
delta = goalPos - oldorigin;
delta.z = 0.0f;
} else {
delta = move.moveDest - oldorigin;
delta.z = 0.0f;
StopMove( MOVE_STATUS_DONE );
}
} else if ( allowMove ) {
#ifdef HUMANHEAD //jsh wallwalk
GetMoveDelta( oldaxis, GetGravViewAxis(), delta );
#else
GetMoveDelta( oldaxis, viewAxis, delta );
#endif
} else {
delta.Zero();
}
if ( move.moveCommand == MOVE_TO_POSITION ) {
goalDelta = move.moveDest - oldorigin;
goalDist = goalDelta.LengthFast();
if ( goalDist < delta.LengthFast() ) {
delta = goalDelta;
}
}
#ifdef HUMANHEAD //shrink functionality
float scale = renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM1];
if ( scale > 0.0f && scale < 2.0f ) {
delta *= scale;
}
#endif
physicsObj.SetDelta( delta );
physicsObj.ForceDeltaMove( disableGravity );
RunPhysics();
if ( ai_debugMove.GetBool() ) {
// HUMANHEAD JRM - so we can see if grav is on or off
if(disableGravity) {
gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 );
} else {
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
}
}
moveResult = physicsObj.GetMoveResult();
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
DirectDamage( attack, enemy.GetEntity() );
} else {
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
if ( blockEnt && blockEnt->IsType( hhHarvesterSimple::Type ) ) {
StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER );
return;
}
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
}
}
BlockedFailSafe();
AI_ONGROUND = physicsObj.OnGround();
idVec3 org = physicsObj.GetOrigin();
if ( oldorigin != org ) {
TouchTriggers();
}
if ( ai_debugMove.GetBool() ) {
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
DrawRoute();
}
}
void hhHarvesterSimple::Event_CanBecomeSolid( void ) {
int i;
int num;
idEntity * hit;
idClipModel *cm;
idClipModel *clipModels[ MAX_GENTITIES ];
num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES );
for ( i = 0; i < num; i++ ) {
cm = clipModels[ i ];
// don't check render entities
if ( cm->IsRenderModel() ) {
continue;
}
hit = cm->GetEntity();
if ( hit == this ) {
continue;
}
// Special case, crush any moveables blocking our start point
if ( hit->IsType( idMoveable::Type ) ) {
hit->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT );
hit->SquishedByDoor(this);
}
if ( !hit->fl.takedamage ) {
continue;
}
if ( physicsObj.ClipContents( cm ) ) {
idThread::ReturnFloat( false );
return;
}
}
idThread::ReturnFloat( true );
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+79
View File
@@ -0,0 +1,79 @@
#ifndef __PREY_AI_HARVESTERSIMPLE_H__
#define __PREY_AI_HARVESTERSIMPLE_H__
extern const idEventDef AI_OnProjectileLaunch;
extern const idEventDef MA_EnterPassageway;
extern const idEventDef MA_ExitPassageway;
#define MAX_HARVESTER_LEGS 4
class hhHarvesterSimple : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhHarvesterSimple);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
virtual void Event_StartPreDeath(void) {};
void Event_OnProjectileLaunch(hhProjectile *proj) {};
void Event_AntiProjectileAttack(hhProjectile *proj) {};
void Event_EnterPassageway(hhAIPassageway *pn) {};
void Event_ExitPassageway(hhAIPassageway *pn) {};
void Event_HandlePassageway(void) {};
void Event_UseThisPassageway(hhAIPassageway *pn, bool force) {};
void Event_AppendFxToList(hhEntityFx *fx) {};
void Event_GibOnDeath(const idList<idStr>* parmList) {};
virtual void Event_Activate(idEntity *activator) {};
virtual void Event_CanBecomeSolid(void) {};
#else
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void LinkScriptVariables(void);
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location);
protected:
void Spawn(void);
virtual int EvaluateReaction( const hhReaction *react );
void SpawnSmoke(void);
void ClearSmoke(void);
//predeath torso code
virtual void Event_StartPreDeath(void);
virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
bool allowPreDeath; // TRUE if we will allow predeath
virtual void AnimMove(void);
//anti-projectile chaff
void Event_OnProjectileLaunch(hhProjectile *proj);
void Event_AntiProjectileAttack(hhProjectile *proj);
void Event_EnterPassageway(hhAIPassageway *pn);
void Event_ExitPassageway(hhAIPassageway *pn);
void Event_HandlePassageway(void);
void Event_UseThisPassageway(hhAIPassageway *pn, bool force);
void Event_AppendFxToList(hhEntityFx *fx);
void Event_GibOnDeath(const idList<idStr>* parmList);
virtual void Event_Activate(idEntity *activator);
virtual void Event_CanBecomeSolid(void);
int lastAntiProjectileAttack;
int lastPassagewayTime;
idEntityPtr<idEntity> lastPassageway;
idEntityPtr<idEntity> nextPassageway;
idScriptFloat AI_CLIMB_RANGE;
idScriptFloat AI_PASSAGEWAY_HEALTH;
idEntityPtr<hhEntityFx> fxSmoke[MAX_HARVESTER_LEGS];
int passageCount;
bool bSmokes;
bool bGibOnDeath;
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
#ifndef __PREY_AI_HUNTER_SIMPLE_H__
#define __PREY_AI_HUNTER_SIMPLE_H__
#define HUNTER_N 0
#define HUNTER_NE 1
#define HUNTER_E 2
#define HUNTER_SE 3
#define HUNTER_S 4
#define HUNTER_SW 5
#define HUNTER_W 6
#define HUNTER_NW 7
class hhHunterSimple : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhHunterSimple);
hhHunterSimple();
void Event_OnProjectileLaunch(hhProjectile *proj);
void Event_OnProjectileHit(hhProjectile *proj);
void Event_LaserOn();
void Event_LaserOff();
void Event_FindReaction( const char* effect );
void Spawn();
void Think();
idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef );
void AnimMove();
bool UpdateAnimationControllers();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
virtual void PrintDebug();
virtual void LinkScriptVariables();
bool TurnToward( const idVec3 &pos );
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length );
void SetNextVoiceTime( int newTime ) { nextVoiceTime = newTime; }
void Show();
void BlockedFailSafe();
void UpdateEnemyPosition();
void Distracted( idActor *newEnemy );
bool ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const;
void Event_EscapePortal();
void Event_AssignSniperFx( hhEntityFx* fx );
void Event_AssignMuzzleFx( hhEntityFx* fx );
void Event_AssignFlashLightFx( hhEntityFx* fx );
void Event_KickAngle( const idAngles &ang );
void Event_FlashLightOn();
void Event_FlashLightOff();
void Event_PostSpawn();
void Event_EnemyCanSee();
void Event_PrintAction( const char *action );
void Event_GetAlly();
void Event_TriggerDelay( idEntity *ent, float delay );
void Event_CallBackup( float delay );
virtual bool CanSee( idEntity *ent, bool useFov );
void Event_GetAdvanceNode();
void Event_GetRetreatNode();
void Event_OnProjectileLand(hhProjectile *proj);
void Event_SaySound( const char *soundName );
void Event_CheckRush();
void Event_CheckRetreat();
void Event_SayEnemyInfo();
void Event_SetNextVoiceTime( int nextTime );
void Event_GiveOrders( const char *orders );
void Event_AllowOrders( bool orders );
void Event_GetCoverNode();
void Event_GetCoverPoint();
void Event_GetSightNode();
void Event_GetNearSightPoint();
void Event_Blocked();
void Event_EnemyPortal( idEntity *portal );
void Event_GetEnemyPortal();
void Event_EnemyVehicleDocked();
void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes
idEntityPtr<hhMonsterAI> ally;
idScriptBool AI_ALLOW_ORDERS;
idScriptBool AI_SHOTBLOCK;
protected:
idVec3 nextMovePos;
idEntityPtr<hhBeamSystem> beamLaser;
idAngles kickAngles;
idAngles kickSpeed;
idEntityPtr<hhEntityFx> sniperFx;
idEntityPtr<hhEntityFx> muzzleFx;
idEntityPtr<hhEntityFx> flashLightFx;
idList< idEntityPtr< idEntity > > nodeList;
bool alternateAccuracy;
bool bFlashLight;
idScriptInt AI_DIR;
idScriptFloat AI_LAST_DAMAGE_TIME;
idStr currentAction;
idStr currentSpeech;
int endSpeechTime;
idScriptBool AI_DIR_MOVEMENT;
idScriptBool AI_ENEMY_RUSH;
idScriptBool AI_ENEMY_RETREAT;
idScriptBool AI_ENEMY_HEALTH_LOW;
idScriptBool AI_ENEMY_RESURRECTED;
idScriptBool AI_ONGROUND;
idScriptBool AI_ENEMY_SHOOTABLE;
idScriptBool AI_BLOCKED_FAILSAFE;
idScriptInt AI_ENEMY_LAST_SEEN;
idScriptInt AI_NEXT_DIR_TIME;
idScriptBool AI_KNOCKBACK;
int nextEnemyCheck;
float lastEnemyDistance;
int enemyRushCount;
int enemyRetreatCount;
int enemyHiddenCount;
int lastChargeTime;
int nextVoiceTime;
idVec3 initialOrigin;
float flashlightLength;
int flashlightTime;
idEntityPtr<idEntity> enemyPortal;
idVec3 lastMoveOrigin;
int nextBlockCheckTime;
int nextSpiritCheck; //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes
};
#endif /* __PREY_AI_HUNTER_SIMPLE_H__ */
+136
View File
@@ -0,0 +1,136 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef MA_CheckReactions( "<checkreactions>", "e" );
CLASS_DECLARATION( hhMonsterAI, hhAIInspector )
EVENT( EV_PostSpawn, hhAIInspector::Event_PostSpawn )
EVENT( MA_CheckReactions, hhAIInspector::Event_CheckReactions )
END_CLASS
void hhAIInspector::Spawn() {
}
void hhAIInspector::Event_PostSpawn() {
const function_t* func = GetScriptFunction( "state_InspectorIdle" );
if( !func ) {
gameLocal.Warning( "unable to find state_InspectorIdle" );
return;
}
SetState( func );
}
void hhAIInspector::CheckReactions( idEntity* entity ) {
PostEventMS( &MA_CheckReactions, 1000, entity );
}
void hhAIInspector::Event_CheckReactions( idEntity* entity ) {
checkReaction = entity;
targetReaction.entity = entity;
targetReaction.reactionIndex = 0;
if( !targetReaction.GetReaction() ) {
targetReaction.reactionIndex = -1;
gameLocal.Warning( "unable to properly use requested reaction... possibly no reactions on this entity" );
return;
}
const function_t* func = GetScriptFunction( "state_InspectorReactions" );
if( !func ) {
gameLocal.Warning( "unable to find checkreaction state on inspector..." );
return;
}
SetState( func );
}
void hhAIInspector::RestartInspector( const char* inspectClass, idEntity* newReaction, idPlayer* starter ) {
float yaw;
idVec3 org;
idDict spawnDict;
idEntity* react_scout;
idEntity* use_reaction = NULL;
//Find our dictionary to use
const idDict* edict = gameLocal.FindEntityDefDict( inspectClass, false );
if( !edict ) {
gameLocal.Printf( "Unable to find entitfydef '%s'", inspectClass );
return;
}
spawnDict = *edict; //copy the values to an editable dict
//Setup values for position/orientation
yaw = starter->viewAngles.yaw;
org = starter->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 180 + idVec3( 0, 0, 1 );
// Set dictionary values...
spawnDict.Set( "angle", va("%f", yaw + 180) );
spawnDict.Set( "origin", org.ToString() );
spawnDict.Set( "spawnClass", "hhAIInspector" );
// Perform actual spawn
react_scout = static_cast<hhAIInspector*>(gameLocal.SpawnEntityType(hhAIInspector::Type, &spawnDict));
if( !react_scout ) {
gameLocal.Printf( "failed to spawn inspector monster..." );
return;
}
use_reaction = newReaction;
if( gameLocal.inspector != NULL ) {
//save our old use_reaction if we haven't specified one
if( !use_reaction && gameLocal.inspector.IsValid() && gameLocal.inspector->checkReaction.IsValid() ) {
use_reaction = gameLocal.inspector->checkReaction.GetEntity();
}
// Remove our old inspector
gameLocal.inspector->PostEventMS( &EV_Remove, 0 );
}
gameLocal.inspector = react_scout;
if( use_reaction ) {
gameLocal.inspector->CheckReactions( use_reaction );
}
}
void hhAIInspector::RestartInspector( idEntity* newReaction, idPlayer* starter ) {
idEntity* react_scout;
float yaw;
idVec3 org;
yaw = starter->viewAngles.yaw;
org = starter->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 180 + idVec3( 0, 0, 1 );
idDict dict = gameLocal.inspector.GetEntity()->spawnArgs;
dict.Set( "angle", va("%f", yaw + 180) );
dict.Set( "origin", org.ToString() );
react_scout = static_cast<hhAIInspector*>(gameLocal.SpawnEntityType(hhAIInspector::Type, &dict));
if( !react_scout ) {
gameLocal.Printf( "failed to spawn inspector monster..." );
return;
}
if( gameLocal.inspector != NULL ) {
gameLocal.inspector->PostEventMS( &EV_Remove, 0 );
}
gameLocal.inspector = react_scout;
gameLocal.inspector->CheckReactions( newReaction );
}
/*
=====================
hhAIInspector::Save
=====================
*/
void hhAIInspector::Save( idSaveGame *savefile ) const {
checkReaction.Save( savefile );
}
/*
=====================
hhAIInspector::Restore
=====================
*/
void hhAIInspector::Restore( idRestoreGame *savefile ) {
checkReaction.Restore( savefile );
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef __PREY_AI_INSPECTOR_H__
#define __PREY_AI_INSPECTOR_H__
class hhAIInspector : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhAIInspector);
public:
void Spawn();
void CheckReactions( idEntity* entity );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
static void RestartInspector( const char* inspectClass, idEntity* newReaction, idPlayer* starter );
static void RestartInspector( idEntity* newReaction, idPlayer* starter );
public:
// Events.
void Event_PostSpawn();
void Event_CheckReactions( idEntity* entity );
protected:
idEntityPtr<idEntity> checkReaction;
};
#endif
@@ -0,0 +1,662 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef AI_RemoveJetFx("removeJetFx");
const idEventDef AI_CreateJetFx("createJetFx");
const idEventDef AI_LaunchMine("launchMine", "d");
const idEventDef MA_IsEnemySniping("isEnemySniping", NULL, 'd');
const idEventDef MA_NumMines("numMines", NULL, 'f');
const idEventDef MA_DodgeProjectile("<dodgeProjectile>", "e");
const idEventDef MA_AppendHoverFx( "<appendHoverFx>", "e" );
CLASS_DECLARATION( hhMonsterAI, hhJetpackHarvesterSimple )
EVENT( AI_RemoveJetFx, hhJetpackHarvesterSimple::Event_RemoveJetFx )
EVENT( EV_Activate, hhJetpackHarvesterSimple::Event_Activate)
EVENT( AI_CreateJetFx, hhJetpackHarvesterSimple::Event_CreateJetFx )
EVENT( AI_LaunchMine, hhJetpackHarvesterSimple::Event_LaunchMine )
EVENT( EV_Broadcast_AppendFxToList, hhJetpackHarvesterSimple::Event_AppendToThrusterList )
EVENT( MA_OnProjectileLaunch, hhJetpackHarvesterSimple::Event_OnProjectileLaunch )
EVENT( MA_DodgeProjectile, hhJetpackHarvesterSimple::Event_DodgeProjectile )
EVENT( MA_IsEnemySniping, hhJetpackHarvesterSimple::Event_IsEnemySniping )
EVENT( MA_NumMines, hhJetpackHarvesterSimple::Event_NumMines )
EVENT( MA_AppendHoverFx, hhJetpackHarvesterSimple::Event_AppendHoverFx )
END_CLASS
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
hhJetpackHarvesterSimple::~hhJetpackHarvesterSimple() {
for ( int i=0;i<mineList.Num();i++ ) {
if ( mineList[i].IsValid() ) {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = mineList[i]->GetPhysics()->GetAxis();
collision.endpos = mineList[i]->GetPhysics()->GetOrigin();
collision.c.point = mineList[i]->GetPhysics()->GetOrigin();
collision.c.normal.Set( 0, 0, 1 );
mineList[i]->Explode( &collision, idVec3(0,0,0), 0 );
}
}
}
void hhJetpackHarvesterSimple::Spawn(void) {
lastAntiProjectileAttack = gameLocal.GetTime();
for(int i=0;i<ThrustSide_Total;i++) {
fxThrusters[ThrustType_Idle][i] = NULL;
fxThrusters[ThrustType_Hover][i] = NULL;
}
if ( !spawnArgs.GetBool( "bAlwaysGib" ) && !spawnArgs.GetBool( "no_pre_death" ) ) {
allowPreDeath = true;
} else {
allowPreDeath = false;
}
freezeDamage = false;
specialDamage = false;
Event_CreateJetFx();
//give random model offset
float offset_min = spawnArgs.GetFloat( "offset_z_min", "0" );
float offset_max = spawnArgs.GetFloat( "offset_z_max", "0" );
modelOffset.z = gameLocal.random.RandomFloat() * (offset_max-offset_min) + offset_min;
walkIK.Init( this, IK_ANIM, modelOffset );
}
void hhJetpackHarvesterSimple::Event_RemoveJetFx(void) {
for(int i=0;i<ThrustSide_Total;i++) {
SAFE_REMOVE(fxThrusters[ThrustType_Idle][i]);
SAFE_REMOVE(fxThrusters[ThrustType_Hover][i]);
}
}
void hhJetpackHarvesterSimple::Event_CreateJetFx(void) {
const char *bones[ThrustSide_Total];
bones[ThrustSide_Left] = spawnArgs.GetString( "bone_thrust_left" );
bones[ThrustSide_Right] = spawnArgs.GetString( "bone_thrust_right" );
const char *psystem = spawnArgs.GetString( "fx_thruster_idle" );
if ( psystem && *psystem ) {
for(int i=0;i<ThrustSide_Total;i++) {
hhFxInfo fxInfo;
idVec3 bonePos;
idMat3 boneAxis;
this->GetJointWorldTransform( bones[i], bonePos, boneAxis );
fxInfo.SetEntity( this );
fxInfo.SetBindBone( bones[i] );
fxInfo.Toggle();
fxInfo.RemoveWhenDone( false );
if( fxThrusters[ThrustType_Idle][i] != NULL ) {
gameLocal.Warning("Jetpack harvester already had flamejet made.");
}
BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList );
}
}
psystem = spawnArgs.GetString( "fx_thruster_hover" );
if ( psystem && *psystem ) {
for(int i=0;i<ThrustSide_Total;i++) {
hhFxInfo fxInfo;
idVec3 bonePos;
idMat3 boneAxis;
GetJointWorldTransform( bones[i], bonePos, boneAxis );
fxInfo.SetEntity( this );
fxInfo.SetBindBone( bones[i] );
fxInfo.Toggle();
fxInfo.RemoveWhenDone( false );
if( fxThrusters[ThrustType_Hover][i] != NULL ) {
gameLocal.Warning("Jetpack harvester already had flamejet made.");
}
BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &MA_AppendHoverFx );
}
}
}
void hhJetpackHarvesterSimple::Event_AppendToThrusterList( hhEntityFx* fx ) {
for( int ix = 0; ix < ThrustSide_Total; ++ix ) {
if( fxThrusters[ThrustType_Idle][ix] != NULL ) {
continue;
}
fxThrusters[ThrustType_Idle][ix] = fx;
return;
}
}
void hhJetpackHarvesterSimple::Event_AppendHoverFx( hhEntityFx* fx ) {
for( int ix = 0; ix < ThrustSide_Total; ++ix ) {
if( fxThrusters[ThrustType_Hover][ix] != NULL ) {
continue;
}
fxThrusters[ThrustType_Hover][ix] = fx;
return;
}
}
void hhJetpackHarvesterSimple::Event_LaunchMine( const idList<idStr>* parmList ) {
if( !parmList || parmList->Num() != 2 ) {
return;
}
idVec3 jointPos;
idMat3 jointAxis;
const char* projectileDefName = (*parmList)[0].c_str();
const char* jointName = (*parmList)[1].c_str();
if( !projectileDefName || !projectileDefName[0] ) {
return;
}
if( !GetJointWorldTransform(jointName, jointPos, jointAxis) ) {
return;
}
hhHarvesterMine* mine = static_cast<hhHarvesterMine*>( gameLocal.SpawnObject(projectileDefName) );
mine->Create( this, jointPos, jointAxis );
mine->Launch( jointPos, jointAxis, physicsObj.GetPushedLinearVelocity() );
}
void hhJetpackHarvesterSimple::Hide( void ) {
hhMonsterAI::Hide();
for(int i=0;i<ThrustSide_Total;i++) {
if(fxThrusters[ThrustType_Idle][i].GetEntity()) {
fxThrusters[ThrustType_Idle][i]->Nozzle(FALSE);
}
if(fxThrusters[ThrustType_Hover][i].GetEntity()) {
fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE);
}
}
}
void hhJetpackHarvesterSimple::Show( void ) {
hhMonsterAI::Show();
for(int i=0;i<ThrustSide_Total;i++) {
if(fxThrusters[ThrustType_Idle][i].GetEntity()) {
fxThrusters[ThrustType_Idle][i]->Nozzle(TRUE);
}
if(fxThrusters[ThrustType_Hover][i].GetEntity()) {
fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE);
}
}
}
idProjectile *hhJetpackHarvesterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
idVec3 muzzle;
idVec3 dir;
idVec3 start;
trace_t tr;
idBounds projBounds;
float distance;
const idClipModel *projClip;
float attack_accuracy;
float attack_cone;
float projectile_spread;
float diff;
float angle;
float spin;
idAngles ang;
int num_projectiles;
int i;
idMat3 axis;
idVec3 tmp;
idProjectile *lastProjectile;
//HUMANHEAD mdc - added to support multiple projectiles
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
int projIndex = FindProjectileInfo( desiredProjectileDef );
if( projIndex >= 0 ) {
SetCurrentProjectile( projIndex );
}
}
//HUMANHEAD END
if ( !projectileDef ) {
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
return NULL;
}
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
projectile_spread = desiredProjectileDef->GetFloat( "projectile_spread", "0" );
num_projectiles = desiredProjectileDef->GetInt( "num_projectiles", "1" );
idVec3 launch_velocity = vec3_zero;
if ( desiredProjectileDef->GetFloat( "launch_y", "0" ) > 0.0f ) {
if ( gameLocal.random.RandomFloat() < 0.5 ) {
launch_velocity.y = -desiredProjectileDef->GetFloat( "launch_y", "0" );
} else {
launch_velocity.y = desiredProjectileDef->GetFloat( "launch_y", "0" );
}
launch_velocity *= viewAxis;
}
GetMuzzle( jointname, muzzle, axis );
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, axis[ 0 ] );
}
lastProjectile = projectile.GetEntity();
if ( target != NULL ) {
tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
tmp.Normalize();
axis = tmp.ToMat3();
} else {
axis = viewAxis;
}
// rotate it because the cone points up by default
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
// make sure the projectile starts inside the monster bounding box
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
projClip = lastProjectile->GetPhysics()->GetClipModel();
projBounds = projClip->GetBounds().Rotate( axis );
// check if the owner bounds is bigger than the projectile bounds
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
start = muzzle + distance * viewAxis[ 0 ];
} else {
start = ownerBounds.GetCenter();
}
} else {
// projectile bounds bigger than the owner bounds, so just start it from the center
start = ownerBounds.GetCenter();
}
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
muzzle = tr.endpos;
// set aiming direction
GetAimDir( muzzle, target, this, dir );
ang = dir.ToAngles();
// adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
if ( clampToAttackCone ) {
// clamp the attack direction to be within monster's attack cone so he doesn't do
// things like throw the missile backwards if you're behind him
diff = idMath::AngleDelta( ang.yaw, current_yaw );
if ( diff > attack_cone ) {
ang.yaw = current_yaw + attack_cone;
} else if ( diff < -attack_cone ) {
ang.yaw = current_yaw - attack_cone;
}
}
axis = ang.ToMat3();
float spreadRad = DEG2RAD( projectile_spread );
for( i = 0; i < num_projectiles; i++ ) {
// spread the projectiles out
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
//dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir.Normalize();
// launch the projectile
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, dir );
}
lastProjectile = projectile.GetEntity();
lastProjectile->Launch( muzzle, dir, launch_velocity );
projectile = NULL;
if ( projectileDef->GetBool( "mine", "0" ) ) {
idEntityPtr<hhProjectile> &entityPtr = mineList.Alloc();
entityPtr = lastProjectile;
}
}
TriggerWeaponEffects( muzzle, axis );
lastAttackTime = gameLocal.time;
//HUMANHEAD mdc - added to support multiple projectiles
projectile = NULL;
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
//HUMANHEAD END
return lastProjectile;
}
void hhJetpackHarvesterSimple::AnimMove( void ) {
idVec3 goalPos;
idVec3 delta;
idVec3 goalDelta;
float goalDist;
monsterMoveResult_t moveResult;
idVec3 newDest;
idVec3 oldorigin = physicsObj.GetOrigin();
#ifdef HUMANHEAD //jsh wallwalk
idMat3 oldaxis = GetGravViewAxis();
#else
idMat3 oldaxis = viewAxis;
#endif
physicsObj.UseFlyMove( false );
AI_BLOCKED = false;
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
move.lastMoveOrigin.Zero();
move.lastMoveTime = gameLocal.time;
}
move.obstacle = NULL;
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
TurnToward( lastVisibleEnemyPos );
goalPos = oldorigin;
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
goalPos = oldorigin;
} else if ( GetMovePos( goalPos ) ) {
if ( move.moveCommand != MOVE_WANDER ) {
CheckObstacleAvoidance( goalPos, newDest );
TurnToward( newDest );
} else {
TurnToward( goalPos );
}
}
Turn();
if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
if ( gameLocal.time < move.startTime + move.duration ) {
goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
delta = goalPos - oldorigin;
delta.z = 0.0f;
} else {
delta = move.moveDest - oldorigin;
delta.z = 0.0f;
StopMove( MOVE_STATUS_DONE );
}
} else if ( allowMove ) {
#ifdef HUMANHEAD //jsh wallwalk
GetMoveDelta( oldaxis, GetGravViewAxis(), delta );
#else
GetMoveDelta( oldaxis, viewAxis, delta );
#endif
} else {
delta.Zero();
}
if ( move.moveCommand == MOVE_TO_POSITION ) {
goalDelta = move.moveDest - oldorigin;
goalDist = goalDelta.LengthFast();
if ( goalDist < delta.LengthFast() ) {
delta = goalDelta;
}
}
delta *= spawnArgs.GetFloat( "fly_scale", "1.0" );
physicsObj.SetDelta( delta );
physicsObj.ForceDeltaMove( disableGravity );
RunPhysics();
if ( ai_debugMove.GetBool() ) {
// HUMANHEAD JRM - so we can see if grav is on or off
if(disableGravity) {
gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 );
} else {
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
}
}
moveResult = physicsObj.GetMoveResult();
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
DirectDamage( attack, enemy.GetEntity() );
} else {
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
}
}
BlockedFailSafe();
AI_ONGROUND = physicsObj.OnGround();
idVec3 org = physicsObj.GetOrigin();
if ( oldorigin != org ) {
TouchTriggers();
}
if ( ai_debugMove.GetBool() ) {
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
DrawRoute();
}
}
void hhJetpackHarvesterSimple::Event_NumMines() {
//we only want valid mines, so remove dead ones
for ( int i=0;i<mineList.Num();i++ ) {
if ( !mineList[i].IsValid() || mineList[i].GetEntity()->health <= 0 ) {
mineList.RemoveIndex(i);
}
}
idThread::ReturnFloat( float(mineList.Num()) );
}
void hhJetpackHarvesterSimple::Event_DodgeProjectile( hhProjectile *proj ) {
if ( !proj || !enemy.IsValid() ) {
return;
}
float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" );
float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" );
float dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() );
if ( ai_debugBrain.GetBool() ) {
gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance );
}
if ( gameLocal.random.RandomFloat() > dodgeChance ) {
return;
}
idVec3 fw = viewAxis[0];
idVec3 projFw = proj->GetAxis()[0];
if(proj->GetOwner())
projFw = proj->GetOwner()->GetAxis()[0];
float dot = fw * projFw;
if(dot > -.7f)
return;
const function_t *newstate = NULL;
//determine which side to dodge to
idVec3 povPos, targetPos;
povPos = enemy->GetPhysics()->GetOrigin();
targetPos = GetPhysics()->GetOrigin();
idVec3 povToTarget = targetPos - povPos;
povToTarget.z = 0.f;
povToTarget.Normalize();
idVec3 povLeft, povUp;
povToTarget.OrthogonalBasis(povLeft, povUp);
povLeft.Normalize();
idVec3 projVel = proj->GetPhysics()->GetLinearVelocity();
projVel.Normalize();
dot = povLeft * projVel;
if ( dot < 0 ) {
newstate = GetScriptFunction( "state_DodgeRight" );
} else {
newstate = GetScriptFunction( "state_DodgeLeft" );
}
if ( newstate ) {
lastAntiProjectileAttack = gameLocal.GetTime();
SetState( newstate );
UpdateScript();
}
}
void hhJetpackHarvesterSimple::Event_OnProjectileLaunch( hhProjectile *proj ) {
if ( !proj || !spawnArgs.GetBool( "can_dodge", "1" ) || health <= 0 ) {
return;
}
if ( gameLocal.GetTime() - lastAntiProjectileAttack < 1000 * int(spawnArgs.GetFloat( "dodge_freq", "2.0" )) ) {
return;
}
if( proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)) ) {
return; // The person who launched this projectile wasn't someone to worry about
}
if ( !enemy.IsValid() ) {
return;
}
PostEventSec( &MA_DodgeProjectile, spawnArgs.GetFloat( "dodge_delay", "0.5" ), proj );
}
bool hhJetpackHarvesterSimple::Collide( const trace_t &collision, const idVec3 &velocity ) {
if ( af.IsActive() && !IsHidden() && !freezeDamage && !specialDamage ) {
const char *fx = spawnArgs.GetString("fx_death");
if ( fx && *fx ) {
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone(true);
BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo);
Hide();
PostEventMS(&EV_Remove, 1000);
}
}
return false;
}
void hhJetpackHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if (!frozen) {
// Being tractored can mess up predeath
fl.canBeTractored = false;
}
HandleNoGore();
if ( AI_DEAD ) {
AI_PAIN = true;
AI_DAMAGE = true;
return;
}
//stop rocket fx and sounds
StopSound( SND_CHANNEL_BODY3, false );
for(int i=0;i<ThrustSide_Total;i++) {
SAFE_REMOVE(fxThrusters[ThrustType_Idle][i]);
SAFE_REMOVE(fxThrusters[ThrustType_Hover][i]);
}
if ( !frozen && allowPreDeath && !AI_BOUND ) {
allowPreDeath = false;
trace_t TraceInfo;
gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() + GetPhysics()->GetGravityNormal() * 800, MASK_MONSTERSOLID, this);
if( TraceInfo.fraction < 1.0f ) {
if ( HasPathTo( TraceInfo.endpos ) ) {
Event_SetMoveType( MOVETYPE_ANIM );
health += 30;
state = GetScriptFunction( "state_StartPreDeath" );
SetState( state );
SetWaitState( "" );
return;
}
}
}
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
//explode creature's mines upon death
for ( int i=0;i<mineList.Num();i++ ) {
if ( mineList[i].IsValid() ) {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = mineList[i]->GetPhysics()->GetAxis();
collision.endpos = mineList[i]->GetPhysics()->GetOrigin();
collision.c.point = mineList[i]->GetPhysics()->GetOrigin();
collision.c.normal.Set( 0, 0, 1 );
mineList[i]->Explode( &collision, idVec3(0,0,0), 0 );
}
}
}
void hhJetpackHarvesterSimple::Event_IsEnemySniping(void) {
idThread::ReturnInt( false );
}
/*
=====================
hhJetpackHarvesterSimple::Save
=====================
*/
void hhJetpackHarvesterSimple::Save( idSaveGame *savefile ) const {
int i;
for ( i = 0; i < ThrustSide_Total; i++ ) {
for ( int j = 0; j < ThrustType_Total; j++ ) {
fxThrusters[i][j].Save( savefile );
}
}
savefile->WriteInt( lastAntiProjectileAttack );
savefile->WriteBool( allowPreDeath );
int num = mineList.Num();
savefile->WriteInt( num );
savefile->WriteBool( specialDamage );
for ( i = 0; i < num; i++ ) {
mineList[i].Save( savefile );
}
}
/*
=====================
hhJetpackHarvesterSimple::Restore
=====================
*/
void hhJetpackHarvesterSimple::Restore( idRestoreGame *savefile ) {
int i;
for ( i = 0; i < ThrustSide_Total; i++ ) {
for ( int j = 0; j < ThrustType_Total; j++ ) {
fxThrusters[i][j].Restore( savefile );
}
}
savefile->ReadInt( lastAntiProjectileAttack );
savefile->ReadBool( allowPreDeath );
int num;
savefile->ReadInt( num );
savefile->ReadBool( specialDamage );
mineList.SetNum( num );
for ( i = 0; i < num; i++ ) {
mineList[i].Restore( savefile );
}
}
void hhJetpackHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
if ( !AI_DEAD ) {
if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) {
freezeDamage = true;
allowPreDeath = false;
} else {
freezeDamage = false;
}
if ( damageDef && damageDef->GetBool( "no_special_death" ) ) {
specialDamage = true;
allowPreDeath = false;
}
}
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
@@ -0,0 +1,66 @@
#ifndef __PREY_AI_JETPACK_HARVESTER_SIMPLE_H__
#define __PREY_AI_JETPACK_HARVESTER_SIMPLE_H__
class hhJetpackHarvesterSimple : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhJetpackHarvesterSimple);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void Event_AppendHoverFx( hhEntityFx* fx ) {};
void Event_AppendToThrusterList( hhEntityFx* fx ) {};
void Event_RemoveJetFx(void) {};
void Event_CreateJetFx(void) {};
void Event_LaunchMine( const idList<idStr>* parmList ) {};
void Event_OnProjectileLaunch( hhProjectile *proj ) {};
void Event_DodgeProjectile( hhProjectile *proj ) {};
void Event_IsEnemySniping(void) {};
void Event_NumMines(void) {};
#else
protected:
enum ThrustSide
{
ThrustSide_Left,
ThrustSide_Right,
ThrustSide_Total
};
enum ThrustType
{
ThrustType_Idle = 0,
ThrustType_Hover,
ThrustType_Total
};
~hhJetpackHarvesterSimple();
void Spawn(void);
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void AnimMove( void );
void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
idProjectile* LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef );
void Event_AppendHoverFx( hhEntityFx* fx );
void Event_AppendToThrusterList( hhEntityFx* fx );
void Event_RemoveJetFx(void);
void Event_CreateJetFx(void);
void Event_LaunchMine( const idList<idStr>* parmList );
void Event_OnProjectileLaunch( hhProjectile *proj );
void Event_DodgeProjectile( hhProjectile *proj );
void Event_IsEnemySniping(void);
void Event_NumMines(void);
virtual void Hide( void );
virtual void Show( void );
virtual bool Collide( const trace_t &collision, const idVec3 &velocity );
void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
idEntityPtr<hhEntityFx> fxThrusters[ThrustType_Total][ThrustSide_Total];
int lastAntiProjectileAttack;
bool allowPreDeath;
idList< idEntityPtr<hhProjectile> > mineList;
bool freezeDamage;
bool specialDamage;
#endif
};
#endif
+694
View File
@@ -0,0 +1,694 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef MA_KeeperStartBlast("keeper_StartBlast", "e");
const idEventDef MA_KeeperEndBlast("keeper_EndBlast", NULL);
const idEventDef MA_KeeperUpdateTelepathicThrow("keeper_UpdateTelepathicThrow", NULL, NULL);
const idEventDef MA_KeeperTelepathicThrow("keeper_TelepathicThrow", NULL, NULL);
const idEventDef MA_KeeperStartTeleport("keeper_StartTeleport", NULL);
const idEventDef MA_KeeperEndTeleport("keeper_EndTeleport", NULL);
const idEventDef MA_KeeperTeleportExit("keeper_TeleportExit", NULL);
const idEventDef MA_KeeperCreatePortal("keeper_createPortal", NULL);
const idEventDef MA_KeeperEnableShield("keeper_enableShield", NULL);
const idEventDef MA_KeeperDisableShield("keeper_disableShield", NULL);
const idEventDef MA_KeeperAssignShieldFx( "<assignShieldFx>", "e" );
const idEventDef MA_KeeperTrigger("keeper_Trigger", NULL, NULL);
const idEventDef MA_KeeperTeleportEnter("keeper_TeleportEnter", NULL);
const idEventDef MA_KeeperStartHeadFx("keeper_StartHeadFx");
const idEventDef MA_KeeperEndHeadFx("keeper_EndHeadFx");
const idEventDef MA_KeeperAssignHeadFx( "<assignHeadFx>", "e" );
const idEventDef MA_KeeperGetThrowEntity( "keeper_GetThrowEntity", NULL, 'e' );
const idEventDef MA_KeeperGetTriggerEntity( "keeper_GetTriggerEntity", NULL, 'e' );
const idEventDef MA_KeeperTriggerEntity( "keeper_TriggerEntity", "e" );
const idEventDef MA_KeeperThrowEntity( "keeper_ThrowEntity", "e" );
const idEventDef MA_KeeperForceDisableShield("keeper_forceDisableShield", NULL);
CLASS_DECLARATION( hhMonsterAI, hhKeeperSimple )
EVENT( MA_KeeperStartBlast, hhKeeperSimple::Event_StartBlast )
EVENT( MA_KeeperEndBlast, hhKeeperSimple::Event_EndBlast )
EVENT( MA_KeeperUpdateTelepathicThrow, hhKeeperSimple::Event_UpdateTelepathicThrow )
EVENT( MA_KeeperTelepathicThrow, hhKeeperSimple::Event_TelepathicThrow )
EVENT( MA_KeeperStartTeleport, hhKeeperSimple::Event_StartTeleport )
EVENT( MA_KeeperEndTeleport, hhKeeperSimple::Event_EndTeleport )
EVENT( MA_KeeperTeleportExit, hhKeeperSimple::Event_TeleportExit )
EVENT( MA_KeeperCreatePortal, hhKeeperSimple::Event_CreatePortal )
EVENT( MA_KeeperEnableShield, hhKeeperSimple::Event_EnableShield )
EVENT( MA_KeeperDisableShield, hhKeeperSimple::Event_DisableShield )
EVENT( MA_KeeperAssignShieldFx, hhKeeperSimple::Event_AssignShieldFx )
EVENT( MA_KeeperTrigger, hhKeeperSimple::Event_KeeperTrigger )
EVENT( MA_KeeperTeleportEnter, hhKeeperSimple::Event_TeleportEnter )
EVENT( MA_KeeperStartHeadFx, hhKeeperSimple::Event_StartHeadFx )
EVENT( MA_KeeperEndHeadFx, hhKeeperSimple::Event_EndHeadFx )
EVENT( MA_KeeperAssignHeadFx, hhKeeperSimple::Event_AssignHeadFx )
EVENT( MA_KeeperTriggerEntity, hhKeeperSimple::Event_TriggerEntity )
EVENT( MA_KeeperThrowEntity, hhKeeperSimple::Event_ThrowEntity )
EVENT( MA_KeeperGetTriggerEntity, hhKeeperSimple::Event_GetTriggerEntity )
EVENT( MA_KeeperGetThrowEntity, hhKeeperSimple::Event_GetThrowEntity )
EVENT( MA_KeeperForceDisableShield, hhKeeperSimple::Event_ForceDisableShield )
END_CLASS
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
hhKeeperSimple::hhKeeperSimple() {
shieldAlpha = 0.0f;
}
hhKeeperSimple::~hhKeeperSimple() {
idEntity *ent = NULL;
if ( throwEntity.IsValid() ) {
ent = throwEntity.GetEntity();
} else if ( targetReaction.entity.IsValid() ) {
ent = targetReaction.entity.GetEntity();
} else {
return;
}
if ( ent && ent->IsType(hhMoveable::Type)) {
hhMoveable *throwMoveable = static_cast<hhMoveable*>(ent);
if ( throwMoveable ) {
throwMoveable->Event_Unhover();
}
}
}
void hhKeeperSimple::Spawn( void ) {
beamAttack = idEntityPtr<hhBeamSystem> ( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamAttack" ) ) );
if(beamAttack.IsValid()) {
beamAttack->MoveToJoint( this, spawnArgs.GetString("bone_beamAttack"));
beamAttack->BindToJoint( this, spawnArgs.GetString("bone_beamAttack"), false );
beamAttack->Activate(FALSE);
}
beamTelepathic = idEntityPtr<hhBeamSystem> (hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamTelepathic" ) ) );
if(beamTelepathic.IsValid()) {
beamTelepathic->MoveToJoint( this, spawnArgs.GetString("bone_beamTelepathic"));
beamTelepathic->BindToJoint( this, spawnArgs.GetString("bone_beamTelepathic"), false );
beamTelepathic->Activate(FALSE);
beamTelepathic->Hide();
}
if ( spawnArgs.GetInt( "always_shield", "0" ) && !spawnArgs.GetBool( "hide", "0" ) ) {
Event_EnableShield();
}
nextShieldImpact = gameLocal.time;
bThrowing = false;
}
void hhKeeperSimple::Think() {
PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
if (ai_skipThink.GetBool()) {
return;
}
idEntity *ent = NULL;
if ( throwEntity.IsValid() ) {
ent = throwEntity.GetEntity();
} else if ( targetReaction.entity.IsValid() ) {
ent = targetReaction.entity.GetEntity();
}
// Draw beam from our brain to the object we are controlling
if ( beamTelepathic.IsValid() ) {
if( bThrowing && ent && !beamTelepathic->IsHidden() && beamTelepathic->IsActivated() ) {
beamTelepathic->SetTargetLocation(ent->GetOrigin());
} else {
beamTelepathic->Activate( false );
beamTelepathic->Hide();
}
}
if ( shieldFx.IsValid() ) {
if ( AI_SHIELD ) {
//fade on shield
shieldAlpha += spawnArgs.GetFloat( "shield_delta", "0.05" );
if ( shieldAlpha > 1.0 ) {
shieldAlpha = 1.0f;
}
} else {
//fade off shield
shieldAlpha -= spawnArgs.GetFloat( "shield_delta", "0.05" );
if ( shieldAlpha < 0.0 ) {
shieldAlpha = 0.0f;
}
}
shieldFx->SetParticleShaderParm( SHADERPARM_TIME_OF_DEATH, shieldAlpha );
}
hhMonsterAI::Think();
if ( physicsObj.HasGroundContacts() && !physicsObj.HadGroundContacts() ) {
AI_LANDED = true;
}
}
void hhKeeperSimple::Event_StartBlast( idEntity *ent) {
if( beamAttack.IsValid() ) {
hhFxInfo fxInfo;
fxInfo.SetEntity( ent );
fxInfo.RemoveWhenDone( true );
BroadcastFxInfo( spawnArgs.GetString("fx_portalstart"), ent->GetOrigin() + (spawnArgs.GetVector( "portal_start_offset" ) * ent->GetAxis()), ent->GetAxis(), &fxInfo );
beamAttack->SetTargetLocation( ent->GetOrigin() + ent->GetAxis() * spawnArgs.GetVector( "telepathic_beam_offset" ) );
beamAttack->Activate(TRUE);
}
}
void hhKeeperSimple::Event_EndBlast() {
if( beamAttack.IsValid() ) {
beamAttack->Activate(FALSE);
}
}
void hhKeeperSimple::Event_UpdateTelepathicThrow() {
idEntity *ent = NULL;
if ( throwEntity.IsValid() ) {
ent = throwEntity.GetEntity();
} else if ( targetReaction.entity.IsValid() ) {
ent = targetReaction.entity.GetEntity();
} else {
return;
}
if ( !ent || !ent->IsType(hhMoveable::Type) ) {
return;
}
bThrowing = true;
if ( beamTelepathic.IsValid() ) {
if ( beamTelepathic->IsHidden() ) {
beamTelepathic->Show();
}
beamTelepathic->Activate( true );
}
hhMoveable *throwMoveable = static_cast<hhMoveable*>(ent);
idVec3 hoverToPos;
if(GetEnemy()) {
UpdateEnemyPosition();
idVec3 toEnemy = GetLastEnemyPos() - GetOrigin();
float dist = toEnemy.Normalize();
hoverToPos = GetOrigin() + toEnemy * dist * 0.25f;
hoverToPos.z = GetOrigin().z + EyeHeight() + 200.0f;
}
else {
hoverToPos = throwMoveable->GetOrigin() + idVec3(0.0f, 0.0f, 128.0f);
}
throwMoveable->AllowImpact( true );
throwMoveable->Event_HoverTo(hoverToPos);
throwMoveable->GetPhysics()->SetAngularVelocity(idVec3(10.0f, 10.0f, 10.0f));
// HUMANHEAD mdl: Beef up the damage on movables thrown by the keeper
throwMoveable->SetDamageDef( spawnArgs.GetString( "moveableDamageDef", "damage_movable20" ) );
PostEventMS(&MA_KeeperUpdateTelepathicThrow, 100);
}
void hhKeeperSimple::Event_TelepathicThrow() {
idEntity *ent = NULL;
bThrowing = false;
if ( beamTelepathic.IsValid() ) {
beamTelepathic->Hide();
}
if ( throwEntity.IsValid() ) {
ent = throwEntity.GetEntity();
} else if ( targetReaction.entity.IsValid() ) {
ent = targetReaction.entity.GetEntity();
} else {
return;
}
// Start the object hovering
if ( !ent || !ent->IsType(hhMoveable::Type) ) {
return;
}
targetReaction.entity.Clear();
CancelEvents( &MA_KeeperUpdateTelepathicThrow );
hhMoveable *throwMoveable = static_cast<hhMoveable*>(ent);
if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) {
throwMoveable->Event_Unhover();
}
// Throw it at an enemy if we have one
if(GetEnemy()) {
idVec3 toEnemy = GetEnemy()->GetAimPosition() - throwMoveable->GetOrigin();
toEnemy.Normalize();
throwMoveable->GetPhysics()->SetLinearVelocity(toEnemy * spawnArgs.GetFloat("telepathic_throw_velocity", "2000"));
}
}
void hhKeeperSimple::Event_TeleportExit() {
const function_t *newstate = NULL;
newstate = GetScriptFunction( "state_TeleportExit" );
if ( newstate ) {
SetState( newstate );
UpdateScript();
}
}
void hhKeeperSimple::Event_TeleportEnter() {
const function_t *newstate = NULL;
newstate = GetScriptFunction( "state_TeleportEnter" );
if ( newstate ) {
SetState( newstate );
UpdateScript();
}
}
void hhKeeperSimple::Event_StartTeleport() {
Hide();
}
void hhKeeperSimple::Event_EndTeleport() {
idVec3 newOrig;
idEntity *ent;
idList<idEntity*> nodeList;
float bestScore = -1;
float bestNodeIndex = -1;
// Find the best position to teleport to
// Find where we want to teleport to
idStr randomNode = spawnArgs.RandomPrefix( "teleport_info", gameLocal.random );
ent = gameLocal.FindEntity( randomNode );
if ( ent ) {
newOrig = ent->GetOrigin();
} else {
newOrig = GetOrigin();
gameLocal.Warning( "%s doesn't have any teleport infos", name.c_str() );
}
//teleport and face enemy
SetOrigin( newOrig );
if( GetEnemy() ) {
TurnToward(GetEnemy()->GetOrigin());
current_yaw = ideal_yaw;
Turn();
}
}
float hhKeeperSimple::GetTeleportDestRating(const idVec3 &pos) {
// Make sure this spot doesn't contain something else
idEntity *entList[MAX_GENTITIES];
idBounds myNewBnds = this->GetPhysics()->GetBounds();
myNewBnds = myNewBnds.Translate(pos);
myNewBnds.ExpandSelf(24.0f);
int numHit = gameLocal.clip.EntitiesTouchingBounds(myNewBnds, -1, entList, MAX_GENTITIES);
if(numHit > 0) {
for(int i=0;i<numHit;i++) {
if(entList[i] && !entList[i]->IsHidden() && (entList[i]->IsType(hhMoveable::Type) || entList[i]->IsType(idActor::Type))) {
return -1;
}
}
}
float w = gameLocal.random.RandomFloat();
return idMath::ClampFloat(-1.0f, 1.0f, w);
}
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
void hhKeeperSimple::LinkScriptVariables() {
hhMonsterAI::LinkScriptVariables();
LinkScriptVariable( AI_LANDED );
LinkScriptVariable( AI_SHIELD );
}
void hhKeeperSimple::Event_CreatePortal (void) {
static const char * passPrefix = "portal_";
const char * portalDef;
idEntity * portal;
idDict portalArgs;
idList<idStr> xferKeys;
idList<idStr> xferValues;
const idKeyValue *buddyKV = spawnArgs.FindKey( "portal_buddy" );
if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) {
// Case of a valid portal_buddy key, make a real portal
portalDef = spawnArgs.GetString( "def_portal" );
} else if ( spawnArgs.FindKey( "portal_cameraTarget" ) ) { // The monster portal has a camera target, so use a real portal instead of the fake portal
portalDef = spawnArgs.GetString( "def_portal" );
} else {
portalDef = spawnArgs.GetString( "def_fakeportal" );
}
if ( !portalDef || !portalDef[0] ) {
return;
}
// Set the origin of the portal to us.
portalArgs.SetVector( "origin", GetOrigin() );
portalArgs.Set( "rotation", spawnArgs.GetString( "rotation" ) );
portalArgs.Set( "remove_on_close", "1" );
// Pass along all 'portal_' keys to the portal's spawnArgs;
hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues );
for ( int i = 0; i < xferValues.Num(); ++i ) {
xferKeys[ i ].StripLeadingOnce( passPrefix );
portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() );
}
// Spawn the portal
portal = gameLocal.SpawnObject( portalDef, &portalArgs );
if ( !portal ) {
return;
}
// Move the portal up some pre determinted amt, since its origin is in the middle of it
trace_t tr;
gameLocal.clip.TracePoint(tr, GetOrigin(), GetOrigin() + idVec3(0,0,-200), MASK_MONSTERSOLID, this);
if ( tr.fraction < 1.0f ) {
portal->GetPhysics()->SetOrigin( tr.c.point + idVec3( 0,0,2 ) );
} else {
float offset = spawnArgs.GetFloat( "offset_portal", 0 );
portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) );
}
// Update the camera stuff
portal->ProcessEvent( &EV_UpdateCameraTarget );
// Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal
//? Should we always pass in the player?
portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() );
}
void hhKeeperSimple::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
if ( beamAttack.IsValid() ) {
SAFE_REMOVE( beamAttack );
}
if( beamTelepathic.IsValid() ) {
SAFE_REMOVE( beamTelepathic );
}
hhMoveable *throwMoveable = static_cast<hhMoveable*>(targetReaction.entity.GetEntity());
if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) {
targetReaction.entity.Clear();
throwMoveable->Event_Unhover();
}
if ( renderEntity.customSkin == NULL ) {
SetSkinByName( "skins/monsters/keeper_nolegs" );
}
}
void hhKeeperSimple::Event_EnableShield(void) {
if ( spawnArgs.GetInt( "never_shield", "0" ) ) {
return;
}
fl.applyDamageEffects = false;
AI_SHIELD = true;
if ( shieldFx.IsValid() ) {
SAFE_REMOVE( shieldFx );
}
hhFxInfo fxInfo;
fxInfo.SetEntity( this );
fxInfo.SetBindBone( "head" );
fxInfo.RemoveWhenDone( true );
BroadcastFxInfo( spawnArgs.GetString("fx_shield"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignShieldFx );
}
void hhKeeperSimple::Event_ForceDisableShield(void) {
fl.applyDamageEffects = true;
AI_SHIELD = false;
}
void hhKeeperSimple::Event_DisableShield(void) {
if ( spawnArgs.GetInt( "always_shield", "0" ) ) {
return;
}
fl.applyDamageEffects = true;
AI_SHIELD = false;
}
void hhKeeperSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int loc ) {
if ( AI_SHIELD ) {
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
if ( damageDef && damageDef->GetBool( "spirit_damage" ) ) {
hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc);
}
if ( inflictor ) {
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone( true );
if ( gameLocal.time >= nextShieldImpact ) {
nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000);
BroadcastFxInfoPrefixed( "fx_shield_impact", inflictor->GetOrigin() + GetAxis()*idVec3(50,0,0), mat3_identity, &fxInfo );
}
StartSound( "snd_shield_impact", SND_CHANNEL_BODY );
if ( inflictor->IsType( idProjectile::Type ) ) {
inflictor->PostEventMS( &EV_Remove, 0 );
}
}
} else {
hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc);
}
}
void hhKeeperSimple::Event_AssignShieldFx( hhEntityFx* fx ) {
shieldFx = fx;
}
void hhKeeperSimple::Event_StartHeadFx() {
hhFxInfo fxInfo;
fxInfo.SetEntity( this );
fxInfo.SetBindBone( spawnArgs.GetString( "bone_telepathic_fx", "head" ) );
fxInfo.RemoveWhenDone( true );
BroadcastFxInfo( spawnArgs.GetString("fx_telepathic"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignHeadFx );
}
void hhKeeperSimple::Event_EndHeadFx() {
SAFE_REMOVE( headFx );
}
void hhKeeperSimple::Event_AssignHeadFx( hhEntityFx* fx ) {
headFx = fx;
}
void hhKeeperSimple::Event_KeeperTrigger() {
if ( targetReaction.entity.IsValid() ) {
targetReaction.entity->ProcessEvent( &EV_Activate, this );
hhReaction *reaction = targetReaction.GetReaction();
if (reaction) {
reaction->active = false;
}
}
}
idProjectile *hhKeeperSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
idVec3 muzzle;
idVec3 dir;
idVec3 start;
trace_t tr;
idBounds projBounds;
float distance;
const idClipModel *projClip;
float attack_accuracy;
float attack_cone;
float projectile_spread;
float diff;
float angle;
float spin;
idAngles ang;
int num_projectiles;
int i;
idMat3 axis;
idVec3 tmp;
idProjectile *lastProjectile;
//HUMANHEAD mdc - added to support multiple projectiles
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
int projIndex = FindProjectileInfo( desiredProjectileDef );
if( projIndex >= 0 ) {
SetCurrentProjectile( projIndex );
}
}
//HUMANHEAD END
if ( !projectileDef ) {
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
return NULL;
}
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
GetMuzzle( jointname, muzzle, axis );
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, axis[ 0 ] );
}
lastProjectile = projectile.GetEntity();
if ( target != NULL ) {
tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
tmp.Normalize();
axis = tmp.ToMat3();
} else {
axis = viewAxis;
}
// rotate it because the cone points up by default
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
// make sure the projectile starts inside the monster bounding box
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
projClip = lastProjectile->GetPhysics()->GetClipModel();
projBounds = projClip->GetBounds().Rotate( axis );
// check if the owner bounds is bigger than the projectile bounds
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
start = muzzle + distance * viewAxis[ 0 ];
} else {
start = ownerBounds.GetCenter();
}
} else {
// projectile bounds bigger than the owner bounds, so just start it from the center
start = ownerBounds.GetCenter();
}
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
muzzle = tr.endpos;
// set aiming direction
GetAimDir( muzzle, target, this, dir );
ang = dir.ToAngles();
// adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
if ( clampToAttackCone ) {
// clamp the attack direction to be within monster's attack cone so he doesn't do
// things like throw the missile backwards if you're behind him
diff = idMath::AngleDelta( ang.yaw, current_yaw );
if ( diff > attack_cone ) {
ang.yaw = current_yaw + attack_cone;
} else if ( diff < -attack_cone ) {
ang.yaw = current_yaw - attack_cone;
}
}
axis = ang.ToMat3();
float spreadRad = DEG2RAD( projectile_spread );
for( i = 0; i < num_projectiles; i++ ) {
// spread the projectiles out
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
//dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir.Normalize();
// launch the projectile
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, dir );
}
lastProjectile = projectile.GetEntity();
lastProjectile->Launch( muzzle, dir, vec3_origin );
projectile = NULL;
}
TriggerWeaponEffects( muzzle, axis );
lastAttackTime = gameLocal.time;
//HUMANHEAD mdc - added to support multiple projectiles
projectile = NULL;
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
//HUMANHEAD END
return lastProjectile;
}
void hhKeeperSimple::Event_GetTriggerEntity() {
idThread::ReturnEntity( triggerEntity.GetEntity() );
}
void hhKeeperSimple::Event_TriggerEntity( idEntity *ent ) {
if ( !ent ) {
return;
}
triggerEntity = ent;
const function_t *newstate = NULL;
newstate = GetScriptFunction( "state_TriggerEntity" );
if ( newstate ) {
SetState( newstate );
UpdateScript();
}
}
void hhKeeperSimple::Event_GetThrowEntity() {
idThread::ReturnEntity( throwEntity.GetEntity() );
}
void hhKeeperSimple::Event_ThrowEntity( idEntity *ent ) {
if ( !ent || !ent->IsType( idMoveable::Type ) ) {
return;
}
throwEntity = ent;
const function_t *newstate = NULL;
newstate = GetScriptFunction( "state_ThrowEntity" );
if ( newstate ) {
SetState( newstate );
UpdateScript();
}
}
void hhKeeperSimple::Hide() {
Event_DisableShield();
hhMonsterAI::Hide();
}
void hhKeeperSimple::Show() {
hhMonsterAI::Show();
if ( spawnArgs.GetInt( "always_shield", "0" ) ) {
Event_EnableShield();
}
}
void hhKeeperSimple::HideNoDormant() {
//overridden to hide shield fx
if ( shieldFx.IsValid() ) {
shieldFx->Hide();
}
idAI::Hide();
}
/*
=====================
hhKeeperSimple::Save
=====================
*/
void hhKeeperSimple::Save( idSaveGame *savefile ) const {
beamTelepathic.Save( savefile );
beamAttack.Save( savefile );
shieldFx.Save( savefile );
headFx.Save( savefile );
triggerEntity.Save( savefile );
throwEntity.Save( savefile );
savefile->WriteInt( nextShieldImpact );
savefile->WriteBool( bThrowing );
}
/*
=====================
hhKeeperSimple::Restore
=====================
*/
void hhKeeperSimple::Restore( idRestoreGame *savefile ) {
beamTelepathic.Restore( savefile );
beamAttack.Restore( savefile );
shieldFx.Restore( savefile );
headFx.Restore( savefile );
triggerEntity.Restore( savefile );
throwEntity.Restore( savefile );
savefile->ReadInt( nextShieldImpact );
savefile->ReadBool( bThrowing );
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+98
View File
@@ -0,0 +1,98 @@
#ifndef __PREY_AI_KEEPER_SIMPLE_H__
#define __PREY_AI_KEEPER_SIMPLE_H__
class hhKeeperSimple : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhKeeperSimple);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void Event_GetTriggerEntity() {};
void Event_TriggerEntity( idEntity *ent ) {};
void Event_GetThrowEntity() {};
void Event_ThrowEntity( idEntity *ent ) {};
void Event_StartBlast( idEntity *ent ) {};
void Event_EndBlast() {};
void Event_BeginTelepathicThrow(idEntity *throwMe) {};
void Event_UpdateTelepathicThrow() {};
void Event_TelepathicThrow() {};
void Event_StartTeleport() {};
void Event_EndTeleport() {};
void Event_TeleportExit() {};
void Event_TeleportEnter() {};
void Event_CreatePortal() {};
void Event_EnableShield() {};
void Event_DisableShield() {};
void Event_ForceDisableShield() {};
void Event_AssignShieldFx( hhEntityFx* fx ) {};
void Event_StartHeadFx() {};
void Event_EndHeadFx() {};
void Event_AssignHeadFx( hhEntityFx* fx ) {};
void Event_KeeperTrigger() {};
#else
hhKeeperSimple();
~hhKeeperSimple();
void Spawn( void );
virtual void Think();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void LinkScriptVariables();
virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
idProjectile* LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef );
void Hide();
void Show();
void HideNoDormant();
idScriptBool AI_LANDED;
idScriptBool AI_SHIELD;
protected:
void Event_GetTriggerEntity();
void Event_TriggerEntity( idEntity *ent );
void Event_GetThrowEntity();
void Event_ThrowEntity( idEntity *ent );
// Attack blast
void Event_StartBlast( idEntity *ent );
void Event_EndBlast();
// Telepathic Throwing
void Event_BeginTelepathicThrow(idEntity *throwMe);
void Event_UpdateTelepathicThrow();
void Event_TelepathicThrow();
// Teleporting
void Event_StartTeleport();
void Event_EndTeleport();
void Event_TeleportExit();
void Event_TeleportEnter();
void Event_CreatePortal();
// Shield
void Event_EnableShield();
void Event_DisableShield();
void Event_ForceDisableShield();
void Event_AssignShieldFx( hhEntityFx* fx );
void Event_StartHeadFx();
void Event_EndHeadFx();
void Event_AssignHeadFx( hhEntityFx* fx );
// Telepathic Triggering
void Event_KeeperTrigger();
float GetTeleportDestRating(const idVec3 &pos);
idEntityPtr<hhBeamSystem> beamTelepathic; // Beam used when we are telepathically controlling an object
idEntityPtr<hhBeamSystem> beamAttack;
idEntityPtr<hhEntityFx> shieldFx;
idEntityPtr<hhEntityFx> headFx;
idEntityPtr<idEntity> triggerEntity;
idEntityPtr<idEntity> throwEntity;
int nextShieldImpact;
float shieldAlpha;
bool bThrowing;
#endif
};
#endif
+63
View File
@@ -0,0 +1,63 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
CLASS_DECLARATION(hhMonsterAI, hhMutate)
EVENT( MA_OnProjectileLaunch, hhMutate::Event_OnProjectileLaunch )
END_CLASS
void hhMutate::Event_OnProjectileLaunch(hhProjectile *proj) {
const function_t *newstate = NULL;
if ( !enemy.IsValid() || !AI_COMBAT ) {
return;
}
float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" );
float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" );
float dodgeChance = 0.6f;
dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() );
if ( ai_debugBrain.GetBool() ) {
gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance );
}
if ( gameLocal.random.RandomFloat() > dodgeChance ) {
return;
}
//determine which side to dodge to
idVec3 povPos, targetPos;
povPos = enemy->GetPhysics()->GetOrigin();
targetPos = GetPhysics()->GetOrigin();
idVec3 povToTarget = targetPos - povPos;
povToTarget.z = 0.f;
povToTarget.Normalize();
idVec3 povLeft, povUp;
povToTarget.OrthogonalBasis(povLeft, povUp);
povLeft.Normalize();
idVec3 projVel = proj->GetPhysics()->GetLinearVelocity();
projVel.Normalize();
float dot = povLeft * projVel;
if ( dot < 0 ) {
newstate = GetScriptFunction( "state_DodgeBackRight" );
} else {
newstate = GetScriptFunction( "state_DodgeBackLeft" );
}
if ( newstate ) {
SetState( newstate );
UpdateScript();
}
}
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
void hhMutate::LinkScriptVariables(void) {
hhMonsterAI::LinkScriptVariables();
LinkScriptVariable( AI_COMBAT );
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+15
View File
@@ -0,0 +1,15 @@
#ifndef __PREY_AI_MUTATE_H__
#define __PREY_AI_MUTATE_H__
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
class hhMutate : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhMutate);
protected:
void Event_OnProjectileLaunch(hhProjectile *proj);
void LinkScriptVariables();
idScriptBool AI_COMBAT;
};
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
#endif
+390
View File
@@ -0,0 +1,390 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef MH_AlertFriends("alertFriends", NULL, NULL);
const idEventDef MH_DropBinds("dropBinds");
const idEventDef MH_DropProjectiles("<dropProjectiles>" );
CLASS_DECLARATION(hhMonsterAI, hhMutilatedHuman)
EVENT( MH_AlertFriends, hhMutilatedHuman::Event_AlertFriends )
EVENT( MH_DropBinds, hhMutilatedHuman::Event_DropBinds )
EVENT( MH_DropProjectiles, hhMutilatedHuman::Event_DropProjectiles )
END_CLASS
void hhMutilatedHuman::Spawn() {
damageFlag = 0;
}
void hhMutilatedHuman::Event_DropBinds( ) {
idEntity *ent;
idEntity *next;
for( ent = teamChain; ent != NULL; ent = next ) {
next = ent->GetTeamChain();
if ( ent && !ent->IsType( hhProjectile::Type ) && !ent->IsType( idEntityFx::Type ) && ent->GetBindMaster() == this && ent != head.GetEntity() ) {
ent->Unbind();
next = teamChain;
}
}
}
void hhMutilatedHuman::Event_DropProjectiles() {
idEntity *ent;
idEntity *next;
for( ent = teamChain; ent != NULL; ent = next ) {
next = ent->GetTeamChain();
if ( ent && ent->IsType( hhProjectile::Type ) ) {
ent->Unbind();
ent->Hide();
ent->PostEventSec( &EV_Remove, 5 );
next = teamChain;
}
}
}
void hhMutilatedHuman::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
if (!fl.takedamage) {
return;
}
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
if ( AI_LIMB_FELL ) {
PostEventSec( &MH_DropProjectiles, 0.1f );
}
if ( AI_DEAD ) {
return;
}
idDict args;
idVec3 bonePos;
idMat3 boneAxis;
idStr debrisName;
idStr damageGroup = GetDamageGroup( location );
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
if ( damageDef && damageDef->GetBool( "ice" ) ) {
return;
}
if ( !( damageGroup == "head" && damageDef->GetInt( "damage" ) >= spawnArgs.GetFloat( "head_falloff_damage", "70" ) ) ) {
if ( gameLocal.random.RandomFloat() >= spawnArgs.GetFloat( "chanceLimbsWillFallOff", "0.1" ) ) { // CJR: Build in a chance that the limbs will fall off
return;
}
}
if ( damageGroup == "left_arm" && !(damageFlag & BIT(0)) ) {
damageFlag |= BIT(0);
debrisName = spawnArgs.GetString( "def_debris_arm" );
if ( !debrisName.IsEmpty() ) {
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
if ( debris ) {
AI_LIMB_FELL = true;
GetJointWorldTransform( spawnArgs.GetString( "bone_arm_left" ), bonePos, boneAxis );
debris->SetOrigin( bonePos );
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() );
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_left_arm_blood"), spawnArgs.GetString("bone_arm_left") );
}
}
} else if ( damageGroup == "right_arm" && !(damageFlag & BIT(1)) ) {
damageFlag |= BIT(1);
debrisName = spawnArgs.GetString( "def_debris_arm" );
if ( !debrisName.IsEmpty() ) {
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
if ( debris ) {
AI_LIMB_FELL = true;
GetJointWorldTransform( spawnArgs.GetString( "bone_arm_right" ), bonePos, boneAxis );
debris->SetOrigin( bonePos );
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() );
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_right_arm_blood"), spawnArgs.GetString("bone_arm_right") );
}
}
} else if ( damageGroup == "chest" && !(damageFlag & BIT(2)) ) {
damageFlag |= BIT(2);
damageFlag |= BIT(3);
debrisName = spawnArgs.GetString( "def_debris_chest" );
if ( !debrisName.IsEmpty() ) {
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
if ( debris ) {
AI_LIMB_FELL = true;
GetJointWorldTransform( spawnArgs.GetString( "bone_chest" ), bonePos, boneAxis );
debris->SetOrigin( bonePos );
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_chest_velocity" ) * GetAxis() );
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_chest_blood"), spawnArgs.GetString("bone_chest") );
}
}
} else if ( damageGroup == "head" ) {
if ( head.IsValid() && !head->IsHidden() ) {
debrisName = spawnArgs.GetString( "def_debris_head" );
if ( !debrisName.IsEmpty() ) {
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
if ( debris ) {
AI_LIMB_FELL = true;
GetJointWorldTransform( spawnArgs.GetString( "bone_head" ), bonePos, boneAxis );
debris->SetOrigin( bonePos );
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_head_velocity" ) * GetAxis() );
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_head_blood"), spawnArgs.GetString("bone_head") );
}
}
head->Hide();
}
}
idStr skinName = spawnArgs.GetString("damage_skin");
skinName += damageFlag;
if ( damageFlag > 0 ) {
SetSkin( declManager->FindSkin( skinName ) );
UpdateVisuals();
}
}
void hhMutilatedHuman::Event_AlertFriends() {
hhMonsterAI *check;
if ( !enemy.IsValid() ) {
return;
}
for( int i = 0; i < targets.Num(); i++ ) {
check = static_cast<hhMonsterAI *>(targets[ i ].GetEntity());
if ( !check || check->IsHidden() || !check->IsType( hhMutilatedHuman::Type ) ) {
continue;
}
check->SetEnemy( enemy.GetEntity() );
}
}
void hhMutilatedHuman::AnimMove( void ) {
//overridden to set enemy if blockedEnt can be an enemy
idVec3 goalPos;
idVec3 delta;
idVec3 goalDelta;
float goalDist;
monsterMoveResult_t moveResult;
idVec3 newDest;
idVec3 oldorigin = physicsObj.GetOrigin();
#ifdef HUMANHEAD //jsh wallwalk
idMat3 oldaxis = GetGravViewAxis();
#else
idMat3 oldaxis = viewAxis;
#endif
AI_BLOCKED = false;
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
move.lastMoveOrigin.Zero();
move.lastMoveTime = gameLocal.time;
}
move.obstacle = NULL;
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
TurnToward( lastVisibleEnemyPos );
goalPos = oldorigin;
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
goalPos = oldorigin;
} else if ( GetMovePos( goalPos ) ) {
if ( move.moveCommand != MOVE_WANDER ) {
CheckObstacleAvoidance( goalPos, newDest );
TurnToward( newDest );
} else {
TurnToward( goalPos );
}
}
Turn();
if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
if ( gameLocal.time < move.startTime + move.duration ) {
goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
delta = goalPos - oldorigin;
delta.z = 0.0f;
} else {
delta = move.moveDest - oldorigin;
delta.z = 0.0f;
StopMove( MOVE_STATUS_DONE );
}
} else if ( allowMove ) {
#ifdef HUMANHEAD //jsh wallwalk
GetMoveDelta( oldaxis, GetGravViewAxis(), delta );
#else
GetMoveDelta( oldaxis, viewAxis, delta );
#endif
} else {
delta.Zero();
}
if ( move.moveCommand == MOVE_TO_POSITION ) {
goalDelta = move.moveDest - oldorigin;
goalDist = goalDelta.LengthFast();
if ( goalDist < delta.LengthFast() ) {
delta = goalDelta;
}
}
physicsObj.SetDelta( delta );
physicsObj.ForceDeltaMove( disableGravity );
RunPhysics();
if ( ai_debugMove.GetBool() ) {
// HUMANHEAD JRM - so we can see if grav is on or off
if(disableGravity) {
gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 );
} else {
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
}
}
moveResult = physicsObj.GetMoveResult();
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
DirectDamage( attack, enemy.GetEntity() );
} else {
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
if ( blockEnt && blockEnt->IsType( idActor::Type ) && ReactionTo( blockEnt ) != ATTACK_IGNORE ) {
SetEnemy( static_cast<idActor*>(blockEnt) );
}
if ( blockEnt && blockEnt->IsType( hhPod::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
blockEnt->Damage( this, this, vec3_zero, spawnArgs.GetString( "kick_damage" ), 1.0f, INVALID_JOINT );
}
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
}
}
BlockedFailSafe();
AI_ONGROUND = physicsObj.OnGround();
idVec3 org = physicsObj.GetOrigin();
if ( oldorigin != org ) {
TouchTriggers();
}
if ( ai_debugMove.GetBool() ) {
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
DrawRoute();
}
}
int hhMutilatedHuman::ReactionTo( const idEntity *ent ) {
const idActor *actor = static_cast<const idActor *>( ent );
if( actor && actor->IsType(hhDeathProxy::Type) ) {
return ATTACK_IGNORE;
}
if ( ent->health <= 0 ) {
return ATTACK_IGNORE;
}
if ( ent->fl.hidden ) {
// ignore hidden entities
return ATTACK_IGNORE;
}
if ( !ent->IsType( idActor::Type ) ) {
return ATTACK_IGNORE;
}
actor = static_cast<const idActor *>( ent );
if ( actor->IsType( idPlayer::Type ) && static_cast<const idPlayer *>(actor)->noclip ) {
// ignore players in noclip mode
return ATTACK_IGNORE;
}
//only attack spiritwalking players if they hurt me
if ( ent->IsType( hhPlayer::Type ) ) {
const hhPlayer *player = static_cast<const hhPlayer*>( ent );
if ( nextSpiritProxyCheck == 0 && player && player->IsSpiritWalking() ) {
nextSpiritProxyCheck = gameLocal.time + SEC2MS(2);
return ATTACK_ON_DAMAGE;
}
}
if ( ent->IsType( hhSpiritProxy::Type ) ) {
if ( gameLocal.time > nextSpiritProxyCheck ) {
nextSpiritProxyCheck = 0;
//attack spiritproxy on sight if we have no enemy or if its closer than our current enemy
if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) {
float distToEnemy = (enemy->GetOrigin() - GetOrigin()).LengthSqr();
float distToProxy = (ent->GetOrigin() - GetOrigin()).LengthSqr();
if ( distToProxy < distToEnemy ) {
return ATTACK_ON_SIGHT;
}
} else {
return ATTACK_ON_SIGHT;
}
} else {
return ATTACK_IGNORE;
}
}
if ( ent && ent->spawnArgs.GetBool( "never_target" ) ) {
return ATTACK_IGNORE;
}
//if we're hurt, its probably because of the player, so keep him as an enemy
if ( actor->IsType( idPlayer::Type ) && health < spawnHealth ) {
return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE | ATTACK_ON_SIGHT;
}
// actors on different teams will always fight each other
if ( actor->team != team ) {
if ( actor->fl.notarget ) {
// don't attack on sight when attacker is notargeted
if ( actor->IsType( idPlayer::Type ) ) {
return ATTACK_ON_DAMAGE;
} else {
return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
}
}
//force players to only alert through damage
if ( actor->IsType( idPlayer::Type ) ) {
return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
} else if ( actor->IsType( idAI::Type ) ) {
//only allow ai actors to otherwise alert them
return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
}
}
// monsters will fight when attacked by lower ranked monsters. rank 0 never fights back.
if ( rank && ( actor->rank < rank ) ) {
return ATTACK_ON_DAMAGE;
}
// don't fight back
return ATTACK_IGNORE;
}
/*
=====================
hhMutilatedHuman::Save
=====================
*/
void hhMutilatedHuman::Save( idSaveGame *savefile ) const {
savefile->WriteInt( damageFlag );
}
/*
=====================
hhMutilatedHuman::Restore
=====================
*/
void hhMutilatedHuman::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( damageFlag );
}
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
void hhMutilatedHuman::LinkScriptVariables(void) {
hhMonsterAI::LinkScriptVariables();
LinkScriptVariable( AI_LIMB_FELL );
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef __PREY_AI_MUTILATEDHUMAN_H__
#define __PREY_AI_MUTILATEDHUMAN_H__
class hhMutilatedHuman : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhMutilatedHuman);
void Event_AlertFriends();
void Event_DropBinds();
void Event_DropProjectiles();
int ReactionTo( const idEntity *ent );
void Spawn();
void AnimMove();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
void LinkScriptVariables();
protected:
idScriptBool AI_LIMB_FELL;
int damageFlag;
};
#endif //__PREY_AI_MUTILATEDHUMAN_H__
+144
View File
@@ -0,0 +1,144 @@
//
// ai_passageway.cpp
//
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_EnablePassageway("enable", NULL);
const idEventDef EV_DisablePassageway("disable", NULL);
// hhAIPassageway
CLASS_DECLARATION(hhAnimated, hhAIPassageway)
EVENT(EV_AnimDone, hhAIPassageway::Event_AnimDone)
EVENT(EV_Activate, hhAIPassageway::Event_Trigger)
EVENT(EV_PostSpawn, hhAIPassageway::PostSpawn)
EVENT(EV_EnablePassageway, hhAIPassageway::Event_EnablePassageway)
EVENT(EV_DisablePassageway, hhAIPassageway::Event_DisablePassageway)
END_CLASS
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
//
// Spawn()
//
void hhAIPassageway::Spawn() {
timeLastEntered = 0;
lastEntered = NULL;
enabled = TRUE;
hasEntered = FALSE;
GetPhysics()->SetContents(CONTENTS_SOLID);
PostEventMS( &EV_PostSpawn, 0 );
}
//
// PostSpawn()
//
void hhAIPassageway::PostSpawn() {
SetEnablePassageway(spawnArgs.GetBool("enabled", "1"));
}
//
// SetEnablePassageway()
//
void hhAIPassageway::SetEnablePassageway(bool tf) {
// Ignore if we're already in that state
if(enabled == tf)
return;
fl.refreshReactions = tf;
enabled = tf;
}
//
// Event_AnimDone()
//
void hhAIPassageway::Event_AnimDone( int animIndex ) {
//When we are done we want to pass ourselves as the activator
activator = this;
hhAnimated::Event_AnimDone( animIndex );
}
//
// Event_Trigger()
//
void hhAIPassageway::Event_Trigger( idEntity *activator ) {
if(!activator)
return;
// Ignore being activated by another passage node - they link to eachother,
// so they must be ignored otherwise it will continuously trigger
if( activator->IsType(hhAIPassageway::Type) ) {
return;
}
// Play our anim
hhAnimated::Event_Activate( activator );
HH_ASSERT(anim != NULL);
if( !activator->IsType(hhMonsterAI::Type) ) {
return;
}
hhMonsterAI *ai = static_cast<hhMonsterAI*>(activator);
// Can't enter if dead
if(ai->health <= 0)
return;
if(!ai->IsInsidePassageway())
{
//HUMANHEAD PCF mdl 04/29/06 - Added lastEntered check
if(hasEntered && lastEntered.GetEntity() == ai) {
ai->ProcessEvent(&MA_EnterPassageway, this);
hasEntered = FALSE;
} else {
lastEntered = ai;
timeLastEntered = gameLocal.GetTime();
hasEntered = TRUE;
}
}
else
{
ai->PostEventMS(&MA_ExitPassageway, 32, this);
hasEntered = FALSE;
}
}
//
// GetExitPos()
//
idVec3 hhAIPassageway::GetExitPos(void) {
idVec3 pos = GetOrigin();
idVec3 exitOffset;
spawnArgs.GetVector("exit_offset", "0 0 0", exitOffset);
exitOffset *= GetAxis();
pos += exitOffset;
pos.z += 0.2f;
return pos;
}
void hhAIPassageway::Save(idSaveGame *savefile) const {
savefile->WriteInt( timeLastEntered - gameLocal.time );
lastEntered.Save( savefile );
savefile->WriteBool( hasEntered );
savefile->WriteBool( enabled );
}
void hhAIPassageway::Restore(idRestoreGame *savefile) {
savefile->ReadInt( timeLastEntered );
timeLastEntered += gameLocal.time;
lastEntered.Restore( savefile );
savefile->ReadBool( hasEntered );
savefile->ReadBool( enabled );
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+49
View File
@@ -0,0 +1,49 @@
#ifndef __PREY_AI_PASSAGENODE_H__
#define __PREY_AI_PASSAGENODE_H__
//
// hhAIPassageway
//
class hhAIPassageway : public hhAnimated
{
public:
CLASS_PROTOTYPE(hhAIPassageway);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
virtual void PostSpawn() {};
void SetEnablePassageway(bool tf) {};
void Event_AnimDone( int animIndex ) {};
void Event_Trigger( idEntity *activator ) {};
void Event_EnablePassageway(void) {}
void Event_DisablePassageway(void) {}
#else
hhAIPassageway() {timeLastEntered = 0; lastEntered = NULL;}
virtual void Spawn();
virtual void PostSpawn();
void Save(idSaveGame *savefile) const;
void Restore(idRestoreGame *savefile);
idVec3 GetExitPos(void); // The point that a monster should be spawned upon exiting this passageway
void SetEnablePassageway(bool tf);
bool IsPassagewayEnabled(void) const {return enabled;}
int timeLastEntered; // The time someone last entered this passage node
idEntityPtr<idAI> lastEntered; // The ai that last entered this passage node
bool hasEntered; // FALSE if the AI has not fully entered yet - waiting for one more trigger
protected:
void Event_AnimDone( int animIndex );
void Event_Trigger( idEntity *activator );
void Event_EnablePassageway(void) {SetEnablePassageway(TRUE);}
void Event_DisablePassageway(void) {SetEnablePassageway(FALSE);}
bool enabled; // TRUE if we are a valid passagway
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif
+120
View File
@@ -0,0 +1,120 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
CLASS_DECLARATION( hhMonsterAI, hhPossessedTommy )
END_CLASS
// TODO: Set up a rhythmic damaging of this entity.
void hhPossessedTommy::Spawn() {
physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL );
nextDrop = 0;
}
void hhPossessedTommy::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if ( attacker->IsType( hhPlayer::Type ) ) { // Being attacked by a player, must be a spiritwalking player
if ( possessedProxy.IsValid() ) {
hhSpiritProxy *proxy = possessedProxy.GetEntity();
Hide();
proxy->SetOrigin( GetOrigin() );
proxy->SetAxis( GetAxis() );
proxy->Show();
proxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL );
if ( proxy->GetPlayer() ) {
proxy->GetPlayer()->Unpossess();
}
}
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
PostEventMS( &EV_Remove, 0 );
return;
}
// OTHERWISE, KILL THIS ENTITY AND KILL THE PLAYER -- create a death proxy here, etc
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
// Kill the player
if (possessedProxy.IsValid() ) {
if ( possessedProxy->GetPlayer() ) {
possessedProxy->GetPlayer()->PossessKilled();
}
}
}
void hhPossessedTommy::Event_Remove(void) {
if (!fl.hidden && possessedProxy.IsValid()) {
Hide();
hhSpiritProxy *proxy = possessedProxy.GetEntity();
proxy->SetOrigin( GetOrigin() );
proxy->SetAxis( GetAxis() );
proxy->Show();
proxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL );
if ( proxy->GetPlayer() ) {
proxy->GetPlayer()->Unpossess();
}
}
hhMonsterAI::Event_Remove();
}
void hhPossessedTommy::Think(void) {
PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
if (ai_skipThink.GetBool()) {
return;
}
hhMonsterAI::Think();
if (possessedProxy.IsValid() && gameLocal.time > nextDrop) {
hhPlayer *player = possessedProxy->GetPlayer();
if (player) {
player->SetHealth(player->GetHealth() - 1);
if (player->GetHealth() <= 0) {
Hide();
possessedProxy->SetOrigin( GetOrigin() );
possessedProxy->SetAxis( GetAxis() );
possessedProxy->Show();
possessedProxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL );
player->Unpossess();
player->Killed(this, this, 1, vec3_origin, INVALID_JOINT);
PostEventMS(&EV_Remove, 0);
}
}
float min = spawnArgs.GetFloat( "dda_drain_min" );
float max = spawnArgs.GetFloat( "dda_drain_max" );
nextDrop = gameLocal.time + min + (max - min) * (1.0f - gameLocal.GetDDAValue());
}
}
void hhPossessedTommy::Save( idSaveGame *savefile ) const {
possessedProxy.Save( savefile );
}
void hhPossessedTommy::Restore( idRestoreGame *savefile ) {
possessedProxy.Restore( savefile );
nextDrop = 0;
}
void hhPossessedTommy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
// Turn to a different direction
float adjust = 45.0f + (gameLocal.random.RandomFloat() * 30.0f);
if (gameLocal.random.RandomInt(100) > 50) {
adjust = -adjust;
}
gameLocal.Printf("Adjusting by %f\n", adjust);
TurnToward(current_yaw + adjust);
move.nextWanderTime = 0;
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef __PREY_AI_POSSESSED_TOMMY_H__
#define __PREY_AI_POSSESSED_TOMMY_H__
/***********************************************************************
hhPossessedTommy.
***********************************************************************/
class hhPossessedTommy : public hhMonsterAI {
public:
CLASS_PROTOTYPE( hhPossessedTommy );
public:
void Spawn();
virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
virtual void Think(void);
void SetPossessedProxy( hhSpiritProxy *newProxy ) { possessedProxy = newProxy; }
virtual void Event_Remove(void);
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
protected:
idEntityPtr<hhSpiritProxy> possessedProxy;
int nextDrop;
};
#endif //__PREY_AI_POSSESSED_TOMMY_H__
+639
View File
@@ -0,0 +1,639 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
///////////////////////////////////////////
//
// hhReactionDesc
//
///////////////////////////////////////////
//
// CausedToStr()
//
idStr hhReactionDesc::CauseToStr(Cause c) {
switch(c) {
case Cause_Damage:
return idStr("Damage");
case Cause_Touch:
return idStr("Touch");
case Cause_Use:
return idStr("Use");
case Cause_None:
return idStr("None");
case Cause_PlayAnim:
return idStr("PlayAnim");
case Cause_Telepathic_Trigger:
return idStr("TelepathicTrigger");
case Cause_Telepathic_Throw:
return idStr("TelepathicThrow");
}
return idStr("!!UNDEFINED!!");
}
//
// EffectToStr()
//
idStr hhReactionDesc::EffectToStr(Effect e) {
switch(e) {
case Effect_Damage:
return idStr("Damage");
case Effect_DamageEnemy:
return idStr("DamageEnemy");
case Effect_VehicleDock:
return idStr("VehicleDock");
case Effect_Vehicle:
return idStr("Vehicle");
case Effect_Heal:
return idStr("Heal");
case Effect_HaveFun:
return idStr("HaveFun");
case Effect_ProvideCover:
return idStr("ProvideCover");
case Effect_BroadcastTargetMsgs:
return idStr("BroadcastTargetMsgs");
case Effect_Climb:
return idStr("Climb");
case Effect_Passageway:
return idStr("Passageway");
case Effect_CallBackup:
return idStr("CallBackup");
}
return idStr("!!UNDEFINED!!");
}
//
// StrToCause()
//
hhReactionDesc::Cause hhReactionDesc::StrToCause(const char* str) {
for(int i=0;i<Cause_Total;i++) {
if(!CauseToStr((Cause)i).Icmp(str)) {
return (Cause)i;
}
}
return Cause_Invalid;
}
//
// StrToEffect()
//
hhReactionDesc::Effect hhReactionDesc::StrToEffect(const char* str) {
for(int i=0;i<Effect_Total;i++)
{
if(!EffectToStr((Effect)i).Icmp(str))
return (Effect)i;
}
return Effect_Invalid;
}
//
// Set()
//
void hhReactionDesc::Set(const idDict &keys) {
cause = StrToCause(keys.GetString("cause"));
effect = StrToEffect(keys.GetString("effect"));
flags = 0;
effectRadius = keys.GetFloat("effect_radius","-1.0f");
effectMinRadius = keys.GetFloat("effect_min_radius","0.0f");
if(keys.GetBool("snap_to_point")) {
flags |= flag_SnapToPoint;
}
if(keys.GetBool("effect_all_players")) {
flags |= flag_EffectAllPlayers;
}
if(keys.GetBool("effect_all_monsters")) {
flags |= flag_EffectAllMonsters;
}
if(keys.GetBool("req_novehicle")) {
flags |= flagReq_NoVehicle;
}
if(keys.GetBool("exclusive")) {
flags |= flag_Exclusive;
}
if(keys.GetBool("effect_listener")) {
flags |= flag_EffectListener;
}
if(keys.GetBool("anim_face_cause_dir")) {
flags |= flag_AnimFaceCauseDir;
}
if(keys.GetBool("anim_trigger_cause")) {
flags |= flag_AnimTriggerCause;
}
idStr keyString("");
if(keys.GetString("req_key", "", keyString)) {
if(keyString.Length() > 1) {
flags |= flagReq_KeyValue;
int firstSpace = keyString.Find(' ');
if( firstSpace >= 0 ) {
key = keyString.Left( firstSpace);
keyVal = keyString.Right( keyString.Length() - firstSpace -1);
}
}
else
gameLocal.Error("Invalide key for 'reaction_req_ley' value: %s", (const char*)keyString);
}
//MDC begin
keyString.Empty();
if( keys.GetString( "finish_key", "", keyString)) {
if( keyString.Length() > 1 ) {
int firstSpace = keyString.Find(' ');
if( firstSpace >= 0 ) {
finish_key = keyString.Left( firstSpace );
finish_val = keyString.Right( keyString.Length() - firstSpace - 1 );
}
}
}
//MDC end
idStr animString("");
if(keys.GetString("req_anim", "", animString)) {
flags |= flagReq_Anim;
anim = animString;
}
if(keys.GetBool("req_rangeattack")) {
flags |= flagReq_RangeAttack;
}
if(keys.GetBool("req_meleeattack")) {
flags |= flagReq_MeleeAttack;
}
if(keys.GetBool("req_can_see")) {
flags |= flagReq_CanSee;
}
if(keys.GetBool("req_telepathic") || cause == hhReactionDesc::Cause_Telepathic_Throw || cause == hhReactionDesc::Cause_Telepathic_Trigger) {
flags |= flagReq_Telepathic;
}
// Effect volumes
effectVolumes.Clear();
const idKeyValue *kv = keys.MatchPrefix("effect_volume", NULL);
while(kv) {
idStr effectVolName = kv->GetValue();
if(effectVolName.Length() > 0) {
idEntity *e = gameLocal.FindEntity(effectVolName.c_str());
if(!e) {
gameLocal.Error("Failed to find effect_volume named %s", effectVolName.c_str());
}
if(!e->IsType(hhReactionVolume::Type)) {
gameLocal.Error("effect_volume named %s was of incorrect spawn type. Must be hhReactionVolume", effectVolName.c_str());
}
effectVolumes.AddUnique(static_cast<hhReactionVolume*>(e));
}
// Move to next volume
kv = keys.MatchPrefix("effect_volume", kv);
}
listenerRadius = keys.GetFloat("listener_radius", "-1.0f");
listenerMinRadius = keys.GetFloat("listener_min_radius", "-1.0f");
// Store listener volumes
listenerVolumes.Clear();
kv = keys.MatchPrefix("listener_volume", NULL);
while(kv) {
idStr listenVolName = kv->GetValue();
if(listenVolName.Length() > 0) {
idEntity *e = gameLocal.FindEntity(listenVolName.c_str());
if(!e) {
gameLocal.Error("Failed to find listener_volume named %s", listenVolName.c_str());
}
if(!e->IsType(hhReactionVolume::Type)) {
gameLocal.Error("listener_volume named %s was of incorrect spawn type. Must be hhReactionVolume", listenVolName.c_str());
}
listenerVolumes.AddUnique(static_cast<hhReactionVolume*>(e));
}
// Move to next volume
kv = keys.MatchPrefix("listener_volume", kv);
}
// Store touchdir
touchDir = keys.GetString("touchdir","");
// Extract out touch offsets
kv = keys.MatchPrefix("touchoffset_", NULL);
touchOffsets.SetGranularity(1);
touchOffsets.Clear();
idStr tmpStr;
idStr keyName;
int usIndex;
while(kv) {
tmpStr = kv->GetKey();
usIndex = tmpStr.FindChar("touchoffset_", '_');
keyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1);
touchOffsets.Set(keyName, kv->GetValue());
// Move to the next
kv = keys.MatchPrefix("touchoffset_", kv);
}
// Extract out touch bounds
kv = keys.MatchPrefix("safebound_", NULL );
while(kv) {
tmpStr = kv->GetKey();
usIndex = tmpStr.FindChar("safebound_", '_');
keyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1);
touchOffsets.Set(keyName, kv->GetValue());
kv = keys.MatchPrefix("safebound_", kv);
}
}
/*
================
hhReactionDesc::Save
================
*/
void hhReactionDesc::Save(idSaveGame *savefile) const {
savefile->WriteString(name);
savefile->WriteInt(effect);
savefile->WriteInt(cause);
savefile->WriteInt(flags);
savefile->WriteFloat(effectRadius);
savefile->WriteFloat(effectMinRadius);
int num = effectVolumes.Num();
savefile->WriteInt(num);
for(int i = 0; i < num; i++) {
savefile->WriteObject( effectVolumes[i] );
}
savefile->WriteString(key);
savefile->WriteString(keyVal);
savefile->WriteString(anim);
savefile->WriteFloat(listenerRadius);
savefile->WriteFloat(listenerMinRadius);
num = listenerVolumes.Num();
savefile->WriteInt(num);
for(int i = 0; i < num; i++) {
savefile->WriteObject(listenerVolumes[i]);
}
savefile->WriteDict(&touchOffsets);
savefile->WriteString(touchDir);
savefile->WriteString(finish_key);
savefile->WriteString(finish_val);
}
/*
================
hhReactionDesc::Restore
================
*/
void hhReactionDesc::Restore( idRestoreGame *savefile ) {
savefile->ReadString(name);
savefile->ReadInt(reinterpret_cast<int &> (effect));
savefile->ReadInt(reinterpret_cast<int &> (cause));
savefile->ReadInt(reinterpret_cast<int &> (flags));
savefile->ReadFloat(effectRadius);
savefile->ReadFloat(effectMinRadius);
int num;
savefile->ReadInt(num);
effectVolumes.SetNum(num);
for(int i = 0; i < num; i++) {
savefile->ReadObject(reinterpret_cast<idClass *&> (effectVolumes[i]));
}
savefile->ReadString(key);
savefile->ReadString(keyVal);
savefile->ReadString(anim);
savefile->ReadFloat(listenerRadius);
savefile->ReadFloat(listenerMinRadius);
savefile->ReadInt(num);
listenerVolumes.SetNum(num);
for(int i = 0; i < num; i++) {
savefile->ReadObject(reinterpret_cast<idClass *&> (listenerVolumes[i]));
}
savefile->ReadDict(&touchOffsets);
savefile->ReadString(touchDir);
savefile->ReadString(finish_key);
savefile->ReadString(finish_val);
}
///////////////////////////////////////////
//
// hhReactionVolume
//
///////////////////////////////////////////
CLASS_DECLARATION(idEntity, hhReactionVolume)
END_CLASS
//
// Spawn()
//
void hhReactionVolume::Spawn() {
flags = 0;
if(spawnArgs.GetBool("players", "1")) {
flags |= flagPlayers;
}
if(spawnArgs.GetBool("monsters", "1")) {
flags |= flagMonsters;
}
if(flags == 0) {
gameLocal.Error("Reaction volume %s is not targetting players or monsters - Why even have this volume?", (const char*)name);
}
}
bool hhReactionVolume::IsValidEntity( idEntity *ent ) {
assert(GetPhysics() != NULL);
assert(flags != 0); // have to be looking for something - else whats the point?
if ( !ent || ent->IsHidden() ) {
return false;
}
if(flags & flagMonsters && !ent->IsType(idAI::Type)) {
if(flags & flagPlayers && !ent->IsType(hhPlayer::Type)) {
return false;
}
}
idEntity * entityList[ MAX_GENTITIES ];
int numEnts = gameLocal.clip.EntitiesTouchingBounds(GetPhysics()->GetAbsBounds(), CONTENTS_BODY, entityList, MAX_GENTITIES);
for(int i=0;i<numEnts;i++) {
if ( entityList[i] == ent ) {
return true;
}
}
return false;
}
//
// GetValidEntitiesInside()
//
void hhReactionVolume::GetValidEntitiesInside(idList<idEntity*> &outValidEnts) {
assert(GetPhysics() != NULL);
assert(flags != 0); // have to be looking for something - else whats the point?
idEntity * entityList[ MAX_GENTITIES ];
int numEnts = gameLocal.clip.EntitiesTouchingBounds(GetPhysics()->GetAbsBounds(), CONTENTS_BODY, entityList, MAX_GENTITIES);
for(int i=0;i<numEnts;i++) {
if(!entityList[i] || entityList[i]->IsHidden())
continue;
if(flags & flagPlayers && entityList[i]->IsType(hhPlayer::Type)) {
outValidEnts.Append(entityList[i]);
if(ai_debugBrain.GetInteger()) {
gameRenderWorld->DebugArrow(colorMagenta, GetOrigin(), entityList[i]->GetOrigin(), 10, 1000);
}
continue;
}
}
if(ai_debugBrain.GetInteger()) {
gameRenderWorld->DebugBounds(colorWhite, GetPhysics()->GetBounds(), GetOrigin(), 1000);
}
}
/*
================
hhReactionVolume::Save
================
*/
void hhReactionVolume::Save(idSaveGame *savefile) const {
savefile->WriteInt(flags);
}
/*
================
hhReactionVolume::Restore
================
*/
void hhReactionVolume::Restore(idRestoreGame *savefile) {
savefile->ReadInt(flags);
}
///////////////////////////////////////////
//
// hhReactionHandler
//
///////////////////////////////////////////
//
// construction
//
hhReactionHandler::hhReactionHandler() {
}
//
// destruction
//
hhReactionHandler::~hhReactionHandler() {
reactionDescs.DeleteContents(TRUE);
}
//
// LoadReactionDesc()
//
const hhReactionDesc* hhReactionHandler::LoadReactionDesc(const char* defName, const idDict *uniqueDescKeys) {
// Ignore null reactions
if ( !defName || !defName[0] ) {
return NULL;
}
const hhReactionDesc *ret = NULL;
// If we do not have unqiue keys, we can just use a cached version
if(!uniqueDescKeys) {
ret = FindReactionDesc(defName);
if(ret) {
return ret;
}
}
// Get the def dict
const idDict *reactDefDict = gameLocal.FindEntityDefDict(defName);
if(!reactDefDict) {
gameLocal.Error("Failed to find reaction dict named %s", defName);
}
// Build the keys that will be passed onto the creation
idDict finalDict;
finalDict.Copy(*reactDefDict);
StripReactionPrefix(finalDict);
if(uniqueDescKeys) {
idDict uniqueCopy(*uniqueDescKeys);
StripReactionPrefix(uniqueCopy);
finalDict.Copy(uniqueCopy);
}
hhReactionDesc* react = CreateReactionDesc(defName, finalDict);
return react;
}
//
// createReactionDesc()
//
hhReactionDesc* hhReactionHandler::CreateReactionDesc(const char *desiredName, const idDict &keys) {
idStr finalName = GetUniqueReactionDescName(desiredName);
hhReactionDesc *desc = new hhReactionDesc();
desc->name = finalName;
desc->Set(keys);
reactionDescs.Append(desc);
return desc;
}
//
// getUniqueReactionDescName()
//
idStr hhReactionHandler::GetUniqueReactionDescName(const char *name) {
int count = 0;
idStr finalName(name);
while(1) {
if(count > 0) {
finalName = idStr(name) + idStr("_") + idStr(count);
}
// Did we find a name that was not taken?
if(FindReactionDesc(finalName.c_str()) == NULL) {
return finalName;
}
#ifdef _DEBUG
if(count > 128) {
assert(FALSE); // How the hell did we get so many copies?
}
#endif
count++;
}
}
//
// findReactionDesc()
//
const hhReactionDesc* hhReactionHandler::FindReactionDesc(const char *name) {
for(int i=0;i<reactionDescs.Num();i++) {
if(strcmp(reactionDescs[i]->name.c_str(), name) == 0) {
return reactionDescs[i];
}
}
return NULL;
}
//
// stripReactionPrefix()
//
void hhReactionHandler::StripReactionPrefix(idDict &dict) {
const idKeyValue *kv = NULL;//dict.MatchPrefix("reaction");
idDict newDict;
for(int i=0;i<dict.GetNumKeyVals();i++) {
kv = dict.GetKeyVal(i);
idStr key;
key = kv->GetKey();
// Do we have a reaction token to strip out?
if(idStr::FindText(key.c_str(), "reaction") != -1) {
int endPrefix = idStr::FindChar(key.c_str(), '_');
if(endPrefix == -1) {
gameLocal.Error("reactionX_ prefix not found.");
}
idStr realKey(key);
realKey = key.Mid(endPrefix+1, key.Length() - endPrefix - 1);
key = realKey;
}
//dict.Delete(kv->GetKey().c_str());
newDict.Set(key.c_str(), kv->GetValue());
kv = dict.MatchPrefix("reaction", kv);
}
dict.Clear();
dict.Copy(newDict);
}
void hhReactionHandler::Save(idSaveGame *savefile) const {
savefile->WriteInt(reactionDescs.Num());
for (int i = 0; i < reactionDescs.Num(); i++) {
reactionDescs[i]->Save(savefile);
}
}
void hhReactionHandler::Restore( idRestoreGame *savefile ) {
reactionDescs.DeleteContents(true);
int num;
savefile->ReadInt(num);
reactionDescs.SetNum(num);
for (int i = 0; i < num; i++) {
reactionDescs[i] = new hhReactionDesc();
reactionDescs[i]->Restore(savefile);
}
}
void hhReaction::Save(idSaveGame *savefile) const {
savefile->WriteBool(active);
exclusiveOwner.Save(savefile);
savefile->WriteInt(exclusiveUntil);
if (desc) {
savefile->WriteString(desc->name);
} else {
savefile->WriteString("");
}
causeEntity.Save(savefile);
int num = effectEntity.Num();
savefile->WriteInt(num);
for(int i = 0; i < num; i++) {
effectEntity[i].ent.Save(savefile);
savefile->WriteFloat(effectEntity[i].weight);
}
}
void hhReaction::Restore(idRestoreGame *savefile) {
savefile->ReadBool(active);
exclusiveOwner.Restore(savefile);
savefile->ReadInt(exclusiveUntil);
idStr name;
savefile->ReadString(name);
desc = gameLocal.GetReactionHandler()->FindReactionDesc(name);
causeEntity.Restore(savefile);
int num;
savefile->ReadInt(num);
hhReactionTarget ent;
for(int i = 0; i < num; i++) {
ent.ent.Restore(savefile);
savefile->ReadFloat(ent.weight);
effectEntity.Append(ent);
}
}
+261
View File
@@ -0,0 +1,261 @@
#ifndef __HH_REACTION_H
#define __HH_REACTION_H
//
// hhReactionTarget
//
class hhReactionTarget
{
public:
hhReactionTarget() {
weight = 1.0f;
ent = NULL;
}
hhReactionTarget(idEntity *e, float w = 1.0f) {
ent = e;
weight = w;
}
idEntityPtr<idEntity> ent; // The entity that is effected
float weight; // The degree ent is effected
};
//
// hhReactionVolume
//
class hhReactionVolume : public idEntity {
public:
CLASS_PROTOTYPE( hhReactionVolume );
enum {
flagPlayers = (1<<0),
flagMonsters = (1<<1),
flagActors = (flagPlayers|flagMonsters),
};
void Spawn(void);
// Gets all of the entities (filtered by validFlags) that are inside this volume
void GetValidEntitiesInside(idList<idEntity*> &outValidEnts);
bool IsValidEntity( idEntity *ent );
void Save(idSaveGame *savefile) const;
void Restore(idRestoreGame *savefile);
protected:
int flags; // Flags for this volume
};
//
// hhReactionDesc
//
// Holds the unchanging properties of a reaction
//
class hhReactionDesc
{
public:
friend class hhReactionHandler;
hhReactionDesc() {
name = idStr("<undefined>");
effect = Effect_Invalid;
cause = Cause_Invalid;
flags = 0;
effectRadius = -1.0f;
effectMinRadius = -1.0f;
effectVolumes.SetGranularity(1);
key = idStr("");
keyVal = idStr("");
anim = idStr("");
listenerRadius = -1.0f;
listenerMinRadius = -1.0f;
listenerVolumes.SetGranularity(1);
}
enum Cause {
Cause_Invalid = -1,
Cause_Damage, // AI can damage this to get reaction
Cause_Touch, // AI can touch this to get reaction
Cause_Use, // AI can 'use' this to get reaction
Cause_None, // AI does nothing, and the effect is always felt
Cause_PlayAnim, // AI must play an anim to get the desired effect
Cause_Telepathic_Trigger, // AI must use their telepathy to trigger this entity
Cause_Telepathic_Throw, // AI must use their telepathy to physically throw this entity
Cause_Total
};
enum Effect {
Effect_Invalid = -1,
Effect_Damage, // Causes damage (reduces health)
Effect_DamageEnemy, // Causes damage (reduces health)
Effect_VehicleDock, // Causes damage (reduces health)
Effect_Vehicle, // Causes damage (reduces health)
Effect_Heal, // Heals entity (increases health)
Effect_HaveFun, // Something 'fun' for this entity to do
Effect_ProvideCover, // Indicates this entity can possibily provide 'cover' for a monster
Effect_BroadcastTargetMsgs, // This entity will have its target's msg's sent
Effect_Climb, // This entity will let the monster climb it.
Effect_Passageway, // This entity is a harvester passageway
Effect_CallBackup, // Calls for help
Effect_Total
};
enum {
flag_EffectAllPlayers = (1<<0), // This entity automatically targets ALL entities of type hhPlayer
flag_EffectAllMonsters = (1<<1), // This entity automatically targets ALL entities of typehhAI
flag_Exclusive = (1<<2), // This msg is exclusive, and once a monster has committed to it - NO one else should listen to it
flag_EffectListener = (1<<3), // The effect of this reaction is applied to the listener
flag_AnimFaceCauseDir = (1<<4), // When playing an anim for this reaction, face the direction of our cause
flag_AnimTriggerCause = (1<<5), // When playing an anim for this reaction, trigger the cause entity
flag_SnapToPoint = (1<<6), // This msg is only for monsters that are telepathic
flagReq_MeleeAttack = (1<<8), // This msg is for monsters that can melee attack
flagReq_RangeAttack = (1<<9), // This msg is for monsters that can range attack
flagReq_Anim = (1<<10), // This msg is for monsters that have a given anim (anim)
flagReq_KeyValue = (1<<11), // This msg is for monsters that have a given key set (key, keyVal)
flagReq_NoVehicle = (1<<12), // This msg is for monsters that are NOT in vehicles
flagReq_CanSee = (1<<13), // This msg is only for monsters that can see the cause entity
flagReq_Telepathic = (1<<14), // This msg is only for monsters that are telepathic
};
static idStr CauseToStr(Cause c); // Converts activate enum into readable form
static idStr EffectToStr(Effect e); // Converts reaction enum to readable form
static Cause StrToCause(const char* str); // Converts string to caused by enum or Caused_Invalid if not found
static Effect StrToEffect(const char* str); // Converts string to effect enum or Effect_Invalid if not found
void Set(const idDict &keys); // Sets this description to the given key values
bool CauseRequiresPathfinding(void) const {return cause == Cause_Touch || cause == Cause_Use || cause == Cause_PlayAnim;}
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
idStr name; // unique name for each reaction desc
Effect effect; // pdm: for reaction system
Cause cause; // pdm: for reaction system
unsigned int flags; // jrm: reaction flags for reaction system
float effectRadius; // jrm: If > 0.0f then ONLY target entities that are at most this far away
float effectMinRadius; // jrm: if > 0.0f then ONLY target entities that are at least this far away
idList<hhReactionVolume*> effectVolumes; // jrm: if valid, then only entities that are inside one of these volumes are valid 'effect' entities
idStr key; // jrm: the key required for a monster to be eligable for this reaction msg (ReactionFlag_KeyReq must be set)
idStr keyVal; // jrm: the value of the reaction key required
idStr anim; // jrm: the value of the reaction key required
float listenerRadius; // jrm: listeners farther away than this distance will not receive this reaction
float listenerMinRadius; // jrm: listeners closer than this dist will not receive this reaction
idList<hhReactionVolume*> listenerVolumes; // jrm: only listeners that are inside one of these volumes will receive this reaction
idDict touchOffsets; // Example: Use "monster_hunter" as key to get touchOffset values
idStr touchDir; // Indicates how a monster should approach the cause entity
idStr finish_key; // mdc: used to set a key on monster after finishing a reaction
idStr finish_val; // mdc: used to set a key on monster after finishing a reaction
};
//
// hhReaction
//
// Describes a reaction ready to be processed by the AI. Holds a description of the reaction, as well as the cause/effect entities.
// Do the cause describe by <desc> to <causeEntity> to get the effect in <desc> applied to <effectEntity>
//
class hhReaction
{
public:
hhReaction()
{
desc = NULL;
causeEntity = NULL;
active = TRUE;
exclusiveOwner = NULL;
exclusiveUntil = -1;
effectEntity.Clear();
};
hhReaction(const hhReactionDesc *reactDesc, idEntity *cause, const hhReactionTarget &effect)
{
active = TRUE;
desc = reactDesc;
causeEntity = cause;
exclusiveUntil = -1;
exclusiveOwner = NULL;
effectEntity.Append(effect);
}
bool IsActive() const {return active;}
bool IsActiveForListener(idEntity *listener) const {
if(!active) {
return FALSE;
}
// If someone has claimed this, and their claim is still valid... Only active for that particular AI
if(exclusiveOwner.GetEntity() && gameLocal.GetTime() < exclusiveUntil) {
return exclusiveOwner.GetEntity() == listener;
}
// No one has claimed us, or time has expired, so anyone can claim us now
return TRUE;
};
bool ClaimExclusivity(idEntity *claimer, int ms=1000) {
// Cannot claim, someone else already has it
if(exclusiveOwner.GetEntity() && gameLocal.GetTime() < exclusiveUntil) {
return FALSE;
}
// They claimed it now....
exclusiveOwner = claimer;
exclusiveUntil = gameLocal.GetTime() + ms;
return TRUE;
}
void Save(idSaveGame *savefile) const;
void Restore(idRestoreGame *savefile);
bool active; // TRUE to be considered for sending
idEntityPtr<idEntity> exclusiveOwner; // The AI that has 'claimed' this reaction
int exclusiveUntil; // Until gameLocal.time has reached this value, this reaction does not send msgs (except for exclusive reactions)
const hhReactionDesc* desc; // Info about the kind of reaction this is
idEntityPtr<idEntity> causeEntity; // The entity to apply the cause to
idList<hhReactionTarget> effectEntity; // The effect of doing the cause is felt by these entities
};
//
// hhReactionHandler
//
// Manages all of the reaction descs currently loaded
//
class hhReactionHandler
{
public:
hhReactionHandler();
virtual ~hhReactionHandler();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
// Loads the given reaction def name. If uniqueDescKeys are specified, a unique hhReactionDesc* is guarenteed to be returned.
// Otherwise, if no override keys are specified and a reaction of the given name is found, it is returned without creating a new one
// The returned desc is ONLY a desc, it is not meant to be modified in any way
const hhReactionDesc* LoadReactionDesc(const char* defName, const idDict &uniqueDescKeys) {
if(uniqueDescKeys.GetNumKeyVals() <= 0) {
return LoadReactionDesc(defName, NULL);
} else {
return LoadReactionDesc(defName, &uniqueDescKeys);
}
}
const hhReactionDesc* LoadReactionDesc(const char* defName) {return LoadReactionDesc(defName, NULL);}
protected:
friend class hhReaction;
// Creates a new reaction desc with the given name and keys to describe the reaction
hhReactionDesc* CreateReactionDesc(const char *name, const idDict &keys);
idStr GetUniqueReactionDescName(const char *name);
const hhReactionDesc* FindReactionDesc(const char *name);
void StripReactionPrefix(idDict &dict); // Removes any "reaction_" or "reaction1_" prefix from the given dict
const hhReactionDesc* LoadReactionDesc(const char* defName, const idDict *uniqueDescKeys);
idList<hhReactionDesc*> reactionDescs;
};
#endif
+227
View File
@@ -0,0 +1,227 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//
// hhAISpawnCase
//
const idEventDef EV_CreateAI( "<createAI>", NULL);
const idEventDef EV_AutoTrigger( "<autotrigger>", NULL);
CLASS_DECLARATION( hhAnimated, hhAISpawnCase )
EVENT( EV_Activate, hhAISpawnCase::Event_Trigger )
EVENT( EV_CreateAI, hhAISpawnCase::Event_CreateAI )
EVENT( EV_AutoTrigger, hhAISpawnCase::Event_AutoTrigger )
END_CLASS
//
// Spawn()
//
void hhAISpawnCase::Spawn( void ) {
triggerToggle = FALSE;
aiSpawnCount = 0;
waitingForAutoTrigger = FALSE;
triggerQueue = 0;
CreateEntSpawnArgs();
GetPhysics()->SetContents(CONTENTS_SHOOTABLE|CONTENTS_PLAYERCLIP|CONTENTS_MOVEABLECLIP|CONTENTS_IKCLIP|CONTENTS_SHOOTABLEBYARROW);
// Next frame, create the AI to attach to this case - Wait a frame so that we can copy targets
PostEventMS(&EV_CreateAI, 0);
}
//
// Event_CreateAI()
//
void hhAISpawnCase::Event_CreateAI( void ) {
// Spawn the monster
const char *monsterName = spawnArgs.GetString("def_monster", NULL);
int i;
if ( aiSpawnCount < spawnArgs.GetInt("max_monsters", "1") && monsterName && monsterName[0]) {
idDict args = entSpawnArgs;
idVec3 offset = spawnArgs.GetVector("monster_offset", "0 0 0");
offset *= GetPhysics()->GetAxis();
args.SetVector( "origin", GetPhysics()->GetOrigin() + offset);
args.SetBool("encased", TRUE);
args.Set("encased_idle", spawnArgs.GetString("anim_encased_monster_idle", "encased_idle"));
args.Set("encased_exit", spawnArgs.GetString("anim_encased_monster_exit", "encased_exit"));
args.Set( "encased_exit_offset", spawnArgs.GetString( "encased_exit_offset", "0 0 0" ) );
args.SetMatrix("rotation", GetAxis());
encasedAI = static_cast<idAI*>(gameLocal.SpawnObject(monsterName, &args));
if(!encasedAI.GetEntity()) {
//gameLocal.Warning("No monster specified for case");
return;
}
// Copy the targets that our case has to our newly spawned monster
for( i = 0; i < targets.Num(); i++ ) {
encasedAI->targets.AddUnique(targets[i]);
}
if ( spawnArgs.GetBool( "rotated" ) ) {
encasedAI->SetAxis( GetAxis() );
encasedAI->GetPhysics()->SetAxis( GetAxis() );
encasedAI->GetRenderEntity()->axis = GetAxis();
encasedAI->viewAxis = GetAxis();
encasedAI->Bind(this, true);
} else {
encasedAI->viewAxis = GetAxis();
encasedAI->Bind(this, FALSE);
}
aiSpawnCount++;
if(spawnArgs.GetBool("monster_hide", "0")) {
encasedAI->ProcessEvent(&EV_Hide);
}
}
}
//
// Event_Trigger()
//
void hhAISpawnCase::Event_Trigger( idEntity *activator ) {
bool triggeredAI = false;
bool showedAI = false;
// We are waiting for an auto-trigger, so don't fire now - but remember that we need to later
if(waitingForAutoTrigger) {
triggerQueue++;
//gameLocal.Printf("\nQUEUED TRIGGERS: %i", triggerQueue);
return;
}
//gameLocal.Printf("\n * TRIGGERED *");
if(encasedAI.GetEntity()) {
if(encasedAI->IsHidden()) {
encasedAI->ProcessEvent(&EV_Show);
showedAI = true;
}
//if triggered_spawn is set, spawn the encasedAI on the first trigger,
//and but don't activate it until a second trigger
if ( !showedAI || !spawnArgs.GetBool( "triggered_spawn", "0" ) ) {
encasedAI->Unbind();
// removed since monster_offset should take care of this
//encasedAI->SetOrigin(encasedAI->GetOrigin() + encasedAI->viewAxis[0] * 64);
encasedAI.GetEntity()->ProcessEvent(&EV_Activate, activator);
triggeredAI = true;
}
}
// Start the anim playing
hhAnimated::Event_Activate(activator);
if ( spawnArgs.GetInt( "no_anim", "0" ) ) {
ProcessEvent(&EV_CreateAI);
//if triggered_spawn is set, dont show the next AI until the next trigger
if ( encasedAI.IsValid() && triggeredAI && spawnArgs.GetBool( "triggered_spawn", "0" ) ) {
encasedAI->ProcessEvent(&EV_Hide);
}
}
triggerToggle = !triggerToggle;
}
//
// Event_AutoTrigger()
//
void hhAISpawnCase::Event_AutoTrigger( void ) {
waitingForAutoTrigger = FALSE;
ProcessEvent(&EV_Activate, this);
}
//
// Event_AnimDone()
//
void hhAISpawnCase::Event_AnimDone( int animIndex ) {
// Call back first
hhAnimated::Event_AnimDone(animIndex);
const char *n = NULL;
// Door is OPEN, now we queue up the CLOSE anim
if(triggerToggle) {
n = spawnArgs.GetString("anim_retrigger");
// Should we automatically retrigger? (ie. close the door after it was opened?)
if(spawnArgs.GetFloat("auto_retrigger_delay", "-1") >= 0.0f ) {
int delay = SEC2MS(spawnArgs.GetFloat("auto_retrigger_delay", "-1"));
waitingForAutoTrigger = TRUE;
PostEventMS(&EV_AutoTrigger, delay);
}
}
// Door is CLOSED, now we queue up the OPEN anim
else {
// If we have queued triggers saved up - then lets fire one now since we are now closed
if(triggerQueue > 0) {
triggerQueue--;
PostEventMS(&EV_Activate, 0, this);
//gameLocal.Printf("\nQUEUED TRIGGERS: %i", triggerQueue);
}
n = spawnArgs.GetString("anim");
int maxMonsters = spawnArgs.GetInt("max_monsters", "1");
if(maxMonsters < 0 || aiSpawnCount < maxMonsters) {
Event_CreateAI();
} else {
//gameLocal.Printf("\nMax monsters reached.");
}
}
anim = GetAnimator()->GetAnim( n );
HH_ASSERT( anim );
}
/*
================
hhAISpawnCase::Save
================
*/
void hhAISpawnCase::Save( idSaveGame *savefile ) const {
encasedAI.Save( savefile );
savefile->WriteBool( triggerToggle );
savefile->WriteInt( aiSpawnCount );
savefile->WriteBool( waitingForAutoTrigger );
savefile->WriteInt( triggerQueue );
}
/*
================
hhAISpawnCase::Restore
================
*/
void hhAISpawnCase::Restore( idRestoreGame *savefile ) {
encasedAI.Restore( savefile );
savefile->ReadBool( triggerToggle );
savefile->ReadInt( aiSpawnCount );
savefile->ReadBool( waitingForAutoTrigger );
savefile->ReadInt( triggerQueue );
CreateEntSpawnArgs();
}
/*
================
hhAISpawnCase::CreateEntSpawnArgs
================
*/
void hhAISpawnCase::CreateEntSpawnArgs( void ) {
// Create list of spawn args, copied to encasedAI in Event_CreateAI
entSpawnArgs.Clear();
idStr tmpStr, realKeyName;
const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL);
while(kv) {
tmpStr = kv->GetKey();
int usIndex = tmpStr.FindChar("ent_", '_');
realKeyName = tmpStr.Mid(usIndex+1, strlen( kv->GetKey())-usIndex-1);
entSpawnArgs.Set(realKeyName, kv->GetValue());
kv = spawnArgs.MatchPrefix("ent_", kv);
}
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef ai_spawncase_H
#define ai_spawncase_H
//
// hhAISpawnCase
//
// Spawns a specific monster, attaches to model - waits for trigger before releasing monster
//
class hhAISpawnCase : public hhAnimated {
public:
CLASS_PROTOTYPE(hhAISpawnCase);
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Event_Trigger( idEntity *activator );
virtual void Event_AnimDone( int animIndex );
protected:
void Event_CreateAI( void );
void Event_AutoTrigger( void );
void CreateEntSpawnArgs( void );
idEntityPtr<idAI> encasedAI; // The AI that is currently encased and waiting to come out
bool triggerToggle; // Toggles back and forth to indicated which anim to play (open or close)
int aiSpawnCount; // Current number of monsters spawned
bool waitingForAutoTrigger; // True if we are waiting for an auto-trigger msg to come in (from auto_retrigger_delay setting)
int triggerQueue; // Number of triggers that have occurred during the delay period that must be re-sent
idDict entSpawnArgs; // Spawn args to copy to spawned entities
};
#endif
+403
View File
@@ -0,0 +1,403 @@
//
// ai_speech.cpp
//
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
extern idCVar ai_skipSpeech;
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
//////////////////////////////////////////////////////////////////////
//
// hhAISpeech
//
//////////////////////////////////////////////////////////////////////
// jrm: none yet
//////////////////////////////////////////////////////////////////////
//
// hhAISpeechHandler
//
//////////////////////////////////////////////////////////////////////
float hhAISpeechHandler::TokenMatchWeight[] = {3.0f, 2.0f, 1.0f};
float hhAISpeechHandler::TokenWildcardMatchWeight[] = {1.5f, 1.0f, 0.5f};
#define TokenWildCard "*"
#define SpeechShaderPrefix "snd_speech_" // IF THIS CHANGES YOU MUST CHANGE THE LENGTH DEFINE!
#define SpeechShaderPrefixLen 11
#define FreqToken ':'
#define FreqTokenStr ":"
//
// Construction
//
hhAISpeechHandler::hhAISpeechHandler()
{
firstSpeech = NULL;
lastSpeech = NULL;
nextSpeechTime = 0;
};
//
// Update()
//
void hhAISpeechHandler::Update()
{
hhAISpeech *currSpeech = firstSpeech;
hhAISpeech *nextSpeech = NULL;
if(ai_skipSpeech.GetBool())
return;
//for( currSpeech = speechQueue.Next(); currSpeech != NULL; currSpeech = nextSpeech )
while(currSpeech != NULL)
{
// We can't play any more speech yet!
if(gameLocal.GetTime() < nextSpeechTime)
return;
nextSpeech = currSpeech->next;
// Has this speech expired?
if(gameLocal.GetTime() > currSpeech->expireTime)
{
RemoveSpeechFromQueue(currSpeech);
currSpeech = nextSpeech;
continue;
}
// Play the speech!
currSpeech->speaker->StartSound(currSpeech->snd, SND_CHANNEL_VOICE, 0, true, NULL);
// Debug Start
/*
idStr speechStr;
sprintf(speechStr, "\"%s\"", (const char*)currSpeech->snd);
speechStr.ToLower();
gameRenderWorld->DrawText(speechStr, currSpeech->speaker->GetOrigin() + idVec3(0.0f, 0.0f, 12.0f), 0.5f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(),1,1000);
gameLocal.Printf("\n%.01f, %s says %s", (float)MS2SEC(gameLocal.GetTime()), (const char*)currSpeech->speaker->name, (const char*)speechStr);
*/
// Debug End
RemoveSpeechFromQueue(currSpeech);
nextSpeechTime = gameLocal.GetTime() + MinSpeechWaitTime;
currSpeech = nextSpeech;
}
};
//
// PlaySpeech()
//
bool hhAISpeechHandler::PlaySpeech(idAI *source, idStr sndShd, int expireDelay, bool allowQueue)
{
if(ai_skipSpeech.GetBool())
return FALSE;
if(!allowQueue) {
// just play it and return
HH_ASSERT(FALSE); // Code not done yet!
return TRUE;
}
// Do we have more slots availble?
hhAISpeech *freeSpeech = GetFreeSpeech();
if(!freeSpeech)
return FALSE;
// Setup the new speech
freeSpeech->speaker = source;
freeSpeech->expireTime = gameLocal.GetTime() + expireDelay;
freeSpeech->snd = sndShd;
AddSpeechToQueue(freeSpeech);
return TRUE;
};
//
// ClearSpeech()
//
void hhAISpeechHandler::ClearSpeech()
{
firstSpeech = NULL;
lastSpeech = NULL;
for(int i=0;i<SpeechPoolSize;i++) {
speechPool[i].Clear();
}
};
//
// getFreeSpeech()
//
hhAISpeech* hhAISpeechHandler::GetFreeSpeech()
{
for(int i=0;i<SpeechPoolSize;i++) {
if(speechPool[i].IsCleared())
return &speechPool[i];
}
return NULL;
};
//
// AddSpeechToQueue()
//
void hhAISpeechHandler::AddSpeechToQueue(hhAISpeech *speech)
{
// First entry?
if(!firstSpeech || !lastSpeech)
{
// this means empty list, so they both better be empty
HH_ASSERT(lastSpeech == NULL);
HH_ASSERT(firstSpeech == NULL);
speech->next = NULL;
speech->prev = NULL;
firstSpeech = speech;
lastSpeech = speech;
return;
}
// Add it to the end of the list
HH_ASSERT(lastSpeech && lastSpeech->next == NULL);
lastSpeech->next = speech;
speech->prev = lastSpeech;
speech->next = NULL;
lastSpeech = speech;
}
//
// RemoveSpeechFromQueue()
//
void hhAISpeechHandler::RemoveSpeechFromQueue(hhAISpeech *speech)
{
speech->Clear();
// Remove from one of the ends?
if(speech == firstSpeech || speech == lastSpeech) {
if(speech == firstSpeech) {
firstSpeech = firstSpeech->next;
if(firstSpeech)
firstSpeech->prev = NULL;
}
if(speech == lastSpeech) {
lastSpeech = lastSpeech->prev;
if(lastSpeech)
lastSpeech->next = NULL;
}
return;
}
// Middle?
if(speech->next)
speech->next->prev = speech->prev;
if(speech->prev)
speech->prev->next = speech->next;
}
//
// GetSpeechFrequency()
//
float hhAISpeechHandler::GetSpeechFrequency(idStr sndShader)
{
if(ai_skipSpeech.GetBool())
return 0.0f;
int firstBreak = sndShader.Find(FreqToken);
if(firstBreak == -1)
return 1.0f;
int secondBreak = sndShader.Find(FreqTokenStr, true, firstBreak);
if(secondBreak == -1)
secondBreak = sndShader.Length();
idStr freqStr = sndShader.Mid(firstBreak, secondBreak - firstBreak);
int f = atoi((const char*)freqStr);
return idMath::ClampFloat(0.0f, 1.0f, (float(f) * 0.01f));
};
//
// GetSpeechResponseDelay()
//
int hhAISpeechHandler::GetSpeechResponseDelay(idStr sndShader)
{
// TODO: Fill this code in when we actually need it. Does 1 second work all the time?
return 500;
};
//
// GetSpeechSoundShader()
//
bool hhAISpeechHandler::GetSpeechSoundShader(idAI *source, idStr speechDesc, idStr &outClosestSndShd)
{
HH_ASSERT(source != NULL);
const idKeyValue *kv = NULL;
if(ai_skipSpeech.GetBool())
return FALSE;
// Quick out - do we have this EXACT snd shader?
idStr sndShader = SpeechShaderPrefix + speechDesc;
kv = source->spawnArgs.MatchPrefix(sndShader);
if(kv) {
outClosestSndShd = kv->GetKey();
return TRUE;
}
// Extrace the tokens for our 'desired' speech pattern
SpeechTokens desired;
if(!ExtractSpeechTokens(speechDesc, desired))
return FALSE;
// Find our best potential speech pattern match
float bestRating = 0.0f;
float currRating = -1.0f;
const idKeyValue *bestKey = NULL;
SpeechTokens currPotential;
kv = source->spawnArgs.MatchPrefix(SpeechShaderPrefix);
while(kv)
{
idStr speechStr;
speechStr = kv->GetKey().Mid(SpeechShaderPrefixLen, kv->GetKey().Length() - SpeechShaderPrefixLen);
if(!ExtractSpeechTokens(speechStr, currPotential))
currRating = -1.0f;
else
currRating = GetMatchRating(desired, currPotential);
if(currRating > bestRating)
{
bestRating = currRating;
bestKey = kv;
}
kv = source->spawnArgs.MatchPrefix(SpeechShaderPrefix,kv);
}
// We've found a match to some degree
if(bestKey && bestRating > 0.0f)
{
outClosestSndShd = bestKey->GetKey();
return TRUE;
}
return FALSE;
};
//
// GetMatchRating()
//
float hhAISpeechHandler::GetMatchRating(SpeechTokens &desired, SpeechTokens &potential)
{
float rating = 0.0f;
for(int i=0;i<TT_Total;i++)
{
if(potential.tokens[i] == desired.tokens[i])
rating += TokenMatchWeight[i];
else if(potential.tokens[i] == TokenWildCard)
rating += TokenWildcardMatchWeight[i];
}
return rating;
};
//
// ExtractSpeechTokens()
//
bool hhAISpeechHandler::ExtractSpeechTokens(idStr speechDesc, SpeechTokens &outTokens)
{
int firstBreak = speechDesc.Find('_');
if(firstBreak < 0)
return FALSE;
int lastBreak = speechDesc.Find("_", true, firstBreak+1);
if(lastBreak < 0)
return FALSE;
outTokens.tokens[TT_Desire] = speechDesc.Mid(0, firstBreak-1);
outTokens.tokens[TT_How] = speechDesc.Mid(firstBreak, lastBreak-(firstBreak));
outTokens.tokens[TT_What] = speechDesc.Mid(lastBreak+1, (speechDesc.Length()-1) - lastBreak);
int findFreqBreak = outTokens.tokens[TT_What].Find(FreqToken);
if(findFreqBreak >= 0)
outTokens.tokens[TT_What] = outTokens.tokens[TT_What].Mid(0, outTokens.tokens[TT_What].Length() - (findFreqBreak+1));
return true;
};
/*
================
hhAISpeechHandler::Save
================
*/
void hhAISpeechHandler::Save(idSaveGame *savefile) const {
for (int i = 0; i < SpeechPoolSize; i++) {
savefile->WriteObject(speechPool[i].speaker);
savefile->WriteString(speechPool[i].snd);
savefile->WriteInt(speechPool[i].expireTime);
savefile->WriteInt(GetPoolIndex(speechPool[i].next));
savefile->WriteInt(GetPoolIndex(speechPool[i].prev));
}
savefile->WriteInt(GetPoolIndex(firstSpeech));
savefile->WriteInt(GetPoolIndex(lastSpeech));
savefile->WriteInt(nextSpeechTime);
}
/*
================
hhAISpeechHandler::Restore
================
*/
void hhAISpeechHandler::Restore(idRestoreGame *savefile) {
int tmp;
for (int i = 0; i < SpeechPoolSize; i++) {
savefile->ReadObject(reinterpret_cast<idClass *&> (speechPool[i].speaker));
savefile->ReadString(speechPool[i].snd);
savefile->ReadInt(speechPool[i].expireTime);
savefile->ReadInt(tmp);
speechPool[i].next = (tmp == -1 ? NULL : &speechPool[tmp]);
savefile->ReadInt(tmp);
speechPool[i].prev = (tmp == -1 ? NULL : &speechPool[tmp]);
}
savefile->ReadInt(tmp);
firstSpeech = (tmp == -1 ? NULL : &speechPool[tmp]);
savefile->ReadInt(tmp);
lastSpeech = (tmp == -1 ? NULL : &speechPool[tmp]);
savefile->ReadInt(nextSpeechTime);
}
int hhAISpeechHandler::GetPoolIndex(hhAISpeech *obj) const {
if (!obj) {
return -1;
}
for (int i = 0; i < SpeechPoolSize; i++) {
if (obj == &speechPool[i]) {
return i;
}
}
HH_ASSERT(!"Couldn't find pool index!");
return -1;
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+102
View File
@@ -0,0 +1,102 @@
#ifndef __HH_AISPEECH_H
#define __HH_AISPEECH_H
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
//
// hhAISpeech
//
class hhAISpeech
{
public:
hhAISpeech() {Clear(); next = NULL; prev = NULL; }
void Clear(void) {speaker = NULL; expireTime = -1;}
bool IsCleared(void) {return speaker == NULL;} // Assumes speech will NEVER come from a 'null' speaker, NULL = empty
idAI* speaker;
idStr snd;
int expireTime;
hhAISpeech *next;
hhAISpeech *prev;
};
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
//
// hhAISpeechHandler
//
class hhAISpeechHandler
{
public:
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
enum
{
SpeechPoolSize = 8,
MinSpeechWaitTime = 2000, // We have to wait at LEAST this long before we can play ANY more speech
};
hhAISpeechHandler();
virtual ~hhAISpeechHandler() { }
virtual void Update(); // note: not named Think() on purpose, because we are NOT an entity
// Play speech from a given AI - returns true if speech was queued - DOES NOT mean it is guarenteed to play
bool PlaySpeech(idAI *source, idStr sndShader, int expireDelay = 1000, bool allowQueue = true);
// Finds the matching snd shader to the speechDesc given, as well as 0->1 of the frequency for this speech
// Returns FALSE if no possible match could be found.
bool GetSpeechSoundShader(idAI *source, idStr speechDesc, idStr &outClosestSndShd);
// From a sound shader name, return the frequency from 0->1
float GetSpeechFrequency(idStr sndShader);
// From a sound shader name, return the delay in MS of how long it takes to 'comprehend' the given shader.
// Example: Someone says "Take the shuttle!" and we want the person taking the shuttle to wait at least 1 second before they do it
// so it looks like they comprehended the command
int GetSpeechResponseDelay(idStr sndShader);
// Remove all queued speech
void ClearSpeech();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
protected:
hhAISpeech speechPool[SpeechPoolSize]; // Pool of all possible speech slots
hhAISpeech* GetFreeSpeech(); // Returns the first empty speech obj available in the pool
int ClearExpiredSpeech(); // Clears out any expired speech in the pool
enum TokenType
{
TT_Desire = 0, // The desire we are going to satisfy
TT_How, // How we are going to satisfy it
TT_What, // What entity we will use to satisfy it
TT_Total,
};
struct SpeechTokens
{
idStr tokens[TT_Total];
};
static float TokenMatchWeight[TT_Total];
static float TokenWildcardMatchWeight[TT_Total];
// Returns a 0+ rating of 'how' close the potential speech matches the desired speech
float GetMatchRating(SpeechTokens &desired, SpeechTokens &potential);
bool ExtractSpeechTokens(idStr speechDesc, SpeechTokens &outTokens);
// because I hate id's linked list
hhAISpeech *firstSpeech;
hhAISpeech *lastSpeech;
void AddSpeechToQueue(hhAISpeech *speech);
void RemoveSpeechFromQueue(hhAISpeech *speech);
int GetPoolIndex(hhAISpeech *obj) const;
int nextSpeechTime; // The time we are next allowed to play speech
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif
+533
View File
@@ -0,0 +1,533 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_UpdateTarget( "<updateTarget>" );
const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v" );
const idEventDef MA_GetCircleNode("getCircleNode", NULL, 'e' );
const idEventDef MA_SpinClouds("spinClouds", "f" );
const idEventDef MA_SetSeekScale("setSeekScale", "f" );
CLASS_DECLARATION(hhMonsterAI, hhSphereBoss)
EVENT( EV_UpdateTarget, hhSphereBoss::Event_UpdateTarget )
EVENT( EV_DirectMoveToPosition, hhSphereBoss::Event_DirectMoveToPosition )
EVENT( MA_GetCircleNode, hhSphereBoss::Event_GetCircleNode )
EVENT( MA_SpinClouds, hhSphereBoss::Event_SpinClouds )
EVENT( MA_SetSeekScale, hhSphereBoss::Event_SetSeekScale )
END_CLASS
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void hhSphereBoss::Spawn() {
for ( int i=0;i<10;i++ ) {
lastTargetPos.Append( vec3_zero );
}
nextShieldImpact = 0;
lastNodeIndex = -1;
PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) );
}
void hhSphereBoss::Event_UpdateTarget() {
for ( int i=0;i<10;i++ ) {
if ( i == 9 ) {
if ( enemy.IsValid() ) {
lastTargetPos[i] = enemy->GetOrigin();
} else {
lastTargetPos[i] = lastTargetPos[i - 1];
}
} else {
lastTargetPos[i] = lastTargetPos[i + 1];
}
}
PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) );
}
//overridden to allow custom accuracy/numbers per projectile def
idProjectile *hhSphereBoss::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) {
idVec3 muzzle;
idVec3 dir;
idVec3 start;
trace_t tr;
idBounds projBounds;
float distance;
const idClipModel *projClip;
float attack_accuracy;
float attack_cone;
float projectile_spread;
float diff;
float angle;
float spin;
idAngles ang;
int num_projectiles;
int i;
idMat3 axis;
idVec3 tmp;
idProjectile *lastProjectile;
//HUMANHEAD mdc - added to support multiple projectiles
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
int projIndex = FindProjectileInfo( desiredProjectileDef );
if( projIndex >= 0 ) {
SetCurrentProjectile( projIndex );
}
}
//HUMANHEAD END
if ( !projectileDef ) {
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
return NULL;
}
if ( projectileDef->GetFloat( "attack_accuracy" ) ) {
attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" );
} else {
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
}
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
if ( projectileDef->GetFloat( "projectile_spread" ) ) {
projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" );
} else {
projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
}
if ( projectileDef->GetFloat( "num_projectiles" ) ) {
num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" );
} else {
num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" );
}
GetMuzzle( jointname, muzzle, axis );
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, axis[ 0 ] );
}
lastProjectile = projectile.GetEntity();
if ( target != NULL ) {
tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
tmp.Normalize();
axis = tmp.ToMat3();
} else {
axis = viewAxis;
}
// rotate it because the cone points up by default
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
// make sure the projectile starts inside the monster bounding box
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
projClip = lastProjectile->GetPhysics()->GetClipModel();
projBounds = projClip->GetBounds().Rotate( axis );
// check if the owner bounds is bigger than the projectile bounds
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
start = muzzle + distance * viewAxis[ 0 ];
} else {
start = ownerBounds.GetCenter();
}
} else {
// projectile bounds bigger than the owner bounds, so just start it from the center
start = ownerBounds.GetCenter();
}
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
muzzle = tr.endpos;
// set aiming direction
if ( spawnArgs.GetBool( "lag_target", "0" ) ) {
dir = (lastTargetPos[0] - muzzle).ToNormal();
} else {
GetAimDir( muzzle, target, this, dir );
}
ang = dir.ToAngles();
// adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
if ( clampToAttackCone ) {
// clamp the attack direction to be within monster's attack cone so he doesn't do
// things like throw the missile backwards if you're behind him
diff = idMath::AngleDelta( ang.yaw, current_yaw );
if ( diff > attack_cone ) {
ang.yaw = current_yaw + attack_cone;
} else if ( diff < -attack_cone ) {
ang.yaw = current_yaw - attack_cone;
}
}
axis = ang.ToMat3();
float spreadRad = DEG2RAD( projectile_spread );
for( i = 0; i < num_projectiles; i++ ) {
// spread the projectiles out
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
dir.Normalize();
// launch the projectile
if ( !projectile.GetEntity() ) {
CreateProjectile( muzzle, dir );
}
lastProjectile = projectile.GetEntity();
lastProjectile->Launch( muzzle, dir, vec3_origin );
projectile = NULL;
}
TriggerWeaponEffects( muzzle,axis );
lastAttackTime = gameLocal.time;
//HUMANHEAD mdc - added to support multiple projectiles
projectile = NULL;
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
//HUMANHEAD END
return lastProjectile;
}
void hhSphereBoss::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
HandleNoGore();
if ( !AI_DEAD ) {
AI_DEAD = true;
state = GetScriptFunction( "state_SphereDeath" );
SetState( state );
SetWaitState( "" );
}
}
void hhSphereBoss::FlyTurn( void ) {
if ( AI_FACE_ENEMY ) {
TurnToward( enemy->GetOrigin() );
} else {
if ( move.moveCommand == MOVE_FACE_ENEMY ) {
TurnToward( lastVisibleEnemyPos );
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
} else if ( move.speed > 0.0f ) {
const idVec3 &vel = physicsObj.GetLinearVelocity();
if ( vel.ToVec2().LengthSqr() > 0.1f ) {
TurnToward( vel.ToYaw() );
}
}
}
Turn();
}
void hhSphereBoss::Event_SetSeekScale( float new_scale ) {
fly_seek_scale = new_scale;
}
void hhSphereBoss::Event_GetCircleNode() {
if ( !enemy.IsValid() ) {
idThread::ReturnEntity( NULL );
return;
}
idEntity *ent = NULL;
idEntity *bestEnt = NULL;
lastNodeIndex++;
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if ( !ent || !ent->IsType( hhAINode::Type ) ) {
continue;
}
if ( !ent->spawnArgs.GetBool( "circle_node" ) ) {
continue;
}
if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) {
bestEnt = ent;
break;
}
}
if ( !bestEnt ) {
lastNodeIndex = 0;
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if ( !ent || !ent->IsType( hhAINode::Type ) ) {
continue;
}
if ( !ent->spawnArgs.GetBool( "circle_node" ) ) {
continue;
}
if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) {
bestEnt = ent;
break;
}
}
}
if ( bestEnt ) {
idThread::ReturnEntity( bestEnt );
} else {
idThread::ReturnEntity( NULL );
}
}
void hhSphereBoss::Event_GetCombatNode() {
idEntity *ent = NULL;
float bestDist = 0.0f;
float dist;
idEntity *bestEnt = NULL;
idList<idEntity*> list;
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if ( !ent || !ent->spawnArgs.GetBool( "ainode" ) ) {
continue;
}
dist = (ent->GetOrigin() - GetOrigin()).Length();
if ( dist < spawnArgs.GetFloat( "node_dist", "400" ) ) {
continue;
}
list.Append( ent );
}
if ( list.Num() > 0 ) {
idThread::ReturnEntity( list[gameLocal.random.RandomInt(list.Num())] );
} else {
idThread::ReturnEntity( NULL );
}
}
void hhSphereBoss::Event_DirectMoveToPosition(const idVec3 &pos) {
StopMove(MOVE_STATUS_DONE);
DirectMoveToPosition(pos);
}
void hhSphereBoss::FlyMove( void ) {
idVec3 goalPos;
idVec3 oldorigin;
idVec3 newDest;
AI_BLOCKED = false;
if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
if ( AI_FACE_ENEMY ) {
StopMove( MOVE_STATUS_DONE );
} else {
AI_MOVE_DONE = true;
}
}
idVec3 vel = physicsObj.GetLinearVelocity();
goalPos = move.moveDest;
if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
StopMove( MOVE_STATUS_DONE );
}
if ( move.speed ) {
FlySeekGoal( vel, goalPos );
}
AddFlyBob( vel );
AdjustFlySpeed( vel );
physicsObj.SetLinearVelocity( vel );
// turn
FlyTurn();
// run the physics for this frame
oldorigin = physicsObj.GetOrigin();
physicsObj.UseFlyMove( true );
physicsObj.UseVelocityMove( false );
physicsObj.SetDelta( vec3_zero );
physicsObj.ForceDeltaMove( disableGravity );
RunPhysics();
monsterMoveResult_t moveResult = physicsObj.GetMoveResult();
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
DirectDamage( attack, enemy.GetEntity() );
} else {
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
} else if ( moveResult == MM_BLOCKED ) {
move.blockTime = gameLocal.time + 500;
AI_BLOCKED = true;
}
}
idVec3 org = physicsObj.GetOrigin();
if ( oldorigin != org ) {
TouchTriggers();
}
if ( ai_debugMove.GetBool() ) {
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 );
gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec );
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true );
gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true );
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
DrawRoute();
}
}
void hhSphereBoss::AdjustFlySpeed( idVec3 &vel ) {
float speed;
// apply dampening
float damp = spawnArgs.GetFloat( "fly_dampening", "0.01" );
vel -= vel * damp * MS2SEC( gameLocal.msec );
// gradually speed up/slow down to desired speed
speed = vel.Normalize();
speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec );
if ( speed < 0.0f ) {
speed = 0.0f;
} else if ( move.speed && ( speed > move.speed ) ) {
speed = move.speed;
}
vel *= speed;
}
bool hhSphereBoss::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const {
if ( move.moveType == MOVETYPE_SLIDE ) {
idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) );
bnds.TranslateSelf( physicsObj.GetOrigin() );
if ( bnds.ContainsPoint( pos ) ) {
return true;
}
} else {
if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) {
if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) {
return true;
}
} else {
idBounds bnds( idVec3( -64.0, -64.0f, -64.0f ), idVec3( 64.0, 64.0f, 64.0f ) );
bnds.TranslateSelf( physicsObj.GetOrigin() );
if ( bnds.ContainsPoint( pos ) ) {
return true;
}
}
}
return false;
}
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
void hhSphereBoss::LinkScriptVariables() {
hhMonsterAI::LinkScriptVariables();
LinkScriptVariable( AI_FACE_ENEMY );
LinkScriptVariable( AI_CAN_DAMAGE );
}
void hhSphereBoss::Event_SpinClouds( float shouldSpin ) {
if ( shouldSpin == 0.0f ) {
const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" );
while( kv ) {
idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
if ( ent ) {
ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), ent->spawnArgs.GetFloat( "shaderparm4", "0" ) );
}
kv = spawnArgs.MatchPrefix( "cloud", kv );
}
} else {
const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" );
while( kv ) {
idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
if ( ent ) {
ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), spawnArgs.GetFloat( "spin_value", "0.5" ) );
}
kv = spawnArgs.MatchPrefix( "cloud", kv );
}
}
}
void hhSphereBoss::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
//overridden to allow custom wound code
if ( !AI_CAN_DAMAGE && inflictor && spawnArgs.MatchPrefix("mineDamage") ) {
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone( true );
if ( gameLocal.time >= nextShieldImpact ) {
nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000);
idVec3 offset;
if ( attacker && inflictor ) {
offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" );
const char *defName = spawnArgs.GetString( "fx_shield_impact" );
idEntityFx *impactFx = SpawnFxLocal( defName, inflictor->GetOrigin() + offset, mat3_identity, &fxInfo, gameLocal.isClient );
if ( impactFx ) {
impactFx->Bind( this, true );
}
}
}
StartSound( "snd_shield_impact", SND_CHANNEL_BODY );
if ( inflictor && inflictor->IsType( idProjectile::Type ) ) {
inflictor->PostEventMS( &EV_Remove, 0 );
}
} else {
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone( true );
if ( gameLocal.time >= nextShieldImpact ) {
nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000);
idVec3 offset;
if ( attacker && inflictor ) {
offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" );
BroadcastFxInfoPrefixed( "fx_pain_impact", inflictor->GetOrigin() + offset, mat3_identity, &fxInfo );
}
}
}
bool mine_damage = false;
if ( !AI_CAN_DAMAGE && spawnArgs.MatchPrefix("mineDamage") != NULL ) {
const idKeyValue *kv = spawnArgs.MatchPrefix("mineDamage");
while( kv && kv->GetValue().Length() ) {
if ( !kv->GetValue().Icmp(damageDefName) ) {
mine_damage = true;
break;
}
kv = spawnArgs.MatchPrefix("mineDamage", kv);
}
if (!mine_damage) {
return;
}
}
if ( !mine_damage ) {
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
if ( !AI_CAN_DAMAGE && health > 0 ) {
SetState( GetScriptFunction( "state_Pain" ) );
SetWaitState( "" );
}
}
/*
=====================
hhSphereBoss::Save
=====================
*/
void hhSphereBoss::Save( idSaveGame *savefile ) const {
savefile->WriteInt( lastTargetPos.Num() );
savefile->WriteInt( nextShieldImpact );
for ( int i=0;i<lastTargetPos.Num(); i++) {
savefile->WriteVec3( lastTargetPos[i] );
}
}
/*
=====================
hhSphereBoss::Restore
=====================
*/
void hhSphereBoss::Restore( idRestoreGame *savefile ) {
int num = 0;
savefile->ReadInt( num );
savefile->ReadInt( nextShieldImpact );
lastTargetPos.SetNum( num );
for ( int i=0;i<num; i++) {
savefile->ReadVec3( lastTargetPos[i] );
}
}
void hhSphereBoss::AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ) {
//overridden to remove woundmanager hook and use custom wound code is Damage()
if ( AI_CAN_DAMAGE ) {
return hhMonsterAI::AddLocalMatterWound( jointNum, localOrigin, localNormal, localDir, damageDefIndex, collisionMaterial );
}
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+45
View File
@@ -0,0 +1,45 @@
#ifndef __PREY_AI_SPHEREBOSS_H__
#define __PREY_AI_SPHEREBOSS_H__
class hhSphereBoss : public hhMonsterAI {
public:
CLASS_PROTOTYPE(hhSphereBoss);
#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
void Event_UpdateTarget() {};
void Event_GetCombatNode() {};
void Event_GetCircleNode() {};
void Event_DirectMoveToPosition(const idVec3 &pos) {};
void Event_SpinClouds( float shouldSpin ) {};
void Event_SetSeekScale( float new_scale ) {};
#else
void Spawn();
void FlyTurn();
void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef );
void AdjustFlySpeed( idVec3 &vel );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void FlyMove( void );
bool ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const;
void LinkScriptVariables();
void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
void AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial );
void Event_UpdateTarget();
void Event_GetCombatNode();
void Event_GetCircleNode();
void Event_DirectMoveToPosition(const idVec3 &pos);
void Event_SpinClouds( float shouldSpin );
void Event_SetSeekScale( float new_scale );
protected:
idScriptBool AI_FACE_ENEMY;
idScriptBool AI_CAN_DAMAGE;
idList<idVec3> lastTargetPos;
int lastNodeIndex;
int nextShieldImpact;
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif
+104
View File
@@ -0,0 +1,104 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
#include "../../renderer/Model_local.h" // Hackish. Needed to access certain classes. Imitates game/anim/Blend.cpp
/*
===============
hhMD5Anim::hhMD5Anim
===============
*/
hhMD5Anim::hhMD5Anim(void) {
start = 0.0f;
end = 1.0f;
}
/*
==============================
hhMD5Anim::setLimits
Set the limits of the animation to play.
start and end should vary from 0 to 1
==============================
*/
void hhMD5Anim::SetLimits(float start, float end) const {
if (start <= end) {
this->start = start;
this->end = end;
}
else {
this->start = end;
this->end = start;
}
}
/*
==============================
hhMD5Anim::ConvertTimeToFrame
==============================
*/
void hhMD5Anim::ConvertTimeToFrame( int time, int cycleCount,
frameBlend_t &frame ) const {
float newTime;
float timeOffset;
timeOffset = animLength * start;
if (time < 0) {
newTime = timeOffset;
}
else {
newTime = time + timeOffset;
}
idMD5Anim::ConvertTimeToFrame(newTime, cycleCount, frame);
}
/*
==============================
hhMD5Anim::Length
==============================
*/
int hhMD5Anim::Length(void) const {
int intLength;
intLength = animLength * (end - start);
if ( ( intLength == 0 ) && ( end != start ) ) {
intLength = 1;
}
return( intLength );
}
/*
================
hhMD5Anim::Save
================
*/
void hhMD5Anim::Save( idSaveGame *savefile ) const {
savefile->WriteFloat( start );
savefile->WriteFloat( end );
}
/*
================
hhMD5Anim::Restore
================
*/
void hhMD5Anim::Restore( idRestoreGame *savefile ) {
savefile->ReadFloat( start );
savefile->ReadFloat( end );
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef __PREY_ANIM_MD5ANIM_H__
#define __PREY_ANIM_MD5ANIM_H__
// Forward declar
class idMD5Bone;
class hhMD5Anim : public idMD5Anim {
public:
hhMD5Anim(void);
void SetLimits(float start, float end) const;
virtual void ConvertTimeToFrame( int time, int cycleCount, frameBlend_t &frame ) const;
virtual int Length( void ) const;
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
protected:
mutable float start;
mutable float end;
};
#endif /* __PREY_ANIM_MD5ANIM_H__ */
+173
View File
@@ -0,0 +1,173 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
CLASS_DECLARATION( idForce, hhForce_Converge )
END_CLASS
hhForce_Converge::hhForce_Converge( void ) {
restoreFactor = 1.0f;
restoreForceSlack = 1.0f;
restoreTime = 0.1f;
ent = NULL;
physics = NULL;
axisEnt = NULL;
bShuttle = false;
}
hhForce_Converge::~hhForce_Converge( void ) {
}
void hhForce_Converge::Save(idSaveGame *savefile) const {
ent.Save(savefile);
axisEnt.Save(savefile);
savefile->WriteVec3( target );
savefile->WriteVec3( offset );
savefile->WriteInt( bodyID );
savefile->WriteFloat( restoreTime );
savefile->WriteFloat( restoreFactor );
savefile->WriteFloat( restoreForceSlack );
savefile->WriteBool( bShuttle );
}
void hhForce_Converge::Restore( idRestoreGame *savefile ) {
ent.Restore(savefile);
axisEnt.Restore(savefile);
savefile->ReadVec3( target );
savefile->ReadVec3( offset );
savefile->ReadInt( bodyID );
savefile->ReadFloat( restoreTime );
savefile->ReadFloat( restoreFactor );
savefile->ReadFloat( restoreForceSlack );
savefile->ReadBool( bShuttle );
physics = NULL; // HUMANHEAD mdl: Updated each frame
}
void hhForce_Converge::Evaluate( int time ) {
// Get the physics of ent each frame, in case their physics object was changed.
physics = ent.IsValid() ? ent->GetPhysics() : NULL;
// Apply convergent force
if (physics) {
idVec3 p = physics->GetOrigin( bodyID ) + offset * physics->GetAxis( bodyID );
idVec3 x = p - target;
if (axisEnt.IsValid()) {
idBounds bounds = ent->GetPhysics()->GetBounds();
idVec3 rightPt = ent->GetOrigin() + (ent->GetAxis()[0] * bounds[0].x);
idVec3 axisRightPt = axisEnt->GetOrigin() + (axisEnt->GetAxis()[1] * bounds[1].x);
idVec3 leftPt = ent->GetOrigin() + (ent->GetAxis()[0] * bounds[1].x);
idVec3 axisLeftPt = axisEnt->GetOrigin() + (axisEnt->GetAxis()[1] * bounds[0].x);
//gameRenderWorld->DebugLine(idVec4(1, 0, 0, 1), rightPt, axisRightPt, 100);
p = rightPt;
x = p - axisRightPt;
x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point
idVec3 v = physics->GetLinearVelocity();
float m = physics->GetMass();
float b = idMath::Sqrt(4*restoreFactor*m);
idVec3 force = -restoreFactor*x - b*v; // use with addforce, k=200000
physics->AddForce(bodyID, p, force * 0.5f ); // Great to show mass variations
//gameRenderWorld->DebugLine(idVec4(1, 0, 0, 1), leftPt, axisLeftPt, 100);
p = leftPt;
x = p - axisLeftPt;
x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point
force = axisLeftPt - leftPt;
force *= m * 4;
physics->AddForce(bodyID, p, force ); // Great to show mass variations
return;
}
x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point
if (ent->IsType(idAFEntity_Base::Type)) { // Actors use velocity method since their masses are unrealistic, ragdolls need it too
if (!ent->fl.tooHeavyForTractor) {
// Velocity method: cover distance in fixed time
idVec3 velocity = -x / restoreTime; // Use with SetLinearVelocity()
if (ent->IsType(idActor::Type)) { //if a player is affected by this force..
//..do not allow him to build up velocity into his ground plane
if (ent->GetPhysics()->IsType(idPhysics_Player::Type) && ent->GetPhysics()->HasGroundContacts()) {
velocity.x *= -ent->GetPhysics()->GetGravityNormal().x;
velocity.y *= -ent->GetPhysics()->GetGravityNormal().y;
velocity.z *= -ent->GetPhysics()->GetGravityNormal().z;
}
}
physics->SetLinearVelocity(velocity, bodyID);
}
else {
// Entities that are too heavy get no force, just give feedback force
}
} else {
float m = physics->GetMass();
if (bShuttle && m < 1500.0f) { // Different equation for picking up low mass objects with the shuttle
idVec3 v = physics->GetLinearVelocity();
float springFactor = restoreFactor*0.0002f;
float dampFactor = idMath::Sqrt(springFactor);
idVec3 force = (-x * springFactor * m) - (v * dampFactor * m);
physics->AddForce(bodyID, p, force );
} else { // Critically Damped spring: m*a = -k*x - b*v ; b = 2 * sqrt(m*k)
idVec3 v = physics->GetLinearVelocity();
float m = physics->GetMass();
float b = idMath::Sqrt(4.0f*restoreFactor*m);
idVec3 force = -restoreFactor*x - b*v; // use with addforce, k=200000
physics->AddForce(bodyID, p, force ); // Great to show mass variations
}
}
/* else { // Impulse method
idVec3 v = physics->GetLinearVelocity();
float b = idMath::Sqrt(4*restoreFactor);
impulse = -restoreFactor*x - b*v; // Use with applyimpulse, k=50000
physics->ApplyImpulse(bodyID, p, impulse ); // Worked for all but some moveables (k=50000)
}
else { // Mass scaled force method, applies varying force to keep constant velocity
idVec3 v = physics->GetLinearVelocity();
float m = physics->GetMass();
float b = idMath::Sqrt(4*restoreFactor);
impulse = (-restoreFactor*x - b*v) * m; // use with addforce, k=500
physics->AddForce(bodyID, p, impulse ); // Works for all but ragdolls (k=500)
}
}*/
}
}
void hhForce_Converge::RemovePhysics( const idPhysics *phys ) {
// physics of ent is stored each evaluate so we can compare pointers directly, rather than querying
// ent about it's physics. This is done because typically, physics objects are reported as removed
// during deconstruction of the entities.
if (ent.IsValid() && phys == physics) {
SetEntity(NULL);
}
}
void hhForce_Converge::SetRestoreTime( float time ) {
restoreTime = idMath::ClampFloat(0.02f, time, time);
}
void hhForce_Converge::SetTarget(idVec3 &newTarget) {
target = newTarget;
}
void hhForce_Converge::SetEntity(idEntity *entity, int id, const idVec3 &point) {
ent = entity;
bodyID = id;
offset = point;
physics = NULL;
}
void hhForce_Converge::SetAxisEntity(idEntity *entity) {
axisEnt = entity;
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef __FORCE_CONVERGE_H__
#define __FORCE_CONVERGE_H__
//===============================================================================
//
// Convergent force
//
//===============================================================================
class hhForce_Converge : public idForce {
CLASS_PROTOTYPE( hhForce_Converge );
public:
hhForce_Converge( void );
virtual ~hhForce_Converge( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void SetTarget(idVec3 &newTarget);
void SetEntity(idEntity *entity, int id=0, const idVec3 &point=vec3_origin);
idEntity * GetEntity() const { return ent.GetEntity(); }
void SetRestoreTime( float time );
void SetRestoreFactor( float factor ){ restoreFactor = factor; }
void SetRestoreSlack(float slack) { restoreForceSlack = slack; }
public: // common force interface
virtual void Evaluate( int time );
virtual void RemovePhysics( const idPhysics *phys );
void SetAxisEntity(idEntity *entity);
void SetShuttle(bool shuttle) { bShuttle = shuttle; }
private:
idEntityPtr<idEntity> ent; // Entity to apply forces to
idEntityPtr<idEntity> axisEnt;
idPhysics * physics; // physics object of entity during last evaluate
idVec3 target; // Point of convergence
idVec3 offset; // Entity local offset for force application
int bodyID; // Body ID for force application
float restoreTime; // Ideal convergence time
float restoreFactor; // Spring constant for restorative force
float restoreForceSlack; //rww - allow a linear buildup based on the distance of the target point
bool bShuttle; //mdl: Use an alternate spring equation for shuttle tractor beam
};
#endif
+100
View File
@@ -0,0 +1,100 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
/***********************************************************************
hhAFEntity
***********************************************************************/
// nla - Added to allow the mappers to do huge translations w/out too much bouncing
const idEventDef EV_EnableRagdoll( "enableRagdoll", "" );
const idEventDef EV_DisableRagdoll( "disableRagdoll", "" );
CLASS_DECLARATION( idAFEntity_Generic, hhAFEntity )
EVENT( EV_EnableRagdoll, hhAFEntity::Event_EnableRagdoll )
EVENT( EV_DisableRagdoll, hhAFEntity::Event_DisableRagdoll )
END_CLASS
void hhAFEntity::Spawn( void ) {
fl.takedamage = !spawnArgs.GetBool("noDamage");
if (spawnArgs.FindKey("gravity")) {
PostEventMS(&EV_ResetGravity, 100); // Post after first think, when gravity is reset by UpdateGravity()
}
}
void hhAFEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) {
return;
}
idAFEntity_Generic::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
//This is so ragdolls shutdown when attached to movers that are outside of the pvs.
void hhAFEntity::DormantBegin() {
idEntity::DormantBegin();
Event_DisableRagdoll();
}
void hhAFEntity::DormantEnd() {
idEntity::DormantEnd();
Event_EnableRagdoll();
}
void hhAFEntity::Event_EnableRagdoll() {
af.GetPhysics()->Thaw();
}
void hhAFEntity::Event_DisableRagdoll() {
af.GetPhysics()->Freeze();
}
/***********************************************************************
hhAFEntity_WithAttachedHead
***********************************************************************/
CLASS_DECLARATION( idAFEntity_WithAttachedHead, hhAFEntity_WithAttachedHead )
EVENT( EV_EnableRagdoll, hhAFEntity_WithAttachedHead::Event_EnableRagdoll )
EVENT( EV_DisableRagdoll, hhAFEntity_WithAttachedHead::Event_DisableRagdoll )
END_CLASS
void hhAFEntity_WithAttachedHead::Spawn( void ) {
fl.takedamage = !spawnArgs.GetBool("noDamage");
if (spawnArgs.FindKey("gravity")) {
PostEventMS(&EV_ResetGravity, 100); // Post after first think, when gravity is reset by UpdateGravity()
}
}
void hhAFEntity_WithAttachedHead::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) {
return;
}
idAFEntity_WithAttachedHead::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
//This is so ragdolls shutdown when attached to movers that are outside of the pvs.
void hhAFEntity_WithAttachedHead::DormantBegin() {
idEntity::DormantBegin();
Event_DisableRagdoll();
}
void hhAFEntity_WithAttachedHead::DormantEnd() {
idEntity::DormantEnd();
Event_EnableRagdoll();
}
void hhAFEntity_WithAttachedHead::Event_EnableRagdoll() {
af.GetPhysics()->Thaw();
}
void hhAFEntity_WithAttachedHead::Event_DisableRagdoll() {
af.GetPhysics()->Freeze();
}
+49
View File
@@ -0,0 +1,49 @@
#ifndef __GAME_AFS_H__
#define __GAME_AFS_H__
extern const idEventDef EV_CursorDrop;
extern const idEventDef EV_CursorDone;
/***********************************************************************
hhAFEntity
***********************************************************************/
class hhAFEntity : public idAFEntity_Generic {
public:
CLASS_PROTOTYPE( hhAFEntity );
void Spawn();
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void DormantBegin();
virtual void DormantEnd();
void Event_EnableRagdoll( void );
void Event_DisableRagdoll( void );
};
/***********************************************************************
hhAFEntity_WithAttachedHead
***********************************************************************/
class hhAFEntity_WithAttachedHead : public idAFEntity_WithAttachedHead {
public:
CLASS_PROTOTYPE( hhAFEntity_WithAttachedHead );
void Spawn();
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void DormantBegin();
virtual void DormantEnd();
void Event_EnableRagdoll();
void Event_DisableRagdoll();
};
#endif // __GAME_AFS_H__
+76
View File
@@ -0,0 +1,76 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//==========================================================================
//
// hhAlarmLight
//
//==========================================================================
CLASS_DECLARATION(idLight, hhAlarmLight)
EVENT(EV_Activate, hhAlarmLight::Event_Activate)
END_CLASS
void hhAlarmLight::Spawn() {
bAlarmOn = false;
fl.takedamage = false; // Never take damage
/* Shader parms set up like this (so the editor shows the light being on):
parm6 parm7
editor(on): 0 0
spawn(off): 1 0
trigger(on): 1 1
broken(off): 1 0
*/
SetParmState(1.0f, 0.0f);
// setup the clipModel
GetPhysics()->SetContents( CONTENTS_SOLID );
}
void hhAlarmLight::Save(idSaveGame *savefile) const {
savefile->WriteBool( bAlarmOn );
}
void hhAlarmLight::Restore( idRestoreGame *savefile ) {
savefile->ReadBool( bAlarmOn );
}
void hhAlarmLight::TurnOn() {
if (!bAlarmOn) {
bAlarmOn = true;
StartSound("snd_alarm", SND_CHANNEL_BODY2, 0, true, NULL);
SetParmState(1.0f, 1.0f);
}
}
void hhAlarmLight::TurnOff() {
if (bAlarmOn) {
bAlarmOn = false;
StopSound(SND_CHANNEL_BODY2, true);
SetParmState(0.0f, 1.0f);
}
}
void hhAlarmLight::SetParmState(float value6, float value7) {
SetShaderParm(6, value6);
SetShaderParm(7, value7);
SetLightParm(6, value6);
SetLightParm(7, value7);
}
void hhAlarmLight::Event_Activate(idEntity *activator) {
if (bAlarmOn) {
if (activator && activator->IsType(hhPlayer::Type)) {
TurnOff();
}
}
else {
TurnOn();
}
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef __GAME_ALARMLIGHT_H__
#define __GAME_ALARMLIGHT_H__
class hhAlarmLight : public idLight {
CLASS_PROTOTYPE( hhAlarmLight );
public:
void Spawn();
void TurnOn();
void TurnOff();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
protected:
void SetParmState(float value6, float value7);
void Event_Activate(idEntity *activator);
bool bAlarmOn;
};
#endif
+244
View File
@@ -0,0 +1,244 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
/*
============
hhAnim::AddFrameCommandExtra
============
*/
bool hhAnim::AddFrameCommandExtra( idToken &token, frameCommand_t &fc, idLexer &src, idStr &errorText ) {
errorText = "";
if ( token == "stopSnd" ) {
fc.type = FC_STOPSND;
return( true );
} else if ( token == "stopSnd_voice" ) {
fc.type = FC_STOPSND_VOICE;
return( true );
} else if ( token == "stopSnd_voice2" ) {
fc.type = FC_STOPSND_VOICE2;
return( true );
} else if ( token == "stopSnd_body" ) {
fc.type = FC_STOPSND_BODY;
return( true );
} else if ( token == "stopSnd_body2" ) {
fc.type = FC_STOPSND_BODY2;
return( true );
} else if ( token == "stopSnd_body3" ) {
fc.type = FC_STOPSND_BODY3;
return( true );
} else if ( token == "stopSnd_weapon" ) {
fc.type = FC_STOPSND_WEAPON;
return( true );
} else if ( token == "stopSnd_item" ) {
fc.type = FC_STOPSND_ITEM;
return( true );
} else if ( token == "event_args" ) {
fc.type = FC_EVENT_ARGS;
src.ParseRestOfLine( token );
idStr error = InitFrameCommandEvent( fc, token );
if( error.Length() ) {
errorText = error.c_str();
return( false );
}
return( true );
} else if ( token == "launch_missile" ) {
/* HUMANHEAD JRM - Because we parse out the joint when launched
if ( !modelDef->FindJoint( cmd ) ) {
return va( "Joint '%s' not found", cmd.c_str() );
}
*/
src.ParseRestOfLine( token );
/* HUMANHEAD JRM - need the rest of the line not just one token
if( !src.ReadTokenOnLine( &token ) ) {
errorText = va( "Unexpected end of line" );
return( true );
}
*/
fc.type = FC_LAUNCHMISSILE;
fc.string = new idStr( token );
return( true );
} else if( token == "launch_altMissile" ) {
if( !src.ReadTokenOnLine( &token ) ) {
errorText = va( "Unexpected end of line" );
return( true );
}
fc.type = FC_LAUNCHALTMISSILE;
fc.string = new idStr( token );
return( true );
} else if ( token == "launch_missile_bonedir" ) {
/* HUMANHEAD JRM - we want the whole token
if( !src.ReadTokenOnLine( &token ) ) {
errorText = va( "Unexpected end of line" );
return( true );
}
*/
src.ParseRestOfLine( token );
fc.type = FC_LAUNCHMISSILE_BONEDIR;
fc.string = new idStr( token );
return( true );
} else if ( token == "leftfootprint" ) {
fc.type = FC_LEFTFOOTPRINT;
return( true );
} else if ( token == "rightfootprint" ) {
fc.type = FC_RIGHTFOOTPRINT;
return( true );
} else if ( token == "mood" ) {
fc.type = FC_MOOD;
if( !src.ReadTokenOnLine( &token ) ) {
fc.string = new idStr( "" );
return( true );
}
fc.string = new idStr( token );
return( true );
} else if ( token == "kick_obstacle" ) {
if( !src.ReadTokenOnLine( &token ) ) {
errorText = va( "Unexpected end of line" );
return( true );
}
fc.type = FC_KICK_OBSTACLE;
fc.string = new idStr( token );
return( true );
} else if ( token == "trigger_anim_ent" ) {
/*
if( !src.ReadTokenOnLine( &token ) ) {
errorText = va( "Unexpected end of line" );
return( true );
}
*/
fc.type = FC_TRIGGER_ANIM_ENT;
return( true );
} else if ( token == "set_key" ) {
fc.type = FC_SETKEY;
if( !src.ParseRestOfLine( token ) ) {
errorText = va( "Unexpected end of line" );
return ( true );
}
if( !fc.parmList ) {
fc.parmList = new idList<idStr>;
}
hhUtils::SplitString( token, *fc.parmList, ' ' );
if( fc.parmList->Num() != 2 ) {
errorText = va( "Invalid number of arguments for setkey frame-command" );
return ( true );
}
return ( true );
}
return( false );
}
/*
============
hhAnim::CallFrameCommandsExtra
============
*/
bool hhAnim::CallFrameCommandsExtra( const frameCommand_t &command, idEntity *ent ) const {
switch( command.type ) {
case FC_EVENT_ARGS: {
if ( command.function && command.parmList && command.function->eventdef ) {
ent->ProcessEvent( command.function->eventdef, (int)command.parmList );
}
return( true );
}
case FC_LAUNCHALTMISSILE: {
//ent->ProcessEvent( &AI_AttackAltMissile, command.string->c_str(), NULL );
return( true );
}
case FC_STOPSND:
case FC_STOPSND_VOICE:
case FC_STOPSND_VOICE2:
case FC_STOPSND_BODY:
case FC_STOPSND_BODY2:
case FC_STOPSND_BODY3:
case FC_STOPSND_WEAPON:
case FC_STOPSND_ITEM: {
ent->StopSound( s_channelType(command.type - FC_STOPSND), false ); //rww - do not broadcast
return( true );
}
case FC_HIDE: {
ent->ProcessEvent( &EV_Hide );
return( true );
}
case FC_MOOD: {
if(command.string) {
ent->ProcessEvent( &AI_SetAnimPrefix, command.string->c_str());
} else {
ent->ProcessEvent( &AI_SetAnimPrefix, " ");
}
return( true );
}
case FC_LEFTFOOTPRINT: {
ent->ProcessEvent( &EV_FootprintLeft );
return( true );
}
case FC_RIGHTFOOTPRINT: {
ent->ProcessEvent( &EV_FootprintRight );
return( true );
}
case FC_LAUNCHMISSILE: { // JRM: changed to AttackMissileEx (so we can pass in projectile)
ent->ProcessEvent( &MA_AttackMissileEx, command.string->c_str(),0 );
return( true );
}
case FC_LAUNCHMISSILE_BONEDIR: { // JRM: need so we can launch missiles
ent->ProcessEvent( &MA_AttackMissileEx, command.string->c_str(),1 );
return( true );
}
case FC_SETKEY: {//mdc: added to be able to set keys from frame commands
if( command.parmList && command.parmList->Num() == 2 ) {
ent->spawnArgs.Set( (*command.parmList)[0].c_str(), (*command.parmList)[1].c_str() );
}
return ( true );
}
}
return( false );
}
/*
=====================
hhAnim::InitFrameCommandEvent
=====================
*/
idStr hhAnim::InitFrameCommandEvent( frameCommand_t &command, const idStr& cmdStr ) const {
idStr error;
if( !command.parmList ) {
command.parmList = new idList<idStr>;
}
hhUtils::SplitString( cmdStr, *command.parmList, ' ' );
// Find the function
command.function = gameLocal.program.FindFunction( (*command.parmList)[0].c_str(), gameLocal.program.FindType((*command.parmList)[0].c_str()) );
if( !command.function || !command.function->eventdef || command.function->eventdef->GetNumArgs() != 1 ) {
error = va("Invalid event('%s') for frameCommands event or event_args\n", (*command.parmList)[0].c_str() );
}
//Remove function name from the list
command.parmList->RemoveIndex( 0 );
//If no parms are needed, no need to waste memory
if( command.parmList->Num() <= 0 ) {
SAFE_DELETE_PTR( command.parmList );
}
return error;
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef __PREY_GAME_ANIM_H__
#define __PREY_GAME_ANIM_H__
class hhAnim : public idAnim {
public:
hhAnim() : idAnim() { exactMatch = false; }
hhAnim( const idDeclModelDef *modelDef, const idAnim *anim ) : idAnim( modelDef, anim ) { }
virtual bool AddFrameCommandExtra( idToken &token, frameCommand_t &fc, idLexer &src, idStr &errorText );
virtual bool CallFrameCommandsExtra( const frameCommand_t &command, idEntity *ent ) const;
bool exactMatch;
protected:
virtual idStr InitFrameCommandEvent( frameCommand_t &command, const idStr& cmdStr ) const;
};
#endif /* __PREY_GAME_ANIM_H__ */
+276
View File
@@ -0,0 +1,276 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
/*
============
hhAnimBlend::hhAnimBlend
============
*/
hhAnimBlend::hhAnimBlend() {
frozen = false;
freezeStart = -1;
freezeEnd = -1;
freezeCurrent = -1;
rotateTime = -1;
rotateEvent = NULL;
}
/*
=====================
hhAnimBlend::FrameHasChanged
=====================
*/
bool hhAnimBlend::FrameHasChanged( int currentTime ) const {
UpdateFreezeTime( currentTime );
if ( frozen ) {
return false;
}
return( idAnimBlend::FrameHasChanged( currentTime ) );
}
/*
=====================
hhAnimBlend::AnimTime
=====================
*/
int hhAnimBlend::AnimTime( int currentTime ) const {
if ( animNum ) {
UpdateFreezeTime( currentTime );
}
return( idAnimBlend::AnimTime( currentTime ) );
}
/*
=====================
hhAnimBlend::GetFrameNumber
=====================
*/
int hhAnimBlend::GetFrameNumber( int currentTime ) const {
//gameLocal.Printf( "In FRAME NUM\n" );
UpdateFreezeTime( currentTime );
return( idAnimBlend::GetFrameNumber( currentTime ) );
}
/*
=====================
hhAnimBlend::CallFrameCommands
=====================
*/
void hhAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const {
if ( frozen ) {
return;
}
idAnimBlend::CallFrameCommands( ent, fromtime, totime );
}
/*
=====================
hhAnimBlend::BlendAnim
=====================
*/
bool hhAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const {
UpdateFreezeTime( currentTime );
return( idAnimBlend::BlendAnim( currentTime, channel, numJoints, blendFrame, blendWeight, removeOriginOffset, overrideBlend, printInfo ) );
}
/*
=====================
hhAnimBlend::BlendOrigin
=====================
*/
void hhAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const {
//gameLocal.Printf( "BLEND ORIGIN\n" );
UpdateFreezeTime( currentTime );
if ( frozen ) {
return;
}
idAnimBlend::BlendOrigin( currentTime, blendPos, blendWeight, removeOriginOffset );
}
/*
=====================
hhAnimBlend::BlendDelta
=====================
*/
void hhAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const {
if ( frozen ) {
return;
}
idAnimBlend::BlendDelta( fromtime, totime, blendDelta, blendWeight );
}
/*
=====================
hhAnimBlend::AddBounds
=====================
*/
bool hhAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const {
UpdateFreezeTime( currentTime );
if ( frozen ) {
return false;
}
return( idAnimBlend::AddBounds( currentTime, bounds, removeOriginOffset ) );
}
/*
===============
hhAnimBlend::UpdateFreezeTime
===============
*/
void hhAnimBlend::UpdateFreezeTime( int currentTime ) const {
if ( !frozen ) {
return;
}
if ( currentTime > freezeCurrent ) {
starttime += currentTime - freezeCurrent;
endtime += currentTime - freezeCurrent;
freezeCurrent = currentTime;
}
}
extern const idEventDef EV_CheckThaw;
/*
================
hhAnimBlend::Freeze
Freeze the animation
Inputs: currentTime - The current time of freeze
freezeEnd - Delta time to end the freeze. Set to -1 if will thaw manually
Outputs: true if frozen successfully, false otherwise
HUMANHEAD nla
================
*/
bool hhAnimBlend::Freeze( int currentTime, idEntity *owner, int aFreezeEnd ) {
if ( frozen ) {
return( false );
}
frozen = true;
freezeStart = currentTime;
freezeCurrent = currentTime;
freezeEnd = currentTime + aFreezeEnd;
// Post the thaw event if time is known
if ( owner ) {
owner->PostEventMS( &EV_CheckThaw, aFreezeEnd );
}
else {
gameLocal.Warning( "Freeze called on an animator with no owner!" );
}
return( true );
}
/*
===============
hhAnimBlend::Freeze
HUMANHEAD nla
===============
*/
bool hhAnimBlend::Freeze( int currentTime, idEntity *owner ) {
return( Freeze( currentTime, owner, -1 ) );
}
/*
===============
hhAnimBlend::Thaw
HUMANHEAD nla
===============
*/
bool hhAnimBlend::Thaw( int currentTime ) {
if ( !frozen ) {
return( false );
}
UpdateFreezeTime( currentTime );
frozen = false;
return( true );
}
/*
===============
hhAnimBlend::ThawIfTime
HUMANHEAD nla
===============
*/
bool hhAnimBlend::ThawIfTime( int currentTime ) {
if ( !frozen ) {
return( false );
}
if ( freezeEnd < 0 ) {
return( false );
}
if ( currentTime >= freezeEnd ) {
return( Thaw( currentTime ) );
}
return( false );
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef __PREY_GAME_ANIMBLEND_H__
#define __PREY_GAME_ANIMBLEND_H__
#endif /* __PREY_GAME_ANIMBLEND_H__ */
+287
View File
@@ -0,0 +1,287 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_PlayCycle( "<playCycle>", "ds" );
CLASS_DECLARATION( hhAnimatedEntity, hhAnimDriven )
EVENT( EV_PlayCycle, hhAnimDriven::Event_PlayCycle )
END_CLASS
//==============
// hhAnimDriven::Spawn
//==============
void hhAnimDriven::Spawn() {
passenger = NULL;
hadPassenger = false;
spawnTime = gameLocal.time;
// We are gonna move the guy manually, turn off having the anim move him
GetAnimator()->RemoveOriginOffset( true );
// How do we deal with our owner?
physicsAnim.SetSelf( this );
if( spawnArgs.GetBool( "solid", "0" ) ) {
fl.takedamage = true;
physicsAnim.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
GetPhysics()->SetContents( CONTENTS_SOLID );
} else {
fl.takedamage = false;
GetPhysics()->SetContents( 0 );
physicsAnim.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
GetPhysics()->UnlinkClip();
}
// move up to make sure the monster is at least an epsilon above the floor
physicsAnim.SetOrigin( GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
SetPhysics( &physicsAnim );
//? Have this start at a set time afterwards?
int animLength = PlayAnim( ANIMCHANNEL_ALL, "initial" );
PostEventMS( &EV_PlayCycle, animLength, ANIMCHANNEL_ALL, "move" );
float delta_min = spawnArgs.GetFloat( "delta_scale_min", "1.0" );
float delta_max = spawnArgs.GetFloat( "delta_scale_max", "1.0" );
if ( delta_min != 1.0f || delta_max != 1.0f ) {
deltaScale.x = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() );
deltaScale.y = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() );
deltaScale.z = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() );
} else {
deltaScale = idVec3( 1.0f, 1.0f, 1.0f );
}
BecomeActive( TH_TICKER );
}
void hhAnimDriven::Save(idSaveGame *savefile) const {
savefile->WriteStaticObject( physicsAnim );
passenger.Save(savefile);
savefile->WriteInt( spawnTime );
savefile->WriteBool( hadPassenger );
}
void hhAnimDriven::Restore( idRestoreGame *savefile ) {
savefile->ReadStaticObject( physicsAnim );
passenger.Restore(savefile);
savefile->ReadInt( spawnTime );
savefile->ReadBool( hadPassenger );
}
//=============
// hhAnimDriven::SetPassenger
//=============
void hhAnimDriven::SetPassenger( idEntity *newPassenger, bool orientFromPassenger ) {
idVec3 origin;
idMat3 axis;
//?! Right now handle only 1 passenger. If we have one, warn and exit.
if ( passenger.IsValid() ) {
const char *oldName = passenger->GetName();
const char *newName = "NULL";
if ( newPassenger ) {
newName = newPassenger->GetName();
}
gameLocal.Warning( "Error: hhAnimDriven::SetPassenger tried to add passenger %s but already had %s\n",
newName, oldName );
return;
}
passenger = newPassenger;
hadPassenger = true;
fl.takedamage = true;
if ( orientFromPassenger ) {
origin = passenger->GetOrigin();
axis = passenger->GetAxis();
}
GetPhysics()->SetClipModel( new idClipModel( passenger->GetPhysics()->GetClipModel() ), 1.0f );
GetPhysics()->SetContents( passenger->GetPhysics()->GetContents() );
GetPhysics()->SetClipMask( passenger->GetPhysics()->GetClipMask() );
if ( orientFromPassenger ) {
SetOrigin( origin );
SetAxis( axis );
}
passenger->Bind( this, true );
}
//==============
// hhAnimDriven::Think
//==============
void hhAnimDriven::Think() {
idVec3 delta;
// Done move unless we have a passenger. (Give us like 10 secs to get one)
if ( !passenger.IsValid() ) {
// If we had a passenger, they removed themselves, and so should we :)
if ( hadPassenger ) {
PostEventMS( &EV_Remove, 0 );
}
// If never had a passenger for a second, remove ourselves
else if ( gameLocal.time - spawnTime > 1000 ) {
PostEventMS( &EV_Remove, 0 );
gameLocal.Warning( "hhAnimDriven %s existed for a second w/out a passenger. Removing.", GetName() );
}
return;
}
// Move them based on their anim
GetAnimator()->GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta );
delta *= GetAxis();
delta.x *= deltaScale.x;
delta.y *= deltaScale.y;
delta.z *= deltaScale.z;
physicsAnim.SetDelta( delta );
// gameLocal.Printf( "%d Doing %s\n", gameLocal.GetTime(), delta.ToString() );
hhAnimatedEntity::Think();
}
//============
//
//============
int hhAnimDriven::PlayAnim( int channel, const char *animName ) {
int animIndex;
int length;
animIndex = GetAnimator()->GetAnim( animName );
if ( !animIndex ) {
return( 0 );
}
//gameLocal.Printf( "Playing Anim %s\n", animName );
GetAnimator()->PlayAnim( channel, animIndex, gameLocal.GetTime(), 0 );
length = GetAnimator()->CurrentAnim( channel )->GetEndTime() - gameLocal.GetTime();
return( length );
}
//============
// hhAnimDrive::Event_PlayCycle()
//============
void hhAnimDriven::Event_PlayCycle( int channel, const char *animName ) {
PlayCycle( channel, animName );
}
//============
//
//============
bool hhAnimDriven::PlayCycle( int channel, const char *animName ) {
int animIndex;
animIndex = GetAnimator()->GetAnim( animName );
if ( !animIndex ) {
return( false );
}
//gameLocal.Printf( "Cycling Anim %s\n", animName );
GetAnimator()->CycleAnim( channel, animIndex, gameLocal.GetTime(), 0 );
return( true );
}
/*
================
hhAnimDriven::ClearAllAnims
================
*/
void hhAnimDriven::ClearAllAnims() {
GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 );
}
//===========
//
//============
bool hhAnimDriven::Collide( const trace_t &collision, const idVec3 &velocity ) {
// If we hit something, and have a passenger, pass along the message
if( passenger.IsValid() ) {
if( !passenger->Collide(collision, velocity) ) {
return( false );
}
passenger->Unbind();
} //. We have a valid passenger
// If we made it this far, assume the collision is for real, and
// remove ourselves from interacting with the world
fl.takedamage = false;
GetPhysics()->SetContents( 0 );
GetPhysics()->UnlinkClip();
ClearAllAnims();
PostEventMS( &EV_Remove, 0 );
return( true );
}
bool hhAnimDriven::AllowCollision( const trace_t& collision ) {
if ( !spawnArgs.GetBool( "projectile_collision", "1" ) ) {
idEntity* ent = gameLocal.entities[ collision.c.entityNum ];
if ( !ent || ent->IsType( idProjectile::Type ) ) {
return false;
}
}
return true;
}
void hhAnimDriven::UpdateAnimation( void ) {
PROFILE_SCOPE("Animation", PROFMASK_NORMAL); // HUMANHEAD pdm
// don't do animations if they're not enabled
if ( !( thinkFlags & TH_ANIMATE ) ) {
return;
}
// is the model an MD5?
if ( !animator.ModelHandle() ) {
// no, so nothing to do
return;
}
// call any frame commands that have happened in the past frame
if ( !fl.hidden ) {
animator.ServiceAnims( gameLocal.previousTime, gameLocal.time );
}
// if the model is animating then we have to update it
if ( !animator.FrameHasChanged( gameLocal.time ) ) {
// still fine the way it was
return;
}
// update the renderEntity
UpdateVisuals();
// the animation is updated
animator.ClearForceUpdate();
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef __PREY_GAME_ANIMDRIVEN_H__
#define __PREY_GAME_ANIMDRIVEN_H__
// Class name by Jimmy! :)
class hhAnimDriven : public hhAnimatedEntity {
public:
CLASS_PROTOTYPE( hhAnimDriven );
void Spawn();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Think();
void SetPassenger( idEntity *newPassenger, bool orientFromPassenger = true );
// Events
void Event_PlayCycle( int channel, const char *animName );
bool Collide( const trace_t &collision, const idVec3 &velocity );
int PlayAnim( int channel, const char *animName );
bool PlayCycle( int channel, const char *animName );
void ClearAllAnims();
void UpdateAnimation();
bool AllowCollision( const trace_t& collision );
protected:
hhPhysics_Delta physicsAnim;
idEntityPtr<idEntity> passenger;
int spawnTime;
bool hadPassenger;
idVec3 deltaScale; //scale anim delta movement by this
};
#endif /* __PREY_GAME_ANIMDRIVEN_H__ */
+424
View File
@@ -0,0 +1,424 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_CheckCycleRotate( "<checkCycleRotate>" );
const idEventDef EV_CheckThaw( "<checkThaw>" );
const idEventDef EV_SpawnFxAlongBone( "spawnFXAlongBone", "d" );
const idEventDef EV_Silent( "<silent>", NULL );
CLASS_DECLARATION( idAnimatedEntity, hhAnimatedEntity )
EVENT( EV_CheckCycleRotate, hhAnimatedEntity::Event_CheckAnimatorCycleRotate )
EVENT( EV_CheckThaw, hhAnimatedEntity::Event_CheckAnimatorThaw )
EVENT( EV_SpawnFxAlongBone, hhAnimatedEntity::Event_SpawnFXAlongBone )
EVENT( EV_Thread_SetSilenceCallback,hhAnimatedEntity::Event_SetSilenceCallback )
EVENT( EV_Silent, hhAnimatedEntity::Event_Silent )
END_CLASS
/*
==============
hhAnimatedEntity::Spawn
==============
*/
void hhAnimatedEntity::Spawn( void ) {
hasFlapInfo = false;
// nla - Added to allow for 1 frame anims to play/proper bone initialization
if ( GetAnimator() ) {
int anim = GetAnimator()->GetAnim("init");
if ( anim ) {
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0);
GetAnimator()->ForceUpdate();
UpdateModel();
}
}
}
void hhAnimatedEntity::Save(idSaveGame *savefile) const {
savefile->WriteInt( waitingThread );
savefile->WriteInt( silentTimeOffset );
savefile->WriteInt( nextSilentTime );
savefile->WriteFloat( lastAmplitude );
}
void hhAnimatedEntity::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( waitingThread );
savefile->ReadInt( silentTimeOffset );
savefile->ReadInt( nextSilentTime );
savefile->ReadFloat( lastAmplitude );
hasFlapInfo = false;
}
/*
==============
hhAnimatedEntity::hhAnimatedEntity
==============
*/
hhAnimatedEntity::hhAnimatedEntity() {
// WaitForSilence support
waitingThread = 0;
silentTimeOffset = 0;
nextSilentTime = 0;
lastAmplitude = 0.0f;
hasFlapInfo = false;
}
/*
==============
hhAnimatedEntity::~hhAnimatedEntity
==============
*/
hhAnimatedEntity::~hhAnimatedEntity() {
Event_Silent();
}
/*
==============
hhAnimatedEntity::FillDebugVars
==============
*/
void hhAnimatedEntity::FillDebugVars(idDict *args, int page) {
switch(page) {
case 1:
args->SetInt("anims", GetAnimator()->NumAnims());
break;
}
idAnimatedEntity::FillDebugVars(args, page);
}
/*
==============
hhAnimatedEntity::Think
==============
*/
void hhAnimatedEntity::Think() {
idAnimatedEntity::Think();
//HUMANHEAD: aob - moved from idActor
UpdateWounds();
//HUMANHEAD END
}
/*
=====================
hhAnimatedEntity::GetAnimator
=====================
*/
hhAnimator *hhAnimatedEntity::GetAnimator( void ) {
return &animator;
}
/*
=====================
hhAnimatedEntity::GetAnimator
=====================
*/
const hhAnimator *hhAnimatedEntity::GetAnimator( void ) const {
return &animator;
}
/*
=====================
hhAnimatedEntity::GetJointWorldTransform
=====================
*/
bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) {
return idAnimatedEntity::GetJointWorldTransform( jointHandle, currentTime, offset, axis );
}
/*
=====================
hhAnimatedEntity::GetJointWorldTransform
=====================
*/
bool hhAnimatedEntity::GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ) {
return GetJointWorldTransform( GetAnimator()->GetJointHandle(jointName), gameLocal.GetTime(), offset, axis );
}
/*
=====================
hhAnimatedEntity::GetJointWorldTransform
=====================
*/
bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ) {
return GetJointWorldTransform( jointHandle, gameLocal.GetTime(), offset, axis );
}
/*
==============
hhAnimatedEntity::UpdateWounds
==============
*/
void hhAnimatedEntity::UpdateWounds( void ) {
}
/*
==================
idEntity::SpawnFXAlongBonePrefixLocal
==================
*/
void hhAnimatedEntity::SpawnFxAlongBonePrefixLocal( const idDict* dict, const char* fxKeyPrefix, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef ) {
idVec3 bonePos;
idMat3 boneAxis;
for( const idKeyValue* kv = dict->MatchPrefix(bonePrefix); kv; kv = dict->MatchPrefix(bonePrefix, kv) ) {
if( !kv->GetValue().Length() ) {
continue;
}
GetJointWorldTransform( kv->GetValue().c_str(), bonePos, boneAxis );
SpawnFXPrefixLocal( dict, fxKeyPrefix, bonePos, boneAxis, fxInfo, eventDef );
}
}
/*
=================
hhAnimatedEntity::SpawnFXAlongBone
=================
*/
void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefix( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) {
return;
}
idVec3 bonePos;
idMat3 boneAxis;
hhFxInfo localFxInfo( fxInfo );
localFxInfo.SetEntity( this );
localFxInfo.RemoveWhenDone( true );
const idKeyValue* keyValue = args->MatchPrefix( bonePrefix );
while( keyValue && keyValue->GetValue().Length() ) {
if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) {
//AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized
localFxInfo.SetNormal( boneAxis[0].ToNormal() );
localFxInfo.SetBindBone( keyValue->GetValue().c_str() );
BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef, broadcast );
}
keyValue = args->MatchPrefix( bonePrefix, keyValue );
}
}
/*
=================
hhAnimatedEntity::SpawnFXAlongBone
mdl
=================
*/
void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefixUnique( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) {
return;
}
idVec3 bonePos;
idMat3 boneAxis;
hhFxInfo localFxInfo( fxInfo );
localFxInfo.SetEntity( this );
localFxInfo.RemoveWhenDone( true );
const idKeyValue* keyValue = args->MatchPrefix( bonePrefix );
idStr postfix;
int prefixLen = strlen( bonePrefix );
while( keyValue && keyValue->GetValue().Length() ) {
postfix = keyValue->GetKey().Right( keyValue->GetKey().Length() - prefixLen );
if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) {
//AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized
localFxInfo.SetNormal( boneAxis[0].ToNormal() );
localFxInfo.SetBindBone( keyValue->GetValue().c_str() );
// Changed this because it was causing every fx listed to be bound to every bone listed. -mdl
BroadcastFxInfo( spawnArgs.GetString( fxKey + postfix ), bonePos, boneAxis, &localFxInfo, eventDef, broadcast );
//BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef );
}
keyValue = args->MatchPrefix( bonePrefix, keyValue );
}
}
/*
================
hhAnimatedEntity::SpawnFxAlongBone
================
*/
void hhAnimatedEntity::BroadcastFxInfoAlongBone( const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
//mdc: pass through to our newer version of the function (this is mainly done for backwards compatibility)
BroadcastFxInfoAlongBone( false, fxName, boneName, fxInfo, eventDef, broadcast );
}
void hhAnimatedEntity::BroadcastFxInfoAlongBone( bool bNoRemoveWhenUnbound, const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
if( !fxName || !fxName[0] || !boneName || !boneName[0] ) {
return;
}
idVec3 bonePos;
idMat3 boneAxis;
hhFxInfo localFxInfo( fxInfo );
GetJointWorldTransform( boneName, bonePos, boneAxis );
localFxInfo.SetNormal( boneAxis[0] );
localFxInfo.SetEntity( this );
localFxInfo.NoRemoveWhenUnbound( bNoRemoveWhenUnbound ); //mdc: added
localFxInfo.SetBindBone( boneName );
BroadcastFxInfo( fxName, bonePos, boneAxis, &localFxInfo, eventDef, broadcast );
}
/*
================
hhAnimatedEntity::Event_SpawnFXAlongBone
================
*/
void hhAnimatedEntity::Event_SpawnFXAlongBone( idList<idStr>* fxParms ) {
if ( !fxParms ) {
return;
}
bool bNoRemoveWhenUnbound = false; //default to false (we normally want to delete fx when the bound-entity is deleted)
HH_ASSERT( fxParms->Num() >= 2 && fxParms->Num() <= 3 ); //allow 2-3 parameters
if( fxParms->Num() == 3 ) {
//make sure the 3rd paramater is our special noRemoveWhenUnbound flag
if( !idStr::Icmp((*fxParms)[2].c_str(), "noremovewhenunbound") ) {
bNoRemoveWhenUnbound = true;
}
else {
gameLocal.Warning( "unrecognized 3rd paramater on frame command for SpawnFXAlongBone on entity '%s'", this->GetName() );
}
}
BroadcastFxInfoAlongBone(bNoRemoveWhenUnbound, spawnArgs.GetString((*fxParms)[0].c_str()), (*fxParms)[1].c_str() );
}
// HUMANHEAD pdm: Jaw flapping support
// This provides generalized support for any animated entity to translate voice channel sound to bone movements
void hhAnimatedEntity::JawFlap(hhAnimator *theAnimator) {
PROFILE_SCOPE("Jaw Flap", PROFMASK_NORMAL);
float amplitude = 0.0f;
if (fl.noJawFlap || !g_jawflap.GetBool()) {
return;
}
// Get amplitude from head or body
idEntity *headEntity = NULL;
if (IsType(idActor::Type)) {
headEntity = static_cast<idActor*>(this)->GetHead();
if (headEntity && headEntity->GetSoundEmitter()) {
amplitude = headEntity->GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE );
}
}
if( amplitude == 0.0f && GetSoundEmitter() ) {
amplitude = GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE );
}
if (amplitude != 0.0f || lastAmplitude != 0.0f) {
//HUMANHEAD rww - only get the joint handles and info for jaw flapping once
//we have to do this here, since the head is not valid at hhAnimatedEntity::Spawn
if (!hasFlapInfo) {
jawFlapList.Clear();
const char *prefix = "jawbone_";
const idKeyValue *kv = spawnArgs.MatchPrefix(prefix);
while( kv && kv->GetValue().Length() ) {
jointHandle_t bone = theAnimator->GetJointHandle( kv->GetValue() );
if (bone != INVALID_JOINT) {
jawFlapInfo_t flapInfo;
idStr xformName = kv->GetKey();
xformName.Strip(prefix);
flapInfo.bone = bone;
flapInfo.rMagnitude = spawnArgs.GetVector( va("jawflapR_%s", xformName.c_str()) );
flapInfo.tMagnitude = spawnArgs.GetVector( va("jawflapT_%s", xformName.c_str()) );
flapInfo.rMinThreshold = spawnArgs.GetFloat( va("jawflapRMin_%s", xformName.c_str()) );
flapInfo.tMinThreshold = spawnArgs.GetFloat( va("jawflapTMin_%s", xformName.c_str()) );
jawFlapList.Append(flapInfo);
}
kv = spawnArgs.MatchPrefix(prefix, kv);
}
hasFlapInfo = true;
}
lastAmplitude = amplitude;
//HUMANHEAD rww - changed to use a list and not do constant runtime joint lookups
for (int i = 0; i < jawFlapList.Num(); i++) {
jawFlapInfo_t &flapInfo = jawFlapList[i];
// Handle Rotation
idAngles angles = ang_zero;
if (amplitude > flapInfo.rMinThreshold) {
float factor = amplitude - flapInfo.rMinThreshold;
angles = idAngles(factor*flapInfo.rMagnitude[0], factor*flapInfo.rMagnitude[1], factor*flapInfo.rMagnitude[2]);
}
theAnimator->SetJointAxis( flapInfo.bone, JOINTMOD_WORLD, angles.ToMat3() );
// Handle translation
idVec3 translate = vec3_origin;
if (amplitude > flapInfo.tMinThreshold) {
float factor = amplitude - flapInfo.tMinThreshold;
translate = idVec3(factor*flapInfo.tMagnitude[0], factor*flapInfo.tMagnitude[1], factor*flapInfo.tMagnitude[2]);
}
theAnimator->SetJointPos( flapInfo.bone, JOINTMOD_WORLD, translate );
}
}
}
// WaitForSilence support: Tracks the next time the voice channel will be silent, and if any threads are doing a waitForSilence() block, calls them back.
// If a waitForSilence() call is issued while no sound is playing on the voice channel, it will wait until there is something and then wait for silence.
bool hhAnimatedEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) {
int localLength = 0;
bool retVal = idAnimatedEntity::StartSoundShader(shader, channel, soundShaderFlags, broadcast, &localLength);
// need to track 'nextSilentTime' locally also, so if a tiny sound is played after a longer sound, it doesn't think silence will follow short sound
if (channel == SND_CHANNEL_VOICE && (localLength > 0) && (gameLocal.time + localLength > nextSilentTime)) {
nextSilentTime = gameLocal.time + localLength;
// If there's already a thread waiting for silence and this sound pushes silence out further, repost an event for it
if (waitingThread) {
CancelEvents(&EV_Silent);
int time = localLength+silentTimeOffset;
time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms
PostEventMS(&EV_Silent, time);
}
}
if (length) {
*length = localLength;
}
return retVal;
}
void hhAnimatedEntity::Event_Silent() {
if (waitingThread) {
idThread::ObjectMoveDone( waitingThread, this );
waitingThread = 0;
}
}
void hhAnimatedEntity::Event_SetSilenceCallback(float plusOrMinusSeconds) {
if (!waitingThread) {
waitingThread = idThread::CurrentThreadNum();
silentTimeOffset = SEC2MS(plusOrMinusSeconds);
if (nextSilentTime > gameLocal.time) {
// If sound already playing
int time = (nextSilentTime - gameLocal.time) + silentTimeOffset;
time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms
CancelEvents(&EV_Silent);
PostEventMS(&EV_Silent, time);
}
else {
// No sound playing, wait for sound to play first
}
idThread::ReturnInt( true );
}
else {
idThread::ReturnInt( false );
}
}
+74
View File
@@ -0,0 +1,74 @@
#ifndef __GAME_ANIMATEDENTITY_H
#define __GAME_ANIMATEDENTITY_H
extern const idEventDef EV_CheckCycleRotate;
extern const idEventDef EV_CheckThaw;
extern const idEventDef EV_SpawnFxAlongBone;
//HUMANHEAD rww
typedef struct jawFlapInfo_s {
jointHandle_t bone;
idVec3 rMagnitude;
idVec3 tMagnitude;
float rMinThreshold;
float tMinThreshold;
} jawFlapInfo_t;
//HUMANHEAD END
class hhAnimatedEntity : public idAnimatedEntity {
public:
CLASS_PROTOTYPE( hhAnimatedEntity );
void Spawn( void );
hhAnimatedEntity();
virtual ~hhAnimatedEntity();
void Think();
virtual hhAnimator * GetAnimator( void );
virtual const hhAnimator * GetAnimator( void ) const;
virtual void FillDebugVars( idDict *args, int page );
virtual idClipModel* GetCombatModel() const { return combatModel; }
virtual bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis );
virtual bool GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis );
virtual bool GetJointWorldTransform( jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis );
void JawFlap(hhAnimator *theAnimator);
void SpawnFxAlongBonePrefixLocal( const idDict* dict, const char* fxKeyPrefix, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL );
virtual void BroadcastFxInfoAlongBonePrefix( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast
virtual void BroadcastFxInfoAlongBonePrefixUnique( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast
virtual void BroadcastFxInfoAlongBone( const char* fxName, const char* boneName, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast
virtual void BroadcastFxInfoAlongBone( bool bNoRemoveWhenUnbound, const char* fxName, const char* boneName, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast
// WaitForSilence support
virtual bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags = 0, bool broadcast = false, int *length = NULL );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
protected:
// WaitForSilence support
int waitingThread; // Thread that's blocking until we tell it we're silent
int silentTimeOffset; // +/- this amount
int nextSilentTime; // Time at which all sounds will be ended
void Event_Silent();
void Event_SetSilenceCallback(float plusOrMinusSeconds);
float lastAmplitude;
bool hasFlapInfo; //HUMANHEAD rww
idList<jawFlapInfo_t> jawFlapList; //HUMANHEAD rww
//HUMANHEAD: aob - moved from idActor
virtual void UpdateWounds( void );
virtual hhWoundManagerRenderEntity* CreateWoundManager() const { if (!animator.IsAnimatedModel()) { return idEntity::CreateWoundManager(); } return new hhWoundManagerAnimatedEntity(this); }
protected:
void Event_CheckAnimatorCycleRotate( void ) { GetAnimator()->CheckCycleRotate(); }
void Event_CheckAnimatorThaw( void ) { GetAnimator()->CheckThaw(); }
void Event_SpawnFXAlongBone( idList<idStr>* fxParms );
};
#endif
+208
View File
@@ -0,0 +1,208 @@
//**************************************************************************
//**
//** hhAnimatedGui
//**
//**************************************************************************
//TODO: Could impliment a queue for open/close events, so we can wait until
// fully open before closing
// -or-
// could make animations blend out of current and into the new
// requires dynamic control of animation weights
// HEADER FILES ------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const float AG_SMALL_SCALE = 0.01f;
const float AG_LARGE_SCALE = 1.0f;
CLASS_DECLARATION( hhAnimatedEntity, hhAnimatedGui )
EVENT(EV_PlayIdle, hhAnimatedGui::Event_PlayIdle)
EVENT(EV_Activate, hhAnimatedGui::Event_Trigger)
END_CLASS
//==========================================================================
//
// hhAnimatedGui::Spawn
//
//==========================================================================
void hhAnimatedGui::Spawn(void) {
idDict args;
GetPhysics()->SetContents( CONTENTS_BODY );
idleOpenAnim = GetAnimator()->GetAnim("idleopen");
idleCloseAnim = GetAnimator()->GetAnim("idleclose");
openAnim = GetAnimator()->GetAnim("open");
closeAnim = GetAnimator()->GetAnim("close");
bOpen = false;
guiScale.Init(gameLocal.time, 0, AG_SMALL_SCALE, AG_SMALL_SCALE);
// Spawn and bind the console on
const char *consoleName = spawnArgs.GetString("def_gui");
if (consoleName && *consoleName) {
args.Clear();
args.Set("gui", spawnArgs.GetString("gui_topass"));
args.Set("origin", GetOrigin().ToString()); // need the joint position
args.Set("rotation", GetAxis().ToString());
attachedConsole = gameLocal.SpawnObject(consoleName, &args);
assert(attachedConsole);
attachedConsole->SetOrigin(GetOrigin() + GetAxis()[0]*10);
attachedConsole->Bind(this, true);
attachedConsole->Hide();
}
// Spawn the trigger
const char *triggerName = spawnArgs.GetString("def_trigger");
if (triggerName && *triggerName) {
args.Clear();
args.Set( "target", name.c_str() );
args.Set( "mins", spawnArgs.GetString("triggerMins") );
args.Set( "maxs", spawnArgs.GetString("triggerMaxs") );
args.Set( "bind", name.c_str() );
args.SetVector( "origin", GetOrigin() );
args.SetMatrix( "rotation", GetAxis() );
idEntity *trigger = gameLocal.SpawnObject( triggerName, &args );
}
if (idleOpenAnim && idleCloseAnim) {
PostEventMS(&EV_PlayIdle, 0);
}
}
hhAnimatedGui::hhAnimatedGui() {
attachedConsole = NULL;
}
hhAnimatedGui::~hhAnimatedGui() {
}
void hhAnimatedGui::Save(idSaveGame *savefile) const {
savefile->WriteBool( bOpen );
savefile->WriteInt( idleOpenAnim );
savefile->WriteInt( idleCloseAnim );
savefile->WriteInt( openAnim );
savefile->WriteInt( closeAnim );
savefile->WriteFloat( guiScale.GetStartTime() ); // idInterpolate<float>
savefile->WriteFloat( guiScale.GetDuration() );
savefile->WriteFloat( guiScale.GetStartValue() );
savefile->WriteFloat( guiScale.GetEndValue() );
savefile->WriteObject( attachedConsole );
}
void hhAnimatedGui::Restore( idRestoreGame *savefile ) {
float set;
savefile->ReadBool( bOpen );
savefile->ReadInt( idleOpenAnim );
savefile->ReadInt( idleCloseAnim );
savefile->ReadInt( openAnim );
savefile->ReadInt( closeAnim );
savefile->ReadFloat( set ); // idInterpolate<float>
guiScale.SetStartTime( set );
savefile->ReadFloat( set );
guiScale.SetDuration( set );
savefile->ReadFloat( set );
guiScale.SetStartValue(set);
savefile->ReadFloat( set );
guiScale.SetEndValue( set );
savefile->ReadObject( reinterpret_cast<idClass *&>( attachedConsole ) );
}
//==========================================================================
//
// hhAnimatedGui::Think
//
//==========================================================================
void hhAnimatedGui::Think( void ) {
hhAnimatedEntity::Think();
if (thinkFlags & TH_THINK) {
float curScale = guiScale.GetCurrentValue(gameLocal.time);
attachedConsole->SetDeformation(DEFORMTYPE_SCALE, curScale);
if (guiScale.IsDone(gameLocal.time)) {
if (curScale == AG_SMALL_SCALE) { // Just finished scaling down
attachedConsole->Hide();
}
BecomeInactive(TH_THINK);
}
}
}
//==========================================================================
//
// hhAnimatedGui::Event_PlayIdle
//
//==========================================================================
void hhAnimatedGui::Event_PlayIdle() {
GetAnimator()->ClearAllAnims(gameLocal.time, 0);
if (bOpen) {
GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleOpenAnim, gameLocal.time, 0);
FadeInGui();
}
else {
GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleCloseAnim, gameLocal.time, 0);
}
}
//==========================================================================
//
// hhAnimatedGui::Event_Trigger
//
//==========================================================================
void hhAnimatedGui::Event_Trigger( idEntity *activator ) {
if (!openAnim || !closeAnim) {
return;
}
GetAnimator()->ClearAllAnims(gameLocal.time, 0);
int ms;
if(bOpen) {
GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, closeAnim, gameLocal.time, 0);
ms = GetAnimator()->GetAnim( closeAnim )->Length();
bOpen = false;
FadeOutGui();
}
else {
GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, openAnim, gameLocal.time, 0);
ms = GetAnimator()->GetAnim( openAnim )->Length();
bOpen = true;
}
PostEventMS(&EV_PlayIdle, ms);
}
void hhAnimatedGui::FadeInGui() {
float curScale = guiScale.GetCurrentValue(gameLocal.time);
guiScale.Init(gameLocal.time, 500, curScale, AG_LARGE_SCALE);
BecomeActive(TH_THINK);
attachedConsole->SetDeformation(DEFORMTYPE_SCALE, curScale);
attachedConsole->Show();
}
void hhAnimatedGui::FadeOutGui() {
float curScale = guiScale.GetCurrentValue(gameLocal.time);
guiScale.Init(gameLocal.time, 100, curScale, AG_SMALL_SCALE);
BecomeActive(TH_THINK);
attachedConsole->Show();
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef __GAME_ANIMATEDGUI_H__
#define __GAME_ANIMATEDGUI_H__
class hhAnimatedGui : public hhAnimatedEntity {
public:
CLASS_PROTOTYPE(hhAnimatedGui);
void Spawn(void);
hhAnimatedGui();
virtual ~hhAnimatedGui();
virtual void Think();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
protected:
void FadeInGui();
void FadeOutGui();
void Event_Trigger( idEntity *activator );
void Event_PlayIdle();
protected:
bool bOpen;
int idleOpenAnim;
int idleCloseAnim;
int openAnim;
int closeAnim;
idInterpolate<float> guiScale;
idEntity * attachedConsole;
};
#endif /* __GAME_ANIMATEDGUI_H__ */
+251
View File
@@ -0,0 +1,251 @@
//**************************************************************************
//**
//** hhAnimated
//**
//**************************************************************************
// HEADER FILES ------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_PositionDefaultPose("<positionDefaultPose>");
const idEventDef EV_StartDefaultAnim("<startDefaultAnim>");
const idEventDef EV_SetAnim( "setAnim", "s" ); // nla
const idEventDef EV_IsAnimDone( "isAnimDone", "d", 'd' );
CLASS_DECLARATION( idAnimated, hhAnimated )
EVENT( EV_Activate, hhAnimated::Event_Activate )
EVENT( EV_SetAnim, hhAnimated::Event_SetAnim )
EVENT( EV_IsAnimDone, hhAnimated::Event_IsAnimDone )
EVENT( EV_AnimDone, hhAnimated::Event_AnimDone )
EVENT( EV_Animated_Start, hhAnimated::Event_Start )
EVENT( EV_PositionDefaultPose, hhAnimated::Event_PositionDefaultPose )
EVENT( EV_StartDefaultAnim, hhAnimated::Event_StartDefaultAnim )
EVENT( EV_Footstep, hhAnimated::Event_Footstep )
EVENT( EV_FootstepLeft, hhAnimated::Event_Footstep )
EVENT( EV_FootstepRight, hhAnimated::Event_Footstep )
END_CLASS
/*
===============
hhAnimated::Spawn
================
*/
void hhAnimated::Spawn() {
isAnimDone = true;
fl.takedamage = health > 0;
const char* startAnimName = spawnArgs.GetString( "start_anim" );
if( !startAnimName || !startAnimName[0] ) {
PostEventMS( &EV_PositionDefaultPose, 0 );
}
}
void hhAnimated::Save(idSaveGame *savefile) const {
savefile->WriteBool( isAnimDone );
}
void hhAnimated::Restore( idRestoreGame *savefile ) {
savefile->ReadBool( isAnimDone );
}
/*
===============
hhAnimated::Damage
================
*/
void hhAnimated::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) {
return;
}
idAnimated::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
/*
===============
hhAnimated::Killed
================
*/
void hhAnimated::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
idAnimBlend* pAnim = NULL;//GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, "death", gameLocal.time, 0, false );
if( !af.IsActive() ) {
PostEventMS( &EV_StartRagdoll, (pAnim) ? pAnim->Length() : 0 );
}
}
/*
===============
hhAnimated::StartRagdoll
================
*/
bool hhAnimated::StartRagdoll( void ) {
bool parentAns = false;
float slomoStart, slomoEnd;
// NOTE: These first two calls are ripped from idAnimated::StartRagdoll()
// if no AF loaded
if ( !af.IsLoaded() ) {
return false;
}
// if the AF is already active
if ( af.IsActive() ) {
return true;
}
// HUMANHEAD nla - Added to fix the issue with inproper offsets for fixed constraints. Taken from idAFEntity::LoadAF (Caused ragdolls to be 'fixed' to the center of the world/0,0,0)
af.GetPhysics()->Rotate( GetPhysics()->GetAxis().ToRotation() );
af.GetPhysics()->Translate( GetPhysics()->GetOrigin() );
// HUMANHEAD END
parentAns = idAnimated::StartRagdoll();
// HUMANHEAD nla - Allow the ragdolls to slow into the ragdoll
slomoStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoStart", "-1.6" );
slomoEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoEnd", "0.8" );
// do the first part of the death in slow motion
af.GetPhysics()->SetTimeScaleRamp( slomoStart, slomoEnd );
// Allow ragdolls to be active when first started
// This logic ripped from idAFEntity::LoadAF
af.GetPhysics()->PutToRest();
af.GetPhysics()->Activate();
af.UpdateAnimation();
GetAnimator()->CreateFrame( gameLocal.time, true );
UpdateVisuals();
// HUMANHEAD END
return( parentAns );
}
/*
================
hhAnimated::UpdateAnimationControllers
================
*/
bool hhAnimated::UpdateAnimationControllers( void ) {
bool retValue = idAnimated::UpdateAnimationControllers();
JawFlap(GetAnimator());
return retValue;
}
/*
================
hhAnimated::Event_SetAnim
================
*/
void hhAnimated::Event_SetAnim( const char *animname ) {
assert( animname );
anim = GetAnimator()->GetAnim( animname );
HH_ASSERT( anim );
ProcessEvent( &EV_Activate, this );
}
/*
================
hhAnimated::Event_Start
================
*/
void hhAnimated::Event_Start( void ) {
idAnimated::Event_Start();
isAnimDone = false;
}
/*
===============
hhAnimated::Event_AnimDone
================
*/
void hhAnimated::Event_AnimDone( int animIndex ) {
idAnimated::Event_AnimDone( animIndex );
if( !spawnArgs.GetBool("resetDefaultAnim") ) {
isAnimDone = true;
} else {
PostEventMS( &EV_StartDefaultAnim, 0 );
}
}
/*
================
hhAnimated::Event_IsAnimDone
================
*/
void hhAnimated::Event_IsAnimDone( int timeMS ) {
idThread::ReturnInt( (int) isAnimDone );
}
/*
===============
hhAnimated::Event_PositionDefaultPose
================
*/
void hhAnimated::Event_PositionDefaultPose() {
const char* animName = spawnArgs.GetString( "defaultPose" );
if( !animName || !animName[0] ) {
return;
}
int pAnim = GetAnimator()->GetAnim( animName );
GetAnimator()->SetFrame( ANIMCHANNEL_ALL, pAnim, spawnArgs.GetInt("pose_frame", "1"), gameLocal.GetTime(), 0 );
GetAnimator()->ForceUpdate();
UpdateModel();
UpdateAnimation();
}
/*
===============
hhAnimated::Event_StartDefaultAnim
================
*/
void hhAnimated::Event_StartDefaultAnim() {
const char* animName = spawnArgs.GetString( "start_anim" );
if( !animName || animName[0] ) {
return;
}
GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 );
int pAnim = GetAnimator()->GetAnim( animName );
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, pAnim, gameLocal.time, FRAME2MS(blendFrames) );
}
/*
===============
hhAnimated::Event_Activate
================
*/
void hhAnimated::Event_Activate( idEntity *_activator ) {
if (spawnArgs.GetBool("ragdollOnTrigger")) {
StartRagdoll();
return;
}
idAnimated::Event_Activate(_activator);
}
/*
===============
hhAnimated::Event_Footstep
================
*/
void hhAnimated::Event_Footstep() {
StartSound( "snd_footstep", SND_CHANNEL_BODY3, 0, false, NULL );
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef __GAME_ANIMATED_H__
#define __GAME_ANIMATED_H__
class hhAnimated : public idAnimated {
CLASS_PROTOTYPE( hhAnimated );
public:
void Spawn();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
virtual bool UpdateAnimationControllers( void );
bool StartRagdoll( void );
protected:
void Event_SetAnim( const char* animname );
void Event_IsAnimDone( int timeMS );
virtual void Event_AnimDone( int animIndex );
void Event_Start( void );
void Event_PositionDefaultPose();
void Event_StartDefaultAnim();
virtual void Event_Activate( idEntity *_activator );
void Event_Footstep();
protected:
bool isAnimDone;
};
#endif /* __GAME_ANIMATED_H__ */
+766
View File
@@ -0,0 +1,766 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
#define DEFAULT_HIGH_SCORE 76535
#define DEFAULT_HIGH_SCORE_NAME "Teacher"
#define FRUIT_DELAY 15000
const idEventDef EV_GameStart("<pmgamestart>", NULL);
const idEventDef EV_PlayerRespawn("<pmplayerspawn>", NULL);
const idEventDef EV_MonsterRespawn("<pmmonsterspawn>", "d");
const idEventDef EV_ToggleFruit("<pmtogglefruit>", NULL);
const idEventDef EV_GameOver("<pmgameover>", NULL);
const idEventDef EV_NextMap("<pmnextmap>", NULL);
CLASS_DECLARATION(hhConsole, hhArcadeGame)
EVENT( EV_GameStart, hhArcadeGame::Event_GameStart)
EVENT( EV_PlayerRespawn, hhArcadeGame::Event_PlayerRespawn)
EVENT( EV_MonsterRespawn, hhArcadeGame::Event_MonsterRespawn)
EVENT( EV_ToggleFruit, hhArcadeGame::Event_ToggleFruit)
EVENT( EV_GameOver, hhArcadeGame::Event_GameOver)
EVENT( EV_NextMap, hhArcadeGame::Event_NextMap)
EVENT( EV_CallGuiEvent, hhArcadeGame::Event_CallGuiEvent)
END_CLASS
hhArcadeGame::hhArcadeGame() {
}
hhArcadeGame::~hhArcadeGame() {
}
void hhArcadeGame::Spawn() {
highscore = DEFAULT_HIGH_SCORE;
highscoreName = DEFAULT_HIGH_SCORE_NAME;
GetMazeForLevel(1);
victoryAmount = spawnArgs.GetInt("victory");
bPlayerMoving = false;
playerMove.Set(1,0);
bGameOver = true;
ResetMap();
ResetPlayerAndMonsters();
ResetGame();
UpdateView();
}
void hhArcadeGame::GetMazeForLevel(int lev) {
numMazes = idMath::ClampInt(1, 999, spawnArgs.GetInt("numMazes"));
const char *maze = spawnArgs.GetString(va("maze%d", (lev-1)%numMazes+1));
assert(idStr::Length(maze) == (ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT));
memset(startingGrid, 0, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT);
for (int y=0; y<ARCADE_GRID_HEIGHT; y++) {
for (int x=0; x<ARCADE_GRID_WIDTH; x++) {
startingGrid[x][y] = maze[y*ARCADE_GRID_WIDTH+x] - '0';
}
}
}
void hhArcadeGame::Save(idSaveGame *savefile) const {
int i,j;
savefile->Write(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT);
for (i=0; i<ARCADE_GRID_WIDTH; i++) {
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
grid[i][j].Save(savefile);
}
}
for (i=0; i<4; i++) {
monsters[i].Save(savefile);
}
player.Save(savefile);
fruit.Save(savefile);
savefile->WriteVec2(playerMove);
savefile->WriteBool(bPlayerMoving);
savefile->WriteBool(bSimulating);
savefile->WriteBool(bGameOver);
savefile->WriteBool(bPoweredUp);
savefile->WriteInt(score);
savefile->WriteInt(lives);
savefile->WriteInt(level);
savefile->WriteInt(nextScoreBoost);
savefile->WriteInt(nextPelletTime);
savefile->WriteInt(victoryAmount);
savefile->WriteInt(numMazes);
savefile->WriteInt(highscore);
savefile->WriteString(highscoreName);
}
void hhArcadeGame::Restore( idRestoreGame *savefile ) {
int i,j;
savefile->Read(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT);
for (i=0; i<ARCADE_GRID_WIDTH; i++) {
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
grid[i][j].Restore(savefile);
}
}
for (i=0; i<4; i++) {
monsters[i].Restore(savefile);
}
player.Restore(savefile);
fruit.Restore(savefile);
savefile->ReadVec2(playerMove);
savefile->ReadBool(bPlayerMoving);
savefile->ReadBool(bSimulating);
savefile->ReadBool(bGameOver);
savefile->ReadBool(bPoweredUp);
savefile->ReadInt(score);
savefile->ReadInt(lives);
savefile->ReadInt(level);
savefile->ReadInt(nextScoreBoost);
savefile->ReadInt(nextPelletTime);
savefile->ReadInt(victoryAmount);
savefile->ReadInt(numMazes);
savefile->ReadInt(highscore);
savefile->ReadString(highscoreName);
}
void hhArcadeGame::ConsoleActivated() {
BecomeActive(TH_MISC3);
}
void hhArcadeGame::StartGame() {
if (bGameOver) {
ResetMap();
ResetPlayerAndMonsters();
StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL);
ResetGame();
StartSound("snd_coin", SND_CHANNEL_ANY, 0, true, NULL);
PostEventMS(&EV_GameStart, 2000);
bGameOver = false;
UpdateView();
}
}
void hhArcadeGame::FindType(int type, int &xOut, int &yOut, int skipX, int skipY) {
xOut = 0;
yOut = 0;
for (int x=0; x<ARCADE_GRID_WIDTH; x++) {
for (int y=0; y<ARCADE_GRID_HEIGHT; y++) {
if (x != skipX || y != skipY) {
if (grid[x][y].GetType() == type) {
xOut = x;
yOut = y;
return;
}
}
}
}
}
void hhArcadeGame::ResetMap() {
int x, y;
// Make a copy of the grid that we can modify it, transposing in the process
for (x=0; x<ARCADE_GRID_WIDTH; x++) {
for (y=0; y<ARCADE_GRID_HEIGHT; y++) {
grid[x][y].SetType(startingGrid[x][y]);
grid[x][y].SetVisible(true);
}
}
FindType(ARCADE_FRUIT, x, y);
fruit.Set(x, y, false);
}
void hhArcadeGame::ResetPlayerAndMonsters() {
int x, y;
FindType(ARCADE_PLAYERSTART, x, y);
player.moveDelay = 200;
player.nextMoveTime = gameLocal.time;
player.isPlayer = true;
player.Set(x, y);
player.dying = false;
bPoweredUp = false;
StopSound(SND_CHANNEL_ITEM, true);
FindType(ARCADE_MONSTERSTART, x, y);
monsters[0].Set(x, y);
monsters[1].Set(x, y);
monsters[2].Set(x, y);
monsters[3].Set(x, y);
for (int ix=0; ix<4; ix++) {
monsters[ix].SetVisible(true);
monsters[ix].moveDelay = 300;
monsters[ix].nextMoveTime = gameLocal.time;
monsters[ix].vulnerableTimeRemaining = 0;
}
}
void hhArcadeGame::ResetGame() {
level = 1;
score = 0;
lives = 3;
nextScoreBoost = 10000;
bSimulating = false;
nextPelletTime = gameLocal.time;
bPoweredUp = false;
}
idVec2 hhArcadeGame::SmoothMovement(MovingGamePiece &piece) {
idVec2 curPos;
idVec2 lastGuiPos;
idVec2 lastGuiMove;
curPos.x = piece.x;
curPos.y = piece.y;
lastGuiPos.x = (curPos.x - piece.lastMove.x) * ARCADE_CELL_WIDTH;
lastGuiPos.y = (curPos.y - piece.lastMove.y) * ARCADE_CELL_HEIGHT;
lastGuiMove.x = piece.lastMove.x * ARCADE_CELL_WIDTH;
lastGuiMove.y = piece.lastMove.y * ARCADE_CELL_HEIGHT;
float alpha = (gameLocal.time - piece.lastMoveTime) / (float)piece.moveDelay;
alpha = idMath::ClampFloat(0.0f, 1.0f, alpha);
return lastGuiPos + lastGuiMove * alpha;
}
void hhArcadeGame::UpdateView() {
int ix;
idVec2 guiPos;
idUserInterface *gui = renderEntity.gui[0];
if (gui) {
if (player.Changed()) {
guiPos = SmoothMovement(player);
gui->SetStateInt("player_x", guiPos.x);
gui->SetStateInt("player_y", guiPos.y);
gui->SetStateBool("player_vis", player.IsVisible());
gui->SetStateBool("player_moving", bPlayerMoving);
gui->SetStateFloat("player_angle", player.angle);
gui->SetStateBool("player_dying", player.dying);
}
for (ix=0; ix<4; ix++) {
if (monsters[ix].Changed()) {
guiPos = SmoothMovement(monsters[ix]);
gui->SetStateInt(va("monster%d_x", ix+1), guiPos.x);
gui->SetStateInt(va("monster%d_y", ix+1), guiPos.y);
gui->SetStateBool(va("monster%d_vis", ix+1), monsters[ix].IsVisible());
gui->SetStateInt(va("monster%d_pu", ix+1), monsters[ix].vulnerableTimeRemaining);
gui->SetStateFloat(va("monster%d_angle", ix+1), monsters[ix].angle);
}
}
// Traverse grid, telling gui about state of the pellets and powerups
int curPowerup = 1;
int curPellet = 1;
for (int x=0; x<ARCADE_GRID_WIDTH; x++) {
for (int y=0; y<ARCADE_GRID_HEIGHT; y++) {
if (grid[x][y].Changed()) {
if (grid[x][y].GetType() == ARCADE_PELLET) {
gui->SetStateInt(va("pellet%d_x", curPellet), x * ARCADE_CELL_WIDTH);
gui->SetStateInt(va("pellet%d_y", curPellet), y * ARCADE_CELL_HEIGHT);
gui->SetStateBool(va("pellet%d_vis", curPellet), grid[x][y].IsVisible());
curPellet++;
}
else if (grid[x][y].GetType() == ARCADE_POWERUP) {
gui->SetStateInt(va("powerup%d_x", curPowerup), x * ARCADE_CELL_WIDTH);
gui->SetStateInt(va("powerup%d_y", curPowerup), y * ARCADE_CELL_HEIGHT);
gui->SetStateBool(va("powerup%d_vis", curPowerup), grid[x][y].IsVisible());
curPowerup++;
}
}
}
}
if (fruit.Changed()) {
gui->SetStateInt("fruit_x", fruit.x * ARCADE_CELL_WIDTH);
gui->SetStateInt("fruit_y", fruit.y * ARCADE_CELL_HEIGHT);
gui->SetStateBool("fruit_vis", fruit.IsVisible());
}
if (score > highscore) {
highscore = score;
highscoreName = cvarSystem->GetCVarString("ui_name");
}
gui->SetStateInt("maze", (level-1)%numMazes+1);
gui->SetStateInt("level", level);
gui->SetStateInt("score", score);
gui->SetStateInt("lives", lives);
gui->SetStateInt("highscore", highscore);
gui->SetStateString("highscorename", highscoreName.c_str());
gui->SetStateBool("gameover", bGameOver);
gui->StateChanged(gameLocal.time, true);
}
}
bool hhArcadeGame::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) {
idToken token;
if (!src->ReadToken(&token)) {
return false;
}
if (token == ";") {
return false;
}
if (token.Icmp("startgame") == 0) {
// Trigger this entity, used for tip system
idEntity *agTrig = gameLocal.FindEntity( "arcadegame_used" );
if( agTrig ) {
agTrig->PostEventMS( &EV_Activate, 0, this );
}
BecomeActive(TH_MISC3);
StartGame();
}
else if (token.Icmp("reset") == 0) {
BecomeInactive(TH_MISC3);
bSimulating = false;
bGameOver = true;
bPlayerMoving = false;
playerMove.Set(1,0);
ResetMap();
ResetPlayerAndMonsters();
ResetGame();
highscore = 1000;
highscoreName = DEFAULT_HIGH_SCORE_NAME;
UpdateView();
}
else {
src->UnreadToken(&token);
return false;
}
return true;
}
void hhArcadeGame::PlayerControls(usercmd_t *cmd) {
if (bGameOver && (cmd->buttons & BUTTON_ATTACK)) {
StartGame();
}
// These come from the player every tick
if (cmd->rightmove != 0 || cmd->forwardmove != 0) {
playerMove.x = cmd->rightmove > 0 ? 1 : cmd->rightmove < 0 ? -1 : 0;
playerMove.y = cmd->forwardmove < 0 ? 1 : cmd->forwardmove > 0 ? -1 : 0;
}
}
idVec2 MoveForDirection(int dir) {
switch(dir) {
case PAC_MOVE_LEFT: return idVec2(-1, 0);
case PAC_MOVE_RIGHT: return idVec2(1, 0);
case PAC_MOVE_UP: return idVec2(0, -1);
case PAC_MOVE_DOWN: return idVec2(0, 1);
}
return idVec2(0,0);
}
bool hhArcadeGame::MoveIsValid(idVec2 &move, MovingGamePiece &piece) {
int newX = piece.x + move.x;
int newY = piece.y + move.y;
int cell = grid[newX][newY].GetType();
if (cell == ARCADE_CLIPPLAYER) {
// Hack: disallow reentry into ghost cage. Assumes the monster start is adjacent to the playerclip
int sourceCell = grid[piece.x][piece.y].GetType();
if (sourceCell != ARCADE_MONSTERSTART) {
return false;
}
}
return (cell != ARCADE_SOLID && (cell != ARCADE_CLIPPLAYER || !piece.isPlayer ) );
}
void hhArcadeGame::GrowPellets() {
if (!bSimulating) {
return;
}
int growth = 0;
int i, j;
int x, y;
int startX = gameLocal.random.RandomInt(ARCADE_GRID_WIDTH);
int startY = gameLocal.random.RandomInt(ARCADE_GRID_HEIGHT);
StartSound("snd_grow", SND_CHANNEL_ANY, 0, true, NULL);
for (i=0; i<ARCADE_GRID_WIDTH; i++) {
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
x = (startX + i) % ARCADE_GRID_WIDTH;
y = (startY + j) % ARCADE_GRID_HEIGHT;
// For each valid pellet, make all adjacent pellets visible
if (grid[x][y].GetType() == ARCADE_PELLET && grid[x][y].IsVisible()) {
if (grid[x-1][y].GetType() == ARCADE_PELLET && !grid[x-1][y].IsVisible()) {
grid[x-1][y].SetVisible(true);
growth++;
}
if (grid[x+1][y].GetType() == ARCADE_PELLET && !grid[x+1][y].IsVisible()) {
grid[x+1][y].SetVisible(true);
growth++;
}
if (grid[x][y-1].GetType() == ARCADE_PELLET && !grid[x][y-1].IsVisible()) {
grid[x][y-1].SetVisible(true);
growth++;
}
if (grid[x][y+1].GetType() == ARCADE_PELLET && !grid[x][y+1].IsVisible()) {
grid[x][y+1].SetVisible(true);
growth++;
}
if (growth > 4) {
return;
}
}
}
}
}
void hhArcadeGame::CheckForCollisions(MovingGamePiece &piece) {
int x = piece.x;
int y = piece.y;
int ix;
int type;
int destX, destY;
switch(grid[x][y].GetType()) {
case ARCADE_TELEPORT:
FindType(ARCADE_TELEPORT, destX, destY, x, y);
piece.Set(destX, destY);
break;
case ARCADE_PELLET:
if (piece.isPlayer && grid[x][y].IsVisible()) {
grid[x][y].SetVisible(false);
AddScore(1);
StartSound("snd_pellet", SND_CHANNEL_ANY, 0, true, NULL);
}
break;
case ARCADE_POWERUP:
if (piece.isPlayer && grid[x][y].IsVisible()) {
AddScore(50);
grid[x][y].SetVisible(false);
monsters[0].vulnerableTimeRemaining += 5000 / level;
monsters[1].vulnerableTimeRemaining += 5000 / level;
monsters[2].vulnerableTimeRemaining += 5000 / level;
monsters[3].vulnerableTimeRemaining += 5000 / level;
bPoweredUp = true;
StopSound(SND_CHANNEL_ITEM, true);
StartSound("snd_powerup", SND_CHANNEL_ANY, 0, true, NULL);
StartSound("snd_poweruploop", SND_CHANNEL_ITEM, 0, true, NULL);
}
break;
}
if (piece.isPlayer) {
if (fruit.IsVisible() && piece.x==fruit.x && piece.y==fruit.y) {
fruit.SetVisible(false);
AddScore(1000*level);
StartSound("snd_fruit", SND_CHANNEL_ANY, 0, true, NULL);
CancelEvents(&EV_ToggleFruit);
PostEventMS(&EV_ToggleFruit, FRUIT_DELAY);
}
}
// player / monster collisions
for (ix=0; ix<4; ix++) {
if (monsters[ix].IsVisible() && player.x==monsters[ix].x && player.y==monsters[ix].y) {
if (monsters[ix].vulnerableTimeRemaining > 0) {
monsters[ix].SetVisible(false);
StartSound("snd_eatmonster", SND_CHANNEL_ANY, 0, true, NULL);
AddScore(500);
PostEventMS(&EV_MonsterRespawn, 3000, ix);
}
else if (!gameLocal.GetLocalPlayer()->godmode) {
bSimulating = false;
player.SetVisible(false);
player.dying = true;
StartSound("snd_die", SND_CHANNEL_ANY, 0, true, NULL);
if (lives <= 0) {
PostEventMS(&EV_GameOver, 0);
}
else {
lives--;
PostEventMS(&EV_PlayerRespawn, 2000);
}
return;
}
}
}
// Check for life bonus
if (score >= nextScoreBoost) {
lives++;
nextScoreBoost *= 2;
StartSound("snd_lifeboost", SND_CHANNEL_ANY, 0, true, NULL);
}
// Check for level completion
if (piece.isPlayer) {
bool levelCompleted = true;
for (x=0; levelCompleted && x<ARCADE_GRID_WIDTH; x++) {
for (y=0; levelCompleted && y<ARCADE_GRID_HEIGHT; y++) {
type = grid[x][y].GetType();
if (grid[x][y].IsVisible() && (type == ARCADE_PELLET || type == ARCADE_POWERUP)) {
levelCompleted = false;
}
}
}
if (levelCompleted) {
AddScore(2000);
bSimulating = false;
if (victoryAmount > 0 && (level+1 >= victoryAmount)) {
ActivateTargets( gameLocal.GetLocalPlayer() );
StartSound( "snd_victory", SND_CHANNEL_ANY );
victoryAmount = 0;
}
StartSound("snd_leveldone", SND_CHANNEL_ANY, 0, true, NULL);
PostEventMS(&EV_NextMap, 2000);
}
}
}
bool hhArcadeGame::DoMove(idVec2 &move, MovingGamePiece &piece) {
bool bMoved = move.x || move.y;
piece.Move(move);
if (piece.isPlayer) {
// Acount for player movement direction
bPlayerMoving = bMoved;
}
CheckForCollisions(piece);
return bMoved;
}
void hhArcadeGame::DoPlayerMove() {
bPlayerMoving = false;
if (!bSimulating) {
return;
}
bool bValidXMove = playerMove.x && MoveIsValid(idVec2(playerMove.x, 0), player);
bool bValidYMove = playerMove.y && MoveIsValid(idVec2(0, playerMove.y), player);
// Allow move direction to alternate between X and Y
if (player.lastMove.x) {
if (bValidYMove) {
DoMove(idVec2(0, playerMove.y), player);
}
else if (bValidXMove) {
DoMove(idVec2(playerMove.x, 0), player);
}
else if (MoveIsValid(player.lastMove, player)) {
DoMove(player.lastMove, player);
}
}
else {
if (bValidXMove) {
DoMove(idVec2(playerMove.x, 0), player);
}
else if (bValidYMove) {
DoMove(idVec2(0, playerMove.y), player);
}
else if (MoveIsValid(player.lastMove, player)) {
DoMove(player.lastMove, player);
}
}
}
void hhArcadeGame::DoMonsterAI(MovingGamePiece &monster, int index) {
if (!bSimulating) {
return;
}
if (monster.vulnerableTimeRemaining > 0) {
monster.vulnerableTimeRemaining -= monster.moveDelay;
monster.vulnerableTimeRemaining = idMath::ClampInt(0, 100000, monster.vulnerableTimeRemaining);
}
idVec2 move;
idVec2 toPlayer;
idVec2 toPlayerMove;
int playerDirX, playerDirY;
toPlayer.Set(player.x - monster.x, player.y - monster.y);
playerDirX = toPlayer.x < 0 ? PAC_MOVE_LEFT : PAC_MOVE_RIGHT;
playerDirY = toPlayer.y < 0 ? PAC_MOVE_UP : PAC_MOVE_DOWN;
toPlayerMove.Set(
toPlayer.x > 0 ? 1 : toPlayer.x < 0 ? -1 : 0,
toPlayer.y > 0 ? 1 : toPlayer.y < 0 ? -1 : 0
);
idVec2 illegalMove = -monster.lastMove;
bool bGoTowardPlayer = gameLocal.random.RandomInt(4) >= index;
int choice;
int startingChoice = index; // Makes each monster unique
int j;
// Keep moving in direction of last move until there is an intersection
// At intersection, choose between one of the directions, favoring one going towards
// the player. Never go back in the direction you came from unless at a dead end.
// Go towards player if possible
if (bGoTowardPlayer) {
move = MoveForDirection(playerDirX);
if (MoveIsValid(move, monster) && move != illegalMove) {
DoMove(move, monster);
return;
}
move = MoveForDirection(playerDirY);
if (MoveIsValid(move, monster) && move != illegalMove) {
DoMove(move, monster);
return;
}
}
// Otherwise move in one of the other valid directions
for (j=0; j<4; j++) {
choice = (startingChoice + j) % 4;
move = MoveForDirection(choice);
if (MoveIsValid(move, monster) && move != illegalMove) {
DoMove(move, monster);
return;
}
}
// If still haven't found a valid move, must be at a dead end, now allow illegalMove
for (j=0; j<4; j++) {
choice = (startingChoice + j) % 4;
move = MoveForDirection(choice);
if (MoveIsValid(move, monster)) {
DoMove(move, monster);
return;
}
}
}
void hhArcadeGame::Think() {
hhConsole::Think();
if (thinkFlags & TH_MISC3) {
if (bSimulating) {
// Player
if (gameLocal.time > player.nextMoveTime) {
player.nextMoveTime = gameLocal.time + player.moveDelay;
if (playerMove.LengthSqr() > 0.0f) {
DoPlayerMove();
}
}
// AI
for (int ix=0; ix<4; ix++) {
if (gameLocal.time > monsters[ix].nextMoveTime) {
monsters[ix].nextMoveTime = gameLocal.time + monsters[ix].moveDelay;
DoMonsterAI(monsters[ix], ix);
}
}
// See if power up sound should end
if (bPoweredUp) {
bPoweredUp = false;
for (int ix=0; ix<4; ix++) {
if (monsters[ix].vulnerableTimeRemaining > 0) {
bPoweredUp = true;
break;
}
}
if (!bPoweredUp) {
StopSound(SND_CHANNEL_ITEM, true);
}
}
if (gameLocal.time > nextPelletTime) {
nextPelletTime = gameLocal.time + 3500;
//GrowPellets();
}
UpdateView();
}
}
}
void hhArcadeGame::AddScore(int amount) {
if (!gameLocal.GetLocalPlayer()->godmode) {
score += amount;
}
}
void hhArcadeGame::Event_GameStart() {
bSimulating = true;
PostEventMS(&EV_ToggleFruit, FRUIT_DELAY);
}
void hhArcadeGame::Event_GameOver() { // Player ran out of lives
CancelEvents(&EV_ToggleFruit);
StopSound(SND_CHANNEL_ITEM, true);
bSimulating = false;
bGameOver = true;
CallNamedEvent("gameover");
UpdateView();
}
void hhArcadeGame::Event_NextMap() { // Transition to next map
CancelEvents(&EV_ToggleFruit);
StopSound(SND_CHANNEL_ITEM, true);
GetMazeForLevel(++level);
ResetMap();
ResetPlayerAndMonsters();
StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL);
PostEventMS(&EV_GameStart, 2000);
UpdateView();
}
void hhArcadeGame::Event_PlayerRespawn() { // Player died, respawn
CancelEvents(&EV_ToggleFruit);
ResetPlayerAndMonsters();
StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL);
player.SetVisible(true);
player.dying = false;
PostEventMS(&EV_GameStart, 3000);
UpdateView();
}
void hhArcadeGame::Event_MonsterRespawn(int index) { // Monster died, respawn
int x,y;
FindType(ARCADE_MONSTERSTART, x, y);
monsters[index].SetVisible(true);
monsters[index].vulnerableTimeRemaining = 0;
monsters[index].Set(x,y);
monsters[index].nextMoveTime = gameLocal.time + 3000;
}
void hhArcadeGame::Event_ToggleFruit() {
if (fruit.IsVisible()) {
fruit.SetVisible(false);
PostEventMS(&EV_ToggleFruit, FRUIT_DELAY);
}
else {
fruit.SetVisible(true);
PostEventMS(&EV_ToggleFruit, 5000);
}
}
void hhArcadeGame::Event_CallGuiEvent(const char *eventName) {
if (!idStr::Icmp(eventName, "turnoff")) {
// Release any players routing commands to us
for( int i = 0; i < gameLocal.numClients; i++ ) {
if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType(hhPlayer::Type) ) {
hhPlayer *player = static_cast<hhPlayer*>(gameLocal.entities[i]);
if (player->guiWantsControls.IsValid() && player->guiWantsControls.GetEntity() == this) {
player->guiWantsControls = NULL;
}
}
}
}
CallNamedEvent(eventName);
}
void hhArcadeGame::LockedGuiReleased(hhPlayer *player) {
idEntity *agTrig = gameLocal.FindEntity( "arcadegame_released" );
if( agTrig ) {
agTrig->PostEventMS( &EV_Activate, 0, this );
}
}
+188
View File
@@ -0,0 +1,188 @@
#ifndef __GAME_ARCADEGAME_H__
#define __GAME_ARCADEGAME_H__
#define ARCADE_GRID_WIDTH 19
#define ARCADE_GRID_HEIGHT 21
#define ARCADE_CELL_WIDTH 20
#define ARCADE_CELL_HEIGHT 20
#define ARCADE_EMPTY 0
#define ARCADE_SOLID 1
#define ARCADE_PELLET 2
#define ARCADE_POWERUP 3
#define ARCADE_CLIPPLAYER 4
#define ARCADE_TELEPORT 5
#define ARCADE_FRUIT 6
#define ARCADE_PLAYERSTART 7
#define ARCADE_MONSTERSTART 8
enum {
PAC_MOVE_LEFT=0,
PAC_MOVE_RIGHT=1,
PAC_MOVE_UP=2,
PAC_MOVE_DOWN=3
};
class GamePiece {
public:
GamePiece() { type = ARCADE_EMPTY; visible = true; changed = true; }
virtual ~GamePiece() { }
void SetType(char t) { type = t; changed = true; }
void SetVisible(bool v) { visible = v; changed = true; }
char GetType() { return type; }
bool IsVisible() { return visible; }
bool Changed() { return changed; }
virtual void Save( idSaveGame *savefile ) const {
savefile->WriteByte(type);
savefile->WriteBool(visible);
savefile->WriteBool(changed);
}
virtual void Restore( idRestoreGame *savefile ) {
savefile->ReadByte(type);
savefile->ReadBool(visible);
savefile->ReadBool(changed);
}
protected:
byte type;
bool visible;
bool changed;
};
class MovingGamePiece : public GamePiece {
public:
MovingGamePiece() {
x = y = 0; isPlayer = false; dying = false;
lastMove.Zero();
lastMoveTime = 0;
angle = 0.0f;
moveDelay = 250;
nextMoveTime = 0;
vulnerableTimeRemaining = 0;
}
void Set(int nx, int ny, bool nvis=true) {
x=nx; y=ny; visible = nvis;
lastMove.Zero();
lastMoveTime = gameLocal.time;
changed = true;
}
void Move(idVec2 move) {
x += move.x;
y += move.y;
lastMove = move;
lastMoveTime = gameLocal.time;
angle =
lastMove.x > 0 ? 0.0f :
lastMove.x < 0 ? 180.0f :
lastMove.y < 0 ? 90.0f :
lastMove.y > 0 ? 270.0f : angle;
changed = true;
}
virtual void Save( idSaveGame *savefile ) const {
GamePiece::Save(savefile);
savefile->WriteInt(x);
savefile->WriteInt(y);
savefile->WriteBool(isPlayer);
savefile->WriteFloat(angle);
savefile->WriteBool(dying);
savefile->WriteVec2(lastMove);
savefile->WriteInt(lastMoveTime);
savefile->WriteInt(moveDelay);
savefile->WriteInt(nextMoveTime);
savefile->WriteInt(vulnerableTimeRemaining);
}
virtual void Restore( idRestoreGame *savefile ) {
GamePiece::Restore(savefile);
savefile->ReadInt(x);
savefile->ReadInt(y);
savefile->ReadBool(isPlayer);
savefile->ReadFloat(angle);
savefile->ReadBool(dying);
savefile->ReadVec2(lastMove);
savefile->ReadInt(lastMoveTime);
savefile->ReadInt(moveDelay);
savefile->ReadInt(nextMoveTime);
savefile->ReadInt(vulnerableTimeRemaining);
}
int x,y;
bool isPlayer;
bool dying; // used for player only
idVec2 lastMove;
float angle;
int lastMoveTime;
int moveDelay; // Delay in MS between moves
int nextMoveTime; // Next time piece is allowed to move
int vulnerableTimeRemaining; // monsters only, time before powerup wears off
};
class hhArcadeGame : public hhConsole {
public:
CLASS_PROTOTYPE( hhArcadeGame );
hhArcadeGame();
virtual ~hhArcadeGame();
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Think();
virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src);
virtual void PlayerControls(usercmd_t *cmd);
virtual void ConsoleActivated();
virtual void LockedGuiReleased(hhPlayer *player);
void StartGame();
bool MoveIsValid(idVec2 &move, MovingGamePiece &piece);
bool DoMove(idVec2 &move, MovingGamePiece &piece);
void CheckForCollisions(MovingGamePiece &piece);
void ResetMap();
void ResetPlayerAndMonsters();
void ResetGame();
void GrowPellets();
void DoPlayerMove();
void DoMonsterAI(MovingGamePiece &monster, int index);
idVec2 SmoothMovement(MovingGamePiece &piece);
void UpdateView();
void FindType(int type, int &x, int &y, int skipX=-1, int skipY=-1);
void GetMazeForLevel(int lev);
void AddScore(int amount);
protected:
void Event_GameStart();
void Event_GameOver();
void Event_PlayerRespawn();
void Event_MonsterRespawn(int index);
void Event_ToggleFruit();
void Event_NextMap();
void Event_CallGuiEvent(const char *eventName);
protected:
char startingGrid[ARCADE_GRID_WIDTH][ARCADE_GRID_HEIGHT];
GamePiece grid[ARCADE_GRID_WIDTH][ARCADE_GRID_HEIGHT];
MovingGamePiece monsters[4];
MovingGamePiece player;
MovingGamePiece fruit;
idVec2 playerMove;
idStr highscoreName;
bool bPlayerMoving;
bool bSimulating;
bool bGameOver;
bool bPoweredUp;
int score;
int highscore;
int lives;
int level;
int numMazes;
int nextScoreBoost;
int nextPelletTime;
private:
int victoryAmount;
};
#endif
+181
View File
@@ -0,0 +1,181 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
CLASS_DECLARATION( hhMoveable, hhBarrel )
END_CLASS
/*
================
hhBarrel::hhBarrel
================
*/
hhBarrel::hhBarrel() {
radius = 1.0f;
barrelAxis = 0;
lastOrigin.Zero();
lastAxis.Identity();
additionalRotation = 0.0f;
additionalAxis.Identity();
fl.networkSync = true;
}
/*
================
hhBarrel::Save
================
*/
void hhBarrel::Save( idSaveGame *savefile ) const {
savefile->WriteFloat( radius );
savefile->WriteInt( barrelAxis );
savefile->WriteVec3( lastOrigin );
savefile->WriteMat3( lastAxis );
savefile->WriteFloat( additionalRotation );
savefile->WriteMat3( additionalAxis );
}
/*
================
hhBarrel::Restore
================
*/
void hhBarrel::Restore( idRestoreGame *savefile ) {
savefile->ReadFloat( radius );
savefile->ReadInt( barrelAxis );
savefile->ReadVec3( lastOrigin );
savefile->ReadMat3( lastAxis );
savefile->ReadFloat( additionalRotation );
savefile->ReadMat3( additionalAxis );
}
/*
================
hhBarrel::BarrelThink
================
*/
void hhBarrel::BarrelThink( void ) {
bool wasAtRest, onGround;
float movedDistance, rotatedDistance, angle;
idVec3 curOrigin, gravityNormal, dir;
idMat3 curAxis, axis;
wasAtRest = IsAtRest();
// run physics
RunPhysics();
// only need to give the visual model an additional rotation if the physics were run
if ( !wasAtRest ) {
// current physics state
onGround = GetPhysics()->HasGroundContacts();
curOrigin = GetPhysics()->GetOrigin();
curAxis = GetPhysics()->GetAxis();
// if the barrel is on the ground
if ( onGround ) {
gravityNormal = GetPhysics()->GetGravityNormal();
dir = curOrigin - lastOrigin;
dir -= gravityNormal * dir * gravityNormal;
movedDistance = dir.LengthSqr();
// if the barrel moved and the barrel is not aligned with the gravity direction
if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) {
// barrel movement since last think frame orthogonal to the barrel axis
movedDistance = idMath::Sqrt( movedDistance );
dir *= 1.0f / movedDistance;
movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance;
// get rotation about barrel axis since last think frame
angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3];
angle = idMath::ACos( angle );
// distance along cylinder hull
rotatedDistance = angle * radius;
// if the barrel moved further than it rotated about it's axis
if ( movedDistance > rotatedDistance ) {
// additional rotation of the visual model to make it look
// like the barrel rolls instead of slides
angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI);
if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) {
additionalRotation += angle;
} else {
additionalRotation -= angle;
}
dir = vec3_origin;
dir[barrelAxis] = 1.0f;
additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3();
}
}
}
// save state for next think
lastOrigin = curOrigin;
lastAxis = curAxis;
}
Present();
}
/*
================
hhBarrel::Think
================
*/
void hhBarrel::Think( void ) {
if ( thinkFlags & TH_THINK ) {
if ( !FollowInitialSplinePath() ) {
BecomeInactive( TH_THINK );
}
}
BarrelThink();
}
/*
================
hhBarrel::GetPhysicsToVisualTransform
================
*/
bool hhBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
origin = vec3_origin;
axis = additionalAxis;
return true;
}
/*
================
hhBarrel::Spawn
================
*/
void hhBarrel::Spawn( void ) {
const idBounds &bounds = GetPhysics()->GetBounds();
// radius of the barrel cylinder
radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f;
// always a vertical barrel with cylinder axis parallel to the z-axis
barrelAxis = 2;
lastOrigin = GetPhysics()->GetOrigin();
lastAxis = GetPhysics()->GetAxis();
additionalRotation = 0.0f;
additionalAxis.Identity();
}
/*
================
hhBarrel::ClientPredictionThink
================
*/
void hhBarrel::ClientPredictionThink( void ) {
Think();
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef __HH_BARREL_H
#define __HH_BARREL_H
class hhBarrel : public hhMoveable {
public:
CLASS_PROTOTYPE( hhBarrel );
hhBarrel();
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void BarrelThink( void );
virtual void Think( void );
virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis );
virtual void ClientPredictionThink( void );
private:
float radius; // radius of barrel
int barrelAxis; // one of the coordinate axes the barrel cylinder is parallel to
idVec3 lastOrigin; // origin of the barrel the last think frame
idMat3 lastAxis; // axis of the barrel the last think frame
float additionalRotation; // additional rotation of the barrel about it's axis
idMat3 additionalAxis; // additional rotation axis
};
#endif
+290
View File
@@ -0,0 +1,290 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//==========================================================================
//
// hhBindController
//
// These can be bound to anything. They can be used to attach a player, monster, almost any entity
// and take control of their movement through space. Players are optionally allowed to look around
// while bound. This class was necessary to abstract all the physics differences away from the
// objects doing the binding.
//==========================================================================
const idEventDef EV_BindAttach("bindattach", "ed");
const idEventDef EV_BindDetach("binddrop", NULL);
const idEventDef EV_BindAttachBody("bindattachbody", "edd");
CLASS_DECLARATION(idEntity, hhBindController)
EVENT( EV_BindAttach, hhBindController::Event_Attach )
EVENT( EV_BindDetach, hhBindController::Event_Detach )
EVENT( EV_BindAttachBody, hhBindController::Event_AttachBody )
END_CLASS
void hhBindController::Spawn() {
boundRider = NULL;
hand = NULL;
yawLimit = 180.0f; // no limit
float tension = spawnArgs.GetFloat("tension", "0.01");
const char *anim = spawnArgs.GetString("boundanim");
const char *hand = spawnArgs.GetString("def_hand");
float yaw = spawnArgs.GetFloat("yawlimit", "180");
float pitch = spawnArgs.GetFloat("pitchlimit", "75");
bOrientPlayer = spawnArgs.GetBool("orientplayer");
bLooseBind = false;
SetRiderParameters(anim, hand, yaw, pitch);
SetTension(tension);
hangID = 0;
}
hhBindController::~hhBindController() {
Detach();
}
void hhBindController::Save(idSaveGame *savefile) const {
boundRider.Save(savefile);
savefile->WriteObject( hand );
savefile->WriteBool( bLooseBind );
savefile->WriteBool( bOrientPlayer );
savefile->WriteStaticObject( force );
savefile->WriteString( animationName );
savefile->WriteString( handName );
savefile->WriteFloat( yawLimit );
savefile->WriteFloat(pitchLimit);
savefile->WriteInt( hangID );
}
void hhBindController::Restore( idRestoreGame *savefile ) {
boundRider.Restore(savefile);
savefile->ReadObject( reinterpret_cast<idClass *&>(hand) );
savefile->ReadBool( bLooseBind );
savefile->ReadBool( bOrientPlayer );
savefile->ReadStaticObject( force );
savefile->ReadString( animationName );
savefile->ReadString( handName );
savefile->ReadFloat( yawLimit );
savefile->ReadFloat(pitchLimit);
savefile->ReadInt( hangID );
}
idEntity *hhBindController::GetRider() const {
// Since the entity could be deleted at any time, we get the rider from the force (if used)
if (bLooseBind) {
//TODO: Make this force use a safe entityptr too
return force.GetEntity();
}
return boundRider.GetEntity();
}
void hhBindController::SetTension(float tension) {
force.SetRestoreTime((1.0f - tension)*5.0f);
force.SetRestoreFactor(tension * g_springConstant.GetFloat());
}
void hhBindController::SetSlack(float slack) {
force.SetRestoreSlack(slack);
}
void hhBindController::Think() {
idEntity::Think(); //TODO: This needed?
if (bLooseBind && GetRider()) {
idVec3 loc = GetOrigin();
force.SetTarget(loc);
//TODO: Cull hangID out of this class if not needed.
// FIXME: Why is this needed? (Took out --pdm, test crane/rail before removing)
/*
idEntity *rider = GetRider();
if (rider->IsType(idAFEntity_Base::Type)) { // Update the offset depending on if actor is ragdoll
idAFEntity_Base *af = static_cast<idAFEntity_Base *>(rider);
if (af->IsActiveAF()) {
force.SetEntity(rider, hangID, vec3_origin);
}
else { // non-ragdolled rider
if (rider->IsType(idActor::Type)) {
force.SetEntity(rider, 0, af->EyeOffset());
}
else {
force.SetEntity(rider, 0, rider->GetOrigin());
}
}
}
*/
force.Evaluate(gameLocal.time);
}
else if (GetRider() && GetRider()->IsType(hhPlayer::Type) && OrientPlayer()) {
// This to insure that oriented players don't get out of sync
hhPlayer *player = static_cast<hhPlayer *>(GetRider());
idAngles angles = player->GetUntransformedViewAngles();
player->SetOrientation(GetOrigin(), GetAxis(), angles.ToMat3()[0], angles);
}
}
// Create our invisible hand to get rid of the weapon
void hhBindController::CreateHand(hhPlayer *player) {
if (gameLocal.isClient) { //rww
return;
}
if (handName.Length()) {
ReleaseHand();
hand = hhHand::AddHand( player, handName.c_str() );
}
}
void hhBindController::ReleaseHand() {
if (hand) {
hand->RemoveHand();
hand = NULL;
}
}
void hhBindController::SetRiderParameters(const char *animname, const char *handname, float yaw, float pitch) {
animationName = animname;
handName = handname;
yawLimit = yaw;
pitchLimit = pitch;
}
bool hhBindController::ValidRider(idEntity *ent) const {
return (ent != NULL);
}
void hhBindController::Attach(idEntity *ent, bool loose, int bodyID, idVec3 &point) {
hhPlayer *player = ent->IsType(hhPlayer::Type) ? static_cast<hhPlayer *>(ent) : NULL;
if (GetRider()==NULL && ValidRider(ent)) {
bLooseBind = loose;
if (bLooseBind) {
idVec3 loc = GetOrigin();
force.SetEntity(ent, bodyID, point);
force.SetTarget(loc);
if (ent->spawnArgs.GetBool("stable_tractor", "0")) {
force.SetAxisEntity(this);
} else {
force.SetAxisEntity(NULL);
}
BecomeActive(TH_THINK);
}
else {
boundRider = ent;
boundRider->SetOrigin(GetOrigin());
if (player && bOrientPlayer) {
idAngles angles = player->GetUntransformedViewAngles();
player->SetOrientation(GetOrigin(), GetAxis(), angles.ToMat3()[0], angles);
}
else {
boundRider->SetAxis(GetAxis());
}
boundRider->Bind(this, false);
}
// Apply player parameters
if (player && !gameLocal.isMultiplayer) { //rww - in mp, don't want to restrict players when bound
player->maxRelativeYaw = yawLimit;
player->maxRelativePitch = pitchLimit;
player->bClampYaw = yawLimit < 180.0f;
CreateHand(player);
// player->hhweapon->Hide();
}
// Play animation on actors (but not players in mp -rww)
if (ent->IsType(idActor::Type) && !ent->fl.tooHeavyForTractor && (!gameLocal.isMultiplayer || !ent->IsType(hhPlayer::Type))) {
idActor *actor = static_cast<idActor *>(ent);
actor->AI_BOUND = true;
if ( actor->GetHealth() > 0 ) {
actor->ProcessEvent(&AI_SetAnimPrefix, animationName.c_str());
actor->SetAnimState( ANIMCHANNEL_LEGS, "Legs_Bound", 4 );
actor->SetAnimState( ANIMCHANNEL_TORSO, "Torso_Bound", 4 );
}
actor->BecameBound(this);
}
hangID = 0;
if (ent->IsType(idAFEntity_Base::Type)) {
hangID = static_cast<idAFEntity_Base*>(ent)->spawnArgs.GetInt("hangID");
}
}
}
void hhBindController::Detach() {
idEntity *rider = GetRider();
hhPlayer *player = rider && rider->IsType(hhPlayer::Type) ? static_cast<hhPlayer *>(rider) : NULL;
if (rider) {
if (player) {
player->bClampYaw = false;
if (gameLocal.isMultiplayer) { //do not fiddle with the player's anims in MP when he is grabbed, let him act normally
// Release player animation
player->AI_BOUND = false;
player->SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
player->SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
}
ReleaseHand();
}
if (rider->IsType(idActor::Type)) {
idActor *actor = static_cast<idActor *>(rider);
actor->AI_BOUND = false;
actor->BecameUnbound(this);
}
if (bLooseBind) {
force.SetEntity(NULL);
}
else {
rider->Unbind();
rider->SetAxis(mat3_identity);
if (player && bOrientPlayer) {
// Set everything back to normal at current view angles
idAngles angles = player->GetUntransformedViewAngles();
player->SetOrientation(GetOrigin(), mat3_identity, angles.ToMat3()[0], angles);
}
boundRider = NULL;
}
}
}
idVec3 hhBindController::GetRiderBindPoint() const {
idEntity* rider = GetRider();
if( !rider ) {
return vec3_origin;
}
if( rider->IsType(idActor::Type) ) {
idActor *actor = static_cast<idActor*>( rider );
if( actor->IsActiveAF() ) {
return actor->GetOrigin( GetHangID() );
} else {
return actor->GetEyePosition();
}
}
return rider->GetOrigin();
}
void hhBindController::Event_Attach(idEntity *rider, bool loose) {
Attach(rider, loose);
}
void hhBindController::Event_AttachBody(idEntity *rider, bool loose, int bodyID) {
Attach(rider, loose, bodyID);
}
void hhBindController::Event_Detach() {
Detach();
}
+68
View File
@@ -0,0 +1,68 @@
#ifndef __HH_GAME_BINDCONTROLLER_H__
#define __HH_GAME_BINDCONTROLLER_H__
/*
===================================================================================
hhBindController
Entity used to control an entity's position in space.
Used for rail rides, tractor beams, crane, etc.
===================================================================================
*/
extern const idEventDef EV_BindAttach;
extern const idEventDef EV_BindDetach;
class hhBindController : public idEntity {
public:
CLASS_PROTOTYPE( hhBindController );
void Spawn();
virtual ~hhBindController();
virtual void Think();
virtual void Attach(idEntity *ent, bool loose=false, int bodyID=0, idVec3 &point=vec3_origin);
virtual void Detach();
void SetTension(float tension);
void SetSlack(float slack);
void SetRiderParameters(const char *animname, const char *handname, float yaw, float pitch);
idVec3 GetRiderBindPoint() const;
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
bool OrientPlayer() const { return bOrientPlayer; }
idEntity * GetRider() const;
ID_INLINE bool IsLoose() const { return bLooseBind; }
ID_INLINE int GetHangID() const { return hangID; }
ID_INLINE void SetShuttle(bool shuttle) { force.SetShuttle(shuttle); }
protected:
bool ValidRider(idEntity *ent) const;
void CreateHand(hhPlayer *player);
void ReleaseHand();
void Event_Attach(idEntity *rider, bool loose);
void Event_AttachBody(idEntity *rider, bool loose, int bodyID);
void Event_Detach();
protected:
idEntityPtr<idEntity> boundRider; // Pointer to rider for true bind-based riders
// Loose bindings store rider in the force
hhHand * hand;
bool bLooseBind; // True if the bind is through a force
bool bOrientPlayer; // Orient the player to my orientation
hhForce_Converge force; // Force used for "loose" binding
// Apply these to any entities bound
idStr animationName; // Animation to play on riders
idStr handName; // Hand to apply to player riders
float yawLimit; // Yaw restriction for player riders
float pitchLimit; //rww - used for slabs and other things to restrict pitch of rider
int hangID; // Body id to attach to for hanging
};
#endif
+508
View File
@@ -0,0 +1,508 @@
// game_blackjack
//
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
enum {
BJRESULT_BUST=0,
BJRESULT_PUSH=1,
BJRESULT_WIN=2,
BJRESULT_LOSE=3,
BJRESULT_BLACKJACK=4,
BJRESULT_5CARD=5
};
const idEventDef EV_Deal("Deal", NULL);
const idEventDef EV_Hit("Hit", NULL);
const idEventDef EV_Stay("Stay", NULL);
const idEventDef EV_Double("Double", NULL);
CLASS_DECLARATION(hhConsole, hhBlackJack)
EVENT( EV_Deal, hhBlackJack::Event_Deal)
EVENT( EV_Hit, hhBlackJack::Event_Hit)
EVENT( EV_Stay, hhBlackJack::Event_Stay)
EVENT( EV_Double, hhBlackJack::Event_Double)
EVENT( EV_UpdateView, hhBlackJack::Event_UpdateView)
END_CLASS
void hhBlackJack::Spawn() {
Reset();
}
void hhBlackJack::Reset() {
bCanDeal = 1;
bCanIncBet = 1;
bCanDecBet = 1;
bCanHit = 0;
bCanStay = 0;
bCanDouble = 0;
bCanSplit = 0;
PlayerBet = Bet = 1;
DealerScore = PlayerScore = 0;
DealerAces = PlayerAces = 0;
PlayerCredits = spawnArgs.GetInt("credits");
victoryAmount = spawnArgs.GetInt("victory");
resultIndex = -1;
creditsWon = 0;
PlayerHand.Clear();
DealerHand.Clear();
UpdateView();
}
void hhBlackJack::Save(idSaveGame *savefile) const {
int i;
savefile->WriteInt( PlayerHand.Num() ); // Saving of idList<card_t>
for( i = 0; i < PlayerHand.Num(); i++ ) {
savefile->Write(&PlayerHand[i], sizeof(card_t));
}
savefile->WriteInt( DealerHand.Num() ); // Saving of idList<card_t>
for( i = 0; i < DealerHand.Num(); i++ ) {
savefile->Write(&DealerHand[i], sizeof(card_t));
}
savefile->WriteInt(Bet);
savefile->WriteInt(PlayerBet);
savefile->WriteInt(DealerScore);
savefile->WriteInt(PlayerScore);
savefile->WriteInt(DealerAces);
savefile->WriteInt(PlayerAces);
savefile->WriteInt(PlayerCredits);
savefile->WriteInt(victoryAmount);
savefile->WriteInt(resultIndex);
savefile->WriteBool(bCanDeal);
savefile->WriteBool(bCanIncBet);
savefile->WriteBool(bCanDecBet);
savefile->WriteBool(bCanHit);
savefile->WriteBool(bCanStay);
savefile->WriteBool(bCanDouble);
savefile->WriteBool(bCanSplit);
savefile->WriteInt(creditsWon);
}
void hhBlackJack::Restore( idRestoreGame *savefile ) {
int i, num;
PlayerHand.Clear();
savefile->ReadInt( num );
PlayerHand.SetNum( num );
for( i = 0; i < num; i++ ) {
savefile->Read(&PlayerHand[i], sizeof(card_t));
}
DealerHand.Clear();
savefile->ReadInt( num );
DealerHand.SetNum( num );
for( i = 0; i < num; i++ ) {
savefile->Read(&DealerHand[i], sizeof(card_t));
}
savefile->ReadInt(Bet);
savefile->ReadInt(PlayerBet);
savefile->ReadInt(DealerScore);
savefile->ReadInt(PlayerScore);
savefile->ReadInt(DealerAces);
savefile->ReadInt(PlayerAces);
savefile->ReadInt(PlayerCredits);
savefile->ReadInt(victoryAmount);
savefile->ReadInt(resultIndex);
savefile->ReadBool(bCanDeal);
savefile->ReadBool(bCanIncBet);
savefile->ReadBool(bCanDecBet);
savefile->ReadBool(bCanHit);
savefile->ReadBool(bCanStay);
savefile->ReadBool(bCanDouble);
savefile->ReadBool(bCanSplit);
savefile->ReadInt(creditsWon);
}
card_t hhBlackJack::GetCard(bool visible)
{
card_t card;
char names[] = { '2','3','4','5','6','7','8','9','T','J','Q','K','A'};
int values[] = { 2 , 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11};
int r = gameLocal.random.RandomInt(13);
card.Name = names[r];
card.Value = values[r];
card.Suit = gameLocal.random.RandomInt(4);
card.Visible = visible;
return card;
}
void hhBlackJack::Deal()
{
// Empty hands
PlayerHand.Clear();
DealerHand.Clear();
Bet = PlayerBet;
creditsWon = 0;
// Deal initial hands
PlayerHand.Append(GetCard(1));
DealerHand.Append(GetCard(0));
PlayerHand.Append(GetCard(1));
DealerHand.Append(GetCard(1));
RetallyScores();
bCanIncBet = false;
bCanDecBet = false;
bCanDeal = false;
DeterminePlayCommands();
resultIndex = -1;
if (PlayerScore == 21) {
AssessScores();
EndGame();
}
UpdateView();
}
void hhBlackJack::Hit()
{
PlayerHand.Append(GetCard(1));
RetallyScores();
DeterminePlayCommands();
if (PlayerScore >= 21 || PlayerHand.Num() == 5) {
FollowDealerRules();
AssessScores();
EndGame();
}
UpdateView();
}
void hhBlackJack::Stay()
{
FollowDealerRules();
AssessScores();
EndGame();
UpdateView();
}
void hhBlackJack::Double()
{
Bet *= 2;
PlayerHand.Append(GetCard(1));
RetallyScores();
DeterminePlayCommands();
// Force player to stay
FollowDealerRules();
AssessScores();
EndGame();
UpdateView();
}
void hhBlackJack::IncBet() {
int amount = 1;
idUserInterface *gui = renderEntity.gui[0];
if (gui) {
amount = gui->GetStateInt("increment");
}
int oldBet = PlayerBet;
PlayerBet = idMath::ClampInt(PlayerBet, PlayerCredits, PlayerBet+amount);
PlayerBet = idMath::ClampInt(0, 999999, PlayerBet);
Bet = PlayerBet;
if (PlayerBet != oldBet) {
StartSound( "snd_betchange", SND_CHANNEL_ANY );
}
UpdateView();
}
void hhBlackJack::DecBet() {
int amount = 1;
idUserInterface *gui = renderEntity.gui[0];
if (gui) {
amount = gui->GetStateInt("increment");
}
int oldBet = PlayerBet;
if (PlayerBet > amount) {
PlayerBet -= amount;
}
else if (PlayerBet > 1) {
PlayerBet = 1;
}
Bet = PlayerBet;
if (PlayerBet != oldBet) {
StartSound( "snd_betchange", SND_CHANNEL_ANY );
}
UpdateView();
}
/*
Blackjack utility functions
*/
void hhBlackJack::RetallyScores()
{
int ix;
PlayerScore = DealerScore = PlayerAces = DealerAces = 0;
for (ix=0; ix<PlayerHand.Num(); ix++) {
PlayerScore += PlayerHand[ix].Value;
PlayerAces += PlayerHand[ix].Value == 11 ? 1 : 0;
}
while (PlayerScore > 21 && PlayerAces) {
PlayerScore -= 10;
PlayerAces--;
}
for (ix=0; ix<DealerHand.Num(); ix++) {
DealerScore += DealerHand[ix].Value;
DealerAces += DealerHand[ix].Value == 11 ? 1 : 0;
}
while (DealerScore > 21 && DealerAces) {
DealerScore -= 10;
DealerAces--;
}
}
void hhBlackJack::AssessScores()
{
if (PlayerScore > 21) {
creditsWon = -Bet;
resultIndex = BJRESULT_BUST;
}
else if (DealerScore > 21) {
creditsWon = Bet;
resultIndex = BJRESULT_WIN;
}
else if (PlayerScore == 21 && PlayerHand.Num() == 2) {
// BlackJack
creditsWon = Bet * 2;
resultIndex = BJRESULT_BLACKJACK;
}
else if (PlayerScore <= 21 && PlayerHand.Num() == 5) {
creditsWon = Bet * 5;
resultIndex = BJRESULT_5CARD;
}
else if (DealerScore > PlayerScore) {
creditsWon = -Bet;
resultIndex = BJRESULT_LOSE;
}
else if (PlayerScore > DealerScore) {
creditsWon = Bet;
resultIndex = BJRESULT_WIN;
}
else {
// Push
creditsWon = 0;
resultIndex = BJRESULT_PUSH;
}
PlayerCredits += creditsWon;
PlayerCredits = idMath::ClampInt(0, 999999999, PlayerCredits);
Bet = PlayerBet;
// Play victory/failure sound
if (victoryAmount && PlayerCredits >= victoryAmount) {
StartSound( "snd_victory", SND_CHANNEL_ANY, 0, true, NULL );
ActivateTargets( gameLocal.GetLocalPlayer() );
victoryAmount = 0;
}
else if (creditsWon > 0) {
StartSound( "snd_win", SND_CHANNEL_ANY, 0, true, NULL );
}
else if (creditsWon < 0) {
StartSound( "snd_lose", SND_CHANNEL_ANY, 0, true, NULL );
}
}
void hhBlackJack::UpdateBetMechanics() {
bCanIncBet = PlayerCredits > PlayerBet;
bCanDecBet = PlayerBet > 0;
if (PlayerCredits < PlayerBet) {
PlayerBet = PlayerCredits;
}
}
void hhBlackJack::EndGame() {
bCanDeal = 1;
UpdateBetMechanics();
bCanHit = 0;
bCanStay = 0;
bCanDouble = 0;
bCanSplit = 0;
}
void hhBlackJack::UpdateView() {
// UpdateView() is posted as an event because sometimes we're already nested down in the gui handling code when it is called
// and it in turn re-enters the gui handling code with HandleNamedEvent()
CancelEvents(&EV_UpdateView);
PostEventMS(&EV_UpdateView, 0);
}
void hhBlackJack::Event_UpdateView() {
int ix;
bool bGameOver = false;
idUserInterface *gui = renderEntity.gui[0];
if (gui) {
if (PlayerCredits <= 0) {
bCanIncBet = bCanDecBet = bCanHit = bCanStay = bCanDouble = bCanSplit = bCanDeal = false;
bGameOver = true;
}
gui->SetStateBool("bgameover", bGameOver);
gui->SetStateBool("bcanincbet", bCanIncBet);
gui->SetStateBool("bcandecbet", bCanDecBet);
gui->SetStateBool("bcanhit", bCanHit);
gui->SetStateBool("bcanstay", bCanStay);
gui->SetStateBool("bcandouble", bCanDouble);
gui->SetStateBool("bcansplit", bCanSplit);
gui->SetStateBool("bcandeal", bCanDeal);
gui->SetStateInt("credits", PlayerCredits);
gui->SetStateInt("currentbet", Bet);
gui->SetStateInt("result", resultIndex);
gui->SetStateInt("creditswon", creditsWon);
// Clear
for (ix=0; ix<6; ix++) {
gui->SetStateInt(va("Dealer%d_Visible", ix+1), 0);
gui->SetStateInt(va("Player%d_Visible", ix+1), 0);
gui->SetStateBool(va("Dealer%d_Flipped", ix+1), 0);
gui->SetStateBool(va("Player%d_Flipped", ix+1), 0);
}
// Show dealer hand
for (ix=0; ix<DealerHand.Num(); ix++) {
gui->SetStateInt(va("Dealer%d_Visible", ix+1), 1);
if (DealerHand[ix].Visible) {
gui->SetStateBool(va("Dealer%d_Flipped", ix+1), 1);
gui->SetStateInt(va("Dealer%d_Suit", ix+1), DealerHand[ix].Suit);
char cardChar = DealerHand[ix].Name;
if (cardChar == 'T') {
gui->SetStateString(va("Dealer%d_Card", ix+1), "10");
}
else {
gui->SetStateString(va("Dealer%d_Card", ix+1), va("%c", cardChar));
}
gui->SetStateInt(va("Dealer%d_Red", ix+1), DealerHand[ix].Suit==SUIT_HEARTS || DealerHand[ix].Suit==SUIT_DIAMONDS ? 1 : 0);
}
}
// Show player hand
for (ix=0; ix<PlayerHand.Num(); ix++) {
gui->SetStateInt(va("Player%d_Visible", ix+1), 1);
if (PlayerHand[ix].Visible) {
gui->SetStateBool(va("Player%d_Flipped", ix+1), 1);
gui->SetStateInt(va("Player%d_Suit", ix+1), PlayerHand[ix].Suit);
char cardChar = PlayerHand[ix].Name;
if (cardChar == 'T') {
gui->SetStateString(va("Player%d_Card", ix+1), "10");
}
else {
gui->SetStateString(va("Player%d_Card", ix+1), va("%c", cardChar));
}
gui->SetStateInt(va("Player%d_Red", ix+1), PlayerHand[ix].Suit==SUIT_HEARTS || PlayerHand[ix].Suit==SUIT_DIAMONDS ? 1 : 0);
}
}
gui->StateChanged(gameLocal.time, true);
CallNamedEvent("Update");
}
}
void hhBlackJack::FollowDealerRules() {
// Flip up cards
for (int i=0; i<DealerHand.Num(); i++)
DealerHand[i].Visible = 1;
// Only take cards if player is NOT busted, not 5 carded, and non blackjacked
if ( PlayerScore <= 21 && PlayerHand.Num() < 5 && (PlayerScore != 21 || PlayerHand.Num() != 2) ) {
while (DealerScore <= 16 && DealerHand.Num() < 5) {
DealerHand.Append(GetCard(1));
RetallyScores();
}
}
UpdateView();
}
void hhBlackJack::DeterminePlayCommands() {
bCanStay = 1;
bCanHit = PlayerScore < 21;
bCanDouble = (PlayerHand.Num() == 2) && (Bet*2 <= PlayerCredits);
// bCanSplit = PlayerHand.Num() == 2 && (PlayerHand[0].Name == PlayerHand[1].Name);
}
bool hhBlackJack::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) {
idToken token;
if (!src->ReadToken(&token)) {
return false;
}
if (token == ";") {
return false;
}
if (token.Icmp("deal") == 0) {
Deal();
}
else if (token.Icmp("hit") == 0) {
Hit();
}
else if (token.Icmp("stay") == 0) {
Stay();
}
else if (token.Icmp("double") == 0) {
Double();
}
else if (token.Icmp("incbet") == 0) {
IncBet();
}
else if (token.Icmp("decbet") == 0) {
DecBet();
}
else if (token.Icmp("reset") == 0) {
Reset();
}
else if (token.Icmp("restart") == 0) {
PlayerCredits = spawnArgs.GetInt("credits");
Bet = PlayerBet = 1;
EndGame();
UpdateView();
}
else {
src->UnreadToken(&token);
return false;
}
return true;
}
void hhBlackJack::Event_Deal() {
Deal();
}
void hhBlackJack::Event_Hit() {
Hit();
}
void hhBlackJack::Event_Stay() {
Stay();
}
void hhBlackJack::Event_Double() {
Double();
}
+70
View File
@@ -0,0 +1,70 @@
#ifndef __GAME_BLACKJACK_H__
#define __GAME_BLACKJACK_H__
extern const idEventDef EV_Deal;
typedef struct card_s {
int Suit;
int Value;
char Name;
unsigned char Visible;
}card_t;
class hhBlackJack : public hhConsole {
public:
CLASS_PROTOTYPE( hhBlackJack );
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Deal();
void Hit();
void Stay();
void Double();
void IncBet();
void DecBet();
void Reset();
card_t GetCard(bool visible);
void RetallyScores();
void AssessScores();
void EndGame();
void UpdateView();
void FollowDealerRules();
void DeterminePlayCommands();
void UpdateBetMechanics();
bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src);
protected:
void Event_Deal();
void Event_Hit();
void Event_Stay();
void Event_Double();
void Event_UpdateView();
private:
idList <card_t> PlayerHand;
idList <card_t> DealerHand;
int Bet;
int PlayerBet;
int DealerScore, PlayerScore;
int DealerAces, PlayerAces;
int PlayerCredits;
int victoryAmount;
int resultIndex;
int creditsWon;
bool bCanDeal;
bool bCanIncBet;
bool bCanDecBet;
bool bCanHit;
bool bCanStay;
bool bCanDouble;
bool bCanSplit;
};
#endif /* __GAME_BLACKJACK_H__ */
+130
View File
@@ -0,0 +1,130 @@
// game_cards.cpp
//
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
static char cardnames[] = { '2','3','4','5','6','7','8','9','T','J','Q','K','A' };
static char suitnames[] = { 'D','H','S','C' };
//==============================================================
// hhCard utility class
//==============================================================
hhCard::hhCard() {
suit = SUIT_SPADES;
value = CARD_ACE;
}
hhCard::hhCard(int value, int suit) {
this->suit = suit;
this->value = value;
}
int hhCard::operator==(const hhCard &other) const {
return value==other.Value() && suit==other.Suit();
}
int hhCard::Value() const {
return value;
}
int hhCard::Suit() const {
return suit;
}
char hhCard::ValueName() {
return cardnames[value];
}
char hhCard::SuitName() {
return suitnames[suit];
}
//==============================================================
// hhDeck utility class
//==============================================================
CLASS_DECLARATION(idClass, hhDeck)
END_CLASS
hhDeck::hhDeck() {
Generate();
Shuffle();
}
void hhDeck::Save(idSaveGame *savefile) const {
int i;
savefile->WriteInt( cards.Num() ); // hhStack<hhCard>
for (i=0; i<cards.Num(); i++) {
savefile->Write(&cards[i], sizeof(hhCard));
}
}
void hhDeck::Restore( idRestoreGame *savefile ) {
int i, num;
hhCard card;
cards.Clear(); // hhStack<hhCard>
savefile->ReadInt( num );
cards.SetNum( num );
for (i=0; i<num; i++) {
savefile->Read(&card, sizeof(hhCard));
cards[i] = card;
}
}
void hhDeck::Generate() {
int value, suit;
cards.Clear();
for (value=0; value<NUM_CARD_VALUES; value++) {
for (suit=0; suit<NUM_SUITS; suit++) {
hhCard card(value, suit);
cards.Append(card);
}
}
}
void hhDeck::Shuffle() {
int numCards = cards.Num();
for (int ix=0; ix<5; ix++) {
for (int c1=0; c1<numCards; c1++) {
int c2 = gameLocal.random.RandomInt(numCards);
idSwap(cards[c1], cards[c2]);
}
// Cut?
}
}
bool hhDeck::HasCard(hhCard &card) {
int numCards = cards.Num();
for (int ix=0; ix<numCards; ix++) {
if (cards[ix] == card) {
return true;
}
}
return false;
}
hhCard hhDeck::GetCard() {
return cards.Pop();
}
hhCard hhDeck::GetCard(int value, int suit) {
hhCard card(value, suit);
assert(HasCard(card));
// Find card and remove from deck
cards.Remove(card);
return card;
}
void hhDeck::ReturnCard(hhCard card) {
cards.Push(card);
}
+63
View File
@@ -0,0 +1,63 @@
#ifndef __GAME_CARDS_H__
#define __GAME_CARDS_H__
enum suits_e {
SUIT_DIAMONDS=0,
SUIT_HEARTS,
SUIT_SPADES,
SUIT_CLUBS,
NUM_SUITS
};
enum cards_e {
CARD_DEUCE=0,
CARD_THREE,
CARD_FOUR,
CARD_FIVE,
CARD_SIX,
CARD_SEVEN,
CARD_EIGHT,
CARD_NINE,
CARD_TEN,
CARD_JACK,
CARD_QUEEN,
CARD_KING,
CARD_ACE,
NUM_CARD_VALUES
};
class hhCard {
public:
hhCard();
hhCard(int value, int suit);
char ValueName();
char SuitName();
int operator==(const hhCard &other) const;
int Value() const;
int Suit() const;
private:
int value;
int suit;
};
class hhDeck : public idClass {
CLASS_PROTOTYPE( hhDeck );
public:
hhDeck();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Generate();
void Shuffle();
bool HasCard(hhCard &card);
hhCard GetCard();
hhCard GetCard(int value, int suit);
void ReturnCard(hhCard card);
private:
hhStack<hhCard> cards;
};
#endif /* __GAME_CARDS_H__ */
+300
View File
@@ -0,0 +1,300 @@
//**************************************************************************
//**
//** GAME_CILIA.CPP
//**
//** Specific type of SpherePart. When shot, they retract for a while,
//** and trigger nearby cilia to retract as well.
//**************************************************************************
// HEADER FILES ------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
// CLASS DECLARATIONS ------------------------------------------------------
const idEventDef EV_StickOut( "stickout", NULL );
const idEventDef EV_TriggerNearby( "triggernearby", NULL );
const idEventDef EV_Idle( "idle", NULL );
CLASS_DECLARATION( hhSpherePart, hhSphereCilia )
EVENT( EV_Activate, hhSphereCilia::Event_Trigger )
EVENT( EV_TriggerNearby, hhSphereCilia::Event_TriggerNearby )
EVENT( EV_StickOut, hhSphereCilia::Event_StickOut )
EVENT( EV_Idle, hhSphereCilia::Event_Idle ) // JRM
EVENT( EV_Touch, hhSphereCilia::Event_Touch )
END_CLASS
// STATE DECLARATIONS -------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// hhSphereCilia::Spawn
//
//==========================================================================
void hhSphereCilia::Spawn(void) {
fl.takedamage = true; // Allow the spherepart to be damaged
fl.allowSpiritWalkTouch = true;
bRetracted = false; // Start sticking out
bAlreadyActivated = false; // Hasn't been touched yet
//HUMANHEAD jsh PCF 4/26/06 allow creatures to trigger cilia
GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL );
idleAnim = GetAnimator()->GetAnim("idle");
pullInAnim = GetAnimator()->GetAnim("pullin");
stickOutAnim = GetAnimator()->GetAnim("stickout");
spawnArgs.GetFloat( "nearbySize", "128", nearbySize );
spawnArgs.GetFloat( "idleDelay", "-1", idleDelay );
if( idleDelay < 0 ) { // negative number denotes a random start delay
idleDelay = gameLocal.random.RandomFloat();
}
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time + idleDelay * 1000, 100 );
}
void hhSphereCilia::Save(idSaveGame *savefile) const {
savefile->WriteInt( pullInAnim );
savefile->WriteInt( stickOutAnim );
savefile->WriteFloat( nearbySize );
savefile->WriteFloat( idleDelay );
savefile->WriteBool( bRetracted );
savefile->WriteBool( bAlreadyActivated );
}
void hhSphereCilia::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( pullInAnim );
savefile->ReadInt( stickOutAnim );
savefile->ReadFloat( nearbySize );
savefile->ReadFloat( idleDelay );
savefile->ReadBool( bRetracted );
savefile->ReadBool( bAlreadyActivated );
}
//==========================================================================
//
// hhSphereCilia::Damage
//
// Currently similar to
//==========================================================================
void hhSphereCilia::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
const char *damageDefName, const float damageScale, const int location ) {
hhFxInfo fxInfo;
if( bRetracted ) { // Don't trigger if already retracted
return;
}
bRetracted = true;
// Remove collision on the model
GetPhysics()->SetContents(0);
// Switch to the broken model
SetModel( spawnArgs.GetString( "model_broken", "" ) );
// Play an explode sound
StartSound( "snd_explode", SND_CHANNEL_ANY, 0, true, NULL );
fl.takedamage = false;
fl.applyDamageEffects = false; // Cilia don't accept damage wounds, since they swap out models
PostEventSec( &EV_TriggerNearby, 0.2f + gameLocal.random.RandomFloat() * 0.1 );
ApplyEffect();
}
//==========================================================================
//
// hhSphereCilia::Event_Touch
//
//==========================================================================
void hhSphereCilia::Event_Touch( idEntity *other, trace_t *trace ) {
Trigger( other );
}
//==========================================================================
//
// hhSphereCilia::Trigger
//
//==========================================================================
void hhSphereCilia::Trigger( idEntity *activator ) {
if( bRetracted || !activator ) { // Don't trigger if already retracted
return;
}
bRetracted = true;
// Remove collision on the model
GetPhysics()->SetContents(0);
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, pullInAnim, gameLocal.time, 100);
StartSound( "snd_in", SND_CHANNEL_ANY, 0, true, NULL );
PostEventSec( &EV_TriggerNearby, 0.2f + gameLocal.random.RandomFloat() * 0.1 );
PostEventSec( &EV_StickOut, 5.0f + idleDelay );
// Trigger this cilia's targets the first time it is touched
if ( !bAlreadyActivated ) {
ActivateTargets( activator );
bAlreadyActivated = true;
}
ApplyEffect();
}
//==========================================================================
//
// hhSphereCilia::Event_Trigger
//
//==========================================================================
void hhSphereCilia::Event_Trigger( idEntity *activator ) {
Trigger( activator );
}
//==========================================================================
//
// hhSphereCilia::Event_TriggerNearby
//
// Trigger nearby cilia (to create a cascading retract effect)
//==========================================================================
void hhSphereCilia::Event_TriggerNearby( void ) {
int i;
int e;
hhSphereCilia *ent;
idEntity *entityList[ MAX_GENTITIES ];
int numListedEntities;
idBounds bounds;
idVec3 org;
org = GetPhysics()->GetOrigin();
for ( i = 0 ; i < 3 ; i++ ) {
bounds[0][i] = org[i] - nearbySize;
bounds[1][i] = org[i] + nearbySize;
}
// Find the first closest neighbor cilia to trigger
numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES );
for ( e = 0 ; e < numListedEntities ; e++ ) {
if( !entityList[e]->IsType( hhSphereCilia::Type ) ) {
continue;
}
ent = static_cast< hhSphereCilia * >( entityList[e] );
if( ent->bRetracted ) {
continue;
}
ent->Trigger( this );
}
}
//==========================================================================
//
// hhSphereCilia::Event_StickOut
//
// Return the cilia to its original state
//==========================================================================
#define MAX_NEARBY_CILIA 8
void hhSphereCilia::Event_StickOut( void ) {
int i;
int num;
idEntity *touch[MAX_NEARBY_CILIA];
idEntity *hit;
bool canFit;
// Check if the cilia can fit when sticking out
num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), -1, touch, MAX_NEARBY_CILIA );
canFit = true;
for( i = 0; i < num; i++ ) {
hit = touch[ i ];
assert( hit );
if(( hit == this )) {
continue;
}
// Hit an entity, so reset the cilia to attempt to emerge in a bit
PostEventSec( &EV_StickOut, 5.0f );
return;
}
//HUMANHEAD jsh PCF 4/26/06 allow creatures to trigger cilia
// Restore the proper collision
GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL );
// Play the stick out anims and blend the idle back in
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, stickOutAnim, gameLocal.time, 100);
PostEventMS(&EV_Idle, 100); // JRM
StartSound( "snd_out", SND_CHANNEL_ANY, 0, true, NULL);
bRetracted = false;
}
//
// Event_Idle
//
// JRM
void hhSphereCilia::Event_Idle(void) {
assert(idleAnim);
GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 200);
};
void hhSphereCilia::ApplyEffect( void ) {
hhFxInfo fxInfo;
fxInfo.SetNormal( GetAxis()[0] );
fxInfo.RemoveWhenDone( true );
BroadcastFxInfoPrefixed( "fx_explode", GetOrigin() + (GetAxis()[0] * 16.0f), GetAxis(), &fxInfo );
int num;
int i;
idEntity* ents[MAX_GENTITIES];
num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX|CONTENTS_RENDERMODEL, ents, MAX_GENTITIES );
const char *damageType = spawnArgs.GetString("damageType", "damage_cilia");
for( i = 0; i < num; i++ ) {
if( ents[ i ] != this && !ents[ i ]->IsType(hhSphereCilia::Type) && !ents[ i ]->IsType(idAFAttachment::Type) ) {
ents[ i ]->Damage( this, this, vec3_origin, damageType, 1.0f, INVALID_JOINT );
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef __GAME_CILIA_H
#define __GAME_CILIA_H
extern const idEventDef EV_TriggerNearby;
extern const idEventDef EV_StickOut;
class hhSphereCilia : public hhSpherePart {
public:
CLASS_PROTOTYPE( hhSphereCilia );
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Trigger( idEntity *activator );
void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
protected:
void Event_Trigger( idEntity *activator );
void Event_TriggerNearby( void );
void Event_StickOut( void );
void Event_Idle( void ); // JRM
void Event_Touch( idEntity *other, trace_t *trace );
void ApplyEffect( void );
protected:
int pullInAnim;
int stickOutAnim;
float nearbySize;
float idleDelay;
bool bRetracted;
bool bAlreadyActivated;
};
#endif /* __GAME_CILIA_H */
+822
View File
@@ -0,0 +1,822 @@
// Game_console.cpp
//
// Container class for all world objects that have GUIs on them. (except monsters & weapons)
// Provides some keys to these guis not provided by default GUI code like "rand"
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
#define TRANSLATION_TIME 1000 // Time over which translation takes place
const idEventDef EV_CallGuiEvent("guiEvent", "s");
//==========================================================================
//
// hhConsole
//
//==========================================================================
CLASS_DECLARATION(idStaticEntity, hhConsole)
EVENT( EV_Activate, hhConsole::Event_Activate )
EVENT( EV_TalonAction, hhConsole::Event_TalonAction )
EVENT( EV_CallGuiEvent, hhConsole::Event_CallGuiEvent )
EVENT( EV_BecomeNonSolid, hhConsole::Event_BecomeNonSolid )
EVENT( EV_PostSpawn, hhConsole::Event_PostSpawn )
END_CLASS
void hhConsole::Spawn() {
// BecomeActive(TH_MISC2); // For ai
bTimeEventsAlways = spawnArgs.GetBool("timeEventsAlways");
bUsesRand = spawnArgs.GetBool("usesRand");
if (bUsesRand || bTimeEventsAlways) {
BecomeActive(TH_THINK);
}
bool bDamageable = !spawnArgs.GetBool("noDamage");
if (bDamageable) {
fl.takedamage = true;
}
// Stuff color into gui_parms 20,21,22
idVec3 color;
GetColor(color);
SetOnAllGuis("gui_parm20", color[0]);
SetOnAllGuis("gui_parm21", color[1]);
SetOnAllGuis("gui_parm22", color[2]);
// Translation variables
translationAlpha.Init(gameLocal.time, 0, 0.0f, 0.0f); // Start at 0 (untranslated)
transState = TS_UNTRANSLATED;
// Start idle sound
StartSound("snd_idle", SND_CHANNEL_ANY, 0, true, NULL);
UpdateVisuals();
// Init AI data
aiCanUse = spawnArgs.GetBool("ai_can_use");
aiUseCount = 0;
aiReuseWaitTime = (int)(spawnArgs.GetFloat("ai_reuse_wait")*1000.0f);
aiUseTime = (int)(spawnArgs.GetFloat("ai_use_time")*1000.0f);
aiTriggerWaitTime = (int)(spawnArgs.GetFloat("ai_use_trigger_wait")*1000.0f);
aiMaxUses = spawnArgs.GetInt("ai_max_uses", "-1");
aiRetriggerWait = (int)(spawnArgs.GetFloat("ai_use_retrigger_wait")*1000.0f);
// Clamp the trigger wait to be less than the total use time
if(aiTriggerWaitTime > aiUseTime) {
aiTriggerWaitTime = aiUseTime - 32;
if(aiTriggerWaitTime < 0) {
aiTriggerWaitTime = 0;
}
}
aiCurrUsedStartTime = gameLocal.GetTime();
aiCurrUsedBy = NULL;
aiLastUsedBy = NULL;
aiLastUsedTime = -aiReuseWaitTime;
aiWaitingToTrigger = true;
aiLastTriggerTime = gameLocal.GetTime() - 1000;
perchSpot = NULL;
PostEventMS( &EV_PostSpawn, 0 );
}
void hhConsole::Event_PostSpawn() {
// Automatically cause all consoles to spawn Talon perch spots
if(!spawnArgs.GetBool("noTalonTarget")) {
idDict args;
idVec3 offset;
bool bGuiInteractive = false;
// cjr - Iterate through guis and determine if any are interactive. If all are not, then Talon should not squawk
for ( int ix = 0; ix < MAX_RENDERENTITY_GUI; ix++ ) {
if ( renderEntity.gui[ix] && renderEntity.gui[ix]->IsInteractive() ) {
bGuiInteractive = true;
}
}
const char *perchDef;
if ( spawnArgs.GetBool( "noTalonSquawk" ) || !bGuiInteractive) { // CJR: Check if talon should squawk at this spot
perchDef = spawnArgs.GetString("def_perch");
} else {
perchDef = spawnArgs.GetString("def_perchSquawk"); // Talon should squawk at this spot
}
if (!gameLocal.isClient) {
if (perchDef && perchDef[0]) {
// Set the offset for the perch location and priority
offset = GetPhysics()->GetAxis() * spawnArgs.GetVector("offset_perch", "0 0 24");
float offsetYaw = spawnArgs.GetFloat("offset_perchyaw");
args.SetVector("origin", GetPhysics()->GetOrigin() + offset);
idAngles angles = GetAxis().ToAngles();
angles.yaw += offsetYaw;
args.SetMatrix("rotation", angles.ToMat3());
args.Set("target", this->name.c_str());
perchSpot = (hhTalonTarget *)gameLocal.SpawnObject( perchDef, &args ); // Consoles are automatically high priority spots
if (perchSpot && ( spawnArgs.GetBool("bindPerchSpot") || IsBound() ) ) {
perchSpot->Bind(this, true);
}
}
else {
gameLocal.Warning("Need def_perch key on %s", name.c_str());
perchSpot = NULL;
}
}
}
}
void hhConsole::Save(idSaveGame *savefile) const {
savefile->WriteFloat( translationAlpha.GetStartTime() ); // idInterpolate<float>
savefile->WriteFloat( translationAlpha.GetDuration() );
savefile->WriteFloat( translationAlpha.GetStartValue() );
savefile->WriteFloat( translationAlpha.GetEndValue() );
savefile->Write( &transState, sizeof(transState) );
savefile->WriteBool( bTimeEventsAlways );
savefile->WriteBool( bUsesRand );
savefile->WriteObject( perchSpot );
savefile->WriteBool( aiCanUse );
savefile->WriteInt( aiUseCount );
savefile->WriteInt( aiMaxUses );
savefile->WriteInt( aiReuseWaitTime );
savefile->WriteInt( aiUseTime );
savefile->WriteInt( aiTriggerWaitTime );
savefile->WriteBool( aiWaitingToTrigger );
savefile->WriteInt( aiLastTriggerTime );
savefile->WriteInt( aiRetriggerWait );
aiCurrUsedBy.Save(savefile);
savefile->WriteInt( aiCurrUsedStartTime );
aiLastUsedBy.Save(savefile);
savefile->WriteInt( aiLastUsedTime );
}
void hhConsole::Restore( idRestoreGame *savefile ) {
float set;
savefile->ReadFloat( set ); // idInterpolate<float>
translationAlpha.SetStartTime( set );
savefile->ReadFloat( set );
translationAlpha.SetDuration( set );
savefile->ReadFloat( set );
translationAlpha.SetStartValue(set);
savefile->ReadFloat( set );
translationAlpha.SetEndValue( set );
savefile->Read( &transState, sizeof(transState) );
savefile->ReadBool( bTimeEventsAlways );
savefile->ReadBool( bUsesRand );
savefile->ReadObject( reinterpret_cast<idClass *&>(perchSpot) );
savefile->ReadBool( aiCanUse );
savefile->ReadInt( aiUseCount );
savefile->ReadInt( aiMaxUses );
savefile->ReadInt( aiReuseWaitTime );
savefile->ReadInt( aiUseTime );
savefile->ReadInt( aiTriggerWaitTime );
savefile->ReadBool( aiWaitingToTrigger );
savefile->ReadInt( aiLastTriggerTime );
savefile->ReadInt( aiRetriggerWait );
aiCurrUsedBy.Restore(savefile);
savefile->ReadInt( aiCurrUsedStartTime );
aiLastUsedBy.Restore(savefile);
savefile->ReadInt( aiLastUsedTime );
}
void hhConsole::Event_CallGuiEvent(const char *eventName) {
CallNamedEvent(eventName);
}
void hhConsole::Event_BecomeNonSolid( void ) {
GetPhysics()->GetClipModel()->SetContents( 0 );
GetPhysics()->SetClipMask( 0 );
}
void hhConsole::SetOnAllGuis(const char *key, float value) {
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateFloat(key, value);
renderEntity.gui[ix]->StateChanged(gameLocal.time);
}
}
}
void hhConsole::SetOnAllGuis(const char *key, int value) {
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateInt(key, value);
renderEntity.gui[ix]->StateChanged(gameLocal.time);
}
}
}
void hhConsole::SetOnAllGuis(const char *key, bool value) {
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateBool(key, value);
renderEntity.gui[ix]->StateChanged(gameLocal.time);
}
}
}
void hhConsole::SetOnAllGuis(const char *key, const char *value) {
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateString(key, value);
renderEntity.gui[ix]->StateChanged(gameLocal.time);
}
}
}
void hhConsole::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
fl.takedamage = false;
// JRM - disable msgs on kill?
if(spawnArgs.GetBool("ai_damage_disable_msgs","0")) {
fl.refreshReactions = false;
}
// Inform the gui that we are damaged (not used, since gui materials can't see entity parms)
// SetShaderParm(SHADERPARM_DIVERSITY, gameLocal.random.RandomFloat());
SetOnAllGuis("damaged", true);
// Tell the gui that we died, so the gui can be in sync
CallNamedEvent("Death");
}
bool hhConsole::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) {
idToken token;
if (!src->ReadToken(&token) || token == ";") {
return false;
}
if (token.Icmp("setfont") == 0) {
if (src->ReadToken(&token)) {
idStr fontname = "fonts\\";
fontname += token;
if (renderEntity.gui[0]) {
// renderEntity.gui[0]->Translate(fontname.c_str()); // jmarshall
}
}
return true;
}
src->UnreadToken( &token );
return false;
}
// This is overridden so we can update camera targets reguardless of PVS
void hhConsole::Present( void ) {
PROFILE_SCOPE("Present", PROFMASK_NORMAL);
if ( !gameLocal.isNewFrame ) {
return;
}
// don't present to the renderer if the entity hasn't changed
if ( !( thinkFlags & TH_UPDATEVISUALS ) ) {
return;
}
BecomeInactive( TH_UPDATEVISUALS );
// camera target for remote render views
if ( cameraTarget ) { // && gameLocal.InPlayerPVS( this ) ) { // HUMANHEAD pdm: removed PVS check
renderEntity.remoteRenderView = cameraTarget->GetRenderView();
}
// if set to invisible, skip
if ( !renderEntity.hModel || IsHidden() ) {
return;
}
// add to refresh list
if ( modelDefHandle == -1 ) {
modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
} else {
gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity );
}
}
void hhConsole::Think() {
if (thinkFlags & TH_THINK) {
if (bUsesRand) {
float rand = gameLocal.random.RandomFloat();
SetOnAllGuis("rand", rand);
}
if (bTimeEventsAlways) {
// Call event handler so time events get run
const char *cmd;
sysEvent_t ev;
memset( &ev, 0, sizeof( ev ) );
ev.evType = SE_NONE;
// This should work after the merge is done, since in the new code, HandleEvent() calls RunTimeEvents()
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
cmd = renderEntity.gui[ix]->HandleEvent(&ev, gameLocal.time);
HandleGuiCommands(this, cmd);
}
}
}
}
if (thinkFlags & TH_MISC1) {
// Handle any translation
if (transState == TS_TRANSLATING || transState == TS_UNTRANSLATING) {
float alpha = translationAlpha.GetCurrentValue(gameLocal.time);
if (renderEntity.gui[0] || renderEntity.gui[1] || renderEntity.gui[2]) {
SetOnAllGuis("translationAlpha", alpha);
// Determine if the transition is done
if (translationAlpha.IsDone(gameLocal.time)) {
if (transState == TS_TRANSLATING) {
transState = TS_TRANSLATED;
BecomeInactive(TH_MISC1);
}
else if (transState == TS_UNTRANSLATING) {
transState = TS_UNTRANSLATED;
BecomeInactive(TH_MISC1);
}
}
}
}
}
// JRM - ai using update
if (thinkFlags & TH_MISC2) {
UpdateUse();
}
idStaticEntity::Think();
}
// Toggles the translation state of the gui
void hhConsole::Translate(bool bLanded) {
if (!spawnArgs.GetBool("translate")) {
return;
}
float curalpha = translationAlpha.GetCurrentValue(gameLocal.time);
switch(transState) {
case TS_UNTRANSLATED:
case TS_UNTRANSLATING:
translationAlpha.Init(gameLocal.time, TRANSLATION_TIME, curalpha, 1.0f);
transState = TS_TRANSLATING;
BecomeActive(TH_MISC1);
break;
case TS_TRANSLATED:
case TS_TRANSLATING:
translationAlpha.Init(gameLocal.time, TRANSLATION_TIME, curalpha, 0.0f);
transState = TS_UNTRANSLATING;
BecomeActive(TH_MISC1);
break;
}
if (bLanded) {
StartSound( "snd_translate", SND_CHANNEL_ANY, 0, true, NULL);
}
}
void hhConsole::ConsoleActivated() {
// Called when someone "uses" the gui (gui calls activate cmd)
// JRM - turn off msgs after the player uses this
if(!aiCurrUsedBy.IsValid() && spawnArgs.GetBool("ai_player_use_disable_msgs", "0")) {
fl.refreshReactions = false;
}
}
void hhConsole::ClearTalonTargetType() { // CJR - Called when any command is sent to a gui. This disables talon's squawking
if ( perchSpot ) {
perchSpot->SetSquawk( false ); // Set the console to stop Talon from squawking
}
}
void hhConsole::Event_Activate( idEntity *activator ) {
// Don't toggle on/off like in idStaticEntity
// any GUIs still get the onTrigger() event
}
void hhConsole::Event_TalonAction(idEntity *talon, bool landed) {
Translate(landed);
}
void hhConsole::Use(idAI *ai) {
HH_ASSERT(ai);
// Just started using console?
if(!aiCurrUsedBy.IsValid()) {
aiCurrUsedBy = ai;
aiCurrUsedStartTime = gameLocal.GetTime();
BecomeActive(TH_MISC2);
}
}
void hhConsole::UpdateUse(void) {
// Time to deactivate?
if(aiCurrUsedBy.IsValid() && gameLocal.GetTime() > aiCurrUsedStartTime + aiUseTime) {
aiLastUsedBy = aiCurrUsedBy;
aiLastUsedTime = gameLocal.GetTime();
aiCurrUsedBy = NULL;
aiWaitingToTrigger = true;
aiUseCount++;
if(aiMaxUses > 0 && aiUseCount >= aiMaxUses) {
fl.refreshReactions = false;
}
BecomeInactive(TH_MISC2);
}
// Time to fire triggers?
if (aiCurrUsedBy.IsValid()) {
// Waiting to trigger...
if(aiWaitingToTrigger && gameLocal.GetTime() > aiCurrUsedStartTime + aiTriggerWaitTime) {
aiWaitingToTrigger = false;
OnTriggeredByAI(aiCurrUsedBy.GetEntity());
}
// Time to re-trigger?
else if (!aiWaitingToTrigger && aiRetriggerWait > 0 && gameLocal.GetTime() - aiLastTriggerTime > aiRetriggerWait) {
OnTriggeredByAI(aiCurrUsedBy.GetEntity());
}
}
}
void hhConsole::OnTriggeredByAI(idAI *ai) {
aiLastTriggerTime = gameLocal.GetTime();
// Tell the gui that we were used, so the gui can be in sync
CallNamedEvent("AIUse");
}
bool hhConsole::CanUse(idAI *ai) {
HH_ASSERT(ai);
if (!aiCanUse) {
return false;
}
if (aiMaxUses > 0 && aiUseCount >= aiMaxUses) {
return false;
}
// Can't use it if someone else is using it
if (aiCurrUsedBy.IsValid() && aiCurrUsedBy.GetEntity() != ai) {
return false;
}
// Have to wait before we can use it again?
if (gameLocal.GetTime() < aiLastUsedTime + aiReuseWaitTime) {
return false;
}
return ai->spawnArgs.GetBool("CanUseConsole", "0");
}
//==========================================================================
//
// hhConsoleCountdown
//
//==========================================================================
const idEventDef EV_CountdownStart("startcountdown", NULL);
const idEventDef EV_CountdownStop("stopcountdown", NULL);
const idEventDef EV_CountdownSet("setcountdown", "d");
CLASS_DECLARATION(hhConsole, hhConsoleCountdown)
EVENT( EV_Activate, hhConsoleCountdown::Event_Activate )
EVENT( EV_CountdownStart, hhConsoleCountdown::Event_StartCountdown )
EVENT( EV_CountdownStop, hhConsoleCountdown::Event_StopCountdown )
EVENT( EV_CountdownSet, hhConsoleCountdown::Event_SetCountdown )
END_CLASS
void hhConsoleCountdown::Spawn() {
countingDown = false;
countStart = spawnArgs.GetFloat("countStart");
countEnd = spawnArgs.GetFloat("countEnd");
countdown.Init(gameLocal.time, 0, countStart, countStart);
SetGuiOctal((int)countStart);
if (spawnArgs.GetBool("enabled")) {
StartCountdown();
}
}
void hhConsoleCountdown::Save(idSaveGame *savefile) const {
savefile->WriteBool(countingDown);
savefile->WriteFloat(countStart);
savefile->WriteFloat(countEnd);
savefile->WriteFloat( countdown.GetStartTime() ); // idInterpolate<float>
savefile->WriteFloat( countdown.GetDuration() );
savefile->WriteFloat( countdown.GetStartValue() );
savefile->WriteFloat( countdown.GetEndValue() );
}
void hhConsoleCountdown::Restore( idRestoreGame *savefile ) {
float set;
savefile->ReadBool(countingDown);
savefile->ReadFloat(countStart);
savefile->ReadFloat(countEnd);
savefile->ReadFloat( set ); // idInterpolate<float>
countdown.SetStartTime( set );
savefile->ReadFloat( set );
countdown.SetDuration( set );
savefile->ReadFloat( set );
countdown.SetStartValue(set);
savefile->ReadFloat( set );
countdown.SetEndValue( set );
}
void hhConsoleCountdown::SetGuiOctal(int value) {
// Convert to base-8 numbers and set on gui
idStr octalValue;
sprintf(octalValue, "%o", value);
SetOnAllGuis("countdown", octalValue.c_str());
}
void hhConsoleCountdown::UpdateGUI(float curValue) {
SetGuiOctal((int)curValue);
SetOnAllGuis("fraction", curValue / countStart);
SetOnAllGuis("counting", countingDown);
}
void hhConsoleCountdown::Think() {
hhConsole::Think();
if (thinkFlags & TH_MISC3) {
if (countingDown) {
UpdateGUI(countdown.GetCurrentValue(gameLocal.time));
if (countdown.IsDone(gameLocal.time)) {
BecomeInactive(TH_MISC3);
ActivateTargets(this);
StopCountdown();
}
}
}
}
void hhConsoleCountdown::SetCountdown(int count) {
if (countingDown) {
gameLocal.Warning("You must stop countdown before set");
return;
}
countStart = count;
countdown.Init(gameLocal.time, 0, countStart, countStart);
UpdateGUI(count);
}
void hhConsoleCountdown::StartCountdown() {
if (!countingDown) {
// Start counting down from current value
float curValue = countdown.GetCurrentValue(gameLocal.time);
// resume from previously stopped count
countdown.Init(gameLocal.time, SEC2MS(countStart), curValue, countEnd);
BecomeActive(TH_MISC3);
countingDown = true;
}
}
void hhConsoleCountdown::StopCountdown() {
if (countingDown) {
// Stop countdown at current value
float curValue = countdown.GetCurrentValue(gameLocal.time);
countdown.Init(gameLocal.time, 0, curValue, curValue);
countingDown = false;
UpdateGUI(countdown.GetCurrentValue(gameLocal.time));
}
}
void hhConsoleCountdown::Reset() {
// Reset to initial start time (or set start time)
countdown.Init(gameLocal.time, 0, countStart, countStart);
UpdateGUI(countdown.GetCurrentValue(gameLocal.time));
}
void hhConsoleCountdown::Event_Activate(idEntity *activator) {
StartCountdown();
}
void hhConsoleCountdown::Event_SetCountdown(int count) {
SetCountdown(count);
}
void hhConsoleCountdown::Event_StartCountdown() {
StartCountdown();
}
void hhConsoleCountdown::Event_StopCountdown() {
StopCountdown();
}
//==========================================================================
//
// hhConsoleKeypad
//
// Generic console for entering text on a keypad and displaying it
//==========================================================================
CLASS_DECLARATION(hhConsole, hhConsoleKeypad)
END_CLASS
void hhConsoleKeypad::Spawn() {
maxLength = spawnArgs.GetInt("maxlength");
SetOnAllGuis("atmax", false);
}
void hhConsoleKeypad::Save(idSaveGame *savefile) const {
savefile->WriteString(keyBuffer);
savefile->WriteInt(maxLength);
}
void hhConsoleKeypad::Restore( idRestoreGame *savefile ) {
savefile->ReadString(keyBuffer);
savefile->ReadInt(maxLength);
}
void hhConsoleKeypad::UpdateGui() {
int length = keyBuffer.Length();
// Enforce maxlength
if (length > maxLength) {
keyBuffer = keyBuffer.Left(maxLength);
}
SetOnAllGuis("atmax", (length >= maxLength));
SetOnAllGuis("keypad", keyBuffer.c_str());
}
bool hhConsoleKeypad::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) {
idToken token;
if (!src->ReadToken(&token) || token == ";") {
return false;
}
if (token.Icmp("backspace") == 0) {
if (keyBuffer.Length() > 0) {
keyBuffer = keyBuffer.Left(keyBuffer.Length()-1);
}
}
else if (token.Icmp("clear") == 0) {
keyBuffer.Empty();
}
else if (token.Icmp("enter") == 0) {
keyBuffer += '\n';
}
else if (token.IcmpPrefix("keypad_") == 0) { // KEYPAD_X
token.ToLower();
token.Strip("keypad_");
keyBuffer += token.c_str();
}
else {
return false;
}
UpdateGui();
return true;
}
//==========================================================================
//
// hhConsoleAlarm
//
// alarm console
//==========================================================================
const idEventDef EV_SpawnMonster("<spawnMonster>");
CLASS_DECLARATION(hhConsole, hhConsoleAlarm)
EVENT( EV_SpawnMonster, hhConsoleAlarm::Event_SpawnMonster )
END_CLASS
void hhConsoleAlarm::Spawn() {
bAlarmActive = false;
numMonsters = 0;
bSpawning = false;
maxMonsters = spawnArgs.GetInt( "max_monsters", "0" );
BecomeActive(TH_TICKER);
}
void hhConsoleAlarm::Save(idSaveGame *savefile) const {
savefile->WriteBool(bAlarmActive);
savefile->WriteInt( numMonsters );
savefile->WriteInt( maxMonsters );
currentMonster.Save( savefile );
savefile->WriteBool( bSpawning );
}
void hhConsoleAlarm::Restore( idRestoreGame *savefile ) {
savefile->ReadBool(bAlarmActive);
savefile->ReadInt( numMonsters );
savefile->ReadInt( maxMonsters );
currentMonster.Restore( savefile );
savefile->ReadBool( bSpawning );
}
void hhConsoleAlarm::ConsoleActivated() {
bAlarmActive = !bAlarmActive;
if ( !bAlarmActive ) {
CancelEvents( &EV_SpawnMonster );
for ( int i=0;i<targets.Num();i++ ) {
if ( targets[i].IsValid() ) {
if ( targets[i]->IsType( hhMountedGun::Type ) ) {
targets[i]->PostEventMS( &EV_Deactivate, 0 );
}
targets[i]->DeactivateTargetsType( hhMountedGun::Type );
}
}
}
}
void hhConsoleAlarm::Event_SpawnMonster() {
idDict args;
// Copy keys for monster
idStr tmpStr, realKeyName;
const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL);
while(kv) {
tmpStr = kv->GetKey();
int usIndex = tmpStr.FindChar("ent_", '_');
realKeyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1);
args.Set(realKeyName, kv->GetValue());
kv = spawnArgs.MatchPrefix("ent_", kv);
}
idEntity *ent = gameLocal.FindEntity( spawnArgs.GetString( "spawn_location" ) );
if ( ent ) {
args.SetVector( "origin", ent->GetOrigin() );
} else {
gameLocal.Warning("No spawn_location specified for %s\n", GetName());
return;
}
// entity collision checks for seeing if we are going to collide with another entity on spawn
const idDict *entDef = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_monster" ), false );
if ( entDef ) {
bool clipped = false;
idVec3 entSize;
idClipModel *cm;
idClipModel *clipModels[ MAX_GENTITIES ];
entDef->GetVector( "size", "0", entSize );
idBounds bounds = idBounds( ent->GetOrigin() ).Expand( max( entSize.x, entSize.y ) );
int num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES );
if ( ai_debugBrain.GetBool() ) {
gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 5000 );
}
for ( int i=0;i<num;i++ ) {
cm = clipModels[ i ];
// don't check render entities
if ( cm->IsRenderModel() ) {
continue;
}
idEntity *hit = cm->GetEntity();
if ( ( hit == this ) || !hit->fl.takedamage || !hit->IsType( idActor::Type ) ) {
continue;
}
clipped = true;
break;
}
if ( !clipped ) {
currentMonster = gameLocal.SpawnObject(spawnArgs.GetString( "def_monster" ), &args);
if ( currentMonster.IsValid() ) {
currentMonster->PostEventMS( &EV_Activate, 0, this );
bSpawning = false;
numMonsters++;
} else {
gameLocal.Warning( "%s could not spawn monster\n", GetName() );
}
}
}
}
void hhConsoleAlarm::Ticker() {
if (bAlarmActive) {
if ( currentMonster.IsValid() && currentMonster->GetHealth() <= 0 ) {
currentMonster.Clear();
}
//check on our monster
if ( !bSpawning && !currentMonster.IsValid() && spawnArgs.GetString( "def_monster" )[0] ) {
if ( !maxMonsters || numMonsters < maxMonsters ) {
float min = spawnArgs.GetFloat( "mindelay", "2.0" );
float max = spawnArgs.GetFloat( "maxdelay", "2.0" );
if ( max < min ) {
max = min;
}
bSpawning = true;
PostEventSec( &EV_SpawnMonster, min + (max-min)*gameLocal.random.RandomFloat() );
}
}
}
hhConsole::Ticker();
}
+148
View File
@@ -0,0 +1,148 @@
#ifndef __GAME_CONSOLE_H__
#define __GAME_CONSOLE_H__
extern const idEventDef EV_CallGuiEvent;
enum ETranslationState {
TS_TRANSLATING,
TS_TRANSLATED,
TS_UNTRANSLATING,
TS_UNTRANSLATED
};
class hhTalonTarget; // CJR
class hhConsole : public idStaticEntity {
public:
CLASS_PROTOTYPE( hhConsole );
void Spawn();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Think();
virtual void ConsoleActivated();
virtual void ClearTalonTargetType(); // CJR - Called when any command is sent to a gui. This disables talon's squawking
virtual void Present( void );
virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src);
virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location);
void Translate(bool bLanded);
void SetOnAllGuis(const char *key, float value);
void SetOnAllGuis(const char *key, int value);
void SetOnAllGuis(const char *key, bool value);
void SetOnAllGuis(const char *key, const char *value);
virtual void PlayerControls(usercmd_t *cmd) {}
virtual void Use(idAI *ai); // JRM - An AI is starting to use this console
bool CanUse(idAI *ai); // JRM - Returns TRUE if the given AI is allowed to use this console right now
int GetLastUsedTime(void) {return aiLastUsedTime;}
idAI* GetLastUsedAI(void) {return aiLastUsedBy.GetEntity();}
virtual void OnTriggeredByAI(idAI *ai); // JRM - Called when the AI actually "presses" the console
void UpdateUse(void); // Called every tick
protected:
void Event_Activate(idEntity *activator);
void Event_TalonAction(idEntity *talon, bool landed);
void Event_CallGuiEvent(const char *eventName);
void Event_BecomeNonSolid( void );
void Event_PostSpawn();
protected:
idInterpolate<float> translationAlpha;
ETranslationState transState;
bool bTimeEventsAlways;
bool bUsesRand;
hhTalonTarget *perchSpot; // Associated Talon perch spot with this console
// JRM - AI data
bool aiCanUse; // True if the AI is allowed to use this console
int aiUseCount; // Number of times AI has used this console
int aiMaxUses; // Max number of times the ai can use this console
int aiReuseWaitTime; // Time the AI has to wait before they can use this console again (ms)
int aiUseTime; // How long once AI starts using this console, do they stay here? (total ms)
int aiTriggerWaitTime; // How long once AI starts using this console until we fire this console's trigger? (ms)
bool aiWaitingToTrigger; // TRUE if the ai is waiting to fire the triggers for this console
int aiLastTriggerTime; // The time we last triggered this console
int aiRetriggerWait; // -1 = Never retrigger, otherwise time (ms) that we should retrigger while using
idEntityPtr<idAI> aiCurrUsedBy; // The AI is currently being using this console
int aiCurrUsedStartTime; // The time the current user STARTED using this console
idEntityPtr<idAI> aiLastUsedBy; // The AI that last used this console
int aiLastUsedTime; // The time that aiLastUsedBy used finished using this console
};
class hhConsoleCountdown : public hhConsole {
CLASS_PROTOTYPE( hhConsoleCountdown );
public:
void Spawn();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Think();
void Reset();
void UpdateGUI(float curValue);
void SetGuiOctal(int value);
void SetCountdown(int count);
void StartCountdown();
void StopCountdown();
protected:
void Event_Activate(idEntity *activator);
void Event_SetCountdown(int count);
void Event_StartCountdown();
void Event_StopCountdown();
protected:
bool countingDown; // Whether we are running or not
float countStart; // Starting value of countdown
float countEnd; // Ending value of countdown
idInterpolate<float> countdown; // Interpolator that always carries the current count
};
class hhConsoleKeypad : public hhConsole {
CLASS_PROTOTYPE( hhConsoleKeypad );
public:
void Spawn();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src);
protected:
void UpdateGui();
protected:
idStr keyBuffer;
int maxLength;
};
class hhConsoleAlarm : public hhConsole {
CLASS_PROTOTYPE( hhConsoleAlarm );
public:
void Spawn();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void ConsoleActivated();
virtual void Ticker();
protected:
void Event_SpawnMonster();
idEntityPtr<idAI> currentMonster;
int maxMonsters;
int numMonsters;
bool bSpawning;
private:
bool bAlarmActive;
};
#endif /* __GAME_CONSOLE_H__ */
+180
View File
@@ -0,0 +1,180 @@
//**************************************************************************
//**
//** GAME_DAMAGETESTER.CPP
//**
//**
//** This object is used to calculate the damage done to it over time
//** When initially damaged, it calculates the amount of damage done for a
//** given amount of time
//**************************************************************************
// HEADER FILES ------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
#include "game_damagetester.h"
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
// CLASS DECLARATIONS ------------------------------------------------------
const idEventDef EV_ResetTarget( "<resetTarget>", NULL );
const idEventDef EV_CheckRemove( "<checkRemove>", NULL );
CLASS_DECLARATION( hhAnimatedEntity, hhDamageTester )
EVENT( EV_ResetTarget, hhDamageTester::Event_ResetTarget )
EVENT( EV_CheckRemove, hhDamageTester::Event_CheckRemove )
END_CLASS
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// hhDamageTester::Spawn
//
//==========================================================================
void hhDamageTester::Spawn(void) {
fl.takedamage = true;
gameLocal.Printf("\"Name\", \"Damage (Melee)\", \"Damage (Close)\", \"Damage (Medium)\", \"Damage (Far)\", \
\"Hits (Melee)\", \"Hits (Close)\", \"Hits (Medium)\", \"Hits (Far)\"\n");
testTime = spawnArgs.GetFloat( "testTime", "5" );
if( testTime < 1 ) {
testTime = 1;
}
// Get the distances and reset values
for( int i = 0; i < DD_MAX; i++ ) {
distance[i] = spawnArgs.GetVector( va("distance%d", i), "0 0 0" );
totalDamage[i] = 0;
hitCount[i] = 0;
}
// Get the name of the weapon (useful for outputting to an Excel-friendly format)
weaponName = spawnArgs.GetString( "weaponName", "None" );
// Store original location and axis
originalLocation = GetPhysics()->GetOrigin();
originalAxis = GetPhysics()->GetAxis();
// Initialize the target
targetIndex = -1;
PostEventMS( &EV_ResetTarget, 0 );
GetPhysics()->SetContents( CONTENTS_SOLID );
}
//==========================================================================
//
// hhDamageTester::~hhDamageTester
//
//==========================================================================
hhDamageTester::~hhDamageTester() {
int i;
gameLocal.Printf( "\nDAMAGE TEST RESULTS:\n" );
// Weapon Name
gameLocal.Printf( "\"%s\"", weaponName);
// Damage/Sec for each distance
for( i = 0; i < DD_MAX; i++ ) {
gameLocal.Printf(", %.1f", totalDamage[i] / testTime );
}
// Hits/Sec for each distance
for( i = 0; i < DD_MAX; i++ ) {
gameLocal.Printf(", %.1f", hitCount[i] / testTime );
}
gameLocal.Printf("\n\n");
}
//==========================================================================
//
// hhDamageTester::Damage
//
//==========================================================================
void hhDamageTester::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
if( targetIndex < 0 || targetIndex >= DD_MAX ) {
return;
}
if( bUndamaged ) { // First time this object has been hit
bUndamaged = false;
PostEventSec( &EV_ResetTarget, testTime );
}
// Obtain damage
const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false );
if ( !damageDef ) {
gameLocal.Warning( "hhDamageTester::Damage: Unknown damageDef '%s'", damageDefName );
return;
}
int damage = damageDef->dict.GetInt( "damage", "0" );
totalDamage[targetIndex] += damage * damageScale;
hitCount[targetIndex]++;
return;
}
//==========================================================================
//
// hhDamageTester::Event_ResetTarget
//
//==========================================================================
void hhDamageTester::Event_ResetTarget( void ) {
// Reset the target to undamaged
bUndamaged = true;
health = 999999; // Ensure that it will never die
targetIndex++;
if( targetIndex >= DD_MAX ) {
PostEventMS( &EV_Remove, 0 );
return;
}
// Set the location of the target
GetPhysics()->SetOrigin( originalLocation + distance[targetIndex] );
UpdateVisuals();
PostEventSec( &EV_CheckRemove, testTime ); // Checks to autoremove the entity after a certain time
}
//==========================================================================
//
// hhDamageTester::Event_CheckRemove
//
// Resets the target if a certain period of inactivity has occured (only if
// the weapon was unable to hit the target, for instance the wrench)
// Only happens if the target has not been hit
//==========================================================================
void hhDamageTester::Event_CheckRemove( void ) {
if( bUndamaged && targetIndex > 0 ) {
PostEventMS( &EV_ResetTarget, 0 );
}
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+39
View File
@@ -0,0 +1,39 @@
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
#ifndef __GAME_DAMAGETESTER_H__
#define __GAME_DAMAGETESTER_H__
typedef enum hhDamageDist_s {
DD_MELEE,
DD_CLOSE,
DD_MEDIUM,
DD_FAR,
DD_MAX
} hhDamageDist_t;
class hhDamageTester : public hhAnimatedEntity {
public:
CLASS_PROTOTYPE( hhDamageTester );
~hhDamageTester();
void Spawn(void);
void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
void Event_ResetTarget( void );
void Event_CheckRemove( void );
protected:
bool bUndamaged;
int targetIndex;
float testTime;
idVec3 originalLocation;
idMat3 originalAxis;
int totalDamage[DD_MAX];
int hitCount[DD_MAX];
idVec3 distance[DD_MAX]; // Relatives distances for each target location (relative to axis)
const char *weaponName;
};
#endif /* __GAME_DAMAGETESTER_H__ */
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+681
View File
@@ -0,0 +1,681 @@
/*
===============================================================================
game_dda.cpp
This contains the generic functionality for the dynamic difficulty adjustment system,
as well as a statistic tracking system.
===============================================================================
*/
// HEADER FILES ---------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//=============================================================================
//
// hhDDAManager::hhDDAManager
//
//=============================================================================
hhDDAManager::hhDDAManager() {
bForcedDifficulty = false;
difficulty = 0.5f;
ClearTracking();
}
//=============================================================================
//
// hhDDAManager::~hhDDAManager
//
//=============================================================================
hhDDAManager::~hhDDAManager() {
ClearTracking();
}
//=============================================================================
//
// hhDDAManager::Save
//
//=============================================================================
void hhDDAManager::Save(idSaveGame *savefile) const {
int i;
int num;
savefile->WriteBool( bForcedDifficulty );
savefile->WriteFloat( difficulty );
for ( i = 0; i < DDA_INDEX_MAX; i++ ) {
ddaProbability[i].Save( savefile );
}
savefile->WriteStringList( locationNameData );
savefile->WriteStringList( locationData );
num = healthData.Num();
savefile->WriteInt( num );
for( i = 0; i < num; i++ ) {
savefile->WriteInt( healthData[i] );
}
savefile->WriteStringList( healthSpiritData );
savefile->WriteStringList( ammoData );
savefile->WriteStringList( miscData );
num = deathData.Num();
savefile->WriteInt( num );
for( i = 0; i < num; i++ ) {
savefile->WriteString( deathData[i].location );
savefile->WriteInt( deathData[i].time );
}
}
//=============================================================================
//
// hhDDAManager::Restore
//
//=============================================================================
void hhDDAManager::Restore( idRestoreGame *savefile ) {
int i;
int num;
savefile->ReadBool( bForcedDifficulty );
savefile->ReadFloat( difficulty );
for ( i = 0; i < DDA_INDEX_MAX; i++ ) {
ddaProbability[i].Restore( savefile );
}
savefile->ReadStringList( locationNameData );
savefile->ReadStringList( locationData );
savefile->ReadInt( num );
healthData.SetNum( num );
for( i = 0; i < num; i++ ) {
savefile->ReadInt( healthData[i] );
}
savefile->ReadStringList( healthSpiritData );
savefile->ReadStringList( ammoData );
savefile->ReadStringList( miscData );
savefile->ReadInt( num );
deathData.SetNum( num );
for( i = 0; i < num; i++ ) {
savefile->ReadString( deathData[i].location );
savefile->ReadInt( deathData[i].time );
}
}
//=============================================================================
//
// hhDDAManager::ClearTracking
//
//=============================================================================
void hhDDAManager::ClearTracking() {
locationNameData.Clear();
locationData.Clear();
healthData.Clear();
healthSpiritData.Clear();
ammoData.Clear();
miscData.Clear();
}
//=============================================================================
//
// hhDDAManager::GetDifficulty
//
//=============================================================================
float hhDDAManager::GetDifficulty() {
return difficulty;
}
//=============================================================================
//
// hhDDAManager::RecalculateDifficulty
//
// Recalculate overall difficulty from the individual creature difficulties
//
// Must be done after any damaged are added or deaths are recorded
//=============================================================================
void hhDDAManager::RecalculateDifficulty( int updateFlags ) {
int count = 0;
float accum = 0.0f;
float oldDiff = difficulty; // TEMP
if ( g_wicked.GetBool() ) { // Wicked mode, force the DDA to 1.0
difficulty = 1.0f;
return;
}
if ( !g_useDDA.GetBool() ) { // Skip the DDA calculations. After g_wicked so that is still a valid mode
difficulty = 0.5f;
return;
}
if ( bForcedDifficulty ) { // Don't recalculate if the difficulty is forced
return;
}
for( int i = 0; i < DDA_INDEX_MAX; i++ ) {
if ( !ddaProbability[i].IsUsed() ) { // Ignore any difficulties that shouldn't count yet
continue;
}
if ( (1 << i ) & updateFlags ) { // Only update the difficulty based upon the creatures currently attacking
accum += ddaProbability[i].GetIndividualDifficulty();
count++;
}
}
if ( count == 0 ) { // No individual difficulties, so return the default
difficulty = 0.5f;
} else {
difficulty = accum / count;
}
if ( count && g_printDDA.GetBool() ) {
common->Printf( "difficulty: [%.2f]\n", difficulty );
}
}
//=============================================================================
//
// hhDDAManager::ForceDifficulty
//
// Force the difficulty to a given value -- used for debugging and Wicked mode
//=============================================================================
void hhDDAManager::ForceDifficulty( float newDifficulty ) {
if ( newDifficulty > 1.0f ) { // Clamp the max
newDifficulty = 1.0f;
} else if ( newDifficulty < 0.0f ) { // negative difficulty values resets the difficulty
common->Printf( "Difficulty defaulted back to 0.5\n" );
difficulty = 0.5f;
bForcedDifficulty = false;
return;
}
bForcedDifficulty = true;
difficulty = newDifficulty;
}
//=============================================================================
//
// hhDDAManager::DDA_Heartbeat
//
//=============================================================================
void hhDDAManager::DDA_Heartbeat( hhPlayer* player ) {
idStr locText;
assert( player );
if ( !g_trackDDA.GetBool() || gameLocal.isMultiplayer ) {
return;
}
ammo_t ammoType;
ammoType = idWeapon::GetAmmoNumForName( "ammo_rifle" );
int ammo_rifle_count = player->inventory.ammo[ ammoType ];
float ammo_rifle = 100 * player->inventory.AmmoPercentage( player, ammoType );
ammoType = idWeapon::GetAmmoNumForName( "ammo_sniper" );
int ammo_sniper_count = player->inventory.ammo[ ammoType ];
float ammo_sniper = 100 * player->inventory.AmmoPercentage( player, ammoType );
ammoType = idWeapon::GetAmmoNumForName( "ammo_crawler" );
int ammo_crawler_count = player->inventory.ammo[ ammoType ];
float ammo_crawler = 100 * player->inventory.AmmoPercentage( player, ammoType );
ammoType = idWeapon::GetAmmoNumForName( "ammo_autocannon" );
int ammo_autocannon_count = player->inventory.ammo[ ammoType ];
float ammo_autocannon = 100 * player->inventory.AmmoPercentage( player, ammoType );
ammoType = idWeapon::GetAmmoNumForName( "ammo_autocannon_grenade" );
int ammo_autocannon_grenade_count = player->inventory.ammo[ ammoType ];
float ammo_autocannon_grenade = 100 * player->inventory.AmmoPercentage( player, ammoType );
ammoType = idWeapon::GetAmmoNumForName( "ammo_acid" );
int ammo_acid_count = player->inventory.ammo[ ammoType ];
float ammo_acid = 100 * player->inventory.AmmoPercentage( player, ammoType );
ammoType = idWeapon::GetAmmoNumForName( "ammo_crawler_red" );
int ammo_crawler_red_count = player->inventory.ammo[ ammoType ];
float ammo_crawler_red = 100 * player->inventory.AmmoPercentage( player, ammoType );
ammoType = idWeapon::GetAmmoNumForName( "ammo_energy" );
int ammo_energy_count = player->inventory.ammo[ ammoType ];
float ammo_energy = 100 * player->inventory.AmmoPercentage( player, ammoType );
idStr location;
player->GetLocationText( location );
idVec3 playerOrigin = player->GetOrigin();
bool bTalonAttack = false;
if ( player->talon.IsValid() && player->talon->IsAttacking() ) {
bTalonAttack = true;
}
locationNameData.Append( location );
locationData.Append( idStr( va( "%.2f %.2f %.2f", playerOrigin.x, playerOrigin.y, playerOrigin.z ) ) );
healthData.Append( player->GetHealth() );
healthSpiritData.Append( idStr( va( "%d, %d, %.2f, %.2f", player->GetHealth(), player->GetSpiritPower(), player->ddaProbabilityAccum, gameLocal.GetDDA()->GetDifficulty() ) ) );
ammoData.Append( idStr( va( "%d, %d, %d, %d, %d, %d, %d, %d, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f",
ammo_rifle_count, ammo_sniper_count, ammo_crawler_count, ammo_autocannon_count, ammo_autocannon_grenade_count, ammo_acid_count, ammo_crawler_red_count, ammo_energy_count, // Ammo totals
ammo_rifle, ammo_sniper, ammo_crawler, ammo_autocannon, ammo_autocannon_grenade, ammo_acid, ammo_crawler_red, ammo_energy ) ) ); // Ammo percentages
miscData.Append( idStr( va( "%d, %d, %d, %d, %d", player->GetCurrentWeapon(), player->IsLighterOn(), bTalonAttack, player->IsSpiritOrDeathwalking(), player->ddaNumEnemies ) ) ); // currentWeapon, lighter, talon attacking, IsSpiritOrDeathwalking, numEnemies
}
//=============================================================================
//
// hhDDAManager::DDA_AddDamage
//
//=============================================================================
void hhDDAManager::DDA_AddDamage( int ddaIndex, int damage ) {
if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) {
common->Warning( "hhDDAManager::DDA_AddDamage: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX );
ddaIndex = 0;
}
ddaProbability[ddaIndex].AddDamage( damage * 2 );
}
//=============================================================================
//
// hhDDAManager::DDA_AddSurvivalHealth
//
//=============================================================================
void hhDDAManager::DDA_AddSurvivalHealth( int ddaIndex, int health ) {
if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) {
common->Warning( "hhDDAManager::DDA_AddSurvivalHealth: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX );
ddaIndex = 0;
}
ddaProbability[ddaIndex].AddSurvivalValue( health );
// Adjust DDA test
float prob = DDA_GetProbability( ddaIndex, health );
float probAdjust = ( prob - 0.5f ) * 0.05f; // Difficulty adjustment raises and lowers based upon how far the probability is from the dda level
ddaProbability[ddaIndex].AdjustDifficulty( probAdjust );
}
//=============================================================================
//
// hhDDAManager::DDA_AddDeath
//
//=============================================================================
void hhDDAManager::DDA_AddDeath( hhPlayer *player, idEntity *attacker ) {
hhMonsterAI *monster;
int ddaIndex;
if ( g_trackDDA.GetBool() && !gameLocal.isMultiplayer ) {
ddaDeath_t death;
death.time = gameLocal.GetTime();
player->GetLocationText( death.location );
deathData.Append( death );
}
if ( !attacker->IsType( hhMonsterAI::Type ) ) {
return;
}
monster = static_cast<hhMonsterAI *>(attacker);
if ( monster ) {
ddaIndex = monster->spawnArgs.GetInt( "ddaIndex", "0" );
if ( ddaIndex < 0 ) { // Player could be killed by monsters that shouldn't be included in the DDA
return;
}
if ( ddaIndex >= DDA_INDEX_MAX ) {
ddaIndex = 0;
}
}
ddaProbability[ddaIndex].AdjustDifficulty( -0.075f );
}
//=============================================================================
//
// hhDDAManager::DDA_GetProbability
//
// Return the probability that this index of creature will do enough damage to reduce the player's health to zero
//=============================================================================
float hhDDAManager::DDA_GetProbability( int ddaIndex, int value ) {
if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) {
common->Warning( "hhDDAManager::DDA_GetProbability: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX );
ddaIndex = 0;
}
return ddaProbability[ddaIndex].GetProbability( value );
}
//=============================================================================
//
// hhDDAManager::Export
//
//=============================================================================
void hhDDAManager::Export( const char* filename ) {
idStr ExportFile, ExportBase;
if( !filename ) {
ExportBase = gameLocal.GetMapName();
ExportBase.StripPath();
ExportBase.StripFileExtension();
ExportBase = va("%s_%s", cvarSystem->GetCVarString("win_username"), ExportBase.c_str() );
ExportBase.DefaultPath( "statfiles/" );
}
else {
ExportBase = va( "%s_", filename );
ExportBase.StripFileExtension();
ExportBase.DefaultPath( "statfiles/" );
}
gameLocal.Printf( "Exporting stats to csv files\n" );
// Export to .cvs files and to a .lin file
ExportDDAData( ExportBase, "Location Name, Health, Spirit Power, Survival Chance, DDA", "_HEALTH", healthSpiritData );
ExportDDAData( ExportBase,
"Location Name, Ammo Rifle, Ammo Sniper, Ammo Crawler, Ammo Autocannon, Ammo Grenade, Ammo Acid, Ammo Launcher, Ammo Energy, Ammo Rifle %, Ammo Sniper %, Ammo Crawler %, Ammo Autocannon %, Ammo Grenade %, Ammo Acid %, Ammo Launcher %, Ammo Energy %",
"_AMMO", ammoData );
ExportDDAData( ExportBase, "Location Name, Current Weapon, Lighter On, Talon Attacking, IsSpiritOrDeathwalking, Num Enemies", "_MISC", miscData );
ExportLINData( ExportBase );
}
//=============================================================================
//
// hhDDAManager::ExportDDAData
//
//=============================================================================
void hhDDAManager::ExportDDAData( idStr fileName, const char* header, const char *fileAddition, idList<idStr> data ) {
idFile* f = NULL;
//Build the filename
fileName.Append( fileAddition );
fileName.DefaultFileExtension("csv");
f = fileSystem->OpenFileWrite( fileName.c_str() );
if( !f ) {
common->Warning( "Failed to open stat tracking file '%s'", fileName.c_str() );
return;
}
//Export the header for this file...
f->Printf( "%s\n", header );
for( int i = 0; i < data.Num(); i++ ) {
f->Printf( "%s, %s\n", locationNameData[i].c_str(), data[i].c_str() );
}
//Close the output file
fileSystem->CloseFile( f );
}
//=============================================================================
//
// hhDDAManager::ExportLINData
//
//=============================================================================
void hhDDAManager::ExportLINData( idStr fileName ) {
idFile* f = NULL;
//Build the filename
fileName.DefaultFileExtension("lin");
f = fileSystem->OpenFileWrite( fileName.c_str() );
if( !f ) {
common->Warning( "Failed to open stat tracking file '%s'", fileName.c_str() );
return;
}
for( int i = 0; i < locationData.Num(); i++ ) {
if ( healthData[i] > 0 ) { // Only store living health values in the .lin file
f->Printf( "%s\n", locationData[i].c_str() );
}
}
//Close the output file
fileSystem->CloseFile( f );
}
//=============================================================================
//
// hhDDAManager::PrintDDA
//
//=============================================================================
void hhDDAManager::PrintDDA( void ) {
int i;
hhDDAProbability *prob;
for( i = 0; i < DDA_INDEX_MAX; i++ ) {
prob = &ddaProbability[i];
if ( !prob->IsUsed() ) {
continue;
}
common->Printf( "DDA Index: %d\n", i );
common->Printf( "Ind. Difficulty: %.2f\n", prob->GetIndividualDifficulty() );
/*
common->Printf( "Last Damages: [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f]\n",
prob->damages[0], prob->damages[1], prob->damages[2], prob->damages[3],
prob->damages[4], prob->damages[5], prob->damages[6], prob->damages[7] );
*/
}
}
//=============================================================================
// NEW DDA
//=============================================================================
//=============================================================================
//
// hhDDAProbability::hhDDAProbability
//
//=============================================================================
hhDDAProbability::hhDDAProbability() {
damageRover = 0;
survivalRover = 0;
mean = 25.0f;
stdDeviation = 1.0f;
bUsed = false;
individualDifficulty = 0.5f;
for( int i = 0; i < NUM_DAMAGES; i++ ) {
damages[i] = mean; // Initialize to a low, but reasonable value
survivalValues[i] = 0.5f; // Initialize to a middle value
}
}
//=============================================================================
//
// hhDDAProbability::Save
//
//=============================================================================
void hhDDAProbability::Save( idSaveGame *savefile ) const {
int i;
savefile->WriteBool( bUsed );
for ( i = 0; i < NUM_DAMAGES; i++ ) {
savefile->WriteInt( damages[i] );
}
savefile->WriteInt( damageRover );
for ( i = 0; i < NUM_DAMAGES; i++ ) {
savefile->WriteFloat( survivalValues[i] );
}
savefile->WriteInt( survivalRover );
savefile->WriteFloat( mean );
savefile->WriteFloat( stdDeviation );
savefile->WriteFloat( individualDifficulty );
}
//=============================================================================
//
// hhDDAProbability::Restore
//
//=============================================================================
void hhDDAProbability::Restore( idRestoreGame *savefile ) {
int i;
savefile->ReadBool( bUsed );
for ( i = 0; i < NUM_DAMAGES; i++ ) {
savefile->ReadInt( damages[i] );
}
savefile->ReadInt( damageRover );
for ( i = 0; i < NUM_DAMAGES; i++ ) {
savefile->ReadFloat( survivalValues[i] );
}
savefile->ReadInt( survivalRover );
savefile->ReadFloat( mean );
savefile->ReadFloat( stdDeviation );
savefile->ReadFloat( individualDifficulty );
}
//=============================================================================
//
// hhDDAProbability::AddDamage
//
//=============================================================================
void hhDDAProbability::AddDamage( int damage ) {
// Add the damage to the list
damages[ damageRover ] = damage;
damageRover = ( damageRover + 1 ) % NUM_DAMAGES;
bUsed = true;
CalculateProbabilityCurve();
}
//=============================================================================
//
// hhDDAProbability::AddSurvivalValue
//
//=============================================================================
void hhDDAProbability::AddSurvivalValue( int playerHealth ) {
// Get the survival probability against this creature and store it
survivalValues[ survivalRover ] = GetProbability( playerHealth );
survivalRover = ( survivalRover + 1 ) % NUM_DAMAGES;
}
//=============================================================================
//
// hhDDAProbability::GetProbability
//
//=============================================================================
float hhDDAProbability::GetProbability( int value ) {
float z;
float x;
float probability = 0;
if ( stdDeviation <= 0.0f ) {
stdDeviation = 1.0f;
}
z = idMath::Fabs( value - mean ) / stdDeviation;
x = 1 + z * ( 0.049867347 + z * ( 0.0211410061 + z * ( 0.0032776263 )));
probability = ( idMath::Exp( idMath::Log( x ) * -16 ) / 2 );
if ( value >= mean ) {
probability = 1.0f - probability;
}
return probability;
}
//=============================================================================
//
// hhDDAProbability::CalculateProbabilityCurve
//
//=============================================================================
void hhDDAProbability::CalculateProbabilityCurve() {
int i;
float diff;
float sumDiffSqr = 0;
// Recompute mean and std deviation when new damage information is obtained
mean = 0;
for ( i = 0; i < NUM_DAMAGES; i++ ) {
mean += damages[i];
}
mean /= NUM_DAMAGES;
// calculate standard deviation
for ( i = 0; i < NUM_DAMAGES; i++ ) {
diff = mean - damages[i];
sumDiffSqr += diff * diff;
}
stdDeviation = idMath::Sqrt( sumDiffSqr / NUM_DAMAGES );
}
//=============================================================================
//
// hhDDAProbability::GetSurvivalMean
//
//=============================================================================
float hhDDAProbability::GetSurvivalMean( void ) {
float sum = 0;
for ( int i = 0; i < NUM_DAMAGES; i++ ) {
sum += survivalValues[i];
}
return sum / NUM_DAMAGES;
}
//=============================================================================
//
// hhDDAProbability::AdjustDifficulty
//
//=============================================================================
void hhDDAProbability::AdjustDifficulty( float diff ) {
individualDifficulty = idMath::ClampFloat( 0.0f, 1.0f, individualDifficulty + diff );
}
+128
View File
@@ -0,0 +1,128 @@
/*
===============================================================================
game_dda.h
This contains the functionality for a dynamic difficulty adjustment system,
as well as a statistic tracking system.
===============================================================================
*/
#ifndef __GAME_DDA_H__
#define __GAME_DDA_H__
// DDA_Export Methods.
enum hhDDAExport {
DDA_ExportTEXT,
DDA_ExportCSV,
};
typedef struct ddaDeath_s {
int time;
idStr location;
} ddaDeath_t;
const int DDA_INDEX_MAX = 16;
//======================================================================================
//======================================================================================
//
// NEW DDA SYSTEM
//
//======================================================================================
//======================================================================================
#define NUM_DAMAGES 8
class hhDDAProbability {
public:
hhDDAProbability();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void AddDamage( int damage );
void AddSurvivalValue( int playerHealth );
float GetProbability( int value );
float GetSurvivalMean( void );
void AdjustDifficulty( float diff );
float GetIndividualDifficulty( void ) { return individualDifficulty; }
bool IsUsed( void ) { return bUsed; }
protected:
void CalculateProbabilityCurve();
bool bUsed; // True if this probability instance is used (so the damage probability of later creatures doesn't influence earlier levels)
int damages[NUM_DAMAGES];
int damageRover;
float survivalValues[NUM_DAMAGES];
int survivalRover;
float mean;
float stdDeviation;
float individualDifficulty;
};
/***********************************************************************
hhDDAManager.
Class that controls the DDA tracking and difficulty of the game.
***********************************************************************/
class hhDDAManager {
public:
hhDDAManager();
~hhDDAManager();
public:
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
//Public difficulty functions/accessors..
float GetDifficulty(); //Called to get current difficulty rating (0.0 - 1.0)
void DifficultySummary();
void ForceDifficulty( float newDifficulty ); // Force the difficulty to this value
void RecalculateDifficulty( int updateFlags );
//Public DDA Notifications. (these are called to update the DDA system about activities happening in the game)
void DDA_Heartbeat( hhPlayer* player );
void DDA_AddDamage( int ddaIndex, int damage );
void DDA_AddSurvivalHealth( int ddaIndex, int health );
void DDA_AddDeath( hhPlayer *player, idEntity *attacker );
float DDA_GetProbability( int ddaIndex, int value );
//Public DDA Exporting functions. These are called to export DDA stats to either the console or a file.
void Export( const char* filename );
void PrintDDA( void );
//Public DDA Controls. These are used to clear/reset the DDA system (on map-switching).
void ClearTracking();
protected:
//Protected Internal functions for exporting purposes.
void ExportDDAData( idStr fileName, const char* header, const char *fileAddition, idList<idStr> data );
void ExportLINData( idStr FileName );
//Protected Interal Difficulty 'Ticker'. Difficulty system will re-evaluate whether to change diff here.
//o void DifficultyUpdate();
//Protected Internal Difficulty data.
bool bForcedDifficulty; // Used for testing, and Wicked mode
float difficulty; // Our current difficulty rating.
hhDDAProbability ddaProbability[DDA_INDEX_MAX]; // CJR: array of probabilites used in the DDA system
idList<idStr> locationNameData;
idList<idStr> locationData;
idList<int> healthData;
idList<idStr> healthSpiritData;
idList<idStr> ammoData;
idList<idStr> miscData;
idList<ddaDeath_t> deathData;
};
#endif // __GAME_DDA_H__
+559
View File
@@ -0,0 +1,559 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
#define DEBUGGING_WRAITHS 0
//=============================================================================
// hhDeathWraith
//=============================================================================
CLASS_DECLARATION( hhWraith, hhDeathWraith )
EVENT( AI_FindEnemy, hhWraith::Event_FindEnemy )
END_CLASS
//=============================================================================
//
// hhDeatWraith::Spawn
//
//=============================================================================
void hhDeathWraith::Spawn() {
// Each wraith is assigned a random distance, rotation speed, and height around the enemy
idVec3 minCircleInfo = spawnArgs.GetVector( "min_circleInfo", "100 10 100" ); // dist, speed, height
idVec3 maxCircleInfo = spawnArgs.GetVector( "max_circleInfo", "100 10 100" ); // dist, speed, height
circleDist = minCircleInfo[0] + gameLocal.random.RandomFloat() * ( maxCircleInfo[0] - minCircleInfo[0] );
circleSpeed = minCircleInfo[1] + gameLocal.random.RandomFloat() * ( maxCircleInfo[1] - minCircleInfo[1] );
circleHeight = minCircleInfo[2] + gameLocal.random.RandomFloat() * ( maxCircleInfo[2] - minCircleInfo[2] );
circleClockwise = (gameLocal.random.RandomFloat() > 0.5f ? true : false);
// Compute the random attack time (in frames)
attackTimeMin = spawnArgs.GetFloat( "min_attackTime", "4" ) * 60;
attackTimeDelta = ( spawnArgs.GetFloat( "max_attackTime", "8" ) * 60 ) - attackTimeMin;
if ( state == WS_FLY ) {
// The wraith is now circling the enemy - set the attack time
countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta );
}
// Determine the wraith type
healthWraith = (gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "healthTypeChance", "0.35" ) ? true : false);
}
//=============================================================================
//
// hhDeathWraith::FlyUp
//
//=============================================================================
void hhDeathWraith::FlyUp() {
hhWraith::FlyUp();
if ( state == WS_FLY ) {
// The wraith is now circling the enemy - set the attack time
countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta );
}
}
//=============================================================================
//
// hhDeathWraith::FlyToEnemy
//
// Circle an enemy
//=============================================================================
void hhDeathWraith::FlyToEnemy() {
float distAdjust;
idVec3 origin;
float delta;
bool dir;
#if DEBUGGING_WRAITHS
gameRenderWorld->DebugLine(colorWhite, GetOrigin(), GetOrigin()+idVec3(0,0,5), 5000);
#endif
// Look for an enemy for the Wraith
// FIXME: This needs to be done often, because the player could spiritwalk after the Wraith has him as the enemy
// and we want the wraiths to try to target the player's proxy.
// We could either have this check via a heartbeat, or have some type of broadcast message when the player spiritwalks (?)
Event_FindEnemy( false );
// Ensure that the wraith is on the correct path, and if not, then set the wraith's velocity and angle to return to the path
origin = GetOrigin();
idVec3 toEnemy = enemy->GetOrigin() - origin;
toEnemy.z = 0;
float distance = toEnemy.Normalize();
idVec3 tangent( -toEnemy.y, toEnemy.x, 0 ); // Quick tangent, swap x and y
// If counter-clockwise, then reverse the tangent vector
if ( !circleClockwise ) {
tangent *= -1;
}
// Determine if the radius is too large and gradually pull the Wraith in closer or if the wraith is too close, push it out
if ( distance > circleDist + circleSpeed ) { // Move closer
distAdjust = circleSpeed * 0.1f;
} else if ( distance < circleDist - circleSpeed ) { // Move farther away
distAdjust = -circleSpeed * 0.1f;
} else { // No adjustment
distAdjust = 0;
}
// Decide if the wraith should change z-height
idVec3 zVelocity( 0, 0, 0 );
if ( origin.z < enemy->GetOrigin().z + circleHeight ) {
zVelocity.z = 2.0f;
} else if ( origin.z > enemy->GetOrigin().z + circleHeight + 30.0f ) {
zVelocity.z = -2.0f;
}
// Move the wraith through the world
idVec3 velocity = tangent * circleSpeed + toEnemy * distAdjust + zVelocity;
// Rotate towards velocity direction
dir = GetFacePosAngle( GetOrigin() + velocity, delta );
if ( delta > this->turn_threshold ) { // Wraith should turn
if ( dir ) { // Turn to the left
deltaViewAngles.yaw = this->turn_radius_max * (60.0f * USERCMD_ONE_OVER_HZ);
} else {
deltaViewAngles.yaw = -this->turn_radius_max * (60.0f * USERCMD_ONE_OVER_HZ);
}
}
SetOrigin( GetOrigin() + (velocity * (60.0f * USERCMD_ONE_OVER_HZ)) );
// Decide if the wraith has waited long enough and then attack
if ( --countDownTimer <= 0 ) {
if ( health > 0 && enemy.IsValid() ) { // Only attack if the wraith has an enemy (which it should at all times) and the wraith hasn't been killed
EnterAttackState();
} else { // Reset the attack time
countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta );
}
}
}
//=============================================================================
//
// hhDeathWraith::Damage
//
//=============================================================================
void hhDeathWraith::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
// Skip wraiths brighten when damaged logic
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
//=============================================================================
//
// hhDeathWraith::Killed
//
// When a deathwraith is killed, it immediately is removed
// The queue is reset and a new wraith will be spawned shortly after
//=============================================================================
void hhDeathWraith::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
hhFxInfo fxInfo;
// Stop flyloop and chatter sounds, if any
StopSound( SND_CHANNEL_BODY );
StopSound( SND_CHANNEL_VOICE );
SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) );
SetShaderParm( SHADERPARM_DIVERSITY, 1.0f );
const char *deathEffect = spawnArgs.GetString("fx_deatheffect1");
if (attacker && attacker->IsType(hhPlayer::Type)) {
hhPlayer *player = static_cast<hhPlayer *>( attacker );
if ( healthWraith ) {
deathEffect = spawnArgs.GetString("fx_deatheffect2");
}
}
fxInfo.RemoveWhenDone( true );
BroadcastFxInfoAlongBone( deathEffect, spawnArgs.GetString( "joint_collision" ), &fxInfo );
physicsObj.SetContents(0);
fl.takedamage = false;
UnlinkCombat();
StartSound( "snd_death", SND_CHANNEL_VOICE );
NotifyDeath( inflictor, attacker );
PostEventSec( &EV_Remove, spawnArgs.GetFloat( "burnOutTime" ) );
}
//=============================================================================
//
// hhDeathWraith::NotifyDeath
//
// Handles notification of player on death
//=============================================================================
void hhDeathWraith::NotifyDeath( idEntity *inflictor, idEntity *attacker ) {
if ( attacker && attacker->IsType( hhPlayer::Type ) ) {
hhPlayer *player = static_cast<hhPlayer *>( attacker );
SpawnEnergy(player);
if ( player->IsDeathWalking() ) {
player->KilledDeathWraith();
}
}
}
//=============================================================================
//
// hhDeathWraith::SpawnEnergy
//
// Energy travels from wraith to deathWalkProxy
//=============================================================================
void hhDeathWraith::SpawnEnergy(hhPlayer *player) {
idEntity *destEntity = NULL;
const char *deathEnergyName = NULL;
idVec3 origin;
idMat3 axis;
idDict args;
if ( healthWraith ) {
// Spawn health energy
deathEnergyName = spawnArgs.GetString("def_deathEnergyHealth", NULL);
}
else {
// Spawn spirit power energy
deathEnergyName = spawnArgs.GetString("def_deathEnergySpirit", NULL);
}
destEntity = player->GetDeathwalkEnergyDestination();
// Spawn the energy system that will deliver the spiritpower to the player
if (deathEnergyName && destEntity) {
idVec3 center = GetPhysics()->GetAbsBounds().GetCenter();
args.Clear();
args.Set("origin", center.ToString());
idEntity *ent = gameLocal.SpawnObject(deathEnergyName, &args);
if (ent && ent->IsType(hhDeathWraithEnergy::Type)) {
hhDeathWraithEnergy *energy = static_cast<hhDeathWraithEnergy*>(ent);
energy->SetPlayer(player);
energy->SetDestination( destEntity->GetOrigin() );
}
}
}
//=============================================================================
//
// hhDeathWraith::Think
//
//=============================================================================
void hhDeathWraith::Think( void ) {
if ( enemy.IsValid() ) {
assert( enemy->IsType( hhPlayer::Type ) );
hhPlayer *player = reinterpret_cast<hhPlayer *> ( enemy.GetEntity() );
if( !player->IsDeathWalking() ) {
BecomeInactive( TH_THINK );
PostEventMS( &EV_Remove, 0 );
return;
}
else {
if ( healthWraith ) {
SetShaderParm(SHADERPARM_MODE, 1.0f);
}
else {
SetShaderParm(SHADERPARM_MODE, 0.0f);
}
}
}
// Don't call hhWraith::Think(), since we don't want our health to drop over time
hhMonsterAI::Think();
// Since idAI is only capable of yawing, not piching, we post apply our pitch
if (state == WS_DEATH_CHARGE && enemy.IsValid()) {
idVec3 toEnemy = enemy->GetOrigin() - GetOrigin();
toEnemy.Normalize();
viewAxis = toEnemy.ToMat3();
}
#if DEBUGGING_WRAITHS
gameRenderWorld->DebugLine(colorRed, GetOrigin(), GetOrigin()+viewAxis[0]*20, 1000);
gameRenderWorld->DebugLine(colorGreen, GetOrigin(), GetOrigin()+viewAxis[1]*20, 1000);
gameRenderWorld->DebugLine(colorBlue, GetOrigin(), GetOrigin()+viewAxis[2]*20, 1000);
#endif
}
//=============================================================================
//
// hhDeathWraith::FlyMove
//
//=============================================================================
void hhDeathWraith::FlyMove( void ) {
idMat3 axis;
idVec3 delta;
idVec3 toEnemy;
// The state of the creature determines how it will move
switch ( state ) {
case WS_SPAWN:
FlyUp(); // Flying up right after spawn
break;
case WS_FLY:
FlyToEnemy();
break;
case WS_FLEE: // Flying away
FlyAway();
break;
case WS_DEATH_CHARGE: // Charging an enemy
#if 1
// Tranform animation movement by actual 'pitched' axis
if (enemy.IsValid()) {
toEnemy = enemy->GetOrigin() - GetOrigin();
toEnemy.Normalize();
axis = toEnemy.ToMat3();
} else { // HUMANHEAD mdl: Enemy might not be valid if player is just leaving deathwalk
axis = viewAxis;
}
#else
axis = viewAxis;
#endif
if (ChargeEnemy()) {
// Using animation delta to generate movement
physicsObj.UseFlyMove( false );
physicsObj.UseVelocityMove( false );
GetMoveDelta( axis, axis, delta );
physicsObj.SetDelta( delta );
physicsObj.ForceDeltaMove( true );
RunPhysics();
}
return;
case WS_STILL: // Wraith is not moving at all (playing a specific anim, etc)
return;
}
// run the physics for this frame
physicsObj.UseFlyMove( true );
physicsObj.UseVelocityMove( false );
physicsObj.SetDelta( vec3_zero );
physicsObj.ForceDeltaMove( disableGravity );
RunPhysics();
}
//=============================================================================
//
// hhDeathWraith::EnterAttackState
//
//=============================================================================
void hhDeathWraith::EnterAttackState() {
state = WS_DEATH_CHARGE;
// Set the wraith's rotation target towards the enemy
TurnTowardEnemy();
SetShaderParm( SHADERPARM_MISC, 1 ); // Charging
StartSound( "snd_charge", SND_CHANNEL_VOICE2 );
countDownTimer = PlayAnim( "alert", 2 ) / USERCMD_MSEC;
}
//=============================================================================
//
// hhDeathWraith::ChargeEnemy
//
//=============================================================================
bool hhDeathWraith::ChargeEnemy( void ) {
if ( !enemy.IsValid() ) {
ExitAttackState();
return false;
}
assert(enemy.IsValid() && enemy->IsType(hhPlayer::Type));
// Set the wraith's rotation target towards the enemy
TurnTowardEnemy();
// Attempt to attack the enemy
// If the wraith did not damage the enemy, then continue the attack flight
if ( !CheckEnemy() ) {
// Once the animation is finished (and the wraith wasn't killed or didn't hit the player),
// then transition to circle mode which will handle getting the wraith back to the path
if ( --countDownTimer <= 0 ) {
ExitAttackState();
return false;
}
}
return true;
}
//=============================================================================
//
// hhDeathWraith::ExitAttackState
//
//=============================================================================
void hhDeathWraith::ExitAttackState() {
state = WS_FLY;
PlayCycle( "fly", 2 );
countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta );
SetShaderParm( SHADERPARM_MISC, 0 ); // No longer charging
GetPhysics()->SetLinearVelocity(vec3_origin);
}
//=============================================================================
//
// hhDeathWraith::CheckEnemy
//
//=============================================================================
bool hhDeathWraith::CheckEnemy( void ) {
// Don't try to damage if the wraith doesn't have a target, or if the wraith is dead or harvested
if ( health <= 0 || !enemy.IsValid() || fl.takedamage == false ) {
return false;
}
idVec3 center = GetPhysics()->GetAbsBounds().GetCenter();
idVec3 dir = enemy->GetPhysics()->GetAbsBounds().GetCenter() - center;
if ( dir.LengthSqr() < spawnArgs.GetFloat( "minDamageDistSqr", "2500" ) ) {
HitEnemy();
return true;
}
return false;
}
//=============================================================================
//
// hhDeathWraith::HitEnemy
//
//=============================================================================
void hhDeathWraith::HitEnemy( void ) {
hhFxInfo fxInfo;
if (IsHidden()) {
return;
}
// Activate the death trigger
idEntityPtr<idEntity> deathTrigger = gameLocal.FindEntity( spawnArgs.GetString( "triggerOnHit" ) );
if ( deathTrigger.IsValid() ) {
if ( deathTrigger->RespondsTo( EV_Activate ) || deathTrigger->HasSignal( SIG_TRIGGER ) ) {
deathTrigger->Signal( SIG_TRIGGER );
deathTrigger->ProcessEvent( &EV_Activate, this );
}
}
Hide();
fl.takedamage = false;
// Hit enemy effects -- for now, using the death effects
idVec3 center = GetPhysics()->GetAbsBounds().GetCenter();
fxInfo.RemoveWhenDone( true );
BroadcastFxInfoPrefixed( "fx_hitenemy", center, mat3_identity, &fxInfo );
StartSound( "snd_attack", SND_CHANNEL_VOICE );
ApplyImpulseToEnemy();
if (enemy->IsType(hhPlayer::Type)) {
hhPlayer *player = static_cast<hhPlayer*>(enemy.GetEntity());
if ( healthWraith ) {
int playerHealth = player->GetHealth();
int minHealth = player->spawnArgs.GetInt( "minResurrectHealth", "50" );
playerHealth -= spawnArgs.GetInt( "playerHealthDamage", "10" );
if ( playerHealth < minHealth ) {
playerHealth = minHealth;
}
player->SetHealth( playerHealth );
} else {
player->DeathWalkDamagedByWraith( this, spawnArgs.GetString( "def_damagetype" ) );
}
}
PostEventMS( &EV_Remove, 2000 );
}
//=============================================================================
//
// hhDeathWraith::ApplyImpulseToEnemy
//
//=============================================================================
void hhDeathWraith::ApplyImpulseToEnemy() {
if( enemy.IsValid() ) {
idMat3 axis = GetAxis();
idVec3 impulseDir = axis[0] - axis[2];
impulseDir.Normalize();
enemy->ApplyImpulse( this, 0, enemy->GetOrigin(), impulseDir * spawnArgs.GetFloat("impulseMagnitude") * enemy->GetPhysics()->GetMass() );
}
}
//=============================================================================
//
// hhDeathWraith::EnemyDead
//
//=============================================================================
void hhDeathWraith::EnemyDead() {
return; // Death wraiths expect their enemies to be dead.
}
//=============================================================================
//
// hhDeathWraith::PlayCycle
//
//=============================================================================
void hhDeathWraith::PlayCycle( const char *name, int blendFrame ) {
GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrame) );
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim(name), gameLocal.GetTime(), FRAME2MS(blendFrame) );
}
//=============================================================================
//
// hhDeathWraith::PlayAnim
//
//=============================================================================
int hhDeathWraith::PlayAnim( const char *name, int blendFrame ) {
int fadeTime = FRAME2MS( blendFrame );
int animLength = 0;
GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrame) );
int anim = GetAnimator()->GetAnim( name );
if ( anim ) {
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.GetTime(), fadeTime );
animLength = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length();
animLength = (animLength > fadeTime) ? animLength - fadeTime : animLength;
}
return animLength;
}
//=============================================================================
//
// hhDeathWraith::Event_FindEnemy
//
//=============================================================================
void hhDeathWraith::Event_FindEnemy( int useFOV ) {
// These are meant for single player deathwalk only
idPlayer *player = gameLocal.GetLocalPlayer();
SetEnemy( player );
idThread::ReturnEntity( player );
}
//=============================================================================
//
// hhDeathWraith::TeleportIn
//
//=============================================================================
void hhDeathWraith::TeleportIn( idEntity *activator ) {
Show();
PostEventMS( &EV_Activate, 0, activator );
}
+48
View File
@@ -0,0 +1,48 @@
#ifndef __GAME_DEATHWRAITH_H__
#define __GAME_DEATHWRAITH_H__
class hhDeathWraith : public hhWraith {
public:
CLASS_PROTOTYPE( hhDeathWraith );
void Spawn();
virtual void FlyToEnemy();
virtual void SetEnemy( idActor *newEnemy ) { enemy = newEnemy; }
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void Think( void );
protected:
virtual int PlayAnim( const char *name, int blendFrame );
virtual void PlayCycle( const char *name, int blendFrame );
virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
virtual void NotifyDeath( idEntity *inflictor, idEntity *attacker );
void SpawnEnergy(hhPlayer *player);
void EnterAttackState();
void ExitAttackState();
virtual bool ChargeEnemy();
bool CheckEnemy();
void HitEnemy();
void ApplyImpulseToEnemy();
virtual void FlyUp();
virtual void FlyMove( void );
virtual void EnemyDead();
void Event_FindEnemy( int useFOV );
void TeleportIn( idEntity *activator );
protected:
// Variables for controlling the movement of the wraith around the anchor point
// These variables are unique for each wraith for variety
float circleDist; // Distance to stay from the anchor point
float circleSpeed; // Orbit speed
float circleHeight; // Height above the point
bool circleClockwise; // if true, orbit clockwise
int attackTimeMin; // Range for the attack time
int attackTimeDelta; // Range for the attack time
idMat3 chargeAxis;
bool healthWraith; // true if a health type
};
#endif
+445
View File
@@ -0,0 +1,445 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_RemoveAll( "removeAll", NULL );
CLASS_DECLARATION( idEntity, hhDebrisSpawner )
EVENT( EV_Activate, hhDebrisSpawner::Activate )
END_CLASS
/*
============
hhDebrisSpawner::hhDebrisSpawner()
============
*/
hhDebrisSpawner::hhDebrisSpawner() {
}
/*
============
hhDebrisSpawner::~hhDebrisSpawner()
============
*/
hhDebrisSpawner::~hhDebrisSpawner() {
}
/*
============
hhDebrisSpawner::Spawn()
Spawns the entity, and all other entities associated with the mass
============
*/
void hhDebrisSpawner::Spawn() {
// Set the defaults
activated = false;
sourceEntity = NULL;
hasBounds = false;
// Get the passed in
spawnArgs.GetVector( "origin", "0 0 0", origin );
spawnArgs.GetVector( "orientation", "1 0 0", orientation );
spawnArgs.GetVector( "velocity", "0 0 0", velocity );
spawnArgs.GetVector( "power", "50 50 50", power );
spawnArgs.GetFloat( "duration", "0", duration );
spawnArgs.GetBool( "multi_trigger", "0", multiActivate );
spawnArgs.GetBool( "spawnUsingEntity", "0", useEntity );
spawnArgs.GetBool( "fillBounds", "1", fillBounds );
spawnArgs.GetBool( "testBounds", "1", testBounds );
spawnArgs.GetBool( "nonsolid", "0", nonSolid );
spawnArgs.GetBool( "useAFBounds", "0", useAFBounds ); //rww
// Hide the model
GetPhysics()->SetContents( 0 );
Hide();
bounds = GetPhysics()->GetClipModel()->GetBounds();
GetPhysics()->DisableClip();
// Do stuff
power *= 20;
// spawnWhenActivated means that
if ( !useEntity && !spawnArgs.GetInt( "trigger", "0" ) ) {
Activate( NULL );
}
}
void hhDebrisSpawner::Save(idSaveGame *savefile) const {
savefile->WriteVec3(origin);
savefile->WriteVec3(orientation);
savefile->WriteVec3(velocity);
savefile->WriteVec3(power);
savefile->WriteBool(activated);
savefile->WriteBool(multiActivate);
savefile->WriteBool(hasBounds);
savefile->WriteBounds(bounds);
savefile->WriteFloat(duration);
savefile->WriteBool(fillBounds);
savefile->WriteBool(testBounds);
savefile->WriteObject(sourceEntity);
}
void hhDebrisSpawner::Restore( idRestoreGame *savefile ) {
savefile->ReadVec3(origin);
savefile->ReadVec3(orientation);
savefile->ReadVec3(velocity);
savefile->ReadVec3(power);
savefile->ReadBool(activated);
savefile->ReadBool(multiActivate);
savefile->ReadBool(hasBounds);
savefile->ReadBounds(bounds);
savefile->ReadFloat(duration);
savefile->ReadBool(fillBounds);
savefile->ReadBool(testBounds);
savefile->ReadObject( reinterpret_cast<idClass *&>(sourceEntity) );
spawnArgs.GetBool( "spawnUsingEntity", "0", useEntity );
spawnArgs.GetBool( "nonsolid", "0", nonSolid );
spawnArgs.GetBool( "useAFBounds", "0", useAFBounds );
}
/*
================
hhDebrisSpawner::Activate
Do the actual spawning of debris, FX, etc..
================
*/
void hhDebrisSpawner::Activate( idEntity *aSourceEntity ) {
if ( useEntity ) {
sourceEntity = aSourceEntity;
}
else { // If we don't have an entity, use our bounds instead
hasBounds = true;
}
if ( !activated || multiActivate ) {
SpawnDebris();
SpawnFX();
SpawnDecals();
// If we have a fixed duration, remove ourselves after they are removed
if ( ( duration > 0.0f ) && !multiActivate ) {
PostEventSec( &EV_Remove, duration + 1.0f );
}
activated = true;
}
}
/*
============
hhDebrisSpawner::SpawnDebris
============
*/
void hhDebrisSpawner::SpawnDebris() {
const idKeyValue * kv = NULL;
idEntity * debris;
idDict args;
idStr debrisEntity;
idList<idStr> debrisEntities;
int numDebris;
// For entity sources
idBounds sourceBounds;
idVec3 sourceBoundCenter;
idVec3 debrisOrigin;
idBounds searchBounds;
idVec3 debrisVelocity;
bool fit;
idBounds afBounds; //rww
bool gotAFBounds = false; //rww
idVec3 defOrigin = vec3_zero;
idAngles defAngles = ang_zero;
// Optimization: if player is far away, don't spawn the chunks
if (!gameLocal.isMultiplayer) {
float maxDistance = spawnArgs.GetFloat("max_debris_distance");
if (maxDistance > 0.0f) {
float distSquared = (gameLocal.GetLocalPlayer()->GetOrigin() - origin).LengthSqr();
if (distSquared > maxDistance*maxDistance) {
return;
}
}
}
args.SetInt( "nodrop", 1 );
args.SetVector( "origin", origin );
args.SetFloat( "duration", duration );
// Pass along variabes requested
hhUtils::PassArgs( spawnArgs, args, "pass_" );
if (useAFBounds && sourceEntity && sourceEntity->IsType(idActor::Type)) { //rww - try for AF bounds
idActor *actor = static_cast<idActor *>(sourceEntity);
if (actor->IsActiveAF()) { //they are ragdolling, so we have a chance.
idPhysics_AF *afPhys = actor->GetAFPhysics();
if (afPhys) { //got the af physics object, now loop through the bodies and collect an appropriate bounds.
afBounds.Zero();
for (int bodyCount = 0; bodyCount < afPhys->GetNumBodies(); bodyCount++) {
idAFBody *b = afPhys->GetBody(bodyCount);
if (b) {
idClipModel *bodyClip = b->GetClipModel();
if (bodyClip) { //now add the bounds of the clipModel into afBounds
idBounds bodyBounds = bodyClip->GetBounds();
idVec3 point = (b->GetWorldOrigin()-origin);
bodyBounds.AddPoint(point);
afBounds.AddBounds(bodyBounds);
gotAFBounds = true;
}
}
}
}
}
}
// Find all Debris
while ( GetNextDebrisData( debrisEntities, numDebris, kv, defOrigin, defAngles ) ) {
int numEntities = debrisEntities.Num();
for ( int count = 0; count < numDebris; ++count ) {
// Select a debris to use from the list
debrisEntity = debrisEntities[ gameLocal.random.RandomInt( numEntities ) ];
// If we have a bounding box to fill, use that
if ( fillBounds && ( sourceEntity || hasBounds ) ) {
if (gotAFBounds) { //rww
sourceBounds = afBounds;
//gameRenderWorld->DebugBounds(colorGreen, afBounds, origin, 1000);
}
else if ( sourceEntity ) {
sourceBounds = sourceEntity->GetPhysics()->GetBounds();
}
else {
sourceBounds = bounds;
}
fit = false;
for ( int i = 0; i < 4; i++ ) {
if ( i == 0 && defOrigin != vec3_zero ) {
//first try bone origin without random offset
debrisOrigin = defOrigin;
} else {
debrisOrigin = origin + hhUtils::RandomPointInBounds( sourceBounds );
}
if ( !testBounds || hhUtils::EntityDefWillFit( debrisEntity, debrisOrigin, defAngles.ToMat3(), CONTENTS_SOLID, NULL ) ) {
fit = true;
break; // Found a spot for the gib
}
}
if ( !fit ) { // Gib didn't fit after 4 attempts, so don't spawn the gib
//common->Warning( "SpawnDebris: gib didn't fit when spawned (%s)\n", debrisEntity.c_str() );
defAngles = ang_zero;
continue;
}
args.SetVector( "origin", debrisOrigin );
if ( sourceEntity ) {
idMat3 sourceAxis = idAngles( 0, sourceEntity->GetAxis().ToAngles().yaw, 0 ).ToMat3();
args.SetMatrix( "rotation", defAngles.ToMat3() * sourceAxis );
} else {
args.SetMatrix( "rotation", defAngles.ToMat3() );
}
}
if ( duration ) {
args.SetFloat( "removeTime", duration + duration * gameLocal.random.CRandomFloat() );
}
// Spawn the object
debris = gameLocal.SpawnObject( debrisEntity, &args );
HH_ASSERT(debris != NULL); // JRM - Nick, I added this because of a crash I got. Hopefully this will catch it sooner
// mdl: Added this check to make sure no AFs go through the debris spawner
if ( debris->IsType( idAFEntity_Base::Type ) ) {
gameLocal.Warning( "hhDebrisSpawner spawned an articulated entity: '%s'.", debrisEntity.c_str() );
}
if ( nonSolid ) {
idVec3 origin;
if ( debris->GetFloorPos( 64.0f, origin ) ) {
debris->SetOrigin( origin ); // Start on floor, since we're not going to be moving at all
debris->RunPhysics(); // Make sure any associated effects get updated before we turn collision off
}
// Turn off collision
debris->GetPhysics()->SetContents( 0 );
debris->GetPhysics()->SetClipMask( 0 );
debris->GetPhysics()->UnlinkClip();
debris->GetPhysics()->PutToRest();
} else {
// Add in random velocity
idVec3 randVel( gameLocal.random.RandomInt( power.x ) - power.x / 2,
gameLocal.random.RandomInt( power.y ) - power.y / 2,
gameLocal.random.RandomInt( power.z ) - power.z / 2 );
// Set linear velocity
debrisVelocity.x = velocity.x * power.x * 0.25;
debrisVelocity.y = velocity.y * power.y * 0.25;
debrisVelocity.z = 0.0f;
debris->GetPhysics()->SetLinearVelocity( debrisVelocity + randVel );
// Add random angular velocity
idVec3 aVel;
aVel.x = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat();
aVel.y = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat();
aVel.z = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat();
if ( defAngles == ang_zero ) {
debris->GetPhysics()->SetAxis( idVec3( 1, 0, 0 ).ToMat3() );
}
debris->GetPhysics()->SetAngularVelocity( aVel );
}
}
defAngles = ang_zero;
// Zero out the list
debrisEntities.Clear();
}
return;
}
/*
============
hhDebrisSpawner::SpawnFX
============
*/
void hhDebrisSpawner::SpawnFX() {
hhFxInfo fxInfo;
fxInfo.RemoveWhenDone( true );
BroadcastFxInfoPrefixed( "fx", origin, orientation.ToMat3(), &fxInfo );
}
//=============================================================================
//
// hhDebrisSpawner::SpawnDecals
//
//=============================================================================
void hhDebrisSpawner::SpawnDecals( void ) {
const int DIR_COUNT = 5;
idVec3 dir[DIR_COUNT] = {
idVec3(1.0f, 0.0f, 0.0f),
idVec3(-1.0f, 0.0f, 0.0f),
idVec3(0.0f, 0.0f, -1.0f),
idVec3(0.0f, 1.0f, 0.0f),
idVec3(0.0f, -1.0f, 0.0f)
};
// do blood splats
float size = spawnArgs.GetFloat( "decal_size", "96" );
const idKeyValue* kv = spawnArgs.MatchPrefix( "mtr_decal" );
while( kv ) {
if( kv->GetValue().Length() ) {
gameLocal.ProjectDecal( origin, dir[ gameLocal.random.RandomInt( DIR_COUNT ) ], 64.0f, true, size, kv->GetValue().c_str() );
}
kv = spawnArgs.MatchPrefix( "mtr_decal", kv );
}
}
/*
============
hhDebrisSpawner::GetNextDebrisData
============
*/
bool hhDebrisSpawner::GetNextDebrisData( idList<idStr> &entityDefs, int &count, const idKeyValue * &kv, idVec3 &origin, idAngles &angle ) {
static char debrisDef[] = "def_debris";
static char debrisKey[] = "debris";
idStr indexStr;
idStr entityDefBase;
int numDebris, minDebris, maxDebris;
origin = vec3_zero;
// Loop through looking for the next valid debris key
for ( kv = spawnArgs.MatchPrefix( debrisDef, kv );
kv && kv->GetValue().Length();
kv = spawnArgs.MatchPrefix( debrisDef, kv ) ) {
indexStr = kv->GetKey();
indexStr.Strip( debrisDef );
// Is a valid debris base And it isn't a variation. (ie, contains a .X postfix)
if ( idStr::IsNumeric( indexStr ) && indexStr.Find( '.' ) < 0 ) {
// Get Number of Debris
numDebris = -1;
if ( sourceEntity && sourceEntity->IsType( idAI::Type ) ) {
idVec3 bonePos;
idMat3 boneAxis;
idStr bone = spawnArgs.GetString( va( "%s%s%s", debrisKey, "_bone", ( const char * ) indexStr ) );
if( !bone.IsEmpty() ) {
static_cast<idAI*>(sourceEntity)->GetJointWorldTransform( bone.c_str(), bonePos, boneAxis );
origin = bonePos;
angle = spawnArgs.GetAngles( va( "%s%s%s", debrisKey, "_angle", ( const char * ) indexStr ) );
}
}
if ( !spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_num",
( const char * ) indexStr ),
"-1", numDebris ) || numDebris < 0 ) {
// No num found, look for min and max
if ( spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_min",
( const char * ) indexStr ),
"-1", minDebris ) && minDebris >= 0 &&
spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_max",
( const char * ) indexStr ),
"-1", maxDebris ) && maxDebris >= 0 ) {
numDebris =
gameLocal.random.RandomInt( ( maxDebris - minDebris ) ) + minDebris;
} //. No min/max found
} //. No num found
// No valid num found
if ( numDebris < 0 ) {
gameLocal.Warning( "ERROR: No debris num could be be found for %s%s",
( const char * ) debrisDef,
( const char * ) indexStr );
}
else { // Valid num found
const char * entityDefPrefix;
const idKeyValue * otherDefKey = NULL;
entityDefBase = kv->GetValue();
count = numDebris;
entityDefs.Append( entityDefBase );
// Find the other defs that may be there. using .X scheme
entityDefPrefix = va( "%s.", (const char *) kv->GetKey() );
// gameLocal.Printf( "Looking for %s\n", entityDefPrefix );
for ( otherDefKey = spawnArgs.MatchPrefix( entityDefPrefix, otherDefKey );
otherDefKey && otherDefKey->GetValue().Length();
otherDefKey = spawnArgs.MatchPrefix( entityDefPrefix, otherDefKey ) ) {
// gameLocal.Printf( "Would have added %s\n", (const char *) otherDefKey->GetValue() );
entityDefs.Append( otherDefKey->GetValue() );
}
return( true );
}
} //. Valid debris base
} //. debris loop
return( false );
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef __PREY_GAME_DEBRISMASS_H__
#define __PREY_GAME_DEBRISMASS_H__
class hhDebrisSpawner : public idEntity {
CLASS_PROTOTYPE( hhDebrisSpawner );
public:
hhDebrisSpawner();
virtual ~hhDebrisSpawner();
void Spawn();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Activate( idEntity *sourceEntity );
ID_INLINE float GetDuration() const { return duration; }
protected:
void Event_RemoveAll();
void SpawnDebris();
void SpawnFX();
void SpawnDecals( void );
bool GetNextDebrisData( idList<idStr> &entityDefs, int &count,
const idKeyValue * &kv, idVec3 &origin, idAngles &angle );
protected:
idVec3 origin;
idVec3 orientation;
idVec3 velocity;
idVec3 power;
bool activated;
bool multiActivate;
bool useEntity; // Use an entity to spawn in
bool useAFBounds; //rww - use collective AF bodies to produce an appropriate bounds for spawning.
bool hasBounds;
idBounds bounds;
// Vars for removing everything after a fixed period
float duration;
bool fillBounds; // Try to fill in the bounds with the gibs.
bool testBounds; // test to see if debris fits in bounds
bool nonSolid;
idEntity * sourceEntity;
};
#endif __PREY_GAME_DEBRISMASS_H__
+70
View File
@@ -0,0 +1,70 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//-----------------------------------------------------------------------
//
// hhDock
//
//-----------------------------------------------------------------------
const idEventDef EV_DockLock("lockVehicleInDock", NULL);
const idEventDef EV_DockUnlock("unlockDock", NULL);
ABSTRACT_DECLARATION(idEntity, hhDock)
EVENT( EV_DockLock, hhDock::Event_Lock)
EVENT( EV_DockUnlock, hhDock::Event_Unlock)
EVENT( EV_Activate, hhDock::Event_Activate)
EVENT( EV_PostSpawn, hhDock::Event_PostSpawn )
END_CLASS
void hhDock::Spawn() {
if ( !gameLocal.isClient ) {
PostEventMS(&EV_PostSpawn, 0);
}
}
void hhDock::Event_PostSpawn() {
dockingZone = SpawnDockingZone();
}
void hhDock::Save(idSaveGame *savefile) const {
dockingZone.Save(savefile);
}
void hhDock::Restore( idRestoreGame *savefile ) {
dockingZone.Restore(savefile);
}
hhDockingZone *hhDock::SpawnDockingZone() {
hhDockingZone *zone;
idBounds localBounds;
// Define our bounds
localBounds[0] = spawnArgs.GetVector("dockingzonemins");
localBounds[1] = spawnArgs.GetVector("dockingzonemaxs");
// create a docking zone with this size
idDict args;
args.SetVector( "origin", GetOrigin() );
args.SetVector( "mins", localBounds[0] );
args.SetVector( "maxs", localBounds[1] );
args.SetMatrix( "rotation", GetAxis() );
zone = (hhDockingZone *)gameLocal.SpawnEntityType(hhDockingZone::Type, &args);
assert(zone);
zone->Bind(this, true);
zone->RegisterDock(this);
return zone;
}
void hhDock::Event_Lock() {
Lock();
}
void hhDock::Event_Unlock() {
Unlock();
}
void hhDock::Event_Activate( idEntity *activator ) {
Unlock();
}
+49
View File
@@ -0,0 +1,49 @@
#ifndef __GAME_DOCK_H__
#define __GAME_DOCK_H__
extern const idEventDef EV_DockLock;
extern const idEventDef EV_DockUnlock;
class hhShuttle;
class hhDockingZone;
class hhDock : public idEntity {
public:
ABSTRACT_PROTOTYPE( hhDock );
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual bool ValidEntity(idEntity *ent) = 0;
virtual void EntityEntered( idEntity *ent ) = 0;
virtual void EntityEncroaching( idEntity *ent ) = 0;
virtual void EntityLeaving( idEntity *ent ) = 0;
virtual void ShuttleExit( hhShuttle *shuttle ) = 0;
virtual bool IsLocked() = 0;
virtual bool CanExitLocked() = 0;
virtual bool AllowsBoost() = 0;
virtual bool AllowsFiring() = 0;
virtual bool AllowsExit() = 0;
virtual bool IsTeleportDest() = 0;
virtual void UpdateAxis( const idMat3 &newAxis ) = 0;
virtual bool Recharges() const { return false; }
protected:
//rww - needs a delay for mp
virtual void Event_PostSpawn();
hhDockingZone* SpawnDockingZone();
virtual void Lock()=0;
virtual void Unlock()=0;
void Event_Lock();
void Event_Unlock();
void Event_Activate(idEntity *activator);
protected:
idEntityPtr<hhDockingZone> dockingZone;
};
#endif
+16
View File
@@ -0,0 +1,16 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//==========================================================================
//
// hhDockedGun
//
//==========================================================================
CLASS_DECLARATION(hhShuttle, hhDockedGun)
END_CLASS
void hhDockedGun::Spawn() {
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef __GAME_DOCKEDGUN_H__
#define __GAME_DOCKEDGUN_H__
//==========================================================================
//
// hhDockedGun
//
//==========================================================================
class hhDockedGun : public hhShuttle {
CLASS_PROTOTYPE( hhDockedGun );
public:
void Spawn();
protected:
};
#endif
+586
View File
@@ -0,0 +1,586 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
CLASS_DECLARATION( idDoor, hhDoor )
EVENT( EV_Mover_ReturnToPos1, hhDoor::Event_ReturnToPos1 )
EVENT( EV_ReachedPos, hhDoor::Event_Reached_BinaryMover )
EVENT( EV_SetBuddiesShaderParm, hhDoor::Event_SetBuddiesShaderParm )
EVENT( EV_Touch, hhDoor::Event_Touch )
END_CLASS
//--------------------------------
// hhDoor::Spawn
//--------------------------------
void hhDoor::Spawn() {
airlockMaster = NULL;
airlockTeam.SetOwner( this );
airlockTeamName = spawnArgs.GetString( "airlockTeam" );
spawnArgs.GetFloat( "airlockwait", "3", airLockSndWait );
airLockSndWait *= 1000.0f; // Convert from seconds to ms
idEntity* master = DetermineTeamMaster( GetAirLockTeamName() );
if( master ) {
airlockMaster = static_cast<hhDoor*>( master );
if( airlockMaster != this ) {
JoinAirLockTeam( airlockMaster );
}
}
if( spawnArgs.GetBool("start_open") ) {
VerifyAirlockTeamStatus();
}
if( moveMaster != this ) {
CopyTeamInfoToMoveMaster( static_cast<hhDoor*>(moveMaster) );
}
// We only open when dead if we have health to start with
openWhenDead = health > 0;
forcedOpen = false;
nextAirLockSnd = 0;
bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" );
}
void hhDoor::Save(idSaveGame *savefile) const {
savefile->WriteString(airlockTeamName);
savefile->WriteObject(airlockMaster);
savefile->WriteBool(openWhenDead);
savefile->WriteBool(forcedOpen);
savefile->WriteFloat(airLockSndWait);
}
void hhDoor::Restore( idRestoreGame *savefile ) {
savefile->ReadString(airlockTeamName);
savefile->ReadObject( reinterpret_cast<idClass *&>(airlockMaster) );
savefile->ReadBool(openWhenDead);
savefile->ReadBool(forcedOpen);
savefile->ReadFloat(airLockSndWait);
airlockTeam.SetOwner( this );
if( airlockMaster && airlockMaster != this ) {
JoinAirLockTeam( airlockMaster );
}
nextAirLockSnd = 0;
bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" );
}
//--------------------------------
// hhDoor::~hhDoor
//--------------------------------
hhDoor::~hhDoor() {
airlockTeam.Remove();
}
//--------------------------------
// hhDoor::Killed
//--------------------------------
void hhDoor::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
// Only can really be killed if we can be damaged
if ( openWhenDead ) {
Open();
fl.takedamage = false;
forcedOpen = true;
}
} // Killed( idEntity *, idEntity *, int, const idVec3 &, int )
//--------------------------------
// hhDoor::Use_BinaryMover
//--------------------------------
void hhDoor::Use_BinaryMover( idEntity *activator ) {
if ( airlockMaster && activator && !activator->IsType( idPlayer::Type ) ) {
return; // Don't allow anyone but the player to affect airlock doors
}
// only the master should be used
if ( moveMaster != this ) {
moveMaster->Use_BinaryMover( activator );
return;
}
if ( !enabled ) {
return;
}
if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) {
GotoPosition2();
}
else if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 ) {
GotoPosition1();
}
}
//--------------------------------
// hhDoor::GotoPosition1
//
// CloseDoor
//--------------------------------
void hhDoor::GotoPosition1() {
GotoPosition1( spawnArgs.GetBool("toggle") ? 0.0f : wait );
}
//--------------------------------
// hhDoor::GotoPosition1
//
// CloseDoor
//--------------------------------
void hhDoor::GotoPosition1( float wait ) {
idMover_Binary* slave = NULL;
int partial = 0;
//HUMANHEAD: aob - airlock stuff
if( !CanClose() ) {
return;
}
//HUMNAHEAD END
if ( moverState == MOVER_POS2 ) {
SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] );
CancelReturnToPos1();
if( wait > 0 ) {
//HUMANHEAD: aob
PostEventSec( &EV_Mover_ReturnToPos1, wait );
//HUMANHEAD END
} else {
ProcessEvent( &EV_Mover_ReturnToPos1 );
}
}
else
// only partway up before reversing
if ( moverState == MOVER_1TO2 ) {
// use the physics times because this might be executed during the physics simulation
partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime();
MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial );
}
}
//--------------------------------
// hhDoor::GotoPosition2
//
// OpenDoor
//--------------------------------
void hhDoor::GotoPosition2() {
GotoPosition2( 0.0f );
}
//--------------------------------
// hhDoor::GotoPosition2
//
// OpenDoor
//--------------------------------
void hhDoor::GotoPosition2( float wait ) {
int partial = 0;
hhDoor *airlockMaster = GetAirLockMaster();
//HUMANHEAD: aob - airlock stuff
if( !CanOpen() && airlockMaster ) {
ForceAirLockTeamClosed();
if( gameLocal.time > nextAirLockSnd ) {
int length;
StartSound( "snd_airlock", SND_CHANNEL_BODY, 0, false, &length );
nextAirLockSnd = gameLocal.time + length + airLockSndWait;
}
return;
}
//HUMNAHEAD END
if ( moverState == MOVER_POS1 ) {
ActivatePrefixed("triggerStartOpen", GetActivator());
SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] );
MatchActivateTeam( MOVER_1TO2, gameLocal.time );
// open areaportal
ProcessEvent( &EV_Mover_OpenPortal );
}
else if ( moverState == MOVER_2TO1 ) { // only partway up before reversing
// use the physics times because this might be executed during the physics simulation
partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime();
MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial );
}
if( airlockMaster ) {
// Mark the other team members as locked
for( hhDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
// Skip this door and it's buddies
if( node == this || buddyNames.Find( node->name ) ) {
continue;
}
// Don't change doors that are really locked
if( node->IsLocked() ) {
continue;
}
// Mark as locked
node->SetBuddiesShaderParm( SHADERPARM_MISC, 1.0f );
}
// This prevents the our shader from being set as locked from the player moving too quickly between airlock doors
SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f );
}
}
//--------------------------------
// hhDoor::Open
//--------------------------------
void hhDoor::Open( void ) {
GotoPosition2( wait );
}
//--------------------------------
// hhDoor::Close
//--------------------------------
void hhDoor::Close( void ) {
// HUMANHEAD nla
if ( ForcedOpen() ) { return; }
// HUMANHEAD
GotoPosition1( 0.0f );
}
//--------------------------------
// hhDoor::CanOpen
//--------------------------------
bool hhDoor::CanOpen() const {
if( !GetAirLockMaster() ) {
return true;
}
for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node == this ) {
continue;
}
if( OnMyMoveTeam(node) ) {
continue;
}
if( node->IsOpen() ) {
return false;
}
}
return true;
}
//--------------------------------
// hhDoor::CanClose
//--------------------------------
bool hhDoor::CanClose() const {
return true;
}
//--------------------------------
// hhDoor::OnMyMoveTeam
//--------------------------------
bool hhDoor::OnMyMoveTeam( hhDoor* doorPiece ) const {
for( const idMover_Binary* localDoorPiece = this; localDoorPiece != NULL; localDoorPiece = localDoorPiece->GetActivateChain() ) {
if( localDoorPiece == doorPiece ) {
return true;
}
}
return false;
}
//--------------------------------
// hhDoor::CopyTeamInfoToMoveMaster
//--------------------------------
void hhDoor::CopyTeamInfoToMoveMaster( hhDoor* master ) {
if( !master ) {
return;
}
for( int ix = buddies.Num() - 1; ix >= 0; --ix ) {
master->buddies.AddUnique( buddies[ix] );
}
for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) {
master->buddyNames.AddUnique( buddyNames[ix] );
}
}
//--------------------------------
// hhDoor::DetermineTeamMaster
//--------------------------------
idEntity* hhDoor::DetermineTeamMaster( const char* teamName ) {
idEntity* ent = NULL;
if ( teamName && teamName[0] ) {
// find the first entity spawned on this team (which could be us)
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if (ent->IsType(hhModelDoor::Type) && !idStr::Icmp( static_cast<hhModelDoor *>(ent)->GetAirLockTeamName(), teamName )) {
return ent;
}
if (ent->IsType(hhDoor::Type) && !idStr::Icmp( static_cast<hhDoor *>(ent)->GetAirLockTeamName(), teamName )) {
return ent;
}
}
}
return NULL;
}
//--------------------------------
// hhDoor::JoinAirLockTeam
//--------------------------------
void hhDoor::JoinAirLockTeam( hhDoor *master ) {
assert( master );
airlockTeam.AddToEnd( master->airlockTeam );
}
//--------------------------------
// hhDoor::VerifyAirlockTeamStatus
//--------------------------------
void hhDoor::VerifyAirlockTeamStatus() {
if( !GetAirLockMaster() ) {
return;
}
for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node == this ) {
continue;
}
if( OnMyMoveTeam(node) ) {
continue;
}
if( !node->IsClosed() ) {
gameLocal.Warning( "Airlock team '%s' has more than one member starting open", GetAirLockTeamName() );
}
}
}
//--------------------------------
// hhDoor::IsClosed
//--------------------------------
bool hhDoor::IsClosed() const {
return ( moverState == MOVER_POS1 );
}
//--------------------------------
// hhDoor::ForceAirLockTeamClosed
//--------------------------------
void hhDoor::ForceAirLockTeamClosed() {
hhDoor* localMoveMaster = NULL;
if( !GetAirLockMaster() ) {
return;
}
for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node == this ) {
continue;
}
if( !node->moveMaster->IsType(hhDoor::Type) ) {
continue;
}
localMoveMaster = static_cast<hhDoor*>( node->moveMaster );
//if( node != localMoveMaster ) {
// continue;
//}
localMoveMaster->CancelReturnToPos1();
if( !localMoveMaster->IsClosed() ) {
localMoveMaster->Close();
}
}
}
//--------------------------------
// hhDoor::ForceAirLockTeamOpen
//--------------------------------
void hhDoor::ForceAirLockTeamOpen() {
hhDoor* localMoveMaster = NULL;
if( !GetAirLockMaster() ) {
return;
}
for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node == this ) {
continue;
}
if( !node->moveMaster->IsType(hhDoor::Type) ) {
continue;
}
localMoveMaster = static_cast<hhDoor*>( node->moveMaster );
//if( node != localMoveMaster ) {
// continue;
//}
localMoveMaster->CancelReturnToPos1();
if( !localMoveMaster->IsOpen() ) {
localMoveMaster->Open();
}
}
}
//--------------------------------
// hhDoor::CancelReturnToPos1
//--------------------------------
void hhDoor::CancelReturnToPos1() {
for( idMover_Binary* slave = this; slave != NULL; slave = slave->GetActivateChain() ) {
slave->CancelEvents( &EV_Mover_ReturnToPos1 );
}
}
//--------------------------------
// hhDoor::SetBuddiesShaderParm
//--------------------------------
void hhDoor::SetBuddiesShaderParm( int parm, float value ) {
idEntity* buddy = NULL;
for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) {
if( !buddyNames[ix].Length() ) {
continue;
}
buddy = gameLocal.FindEntity( buddyNames[ix].c_str() );
if( !buddy ) {
continue;
}
buddy->SetShaderParm( parm, value );
}
}
//--------------------------------
// hhDoor::ToggleBuddiesShaderParm
//--------------------------------
void hhDoor::ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay) {
SetBuddiesShaderParm( parm, firstValue );
PostEventMS( &EV_SetBuddiesShaderParm, toggleDelay, parm, secondValue );
}
//--------------------------------
// hhDoor::Event_SetBuddiesShaderParm
//--------------------------------
void hhDoor::Event_SetBuddiesShaderParm( int parm, float value ) {
SetBuddiesShaderParm( parm, value );
}
//--------------------------------
// hhDoor::Event_ReturnToPos1
//--------------------------------
void hhDoor::Event_ReturnToPos1( void ) {
if ( !ForcedOpen() ) {
idDoor::Event_ReturnToPos1();
}
} // Event_ReturnToPos1()
//--------------------------------
// hhDoor::Event_Reached_BinaryMover
//--------------------------------
void hhDoor::Event_Reached_BinaryMover( void ) {
if ( moverState == MOVER_2TO1 ) {
SetBlocked(false);
ActivatePrefixed("triggerClosed", this);
} else if (moverState == MOVER_1TO2) {
ActivatePrefixed("triggerOpened", this);
}
if ( moverState == MOVER_1TO2 ) {
// reached pos2
idThread::ObjectMoveDone( move_thread, this );
move_thread = 0;
// HUMANHEAD aob - removed
//if ( moveMaster == this ) {
//StartSound( "snd_opened", SND_CHANNEL_ANY );
//}
//HUMANHEAD END
SetMoverState( MOVER_POS2, gameLocal.time );
SetGuiStates( guiBinaryMoverStates[MOVER_POS2] );
UpdateBuddies(1);
if( enabled && wait >= 0 && !spawnArgs.GetBool("toggle") ) {
// return to pos1 after a delay
//HUMANHEAD: aob
CancelReturnToPos1();
//HUMANHEAD END
PostEventSec( &EV_Mover_ReturnToPos1, wait );
}
// fire targets
ActivateTargets( moveMaster->GetActivator() );
} else if ( moverState == MOVER_2TO1 ) {
// reached pos1
idThread::ObjectMoveDone( move_thread, this );
move_thread = 0;
SetMoverState( MOVER_POS1, gameLocal.time );
SetGuiStates( guiBinaryMoverStates[MOVER_POS1] );
UpdateBuddies(0);
// close areaportals
if ( moveMaster == this ) {
ProcessEvent( &EV_Mover_ClosePortal );
}
if( GetAirLockMaster() ) {
// Mark the other team members as unlocked
for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
// Skip this door and it's buddies
if( node == this || buddyNames.Find( node->name ) ) {
continue;
}
// Don't change doors that are really locked
if( node->IsLocked() ) {
continue;
}
// Mark as unlocked when door closes
node->SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f );
}
}
} else {
gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" );
}
}
/*
================
hhDoor::Event_Touch
================
*/
void hhDoor::Event_Touch( idEntity *other, trace_t *trace ) {
if ( bShuttleDoors && ( ! other->IsType( idActor::Type ) || ! reinterpret_cast<idActor *> ( other )->InVehicle() ) ) {
if ( gameLocal.time > nextSndTriggerTime ) {
StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL );
nextSndTriggerTime = gameLocal.time + 10000;
}
return;
}
idDoor::Event_Touch( other, trace );
}
+69
View File
@@ -0,0 +1,69 @@
#ifndef __PREY_DOOR_H__
#define __PREY_DOOR_H__
class hhDoor : public idDoor {
CLASS_PROTOTYPE( hhDoor );
public:
void Spawn();
virtual ~hhDoor();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
virtual void Use_BinaryMover( idEntity *activator );
virtual void GotoPosition1();
virtual void GotoPosition2();
//HUMANHEAD: aob
virtual void GotoPosition1( float wait );
virtual void GotoPosition2( float wait );
//HUMANHEAD END
ID_INLINE const char* GetAirLockTeamName() const { return airlockTeamName.c_str(); }
ID_INLINE hhDoor* GetAirLockMaster() const { return airlockMaster; }
void Open();
void Close();
bool CanOpen() const;
bool CanClose() const;
bool IsClosed() const;
bool OnMyMoveTeam( hhDoor* doorPiece ) const;
void CopyTeamInfoToMoveMaster( hhDoor* master );
void CancelReturnToPos1();
void SetBuddiesShaderParm( int parm, float value );
void ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay );
bool ForcedOpen() const { return( forcedOpen ); };
void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
protected:
idEntity* DetermineTeamMaster( const char* teamName );
void JoinAirLockTeam( hhDoor *master );
void VerifyAirlockTeamStatus();
void ForceAirLockTeamClosed();
void ForceAirLockTeamOpen();
protected:
void Event_ReturnToPos1( void ); // overridden to stop it moving when closed
void Event_SetBuddiesShaderParm( int parm, float value );
void Event_Reached_BinaryMover( void );
virtual void Event_Touch( idEntity *other, trace_t *trace );
public:
idLinkList<hhDoor> airlockTeam;
protected://More airLock stuff
idStr airlockTeamName;
hhDoor* airlockMaster;
bool openWhenDead; // Do we open when dead?
bool forcedOpen; // Is the door forced open
float airLockSndWait;
float nextAirLockSnd;
bool bShuttleDoors;
};
#endif
+281
View File
@@ -0,0 +1,281 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//==========================================================================
//
// hhEggSpawner
//
// When activated, shoots out an egg along it's X axis
//==========================================================================
#define MAX_HATCH_TIME (SEC2MS(10))
CLASS_DECLARATION(hhAnimatedEntity, hhEggSpawner)
EVENT( EV_PlayIdle, hhEggSpawner::Event_PlayIdle)
EVENT( EV_Activate, hhEggSpawner::Event_Activate)
END_CLASS
void hhEggSpawner::Spawn(void) {
GetPhysics()->SetContents( CONTENTS_SOLID );
fl.takedamage = true;
idleAnim = GetAnimator()->GetAnim("idle");
hatchAnim = GetAnimator()->GetAnim("launch");
painAnim = GetAnimator()->GetAnim("pain");
PostEventMS(&EV_PlayIdle, 0);
}
void hhEggSpawner::Save(idSaveGame *savefile) const {
savefile->WriteInt( idleAnim );
savefile->WriteInt( hatchAnim );
savefile->WriteInt( painAnim );
}
void hhEggSpawner::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( idleAnim );
savefile->ReadInt( hatchAnim );
savefile->ReadInt( painAnim );
}
void hhEggSpawner::SpawnEgg(idEntity *activator) {
idVec3 dir = GetPhysics()->GetAxis()[0];
idVec3 offset = spawnArgs.GetVector("offset_spawn");
float power = spawnArgs.GetFloat("power");
// Play anim
if (hatchAnim) {
GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, hatchAnim, gameLocal.time, 0);
int ms = GetAnimator()->GetAnim( hatchAnim )->Length();
PostEventMS(&EV_PlayIdle, ms);
}
idDict args;
args.Clear();
args.SetVector( "origin", GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis() );
hhEgg *egg = static_cast<hhEgg*>(gameLocal.SpawnObject(spawnArgs.GetString("def_egg"), &args));
if (egg) {
egg->GetPhysics()->SetLinearVelocity(dir * power);
egg->SetActivator(activator);
// Copy our targets
for ( int i = 0; i < targets.Num(); i++ ) {
egg->targets.AddUnique(targets[i]);
}
}
StartSound( "snd_spawn", SND_CHANNEL_ANY, 0, true, NULL );
}
void hhEggSpawner::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
// Don't actually take damage, but give feedback
if (painAnim) {
GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 0);
int ms = GetAnimator()->GetAnim( painAnim )->Length();
PostEventMS(&EV_PlayIdle, ms);
}
}
void hhEggSpawner::Event_PlayIdle() {
if (idleAnim) {
GetAnimator()->ClearAllAnims(gameLocal.time, 200);
GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 200);
}
}
void hhEggSpawner::Event_Activate(idEntity *activator) {
SpawnEgg(activator);
}
//==========================================================================
//
// hhEgg
//
// Moveable that spawns a creature on impact
//==========================================================================
const idEventDef EV_Hatch("<hatch>");
CLASS_DECLARATION(hhMoveable, hhEgg)
EVENT( EV_Activate, hhEgg::Event_Activate)
EVENT( EV_Hatch, hhEgg::Event_Hatch)
END_CLASS
void hhEgg::Spawn(void) {
enemy = NULL;
bHatched = false;
bHatching = false;
const char *tableName = spawnArgs.GetString("table_hatch");
table = static_cast<const idDeclTable *>(declManager->FindType( DECL_TABLE, tableName, true ));
hatchTime = -1.0f;
PostEventSec( &EV_Activate, spawnArgs.GetFloat("secondsBeforeHatch"), this );
}
void hhEgg::Save(idSaveGame *savefile) const {
savefile->WriteBool( bHatching );
savefile->WriteBool( bHatched );
savefile->WriteFloat( deformAlpha.GetStartTime() ); // idInterpolate<float>
savefile->WriteFloat( deformAlpha.GetDuration() );
savefile->WriteFloat( deformAlpha.GetStartValue() );
savefile->WriteFloat( deformAlpha.GetEndValue() );
savefile->WriteFloat( hatchTime );
enemy.Save(savefile);
}
void hhEgg::Restore( idRestoreGame *savefile ) {
float set;
savefile->ReadBool( bHatching );
savefile->ReadBool( bHatched );
savefile->ReadFloat( set ); // idInterpolate<float>
deformAlpha.SetStartTime( set );
savefile->ReadFloat( set );
deformAlpha.SetDuration( set );
savefile->ReadFloat( set );
deformAlpha.SetStartValue(set);
savefile->ReadFloat( set );
deformAlpha.SetEndValue( set );
savefile->ReadFloat( hatchTime );
const char *tableName = spawnArgs.GetString("table_hatch");
table = static_cast<const idDeclTable *>(declManager->FindType( DECL_TABLE, tableName, true ));
enemy.Restore(savefile);
}
void hhEgg::SetActivator(idEntity *activator) {
if (activator && activator->IsType(idActor::Type)) {
enemy = static_cast<idActor*>(activator);
}
}
void hhEgg::Ticker() {
if (bHatching) {
// over time, send different parms into deformation
if (deformAlpha.IsDone(gameLocal.time)) {
Hatch();
}
else {
float alpha = deformAlpha.GetCurrentValue(gameLocal.time);
float value = table->TableLookup(alpha);
SetShaderParm(SHADERPARM_ANY_DEFORM, DEFORMTYPE_VIBRATE);
SetShaderParm(SHADERPARM_ANY_DEFORM_PARM1, value);
}
}
}
bool hhEgg::Collide( const trace_t &collision, const idVec3 &velocity ) {
AttemptToPlayBounceSound( collision, velocity );
if (spawnArgs.GetBool("spawnOnImpact")) { //obs
Hatch();
return true;
}
return false;
}
void hhEgg::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
Hatch();
}
bool hhEgg::SpawnHatchling(const char *monsterName, const idVec3 &spawnLocation) {
idDict args;
args.Clear();
args.SetVector( "origin", spawnLocation );
args.SetFloat( "angle", GetAxis().ToAngles().yaw );
args.Set( "trigger_anim", "birth" );
hhMonsterAI *monster = static_cast<hhMonsterAI*>(gameLocal.SpawnObject(monsterName, &args));
if (monster) {
// Set enemy
if (enemy.IsValid()) {
monster->SetEnemy(enemy.GetEntity());
}
// Copy our targets
for ( int i = 0; i < targets.Num(); i++ ) {
monster->targets.AddUnique(targets[i]);
}
monster->PostEventSec( &EV_Activate, 0, this );
}
return monster != NULL;
}
void hhEgg::Hatch() {
if (!bHatched) {
if (hatchTime == -1.0f) {
hatchTime = gameLocal.time;
}
// Spawn hatchling
const char *monsterName = spawnArgs.GetString("def_hatchling", NULL);
if (monsterName) {
idVec3 spawnLocation = GetPhysics()->GetOrigin();
const float hatchOffsetDistance = 50.0f;
bool spawned = false;
idVec3 offset;
idVec3 offsetLocation;
trace_t trace;
memset(&trace, 0, sizeof(trace_t));
if (hhUtils::EntityDefWillFit(monsterName, spawnLocation, mat3_identity, MASK_MONSTERSOLID, this)) {
//gameRenderWorld->DebugArrow(colorGreen, spawnLocation, offsetLocation, 10, 10000);
spawned = SpawnHatchling(monsterName, spawnLocation);
}
if (!spawned) { // Creature wouldn't fit, try again later
CancelEvents(&EV_Hatch);
if (gameLocal.time - hatchTime > MAX_HATCH_TIME) {
// Give up after 15 seconds of struggling
ActivateTargets(this);
// Spawn gibs from monster
const idDict *dict = gameLocal.FindEntityDefDict(monsterName, false);
if (dict && dict->FindKey("def_gibdebrisspawner")) {
GetPhysics()->SetLinearVelocity( vec3_zero ); //HUMANHEAD jsh PCF zero out velocity to prevent debris huge translation
hhUtils::SpawnDebrisMass(dict->GetString("def_gibdebrisspawner"), this);
}
} else {
//idVec3 dir = hhUtils::RandomVector();
//gameRenderWorld->DebugArrow(colorGreen, spawnLocation, spawnLocation + dir * 50.0f, 10, 250);
GetPhysics()->AddForce(0, GetPhysics()->GetOrigin(), hhUtils::RandomVector() * idMath::ClampFloat(0x1000000, 0x100000000, (0x1000000 * ((gameLocal.time - hatchTime) / 25.0f))));
PostEventMS(&EV_Hatch, 250);
return;
}
}
}
hatchTime = -1.0f;
bHatched = true;
GetPhysics()->SetContents(0);
PostEventMS(&EV_Remove, 0);
StartSound( "snd_hatch", SND_CHANNEL_ANY, 0, true, NULL );
GetPhysics()->SetLinearVelocity( vec3_zero ); //HUMANHEAD jsh PCF zero out velocity to prevent debris huge translation
hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_afterbirth"), this);
}
}
void hhEgg::Event_Activate(idEntity *activator) {
if (!bHatching) {
bHatching = true;
int msForTable = SEC2MS(spawnArgs.GetFloat("secondsToHatch"));
deformAlpha.Init(gameLocal.time, msForTable, 0.0f, 1.0f);
BecomeActive(TH_TICKER);
}
}
void hhEgg::Event_Hatch(void) {
Hatch();
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef __GAME_EGGSPAWNER_H__
#define __GAME_EGGSPAWNER_H__
class hhEggSpawner : public hhAnimatedEntity {
CLASS_PROTOTYPE( hhEggSpawner );
public:
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void SpawnEgg(idEntity *activator);
virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
protected:
void Event_Activate(idEntity *activator);
void Event_PlayIdle();
protected:
int idleAnim;
int hatchAnim;
int painAnim;
};
class hhEgg : public hhMoveable {
CLASS_PROTOTYPE( hhEgg );
public:
virtual bool Collide( const trace_t &collision, const idVec3 &velocity );
virtual void Ticker();
virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location);
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Hatch();
void SetActivator(idEntity *activator);
protected:
bool SpawnHatchling(const char *monsterName, const idVec3 &spawnLocation);
void Event_Activate(idEntity *activator);
void Event_Hatch();
protected:
bool bHatching;
bool bHatched;
idInterpolate<float> deformAlpha;
const idDeclTable * table;
idEntityPtr<idActor> enemy;
float hatchTime;
};
#endif
+126
View File
@@ -0,0 +1,126 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_EnableNode( "enableNode", NULL );
const idEventDef EV_DisableNode( "disableNode", NULL );
CLASS_DECLARATION( idStaticEntity, hhEnergyNode )
EVENT( EV_EnableNode, hhEnergyNode::Event_Enable )
EVENT( EV_DisableNode, hhEnergyNode::Event_Disable )
END_CLASS
hhEnergyNode::hhEnergyNode(void)
:disabled(false)
{}
hhEnergyNode::~hhEnergyNode(void) {
Event_Disable();
}
void hhEnergyNode::Save(idSaveGame *savefile) const {
savefile->WriteBool( disabled );
savefile->WriteVec3( leechPoint );
energyFx.Save( savefile );
}
void hhEnergyNode::Restore( idRestoreGame *savefile ) {
savefile->ReadBool( disabled );
savefile->ReadVec3( leechPoint );
energyFx.Restore( savefile );
}
void hhEnergyNode::Spawn( void ) {
fl.networkSync = true;
GetPhysics()->SetContents( CONTENTS_SOLID );
leechPoint = GetAxis()*spawnArgs.GetVector("leechPoint")+GetOrigin();
fl.clientEvents = true;
PostEventMS(&EV_EnableNode, 1);
}
void hhEnergyNode::LeechTrigger(idEntity *activator, const char* type) {
idStr name = spawnArgs.GetString(type);
if ( name != "" ) {
idEntity* ent = gameLocal.FindEntity(name);
if ( ent ) {
if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) {
ent->Signal( SIG_TRIGGER );
ent->ProcessEvent( &EV_Activate, activator );
}
ent->TriggerGuis();
}
}
}
void hhEnergyNode::Finish() {
Event_Disable();
if ( spawnArgs.GetInt("infinite","0") || gameLocal.isMultiplayer ) { // delay before reenabling -cjr
PostEventSec( &EV_EnableNode, spawnArgs.GetFloat( "reenableDelay", "20" ) );
}
}
void hhEnergyNode::Event_Enable() {
disabled = false;
const idDict *energyDef = NULL;
const char* str = spawnArgs.GetString( "def_energy" );
if ( str && str[0] ) {
energyDef = gameLocal.FindEntityDefDict( str );
}
if ( energyDef ) {
hhFxInfo fxInfo;
if (IsBound() || spawnArgs.GetBool("force_bind")) {
fxInfo.SetEntity( this ); // Only bind if we will be moving
}
energyFx = SpawnFxLocal( energyDef->GetString("fx_node"), leechPoint, GetAxis(), &fxInfo, true );
idVec3 color = energyDef->GetVector("nodeColor");
SetShaderParm( SHADERPARM_RED, color.x );
SetShaderParm( SHADERPARM_GREEN, color.y );
SetShaderParm( SHADERPARM_BLUE, color.z );
StartSound( "snd_activate", SND_CHANNEL_ANY );
StartSound( "snd_idle", SND_CHANNEL_BODY );
}
}
void hhEnergyNode::Event_Disable() {
disabled = true;
if( energyFx.IsValid() ) {
SAFE_REMOVE(energyFx);
}
SetShaderParm( SHADERPARM_RED, 0 );
SetShaderParm( SHADERPARM_GREEN, 0 );
SetShaderParm( SHADERPARM_BLUE, 0 );
StopSound( SND_CHANNEL_BODY );
}
void hhEnergyNode::WriteToSnapshot( idBitMsgDelta &msg ) const {
idStaticEntity::WriteToSnapshot(msg);
msg.WriteBits(disabled, 1);
}
void hhEnergyNode::ReadFromSnapshot( const idBitMsgDelta &msg ) {
idStaticEntity::ReadFromSnapshot(msg);
bool snapDisabled = !!msg.ReadBits(1);
if (snapDisabled != disabled) {
if (snapDisabled) {
Event_Disable();
}
else {
Event_Enable();
}
}
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef __GAME_ENERGYNODE_H__
#define __GAME_ENERGYNODE_H__
class hhEnergyNode : public idStaticEntity {
public:
CLASS_PROTOTYPE( hhEnergyNode );
hhEnergyNode(void);
~hhEnergyNode();
void Spawn( void );
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
inline bool CanLeech() { return !disabled; }
void LeechTrigger(idEntity *activator, const char* type);
void Finish();
virtual void Event_Enable();
virtual void Event_Disable();
//rww - network code
virtual void WriteToSnapshot( idBitMsgDelta &msg ) const;
virtual void ReadFromSnapshot( const idBitMsgDelta &msg );
idVec3 leechPoint;
protected:
bool disabled;
idEntityPtr<hhEntityFx> energyFx;
};
#endif // __GAME_ENERGYNODE_H__
+637
View File
@@ -0,0 +1,637 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
CLASS_DECLARATION( idEntityFx, hhEntityFx )
EVENT( EV_Activate, hhEntityFx::Event_Trigger )
EVENT( EV_Fx_KillFx, hhEntityFx::Event_ClearFx )
END_CLASS
/*
================
hhEntityFx::hhEntityFx
================
*/
hhEntityFx::hhEntityFx() {
// HUMANHEAD nla
setFxInfo = false;
// HUMANHEAD
// HUMANHEAD bg
restartActive = false;
// HUMANHEAD END
}
/*
================
hhEntityFx::~hhEntityFx
================
*/
hhEntityFx::~hhEntityFx() {
//HUMANHEAD: aob - need to shut everything down
Event_ClearFx();
//HUMANHEAD END
}
void hhEntityFx::Save(idSaveGame *savefile) const {
savefile->WriteStaticObject( fxInfo );
savefile->WriteBool( setFxInfo );
savefile->WriteBool( removeWhenDone );
// HUMANHEAD bg
savefile->WriteBool( restartActive );
// HUMANHEAD END
}
void hhEntityFx::Restore( idRestoreGame *savefile ) {
savefile->ReadStaticObject( fxInfo );
savefile->ReadBool( setFxInfo );
savefile->ReadBool( removeWhenDone );
// HUMANHEAD bg
savefile->ReadBool( restartActive );
// HUMANHEAD END
}
void hhEntityFx::WriteToSnapshot( idBitMsgDelta &msg ) const {
idEntityFx::WriteToSnapshot(msg);
fxInfo.WriteToSnapshot(msg);
}
void hhEntityFx::ReadFromSnapshot( const idBitMsgDelta &msg ) {
idEntityFx::ReadFromSnapshot(msg);
fxInfo.ReadFromSnapshot(msg);
}
/*
================
hhEntityFx::CleanUpSingleAction
================
*/
void hhEntityFx::CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ) {
idEntityFx::CleanUpSingleAction( fxaction, laction );
// HUMANHEAD pdm: sound "duration" support
if ( fxaction.type == FX_SOUND && fxaction.sibling == -1 && laction.soundStarted ) {
const idSoundShader *def = declManager->FindSound( fxaction.data );
refSound.referenceSound->StopSound( SND_CHANNEL_ANY );
}
if ( fxaction.type == FX_PARTICLE && fxaction.sibling == -1 ) {
laction.particleSystem = NULL;
laction.particleStartTime = -1;
}
// HUMANHEAD END
}
/*
================
hhEntityFx::Hide
================
*/
void hhEntityFx::Hide() {
CleanUp();
}
/*
================
hhEntityFx::Run
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
================
*/
void hhEntityFx::Run( int time ) {
int ieff, j;
idEntity *ent = NULL;
const idDict *projectileDef = NULL;
idProjectile *projectile = NULL;
if ( !fxEffect || IsHidden() ) {
return;
}
for( ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) {
const idFXSingleAction& fxaction = fxEffect->events[ieff];
idFXLocalAction& laction = actions[ieff];
//
// if we're currently done with this one
//
if ( laction.start == -1 ) {
continue;
}
//
// see if it's delayed
//
if ( laction.delay ) {
if ( laction.start + (time - laction.start) < laction.start + (laction.delay * 1000) ) {
continue;
}
}
//
// each event can have it's own delay and restart
//
int actualStart = laction.delay ? laction.start + (int)( laction.delay * 1000 ) : laction.start;
float pct = (float)( time - actualStart ) / (1000 * fxaction.duration );
if ( pct >= 1.0f ) {
laction.start = -1;
float totalDelay = 0.0f;
if ( fxaction.restart ) {
if ( fxaction.random1 || fxaction.random2 ) {
totalDelay = fxaction.random1 + gameLocal.random.RandomFloat() * (fxaction.random2 - fxaction.random1);
} else {
totalDelay = fxaction.delay;
}
laction.delay = totalDelay;
laction.start = time;
}
continue;
}
if ( fxaction.fire.Length() ) {
for( j = 0; j < fxEffect->events.Num(); j++ ) {
if ( fxEffect->events[j].name.Icmp( fxaction.fire ) == 0 ) {
actions[j].delay = 0;
}
}
}
idFXLocalAction *useAction = NULL;
if ( fxaction.sibling == -1 ) {
useAction = &laction;
} else {
useAction = &actions[fxaction.sibling];
}
assert( useAction );
switch( fxaction.type ) {
case FX_ATTACHLIGHT:
case FX_LIGHT: {
if ( useAction->lightDefHandle == -1 ) {
if ( fxaction.type == FX_LIGHT ) {
memset( &useAction->renderLight, 0, sizeof( renderLight_t ) );
//HUMANHEAD: bjk once again fixing aob code
useAction->renderLight.axis = DetermineAxis( fxaction );
useAction->renderLight.origin = GetOrigin() + fxaction.offset * useAction->renderLight.axis;
useAction->renderLight.axis = hhUtils::SwapXZ( useAction->renderLight.axis );
//HUMANHEAD END
useAction->renderLight.lightRadius[0] = fxaction.lightRadius;
useAction->renderLight.lightRadius[1] = fxaction.lightRadius;
useAction->renderLight.lightRadius[2] = fxaction.lightRadius;
useAction->renderLight.shader = declManager->FindMaterial( fxaction.data, false );
useAction->renderLight.shaderParms[ SHADERPARM_RED ] = fxaction.lightColor.x;
useAction->renderLight.shaderParms[ SHADERPARM_GREEN ] = fxaction.lightColor.y;
useAction->renderLight.shaderParms[ SHADERPARM_BLUE ] = fxaction.lightColor.z;
useAction->renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
useAction->renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time );
useAction->renderLight.referenceSound = refSound.referenceSound;
useAction->renderLight.pointLight = true;
if ( fxaction.noshadows ) {
useAction->renderLight.noShadows = true;
}
useAction->lightDefHandle = gameRenderWorld->AddLightDef( &useAction->renderLight );
}
if ( fxaction.noshadows ) {
for( j = 0; j < fxEffect->events.Num(); j++ ) {
idFXLocalAction& laction2 = actions[j];
if ( laction2.modelDefHandle != -1 ) {
laction2.renderEntity.noShadow = true;
}
}
}
} else if ( fxaction.trackOrigin ) {
//HUMANHEAD: bjk
useAction->renderLight.axis = DetermineAxis( fxaction );
useAction->renderLight.origin = GetOrigin() + fxaction.offset * useAction->renderLight.axis;
useAction->renderLight.axis = hhUtils::SwapXZ( useAction->renderLight.axis );
gameRenderWorld->UpdateLightDef( useAction->lightDefHandle, &useAction->renderLight );
//HUMANHEAD END
}
ApplyFade( fxaction, *useAction, time, actualStart );
break;
}
case FX_SOUND: {
if ( !useAction->soundStarted ) {
useAction->soundStarted = true;
const idSoundShader *shader = declManager->FindSound(fxaction.data);
StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL );
for( j = 0; j < fxEffect->events.Num(); j++ ) {
idFXLocalAction& laction2 = actions[j];
if ( laction2.lightDefHandle != -1 ) {
laction2.renderLight.referenceSound = refSound.referenceSound;
gameRenderWorld->UpdateLightDef( laction2.lightDefHandle, &laction2.renderLight );
}
}
}
break;
}
case FX_DECAL: {
if ( !useAction->decalDropped ) {
useAction->decalDropped = true;
// HUMANHEAD pdm: Increased depth to from 8 to 25
//TODO: Expose depth and parrallel to the FX files
gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 25.0f, true, fxaction.size, fxaction.data );
}
break;
}
case FX_SHAKE: {
if ( !useAction->shakeStarted ) {
idDict args;
args.Clear();
args.SetFloat( "kick_time", fxaction.shakeTime );
args.SetFloat( "kick_amplitude", fxaction.shakeAmplitude );
for ( j = 0; j < gameLocal.numClients; j++ ) {
idPlayer *player = gameLocal.GetClientByNum( j );
if ( player && ( player->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr() < Square( fxaction.shakeDistance ) ) {
if ( !gameLocal.isMultiplayer || !fxaction.shakeIgnoreMaster || GetBindMaster() != player ) {
player->playerView.DamageImpulse( fxaction.offset, &args );
}
}
}
if ( fxaction.shakeImpulse != 0.0f && fxaction.shakeDistance != 0.0f ) {
idEntity *ignore_ent = NULL;
if ( gameLocal.isMultiplayer ) {
ignore_ent = this;
if ( fxaction.shakeIgnoreMaster ) {
ignore_ent = GetBindMaster();
}
}
// lookup the ent we are bound to?
gameLocal.RadiusPush( GetPhysics()->GetOrigin(), fxaction.shakeDistance, fxaction.shakeImpulse, this, ignore_ent, 1.0f, true );
}
useAction->shakeStarted = true;
}
break;
}
case FX_ATTACHENTITY:
case FX_MODEL: {
if ( useAction->modelDefHandle == -1 ) {
memset( &useAction->renderEntity, 0, sizeof( renderEntity_t ) );
//HUMANHEAD: aob - rotated offset by axis
useAction->renderEntity.axis = DetermineAxis( fxaction );
useAction->renderEntity.origin = GetOrigin() + fxaction.offset * useAction->renderEntity.axis;
useAction->renderEntity.axis = hhUtils::SwapXZ( useAction->renderEntity.axis );
useAction->renderEntity.weaponDepthHack = renderEntity.weaponDepthHack;
useAction->renderEntity.onlyVisibleInSpirit = renderEntity.onlyVisibleInSpirit;
useAction->renderEntity.onlyInvisibleInSpirit = renderEntity.onlyInvisibleInSpirit; // tmj
useAction->renderEntity.allowSurfaceInViewID = renderEntity.allowSurfaceInViewID; // bjk
//HUMANHEAD END
useAction->renderEntity.hModel = renderModelManager->FindModel( fxaction.data );
// HUMANHEAD pdm: allow color on fx, but they'll be the same for all particles
if ( renderEntity.shaderParms[SHADERPARM_RED] != 0.0f ||
renderEntity.shaderParms[SHADERPARM_GREEN] != 0.0f ||
renderEntity.shaderParms[SHADERPARM_BLUE] != 0.0f) {
useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = renderEntity.shaderParms[SHADERPARM_RED];
useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = renderEntity.shaderParms[SHADERPARM_GREEN];
useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = renderEntity.shaderParms[SHADERPARM_BLUE];
}
else {
useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f;
useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
}
useAction->renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time );
useAction->renderEntity.shaderParms[3] = 1.0f;
useAction->renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.RandomFloat(); //HUMANHEAD bjk
if ( useAction->renderEntity.hModel ) {
useAction->renderEntity.bounds = useAction->renderEntity.hModel->Bounds( &useAction->renderEntity );
}
useAction->modelDefHandle = gameRenderWorld->AddEntityDef( &useAction->renderEntity );
} else if ( fxaction.trackOrigin ) {
//HUMANHEAD: aob - rotated offset by axis
useAction->renderEntity.axis = DetermineAxis( fxaction );
if (fxaction.offset.x || fxaction.offset.y || fxaction.offset.z) {
useAction->renderEntity.origin = GetOrigin() + fxaction.offset * useAction->renderEntity.axis;
}
else {
useAction->renderEntity.origin = GetOrigin();
}
useAction->renderEntity.axis = hhUtils::SwapXZ( useAction->renderEntity.axis );
gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity );
//HUMANHEAD END
}
ApplyFade( fxaction, *useAction, time, actualStart );
break;
}
case FX_PARTICLE: {
//HUMANHEAD: aob
//rww - kind of a hack, but otherwise the timing is off, which causes issues in mp.
if (useAction->particleStartTime == -2) {
useAction->particleStartTime = gameLocal.time;
}
if( !useAction->particleSystem && fxaction.data.Length() ) {
useAction->particleSystem = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, fxaction.data, false ) );
} else if( useAction->particleStartTime >= 0 && useAction->particleSystem ) {
idMat3 axis = DetermineAxis( fxaction );
if( !gameLocal.smokeParticles->EmitSmoke(useAction->particleSystem, useAction->particleStartTime, gameLocal.random.RandomFloat(), GetOrigin() + fxaction.offset * axis, hhUtils::SwapXZ(axis)) ) {
useAction->particleStartTime = -1;
}
}
// HUMANHEAD END
break;
}
case FX_LAUNCH: {
//HUMANHEAD rww - if this is a client ent then assert.
assert(!fl.clientEntity);
if ( gameLocal.isClient ) {
// client never spawns entities outside of ClientReadSnapshot
useAction->launched = true;
break;
}
if ( !useAction->launched ) {
useAction->launched = true;
projectile = NULL;
// FIXME: may need to cache this if it is slow
projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false );
if ( !projectileDef ) {
gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() );
} else {
gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
if ( ent && ent->IsType( idProjectile::Type ) ) {
projectile = ( idProjectile * )ent;
projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] );
projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin );
}
}
}
break;
}
}
}
}
/*
================
hhEntityFx::DetermineAxis
HUMANHEAD: aob
================
*/
idMat3 hhEntityFx::DetermineAxis( const idFXSingleAction& fxaction ) {
idVec3 fxDir;
if( fxaction.explicitAxis ) {
if (fxaction.useAxis == AXIS_CUSTOMLOCAL) {
// When customlocal is set, axis is the specifed axis, untransformed by the entities axis
return fxaction.dir.ToMat3();
}
return (fxaction.dir[0] * GetAxis()[0] + fxaction.dir[1] * GetAxis()[1] + fxaction.dir[2] * GetAxis()[2]).ToMat3();
} else if( fxInfo.GetAxisFor(fxaction.useAxis, fxDir) ) {
return fxDir.ToMat3();
}
return GetAxis();
}
/*
================
hhEntityFx::Nozzle
HUMANHEAD: aob - used to toggle particle systems
================
*/
void hhEntityFx::Nozzle( bool bOn ) {
if ( !fxEffect ) {
return;
}
if( bOn && !IsActive( TH_THINK ) ) {
Event_Trigger( NULL );
}
if( !bOn ) {
//Canceling events incase we are toggled before our actvate event has fired.
CancelEvents( &EV_Activate );
if( IsActive( TH_THINK ) ) {
Event_ClearFx();
}
}
}
/*
================
hhEntityFx::DormantBegin
//HUMANHEAD: aob
================
*/
void hhEntityFx::DormantBegin() {
idEntityFx::DormantBegin();
//HUMANHEAD: aob - used CleanUp directly so 'started' doesn't get reset
//Stop();
CleanUp();
//HUMANHEAD END
//want to make sure we can trigger these back on
nextTriggerTime = -1;
}
/*
================
hhEntityFx::DormantEnd
//HUMANHEAD: aob
================
*/
void hhEntityFx::DormantEnd() {
idEntityFx::DormantEnd();
if ( started >= 0 ) {
CreateFx( this );
}
}
/*
================
hhEntityFx::Event_Trigger
================
*/
void hhEntityFx::Event_Trigger( idEntity *activator ) {
if ( gameLocal.time < nextTriggerTime ) {
return;
}
// HUMANHEAD bg: Special handling for "restart" fx enable/disable.
if ( restartActive && (activator != this) ) {
CancelEvents( &EV_Fx_Action );
CancelEvents( &EV_Activate );
CancelEvents( &EV_Fx_KillFx );
Stop();
CleanUp();
BecomeInactive( TH_THINK );
restartActive = false;
return;
}
if ( !restartActive && spawnArgs.GetFloat( "restart" ) ) {
restartActive = true;
}
// HUMANHEAD END
//HUMANHEAD: aob
if( spawnArgs.GetBool("toggle") && IsActive(TH_THINK) ) {
ProcessEvent( &EV_Fx_KillFx );
return;
}
//HUMANHEAD END
//HUMANHEAD: aob - moved logic to helper function so I can call code specifically
CreateFx( activator );
//HUMANHEAD END
}
/*
================
hhEntityFx::CreateFx
HUMANHEAD: aob
================
*/
void hhEntityFx::CreateFx( idEntity *activator ) {
if ( g_skipFX.GetBool() ) {
return;
}
float fxActionDelay;
const char *fx;
if ( spawnArgs.GetString( "fx", "", &fx) ) {
Setup( fx );
Start( gameLocal.time );
//HUMANHEAD: aob - added so fx can be deleted at end of duration
if( RemoveWhenDone() ) {
PostEventMS( &EV_Remove, Duration() );
} else {
PostEventMS( &EV_Fx_KillFx, Duration() );
}
//HUMANHEAD END
BecomeActive( TH_THINK );
}
fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" );
if ( fxActionDelay != 0.0f ) {
nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay );
} else {
// prevent multiple triggers on same frame
nextTriggerTime = gameLocal.time + 1;
}
PostEventSec( &EV_Fx_Action, fxActionDelay, activator );
}
/*
================
hhEntityFx::StartFx
creates an fx entity, ONLY CALL THIS ON THE SERVER. -rww
================
*/
hhEntityFx *hhEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind )
{
if ( g_skipFX.GetBool() || !fx || !*fx ) {
return NULL;
}
idDict args;
args.SetBool( "start", true );
args.Set( "fx", fx );
hhEntityFx *nfx = static_cast<hhEntityFx *>( gameLocal.SpawnEntityType( hhEntityFx::Type, &args ) );
if ( nfx->Joint() && *nfx->Joint() ) {
nfx->BindToJoint( ent, nfx->Joint(), true );
nfx->SetOrigin( vec3_origin );
} else {
nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() );
nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() );
}
if ( bind ) {
// never bind to world spawn
if ( ent != gameLocal.world ) {
nfx->Bind( ent, true );
}
}
nfx->Show();
return nfx;
}
/*
================
hhEntityFx::SetParticleShaderParm
Allow setting shader parms directly to particle renderentities
Note that this functionality isn't set to the default SetShaderParm()
to avoid breaking any existing code
================
*/
void hhEntityFx::SetParticleShaderParm( int parmnum, float value ) {
if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) {
gameLocal.Warning( "shader parm index (%d) out of range", parmnum );
return;
}
for( int ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) {
const idFXSingleAction& fxaction = fxEffect->events[ieff];
idFXLocalAction *useAction = NULL;
idFXLocalAction& laction = actions[ieff];
if ( fxaction.sibling == -1 ) {
useAction = &laction;
} else {
useAction = &actions[fxaction.sibling];
}
if ( !useAction ) {
continue;
}
switch( fxaction.type ) {
case FX_MODEL: {
useAction->renderEntity.shaderParms[parmnum] = value;
break;
}
}
}
}
/*
================
hhEntityFx::Event_ClearFx
Clears any visual fx started when item(mob) was spawned
================
*/
void hhEntityFx::Event_ClearFx( void ) {
if ( g_skipFX.GetBool() ) {
return;
}
Stop();
CleanUp();
BecomeInactive( TH_THINK );
if ( spawnArgs.GetBool("test") ) {
PostEventMS( &EV_Activate, 0, this );
} else {
if (!spawnArgs.GetBool("triggered")) {
float rest = spawnArgs.GetInt( "restart", "0" );
if ( rest == 0.0f ) {
//HUMANHEAD: aob - added RemoveWhenDone and CancelEvents call
if( RemoveWhenDone() ) {
PostEventSec( &EV_Remove, 0.1f );
} else {
CancelEvents( &EV_Fx_Action );
}
//HUMANHEAD END
} else {
rest *= gameLocal.random.RandomFloat();
// HUMANHEAD bg: Give ability for minimum restart time by adding offset.
rest += spawnArgs.GetFloat( "restartDelay", "0" );
// HUMANHEAD END
PostEventSec( &EV_Activate, rest, this );
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
#ifndef __HH_ENTITY_FX_H
#define __HH_ENTITY_FX_H
class hhEntityFx : public idEntityFx {
CLASS_PROTOTYPE( hhEntityFx );
public:
hhEntityFx();
virtual ~hhEntityFx();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
//HUMANHEAD rww
virtual void WriteToSnapshot( idBitMsgDelta &msg ) const;
virtual void ReadFromSnapshot( const idBitMsgDelta &msg );
//HUMANHEAD END
virtual void Run( int time );
// HUMANHEAD nla
void SetUseAxis( fxAxis theAxis ) { };
void SetFxInfo( const hhFxInfo &i ) { fxInfo = i; setFxInfo = true; removeWhenDone = fxInfo.RemoveWhenDone(); GetRenderEntity()->weaponDepthHack = fxInfo.UseWeaponDepthHack(); }
bool RemoveWhenDone() { return( removeWhenDone ); }
void RemoveWhenDone( bool remove ) { removeWhenDone = remove; }
void Toggle() { Nozzle( !IsActive(TH_THINK) ); }
void Nozzle( bool bOn );
idMat3 DetermineAxis( const idFXSingleAction& fxaction );
void CreateFx( idEntity *activator );
virtual void Hide();
static hhEntityFx *StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind );
void SetParticleShaderParm( int parmnum, float value );
// HUMANHEAD END
protected:
virtual void CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction );
virtual void DormantBegin();
virtual void DormantEnd();
protected:
void Event_Trigger( idEntity *activator );
void Event_ClearFx( void );
protected:
// HUMANHEAD nla
enum fxAxis { AXIS_CURRENT, AXIS_NORMAL, AXIS_BOUNCE, AXIS_INCOMING, AXIS_CUSTOMLOCAL };
// HUMANHEAD END
// HUMANHEAD
hhFxInfo fxInfo;
bool setFxInfo;
bool removeWhenDone;
// HUMANHEAD END
// HUMANHEAD bg
bool restartActive;
// HUMANHEAD END
};
#endif
+117
View File
@@ -0,0 +1,117 @@
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_SpawnEntity("spawnEntity", NULL, 'd');
CLASS_DECLARATION( idEntity, hhEntitySpawner )
EVENT(EV_Activate, hhEntitySpawner::Event_Activate)
EVENT(EV_SpawnEntity, hhEntitySpawner::Event_SpawnEntity)
END_CLASS
//
// Spawn()
//
void hhEntitySpawner::Spawn( void ) {
maxSpawnCount = spawnArgs.GetInt("max_spawn", "-1"); // Default: Infinite
currSpawnCount = 0;
if(!spawnArgs.GetString("def_entity", "", entDefName)) {
gameLocal.Error("def_entity not specified for hhEntitySpawner");
}
entSpawnArgs.Clear();
idStr tmpStr;
idStr realKeyName;
// Copy keys for monster
const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL);
while(kv) {
tmpStr = kv->GetKey();
int usIndex = tmpStr.FindChar("ent_", '_');
realKeyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1);
entSpawnArgs.Set(realKeyName, kv->GetValue());
kv = spawnArgs.MatchPrefix("ent_", kv);
}
}
void hhEntitySpawner::Save(idSaveGame *savefile) const {
savefile->WriteString( entDefName );
savefile->WriteDict( &entSpawnArgs );
savefile->WriteInt( maxSpawnCount );
savefile->WriteInt( currSpawnCount );
}
void hhEntitySpawner::Restore( idRestoreGame *savefile ) {
savefile->ReadString( entDefName );
savefile->ReadDict( &entSpawnArgs );
savefile->ReadInt( maxSpawnCount );
savefile->ReadInt( currSpawnCount );
}
//
// Event_Activate()
//
void hhEntitySpawner::Event_Activate(idEntity *activator) {
Event_SpawnEntity();
}
//
// Event_Activate()
//
void hhEntitySpawner::Event_SpawnEntity( void ) {
idVec3 entSize;
// Can't spawn anymore
if(maxSpawnCount >= 0 && currSpawnCount >= maxSpawnCount) {
return;
}
idDict args = entSpawnArgs;;
args.SetVector( "origin", GetPhysics()->GetOrigin());
args.SetMatrix("rotation", GetAxis());
// entity collision checks for seeing if we are going to collide with another entity on spawn
const idDict *entDef = gameLocal.FindEntityDefDict( entDefName, false );
if ( !entDef ) {
if (!entDefName) { //HUMANHEAD rww
gameLocal.Error("NULL entDefName in hhEntitySpawner::Event_SpawnEntity\n");
}
else {
gameLocal.Warning( "Unknown Def '%s'", entDefName );
}
return;
}
entDef->GetVector( "size", "0", entSize );
idBounds bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( max( entSize.x, entSize.y ) );
idEntity* ents[MAX_GENTITIES];
int numModels = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, ents, MAX_GENTITIES );
for ( int i = 0; i < numModels ; i++ ) {
if( ents[i] && ents[i]->IsType(idActor::Type) ) {
idThread::ReturnInt( false );
return;
}
}
idEntity *e = gameLocal.SpawnObject(entDefName, &args);
if(!e) {
gameLocal.Error("hhEntitySpawner: Failed to spawn entity def named: %s", entDefName);
return;
}
// Copy the targets that our case has to our newly spawned entity
for( int i = 0; i < targets.Num(); i++ ) {
e->targets.AddUnique(targets[i]);
}
if(e->IsType(idAI::Type)) {
static_cast<idAI*>(e)->viewAxis = GetAxis();
}
currSpawnCount++;
idThread::ReturnInt( true );
}

Some files were not shown because too many files have changed in this diff Show More