forked from valve/halflife-sdk
528 lines
13 KiB
C++
528 lines
13 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2001, 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "../common/nowin.h"
|
|
|
|
typedef int BOOL;
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
|
|
// hack into header files that we can ship
|
|
typedef int qboolean;
|
|
typedef unsigned char byte;
|
|
#include "../utils/common/mathlib.h"
|
|
#include "const.h"
|
|
#include "progdefs.h"
|
|
#include "edict.h"
|
|
#include "eiface.h"
|
|
|
|
#include "studio.h"
|
|
|
|
#ifndef ACTIVITY_H
|
|
#include "activity.h"
|
|
#endif
|
|
|
|
#include "activitymap.h"
|
|
|
|
#ifndef ANIMATION_H
|
|
#include "animation.h"
|
|
#endif
|
|
|
|
#ifndef SCRIPTEVENT_H
|
|
#include "scriptevent.h"
|
|
#endif
|
|
|
|
#ifndef ENGINECALLBACK_H
|
|
#include "enginecallback.h"
|
|
#endif
|
|
|
|
extern globalvars_t *gpGlobals;
|
|
|
|
#pragma warning( disable : 4244 )
|
|
|
|
|
|
|
|
int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioseqdesc_t *pseqdesc;
|
|
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex);
|
|
|
|
mins[0] = pseqdesc[ sequence ].bbmin[0];
|
|
mins[1] = pseqdesc[ sequence ].bbmin[1];
|
|
mins[2] = pseqdesc[ sequence ].bbmin[2];
|
|
|
|
maxs[0] = pseqdesc[ sequence ].bbmax[0];
|
|
maxs[1] = pseqdesc[ sequence ].bbmax[1];
|
|
maxs[2] = pseqdesc[ sequence ].bbmax[2];
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int LookupActivity( void *pmodel, entvars_t *pev, int activity )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
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, entvars_t *pev, int activity )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if ( !pstudiohdr )
|
|
return 0;
|
|
|
|
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;
|
|
}
|
|
|
|
void GetEyePosition ( void *pmodel, float *vecEyePosition )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
|
|
if ( !pstudiohdr )
|
|
{
|
|
ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" );
|
|
return;
|
|
}
|
|
|
|
VectorCopy ( pstudiohdr->eyeposition, vecEyePosition );
|
|
}
|
|
|
|
int LookupSequence( void *pmodel, const char *label )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioseqdesc_t *pseqdesc;
|
|
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex);
|
|
|
|
for (int i = 0; i < pstudiohdr->numseq; i++)
|
|
{
|
|
if (stricmp( pseqdesc[i].label, label ) == 0)
|
|
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;
|
|
|
|
// UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy
|
|
// of it's name if it is.
|
|
if ( IsSoundEvent( pevent[i].event ) )
|
|
{
|
|
if ( !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( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
return;
|
|
|
|
mstudioseqdesc_t *pseqdesc;
|
|
|
|
if (pev->sequence >= pstudiohdr->numseq)
|
|
{
|
|
*pflFrameRate = 0.0;
|
|
*pflGroundSpeed = 0.0;
|
|
return;
|
|
}
|
|
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence;
|
|
|
|
if (pseqdesc->numframes > 1)
|
|
{
|
|
*pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1);
|
|
*pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] );
|
|
*pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1);
|
|
}
|
|
else
|
|
{
|
|
*pflFrameRate = 256.0;
|
|
*pflGroundSpeed = 0.0;
|
|
}
|
|
}
|
|
|
|
|
|
int GetSequenceFlags( void *pmodel, entvars_t *pev )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq )
|
|
return 0;
|
|
|
|
mstudioseqdesc_t *pseqdesc;
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence;
|
|
|
|
return pseqdesc->flags;
|
|
}
|
|
|
|
|
|
int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent )
|
|
return 0;
|
|
|
|
int events = 0;
|
|
|
|
mstudioseqdesc_t *pseqdesc;
|
|
mstudioevent_t *pevent;
|
|
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->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.0;
|
|
flEnd *= (pseqdesc->numframes - 1) / 256.0;
|
|
}
|
|
else
|
|
{
|
|
flStart = 0;
|
|
flEnd = 1.0;
|
|
}
|
|
|
|
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, entvars_t *pev, int iController, float flValue )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
int i;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
return flValue;
|
|
|
|
mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex);
|
|
|
|
// find first controller that matches the index
|
|
for ( 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.0 >= pbonecontroller->end)
|
|
{
|
|
if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180)
|
|
flValue = flValue - 360;
|
|
if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180)
|
|
flValue = flValue + 360;
|
|
}
|
|
else
|
|
{
|
|
if (flValue > 360)
|
|
flValue = flValue - (int)(flValue / 360.0) * 360.0;
|
|
else if (flValue < 0)
|
|
flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0;
|
|
}
|
|
}
|
|
|
|
int setting = 255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start);
|
|
|
|
if (setting < 0) setting = 0;
|
|
if (setting > 255) setting = 255;
|
|
pev->controller[iController] = setting;
|
|
|
|
return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start;
|
|
}
|
|
|
|
|
|
float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
return flValue;
|
|
|
|
mstudioseqdesc_t *pseqdesc;
|
|
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->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.0 >= pseqdesc->blendend[iBlender])
|
|
{
|
|
if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180)
|
|
flValue = flValue - 360;
|
|
if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180)
|
|
flValue = flValue + 360;
|
|
}
|
|
}
|
|
|
|
int setting = 255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]);
|
|
|
|
if (setting < 0) setting = 0;
|
|
if (setting > 255) setting = 255;
|
|
|
|
pev->blending[iBlender] = setting;
|
|
|
|
return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender];
|
|
}
|
|
|
|
|
|
|
|
|
|
int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
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;
|
|
|
|
int i;
|
|
|
|
// look for someone going
|
|
for (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, entvars_t *pev, int iGroup, int iValue )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
return;
|
|
|
|
if (iGroup > pstudiohdr->numbodyparts)
|
|
return;
|
|
|
|
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup;
|
|
|
|
if (iValue >= pbodypart->nummodels)
|
|
return;
|
|
|
|
int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels;
|
|
|
|
pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base));
|
|
}
|
|
|
|
|
|
int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
|
|
pstudiohdr = (studiohdr_t *)pmodel;
|
|
if (! pstudiohdr)
|
|
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 = (pev->body / pbodypart->base) % pbodypart->nummodels;
|
|
|
|
return iCurrent;
|
|
}
|