mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-17 11:00:38 +02:00
Initial Prey Game integration support.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
};
|
||||
@@ -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
@@ -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__ */
|
||||
@@ -0,0 +1,4 @@
|
||||
EXPORTS
|
||||
dllEntry
|
||||
Maya_ConvertModel
|
||||
Maya_Shutdown
|
||||
@@ -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 *)
|
||||
*/
|
||||
@@ -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__ */
|
||||
@@ -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
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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__ */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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__ */
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
#ifndef __PREY_GAME_ANIMBLEND_H__
|
||||
#define __PREY_GAME_ANIMBLEND_H__
|
||||
|
||||
|
||||
#endif /* __PREY_GAME_ANIMBLEND_H__ */
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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__
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "precompiled.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "prey_local.h"
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// hhDockedGun
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
CLASS_DECLARATION(hhShuttle, hhDockedGun)
|
||||
END_CLASS
|
||||
|
||||
void hhDockedGun::Spawn() {
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user