mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-23 21:02:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
5404 lines
131 KiB
C++
5404 lines
131 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "idlib/geometry/JointTransform.h"
|
|
#include "idlib/LangDict.h"
|
|
#include "framework/async/NetworkSystem.h"
|
|
#include "framework/DeclEntityDef.h"
|
|
#include "renderer/ModelManager.h"
|
|
|
|
#include "gamesys/SysCvar.h"
|
|
#include "physics/Physics_Parametric.h"
|
|
#include "physics/Physics_Actor.h"
|
|
#include "script/Script_Thread.h"
|
|
#include "Fx.h"
|
|
#include "AFEntity.h"
|
|
#include "Player.h"
|
|
#include "Mover.h"
|
|
#include "WorldSpawn.h"
|
|
#include "SmokeParticles.h"
|
|
|
|
#include "Entity.h"
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
idEntity
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
// overridable events
|
|
const idEventDef EV_PostSpawn( "<postspawn>", NULL );
|
|
const idEventDef EV_FindTargets( "<findTargets>", NULL );
|
|
const idEventDef EV_Touch( "<touch>", "et" );
|
|
const idEventDef EV_GetName( "getName", NULL, 's' );
|
|
const idEventDef EV_SetName( "setName", "s" );
|
|
const idEventDef EV_Activate( "activate", "e" );
|
|
const idEventDef EV_ActivateTargets( "activateTargets", "e" );
|
|
const idEventDef EV_NumTargets( "numTargets", NULL, 'f' );
|
|
const idEventDef EV_GetTarget( "getTarget", "f", 'e' );
|
|
const idEventDef EV_RandomTarget( "randomTarget", "s", 'e' );
|
|
const idEventDef EV_Bind( "bind", "e" );
|
|
const idEventDef EV_BindPosition( "bindPosition", "e" );
|
|
const idEventDef EV_BindToJoint( "bindToJoint", "esf" );
|
|
const idEventDef EV_Unbind( "unbind", NULL );
|
|
const idEventDef EV_RemoveBinds( "removeBinds" );
|
|
const idEventDef EV_SpawnBind( "<spawnbind>", NULL );
|
|
const idEventDef EV_SetOwner( "setOwner", "e" );
|
|
const idEventDef EV_SetModel( "setModel", "s" );
|
|
const idEventDef EV_SetSkin( "setSkin", "s" );
|
|
const idEventDef EV_GetWorldOrigin( "getWorldOrigin", NULL, 'v' );
|
|
const idEventDef EV_SetWorldOrigin( "setWorldOrigin", "v" );
|
|
const idEventDef EV_GetOrigin( "getOrigin", NULL, 'v' );
|
|
const idEventDef EV_SetOrigin( "setOrigin", "v" );
|
|
const idEventDef EV_GetAngles( "getAngles", NULL, 'v' );
|
|
const idEventDef EV_SetAngles( "setAngles", "v" );
|
|
const idEventDef EV_GetLinearVelocity( "getLinearVelocity", NULL, 'v' );
|
|
const idEventDef EV_SetLinearVelocity( "setLinearVelocity", "v" );
|
|
const idEventDef EV_GetAngularVelocity( "getAngularVelocity", NULL, 'v' );
|
|
const idEventDef EV_SetAngularVelocity( "setAngularVelocity", "v" );
|
|
const idEventDef EV_GetSize( "getSize", NULL, 'v' );
|
|
const idEventDef EV_SetSize( "setSize", "vv" );
|
|
const idEventDef EV_GetMins( "getMins", NULL, 'v' );
|
|
const idEventDef EV_GetMaxs( "getMaxs", NULL, 'v' );
|
|
const idEventDef EV_IsHidden( "isHidden", NULL, 'd' );
|
|
const idEventDef EV_Hide( "hide", NULL );
|
|
const idEventDef EV_Show( "show", NULL );
|
|
const idEventDef EV_Touches( "touches", "E", 'd' );
|
|
const idEventDef EV_ClearSignal( "clearSignal", "d" );
|
|
const idEventDef EV_GetShaderParm( "getShaderParm", "d", 'f' );
|
|
const idEventDef EV_SetShaderParm( "setShaderParm", "df" );
|
|
const idEventDef EV_SetShaderParms( "setShaderParms", "ffff" );
|
|
const idEventDef EV_SetColor( "setColor", "fff" );
|
|
const idEventDef EV_GetColor( "getColor", NULL, 'v' );
|
|
const idEventDef EV_CacheSoundShader( "cacheSoundShader", "s" );
|
|
const idEventDef EV_StartSoundShader( "startSoundShader", "sd", 'f' );
|
|
const idEventDef EV_StartSound( "startSound", "sdd", 'f' );
|
|
const idEventDef EV_StopSound( "stopSound", "dd" );
|
|
const idEventDef EV_FadeSound( "fadeSound", "dff" );
|
|
const idEventDef EV_SetGuiParm( "setGuiParm", "ss" );
|
|
const idEventDef EV_SetGuiFloat( "setGuiFloat", "sf" );
|
|
const idEventDef EV_GetNextKey( "getNextKey", "ss", 's' );
|
|
const idEventDef EV_SetKey( "setKey", "ss" );
|
|
const idEventDef EV_GetKey( "getKey", "s", 's' );
|
|
const idEventDef EV_GetIntKey( "getIntKey", "s", 'f' );
|
|
const idEventDef EV_GetFloatKey( "getFloatKey", "s", 'f' );
|
|
const idEventDef EV_GetVectorKey( "getVectorKey", "s", 'v' );
|
|
const idEventDef EV_GetEntityKey( "getEntityKey", "s", 'e' );
|
|
const idEventDef EV_RestorePosition( "restorePosition" );
|
|
const idEventDef EV_UpdateCameraTarget( "<updateCameraTarget>", NULL );
|
|
const idEventDef EV_DistanceTo( "distanceTo", "E", 'f' );
|
|
const idEventDef EV_DistanceToPoint( "distanceToPoint", "v", 'f' );
|
|
const idEventDef EV_StartFx( "startFx", "s" );
|
|
const idEventDef EV_HasFunction( "hasFunction", "s", 'd' );
|
|
const idEventDef EV_CallFunction( "callFunction", "s" );
|
|
const idEventDef EV_SetNeverDormant( "setNeverDormant", "d" );
|
|
|
|
ABSTRACT_DECLARATION( idClass, idEntity )
|
|
EVENT( EV_GetName, idEntity::Event_GetName )
|
|
EVENT( EV_SetName, idEntity::Event_SetName )
|
|
EVENT( EV_FindTargets, idEntity::Event_FindTargets )
|
|
EVENT( EV_ActivateTargets, idEntity::Event_ActivateTargets )
|
|
EVENT( EV_NumTargets, idEntity::Event_NumTargets )
|
|
EVENT( EV_GetTarget, idEntity::Event_GetTarget )
|
|
EVENT( EV_RandomTarget, idEntity::Event_RandomTarget )
|
|
EVENT( EV_BindToJoint, idEntity::Event_BindToJoint )
|
|
EVENT( EV_RemoveBinds, idEntity::Event_RemoveBinds )
|
|
EVENT( EV_Bind, idEntity::Event_Bind )
|
|
EVENT( EV_BindPosition, idEntity::Event_BindPosition )
|
|
EVENT( EV_Unbind, idEntity::Event_Unbind )
|
|
EVENT( EV_SpawnBind, idEntity::Event_SpawnBind )
|
|
EVENT( EV_SetOwner, idEntity::Event_SetOwner )
|
|
EVENT( EV_SetModel, idEntity::Event_SetModel )
|
|
EVENT( EV_SetSkin, idEntity::Event_SetSkin )
|
|
EVENT( EV_GetShaderParm, idEntity::Event_GetShaderParm )
|
|
EVENT( EV_SetShaderParm, idEntity::Event_SetShaderParm )
|
|
EVENT( EV_SetShaderParms, idEntity::Event_SetShaderParms )
|
|
EVENT( EV_SetColor, idEntity::Event_SetColor )
|
|
EVENT( EV_GetColor, idEntity::Event_GetColor )
|
|
EVENT( EV_IsHidden, idEntity::Event_IsHidden )
|
|
EVENT( EV_Hide, idEntity::Event_Hide )
|
|
EVENT( EV_Show, idEntity::Event_Show )
|
|
EVENT( EV_CacheSoundShader, idEntity::Event_CacheSoundShader )
|
|
EVENT( EV_StartSoundShader, idEntity::Event_StartSoundShader )
|
|
EVENT( EV_StartSound, idEntity::Event_StartSound )
|
|
EVENT( EV_StopSound, idEntity::Event_StopSound )
|
|
EVENT( EV_FadeSound, idEntity::Event_FadeSound )
|
|
EVENT( EV_GetWorldOrigin, idEntity::Event_GetWorldOrigin )
|
|
EVENT( EV_SetWorldOrigin, idEntity::Event_SetWorldOrigin )
|
|
EVENT( EV_GetOrigin, idEntity::Event_GetOrigin )
|
|
EVENT( EV_SetOrigin, idEntity::Event_SetOrigin )
|
|
EVENT( EV_GetAngles, idEntity::Event_GetAngles )
|
|
EVENT( EV_SetAngles, idEntity::Event_SetAngles )
|
|
EVENT( EV_GetLinearVelocity, idEntity::Event_GetLinearVelocity )
|
|
EVENT( EV_SetLinearVelocity, idEntity::Event_SetLinearVelocity )
|
|
EVENT( EV_GetAngularVelocity, idEntity::Event_GetAngularVelocity )
|
|
EVENT( EV_SetAngularVelocity, idEntity::Event_SetAngularVelocity )
|
|
EVENT( EV_GetSize, idEntity::Event_GetSize )
|
|
EVENT( EV_SetSize, idEntity::Event_SetSize )
|
|
EVENT( EV_GetMins, idEntity::Event_GetMins)
|
|
EVENT( EV_GetMaxs, idEntity::Event_GetMaxs )
|
|
EVENT( EV_Touches, idEntity::Event_Touches )
|
|
EVENT( EV_SetGuiParm, idEntity::Event_SetGuiParm )
|
|
EVENT( EV_SetGuiFloat, idEntity::Event_SetGuiFloat )
|
|
EVENT( EV_GetNextKey, idEntity::Event_GetNextKey )
|
|
EVENT( EV_SetKey, idEntity::Event_SetKey )
|
|
EVENT( EV_GetKey, idEntity::Event_GetKey )
|
|
EVENT( EV_GetIntKey, idEntity::Event_GetIntKey )
|
|
EVENT( EV_GetFloatKey, idEntity::Event_GetFloatKey )
|
|
EVENT( EV_GetVectorKey, idEntity::Event_GetVectorKey )
|
|
EVENT( EV_GetEntityKey, idEntity::Event_GetEntityKey )
|
|
EVENT( EV_RestorePosition, idEntity::Event_RestorePosition )
|
|
EVENT( EV_UpdateCameraTarget, idEntity::Event_UpdateCameraTarget )
|
|
EVENT( EV_DistanceTo, idEntity::Event_DistanceTo )
|
|
EVENT( EV_DistanceToPoint, idEntity::Event_DistanceToPoint )
|
|
EVENT( EV_StartFx, idEntity::Event_StartFx )
|
|
EVENT( EV_Thread_WaitFrame, idEntity::Event_WaitFrame )
|
|
EVENT( EV_Thread_Wait, idEntity::Event_Wait )
|
|
EVENT( EV_HasFunction, idEntity::Event_HasFunction )
|
|
EVENT( EV_CallFunction, idEntity::Event_CallFunction )
|
|
EVENT( EV_SetNeverDormant, idEntity::Event_SetNeverDormant )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
UpdateGuiParms
|
|
================
|
|
*/
|
|
void UpdateGuiParms( idUserInterface *gui, const idDict *args ) {
|
|
if ( gui == NULL || args == NULL ) {
|
|
return;
|
|
}
|
|
const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL );
|
|
while( kv ) {
|
|
gui->SetStateString( kv->GetKey(), kv->GetValue() );
|
|
kv = args->MatchPrefix( "gui_parm", kv );
|
|
}
|
|
gui->SetStateBool( "noninteractive", args->GetBool( "gui_noninteractive" ) ) ;
|
|
gui->StateChanged( gameLocal.time );
|
|
}
|
|
|
|
/*
|
|
================
|
|
AddRenderGui
|
|
================
|
|
*/
|
|
void AddRenderGui( const char *name, idUserInterface **gui, const idDict *args ) {
|
|
const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL );
|
|
*gui = uiManager->FindGui( name, true, ( kv != NULL ) );
|
|
UpdateGuiParms( *gui, args );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameEdit::ParseSpawnArgsToRenderEntity
|
|
|
|
parse the static model parameters
|
|
this is the canonical renderEntity parm parsing,
|
|
which should be used by dmap and the editor
|
|
================
|
|
*/
|
|
void idGameEdit::ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ) {
|
|
int i;
|
|
const char *temp;
|
|
idVec3 color;
|
|
float angle;
|
|
const idDeclModelDef *modelDef;
|
|
|
|
memset( renderEntity, 0, sizeof( *renderEntity ) );
|
|
|
|
temp = args->GetString( "model" );
|
|
|
|
modelDef = NULL;
|
|
if ( temp[0] != '\0' ) {
|
|
modelDef = static_cast<const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, temp, false ) );
|
|
if ( modelDef ) {
|
|
renderEntity->hModel = modelDef->ModelHandle();
|
|
}
|
|
if ( !renderEntity->hModel ) {
|
|
renderEntity->hModel = renderModelManager->FindModel( temp );
|
|
}
|
|
}
|
|
if ( renderEntity->hModel ) {
|
|
renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity );
|
|
} else {
|
|
renderEntity->bounds.Zero();
|
|
}
|
|
|
|
temp = args->GetString( "skin" );
|
|
if ( temp[0] != '\0' ) {
|
|
renderEntity->customSkin = declManager->FindSkin( temp );
|
|
} else if ( modelDef ) {
|
|
renderEntity->customSkin = modelDef->GetDefaultSkin();
|
|
}
|
|
|
|
temp = args->GetString( "shader" );
|
|
if ( temp[0] != '\0' ) {
|
|
renderEntity->customShader = declManager->FindMaterial( temp );
|
|
}
|
|
|
|
args->GetVector( "origin", "0 0 0", renderEntity->origin );
|
|
|
|
// get the rotation matrix in either full form, or single angle form
|
|
if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", renderEntity->axis ) ) {
|
|
angle = args->GetFloat( "angle" );
|
|
if ( angle != 0.0f ) {
|
|
renderEntity->axis = idAngles( 0.0f, angle, 0.0f ).ToMat3();
|
|
} else {
|
|
renderEntity->axis.Identity();
|
|
}
|
|
}
|
|
|
|
renderEntity->referenceSound = NULL;
|
|
|
|
// get shader parms
|
|
args->GetVector( "_color", "1 1 1", color );
|
|
renderEntity->shaderParms[ SHADERPARM_RED ] = color[0];
|
|
renderEntity->shaderParms[ SHADERPARM_GREEN ] = color[1];
|
|
renderEntity->shaderParms[ SHADERPARM_BLUE ] = color[2];
|
|
renderEntity->shaderParms[ 3 ] = args->GetFloat( "shaderParm3", "1" );
|
|
renderEntity->shaderParms[ 4 ] = args->GetFloat( "shaderParm4", "0" );
|
|
renderEntity->shaderParms[ 5 ] = args->GetFloat( "shaderParm5", "0" );
|
|
renderEntity->shaderParms[ 6 ] = args->GetFloat( "shaderParm6", "0" );
|
|
renderEntity->shaderParms[ 7 ] = args->GetFloat( "shaderParm7", "0" );
|
|
renderEntity->shaderParms[ 8 ] = args->GetFloat( "shaderParm8", "0" );
|
|
renderEntity->shaderParms[ 9 ] = args->GetFloat( "shaderParm9", "0" );
|
|
renderEntity->shaderParms[ 10 ] = args->GetFloat( "shaderParm10", "0" );
|
|
renderEntity->shaderParms[ 11 ] = args->GetFloat( "shaderParm11", "0" );
|
|
|
|
// check noDynamicInteractions flag
|
|
renderEntity->noDynamicInteractions = args->GetBool( "noDynamicInteractions" );
|
|
|
|
// check noshadows flag
|
|
renderEntity->noShadow = args->GetBool( "noshadows" );
|
|
|
|
// check noselfshadows flag
|
|
renderEntity->noSelfShadow = args->GetBool( "noselfshadows" );
|
|
|
|
// init any guis, including entity-specific states
|
|
for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) {
|
|
temp = args->GetString( i == 0 ? "gui" : va( "gui%d", i + 1 ) );
|
|
if ( temp[ 0 ] != '\0' ) {
|
|
AddRenderGui( temp, &renderEntity->gui[ i ], args );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameEdit::ParseSpawnArgsToRefSound
|
|
|
|
parse the sound parameters
|
|
this is the canonical refSound parm parsing,
|
|
which should be used by dmap and the editor
|
|
================
|
|
*/
|
|
void idGameEdit::ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ) {
|
|
const char *temp;
|
|
|
|
memset( refSound, 0, sizeof( *refSound ) );
|
|
|
|
refSound->parms.minDistance = args->GetFloat( "s_mindistance" );
|
|
refSound->parms.maxDistance = args->GetFloat( "s_maxdistance" );
|
|
refSound->parms.volume = args->GetFloat( "s_volume" );
|
|
refSound->parms.shakes = args->GetFloat( "s_shakes" );
|
|
|
|
args->GetVector( "origin", "0 0 0", refSound->origin );
|
|
|
|
refSound->referenceSound = NULL;
|
|
|
|
// if a diversity is not specified, every sound start will make
|
|
// a random one. Specifying diversity is usefull to make multiple
|
|
// lights all share the same buzz sound offset, for instance.
|
|
refSound->diversity = args->GetFloat( "s_diversity", "-1" );
|
|
refSound->waitfortrigger = args->GetBool( "s_waitfortrigger" );
|
|
|
|
if ( args->GetBool( "s_omni" ) ) {
|
|
refSound->parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL;
|
|
}
|
|
if ( args->GetBool( "s_looping" ) ) {
|
|
refSound->parms.soundShaderFlags |= SSF_LOOPING;
|
|
}
|
|
if ( args->GetBool( "s_occlusion" ) ) {
|
|
refSound->parms.soundShaderFlags |= SSF_NO_OCCLUSION;
|
|
}
|
|
if ( args->GetBool( "s_global" ) ) {
|
|
refSound->parms.soundShaderFlags |= SSF_GLOBAL;
|
|
}
|
|
if ( args->GetBool( "s_unclamped" ) ) {
|
|
refSound->parms.soundShaderFlags |= SSF_UNCLAMPED;
|
|
}
|
|
refSound->parms.soundClass = args->GetInt( "s_soundClass" );
|
|
|
|
temp = args->GetString( "s_shader" );
|
|
if ( temp[0] != '\0' ) {
|
|
refSound->shader = declManager->FindSound( temp );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idEntity::UpdateChangeableSpawnArgs
|
|
|
|
Any key val pair that might change during the course of the game ( via a gui or whatever )
|
|
should be initialize here so a gui or other trigger can change something and have it updated
|
|
properly. An optional source may be provided if the values reside in an outside dictionary and
|
|
first need copied over to spawnArgs
|
|
===============
|
|
*/
|
|
void idEntity::UpdateChangeableSpawnArgs( const idDict *source ) {
|
|
int i;
|
|
const char *target;
|
|
|
|
if ( !source ) {
|
|
source = &spawnArgs;
|
|
}
|
|
cameraTarget = NULL;
|
|
target = source->GetString( "cameraTarget" );
|
|
if ( target && target[0] ) {
|
|
// update the camera taget
|
|
PostEventMS( &EV_UpdateCameraTarget, 0 );
|
|
}
|
|
|
|
for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) {
|
|
UpdateGuiParms( renderEntity.gui[ i ], source );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::idEntity
|
|
================
|
|
*/
|
|
idEntity::idEntity() {
|
|
|
|
entityNumber = ENTITYNUM_NONE;
|
|
entityDefNumber = -1;
|
|
|
|
spawnNode.SetOwner( this );
|
|
activeNode.SetOwner( this );
|
|
|
|
snapshotNode.SetOwner( this );
|
|
snapshotSequence = -1;
|
|
snapshotBits = 0;
|
|
|
|
thinkFlags = 0;
|
|
dormantStart = 0;
|
|
cinematic = false;
|
|
renderView = NULL;
|
|
cameraTarget = NULL;
|
|
health = 0;
|
|
|
|
physics = NULL;
|
|
bindMaster = NULL;
|
|
bindJoint = INVALID_JOINT;
|
|
bindBody = -1;
|
|
teamMaster = NULL;
|
|
teamChain = NULL;
|
|
signals = NULL;
|
|
|
|
memset( PVSAreas, 0, sizeof( PVSAreas ) );
|
|
numPVSAreas = -1;
|
|
|
|
memset( &fl, 0, sizeof( fl ) );
|
|
fl.neverDormant = true; // most entities never go dormant
|
|
|
|
memset( &renderEntity, 0, sizeof( renderEntity ) );
|
|
modelDefHandle = -1;
|
|
memset( &refSound, 0, sizeof( refSound ) );
|
|
|
|
mpGUIState = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::FixupLocalizedStrings
|
|
================
|
|
*/
|
|
void idEntity::FixupLocalizedStrings() {
|
|
for ( int i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) {
|
|
const idKeyValue *kv = spawnArgs.GetKeyVal( i );
|
|
if ( idStr::Cmpn( kv->GetValue(), STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ){
|
|
spawnArgs.Set( kv->GetKey(), common->GetLanguageDict()->GetString( kv->GetValue() ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Spawn
|
|
================
|
|
*/
|
|
void idEntity::Spawn( void ) {
|
|
int i;
|
|
const char *temp;
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
const idKeyValue *networkSync;
|
|
const char *classname;
|
|
const char *scriptObjectName;
|
|
|
|
gameLocal.RegisterEntity( this );
|
|
|
|
spawnArgs.GetString( "classname", NULL, &classname );
|
|
const idDeclEntityDef *def = gameLocal.FindEntityDef( classname, false );
|
|
if ( def ) {
|
|
entityDefNumber = def->Index();
|
|
}
|
|
|
|
FixupLocalizedStrings();
|
|
|
|
// parse static models the same way the editor display does
|
|
gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity );
|
|
|
|
renderEntity.entityNum = entityNumber;
|
|
|
|
// go dormant within 5 frames so that when the map starts most monsters are dormant
|
|
dormantStart = gameLocal.time - DELAY_DORMANT_TIME + gameLocal.msec * 5;
|
|
|
|
origin = renderEntity.origin;
|
|
axis = renderEntity.axis;
|
|
|
|
// do the audio parsing the same way dmap and the editor do
|
|
gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound );
|
|
|
|
// only play SCHANNEL_PRIVATE when sndworld->PlaceListener() is called with this listenerId
|
|
// don't spatialize sounds from the same entity
|
|
refSound.listenerId = entityNumber + 1;
|
|
|
|
cameraTarget = NULL;
|
|
temp = spawnArgs.GetString( "cameraTarget" );
|
|
if ( temp && temp[0] ) {
|
|
// update the camera taget
|
|
PostEventMS( &EV_UpdateCameraTarget, 0 );
|
|
}
|
|
|
|
for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) {
|
|
UpdateGuiParms( renderEntity.gui[ i ], &spawnArgs );
|
|
}
|
|
|
|
fl.solidForTeam = spawnArgs.GetBool( "solidForTeam", "0" );
|
|
fl.neverDormant = spawnArgs.GetBool( "neverDormant", "0" );
|
|
fl.hidden = spawnArgs.GetBool( "hide", "0" );
|
|
if ( fl.hidden ) {
|
|
// make sure we're hidden, since a spawn function might not set it up right
|
|
PostEventMS( &EV_Hide, 0 );
|
|
}
|
|
cinematic = spawnArgs.GetBool( "cinematic", "0" );
|
|
|
|
networkSync = spawnArgs.FindKey( "networkSync" );
|
|
if ( networkSync ) {
|
|
fl.networkSync = ( atoi( networkSync->GetValue() ) != 0 );
|
|
}
|
|
|
|
#if 0
|
|
if ( !gameLocal.isClient ) {
|
|
// common->DPrintf( "NET: DBG %s - %s is synced: %s\n", spawnArgs.GetString( "classname", "" ), GetType()->classname, fl.networkSync ? "true" : "false" );
|
|
if ( spawnArgs.GetString( "classname", "" )[ 0 ] == '\0' && !fl.networkSync ) {
|
|
common->DPrintf( "NET: WRN %s entity, no classname, and no networkSync?\n", GetType()->classname );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// every object will have a unique name
|
|
temp = spawnArgs.GetString( "name", va( "%s_%s_%d", GetClassname(), spawnArgs.GetString( "classname" ), entityNumber ) );
|
|
SetName( temp );
|
|
|
|
// if we have targets, wait until all entities are spawned to get them
|
|
if ( spawnArgs.MatchPrefix( "target" ) || spawnArgs.MatchPrefix( "guiTarget" ) ) {
|
|
if ( gameLocal.GameState() == GAMESTATE_STARTUP ) {
|
|
PostEventMS( &EV_FindTargets, 0 );
|
|
} else {
|
|
// not during spawn, so it's ok to get the targets
|
|
FindTargets();
|
|
}
|
|
}
|
|
|
|
health = spawnArgs.GetInt( "health" );
|
|
|
|
InitDefaultPhysics( origin, axis );
|
|
|
|
SetOrigin( origin );
|
|
SetAxis( axis );
|
|
|
|
temp = spawnArgs.GetString( "model" );
|
|
if ( temp && *temp ) {
|
|
SetModel( temp );
|
|
}
|
|
|
|
if ( spawnArgs.GetString( "bind", "", &temp ) ) {
|
|
PostEventMS( &EV_SpawnBind, 0 );
|
|
}
|
|
|
|
// auto-start a sound on the entity
|
|
if ( refSound.shader && !refSound.waitfortrigger ) {
|
|
StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL );
|
|
}
|
|
|
|
// setup script object
|
|
if ( ShouldConstructScriptObjectAtSpawn() && spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) {
|
|
if ( !scriptObject.SetType( scriptObjectName ) ) {
|
|
gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() );
|
|
}
|
|
|
|
ConstructScriptObject();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::~idEntity
|
|
================
|
|
*/
|
|
idEntity::~idEntity( void ) {
|
|
|
|
if ( gameLocal.GameState() != GAMESTATE_SHUTDOWN && !gameLocal.isClient && fl.networkSync && entityNumber >= MAX_CLIENTS ) {
|
|
idBitMsg msg;
|
|
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
|
|
|
|
msg.Init( msgBuf, sizeof( msgBuf ) );
|
|
msg.WriteByte( GAME_RELIABLE_MESSAGE_DELETE_ENT );
|
|
msg.WriteBits( gameLocal.GetSpawnId( this ), 32 );
|
|
networkSystem->ServerSendReliableMessage( -1, msg );
|
|
}
|
|
|
|
DeconstructScriptObject();
|
|
scriptObject.Free();
|
|
|
|
if ( thinkFlags ) {
|
|
BecomeInactive( thinkFlags );
|
|
}
|
|
activeNode.Remove();
|
|
|
|
Signal( SIG_REMOVED );
|
|
|
|
// we have to set back the default physics object before unbinding because the entity
|
|
// specific physics object might be an entity variable and as such could already be destroyed.
|
|
SetPhysics( NULL );
|
|
|
|
// remove any entities that are bound to me
|
|
RemoveBinds();
|
|
|
|
// unbind from master
|
|
Unbind();
|
|
QuitTeam();
|
|
|
|
gameLocal.RemoveEntityFromHash( name.c_str(), this );
|
|
|
|
delete renderView;
|
|
renderView = NULL;
|
|
|
|
delete signals;
|
|
signals = NULL;
|
|
|
|
FreeModelDef();
|
|
FreeSoundEmitter( false );
|
|
|
|
gameLocal.UnregisterEntity( this );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Save
|
|
================
|
|
*/
|
|
void idEntity::Save( idSaveGame *savefile ) const {
|
|
int i, j;
|
|
|
|
savefile->WriteInt( entityNumber );
|
|
savefile->WriteInt( entityDefNumber );
|
|
|
|
// spawnNode and activeNode are restored by gameLocal
|
|
|
|
savefile->WriteInt( snapshotSequence );
|
|
savefile->WriteInt( snapshotBits );
|
|
|
|
savefile->WriteDict( &spawnArgs );
|
|
savefile->WriteString( name );
|
|
scriptObject.Save( savefile );
|
|
|
|
savefile->WriteInt( thinkFlags );
|
|
savefile->WriteInt( dormantStart );
|
|
savefile->WriteBool( cinematic );
|
|
|
|
savefile->WriteObject( cameraTarget );
|
|
|
|
savefile->WriteInt( health );
|
|
|
|
savefile->WriteInt( targets.Num() );
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
targets[ i ].Save( savefile );
|
|
}
|
|
|
|
entityFlags_s flags = fl;
|
|
LittleBitField( &flags, sizeof( flags ) );
|
|
savefile->Write( &flags, sizeof( flags ) );
|
|
|
|
savefile->WriteRenderEntity( renderEntity );
|
|
savefile->WriteInt( modelDefHandle );
|
|
savefile->WriteRefSound( refSound );
|
|
|
|
savefile->WriteObject( bindMaster );
|
|
savefile->WriteJoint( bindJoint );
|
|
savefile->WriteInt( bindBody );
|
|
savefile->WriteObject( teamMaster );
|
|
savefile->WriteObject( teamChain );
|
|
|
|
savefile->WriteStaticObject( defaultPhysicsObj );
|
|
|
|
savefile->WriteInt( numPVSAreas );
|
|
for( i = 0; i < MAX_PVS_AREAS; i++ ) {
|
|
savefile->WriteInt( PVSAreas[ i ] );
|
|
}
|
|
|
|
if ( !signals ) {
|
|
savefile->WriteBool( false );
|
|
} else {
|
|
savefile->WriteBool( true );
|
|
for( i = 0; i < NUM_SIGNALS; i++ ) {
|
|
savefile->WriteInt( signals->signal[ i ].Num() );
|
|
for( j = 0; j < signals->signal[ i ].Num(); j++ ) {
|
|
savefile->WriteInt( signals->signal[ i ][ j ].threadnum );
|
|
savefile->WriteString( signals->signal[ i ][ j ].function->Name() );
|
|
}
|
|
}
|
|
}
|
|
|
|
savefile->WriteInt( mpGUIState );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Restore
|
|
================
|
|
*/
|
|
void idEntity::Restore( idRestoreGame *savefile ) {
|
|
int i, j;
|
|
int num;
|
|
idStr funcname;
|
|
|
|
savefile->ReadInt( entityNumber );
|
|
savefile->ReadInt( entityDefNumber );
|
|
|
|
// spawnNode and activeNode are restored by gameLocal
|
|
|
|
savefile->ReadInt( snapshotSequence );
|
|
savefile->ReadInt( snapshotBits );
|
|
|
|
savefile->ReadDict( &spawnArgs );
|
|
savefile->ReadString( name );
|
|
SetName( name );
|
|
|
|
scriptObject.Restore( savefile );
|
|
|
|
savefile->ReadInt( thinkFlags );
|
|
savefile->ReadInt( dormantStart );
|
|
savefile->ReadBool( cinematic );
|
|
|
|
savefile->ReadObject( reinterpret_cast<idClass *&>( cameraTarget ) );
|
|
|
|
savefile->ReadInt( health );
|
|
|
|
targets.Clear();
|
|
savefile->ReadInt( num );
|
|
targets.SetNum( num );
|
|
for( i = 0; i < num; i++ ) {
|
|
targets[ i ].Restore( savefile );
|
|
}
|
|
|
|
savefile->Read( &fl, sizeof( fl ) );
|
|
LittleBitField( &fl, sizeof( fl ) );
|
|
|
|
savefile->ReadRenderEntity( renderEntity );
|
|
savefile->ReadInt( modelDefHandle );
|
|
savefile->ReadRefSound( refSound );
|
|
|
|
savefile->ReadObject( reinterpret_cast<idClass *&>( bindMaster ) );
|
|
savefile->ReadJoint( bindJoint );
|
|
savefile->ReadInt( bindBody );
|
|
savefile->ReadObject( reinterpret_cast<idClass *&>( teamMaster ) );
|
|
savefile->ReadObject( reinterpret_cast<idClass *&>( teamChain ) );
|
|
|
|
savefile->ReadStaticObject( defaultPhysicsObj );
|
|
RestorePhysics( &defaultPhysicsObj );
|
|
|
|
savefile->ReadInt( numPVSAreas );
|
|
for( i = 0; i < MAX_PVS_AREAS; i++ ) {
|
|
savefile->ReadInt( PVSAreas[ i ] );
|
|
}
|
|
|
|
bool readsignals;
|
|
savefile->ReadBool( readsignals );
|
|
if ( readsignals ) {
|
|
signals = new signalList_t;
|
|
for( i = 0; i < NUM_SIGNALS; i++ ) {
|
|
savefile->ReadInt( num );
|
|
signals->signal[ i ].SetNum( num );
|
|
for( j = 0; j < num; j++ ) {
|
|
savefile->ReadInt( signals->signal[ i ][ j ].threadnum );
|
|
savefile->ReadString( funcname );
|
|
signals->signal[ i ][ j ].function = gameLocal.program.FindFunction( funcname );
|
|
if ( !signals->signal[ i ][ j ].function ) {
|
|
savefile->Error( "Function '%s' not found", funcname.c_str() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
savefile->ReadInt( mpGUIState );
|
|
|
|
// restore must retrieve modelDefHandle from the renderer
|
|
if ( modelDefHandle != -1 ) {
|
|
modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetEntityDefName
|
|
================
|
|
*/
|
|
const char * idEntity::GetEntityDefName( void ) const {
|
|
if ( entityDefNumber < 0 ) {
|
|
return "*unknown*";
|
|
}
|
|
return declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetName
|
|
================
|
|
*/
|
|
void idEntity::SetName( const char *newname ) {
|
|
if ( name.Length() ) {
|
|
gameLocal.RemoveEntityFromHash( name.c_str(), this );
|
|
gameLocal.program.SetEntity( name, NULL );
|
|
}
|
|
|
|
name = newname;
|
|
if ( name.Length() ) {
|
|
if ( ( name == "NULL" ) || ( name == "null_entity" ) ) {
|
|
gameLocal.Error( "Cannot name entity '%s'. '%s' is reserved for script.", name.c_str(), name.c_str() );
|
|
}
|
|
gameLocal.AddEntityToHash( name.c_str(), this );
|
|
gameLocal.program.SetEntity( name, this );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetName
|
|
================
|
|
*/
|
|
const char * idEntity::GetName( void ) const {
|
|
return name.c_str();
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
Thinking
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::Think
|
|
================
|
|
*/
|
|
void idEntity::Think( void ) {
|
|
RunPhysics();
|
|
Present();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::DoDormantTests
|
|
|
|
Monsters and other expensive entities that are completely closed
|
|
off from the player can skip all of their work
|
|
================
|
|
*/
|
|
bool idEntity::DoDormantTests( void ) {
|
|
|
|
if ( fl.neverDormant ) {
|
|
return false;
|
|
}
|
|
|
|
// if the monster area is not topologically connected to a player
|
|
if ( !gameLocal.InPlayerConnectedArea( this ) ) {
|
|
if ( dormantStart == 0 ) {
|
|
dormantStart = gameLocal.time;
|
|
}
|
|
if ( gameLocal.time - dormantStart < DELAY_DORMANT_TIME ) {
|
|
// just got closed off, don't go dormant yet
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
// the monster area is topologically connected to a player, but if
|
|
// the monster hasn't been woken up before, do the more precise PVS check
|
|
if ( !fl.hasAwakened ) {
|
|
if ( !gameLocal.InPlayerPVS( this ) ) {
|
|
return true; // stay dormant
|
|
}
|
|
}
|
|
|
|
// wake up
|
|
dormantStart = 0;
|
|
fl.hasAwakened = true; // only go dormant when area closed off now, not just out of PVS
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::CheckDormant
|
|
|
|
Monsters and other expensive entities that are completely closed
|
|
off from the player can skip all of their work
|
|
================
|
|
*/
|
|
bool idEntity::CheckDormant( void ) {
|
|
bool dormant;
|
|
|
|
dormant = DoDormantTests();
|
|
if ( dormant && !fl.isDormant ) {
|
|
fl.isDormant = true;
|
|
DormantBegin();
|
|
} else if ( !dormant && fl.isDormant ) {
|
|
fl.isDormant = false;
|
|
DormantEnd();
|
|
}
|
|
|
|
return dormant;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::DormantBegin
|
|
|
|
called when entity becomes dormant
|
|
================
|
|
*/
|
|
void idEntity::DormantBegin( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::DormantEnd
|
|
|
|
called when entity wakes from being dormant
|
|
================
|
|
*/
|
|
void idEntity::DormantEnd( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::IsActive
|
|
================
|
|
*/
|
|
bool idEntity::IsActive( void ) const {
|
|
return activeNode.InList();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::BecomeActive
|
|
================
|
|
*/
|
|
void idEntity::BecomeActive( int flags ) {
|
|
if ( ( flags & TH_PHYSICS ) ) {
|
|
// enable the team master if this entity is part of a physics team
|
|
if ( teamMaster && teamMaster != this ) {
|
|
teamMaster->BecomeActive( TH_PHYSICS );
|
|
} else if ( !( thinkFlags & TH_PHYSICS ) ) {
|
|
// if this is a pusher
|
|
if ( physics->IsType( idPhysics_Parametric::Type ) || physics->IsType( idPhysics_Actor::Type ) ) {
|
|
gameLocal.sortPushers = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
int oldFlags = thinkFlags;
|
|
thinkFlags |= flags;
|
|
if ( thinkFlags ) {
|
|
if ( !IsActive() ) {
|
|
activeNode.AddToEnd( gameLocal.activeEntities );
|
|
} else if ( !oldFlags ) {
|
|
// we became inactive this frame, so we have to decrease the count of entities to deactivate
|
|
gameLocal.numEntitiesToDeactivate--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::BecomeInactive
|
|
================
|
|
*/
|
|
void idEntity::BecomeInactive( int flags ) {
|
|
if ( ( flags & TH_PHYSICS ) ) {
|
|
// may only disable physics on a team master if no team members are running physics or bound to a joints
|
|
if ( teamMaster == this ) {
|
|
for ( idEntity *ent = teamMaster->teamChain; ent; ent = ent->teamChain ) {
|
|
if ( ( ent->thinkFlags & TH_PHYSICS ) || ( ( ent->bindMaster == this ) && ( ent->bindJoint != INVALID_JOINT ) ) ) {
|
|
flags &= ~TH_PHYSICS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( thinkFlags ) {
|
|
thinkFlags &= ~flags;
|
|
if ( !thinkFlags && IsActive() ) {
|
|
gameLocal.numEntitiesToDeactivate++;
|
|
}
|
|
}
|
|
|
|
if ( ( flags & TH_PHYSICS ) ) {
|
|
// if this entity has a team master
|
|
if ( teamMaster && teamMaster != this ) {
|
|
// if the team master is at rest
|
|
if ( teamMaster->IsAtRest() ) {
|
|
teamMaster->BecomeInactive( TH_PHYSICS );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Visuals
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::SetShaderParm
|
|
================
|
|
*/
|
|
void idEntity::SetShaderParm( int parmnum, float value ) {
|
|
if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) {
|
|
gameLocal.Warning( "shader parm index (%d) out of range", parmnum );
|
|
return;
|
|
}
|
|
|
|
renderEntity.shaderParms[ parmnum ] = value;
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetColor
|
|
================
|
|
*/
|
|
void idEntity::SetColor( float red, float green, float blue ) {
|
|
renderEntity.shaderParms[ SHADERPARM_RED ] = red;
|
|
renderEntity.shaderParms[ SHADERPARM_GREEN ] = green;
|
|
renderEntity.shaderParms[ SHADERPARM_BLUE ] = blue;
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetColor
|
|
================
|
|
*/
|
|
void idEntity::SetColor( const idVec3 &color ) {
|
|
SetColor( color[ 0 ], color[ 1 ], color[ 2 ] );
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetColor
|
|
================
|
|
*/
|
|
void idEntity::GetColor( idVec3 &out ) const {
|
|
out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ];
|
|
out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ];
|
|
out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetColor
|
|
================
|
|
*/
|
|
void idEntity::SetColor( const idVec4 &color ) {
|
|
renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ];
|
|
renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ];
|
|
renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ];
|
|
renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ];
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetColor
|
|
================
|
|
*/
|
|
void idEntity::GetColor( idVec4 &out ) const {
|
|
out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ];
|
|
out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ];
|
|
out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ];
|
|
out[ 3 ] = renderEntity.shaderParms[ SHADERPARM_ALPHA ];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdateAnimationControllers
|
|
================
|
|
*/
|
|
bool idEntity::UpdateAnimationControllers( void ) {
|
|
// any ragdoll and IK animation controllers should be updated here
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetModel
|
|
================
|
|
*/
|
|
void idEntity::SetModel( const char *modelname ) {
|
|
assert( modelname );
|
|
|
|
FreeModelDef();
|
|
|
|
renderEntity.hModel = renderModelManager->FindModel( modelname );
|
|
|
|
if ( renderEntity.hModel ) {
|
|
renderEntity.hModel->Reset();
|
|
}
|
|
|
|
renderEntity.callback = NULL;
|
|
renderEntity.numJoints = 0;
|
|
renderEntity.joints = NULL;
|
|
if ( renderEntity.hModel ) {
|
|
renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity );
|
|
} else {
|
|
renderEntity.bounds.Zero();
|
|
}
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetSkin
|
|
================
|
|
*/
|
|
void idEntity::SetSkin( const idDeclSkin *skin ) {
|
|
renderEntity.customSkin = skin;
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetSkin
|
|
================
|
|
*/
|
|
const idDeclSkin *idEntity::GetSkin( void ) const {
|
|
return renderEntity.customSkin;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::FreeModelDef
|
|
================
|
|
*/
|
|
void idEntity::FreeModelDef( void ) {
|
|
if ( modelDefHandle != -1 ) {
|
|
gameRenderWorld->FreeEntityDef( modelDefHandle );
|
|
modelDefHandle = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::FreeLightDef
|
|
================
|
|
*/
|
|
void idEntity::FreeLightDef( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::IsHidden
|
|
================
|
|
*/
|
|
bool idEntity::IsHidden( void ) const {
|
|
return fl.hidden;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Hide
|
|
================
|
|
*/
|
|
void idEntity::Hide( void ) {
|
|
if ( !IsHidden() ) {
|
|
fl.hidden = true;
|
|
FreeModelDef();
|
|
UpdateVisuals();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Show
|
|
================
|
|
*/
|
|
void idEntity::Show( void ) {
|
|
if ( IsHidden() ) {
|
|
fl.hidden = false;
|
|
UpdateVisuals();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdateModelTransform
|
|
================
|
|
*/
|
|
void idEntity::UpdateModelTransform( void ) {
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
|
|
if ( GetPhysicsToVisualTransform( origin, axis ) ) {
|
|
renderEntity.axis = axis * GetPhysics()->GetAxis();
|
|
renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis;
|
|
} else {
|
|
renderEntity.axis = GetPhysics()->GetAxis();
|
|
renderEntity.origin = GetPhysics()->GetOrigin();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdateModel
|
|
================
|
|
*/
|
|
void idEntity::UpdateModel( void ) {
|
|
UpdateModelTransform();
|
|
|
|
// check if the entity has an MD5 model
|
|
idAnimator *animator = GetAnimator();
|
|
if ( animator && animator->ModelHandle() ) {
|
|
// set the callback to update the joints
|
|
renderEntity.callback = idEntity::ModelCallback;
|
|
}
|
|
|
|
// set to invalid number to force an update the next time the PVS areas are retrieved
|
|
ClearPVSAreas();
|
|
|
|
// ensure that we call Present this frame
|
|
BecomeActive( TH_UPDATEVISUALS );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdateVisuals
|
|
================
|
|
*/
|
|
void idEntity::UpdateVisuals( void ) {
|
|
UpdateModel();
|
|
UpdateSound();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdatePVSAreas
|
|
================
|
|
*/
|
|
void idEntity::UpdatePVSAreas( void ) {
|
|
int localNumPVSAreas, localPVSAreas[32];
|
|
idBounds modelAbsBounds;
|
|
int i;
|
|
|
|
modelAbsBounds.FromTransformedBounds( renderEntity.bounds, renderEntity.origin, renderEntity.axis );
|
|
localNumPVSAreas = gameLocal.pvs.GetPVSAreas( modelAbsBounds, localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) );
|
|
|
|
// FIXME: some particle systems may have huge bounds and end up in many PVS areas
|
|
// the first MAX_PVS_AREAS may not be visible to a network client and as a result the particle system may not show up when it should
|
|
if ( localNumPVSAreas > MAX_PVS_AREAS ) {
|
|
localNumPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( modelAbsBounds.GetCenter() ).Expand( 64.0f ), localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) );
|
|
}
|
|
|
|
for ( numPVSAreas = 0; numPVSAreas < MAX_PVS_AREAS && numPVSAreas < localNumPVSAreas; numPVSAreas++ ) {
|
|
PVSAreas[numPVSAreas] = localPVSAreas[numPVSAreas];
|
|
}
|
|
|
|
for( i = numPVSAreas; i < MAX_PVS_AREAS; i++ ) {
|
|
PVSAreas[ i ] = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdatePVSAreas
|
|
================
|
|
*/
|
|
void idEntity::UpdatePVSAreas( const idVec3 &pos ) {
|
|
int i;
|
|
|
|
numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( pos ), PVSAreas, MAX_PVS_AREAS );
|
|
i = numPVSAreas;
|
|
while ( i < MAX_PVS_AREAS ) {
|
|
PVSAreas[ i++ ] = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetNumPVSAreas
|
|
================
|
|
*/
|
|
int idEntity::GetNumPVSAreas( void ) {
|
|
if ( numPVSAreas < 0 ) {
|
|
UpdatePVSAreas();
|
|
}
|
|
return numPVSAreas;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetPVSAreas
|
|
================
|
|
*/
|
|
const int *idEntity::GetPVSAreas( void ) {
|
|
if ( numPVSAreas < 0 ) {
|
|
UpdatePVSAreas();
|
|
}
|
|
return PVSAreas;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ClearPVSAreas
|
|
================
|
|
*/
|
|
void idEntity::ClearPVSAreas( void ) {
|
|
numPVSAreas = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::PhysicsTeamInPVS
|
|
|
|
FIXME: for networking also return true if any of the entity shadows is in the PVS
|
|
================
|
|
*/
|
|
bool idEntity::PhysicsTeamInPVS( pvsHandle_t pvsHandle ) {
|
|
idEntity *part;
|
|
|
|
if ( teamMaster ) {
|
|
for ( part = teamMaster; part; part = part->teamChain ) {
|
|
if ( gameLocal.pvs.InCurrentPVS( pvsHandle, part->GetPVSAreas(), part->GetNumPVSAreas() ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
return gameLocal.pvs.InCurrentPVS( pvsHandle, GetPVSAreas(), GetNumPVSAreas() );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idEntity::ProjectOverlay
|
|
==============
|
|
*/
|
|
void idEntity::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) {
|
|
float s, c;
|
|
idMat3 axis, axistemp;
|
|
idVec3 localOrigin, localAxis[2];
|
|
idPlane localPlane[2];
|
|
|
|
// make sure the entity has a valid model handle
|
|
if ( modelDefHandle < 0 ) {
|
|
return;
|
|
}
|
|
|
|
// only do this on dynamic md5 models
|
|
if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) {
|
|
return;
|
|
}
|
|
|
|
idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c );
|
|
|
|
axis[2] = -dir;
|
|
axis[2].NormalVectors( axistemp[0], axistemp[1] );
|
|
axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s;
|
|
axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c;
|
|
|
|
renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin );
|
|
renderEntity.axis.ProjectVector( axis[0], localAxis[0] );
|
|
renderEntity.axis.ProjectVector( axis[1], localAxis[1] );
|
|
|
|
size = 1.0f / size;
|
|
localAxis[0] *= size;
|
|
localAxis[1] *= size;
|
|
|
|
localPlane[0] = localAxis[0];
|
|
localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f;
|
|
|
|
localPlane[1] = localAxis[1];
|
|
localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f;
|
|
|
|
const idMaterial *mtr = declManager->FindMaterial( material );
|
|
|
|
// project an overlay onto the model
|
|
gameRenderWorld->ProjectOverlay( modelDefHandle, localPlane, mtr );
|
|
|
|
// make sure non-animating models update their overlay
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Present
|
|
|
|
Present is called to allow entities to generate refEntities, lights, etc for the renderer.
|
|
================
|
|
*/
|
|
void idEntity::Present( void ) {
|
|
|
|
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 ) ) {
|
|
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 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetRenderEntity
|
|
================
|
|
*/
|
|
renderEntity_t *idEntity::GetRenderEntity( void ) {
|
|
return &renderEntity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetModelDefHandle
|
|
================
|
|
*/
|
|
int idEntity::GetModelDefHandle( void ) {
|
|
return modelDefHandle;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdateRenderEntity
|
|
================
|
|
*/
|
|
bool idEntity::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
|
|
if ( gameLocal.inCinematic && gameLocal.skipCinematic ) {
|
|
return false;
|
|
}
|
|
|
|
idAnimator *animator = GetAnimator();
|
|
if ( animator ) {
|
|
return animator->CreateFrame( gameLocal.time, false );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ModelCallback
|
|
|
|
NOTE: may not change the game state whatsoever!
|
|
================
|
|
*/
|
|
bool idEntity::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
|
|
idEntity *ent;
|
|
|
|
ent = gameLocal.entities[ renderEntity->entityNum ];
|
|
if ( !ent ) {
|
|
gameLocal.Error( "idEntity::ModelCallback: callback with NULL game entity" );
|
|
}
|
|
|
|
return ent->UpdateRenderEntity( renderEntity, renderView );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetAnimator
|
|
|
|
Subclasses will be responsible for allocating animator.
|
|
================
|
|
*/
|
|
idAnimator *idEntity::GetAnimator( void ) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idEntity::GetRenderView
|
|
|
|
This is used by remote camera views to look from an entity
|
|
=============
|
|
*/
|
|
renderView_t *idEntity::GetRenderView( void ) {
|
|
if ( !renderView ) {
|
|
renderView = new renderView_t;
|
|
}
|
|
memset( renderView, 0, sizeof( *renderView ) );
|
|
|
|
renderView->vieworg = GetPhysics()->GetOrigin();
|
|
renderView->fov_x = 120;
|
|
renderView->fov_y = 120;
|
|
renderView->viewaxis = GetPhysics()->GetAxis();
|
|
|
|
// copy global shader parms
|
|
for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) {
|
|
renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ];
|
|
}
|
|
|
|
renderView->globalMaterial = gameLocal.GetGlobalMaterial();
|
|
|
|
renderView->time = gameLocal.time;
|
|
|
|
return renderView;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Sound
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::CanPlayChatterSounds
|
|
|
|
Used for playing chatter sounds on monsters.
|
|
================
|
|
*/
|
|
bool idEntity::CanPlayChatterSounds( void ) const {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::StartSound
|
|
================
|
|
*/
|
|
bool idEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) {
|
|
const idSoundShader *shader;
|
|
const char *sound;
|
|
|
|
if ( length ) {
|
|
*length = 0;
|
|
}
|
|
|
|
// we should ALWAYS be playing sounds from the def.
|
|
// hardcoded sounds MUST be avoided at all times because they won't get precached.
|
|
assert( idStr::Icmpn( soundName, "snd_", 4 ) == 0 );
|
|
|
|
if ( !spawnArgs.GetString( soundName, "", &sound ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( sound[0] == '\0' ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !gameLocal.isNewFrame ) {
|
|
// don't play the sound, but don't report an error
|
|
return true;
|
|
}
|
|
|
|
shader = declManager->FindSound( sound );
|
|
return StartSoundShader( shader, channel, soundShaderFlags, broadcast, length );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::StartSoundShader
|
|
================
|
|
*/
|
|
bool idEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) {
|
|
float diversity;
|
|
int len;
|
|
|
|
if ( length ) {
|
|
*length = 0;
|
|
}
|
|
|
|
if ( !shader ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !gameLocal.isNewFrame ) {
|
|
return true;
|
|
}
|
|
|
|
if ( gameLocal.isServer && broadcast ) {
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.Init( msgBuf, sizeof( msgBuf ) );
|
|
msg.BeginWriting();
|
|
msg.WriteLong( gameLocal.ServerRemapDecl( -1, DECL_SOUND, shader->Index() ) );
|
|
msg.WriteByte( channel );
|
|
ServerSendEvent( EVENT_STARTSOUNDSHADER, &msg, false, -1 );
|
|
}
|
|
|
|
// set a random value for diversity unless one was parsed from the entity
|
|
if ( refSound.diversity < 0.0f ) {
|
|
diversity = gameLocal.random.RandomFloat();
|
|
} else {
|
|
diversity = refSound.diversity;
|
|
}
|
|
|
|
// if we don't have a soundEmitter allocated yet, get one now
|
|
if ( !refSound.referenceSound ) {
|
|
refSound.referenceSound = gameSoundWorld->AllocSoundEmitter();
|
|
}
|
|
|
|
UpdateSound();
|
|
|
|
len = refSound.referenceSound->StartSound( shader, channel, diversity, soundShaderFlags );
|
|
if ( length ) {
|
|
*length = len;
|
|
}
|
|
|
|
// set reference to the sound for shader synced effects
|
|
renderEntity.referenceSound = refSound.referenceSound;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::StopSound
|
|
================
|
|
*/
|
|
void idEntity::StopSound( const s_channelType channel, bool broadcast ) {
|
|
if ( !gameLocal.isNewFrame ) {
|
|
return;
|
|
}
|
|
|
|
if ( gameLocal.isServer && broadcast ) {
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.Init( msgBuf, sizeof( msgBuf ) );
|
|
msg.BeginWriting();
|
|
msg.WriteByte( channel );
|
|
ServerSendEvent( EVENT_STOPSOUNDSHADER, &msg, false, -1 );
|
|
}
|
|
|
|
if ( refSound.referenceSound ) {
|
|
refSound.referenceSound->StopSound( channel );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetSoundVolume
|
|
|
|
Must be called before starting a new sound.
|
|
================
|
|
*/
|
|
void idEntity::SetSoundVolume( float volume ) {
|
|
refSound.parms.volume = volume;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdateSound
|
|
================
|
|
*/
|
|
void idEntity::UpdateSound( void ) {
|
|
if ( refSound.referenceSound ) {
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
|
|
if ( GetPhysicsToSoundTransform( origin, axis ) ) {
|
|
refSound.origin = GetPhysics()->GetOrigin() + origin * axis;
|
|
} else {
|
|
refSound.origin = GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
refSound.referenceSound->UpdateEmitter( refSound.origin, refSound.listenerId, &refSound.parms );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetListenerId
|
|
================
|
|
*/
|
|
int idEntity::GetListenerId( void ) const {
|
|
return refSound.listenerId;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetSoundEmitter
|
|
================
|
|
*/
|
|
idSoundEmitter *idEntity::GetSoundEmitter( void ) const {
|
|
return refSound.referenceSound;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::FreeSoundEmitter
|
|
================
|
|
*/
|
|
void idEntity::FreeSoundEmitter( bool immediate ) {
|
|
if ( refSound.referenceSound ) {
|
|
refSound.referenceSound->Free( immediate );
|
|
refSound.referenceSound = NULL;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
entity binding
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::PreBind
|
|
================
|
|
*/
|
|
void idEntity::PreBind( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::PostBind
|
|
================
|
|
*/
|
|
void idEntity::PostBind( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::PreUnbind
|
|
================
|
|
*/
|
|
void idEntity::PreUnbind( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::PostUnbind
|
|
================
|
|
*/
|
|
void idEntity::PostUnbind( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::InitBind
|
|
================
|
|
*/
|
|
bool idEntity::InitBind( idEntity *master ) {
|
|
|
|
if ( master == this ) {
|
|
gameLocal.Error( "Tried to bind an object to itself." );
|
|
return false;
|
|
}
|
|
|
|
if ( this == gameLocal.world ) {
|
|
gameLocal.Error( "Tried to bind world to another entity" );
|
|
return false;
|
|
}
|
|
|
|
// unbind myself from my master
|
|
Unbind();
|
|
|
|
// add any bind constraints to an articulated figure
|
|
if ( master && IsType( idAFEntity_Base::Type ) ) {
|
|
static_cast<idAFEntity_Base *>(this)->AddBindConstraints();
|
|
}
|
|
|
|
if ( !master || master == gameLocal.world ) {
|
|
// this can happen in scripts, so safely exit out.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::FinishBind
|
|
================
|
|
*/
|
|
void idEntity::FinishBind( void ) {
|
|
|
|
// set the master on the physics object
|
|
physics->SetMaster( bindMaster, fl.bindOrientated );
|
|
|
|
// We are now separated from our previous team and are either
|
|
// an individual, or have a team of our own. Now we can join
|
|
// the new bindMaster's team. Bindmaster must be set before
|
|
// joining the team, or we will be placed in the wrong position
|
|
// on the team.
|
|
JoinTeam( bindMaster );
|
|
|
|
// if our bindMaster is enabled during a cinematic, we must be, too
|
|
cinematic = bindMaster->cinematic;
|
|
|
|
// make sure the team master is active so that physics get run
|
|
teamMaster->BecomeActive( TH_PHYSICS );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Bind
|
|
|
|
bind relative to the visual position of the master
|
|
================
|
|
*/
|
|
void idEntity::Bind( idEntity *master, bool orientated ) {
|
|
|
|
if ( !InitBind( master ) ) {
|
|
return;
|
|
}
|
|
|
|
PreBind();
|
|
|
|
bindJoint = INVALID_JOINT;
|
|
bindBody = -1;
|
|
bindMaster = master;
|
|
fl.bindOrientated = orientated;
|
|
|
|
FinishBind();
|
|
|
|
PostBind( );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::BindToJoint
|
|
|
|
bind relative to a joint of the md5 model used by the master
|
|
================
|
|
*/
|
|
void idEntity::BindToJoint( idEntity *master, const char *jointname, bool orientated ) {
|
|
jointHandle_t jointnum;
|
|
idAnimator *masterAnimator;
|
|
|
|
if ( !InitBind( master ) ) {
|
|
return;
|
|
}
|
|
|
|
masterAnimator = master->GetAnimator();
|
|
if ( !masterAnimator ) {
|
|
gameLocal.Warning( "idEntity::BindToJoint: entity '%s' cannot support skeletal models.", master->GetName() );
|
|
return;
|
|
}
|
|
|
|
jointnum = masterAnimator->GetJointHandle( jointname );
|
|
if ( jointnum == INVALID_JOINT ) {
|
|
gameLocal.Warning( "idEntity::BindToJoint: joint '%s' not found on entity '%s'.", jointname, master->GetName() );
|
|
}
|
|
|
|
PreBind();
|
|
|
|
bindJoint = jointnum;
|
|
bindBody = -1;
|
|
bindMaster = master;
|
|
fl.bindOrientated = orientated;
|
|
|
|
FinishBind();
|
|
|
|
PostBind();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::BindToJoint
|
|
|
|
bind relative to a joint of the md5 model used by the master
|
|
================
|
|
*/
|
|
void idEntity::BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ) {
|
|
|
|
if ( !InitBind( master ) ) {
|
|
return;
|
|
}
|
|
|
|
PreBind();
|
|
|
|
bindJoint = jointnum;
|
|
bindBody = -1;
|
|
bindMaster = master;
|
|
fl.bindOrientated = orientated;
|
|
|
|
FinishBind();
|
|
|
|
PostBind();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::BindToBody
|
|
|
|
bind relative to a collision model used by the physics of the master
|
|
================
|
|
*/
|
|
void idEntity::BindToBody( idEntity *master, int bodyId, bool orientated ) {
|
|
|
|
if ( !InitBind( master ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( bodyId < 0 ) {
|
|
gameLocal.Warning( "idEntity::BindToBody: body '%d' not found.", bodyId );
|
|
}
|
|
|
|
PreBind();
|
|
|
|
bindJoint = INVALID_JOINT;
|
|
bindBody = bodyId;
|
|
bindMaster = master;
|
|
fl.bindOrientated = orientated;
|
|
|
|
FinishBind();
|
|
|
|
PostBind();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Unbind
|
|
================
|
|
*/
|
|
void idEntity::Unbind( void ) {
|
|
idEntity * prev;
|
|
idEntity * next;
|
|
idEntity * last;
|
|
idEntity * ent;
|
|
|
|
// remove any bind constraints from an articulated figure
|
|
if ( IsType( idAFEntity_Base::Type ) ) {
|
|
static_cast<idAFEntity_Base *>(this)->RemoveBindConstraints();
|
|
}
|
|
|
|
if ( !bindMaster ) {
|
|
return;
|
|
}
|
|
|
|
if ( !teamMaster ) {
|
|
// Teammaster already has been freed
|
|
bindMaster = NULL;
|
|
return;
|
|
}
|
|
|
|
PreUnbind();
|
|
|
|
if ( physics ) {
|
|
physics->SetMaster( NULL, fl.bindOrientated );
|
|
}
|
|
|
|
// We're still part of a team, so that means I have to extricate myself
|
|
// and any entities that are bound to me from the old team.
|
|
// Find the node previous to me in the team
|
|
prev = teamMaster;
|
|
for( ent = teamMaster->teamChain; ent && ( ent != this ); ent = ent->teamChain ) {
|
|
prev = ent;
|
|
}
|
|
|
|
assert( ent == this ); // If ent is not pointing to this, then something is very wrong.
|
|
|
|
// Find the last node in my team that is bound to me.
|
|
// Also find the first node not bound to me, if one exists.
|
|
last = this;
|
|
for( next = teamChain; next != NULL; next = next->teamChain ) {
|
|
if ( !next->IsBoundTo( this ) ) {
|
|
break;
|
|
}
|
|
|
|
// Tell them I'm now the teamMaster
|
|
next->teamMaster = this;
|
|
last = next;
|
|
}
|
|
|
|
// disconnect the last member of our team from the old team
|
|
last->teamChain = NULL;
|
|
|
|
// connect up the previous member of the old team to the node that
|
|
// follow the last node bound to me (if one exists).
|
|
if ( teamMaster != this ) {
|
|
prev->teamChain = next;
|
|
if ( !next && ( teamMaster == prev ) ) {
|
|
prev->teamMaster = NULL;
|
|
}
|
|
} else if ( next ) {
|
|
// If we were the teamMaster, then the nodes that were not bound to me are now
|
|
// a disconnected chain. Make them into their own team.
|
|
for( ent = next; ent->teamChain != NULL; ent = ent->teamChain ) {
|
|
ent->teamMaster = next;
|
|
}
|
|
next->teamMaster = next;
|
|
}
|
|
|
|
// If we don't have anyone on our team, then clear the team variables.
|
|
if ( teamChain ) {
|
|
// make myself my own team
|
|
teamMaster = this;
|
|
} else {
|
|
// no longer a team
|
|
teamMaster = NULL;
|
|
}
|
|
|
|
bindJoint = INVALID_JOINT;
|
|
bindBody = -1;
|
|
bindMaster = NULL;
|
|
|
|
PostUnbind();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::RemoveBinds
|
|
================
|
|
*/
|
|
void idEntity::RemoveBinds( void ) {
|
|
idEntity *ent;
|
|
idEntity *next;
|
|
|
|
for( ent = teamChain; ent != NULL; ent = next ) {
|
|
next = ent->teamChain;
|
|
if ( ent->bindMaster == this ) {
|
|
ent->Unbind();
|
|
ent->PostEventMS( &EV_Remove, 0 );
|
|
next = teamChain;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::IsBound
|
|
================
|
|
*/
|
|
bool idEntity::IsBound( void ) const {
|
|
if ( bindMaster ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::IsBoundTo
|
|
================
|
|
*/
|
|
bool idEntity::IsBoundTo( idEntity *master ) const {
|
|
idEntity *ent;
|
|
|
|
if ( !bindMaster ) {
|
|
return false;
|
|
}
|
|
|
|
for ( ent = bindMaster; ent != NULL; ent = ent->bindMaster ) {
|
|
if ( ent == master ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetBindMaster
|
|
================
|
|
*/
|
|
idEntity *idEntity::GetBindMaster( void ) const {
|
|
return bindMaster;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetBindJoint
|
|
================
|
|
*/
|
|
jointHandle_t idEntity::GetBindJoint( void ) const {
|
|
return bindJoint;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetBindBody
|
|
================
|
|
*/
|
|
int idEntity::GetBindBody( void ) const {
|
|
return bindBody;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetTeamMaster
|
|
================
|
|
*/
|
|
idEntity *idEntity::GetTeamMaster( void ) const {
|
|
return teamMaster;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetNextTeamEntity
|
|
================
|
|
*/
|
|
idEntity *idEntity::GetNextTeamEntity( void ) const {
|
|
return teamChain;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idEntity::ConvertLocalToWorldTransform
|
|
=====================
|
|
*/
|
|
void idEntity::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) {
|
|
UpdateModelTransform();
|
|
|
|
offset = renderEntity.origin + offset * renderEntity.axis;
|
|
axis *= renderEntity.axis;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetLocalVector
|
|
|
|
Takes a vector in worldspace and transforms it into the parent
|
|
object's localspace.
|
|
|
|
Note: Does not take origin into acount. Use getLocalCoordinate to
|
|
convert coordinates.
|
|
================
|
|
*/
|
|
idVec3 idEntity::GetLocalVector( const idVec3 &vec ) const {
|
|
idVec3 pos;
|
|
|
|
if ( !bindMaster ) {
|
|
return vec;
|
|
}
|
|
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
|
|
GetMasterPosition( masterOrigin, masterAxis );
|
|
masterAxis.ProjectVector( vec, pos );
|
|
|
|
return pos;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetLocalCoordinates
|
|
|
|
Takes a vector in world coordinates and transforms it into the parent
|
|
object's local coordinates.
|
|
================
|
|
*/
|
|
idVec3 idEntity::GetLocalCoordinates( const idVec3 &vec ) const {
|
|
idVec3 pos;
|
|
|
|
if ( !bindMaster ) {
|
|
return vec;
|
|
}
|
|
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
|
|
GetMasterPosition( masterOrigin, masterAxis );
|
|
masterAxis.ProjectVector( vec - masterOrigin, pos );
|
|
|
|
return pos;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetWorldVector
|
|
|
|
Takes a vector in the parent object's local coordinates and transforms
|
|
it into world coordinates.
|
|
|
|
Note: Does not take origin into acount. Use getWorldCoordinate to
|
|
convert coordinates.
|
|
================
|
|
*/
|
|
idVec3 idEntity::GetWorldVector( const idVec3 &vec ) const {
|
|
idVec3 pos;
|
|
|
|
if ( !bindMaster ) {
|
|
return vec;
|
|
}
|
|
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
|
|
GetMasterPosition( masterOrigin, masterAxis );
|
|
masterAxis.UnprojectVector( vec, pos );
|
|
|
|
return pos;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetWorldCoordinates
|
|
|
|
Takes a vector in the parent object's local coordinates and transforms
|
|
it into world coordinates.
|
|
================
|
|
*/
|
|
idVec3 idEntity::GetWorldCoordinates( const idVec3 &vec ) const {
|
|
idVec3 pos;
|
|
|
|
if ( !bindMaster ) {
|
|
return vec;
|
|
}
|
|
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
|
|
GetMasterPosition( masterOrigin, masterAxis );
|
|
masterAxis.UnprojectVector( vec, pos );
|
|
pos += masterOrigin;
|
|
|
|
return pos;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetMasterPosition
|
|
================
|
|
*/
|
|
bool idEntity::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const {
|
|
idVec3 localOrigin;
|
|
idMat3 localAxis;
|
|
idAnimator *masterAnimator;
|
|
|
|
if ( bindMaster ) {
|
|
// if bound to a joint of an animated model
|
|
if ( bindJoint != INVALID_JOINT ) {
|
|
masterAnimator = bindMaster->GetAnimator();
|
|
if ( !masterAnimator ) {
|
|
masterOrigin = vec3_origin;
|
|
masterAxis = mat3_identity;
|
|
return false;
|
|
} else {
|
|
masterAnimator->GetJointTransform( bindJoint, gameLocal.time, masterOrigin, masterAxis );
|
|
masterAxis *= bindMaster->renderEntity.axis;
|
|
masterOrigin = bindMaster->renderEntity.origin + masterOrigin * bindMaster->renderEntity.axis;
|
|
}
|
|
} else if ( bindBody >= 0 && bindMaster->GetPhysics() ) {
|
|
masterOrigin = bindMaster->GetPhysics()->GetOrigin( bindBody );
|
|
masterAxis = bindMaster->GetPhysics()->GetAxis( bindBody );
|
|
} else {
|
|
masterOrigin = bindMaster->renderEntity.origin;
|
|
masterAxis = bindMaster->renderEntity.axis;
|
|
}
|
|
return true;
|
|
} else {
|
|
masterOrigin = vec3_origin;
|
|
masterAxis = mat3_identity;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetWorldVelocities
|
|
================
|
|
*/
|
|
void idEntity::GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const {
|
|
|
|
linearVelocity = physics->GetLinearVelocity();
|
|
angularVelocity = physics->GetAngularVelocity();
|
|
|
|
if ( bindMaster ) {
|
|
idVec3 masterOrigin, masterLinearVelocity, masterAngularVelocity;
|
|
idMat3 masterAxis;
|
|
|
|
// get position of master
|
|
GetMasterPosition( masterOrigin, masterAxis );
|
|
|
|
// get master velocities
|
|
bindMaster->GetWorldVelocities( masterLinearVelocity, masterAngularVelocity );
|
|
|
|
// linear velocity relative to master plus master linear and angular velocity
|
|
linearVelocity = linearVelocity * masterAxis + masterLinearVelocity +
|
|
masterAngularVelocity.Cross( GetPhysics()->GetOrigin() - masterOrigin );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::JoinTeam
|
|
================
|
|
*/
|
|
void idEntity::JoinTeam( idEntity *teammember ) {
|
|
idEntity *ent;
|
|
idEntity *master;
|
|
idEntity *prev;
|
|
idEntity *next;
|
|
|
|
// if we're already on a team, quit it so we can join this one
|
|
if ( teamMaster && ( teamMaster != this ) ) {
|
|
QuitTeam();
|
|
}
|
|
|
|
assert( teammember );
|
|
|
|
if ( teammember == this ) {
|
|
teamMaster = this;
|
|
return;
|
|
}
|
|
|
|
// check if our new team mate is already on a team
|
|
master = teammember->teamMaster;
|
|
if ( !master ) {
|
|
// he's not on a team, so he's the new teamMaster
|
|
master = teammember;
|
|
teammember->teamMaster = teammember;
|
|
teammember->teamChain = this;
|
|
|
|
// make anyone who's bound to me part of the new team
|
|
for( ent = teamChain; ent != NULL; ent = ent->teamChain ) {
|
|
ent->teamMaster = master;
|
|
}
|
|
} else {
|
|
// skip past the chain members bound to the entity we're teaming up with
|
|
prev = teammember;
|
|
next = teammember->teamChain;
|
|
if ( bindMaster ) {
|
|
// if we have a bindMaster, join after any entities bound to the entity
|
|
// we're joining
|
|
while( next && next->IsBoundTo( teammember ) ) {
|
|
prev = next;
|
|
next = next->teamChain;
|
|
}
|
|
} else {
|
|
// if we're not bound to someone, then put us at the end of the team
|
|
while( next ) {
|
|
prev = next;
|
|
next = next->teamChain;
|
|
}
|
|
}
|
|
|
|
// make anyone who's bound to me part of the new team and
|
|
// also find the last member of my team
|
|
for( ent = this; ent->teamChain != NULL; ent = ent->teamChain ) {
|
|
ent->teamChain->teamMaster = master;
|
|
}
|
|
|
|
prev->teamChain = this;
|
|
ent->teamChain = next;
|
|
}
|
|
|
|
teamMaster = master;
|
|
|
|
// reorder the active entity list
|
|
gameLocal.sortTeamMasters = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::QuitTeam
|
|
================
|
|
*/
|
|
void idEntity::QuitTeam( void ) {
|
|
idEntity *ent;
|
|
|
|
if ( !teamMaster ) {
|
|
return;
|
|
}
|
|
|
|
// check if I'm the teamMaster
|
|
if ( teamMaster == this ) {
|
|
// do we have more than one teammate?
|
|
if ( !teamChain->teamChain ) {
|
|
// no, break up the team
|
|
teamChain->teamMaster = NULL;
|
|
} else {
|
|
// yes, so make the first teammate the teamMaster
|
|
for( ent = teamChain; ent; ent = ent->teamChain ) {
|
|
ent->teamMaster = teamChain;
|
|
}
|
|
}
|
|
} else {
|
|
assert( teamMaster );
|
|
assert( teamMaster->teamChain );
|
|
|
|
// find the previous member of the teamChain
|
|
ent = teamMaster;
|
|
while( ent->teamChain != this ) {
|
|
assert( ent->teamChain ); // this should never happen
|
|
ent = ent->teamChain;
|
|
}
|
|
|
|
// remove this from the teamChain
|
|
ent->teamChain = teamChain;
|
|
|
|
// if no one is left on the team, break it up
|
|
if ( !teamMaster->teamChain ) {
|
|
teamMaster->teamMaster = NULL;
|
|
}
|
|
}
|
|
|
|
teamMaster = NULL;
|
|
teamChain = NULL;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Physics.
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::InitDefaultPhysics
|
|
================
|
|
*/
|
|
void idEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) {
|
|
const char *temp;
|
|
idClipModel *clipModel = NULL;
|
|
|
|
// check if a clipmodel key/value pair is set
|
|
if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) {
|
|
if ( idClipModel::CheckModel( temp ) ) {
|
|
clipModel = new idClipModel( temp );
|
|
}
|
|
}
|
|
|
|
if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) {
|
|
|
|
// check if mins/maxs or size key/value pairs are set
|
|
if ( !clipModel ) {
|
|
idVec3 size;
|
|
idBounds bounds;
|
|
bool setClipModel = false;
|
|
|
|
if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) &&
|
|
spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) {
|
|
setClipModel = true;
|
|
if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) {
|
|
gameLocal.Error( "Invalid bounds '%s'-'%s' on entity '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() );
|
|
}
|
|
} else if ( spawnArgs.GetVector( "size", NULL, size ) ) {
|
|
if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) {
|
|
gameLocal.Error( "Invalid size '%s' on entity '%s'", size.ToString(), name.c_str() );
|
|
}
|
|
bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f );
|
|
bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z );
|
|
setClipModel = true;
|
|
}
|
|
|
|
if ( setClipModel ) {
|
|
int numSides;
|
|
idTraceModel trm;
|
|
|
|
if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) {
|
|
trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides );
|
|
} else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) {
|
|
trm.SetupCone( bounds, numSides < 3 ? 3 : numSides );
|
|
} else {
|
|
trm.SetupBox( bounds );
|
|
}
|
|
clipModel = new idClipModel( trm );
|
|
}
|
|
}
|
|
|
|
// check if the visual model can be used as collision model
|
|
if ( !clipModel ) {
|
|
temp = spawnArgs.GetString( "model" );
|
|
if ( ( temp != NULL ) && ( *temp != 0 ) ) {
|
|
if ( idClipModel::CheckModel( temp ) ) {
|
|
clipModel = new idClipModel( temp );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
defaultPhysicsObj.SetSelf( this );
|
|
defaultPhysicsObj.SetClipModel( clipModel, 1.0f );
|
|
defaultPhysicsObj.SetOrigin( origin );
|
|
defaultPhysicsObj.SetAxis( axis );
|
|
|
|
physics = &defaultPhysicsObj;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetPhysics
|
|
================
|
|
*/
|
|
void idEntity::SetPhysics( idPhysics *phys ) {
|
|
// clear any contacts the current physics object has
|
|
if ( physics ) {
|
|
physics->ClearContacts();
|
|
}
|
|
// set new physics object or set the default physics if NULL
|
|
if ( phys != NULL ) {
|
|
defaultPhysicsObj.SetClipModel( NULL, 1.0f );
|
|
physics = phys;
|
|
physics->Activate();
|
|
} else {
|
|
physics = &defaultPhysicsObj;
|
|
}
|
|
physics->UpdateTime( gameLocal.time );
|
|
physics->SetMaster( bindMaster, fl.bindOrientated );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::RestorePhysics
|
|
================
|
|
*/
|
|
void idEntity::RestorePhysics( idPhysics *phys ) {
|
|
assert( phys != NULL );
|
|
// restore physics pointer
|
|
physics = phys;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetPhysics
|
|
================
|
|
*/
|
|
idPhysics *idEntity::GetPhysics( void ) const {
|
|
return physics;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::RunPhysics
|
|
================
|
|
*/
|
|
bool idEntity::RunPhysics( void ) {
|
|
int i, reachedTime, startTime, endTime;
|
|
idEntity * part, *blockedPart, *blockingEntity;
|
|
bool moved;
|
|
|
|
// don't run physics if not enabled
|
|
if ( !( thinkFlags & TH_PHYSICS ) ) {
|
|
// however do update any animation controllers
|
|
if ( UpdateAnimationControllers() ) {
|
|
BecomeActive( TH_ANIMATE );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// if this entity is a team slave don't do anything because the team master will handle everything
|
|
if ( teamMaster && teamMaster != this ) {
|
|
return false;
|
|
}
|
|
|
|
startTime = gameLocal.previousTime;
|
|
endTime = gameLocal.time;
|
|
|
|
gameLocal.push.InitSavingPushedEntityPositions();
|
|
blockedPart = NULL;
|
|
|
|
// save the physics state of the whole team and disable the team for collision detection
|
|
for ( part = this; part != NULL; part = part->teamChain ) {
|
|
if ( part->physics ) {
|
|
if ( !part->fl.solidForTeam ) {
|
|
part->physics->DisableClip();
|
|
}
|
|
part->physics->SaveState();
|
|
}
|
|
}
|
|
|
|
// move the whole team
|
|
for ( part = this; part != NULL; part = part->teamChain ) {
|
|
|
|
if ( part->physics ) {
|
|
|
|
// run physics
|
|
moved = part->physics->Evaluate( endTime - startTime, endTime );
|
|
|
|
// check if the object is blocked
|
|
blockingEntity = part->physics->GetBlockingEntity();
|
|
if ( blockingEntity ) {
|
|
blockedPart = part;
|
|
break;
|
|
}
|
|
|
|
// if moved or forced to update the visual position and orientation from the physics
|
|
if ( moved || part->fl.forcePhysicsUpdate ) {
|
|
part->UpdateFromPhysics( false );
|
|
}
|
|
|
|
// update any animation controllers here so an entity bound
|
|
// to a joint of this entity gets the correct position
|
|
if ( part->UpdateAnimationControllers() ) {
|
|
part->BecomeActive( TH_ANIMATE );
|
|
}
|
|
}
|
|
}
|
|
|
|
// enable the whole team for collision detection
|
|
for ( part = this; part != NULL; part = part->teamChain ) {
|
|
if ( part->physics ) {
|
|
if ( !part->fl.solidForTeam ) {
|
|
part->physics->EnableClip();
|
|
}
|
|
}
|
|
}
|
|
|
|
// if one of the team entities is a pusher and blocked
|
|
if ( blockedPart ) {
|
|
// move the parts back to the previous position
|
|
for ( part = this; part != blockedPart; part = part->teamChain ) {
|
|
|
|
if ( part->physics ) {
|
|
|
|
// restore the physics state
|
|
part->physics->RestoreState();
|
|
|
|
// move back the visual position and orientation
|
|
part->UpdateFromPhysics( true );
|
|
}
|
|
}
|
|
for ( part = this; part != NULL; part = part->teamChain ) {
|
|
if ( part->physics ) {
|
|
// update the physics time without moving
|
|
part->physics->UpdateTime( endTime );
|
|
}
|
|
}
|
|
|
|
// restore the positions of any pushed entities
|
|
gameLocal.push.RestorePushedEntityPositions();
|
|
|
|
if ( gameLocal.isClient ) {
|
|
return false;
|
|
}
|
|
|
|
// if the master pusher has a "blocked" function, call it
|
|
Signal( SIG_BLOCKED );
|
|
ProcessEvent( &EV_TeamBlocked, blockedPart, blockingEntity );
|
|
// call the blocked function on the blocked part
|
|
blockedPart->ProcessEvent( &EV_PartBlocked, blockingEntity );
|
|
return false;
|
|
}
|
|
|
|
// set pushed
|
|
for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) {
|
|
idEntity *ent = gameLocal.push.GetPushedEntity( i );
|
|
ent->physics->SetPushed( endTime - startTime );
|
|
}
|
|
|
|
if ( gameLocal.isClient ) {
|
|
return true;
|
|
}
|
|
|
|
// post reached event if the current time is at or past the end point of the motion
|
|
for ( part = this; part != NULL; part = part->teamChain ) {
|
|
|
|
if ( part->physics ) {
|
|
|
|
reachedTime = part->physics->GetLinearEndTime();
|
|
if ( startTime < reachedTime && endTime >= reachedTime ) {
|
|
part->ProcessEvent( &EV_ReachedPos );
|
|
}
|
|
reachedTime = part->physics->GetAngularEndTime();
|
|
if ( startTime < reachedTime && endTime >= reachedTime ) {
|
|
part->ProcessEvent( &EV_ReachedAng );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::UpdateFromPhysics
|
|
================
|
|
*/
|
|
void idEntity::UpdateFromPhysics( bool moveBack ) {
|
|
|
|
if ( IsType( idActor::Type ) ) {
|
|
idActor *actor = static_cast<idActor *>( this );
|
|
|
|
// set master delta angles for actors
|
|
if ( GetBindMaster() ) {
|
|
idAngles delta = actor->GetDeltaViewAngles();
|
|
if ( moveBack ) {
|
|
delta.yaw -= static_cast<idPhysics_Actor *>(physics)->GetMasterDeltaYaw();
|
|
} else {
|
|
delta.yaw += static_cast<idPhysics_Actor *>(physics)->GetMasterDeltaYaw();
|
|
}
|
|
actor->SetDeltaViewAngles( delta );
|
|
}
|
|
}
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetOrigin
|
|
================
|
|
*/
|
|
void idEntity::SetOrigin( const idVec3 &org ) {
|
|
|
|
GetPhysics()->SetOrigin( org );
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetAxis
|
|
================
|
|
*/
|
|
void idEntity::SetAxis( const idMat3 &axis ) {
|
|
|
|
if ( GetPhysics()->IsType( idPhysics_Actor::Type ) ) {
|
|
static_cast<idActor *>(this)->viewAxis = axis;
|
|
} else {
|
|
GetPhysics()->SetAxis( axis );
|
|
}
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetAngles
|
|
================
|
|
*/
|
|
void idEntity::SetAngles( const idAngles &ang ) {
|
|
SetAxis( ang.ToMat3() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetFloorPos
|
|
================
|
|
*/
|
|
bool idEntity::GetFloorPos( float max_dist, idVec3 &floorpos ) const {
|
|
trace_t result;
|
|
|
|
if ( !GetPhysics()->HasGroundContacts() ) {
|
|
GetPhysics()->ClipTranslation( result, GetPhysics()->GetGravityNormal() * max_dist, NULL );
|
|
if ( result.fraction < 1.0f ) {
|
|
floorpos = result.endpos;
|
|
return true;
|
|
} else {
|
|
floorpos = GetPhysics()->GetOrigin();
|
|
return false;
|
|
}
|
|
} else {
|
|
floorpos = GetPhysics()->GetOrigin();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetPhysicsToVisualTransform
|
|
================
|
|
*/
|
|
bool idEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetPhysicsToSoundTransform
|
|
================
|
|
*/
|
|
bool idEntity::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) {
|
|
// by default play the sound at the center of the bounding box of the first clip model
|
|
if ( GetPhysics()->GetNumClipModels() > 0 ) {
|
|
origin = GetPhysics()->GetBounds().GetCenter();
|
|
axis.Identity();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Collide
|
|
================
|
|
*/
|
|
bool idEntity::Collide( const trace_t &collision, const idVec3 &velocity ) {
|
|
// this entity collides with collision.c.entityNum
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetImpactInfo
|
|
================
|
|
*/
|
|
void idEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) {
|
|
GetPhysics()->GetImpactInfo( id, point, info );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ApplyImpulse
|
|
================
|
|
*/
|
|
void idEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
|
|
GetPhysics()->ApplyImpulse( id, point, impulse );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::AddForce
|
|
================
|
|
*/
|
|
void idEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) {
|
|
GetPhysics()->AddForce( id, point, force );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ActivatePhysics
|
|
================
|
|
*/
|
|
void idEntity::ActivatePhysics( idEntity *ent ) {
|
|
GetPhysics()->Activate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::IsAtRest
|
|
================
|
|
*/
|
|
bool idEntity::IsAtRest( void ) const {
|
|
return GetPhysics()->IsAtRest();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetRestStartTime
|
|
================
|
|
*/
|
|
int idEntity::GetRestStartTime( void ) const {
|
|
return GetPhysics()->GetRestStartTime();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::AddContactEntity
|
|
================
|
|
*/
|
|
void idEntity::AddContactEntity( idEntity *ent ) {
|
|
GetPhysics()->AddContactEntity( ent );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::RemoveContactEntity
|
|
================
|
|
*/
|
|
void idEntity::RemoveContactEntity( idEntity *ent ) {
|
|
GetPhysics()->RemoveContactEntity( ent );
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
Damage
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
============
|
|
idEntity::CanDamage
|
|
|
|
Returns true if the inflictor can directly damage the target. Used for
|
|
explosions and melee attacks.
|
|
============
|
|
*/
|
|
bool idEntity::CanDamage( const idVec3 &origin, idVec3 &damagePoint ) const {
|
|
idVec3 dest;
|
|
trace_t tr;
|
|
idVec3 midpoint;
|
|
|
|
// use the midpoint of the bounds instead of the origin, because
|
|
// bmodels may have their origin at 0,0,0
|
|
midpoint = ( GetPhysics()->GetAbsBounds()[0] + GetPhysics()->GetAbsBounds()[1] ) * 0.5;
|
|
|
|
dest = midpoint;
|
|
gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL );
|
|
if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
damagePoint = tr.endpos;
|
|
return true;
|
|
}
|
|
|
|
// this should probably check in the plane of projection, rather than in world coordinate
|
|
dest = midpoint;
|
|
dest[0] += 15.0;
|
|
dest[1] += 15.0;
|
|
gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL );
|
|
if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
damagePoint = tr.endpos;
|
|
return true;
|
|
}
|
|
|
|
dest = midpoint;
|
|
dest[0] += 15.0;
|
|
dest[1] -= 15.0;
|
|
gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL );
|
|
if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
damagePoint = tr.endpos;
|
|
return true;
|
|
}
|
|
|
|
dest = midpoint;
|
|
dest[0] -= 15.0;
|
|
dest[1] += 15.0;
|
|
gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL );
|
|
if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
damagePoint = tr.endpos;
|
|
return true;
|
|
}
|
|
|
|
dest = midpoint;
|
|
dest[0] -= 15.0;
|
|
dest[1] -= 15.0;
|
|
gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL );
|
|
if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
damagePoint = tr.endpos;
|
|
return true;
|
|
}
|
|
|
|
dest = midpoint;
|
|
dest[2] += 15.0;
|
|
gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL );
|
|
if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
damagePoint = tr.endpos;
|
|
return true;
|
|
}
|
|
|
|
dest = midpoint;
|
|
dest[2] -= 15.0;
|
|
gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL );
|
|
if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
damagePoint = tr.endpos;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::DamageFeedback
|
|
|
|
callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller.
|
|
================
|
|
*/
|
|
void idEntity::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
|
|
// implemented in subclasses
|
|
}
|
|
|
|
/*
|
|
============
|
|
Damage
|
|
|
|
this entity that is being damaged
|
|
inflictor entity that is causing the damage
|
|
attacker entity that caused the inflictor to damage targ
|
|
example: this=monster, inflictor=rocket, attacker=player
|
|
|
|
dir direction of the attack for knockback in global space
|
|
point point at which the damage is being inflicted, used for headshots
|
|
damage amount of damage being inflicted
|
|
|
|
inflictor, attacker, dir, and point can be NULL for environmental effects
|
|
|
|
============
|
|
*/
|
|
void idEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
|
|
const char *damageDefName, const float damageScale, const int location ) {
|
|
if ( !fl.takedamage ) {
|
|
return;
|
|
}
|
|
|
|
if ( !inflictor ) {
|
|
inflictor = gameLocal.world;
|
|
}
|
|
|
|
if ( !attacker ) {
|
|
attacker = gameLocal.world;
|
|
}
|
|
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( !damageDef ) {
|
|
gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName );
|
|
}
|
|
|
|
int damage = damageDef->GetInt( "damage" );
|
|
|
|
// inform the attacker that they hit someone
|
|
attacker->DamageFeedback( this, inflictor, damage );
|
|
if ( damage ) {
|
|
// do the damage
|
|
health -= damage;
|
|
if ( health <= 0 ) {
|
|
if ( health < -999 ) {
|
|
health = -999;
|
|
}
|
|
|
|
Killed( inflictor, attacker, damage, dir, location );
|
|
} else {
|
|
Pain( inflictor, attacker, damage, dir, location );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::AddDamageEffect
|
|
================
|
|
*/
|
|
void idEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) {
|
|
const char *sound, *decal, *key;
|
|
|
|
const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false );
|
|
if ( def == NULL ) {
|
|
return;
|
|
}
|
|
|
|
const char *materialType = gameLocal.sufaceTypeNames[ collision.c.material->GetSurfaceType() ];
|
|
|
|
// start impact sound based on material type
|
|
key = va( "snd_%s", materialType );
|
|
sound = spawnArgs.GetString( key );
|
|
if ( *sound == '\0' ) {
|
|
sound = def->dict.GetString( key );
|
|
}
|
|
if ( *sound != '\0' ) {
|
|
StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
|
|
}
|
|
|
|
if ( g_decals.GetBool() ) {
|
|
// place a wound overlay on the model
|
|
key = va( "mtr_wound_%s", materialType );
|
|
decal = spawnArgs.RandomPrefix( key, gameLocal.random );
|
|
if ( *decal == '\0' ) {
|
|
decal = def->dict.RandomPrefix( key, gameLocal.random );
|
|
}
|
|
if ( *decal != '\0' ) {
|
|
idVec3 dir = velocity;
|
|
dir.Normalize();
|
|
ProjectOverlay( collision.c.point, dir, 20.0f, decal );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idEntity::Pain
|
|
|
|
Called whenever an entity recieves damage. Returns whether the entity responds to the pain.
|
|
This is a virtual function that subclasses are expected to implement.
|
|
============
|
|
*/
|
|
bool idEntity::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idEntity::Killed
|
|
|
|
Called whenever an entity's health is reduced to 0 or less.
|
|
This is a virtual function that subclasses are expected to implement.
|
|
============
|
|
*/
|
|
void idEntity::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
Script functions
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::ShouldConstructScriptObjectAtSpawn
|
|
|
|
Called during idEntity::Spawn to see if it should construct the script object or not.
|
|
Overridden by subclasses that need to spawn the script object themselves.
|
|
================
|
|
*/
|
|
bool idEntity::ShouldConstructScriptObjectAtSpawn( void ) const {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ConstructScriptObject
|
|
|
|
Called during idEntity::Spawn. Calls the constructor on the script object.
|
|
Can be overridden by subclasses when a thread doesn't need to be allocated.
|
|
================
|
|
*/
|
|
idThread *idEntity::ConstructScriptObject( void ) {
|
|
idThread *thread;
|
|
const function_t *constructor;
|
|
|
|
// init the script object's data
|
|
scriptObject.ClearObject();
|
|
|
|
// call script object's constructor
|
|
constructor = scriptObject.GetConstructor();
|
|
if ( constructor ) {
|
|
// start a thread that will initialize after Spawn is done being called
|
|
thread = new idThread();
|
|
thread->SetThreadName( name.c_str() );
|
|
thread->CallFunction( this, constructor, true );
|
|
thread->DelayedStart( 0 );
|
|
} else {
|
|
thread = NULL;
|
|
}
|
|
|
|
// clear out the object's memory
|
|
scriptObject.ClearObject();
|
|
|
|
return thread;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::DeconstructScriptObject
|
|
|
|
Called during idEntity::~idEntity. Calls the destructor on the script object.
|
|
Can be overridden by subclasses when a thread doesn't need to be allocated.
|
|
Not called during idGameLocal::MapShutdown.
|
|
================
|
|
*/
|
|
void idEntity::DeconstructScriptObject( void ) {
|
|
idThread *thread;
|
|
const function_t *destructor;
|
|
|
|
// don't bother calling the script object's destructor on map shutdown
|
|
if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) {
|
|
return;
|
|
}
|
|
|
|
// call script object's destructor
|
|
destructor = scriptObject.GetDestructor();
|
|
if ( destructor ) {
|
|
// start a thread that will run immediately and be destroyed
|
|
thread = new idThread();
|
|
thread->SetThreadName( name.c_str() );
|
|
thread->CallFunction( this, destructor, true );
|
|
thread->Execute();
|
|
delete thread;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::HasSignal
|
|
================
|
|
*/
|
|
bool idEntity::HasSignal( signalNum_t signalnum ) const {
|
|
if ( !signals ) {
|
|
return false;
|
|
}
|
|
assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) );
|
|
return ( signals->signal[ signalnum ].Num() > 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SetSignal
|
|
================
|
|
*/
|
|
void idEntity::SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ) {
|
|
int i;
|
|
int num;
|
|
signal_t sig;
|
|
int threadnum;
|
|
|
|
assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) );
|
|
|
|
if ( !signals ) {
|
|
signals = new signalList_t;
|
|
}
|
|
|
|
assert( thread );
|
|
threadnum = thread->GetThreadNum();
|
|
|
|
num = signals->signal[ signalnum ].Num();
|
|
for( i = 0; i < num; i++ ) {
|
|
if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) {
|
|
signals->signal[ signalnum ][ i ].function = function;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( num >= MAX_SIGNAL_THREADS ) {
|
|
thread->Error( "Exceeded maximum number of signals per object" );
|
|
}
|
|
|
|
sig.threadnum = threadnum;
|
|
sig.function = function;
|
|
signals->signal[ signalnum ].Append( sig );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ClearSignal
|
|
================
|
|
*/
|
|
void idEntity::ClearSignal( idThread *thread, signalNum_t signalnum ) {
|
|
assert( thread );
|
|
if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) {
|
|
gameLocal.Error( "Signal out of range" );
|
|
}
|
|
|
|
if ( !signals ) {
|
|
return;
|
|
}
|
|
|
|
signals->signal[ signalnum ].Clear();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ClearSignalThread
|
|
================
|
|
*/
|
|
void idEntity::ClearSignalThread( signalNum_t signalnum, idThread *thread ) {
|
|
int i;
|
|
int num;
|
|
int threadnum;
|
|
|
|
assert( thread );
|
|
|
|
if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) {
|
|
gameLocal.Error( "Signal out of range" );
|
|
}
|
|
|
|
if ( !signals ) {
|
|
return;
|
|
}
|
|
|
|
threadnum = thread->GetThreadNum();
|
|
|
|
num = signals->signal[ signalnum ].Num();
|
|
for( i = 0; i < num; i++ ) {
|
|
if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) {
|
|
signals->signal[ signalnum ].RemoveIndex( i );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Signal
|
|
================
|
|
*/
|
|
void idEntity::Signal( signalNum_t signalnum ) {
|
|
int i;
|
|
int num;
|
|
signal_t sigs[ MAX_SIGNAL_THREADS ];
|
|
idThread *thread;
|
|
|
|
assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) );
|
|
|
|
if ( !signals ) {
|
|
return;
|
|
}
|
|
|
|
// we copy the signal list since each thread has the potential
|
|
// to end any of the threads in the list. By copying the list
|
|
// we don't have to worry about the list changing as we're
|
|
// processing it.
|
|
num = signals->signal[ signalnum ].Num();
|
|
for( i = 0; i < num; i++ ) {
|
|
sigs[ i ] = signals->signal[ signalnum ][ i ];
|
|
}
|
|
|
|
// clear out the signal list so that we don't get into an infinite loop
|
|
signals->signal[ signalnum ].Clear();
|
|
|
|
for( i = 0; i < num; i++ ) {
|
|
thread = idThread::GetThread( sigs[ i ].threadnum );
|
|
if ( thread ) {
|
|
thread->CallFunction( this, sigs[ i ].function, true );
|
|
thread->Execute();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::SignalEvent
|
|
================
|
|
*/
|
|
void idEntity::SignalEvent( idThread *thread, signalNum_t signalnum ) {
|
|
if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) {
|
|
gameLocal.Error( "Signal out of range" );
|
|
}
|
|
|
|
if ( !signals ) {
|
|
return;
|
|
}
|
|
|
|
Signal( signalnum );
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Guis.
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
/*
|
|
================
|
|
idEntity::TriggerGuis
|
|
================
|
|
*/
|
|
void idEntity::TriggerGuis( void ) {
|
|
int i;
|
|
for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) {
|
|
if ( renderEntity.gui[ i ] ) {
|
|
renderEntity.gui[ i ]->Trigger( gameLocal.time );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::HandleGuiCommands
|
|
================
|
|
*/
|
|
bool idEntity::HandleGuiCommands( idEntity *entityGui, const char *cmds ) {
|
|
idEntity *targetEnt;
|
|
bool ret = false;
|
|
if ( entityGui && cmds && *cmds ) {
|
|
idLexer src;
|
|
idToken token, token2, token3, token4;
|
|
src.LoadMemory( cmds, strlen( cmds ), "guiCommands" );
|
|
while( 1 ) {
|
|
|
|
if ( !src.ReadToken( &token ) ) {
|
|
return ret;
|
|
}
|
|
|
|
if ( token == ";" ) {
|
|
continue;
|
|
}
|
|
|
|
if ( token.Icmp( "activate" ) == 0 ) {
|
|
bool targets = true;
|
|
if ( src.ReadToken( &token2 ) ) {
|
|
if ( token2 == ";" ) {
|
|
src.UnreadToken( &token2 );
|
|
} else {
|
|
targets = false;
|
|
}
|
|
}
|
|
|
|
if ( targets ) {
|
|
entityGui->ActivateTargets( this );
|
|
} else {
|
|
idEntity *ent = gameLocal.FindEntity( token2 );
|
|
if ( ent ) {
|
|
ent->Signal( SIG_TRIGGER );
|
|
ent->PostEventMS( &EV_Activate, 0, this );
|
|
}
|
|
}
|
|
|
|
entityGui->renderEntity.shaderParms[ SHADERPARM_MODE ] = 1.0f;
|
|
continue;
|
|
}
|
|
|
|
|
|
if ( token.Icmp( "runScript" ) == 0 ) {
|
|
if ( src.ReadToken( &token2 ) ) {
|
|
while( src.CheckTokenString( "::" ) ) {
|
|
idToken token3;
|
|
if ( !src.ReadToken( &token3 ) ) {
|
|
gameLocal.Error( "Expecting function name following '::' in gui for entity '%s'", entityGui->name.c_str() );
|
|
}
|
|
token2 += "::" + token3;
|
|
}
|
|
const function_t *func = gameLocal.program.FindFunction( token2 );
|
|
if ( !func ) {
|
|
gameLocal.Error( "Can't find function '%s' for gui in entity '%s'", token2.c_str(), entityGui->name.c_str() );
|
|
} else {
|
|
idThread *thread = new idThread( func );
|
|
thread->DelayedStart( 0 );
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( token.Icmp("play") == 0 ) {
|
|
if ( src.ReadToken( &token2 ) ) {
|
|
const idSoundShader *shader = declManager->FindSound(token2);
|
|
entityGui->StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( token.Icmp( "setkeyval" ) == 0 ) {
|
|
if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) && src.ReadToken( &token4 ) ) {
|
|
idEntity *ent = gameLocal.FindEntity( token2 );
|
|
if ( ent ) {
|
|
ent->spawnArgs.Set( token3, token4 );
|
|
ent->UpdateChangeableSpawnArgs( NULL );
|
|
ent->UpdateVisuals();
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( token.Icmp( "setshaderparm" ) == 0 ) {
|
|
if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) ) {
|
|
entityGui->SetShaderParm( atoi( token2 ), atof( token3 ) );
|
|
entityGui->UpdateVisuals();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( token.Icmp("close") == 0 ) {
|
|
ret = true;
|
|
continue;
|
|
}
|
|
|
|
if ( !token.Icmp( "turkeyscore" ) ) {
|
|
if ( src.ReadToken( &token2 ) && entityGui->renderEntity.gui[0] ) {
|
|
int score = entityGui->renderEntity.gui[0]->State().GetInt( "score" );
|
|
score += atoi( token2 );
|
|
entityGui->renderEntity.gui[0]->SetStateInt( "score", score );
|
|
if ( gameLocal.GetLocalPlayer() && score >= 25000 && !gameLocal.GetLocalPlayer()->inventory.turkeyScore ) {
|
|
gameLocal.GetLocalPlayer()->GiveEmail( "highScore" );
|
|
gameLocal.GetLocalPlayer()->inventory.turkeyScore = true;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
|
|
// handy for debugging GUI stuff
|
|
if ( !token.Icmp( "print" ) ) {
|
|
idStr msg;
|
|
while ( src.ReadToken( &token2 ) ) {
|
|
if ( token2 == ";" ) {
|
|
src.UnreadToken( &token2 );
|
|
break;
|
|
}
|
|
msg += token2.c_str();
|
|
}
|
|
common->Printf( "ent gui 0x%x '%s': %s\n", entityNumber, name.c_str(), msg.c_str() );
|
|
continue;
|
|
}
|
|
|
|
// if we get to this point we don't know how to handle it
|
|
src.UnreadToken(&token);
|
|
if ( !HandleSingleGuiCommand( entityGui, &src ) ) {
|
|
// not handled there see if entity or any of its targets can handle it
|
|
// this will only work for one target atm
|
|
if ( entityGui->HandleSingleGuiCommand( entityGui, &src ) ) {
|
|
continue;
|
|
}
|
|
|
|
int c = entityGui->targets.Num();
|
|
int i;
|
|
for ( i = 0; i < c; i++) {
|
|
targetEnt = entityGui->targets[ i ].GetEntity();
|
|
if ( targetEnt && targetEnt->HandleSingleGuiCommand( entityGui, &src ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i == c ) {
|
|
// not handled
|
|
common->DPrintf( "idEntity::HandleGuiCommands: '%s' not handled\n", token.c_str() );
|
|
src.ReadToken( &token );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::HandleSingleGuiCommand
|
|
================
|
|
*/
|
|
bool idEntity::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) {
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Targets
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
===============
|
|
idEntity::FindTargets
|
|
|
|
We have to wait until all entities are spawned
|
|
Used to build lists of targets after the entity is spawned. Since not all entities
|
|
have been spawned when the entity is created at map load time, we have to wait
|
|
===============
|
|
*/
|
|
void idEntity::FindTargets( void ) {
|
|
int i;
|
|
|
|
// targets can be a list of multiple names
|
|
gameLocal.GetTargets( spawnArgs, targets, "target" );
|
|
|
|
// ensure that we don't target ourselves since that could cause an infinite loop when activating entities
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
if ( targets[ i ].GetEntity() == this ) {
|
|
gameLocal.Error( "Entity '%s' is targeting itself", name.c_str() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::RemoveNullTargets
|
|
================
|
|
*/
|
|
void idEntity::RemoveNullTargets( void ) {
|
|
int i;
|
|
|
|
for( i = targets.Num() - 1; i >= 0; i-- ) {
|
|
if ( !targets[ i ].GetEntity() ) {
|
|
targets.RemoveIndex( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
idEntity::ActivateTargets
|
|
|
|
"activator" should be set to the entity that initiated the firing.
|
|
==============================
|
|
*/
|
|
void idEntity::ActivateTargets( idEntity *activator ) const {
|
|
idEntity *ent;
|
|
int i, j;
|
|
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
ent = targets[ i ].GetEntity();
|
|
if ( !ent ) {
|
|
continue;
|
|
}
|
|
if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) {
|
|
ent->Signal( SIG_TRIGGER );
|
|
ent->ProcessEvent( &EV_Activate, activator );
|
|
}
|
|
for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) {
|
|
if ( ent->renderEntity.gui[ j ] ) {
|
|
ent->renderEntity.gui[ j ]->Trigger( gameLocal.time );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Misc.
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::Teleport
|
|
================
|
|
*/
|
|
void idEntity::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) {
|
|
GetPhysics()->SetOrigin( origin );
|
|
GetPhysics()->SetAxis( angles.ToMat3() );
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
============
|
|
idEntity::TouchTriggers
|
|
|
|
Activate all trigger entities touched at the current position.
|
|
============
|
|
*/
|
|
bool idEntity::TouchTriggers( void ) const {
|
|
int i, numClipModels, numEntities;
|
|
idClipModel * cm;
|
|
idClipModel * clipModels[ MAX_GENTITIES ];
|
|
idEntity * ent;
|
|
trace_t trace;
|
|
|
|
memset( &trace, 0, sizeof( trace ) );
|
|
trace.endpos = GetPhysics()->GetOrigin();
|
|
trace.endAxis = GetPhysics()->GetAxis();
|
|
|
|
numClipModels = gameLocal.clip.ClipModelsTouchingBounds( GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, clipModels, MAX_GENTITIES );
|
|
numEntities = 0;
|
|
|
|
for ( i = 0; i < numClipModels; i++ ) {
|
|
cm = clipModels[ i ];
|
|
|
|
// don't touch it if we're the owner
|
|
if ( cm->GetOwner() == this ) {
|
|
continue;
|
|
}
|
|
|
|
ent = cm->GetEntity();
|
|
|
|
if ( !ent->RespondsTo( EV_Touch ) && !ent->HasSignal( SIG_TOUCH ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !GetPhysics()->ClipContents( cm ) ) {
|
|
continue;
|
|
}
|
|
|
|
numEntities++;
|
|
|
|
trace.c.contents = cm->GetContents();
|
|
trace.c.entityNum = cm->GetEntity()->entityNumber;
|
|
trace.c.id = cm->GetId();
|
|
|
|
ent->Signal( SIG_TOUCH );
|
|
ent->ProcessEvent( &EV_Touch, this, &trace );
|
|
|
|
if ( !gameLocal.entities[ entityNumber ] ) {
|
|
gameLocal.Printf( "entity was removed while touching triggers\n" );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return ( numEntities != 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::GetSpline
|
|
================
|
|
*/
|
|
idCurve_Spline<idVec3> *idEntity::GetSpline( void ) const {
|
|
int i, numPoints, t;
|
|
const idKeyValue *kv;
|
|
idLexer lex;
|
|
idVec3 v;
|
|
idCurve_Spline<idVec3> *spline;
|
|
const char *curveTag = "curve_";
|
|
|
|
kv = spawnArgs.MatchPrefix( curveTag );
|
|
if ( !kv ) {
|
|
return NULL;
|
|
}
|
|
|
|
idStr str = kv->GetKey().Right( kv->GetKey().Length() - strlen( curveTag ) );
|
|
if ( str.Icmp( "CatmullRomSpline" ) == 0 ) {
|
|
spline = new idCurve_CatmullRomSpline<idVec3>();
|
|
} else if ( str.Icmp( "nubs" ) == 0 ) {
|
|
spline = new idCurve_NonUniformBSpline<idVec3>();
|
|
} else if ( str.Icmp( "nurbs" ) == 0 ) {
|
|
spline = new idCurve_NURBS<idVec3>();
|
|
} else {
|
|
spline = new idCurve_BSpline<idVec3>();
|
|
}
|
|
|
|
spline->SetBoundaryType( idCurve_Spline<idVec3>::BT_CLAMPED );
|
|
|
|
lex.LoadMemory( kv->GetValue(), kv->GetValue().Length(), curveTag );
|
|
numPoints = lex.ParseInt();
|
|
lex.ExpectTokenString( "(" );
|
|
for ( t = i = 0; i < numPoints; i++, t += 100 ) {
|
|
v.x = lex.ParseFloat();
|
|
v.y = lex.ParseFloat();
|
|
v.z = lex.ParseFloat();
|
|
spline->AddValue( t, v );
|
|
}
|
|
lex.ExpectTokenString( ")" );
|
|
|
|
return spline;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idEntity::ShowEditingDialog
|
|
===============
|
|
*/
|
|
void idEntity::ShowEditingDialog( void ) {
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Events
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetName
|
|
================
|
|
*/
|
|
void idEntity::Event_GetName( void ) {
|
|
idThread::ReturnString( name.c_str() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetName
|
|
================
|
|
*/
|
|
void idEntity::Event_SetName( const char *newname ) {
|
|
SetName( newname );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idEntity::Event_FindTargets
|
|
===============
|
|
*/
|
|
void idEntity::Event_FindTargets( void ) {
|
|
FindTargets();
|
|
}
|
|
|
|
/*
|
|
============
|
|
idEntity::Event_ActivateTargets
|
|
|
|
Activates any entities targeted by this entity. Mainly used as an
|
|
event to delay activating targets.
|
|
============
|
|
*/
|
|
void idEntity::Event_ActivateTargets( idEntity *activator ) {
|
|
ActivateTargets( activator );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_NumTargets
|
|
================
|
|
*/
|
|
void idEntity::Event_NumTargets( void ) {
|
|
idThread::ReturnFloat( targets.Num() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetTarget
|
|
================
|
|
*/
|
|
void idEntity::Event_GetTarget( float index ) {
|
|
int i;
|
|
|
|
i = ( int )index;
|
|
if ( ( i < 0 ) || i >= targets.Num() ) {
|
|
idThread::ReturnEntity( NULL );
|
|
} else {
|
|
idThread::ReturnEntity( targets[ i ].GetEntity() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_RandomTarget
|
|
================
|
|
*/
|
|
void idEntity::Event_RandomTarget( const char *ignore ) {
|
|
int num;
|
|
idEntity *ent;
|
|
int i;
|
|
int ignoreNum;
|
|
|
|
RemoveNullTargets();
|
|
if ( !targets.Num() ) {
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
ignoreNum = -1;
|
|
if ( ignore && ( ignore[ 0 ] != 0 ) && ( targets.Num() > 1 ) ) {
|
|
for( i = 0; i < targets.Num(); i++ ) {
|
|
ent = targets[ i ].GetEntity();
|
|
if ( ent && ( ent->name == ignore ) ) {
|
|
ignoreNum = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ignoreNum >= 0 ) {
|
|
num = gameLocal.random.RandomInt( targets.Num() - 1 );
|
|
if ( num >= ignoreNum ) {
|
|
num++;
|
|
}
|
|
} else {
|
|
num = gameLocal.random.RandomInt( targets.Num() );
|
|
}
|
|
|
|
ent = targets[ num ].GetEntity();
|
|
idThread::ReturnEntity( ent );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_BindToJoint
|
|
================
|
|
*/
|
|
void idEntity::Event_BindToJoint( idEntity *master, const char *jointname, float orientated ) {
|
|
BindToJoint( master, jointname, ( orientated != 0.0f ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_RemoveBinds
|
|
================
|
|
*/
|
|
void idEntity::Event_RemoveBinds( void ) {
|
|
RemoveBinds();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_Bind
|
|
================
|
|
*/
|
|
void idEntity::Event_Bind( idEntity *master ) {
|
|
Bind( master, true );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_BindPosition
|
|
================
|
|
*/
|
|
void idEntity::Event_BindPosition( idEntity *master ) {
|
|
Bind( master, false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_Unbind
|
|
================
|
|
*/
|
|
void idEntity::Event_Unbind( void ) {
|
|
Unbind();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SpawnBind
|
|
================
|
|
*/
|
|
void idEntity::Event_SpawnBind( void ) {
|
|
idEntity *parent;
|
|
const char *bind, *joint, *bindanim;
|
|
jointHandle_t bindJoint;
|
|
bool bindOrientated;
|
|
int id;
|
|
const idAnim *anim;
|
|
int animNum;
|
|
idAnimator *parentAnimator;
|
|
|
|
if ( spawnArgs.GetString( "bind", "", &bind ) ) {
|
|
if ( idStr::Icmp( bind, "worldspawn" ) == 0 ) {
|
|
//FIXME: Completely unneccessary since the worldspawn is called "world"
|
|
parent = gameLocal.world;
|
|
} else {
|
|
parent = gameLocal.FindEntity( bind );
|
|
}
|
|
bindOrientated = spawnArgs.GetBool( "bindOrientated", "1" );
|
|
if ( parent ) {
|
|
// bind to a joint of the skeletal model of the parent
|
|
if ( spawnArgs.GetString( "bindToJoint", "", &joint ) && *joint ) {
|
|
parentAnimator = parent->GetAnimator();
|
|
if ( !parentAnimator ) {
|
|
gameLocal.Error( "Cannot bind to joint '%s' on '%s'. Entity does not support skeletal models.", joint, name.c_str() );
|
|
}
|
|
bindJoint = parentAnimator->GetJointHandle( joint );
|
|
if ( bindJoint == INVALID_JOINT ) {
|
|
gameLocal.Error( "Joint '%s' not found for bind on '%s'", joint, name.c_str() );
|
|
}
|
|
|
|
// bind it relative to a specific anim
|
|
if ( ( parent->spawnArgs.GetString( "bindanim", "", &bindanim ) || parent->spawnArgs.GetString( "anim", "", &bindanim ) ) && *bindanim ) {
|
|
animNum = parentAnimator->GetAnim( bindanim );
|
|
if ( !animNum ) {
|
|
gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() );
|
|
}
|
|
anim = parentAnimator->GetAnim( animNum );
|
|
if ( !anim ) {
|
|
gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() );
|
|
}
|
|
|
|
// make sure parent's render origin has been set
|
|
parent->UpdateModelTransform();
|
|
|
|
//FIXME: need a BindToJoint that accepts a joint position
|
|
parentAnimator->CreateFrame( gameLocal.time, true );
|
|
idJointMat *frame = parent->renderEntity.joints;
|
|
gameEdit->ANIM_CreateAnimFrame( parentAnimator->ModelHandle(), anim->MD5Anim( 0 ), parent->renderEntity.numJoints, frame, 0, parentAnimator->ModelDef()->GetVisualOffset(), parentAnimator->RemoveOrigin() );
|
|
BindToJoint( parent, joint, bindOrientated );
|
|
parentAnimator->ForceUpdate();
|
|
} else {
|
|
BindToJoint( parent, joint, bindOrientated );
|
|
}
|
|
}
|
|
// bind to a body of the physics object of the parent
|
|
else if ( spawnArgs.GetInt( "bindToBody", "0", id ) ) {
|
|
BindToBody( parent, id, bindOrientated );
|
|
}
|
|
// bind to the parent
|
|
else {
|
|
Bind( parent, bindOrientated );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetOwner
|
|
================
|
|
*/
|
|
void idEntity::Event_SetOwner( idEntity *owner ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < GetPhysics()->GetNumClipModels(); i++ ) {
|
|
GetPhysics()->GetClipModel( i )->SetOwner( owner );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetModel
|
|
================
|
|
*/
|
|
void idEntity::Event_SetModel( const char *modelname ) {
|
|
SetModel( modelname );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetSkin
|
|
================
|
|
*/
|
|
void idEntity::Event_SetSkin( const char *skinname ) {
|
|
renderEntity.customSkin = declManager->FindSkin( skinname );
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetShaderParm
|
|
================
|
|
*/
|
|
void idEntity::Event_GetShaderParm( int parmnum ) {
|
|
if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) {
|
|
gameLocal.Error( "shader parm index (%d) out of range", parmnum );
|
|
}
|
|
|
|
idThread::ReturnFloat( renderEntity.shaderParms[ parmnum ] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetShaderParm
|
|
================
|
|
*/
|
|
void idEntity::Event_SetShaderParm( int parmnum, float value ) {
|
|
SetShaderParm( parmnum, value );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetShaderParms
|
|
================
|
|
*/
|
|
void idEntity::Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ) {
|
|
renderEntity.shaderParms[ SHADERPARM_RED ] = parm0;
|
|
renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1;
|
|
renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2;
|
|
renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3;
|
|
UpdateVisuals();
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetColor
|
|
================
|
|
*/
|
|
void idEntity::Event_SetColor( float red, float green, float blue ) {
|
|
SetColor( red, green, blue );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetColor
|
|
================
|
|
*/
|
|
void idEntity::Event_GetColor( void ) {
|
|
idVec3 out;
|
|
|
|
GetColor( out );
|
|
idThread::ReturnVector( out );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_IsHidden
|
|
================
|
|
*/
|
|
void idEntity::Event_IsHidden( void ) {
|
|
idThread::ReturnInt( fl.hidden );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_Hide
|
|
================
|
|
*/
|
|
void idEntity::Event_Hide( void ) {
|
|
Hide();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_Show
|
|
================
|
|
*/
|
|
void idEntity::Event_Show( void ) {
|
|
Show();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_CacheSoundShader
|
|
================
|
|
*/
|
|
void idEntity::Event_CacheSoundShader( const char *soundName ) {
|
|
declManager->FindSound( soundName );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_StartSoundShader
|
|
================
|
|
*/
|
|
void idEntity::Event_StartSoundShader( const char *soundName, int channel ) {
|
|
int length;
|
|
|
|
StartSoundShader( declManager->FindSound( soundName ), (s_channelType)channel, 0, false, &length );
|
|
idThread::ReturnFloat( MS2SEC( length ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_StopSound
|
|
================
|
|
*/
|
|
void idEntity::Event_StopSound( int channel, int netSync ) {
|
|
StopSound( channel, ( netSync != 0 ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_StartSound
|
|
================
|
|
*/
|
|
void idEntity::Event_StartSound( const char *soundName, int channel, int netSync ) {
|
|
int time;
|
|
|
|
StartSound( soundName, ( s_channelType )channel, 0, ( netSync != 0 ), &time );
|
|
idThread::ReturnFloat( MS2SEC( time ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_FadeSound
|
|
================
|
|
*/
|
|
void idEntity::Event_FadeSound( int channel, float to, float over ) {
|
|
if ( refSound.referenceSound ) {
|
|
refSound.referenceSound->FadeSound( channel, to, over );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetWorldOrigin
|
|
================
|
|
*/
|
|
void idEntity::Event_GetWorldOrigin( void ) {
|
|
idThread::ReturnVector( GetPhysics()->GetOrigin() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetWorldOrigin
|
|
================
|
|
*/
|
|
void idEntity::Event_SetWorldOrigin( idVec3 const &org ) {
|
|
idVec3 neworg = GetLocalCoordinates( org );
|
|
SetOrigin( neworg );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetOrigin
|
|
================
|
|
*/
|
|
void idEntity::Event_SetOrigin( idVec3 const &org ) {
|
|
SetOrigin( org );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetOrigin
|
|
================
|
|
*/
|
|
void idEntity::Event_GetOrigin( void ) {
|
|
idThread::ReturnVector( GetLocalCoordinates( GetPhysics()->GetOrigin() ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetAngles
|
|
================
|
|
*/
|
|
void idEntity::Event_SetAngles( idAngles const &ang ) {
|
|
SetAngles( ang );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetAngles
|
|
================
|
|
*/
|
|
void idEntity::Event_GetAngles( void ) {
|
|
idAngles ang = GetPhysics()->GetAxis().ToAngles();
|
|
idThread::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetLinearVelocity
|
|
================
|
|
*/
|
|
void idEntity::Event_SetLinearVelocity( const idVec3 &velocity ) {
|
|
GetPhysics()->SetLinearVelocity( velocity );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetLinearVelocity
|
|
================
|
|
*/
|
|
void idEntity::Event_GetLinearVelocity( void ) {
|
|
idThread::ReturnVector( GetPhysics()->GetLinearVelocity() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetAngularVelocity
|
|
================
|
|
*/
|
|
void idEntity::Event_SetAngularVelocity( const idVec3 &velocity ) {
|
|
GetPhysics()->SetAngularVelocity( velocity );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetAngularVelocity
|
|
================
|
|
*/
|
|
void idEntity::Event_GetAngularVelocity( void ) {
|
|
idThread::ReturnVector( GetPhysics()->GetAngularVelocity() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetSize
|
|
================
|
|
*/
|
|
void idEntity::Event_SetSize( idVec3 const &mins, idVec3 const &maxs ) {
|
|
GetPhysics()->SetClipBox( idBounds( mins, maxs ), 1.0f );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetSize
|
|
================
|
|
*/
|
|
void idEntity::Event_GetSize( void ) {
|
|
idBounds bounds;
|
|
|
|
bounds = GetPhysics()->GetBounds();
|
|
idThread::ReturnVector( bounds[1] - bounds[0] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetMins
|
|
================
|
|
*/
|
|
void idEntity::Event_GetMins( void ) {
|
|
idThread::ReturnVector( GetPhysics()->GetBounds()[0] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetMaxs
|
|
================
|
|
*/
|
|
void idEntity::Event_GetMaxs( void ) {
|
|
idThread::ReturnVector( GetPhysics()->GetBounds()[1] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_Touches
|
|
================
|
|
*/
|
|
void idEntity::Event_Touches( idEntity *ent ) {
|
|
if ( !ent ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
const idBounds &myBounds = GetPhysics()->GetAbsBounds();
|
|
const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds();
|
|
|
|
idThread::ReturnInt( myBounds.IntersectsBounds( entBounds ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetGuiParm
|
|
================
|
|
*/
|
|
void idEntity::Event_SetGuiParm( const char *key, const char *val ) {
|
|
for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) {
|
|
if ( renderEntity.gui[ i ] ) {
|
|
if ( idStr::Icmpn( key, "gui_", 4 ) == 0 ) {
|
|
spawnArgs.Set( key, val );
|
|
}
|
|
renderEntity.gui[ i ]->SetStateString( key, val );
|
|
renderEntity.gui[ i ]->StateChanged( gameLocal.time );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetGuiParm
|
|
================
|
|
*/
|
|
void idEntity::Event_SetGuiFloat( const char *key, float f ) {
|
|
for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) {
|
|
if ( renderEntity.gui[ i ] ) {
|
|
renderEntity.gui[ i ]->SetStateString( key, va( "%f", f ) );
|
|
renderEntity.gui[ i ]->StateChanged( gameLocal.time );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetNextKey
|
|
================
|
|
*/
|
|
void idEntity::Event_GetNextKey( const char *prefix, const char *lastMatch ) {
|
|
const idKeyValue *kv;
|
|
const idKeyValue *previous;
|
|
|
|
if ( *lastMatch ) {
|
|
previous = spawnArgs.FindKey( lastMatch );
|
|
} else {
|
|
previous = NULL;
|
|
}
|
|
|
|
kv = spawnArgs.MatchPrefix( prefix, previous );
|
|
if ( !kv ) {
|
|
idThread::ReturnString( "" );
|
|
} else {
|
|
idThread::ReturnString( kv->GetKey() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetKey
|
|
================
|
|
*/
|
|
void idEntity::Event_SetKey( const char *key, const char *value ) {
|
|
spawnArgs.Set( key, value );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetKey
|
|
================
|
|
*/
|
|
void idEntity::Event_GetKey( const char *key ) {
|
|
const char *value;
|
|
|
|
spawnArgs.GetString( key, "", &value );
|
|
idThread::ReturnString( value );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetIntKey
|
|
================
|
|
*/
|
|
void idEntity::Event_GetIntKey( const char *key ) {
|
|
int value;
|
|
|
|
spawnArgs.GetInt( key, "0", value );
|
|
|
|
// scripts only support floats
|
|
idThread::ReturnFloat( value );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetFloatKey
|
|
================
|
|
*/
|
|
void idEntity::Event_GetFloatKey( const char *key ) {
|
|
float value;
|
|
|
|
spawnArgs.GetFloat( key, "0", value );
|
|
idThread::ReturnFloat( value );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetVectorKey
|
|
================
|
|
*/
|
|
void idEntity::Event_GetVectorKey( const char *key ) {
|
|
idVec3 value;
|
|
|
|
spawnArgs.GetVector( key, "0 0 0", value );
|
|
idThread::ReturnVector( value );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_GetEntityKey
|
|
================
|
|
*/
|
|
void idEntity::Event_GetEntityKey( const char *key ) {
|
|
idEntity *ent;
|
|
const char *entname;
|
|
|
|
if ( !spawnArgs.GetString( key, NULL, &entname ) ) {
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
ent = gameLocal.FindEntity( entname );
|
|
if ( !ent ) {
|
|
gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() );
|
|
}
|
|
|
|
idThread::ReturnEntity( ent );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_RestorePosition
|
|
================
|
|
*/
|
|
void idEntity::Event_RestorePosition( void ) {
|
|
idVec3 org;
|
|
idAngles angles;
|
|
idMat3 axis;
|
|
idEntity * part;
|
|
|
|
spawnArgs.GetVector( "origin", "0 0 0", org );
|
|
|
|
// get the rotation matrix in either full form, or single angle form
|
|
if ( spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) {
|
|
angles = axis.ToAngles();
|
|
} else {
|
|
angles[ 0 ] = 0;
|
|
angles[ 1 ] = spawnArgs.GetFloat( "angle" );
|
|
angles[ 2 ] = 0;
|
|
}
|
|
|
|
Teleport( org, angles, NULL );
|
|
|
|
for ( part = teamChain; part != NULL; part = part->teamChain ) {
|
|
if ( part->bindMaster != this ) {
|
|
continue;
|
|
}
|
|
if ( part->GetPhysics()->IsType( idPhysics_Parametric::Type ) ) {
|
|
if ( static_cast<idPhysics_Parametric *>(part->GetPhysics())->IsPusher() ) {
|
|
gameLocal.Warning( "teleported '%s' which has the pushing mover '%s' bound to it\n", GetName(), part->GetName() );
|
|
}
|
|
} else if ( part->GetPhysics()->IsType( idPhysics_AF::Type ) ) {
|
|
gameLocal.Warning( "teleported '%s' which has the articulated figure '%s' bound to it\n", GetName(), part->GetName() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_UpdateCameraTarget
|
|
================
|
|
*/
|
|
void idEntity::Event_UpdateCameraTarget( void ) {
|
|
const char *target;
|
|
const idKeyValue *kv;
|
|
idVec3 dir;
|
|
|
|
target = spawnArgs.GetString( "cameraTarget" );
|
|
|
|
cameraTarget = gameLocal.FindEntity( target );
|
|
|
|
if ( cameraTarget ) {
|
|
kv = cameraTarget->spawnArgs.MatchPrefix( "target", NULL );
|
|
while( kv ) {
|
|
idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
|
|
if ( ent && idStr::Icmp( ent->GetEntityDefName(), "target_null" ) == 0) {
|
|
dir = ent->GetPhysics()->GetOrigin() - cameraTarget->GetPhysics()->GetOrigin();
|
|
dir.Normalize();
|
|
cameraTarget->SetAxis( dir.ToMat3() );
|
|
SetAxis(dir.ToMat3());
|
|
break;
|
|
}
|
|
kv = cameraTarget->spawnArgs.MatchPrefix( "target", kv );
|
|
}
|
|
}
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_DistanceTo
|
|
================
|
|
*/
|
|
void idEntity::Event_DistanceTo( idEntity *ent ) {
|
|
if ( !ent ) {
|
|
// just say it's really far away
|
|
idThread::ReturnFloat( MAX_WORLD_SIZE );
|
|
} else {
|
|
float dist = ( GetPhysics()->GetOrigin() - ent->GetPhysics()->GetOrigin() ).LengthFast();
|
|
idThread::ReturnFloat( dist );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_DistanceToPoint
|
|
================
|
|
*/
|
|
void idEntity::Event_DistanceToPoint( const idVec3 &point ) {
|
|
float dist = ( GetPhysics()->GetOrigin() - point ).LengthFast();
|
|
idThread::ReturnFloat( dist );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_StartFx
|
|
================
|
|
*/
|
|
void idEntity::Event_StartFx( const char *fx ) {
|
|
idEntityFx::StartFx( fx, NULL, NULL, this, true );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_WaitFrame
|
|
================
|
|
*/
|
|
void idEntity::Event_WaitFrame( void ) {
|
|
idThread *thread;
|
|
|
|
thread = idThread::CurrentThread();
|
|
if ( thread ) {
|
|
thread->WaitFrame();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idEntity::Event_Wait
|
|
=====================
|
|
*/
|
|
void idEntity::Event_Wait( float time ) {
|
|
idThread *thread = idThread::CurrentThread();
|
|
|
|
if ( !thread ) {
|
|
gameLocal.Error( "Event 'wait' called from outside thread" );
|
|
}
|
|
|
|
thread->WaitSec( time );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idEntity::Event_HasFunction
|
|
=====================
|
|
*/
|
|
void idEntity::Event_HasFunction( const char *name ) {
|
|
const function_t *func;
|
|
|
|
func = scriptObject.GetFunction( name );
|
|
if ( func ) {
|
|
idThread::ReturnInt( true );
|
|
} else {
|
|
idThread::ReturnInt( false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idEntity::Event_CallFunction
|
|
=====================
|
|
*/
|
|
void idEntity::Event_CallFunction( const char *funcname ) {
|
|
const function_t *func;
|
|
idThread *thread;
|
|
|
|
thread = idThread::CurrentThread();
|
|
if ( !thread ) {
|
|
gameLocal.Error( "Event 'callFunction' called from outside thread" );
|
|
}
|
|
|
|
func = scriptObject.GetFunction( funcname );
|
|
if ( !func ) {
|
|
gameLocal.Error( "Unknown function '%s' in '%s'", funcname, scriptObject.GetTypeName() );
|
|
}
|
|
|
|
if ( func->type->NumParameters() != 1 ) {
|
|
gameLocal.Error( "Function '%s' has the wrong number of parameters for 'callFunction'", funcname );
|
|
}
|
|
if ( !scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) {
|
|
gameLocal.Error( "Function '%s' is the wrong type for 'callFunction'", funcname );
|
|
}
|
|
|
|
// function args will be invalid after this call
|
|
thread->CallFunction( this, func, false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::Event_SetNeverDormant
|
|
================
|
|
*/
|
|
void idEntity::Event_SetNeverDormant( int enable ) {
|
|
fl.neverDormant = ( enable != 0 );
|
|
dormantStart = 0;
|
|
}
|
|
|
|
/***********************************************************************
|
|
|
|
Network
|
|
|
|
***********************************************************************/
|
|
|
|
/*
|
|
================
|
|
idEntity::ClientPredictionThink
|
|
================
|
|
*/
|
|
void idEntity::ClientPredictionThink( void ) {
|
|
RunPhysics();
|
|
Present();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::WriteBindToSnapshot
|
|
================
|
|
*/
|
|
void idEntity::WriteBindToSnapshot( idBitMsgDelta &msg ) const {
|
|
int bindInfo;
|
|
|
|
if ( bindMaster ) {
|
|
bindInfo = bindMaster->entityNumber;
|
|
bindInfo |= ( fl.bindOrientated & 1 ) << GENTITYNUM_BITS;
|
|
if ( bindJoint != INVALID_JOINT ) {
|
|
bindInfo |= 1 << ( GENTITYNUM_BITS + 1 );
|
|
bindInfo |= bindJoint << ( 3 + GENTITYNUM_BITS );
|
|
} else if ( bindBody != -1 ) {
|
|
bindInfo |= 2 << ( GENTITYNUM_BITS + 1 );
|
|
bindInfo |= bindBody << ( 3 + GENTITYNUM_BITS );
|
|
}
|
|
} else {
|
|
bindInfo = ENTITYNUM_NONE;
|
|
}
|
|
msg.WriteBits( bindInfo, GENTITYNUM_BITS + 3 + 9 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ReadBindFromSnapshot
|
|
================
|
|
*/
|
|
void idEntity::ReadBindFromSnapshot( const idBitMsgDelta &msg ) {
|
|
int bindInfo, bindEntityNum, bindPos;
|
|
bool bindOrientated;
|
|
idEntity *master;
|
|
|
|
bindInfo = msg.ReadBits( GENTITYNUM_BITS + 3 + 9 );
|
|
bindEntityNum = bindInfo & ( ( 1 << GENTITYNUM_BITS ) - 1 );
|
|
|
|
if ( bindEntityNum != ENTITYNUM_NONE ) {
|
|
master = gameLocal.entities[ bindEntityNum ];
|
|
|
|
bindOrientated = ( bindInfo >> GENTITYNUM_BITS ) & 1;
|
|
bindPos = ( bindInfo >> ( GENTITYNUM_BITS + 3 ) );
|
|
switch( ( bindInfo >> ( GENTITYNUM_BITS + 1 ) ) & 3 ) {
|
|
case 1: {
|
|
BindToJoint( master, (jointHandle_t) bindPos, bindOrientated );
|
|
break;
|
|
}
|
|
case 2: {
|
|
BindToBody( master, bindPos, bindOrientated );
|
|
break;
|
|
}
|
|
default: {
|
|
Bind( master, bindOrientated );
|
|
break;
|
|
}
|
|
}
|
|
} else if ( bindMaster ) {
|
|
Unbind();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::WriteColorToSnapshot
|
|
================
|
|
*/
|
|
void idEntity::WriteColorToSnapshot( idBitMsgDelta &msg ) const {
|
|
idVec4 color;
|
|
|
|
color[0] = renderEntity.shaderParms[ SHADERPARM_RED ];
|
|
color[1] = renderEntity.shaderParms[ SHADERPARM_GREEN ];
|
|
color[2] = renderEntity.shaderParms[ SHADERPARM_BLUE ];
|
|
color[3] = renderEntity.shaderParms[ SHADERPARM_ALPHA ];
|
|
msg.WriteLong( PackColor( color ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ReadColorFromSnapshot
|
|
================
|
|
*/
|
|
void idEntity::ReadColorFromSnapshot( const idBitMsgDelta &msg ) {
|
|
idVec4 color;
|
|
|
|
UnpackColor( msg.ReadLong(), color );
|
|
renderEntity.shaderParms[ SHADERPARM_RED ] = color[0];
|
|
renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1];
|
|
renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2];
|
|
renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[3];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::WriteGUIToSnapshot
|
|
================
|
|
*/
|
|
void idEntity::WriteGUIToSnapshot( idBitMsgDelta &msg ) const {
|
|
// no need to loop over MAX_RENDERENTITY_GUI at this time
|
|
if ( renderEntity.gui[ 0 ] ) {
|
|
msg.WriteByte( renderEntity.gui[ 0 ]->State().GetInt( "networkState" ) );
|
|
} else {
|
|
msg.WriteByte( 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ReadGUIFromSnapshot
|
|
================
|
|
*/
|
|
void idEntity::ReadGUIFromSnapshot( const idBitMsgDelta &msg ) {
|
|
int state;
|
|
idUserInterface *gui;
|
|
state = msg.ReadByte( );
|
|
gui = renderEntity.gui[ 0 ];
|
|
if ( gui && state != mpGUIState ) {
|
|
mpGUIState = state;
|
|
gui->SetStateInt( "networkState", state );
|
|
gui->HandleNamedEvent( "networkState" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::WriteToSnapshot
|
|
================
|
|
*/
|
|
void idEntity::WriteToSnapshot( idBitMsgDelta &msg ) const {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void idEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ServerSendEvent
|
|
|
|
Saved events are also sent to any client that connects late so all clients
|
|
always receive the events nomatter what time they join the game.
|
|
================
|
|
*/
|
|
void idEntity::ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const {
|
|
idBitMsg outMsg;
|
|
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
|
|
|
|
if ( !gameLocal.isServer ) {
|
|
return;
|
|
}
|
|
|
|
// prevent dupe events caused by frame re-runs
|
|
if ( !gameLocal.isNewFrame ) {
|
|
return;
|
|
}
|
|
|
|
outMsg.Init( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.BeginWriting();
|
|
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT );
|
|
outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 );
|
|
outMsg.WriteByte( eventId );
|
|
outMsg.WriteLong( gameLocal.time );
|
|
if ( msg ) {
|
|
outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) );
|
|
outMsg.WriteData( msg->GetData(), msg->GetSize() );
|
|
} else {
|
|
outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) );
|
|
}
|
|
|
|
if ( excludeClient != -1 ) {
|
|
networkSystem->ServerSendReliableMessageExcluding( excludeClient, outMsg );
|
|
} else {
|
|
networkSystem->ServerSendReliableMessage( -1, outMsg );
|
|
}
|
|
|
|
if ( saveEvent ) {
|
|
gameLocal.SaveEntityNetworkEvent( this, eventId, msg );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ClientSendEvent
|
|
================
|
|
*/
|
|
void idEntity::ClientSendEvent( int eventId, const idBitMsg *msg ) const {
|
|
idBitMsg outMsg;
|
|
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
|
|
|
|
if ( !gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
// prevent dupe events caused by frame re-runs
|
|
if ( !gameLocal.isNewFrame ) {
|
|
return;
|
|
}
|
|
|
|
outMsg.Init( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.BeginWriting();
|
|
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT );
|
|
outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 );
|
|
outMsg.WriteByte( eventId );
|
|
outMsg.WriteLong( gameLocal.time );
|
|
if ( msg ) {
|
|
outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) );
|
|
outMsg.WriteData( msg->GetData(), msg->GetSize() );
|
|
} else {
|
|
outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) );
|
|
}
|
|
|
|
networkSystem->ClientSendReliableMessage( outMsg );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ServerReceiveEvent
|
|
================
|
|
*/
|
|
bool idEntity::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) {
|
|
switch( event ) {
|
|
case 0: {
|
|
}
|
|
default: {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idEntity::ClientReceiveEvent
|
|
================
|
|
*/
|
|
bool idEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
|
|
int index;
|
|
const idSoundShader *shader;
|
|
s_channelType channel;
|
|
|
|
switch( event ) {
|
|
case EVENT_STARTSOUNDSHADER: {
|
|
// the sound stuff would early out
|
|
assert( gameLocal.isNewFrame );
|
|
if ( time < gameLocal.realClientTime - 1000 ) {
|
|
// too old, skip it ( reliable messages don't need to be parsed in full )
|
|
common->DPrintf( "ent 0x%x: start sound shader too old (%d ms)\n", entityNumber, gameLocal.realClientTime - time );
|
|
return true;
|
|
}
|
|
index = gameLocal.ClientRemapDecl( DECL_SOUND, msg.ReadLong() );
|
|
if ( index >= 0 && index < declManager->GetNumDecls( DECL_SOUND ) ) {
|
|
shader = declManager->SoundByIndex( index, false );
|
|
channel = (s_channelType)msg.ReadByte();
|
|
StartSoundShader( shader, channel, 0, false, NULL );
|
|
}
|
|
return true;
|
|
}
|
|
case EVENT_STOPSOUNDSHADER: {
|
|
// the sound stuff would early out
|
|
assert( gameLocal.isNewFrame );
|
|
channel = (s_channelType)msg.ReadByte();
|
|
StopSound( channel, false );
|
|
return true;
|
|
}
|
|
default: {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
idAnimatedEntity
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
const idEventDef EV_GetJointHandle( "getJointHandle", "s", 'd' );
|
|
const idEventDef EV_ClearAllJoints( "clearAllJoints" );
|
|
const idEventDef EV_ClearJoint( "clearJoint", "d" );
|
|
const idEventDef EV_SetJointPos( "setJointPos", "ddv" );
|
|
const idEventDef EV_SetJointAngle( "setJointAngle", "ddv" );
|
|
const idEventDef EV_GetJointPos( "getJointPos", "d", 'v' );
|
|
const idEventDef EV_GetJointAngle( "getJointAngle", "d", 'v' );
|
|
|
|
CLASS_DECLARATION( idEntity, idAnimatedEntity )
|
|
EVENT( EV_GetJointHandle, idAnimatedEntity::Event_GetJointHandle )
|
|
EVENT( EV_ClearAllJoints, idAnimatedEntity::Event_ClearAllJoints )
|
|
EVENT( EV_ClearJoint, idAnimatedEntity::Event_ClearJoint )
|
|
EVENT( EV_SetJointPos, idAnimatedEntity::Event_SetJointPos )
|
|
EVENT( EV_SetJointAngle, idAnimatedEntity::Event_SetJointAngle )
|
|
EVENT( EV_GetJointPos, idAnimatedEntity::Event_GetJointPos )
|
|
EVENT( EV_GetJointAngle, idAnimatedEntity::Event_GetJointAngle )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::idAnimatedEntity
|
|
================
|
|
*/
|
|
idAnimatedEntity::idAnimatedEntity() {
|
|
animator.SetEntity( this );
|
|
damageEffects = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::~idAnimatedEntity
|
|
================
|
|
*/
|
|
idAnimatedEntity::~idAnimatedEntity() {
|
|
damageEffect_t *de;
|
|
|
|
for ( de = damageEffects; de; de = damageEffects ) {
|
|
damageEffects = de->next;
|
|
delete de;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Save
|
|
|
|
archives object for save game file
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Save( idSaveGame *savefile ) const {
|
|
animator.Save( savefile );
|
|
|
|
// Wounds are very temporary, ignored at this time
|
|
//damageEffect_t *damageEffects;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Restore
|
|
|
|
unarchives object from save game file
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Restore( idRestoreGame *savefile ) {
|
|
animator.Restore( savefile );
|
|
|
|
// check if the entity has an MD5 model
|
|
if ( animator.ModelHandle() ) {
|
|
// set the callback to update the joints
|
|
renderEntity.callback = idEntity::ModelCallback;
|
|
animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints );
|
|
animator.GetBounds( gameLocal.time, renderEntity.bounds );
|
|
if ( modelDefHandle != -1 ) {
|
|
gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::ClientPredictionThink
|
|
================
|
|
*/
|
|
void idAnimatedEntity::ClientPredictionThink( void ) {
|
|
RunPhysics();
|
|
UpdateAnimation();
|
|
Present();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Think
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Think( void ) {
|
|
RunPhysics();
|
|
UpdateAnimation();
|
|
Present();
|
|
UpdateDamageEffects();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::UpdateAnimation
|
|
================
|
|
*/
|
|
void idAnimatedEntity::UpdateAnimation( void ) {
|
|
// 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;
|
|
}
|
|
|
|
// get the latest frame bounds
|
|
animator.GetBounds( gameLocal.time, renderEntity.bounds );
|
|
if ( renderEntity.bounds.IsCleared() && !fl.hidden ) {
|
|
gameLocal.DPrintf( "%d: inside out bounds\n", gameLocal.time );
|
|
}
|
|
|
|
// update the renderEntity
|
|
UpdateVisuals();
|
|
|
|
// the animation is updated
|
|
animator.ClearForceUpdate();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::GetAnimator
|
|
================
|
|
*/
|
|
idAnimator *idAnimatedEntity::GetAnimator( void ) {
|
|
return &animator;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::SetModel
|
|
================
|
|
*/
|
|
void idAnimatedEntity::SetModel( const char *modelname ) {
|
|
FreeModelDef();
|
|
|
|
renderEntity.hModel = animator.SetModel( modelname );
|
|
if ( !renderEntity.hModel ) {
|
|
idEntity::SetModel( modelname );
|
|
return;
|
|
}
|
|
|
|
if ( !renderEntity.customSkin ) {
|
|
renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin();
|
|
}
|
|
|
|
// set the callback to update the joints
|
|
renderEntity.callback = idEntity::ModelCallback;
|
|
animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints );
|
|
animator.GetBounds( gameLocal.time, renderEntity.bounds );
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAnimatedEntity::GetJointWorldTransform
|
|
=====================
|
|
*/
|
|
bool idAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) {
|
|
if ( !animator.GetJointTransform( jointHandle, currentTime, offset, axis ) ) {
|
|
return false;
|
|
}
|
|
|
|
ConvertLocalToWorldTransform( offset, axis );
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idAnimatedEntity::GetJointTransformForAnim
|
|
==============
|
|
*/
|
|
bool idAnimatedEntity::GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int frameTime, idVec3 &offset, idMat3 &axis ) const {
|
|
const idAnim *anim;
|
|
int numJoints;
|
|
idJointMat *frame;
|
|
|
|
anim = animator.GetAnim( animNum );
|
|
if ( !anim ) {
|
|
assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
numJoints = animator.NumJoints();
|
|
if ( ( jointHandle < 0 ) || ( jointHandle >= numJoints ) ) {
|
|
assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
frame = ( idJointMat * )_alloca16( numJoints * sizeof( idJointMat ) );
|
|
gameEdit->ANIM_CreateAnimFrame( animator.ModelHandle(), anim->MD5Anim( 0 ), renderEntity.numJoints, frame, frameTime, animator.ModelDef()->GetVisualOffset(), animator.RemoveOrigin() );
|
|
|
|
offset = frame[ jointHandle ].ToVec3();
|
|
axis = frame[ jointHandle ].ToMat3();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idAnimatedEntity::AddDamageEffect
|
|
|
|
Dammage effects track the animating impact position, spitting out particles.
|
|
==============
|
|
*/
|
|
void idAnimatedEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) {
|
|
jointHandle_t jointNum;
|
|
idVec3 origin, dir, localDir, localOrigin, localNormal;
|
|
idMat3 axis;
|
|
|
|
if ( !g_bloodEffects.GetBool() || renderEntity.joints == NULL ) {
|
|
return;
|
|
}
|
|
|
|
const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false );
|
|
if ( def == NULL ) {
|
|
return;
|
|
}
|
|
|
|
jointNum = CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id );
|
|
if ( jointNum == INVALID_JOINT ) {
|
|
return;
|
|
}
|
|
|
|
dir = velocity;
|
|
dir.Normalize();
|
|
|
|
axis = renderEntity.joints[jointNum].ToMat3() * renderEntity.axis;
|
|
origin = renderEntity.origin + renderEntity.joints[jointNum].ToVec3() * renderEntity.axis;
|
|
|
|
localOrigin = ( collision.c.point - origin ) * axis.Transpose();
|
|
localNormal = collision.c.normal * axis.Transpose();
|
|
localDir = dir * axis.Transpose();
|
|
|
|
AddLocalDamageEffect( jointNum, localOrigin, localNormal, localDir, def, collision.c.material );
|
|
|
|
if ( gameLocal.isServer ) {
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.Init( msgBuf, sizeof( msgBuf ) );
|
|
msg.BeginWriting();
|
|
msg.WriteShort( (int)jointNum );
|
|
msg.WriteFloat( localOrigin[0] );
|
|
msg.WriteFloat( localOrigin[1] );
|
|
msg.WriteFloat( localOrigin[2] );
|
|
msg.WriteDir( localNormal, 24 );
|
|
msg.WriteDir( localDir, 24 );
|
|
msg.WriteLong( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, def->Index() ) );
|
|
msg.WriteLong( gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) );
|
|
ServerSendEvent( EVENT_ADD_DAMAGE_EFFECT, &msg, false, -1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idAnimatedEntity::GetDefaultSurfaceType
|
|
==============
|
|
*/
|
|
int idAnimatedEntity::GetDefaultSurfaceType( void ) const {
|
|
return SURFTYPE_METAL;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idAnimatedEntity::AddLocalDamageEffect
|
|
==============
|
|
*/
|
|
void idAnimatedEntity::AddLocalDamageEffect( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, const idDeclEntityDef *def, const idMaterial *collisionMaterial ) {
|
|
const char *sound, *splat, *decal, *bleed, *key;
|
|
damageEffect_t *de;
|
|
idVec3 origin, dir;
|
|
idMat3 axis;
|
|
|
|
axis = renderEntity.joints[jointNum].ToMat3() * renderEntity.axis;
|
|
origin = renderEntity.origin + renderEntity.joints[jointNum].ToVec3() * renderEntity.axis;
|
|
|
|
origin = origin + localOrigin * axis;
|
|
dir = localDir * axis;
|
|
|
|
int type = collisionMaterial->GetSurfaceType();
|
|
if ( type == SURFTYPE_NONE ) {
|
|
type = GetDefaultSurfaceType();
|
|
}
|
|
|
|
const char *materialType = gameLocal.sufaceTypeNames[ type ];
|
|
|
|
// start impact sound based on material type
|
|
key = va( "snd_%s", materialType );
|
|
sound = spawnArgs.GetString( key );
|
|
if ( *sound == '\0' ) {
|
|
sound = def->dict.GetString( key );
|
|
}
|
|
if ( *sound != '\0' ) {
|
|
StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
|
|
}
|
|
|
|
// blood splats are thrown onto nearby surfaces
|
|
key = va( "mtr_splat_%s", materialType );
|
|
splat = spawnArgs.RandomPrefix( key, gameLocal.random );
|
|
if ( *splat == '\0' ) {
|
|
splat = def->dict.RandomPrefix( key, gameLocal.random );
|
|
}
|
|
if ( *splat != '\0' ) {
|
|
gameLocal.BloodSplat( origin, dir, 64.0f, splat );
|
|
}
|
|
|
|
// can't see wounds on the player model in single player mode
|
|
if ( !( IsType( idPlayer::Type ) && !gameLocal.isMultiplayer ) ) {
|
|
// place a wound overlay on the model
|
|
key = va( "mtr_wound_%s", materialType );
|
|
decal = spawnArgs.RandomPrefix( key, gameLocal.random );
|
|
if ( *decal == '\0' ) {
|
|
decal = def->dict.RandomPrefix( key, gameLocal.random );
|
|
}
|
|
if ( *decal != '\0' ) {
|
|
ProjectOverlay( origin, dir, 20.0f, decal );
|
|
}
|
|
}
|
|
|
|
// a blood spurting wound is added
|
|
key = va( "smoke_wound_%s", materialType );
|
|
bleed = spawnArgs.GetString( key );
|
|
if ( *bleed == '\0' ) {
|
|
bleed = def->dict.GetString( key );
|
|
}
|
|
if ( *bleed != '\0' ) {
|
|
de = new damageEffect_t;
|
|
de->next = this->damageEffects;
|
|
this->damageEffects = de;
|
|
|
|
de->jointNum = jointNum;
|
|
de->localOrigin = localOrigin;
|
|
de->localNormal = localNormal;
|
|
de->type = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, bleed ) );
|
|
de->time = gameLocal.time;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idAnimatedEntity::UpdateDamageEffects
|
|
==============
|
|
*/
|
|
void idAnimatedEntity::UpdateDamageEffects( void ) {
|
|
damageEffect_t *de, **prev;
|
|
|
|
// free any that have timed out
|
|
prev = &this->damageEffects;
|
|
while ( *prev ) {
|
|
de = *prev;
|
|
if ( de->time == 0 ) { // FIXME:SMOKE
|
|
*prev = de->next;
|
|
delete de;
|
|
} else {
|
|
prev = &de->next;
|
|
}
|
|
}
|
|
|
|
if ( !g_bloodEffects.GetBool() ) {
|
|
return;
|
|
}
|
|
|
|
// emit a particle for each bleeding wound
|
|
for ( de = this->damageEffects; de; de = de->next ) {
|
|
idVec3 origin, start;
|
|
idMat3 axis;
|
|
|
|
animator.GetJointTransform( de->jointNum, gameLocal.time, origin, axis );
|
|
axis *= renderEntity.axis;
|
|
origin = renderEntity.origin + origin * renderEntity.axis;
|
|
start = origin + de->localOrigin * axis;
|
|
if ( !gameLocal.smokeParticles->EmitSmoke( de->type, de->time, gameLocal.random.CRandomFloat(), start, axis ) ) {
|
|
de->time = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::ClientReceiveEvent
|
|
================
|
|
*/
|
|
bool idAnimatedEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
|
|
int damageDefIndex;
|
|
int materialIndex;
|
|
jointHandle_t jointNum;
|
|
idVec3 localOrigin, localNormal, localDir;
|
|
|
|
switch( event ) {
|
|
case EVENT_ADD_DAMAGE_EFFECT: {
|
|
jointNum = (jointHandle_t) msg.ReadShort();
|
|
localOrigin[0] = msg.ReadFloat();
|
|
localOrigin[1] = msg.ReadFloat();
|
|
localOrigin[2] = msg.ReadFloat();
|
|
localNormal = msg.ReadDir( 24 );
|
|
localDir = msg.ReadDir( 24 );
|
|
damageDefIndex = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadLong() );
|
|
materialIndex = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() );
|
|
const idDeclEntityDef *damageDef = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, damageDefIndex ) );
|
|
const idMaterial *collisionMaterial = static_cast<const idMaterial *>( declManager->DeclByIndex( DECL_MATERIAL, materialIndex ) );
|
|
AddLocalDamageEffect( jointNum, localOrigin, localNormal, localDir, damageDef, collisionMaterial );
|
|
return true;
|
|
}
|
|
default: {
|
|
return idEntity::ClientReceiveEvent( event, time, msg );
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Event_GetJointHandle
|
|
|
|
looks up the number of the specified joint. returns INVALID_JOINT if the joint is not found.
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Event_GetJointHandle( const char *jointname ) {
|
|
jointHandle_t joint;
|
|
|
|
joint = animator.GetJointHandle( jointname );
|
|
idThread::ReturnInt( joint );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Event_ClearAllJoints
|
|
|
|
removes any custom transforms on all joints
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Event_ClearAllJoints( void ) {
|
|
animator.ClearAllJoints();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Event_ClearJoint
|
|
|
|
removes any custom transforms on the specified joint
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Event_ClearJoint( jointHandle_t jointnum ) {
|
|
animator.ClearJoint( jointnum );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Event_SetJointPos
|
|
|
|
modifies the position of the joint based on the transform type
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) {
|
|
animator.SetJointPos( jointnum, transform_type, pos );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Event_SetJointAngle
|
|
|
|
modifies the orientation of the joint based on the transform type
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ) {
|
|
idMat3 mat;
|
|
|
|
mat = angles.ToMat3();
|
|
animator.SetJointAxis( jointnum, transform_type, mat );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Event_GetJointPos
|
|
|
|
returns the position of the joint in worldspace
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) {
|
|
idVec3 offset;
|
|
idMat3 axis;
|
|
|
|
if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) {
|
|
gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() );
|
|
}
|
|
|
|
idThread::ReturnVector( offset );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAnimatedEntity::Event_GetJointAngle
|
|
|
|
returns the orientation of the joint in worldspace
|
|
================
|
|
*/
|
|
void idAnimatedEntity::Event_GetJointAngle( jointHandle_t jointnum ) {
|
|
idVec3 offset;
|
|
idMat3 axis;
|
|
|
|
if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) {
|
|
gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() );
|
|
}
|
|
|
|
idAngles ang = axis.ToAngles();
|
|
idVec3 vec( ang[ 0 ], ang[ 1 ], ang[ 2 ] );
|
|
idThread::ReturnVector( vec );
|
|
}
|