/*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #include "extdll.h" #include "util.h" #include "studio.h" #include "bs_defs.h" #include "r_studioint.h" #ifndef ACTIVITY_H #include "activity.h" #endif #ifndef ANIMATION_H #include "animation.h" #endif #ifndef SCRIPTEVENT_H #include "scriptevent.h" #endif #include "cbase.h" // Global engine <-> studio model rendering code interface float m_poseparameter[MAXSTUDIOPOSEPARAM]; // stub server_studio_api_t IEngineStudio; class CBaseBoneSetup : public CStudioBoneSetup { model_t *m_pSubModel; public: virtual void debugMsg( char *szFmt, ... ) { char buffer[2048]; // must support > 1k messages va_list args; va_start( args, szFmt ); Q_vsnprintf( buffer, 2048, szFmt, args ); va_end( args ); ALERT( at_console, "%s", buffer ); } virtual mstudioanim_t *GetAnimSourceData( mstudioseqdesc_t *pseqdesc ) { mstudioseqgroup_t *pseqgroup; cache_user_t *paSequences; pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; if( pseqdesc->seqgroup == 0 ) return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); assert( m_pSubModel ); // assume model is set paSequences = (cache_user_t *)m_pSubModel->submodels; if( paSequences == NULL ) { paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( MAXSTUDIOGROUPS, sizeof( cache_user_t )); m_pSubModel->submodels = (dmodel_t *)paSequences; } // check for already loaded if( !IEngineStudio.Cache_Check(( struct cache_user_s *)&(paSequences[pseqdesc->seqgroup] ))) { char filepath[128], modelpath[128], modelname[64]; COM_FileBase( m_pSubModel->name, modelname ); COM_ExtractFilePath( m_pSubModel->name, modelpath ); // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 ); ALERT( at_console, "loading: %s\n", filepath ); IEngineStudio.LoadCacheFile( filepath, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); } return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); } void SetBaseModel( model_t *mod ) { m_pSubModel = mod; } }; static CBaseBoneSetup g_boneSetup; //================================================================================================ // HUD_GetStudioModelInterface // Export this function for the engine to use the studio renderer class to render objects. //================================================================================================ int Server_GetBlendingInterface( int version, sv_blending_interface_t **ppinterface, server_studio_api_t *pstudio, float (*transform)[3][4], float (*bones)[MAXSTUDIOBONES][3][4] ) { if( version != SV_BLENDING_INTERFACE_VERSION ) return 0; ALERT( at_aiconsole, "Server_GetBlendingInterface()\n" ); // Copy in engine helper functions memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio )); // Success return 1; } void SetupModelBones( studiohdr_t *header ) { g_boneSetup.SetStudioPointers( header, m_poseparameter ); } void CalcDefaultPoseParameters( void *pmodel, float *poseparams ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); g_boneSetup.CalcDefaultPoseParameters( poseparams ); } CStudioBoneSetup *GetBaseBoneSetup( int modelindex, float *poseparams ) { studiohdr_t *pstudiohdr; model_t *mod = (model_t *)MODEL_HANDLE( modelindex ); if( !mod || mod->type != mod_studio ) return NULL; if( !( pstudiohdr = (studiohdr_t *)mod->cache.data )) return NULL; g_boneSetup.SetBaseModel( mod ); g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); return &g_boneSetup; } //========================================================= //========================================================= int LookupPoseParameter( void *pmodel, const char *szName, float *poseparams ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return -1; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); for( int i = 0; i < g_boneSetup.CountPoseParameters(); i++ ) { if( !Q_stricmp( g_boneSetup.pPoseParameter( i )->name, szName )) return i; } return -1; // Error } void SetPoseParameter( void *pmodel, int iParameter, float flValue, float *poseparams ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); g_boneSetup.SetPoseParameter( iParameter, flValue, poseparams[iParameter] ); } float GetPoseParameter( void *pmodel, int iParameter, float *poseparams ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return 0.0f; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); return g_boneSetup.GetPoseParameter( iParameter, poseparams[iParameter] ); } bool HasPoseParameter( void *pmodel, int iSequence, int iParameter ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return false; if( iSequence < 0 || iSequence >= pstudiohdr->numseq ) return false; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + iSequence; if( pseqdesc->blendtype[0] == iParameter || pseqdesc->blendtype[1] == iParameter ) return true; return false; } int FindHitboxSetByName( void *pmodel, const char *name ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return -1; SetupModelBones( pstudiohdr ); for( int i = 0; i < g_boneSetup.GetNumHitboxSets(); i++ ) { mstudiohitboxset_t *set = g_boneSetup.pHitboxSet( i ); if( !set ) continue; if( !Q_stricmp( set->name, name )) return i; } return -1; } int ExtractBbox( void *pmodel, int sequence, Vector &mins, Vector &maxs ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return 0; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); mins = pseqdesc[sequence].bbmin; maxs = pseqdesc[sequence].bbmax; return 1; } int LookupActivity( void *pmodel, int activity ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return ACTIVITY_NOT_AVAILABLE; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); int weighttotal = 0; int seq = ACTIVITY_NOT_AVAILABLE; for( int i = 0; i < pstudiohdr->numseq; i++ ) { if( pseqdesc[i].activity == activity ) { weighttotal += pseqdesc[i].actweight; if( !weighttotal || RANDOM_LONG( 0, weighttotal - 1 ) < pseqdesc[i].actweight ) seq = i; } } return seq; } int LookupActivityHeaviest( void *pmodel, int activity ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return ACTIVITY_NOT_AVAILABLE; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); int weight = 0; int seq = ACTIVITY_NOT_AVAILABLE; for( int i = 0; i < pstudiohdr->numseq; i++ ) { if( pseqdesc[i].activity == activity ) { if( pseqdesc[i].actweight > weight ) { weight = pseqdesc[i].actweight; seq = i; } } } return seq; } int GetEyePosition( void *pmodel, Vector &vecEyePosition ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return 0; vecEyePosition = pstudiohdr->eyeposition; return 1; } int LookupSequence( void *pmodel, const char *label ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return -1; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); for( int i = 0; i < pstudiohdr->numseq; i++ ) { if( !Q_stricmp( pseqdesc[i].label, label )) return i; } return -1; } int IsSoundEvent( int eventNumber ) { if( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) return 1; return 0; } void SequencePrecache( void *pmodel, const char *pSequenceName ) { int index = LookupSequence( pmodel, pSequenceName ); if( index >= 0 ) { studiohdr_t *pstudiohdr; pstudiohdr = (studiohdr_t *)pmodel; if( !pstudiohdr || index >= pstudiohdr->numseq ) return; mstudioseqdesc_t *pseqdesc; mstudioevent_t *pevent; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); for( int i = 0; i < pseqdesc->numevents; i++ ) { // don't send client-side events to the server AI if( pevent[i].event >= EVENT_CLIENT ) continue; if( IsSoundEvent( pevent[i].event )) { if( !Q_strlen(pevent[i].options )) { ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); } PRECACHE_SOUND( pevent[i].options ); } } } } void CalcGaitFrame( void *pmodel, float poseparams[], int &gaitsequence, float &flGaitFrame, float flGaitMovement ) { studiohdr_t *pstudiohdr; Vector vecMove, vecAngle; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return; if( gaitsequence < 0 || gaitsequence >= pstudiohdr->numseq ) gaitsequence = 0; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); g_boneSetup.SeqMovement( gaitsequence, 0.0f, 1.0f, vecMove, vecAngle ); int numframes = g_boneSetup.LocalMaxFrame( gaitsequence ); float fps = g_boneSetup.LocalFPS( gaitsequence ); mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + gaitsequence; float seqMovement = vecMove.Length(); // calc gait frame if( seqMovement > 0.0f ) flGaitFrame += (flGaitMovement / seqMovement) * numframes; else flGaitFrame += fps * gpGlobals->frametime; // do modulo flGaitFrame = fmod( flGaitFrame, (float)numframes ); while( flGaitFrame < 0.0 ) flGaitFrame += numframes; } void GetSequenceInfo( void *pmodel, float poseparams[], int sequence, float *pflFrameRate, float *pflGroundSpeed ) { studiohdr_t *pstudiohdr; Vector vecMove, vecAngle; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); mstudioseqdesc_t *pseqdesc; if( sequence >= pstudiohdr->numseq ) { *pflFrameRate = 0.0f; *pflGroundSpeed = 0.0f; return; } pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; g_boneSetup.SeqMovement( sequence, 0.0f, 1.0f, vecMove, vecAngle ); int numframes = g_boneSetup.LocalMaxFrame( sequence ); float fps = g_boneSetup.LocalFPS( sequence ); if( numframes > 1 ) { *pflFrameRate = 256.0f * fps / numframes; *pflGroundSpeed = vecMove.Length(); *pflGroundSpeed = *pflGroundSpeed * fps / numframes; } else { *pflFrameRate = 256.0f; *pflGroundSpeed = 0.0f; } } void GetSequenceLinearMotion( void *pmodel, float poseparams[], int sequence, Vector *pVec ) { studiohdr_t *pstudiohdr; Vector vecMove, vecAngle; *pVec = g_vecZero; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return; if( sequence < 0 || sequence >= pstudiohdr->numseq ) return; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); g_boneSetup.SeqMovement( sequence, 0.0f, 1.0f, vecMove, vecAngle ); *pVec = vecMove; } int GetSequenceCount( void *pmodel ) { studiohdr_t *pstudiohdr = (studiohdr_t *)pmodel; if ( !pstudiohdr ) return 0; return pstudiohdr->numseq; } int GetSequenceFlags( void *pmodel, int sequence ) { studiohdr_t *pstudiohdr; pstudiohdr = (studiohdr_t *)pmodel; if( !pstudiohdr || sequence >= pstudiohdr->numseq ) return 0; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; return pseqdesc->flags; } int GetAnimationEvent( void *pmodel, int sequence, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) { studiohdr_t *pstudiohdr; pstudiohdr = (studiohdr_t *)pmodel; if( !pstudiohdr || sequence >= pstudiohdr->numseq || !pMonsterEvent ) return 0; int events = 0; mstudioseqdesc_t *pseqdesc; mstudioevent_t *pevent; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) return 0; if( pseqdesc->numframes > 1 ) { flStart *= (pseqdesc->numframes - 1) / 256.0f; flEnd *= (pseqdesc->numframes - 1) / 256.0f; } else { flStart = 0.0f; flEnd = 1.0f; } for( ; index < pseqdesc->numevents; index++ ) { // Don't send client-side events to the server AI if( pevent[index].event >= EVENT_CLIENT ) continue; if(( pevent[index].frame >= flStart && pevent[index].frame < flEnd ) || ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) { pMonsterEvent->event = pevent[index].event; pMonsterEvent->options = pevent[index].options; return index + 1; } } return 0; } float SetController( void *pmodel, byte *controller, int iController, float flValue ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return flValue; mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); // find first controller that matches the index for( int i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++ ) { if( pbonecontroller->index == iController ) break; } if( i >= pstudiohdr->numbonecontrollers ) return flValue; // wrap 0..360 if it's a rotational controller if( pbonecontroller->type & ( STUDIO_XR|STUDIO_YR|STUDIO_ZR )) { // ugly hack, invert value if end < start if( pbonecontroller->end < pbonecontroller->start ) flValue = -flValue; // does the controller not wrap? if( pbonecontroller->start + 359.0f >= pbonecontroller->end ) { if( flValue > (( pbonecontroller->start + pbonecontroller->end ) / 2.0f ) + 180 ) flValue = flValue - 360; if( flValue < (( pbonecontroller->start + pbonecontroller->end ) / 2.0f ) - 180 ) flValue = flValue + 360; } else { if( flValue > 360.0f ) flValue = flValue - (int)(flValue / 360.0f) * 360.0f; else if( flValue < 0.0f ) flValue = flValue + (int)((flValue / -360.0f) + 1.0f) * 360.0f; } } int setting = 255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); setting = bound( 0, setting, 255 ); controller[iController] = setting; return setting * (1.0f / 255.0f) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; } // buz: GetControllerBound float GetControllerBound2( void *pmodel, int iController ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return 0; mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); // find first controller that matches the index for( int i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++ ) { if( pbonecontroller->index == iController ) break; } if( i >= pstudiohdr->numbonecontrollers ) return 0; float result = pbonecontroller->end; if (result < 0) result *= -1; return result; } float SetBlending( void *pmodel, int sequence, byte *blending, int iBlender, float flValue ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return flValue; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; if( pseqdesc->blendtype[iBlender] == 0 ) return flValue; if( pseqdesc->blendtype[iBlender] & ( STUDIO_XR|STUDIO_YR|STUDIO_ZR )) { // ugly hack, invert value if end < start if( pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender] ) flValue = -flValue; // does the controller not wrap? if( pseqdesc->blendstart[iBlender] + 359.0f >= pseqdesc->blendend[iBlender] ) { if( flValue > (( pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender] ) / 2.0f ) + 180.0f ) flValue = flValue - 360.0f; if( flValue < (( pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender] ) / 2.0f ) - 180.0f ) flValue = flValue + 360.0f; } } int setting = 255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]); setting = bound( 0, setting, 255 ); blending[iBlender] = setting; return setting * (1.0f / 255.0f) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; } int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return iGoalAnim; mstudioseqdesc_t *pseqdesc; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); // bail if we're going to or from a node 0 if( pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0 ) { return iGoalAnim; } int iEndNode; // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); if( *piDir > 0 ) { iEndNode = pseqdesc[iEndingAnim].exitnode; } else { iEndNode = pseqdesc[iEndingAnim].entrynode; } if( iEndNode == pseqdesc[iGoalAnim].entrynode ) { *piDir = 1; return iGoalAnim; } byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; if( iInternNode == 0 ) return iGoalAnim; // look for someone going for( int i = 0; i < pstudiohdr->numseq; i++ ) { if( pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode ) { *piDir = 1; return i; } if( pseqdesc[i].nodeflags ) { if( pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode ) { *piDir = -1; return i; } } } ALERT( at_console, "error in transition graph" ); return iGoalAnim; } void SetBodygroup( void *pmodel, int &iBody, int iGroup, int iValue ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return; if( iGroup > pstudiohdr->numbodyparts ) return; mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; if( iValue >= pbodypart->nummodels ) return; int iCurrent = (iBody / pbodypart->base) % pbodypart->nummodels; iBody = (iBody - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); } int GetBodygroup( void *pmodel, int iBody, int iGroup ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return 0; if( iGroup > pstudiohdr->numbodyparts ) return 0; mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; if( pbodypart->nummodels <= 1 ) return 0; int iCurrent = (iBody / pbodypart->base) % pbodypart->nummodels; return iCurrent; } int FindAttachmentByName( void *pmodel, const char *pName ) { studiohdr_t *pstudiohdr; if( !( pstudiohdr = (studiohdr_t *)pmodel )) return 0; for( int i = 0; i < pstudiohdr->numattachments; i++ ) { mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)pstudiohdr + pstudiohdr->attachmentindex); if( !Q_stricmp( pattachment[i].name, pName )) return i; } return -1; } float SequenceDuration( void *pmodel, float poseparams[], int iSequence ) { studiohdr_t *pstudiohdr; pstudiohdr = (studiohdr_t *)pmodel; if( !pstudiohdr ) return 0.1f; if( iSequence < 0 || iSequence >= pstudiohdr->numseq ) return 0.1f; g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); return g_boneSetup.LocalDuration( iSequence ); }