2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
2023-10-12 19:42:49 +02:00
|
|
|
Copyright (C) 2023 the OpenMoHAA team
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
|
|
|
|
OpenMoHAA 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 2 of the License,
|
|
|
|
or (at your option) any later version.
|
|
|
|
|
|
|
|
OpenMoHAA 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 OpenMoHAA source code; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
// simpleactor.cpp: Base class for character AI
|
|
|
|
|
2018-09-05 16:55:10 +02:00
|
|
|
#include "actor.h"
|
|
|
|
#include "bg_local.h"
|
2023-04-29 21:56:38 +02:00
|
|
|
#include "scriptexception.h"
|
2023-10-12 20:06:00 +02:00
|
|
|
#include "scriptthread.h"
|
2023-10-12 21:30:53 +02:00
|
|
|
#include "../script/scriptclass.h"
|
2023-01-30 00:23:47 +01:00
|
|
|
#include <tiki.h>
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 21:30:53 +02:00
|
|
|
Event EV_NoAnimLerp
|
|
|
|
(
|
|
|
|
"noanimlerp",
|
|
|
|
EV_DEFAULT,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
"Do not LERP to the next animation",
|
|
|
|
EV_NORMAL
|
|
|
|
);
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
CLASS_DECLARATION(Sentient, SimpleActor, NULL) {
|
2023-10-12 21:30:53 +02:00
|
|
|
{&EV_NoAnimLerp, &SimpleActor::EventNoAnimLerp},
|
|
|
|
{NULL, NULL }
|
2016-03-27 11:49:47 +02:00
|
|
|
};
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
#define OVERLOADED_ERROR() assert(!"overloaded version should always get called")
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
SimpleActor::SimpleActor()
|
|
|
|
{
|
2023-10-12 19:42:49 +02:00
|
|
|
m_ChangeMotionAnimIndex = -1;
|
|
|
|
m_ChangeActionAnimIndex = -1;
|
|
|
|
m_ChangeSayAnimIndex = -1;
|
|
|
|
|
|
|
|
if (LoadingSavegame) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_AnimMotionHigh = true;
|
|
|
|
m_AnimActionHigh = true;
|
|
|
|
m_AnimDialogHigh = true;
|
|
|
|
|
|
|
|
m_fAimLimit_up = 60.0f;
|
|
|
|
m_fAimLimit_down = -60.0f;
|
|
|
|
|
|
|
|
VectorClear(m_DesiredGunDir);
|
|
|
|
VectorClear(m_DesiredLookAngles);
|
|
|
|
VectorClear(m_Dest);
|
|
|
|
VectorClear(m_NoClipDest);
|
|
|
|
|
|
|
|
for (int i = 0; i < MAX_FRAMEINFOS; i++) {
|
|
|
|
m_weightBase[i] = 0.0f;
|
|
|
|
m_weightCrossBlend[i] = 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_eEmotionMode = EMOTION_NEUTRAL;
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
m_csPathGoalEndAnimScript = STRING_EMPTY;
|
|
|
|
m_bNextForceStart = false;
|
|
|
|
m_fCrossblendTime = 0.5f;
|
|
|
|
m_csCurrentPosition = STRING_STAND;
|
|
|
|
m_fPathGoalTime = 0.0f;
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
m_csNextAnimString = STRING_NULL;
|
|
|
|
|
|
|
|
for (int i = 0; i < MAX_FRAMEINFOS; i++) {
|
|
|
|
m_weightType[i] = 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ChangeActionAnimIndex = -1;
|
|
|
|
m_ChangeActionAnimIndex = -1;
|
|
|
|
m_ChangeSayAnimIndex = -1;
|
|
|
|
|
|
|
|
m_bMotionAnimSet = false;
|
|
|
|
m_bActionAnimSet = false;
|
|
|
|
m_bSayAnimSet = false;
|
|
|
|
m_iVoiceTime = 0;
|
|
|
|
m_bAimAnimSet = false;
|
|
|
|
|
|
|
|
m_iMotionSlot = -1;
|
|
|
|
m_iActionSlot = -1;
|
|
|
|
m_iSaySlot = -1;
|
|
|
|
m_bLevelMotionAnim = false;
|
|
|
|
m_bLevelActionAnim = false;
|
|
|
|
m_bLevelSayAnim = false;
|
|
|
|
m_bNextLevelSayAnim = false;
|
|
|
|
m_DesiredYaw = 0.0f;
|
|
|
|
m_YawAchieved = true;
|
|
|
|
m_bPathErrorTime = -10000000;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
m_PainHandler.TrySetScript(STRING_ANIM_PAIN_SCR);
|
|
|
|
m_DeathHandler.TrySetScript(STRING_ANIM_KILLED_SCR);
|
|
|
|
m_AttackHandler.TrySetScript(STRING_ANIM_ATTACK_SCR);
|
|
|
|
m_SniperHandler.TrySetScript(STRING_ANIM_SNIPER_SCR);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
m_bHasDesiredLookDest = false;
|
|
|
|
m_bUpdateAnimDoneFlags = false;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
m_NearestNode = NULL;
|
|
|
|
m_fCrouchWeight = 0.0f;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
m_csMood = STRING_BORED;
|
|
|
|
m_csIdleMood = STRING_BORED;
|
|
|
|
|
|
|
|
m_groundPlane = qfalse;
|
|
|
|
m_walking = qfalse;
|
|
|
|
VectorClear(m_groundPlaneNormal);
|
|
|
|
m_maxspeed = 1000000.0f;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:45:16 +02:00
|
|
|
SimpleActor::~SimpleActor()
|
|
|
|
{
|
|
|
|
if (m_pAnimThread) {
|
|
|
|
delete m_pAnimThread->GetScriptClass();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::SetMoveInfo(mmove_t *)
|
|
|
|
{
|
|
|
|
OVERLOADED_ERROR();
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::GetMoveInfo(mmove_t *)
|
|
|
|
{
|
|
|
|
OVERLOADED_ERROR();
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
bool SimpleActor::CanSeeFrom(vec3_t pos, Entity *ent)
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
return false;
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
bool SimpleActor::CanTarget(void)
|
|
|
|
{
|
|
|
|
OVERLOADED_ERROR();
|
|
|
|
return false;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
bool SimpleActor::IsImmortal(void)
|
|
|
|
{
|
|
|
|
OVERLOADED_ERROR();
|
|
|
|
return false;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
bool SimpleActor::DoesTheoreticPathExist(Vector vDestPos, float fMaxPath)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:42:49 +02:00
|
|
|
return m_Path.DoesTheoreticPathExist(origin, vDestPos, this, fMaxPath, NULL, 0);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::SetPath(
|
|
|
|
Vector vDestPos, const char *description, int iMaxDirtyTime, float *vLeashHome, float fLeashDistSquared
|
2016-03-27 11:49:47 +02:00
|
|
|
)
|
|
|
|
{
|
2023-10-12 19:42:49 +02:00
|
|
|
if (!PathExists()
|
|
|
|
|| ((level.inttime >= m_Path.Time() + iMaxDirtyTime || m_Path.Complete(origin)) && PathGoal() != vDestPos)) {
|
|
|
|
m_Path.FindPath(origin, vDestPos, this, 0.0, vLeashHome, fLeashDistSquared);
|
|
|
|
|
|
|
|
if (!PathExists()) {
|
|
|
|
if (g_patherror->integer) {
|
|
|
|
if (description) {
|
|
|
|
int thinkState = ((Actor *)this)->m_ThinkState;
|
|
|
|
if (g_patherror->integer == 1
|
|
|
|
|| (g_patherror->integer == 2
|
|
|
|
&& (thinkState == THINKSTATE_IDLE || thinkState == THINKSTATE_CURIOUS))) {
|
|
|
|
if (m_bPathErrorTime + 5000 < level.inttime) {
|
|
|
|
m_bPathErrorTime = level.inttime;
|
|
|
|
Com_Printf(
|
|
|
|
"^~^~^ Path not found in '%s' for (entnum %d, radnum %d, targetname '%s') from (%f %f "
|
|
|
|
"%f) to (%f %f %f)\n",
|
|
|
|
description,
|
|
|
|
entnum,
|
|
|
|
radnum,
|
|
|
|
targetname.c_str(),
|
|
|
|
origin.x,
|
|
|
|
origin.y,
|
|
|
|
origin.z,
|
|
|
|
vDestPos.x,
|
|
|
|
vDestPos.y,
|
|
|
|
vDestPos.z
|
|
|
|
);
|
|
|
|
Com_Printf("Reason: %s\n", PathSearch::last_error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::SetPath(SimpleEntity *pDestNode, const char *description, int iMaxDirtyTime)
|
|
|
|
{
|
|
|
|
if (pDestNode) {
|
|
|
|
SetPath(pDestNode->origin, description, iMaxDirtyTime, NULL, 0.0);
|
|
|
|
} else {
|
|
|
|
if (m_bPathErrorTime + 5000 < level.inttime) {
|
|
|
|
m_bPathErrorTime = level.inttime;
|
|
|
|
Com_Printf(
|
|
|
|
"^~^~^ No destination node specified for '%s' at (%f %f %f)\n",
|
|
|
|
targetname.c_str(),
|
|
|
|
origin.x,
|
|
|
|
origin.y,
|
|
|
|
origin.z
|
|
|
|
);
|
|
|
|
}
|
|
|
|
ClearPath();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::SetPathWithinDistance(Vector vDestPos, char *description, float fMaxPath, int iMaxDirtyTime)
|
|
|
|
{
|
|
|
|
SetPath(vDestPos, description, iMaxDirtyTime, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::FindPathAway(vec_t *vAwayFrom, vec_t *vDirPreferred, float fMinSafeDist)
|
|
|
|
{
|
|
|
|
m_Path.FindPathAway(origin, vAwayFrom, vDirPreferred, this, fMinSafeDist, NULL, 0);
|
|
|
|
|
|
|
|
ShortenPathToAvoidSquadMates();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::ClearPath(void)
|
|
|
|
{
|
|
|
|
m_Path.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleActor::PathComplete(void) const
|
|
|
|
{
|
|
|
|
if (level.time >= m_fPathGoalTime) {
|
|
|
|
if (m_Path.Complete(origin)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleActor::PathExists(void) const
|
|
|
|
{
|
|
|
|
return m_Path.CurrentNode() != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleActor::PathIsValid(void) const
|
|
|
|
{
|
|
|
|
//Called by SetPath...
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleActor::PathAvoidsSquadMates(void) const
|
|
|
|
{
|
|
|
|
Entity *player;
|
|
|
|
float fDelta;
|
|
|
|
float fDistSoFar;
|
|
|
|
float fDistCap;
|
|
|
|
float vDelta2[2];
|
|
|
|
float vMins[3];
|
|
|
|
float vMaxs[3];
|
|
|
|
float vPos[3];
|
|
|
|
//Sentient *pOther;
|
|
|
|
Sentient *pBuddy[256];
|
|
|
|
int iNumBuddies;
|
|
|
|
int i;
|
|
|
|
//float fRatio;
|
|
|
|
|
|
|
|
if (ai_pathchecktime->value <= 0.0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
player = G_GetEntity(0);
|
|
|
|
if (!player) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//player = G_GetEntity(0);
|
|
|
|
//player = G_GetEntity(0);
|
|
|
|
VectorSub2D(player->origin, origin, vDelta2);
|
|
|
|
if (VectorLength2D(vDelta2) > Square(ai_pathcheckdist->value)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
fDistCap = (ai_pathchecktime->value * 250.0);
|
|
|
|
fDistSoFar = 0;
|
|
|
|
|
|
|
|
VectorCopy(vMins, CurrentPathNode()->point);
|
|
|
|
VectorCopy(vMaxs, CurrentPathNode()->point);
|
|
|
|
PathInfo *pNode = CurrentPathNode() - 1;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
if (pNode < LastPathNode()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fDistSoFar <= fDistCap) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
fDelta = fDistCap + 0.001 - fDistSoFar;
|
|
|
|
|
|
|
|
if (pNode->dist < fDelta) {
|
|
|
|
VectorCopy(pNode->point, vPos);
|
|
|
|
} else {
|
|
|
|
VectorSubtract(pNode[1].point, pNode[0].point, vPos);
|
|
|
|
VectorMA(pNode[1].point, fDelta / pNode->dist, vPos, vPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vPos[0] > vMaxs[0]) {
|
|
|
|
vMaxs[0] = vPos[0];
|
|
|
|
} else {
|
|
|
|
if (vPos[0] < vMins[0]) {
|
|
|
|
vMins[0] = vPos[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vPos[1] > vMaxs[1]) {
|
|
|
|
vMaxs[1] = vPos[1];
|
|
|
|
} else {
|
|
|
|
if (vPos[1] < vMins[1]) {
|
|
|
|
vMins[1] = vPos[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vPos[2] > vMaxs[2]) {
|
|
|
|
vMaxs[2] = vPos[2];
|
|
|
|
} else {
|
|
|
|
if (vPos[2] < vMins[2]) {
|
|
|
|
vMins[2] = vPos[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fDistSoFar += fDelta;
|
|
|
|
pNode--;
|
|
|
|
}
|
|
|
|
|
|
|
|
vMins[0] -= 30;
|
|
|
|
vMins[1] -= 30;
|
|
|
|
vMins[2] -= 94;
|
|
|
|
|
|
|
|
vMaxs[0] += 30;
|
|
|
|
vMaxs[1] += 30;
|
|
|
|
vMaxs[2] += 94;
|
|
|
|
|
|
|
|
iNumBuddies = 0;
|
|
|
|
if ((Sentient *)m_pNextSquadMate != this) {
|
|
|
|
do {
|
|
|
|
if (m_pNextSquadMate->origin[0] > vMins[0] && m_pNextSquadMate->origin[0] < vMaxs[0]
|
|
|
|
&& m_pNextSquadMate->origin[1] > vMins[1] && m_pNextSquadMate->origin[1] < vMaxs[1]
|
|
|
|
&& m_pNextSquadMate->origin[2] > vMins[2] && m_pNextSquadMate->origin[2] < vMaxs[2]) {
|
|
|
|
VectorSub2D(m_pNextSquadMate->origin, origin, vDelta2);
|
|
|
|
if (vDelta2[0] <= -32 || vDelta2[0] >= 32 || vDelta2[1] <= -32 || vDelta2[1] >= 32) {
|
|
|
|
if (DotProduct2D(vDelta2, m_pNextSquadMate->velocity) <= 0) {
|
|
|
|
pBuddy[iNumBuddies++] = m_pNextSquadMate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while ((Sentient *)m_pNextSquadMate != this && iNumBuddies <= 255);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iNumBuddies == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
float fDist;
|
|
|
|
while (1) {
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
if (iNumBuddies > 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
weird_lbl:
|
|
|
|
pNode++;
|
|
|
|
|
|
|
|
VectorCopy2D(pNode->point, vPos);
|
|
|
|
fDist = pNode->dist;
|
|
|
|
|
|
|
|
if (pNode >= CurrentPathNode()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
float fDot;
|
|
|
|
while (1) {
|
|
|
|
VectorSub2D(pBuddy[i]->origin, vPos, vDelta2);
|
|
|
|
if (VectorLength2D(vDelta2) <= 900) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fDot = DotProduct2D(vDelta2, pNode->dir);
|
|
|
|
if (fDot < 0.0 && -fDist <= fDot) {
|
|
|
|
if (Square(CrossProduct2D(vDelta2, pNode->dir)) <= 900) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (++i >= iNumBuddies) {
|
|
|
|
goto weird_lbl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::ShortenPathToAvoidSquadMates(void)
|
|
|
|
{
|
|
|
|
if (PathExists() && !PathComplete()) {
|
|
|
|
Vector vBuddyPos;
|
|
|
|
Vector vDelta;
|
|
|
|
Vector vGoal;
|
|
|
|
do {
|
|
|
|
vGoal = PathGoal();
|
|
|
|
Actor *pSquadMate = (Actor *)m_pNextSquadMate.Pointer();
|
|
|
|
if (pSquadMate == this) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
vGoal = PathGoal();
|
|
|
|
vBuddyPos = pSquadMate->origin;
|
|
|
|
if (pSquadMate->IsSubclassOfActor() && pSquadMate->PathExists()) {
|
|
|
|
vBuddyPos = pSquadMate->PathGoal();
|
|
|
|
}
|
|
|
|
vDelta.x = vGoal[0] - vBuddyPos.x;
|
|
|
|
if (vDelta.x >= -15.0 && vDelta.x <= 15.0) {
|
|
|
|
vDelta.y = vGoal[1] - vBuddyPos.y;
|
|
|
|
vDelta.z = vGoal[2] - vBuddyPos.z;
|
|
|
|
|
|
|
|
if (vDelta.y >= -15.0 && vDelta.y <= 15.0 && vDelta.z >= 0.0 && vDelta.z <= 94.0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pSquadMate = (Actor *)pSquadMate->m_pNextSquadMate.Pointer();
|
|
|
|
if (pSquadMate == this) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_Path.Shorten(45.0);
|
|
|
|
|
|
|
|
} while (PathExists());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector SimpleActor::PathGoal(void) const
|
|
|
|
{
|
|
|
|
return LastPathNode()->point;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleActor::PathGoalSlowdownStarted(void) const
|
|
|
|
{
|
|
|
|
return m_fPathGoalTime >= level.time;
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
float *SimpleActor::PathDelta(void) const
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
return m_Path.CurrentDelta();
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
PathInfo *SimpleActor::CurrentPathNode(void) const
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
return m_Path.CurrentNode();
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
PathInfo *SimpleActor::LastPathNode(void) const
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
return m_Path.LastNode();
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
float SimpleActor::PathDist(void) const
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
return m_Path.TotalDist();
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
bool SimpleActor::PathHasCompleteLookahead(void) const
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
return m_Path.HasCompleteLookahead();
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::UpdateEmotion(void)
|
|
|
|
{
|
|
|
|
int anim;
|
|
|
|
if (IsDead()) {
|
|
|
|
Anim_Emotion(EMOTION_DEAD);
|
|
|
|
}
|
|
|
|
|
|
|
|
anim = GetEmotionAnim();
|
|
|
|
|
|
|
|
if (anim == -1) {
|
|
|
|
Com_Printf(
|
|
|
|
"Failed to set emotion for (entnum %d, radnum %d, targetname '%s'\n", entnum, radnum, targetname.c_str()
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
m_bSayAnimSet = true;
|
|
|
|
StartSayAnimSlot(anim);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
int SimpleActor::GetMotionSlot(int slot)
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
if (m_AnimMotionHigh) {
|
|
|
|
return slot + 3;
|
2023-10-12 19:42:49 +02:00
|
|
|
} else {
|
2023-10-12 19:54:00 +02:00
|
|
|
return slot;
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
2023-10-12 19:54:00 +02:00
|
|
|
}
|
2023-10-12 19:42:49 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::ChangeMotionAnim(void)
|
|
|
|
{
|
|
|
|
//int lastMotionSlot;
|
|
|
|
//int firstMotionSlot;
|
|
|
|
int iSlot;
|
|
|
|
int i;
|
2023-10-12 19:42:49 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
m_bMotionAnimSet = false;
|
|
|
|
m_iMotionSlot = -1;
|
|
|
|
m_bLevelMotionAnim = false;
|
|
|
|
|
|
|
|
if (m_ChangeMotionAnimIndex != level.frame_skel_index) {
|
|
|
|
m_ChangeMotionAnimIndex = level.frame_skel_index;
|
|
|
|
|
|
|
|
MPrintf("Swapping motion channels....\n");
|
|
|
|
for (iSlot = GetMotionSlot(0), i = 0; i < 3; i++, iSlot++) {
|
|
|
|
StartCrossBlendAnimSlot(iSlot);
|
|
|
|
}
|
|
|
|
m_AnimDialogHigh = !m_AnimDialogHigh;
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
for (iSlot = GetMotionSlot(0), i = 0; i < 3; i++, iSlot++) {
|
|
|
|
StopAnimating(iSlot);
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int SimpleActor::GetActionSlot(int slot)
|
|
|
|
{
|
|
|
|
if (m_AnimActionHigh) {
|
|
|
|
return slot + 9;
|
|
|
|
} else {
|
|
|
|
return slot + 6;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::ChangeActionAnim(void)
|
|
|
|
{
|
|
|
|
int iSlot;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
m_bAimAnimSet = false;
|
|
|
|
m_bActionAnimSet = false;
|
|
|
|
m_iActionSlot = -1;
|
|
|
|
m_bLevelActionAnim = false;
|
|
|
|
|
|
|
|
if (m_ChangeActionAnimIndex != level.frame_skel_index) {
|
|
|
|
m_ChangeActionAnimIndex = level.frame_skel_index;
|
|
|
|
|
|
|
|
MPrintf("Swapping action channels....\n");
|
|
|
|
|
|
|
|
iSlot = GetActionSlot(0);
|
|
|
|
for (i = iSlot; i < iSlot + 3; i++) {
|
|
|
|
animFlags[i] |= ANIM_NOACTION;
|
|
|
|
StartCrossBlendAnimSlot(i);
|
|
|
|
}
|
|
|
|
m_AnimDialogHigh = !m_AnimDialogHigh; // toggle
|
|
|
|
}
|
|
|
|
|
|
|
|
iSlot = GetActionSlot(0);
|
|
|
|
for (i = iSlot; i < iSlot + 3; i++) {
|
|
|
|
StopAnimating(iSlot);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
int SimpleActor::GetSaySlot(void)
|
|
|
|
{
|
|
|
|
return m_AnimDialogHigh ? 13 : 12;
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::ChangeSayAnim(void)
|
|
|
|
{
|
|
|
|
int iSlot;
|
|
|
|
|
|
|
|
m_bSayAnimSet = false;
|
|
|
|
m_bLevelSayAnim = 0;
|
|
|
|
m_iVoiceTime = level.inttime;
|
|
|
|
m_iSaySlot = -1;
|
|
|
|
|
|
|
|
if (m_ChangeSayAnimIndex != level.frame_skel_index) {
|
|
|
|
m_ChangeSayAnimIndex = level.frame_skel_index;
|
|
|
|
|
|
|
|
MPrintf("Swapping dialog channel....\n");
|
|
|
|
|
|
|
|
iSlot = GetSaySlot();
|
|
|
|
StartCrossBlendAnimSlot(iSlot);
|
|
|
|
|
|
|
|
m_AnimDialogHigh = !m_AnimDialogHigh; // toggle
|
|
|
|
}
|
|
|
|
|
|
|
|
iSlot = GetSaySlot();
|
|
|
|
StopAnimating(iSlot);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::StopAnimating(int slot)
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
m_weightType[slot] = 0;
|
|
|
|
DoExitCommands(slot);
|
|
|
|
|
|
|
|
if (edict->s.frameInfo[slot].index || gi.TIKI_NumAnims(edict->tiki) <= 1) {
|
|
|
|
edict->s.frameInfo[slot].index = 0;
|
|
|
|
} else {
|
|
|
|
edict->s.frameInfo[slot].index = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
animFlags[slot] = ANIM_LOOP | ANIM_NODELTA | ANIM_NOEXIT | ANIM_PAUSED;
|
|
|
|
edict->s.frameInfo[slot].weight = 0;
|
|
|
|
animtimes[slot] = 0;
|
|
|
|
animFlags[slot] = (animFlags[slot] | ANIM_NODELTA) & ~ANIM_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventSetAnimLength(Event *ev)
|
|
|
|
{
|
|
|
|
int slot;
|
|
|
|
float length;
|
|
|
|
|
|
|
|
if (ev->NumArgs() != 1) {
|
|
|
|
ScriptError("bad number of arguments");
|
|
|
|
}
|
|
|
|
|
|
|
|
length = ev->GetFloat(1);
|
|
|
|
|
|
|
|
if (length <= 0) {
|
|
|
|
ScriptError("Positive lengths only allowed");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_bMotionAnimSet) {
|
|
|
|
ScriptError("Must set anim before length");
|
|
|
|
}
|
|
|
|
|
|
|
|
slot = GetMotionSlot(0);
|
|
|
|
|
|
|
|
if (animFlags[slot] & ANIM_LOOP) {
|
|
|
|
gi.Anim_Frametime(edict->tiki, edict->s.frameInfo[slot].index);
|
|
|
|
|
|
|
|
animFlags[slot] = (animFlags[slot] | ANIM_NODELTA) & ~ANIM_FINISHED;
|
|
|
|
|
|
|
|
animtimes[slot] = Square(gi.Anim_NumFrames(edict->tiki, edict->s.frameInfo[slot].index) - 1);
|
|
|
|
|
|
|
|
SetOnceType(slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
SetSyncTime(0);
|
|
|
|
|
|
|
|
if (length > animtimes[slot]) {
|
|
|
|
ScriptError("cannot lengthen animation which has length %f", animtimes[slot]);
|
|
|
|
}
|
|
|
|
|
|
|
|
animtimes[slot] = length;
|
|
|
|
animFlags[slot] = (animFlags[slot] | ANIM_NODELTA) & ~ANIM_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventSetCrossblendTime(Event *ev)
|
|
|
|
{
|
|
|
|
m_fCrossblendTime = ev->GetFloat(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventGetCrossblendTime(Event *ev)
|
|
|
|
{
|
|
|
|
ev->AddFloat(m_fCrossblendTime);
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::StartCrossBlendAnimSlot(int slot)
|
|
|
|
{
|
|
|
|
if (m_weightType[slot] == 1) {
|
|
|
|
m_weightType[slot] = 4;
|
|
|
|
} else {
|
|
|
|
if (m_weightType[slot] < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m_weightType[slot] == 6) {
|
|
|
|
m_weightType[slot] = 5;
|
|
|
|
} else {
|
|
|
|
m_weightType[slot] = 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_weightCrossBlend[slot] = 1.0;
|
|
|
|
m_weightBase[slot] = edict->s.frameInfo[slot].weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::StartMotionAnimSlot(int slot, int anim, float weight)
|
|
|
|
{
|
|
|
|
int iSlot = GetMotionSlot(slot);
|
|
|
|
|
|
|
|
m_weightCrossBlend[iSlot] = 0.0;
|
|
|
|
m_weightType[iSlot] = 1;
|
|
|
|
m_weightBase[iSlot] = weight;
|
|
|
|
|
|
|
|
NewAnim(anim, iSlot, 1.0);
|
|
|
|
animFlags[iSlot] |= ANIM_NOACTION;
|
|
|
|
|
|
|
|
SetTime(iSlot, 0.0);
|
|
|
|
|
|
|
|
UpdateNormalAnimSlot(iSlot);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::StartAimMotionAnimSlot(int slot, int anim)
|
|
|
|
{
|
|
|
|
int iSlot = GetMotionSlot(slot);
|
|
|
|
|
|
|
|
m_weightCrossBlend[iSlot] = 0.0;
|
|
|
|
m_weightType[iSlot] = 1;
|
|
|
|
|
|
|
|
NewAnim(anim, iSlot, 1.0);
|
|
|
|
animFlags[iSlot] |= ANIM_NOACTION;
|
|
|
|
|
|
|
|
SetTime(iSlot, 0.0);
|
|
|
|
|
|
|
|
UpdateNormalAnimSlot(iSlot);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::StartActionAnimSlot(int anim)
|
|
|
|
{
|
|
|
|
int iSlot = GetActionSlot(0);
|
|
|
|
|
|
|
|
m_weightCrossBlend[iSlot] = 0.0;
|
|
|
|
m_weightType[iSlot] = 2;
|
|
|
|
m_weightBase[iSlot] = 1.0;
|
|
|
|
|
|
|
|
NewAnim(anim, iSlot, 1.0);
|
|
|
|
|
|
|
|
SetTime(iSlot, 0.0);
|
|
|
|
|
|
|
|
UpdateNormalAnimSlot(iSlot);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::StartSayAnimSlot(int anim)
|
|
|
|
{
|
|
|
|
int iSlot = GetSaySlot();
|
|
|
|
|
|
|
|
m_weightCrossBlend[iSlot] = 0.0;
|
|
|
|
m_weightType[iSlot] = 6;
|
|
|
|
m_weightBase[iSlot] = 1.0;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
NewAnim(anim, iSlot, 1.0);
|
|
|
|
animFlags[iSlot] |= ANIM_NOACTION;
|
|
|
|
|
|
|
|
SetTime(iSlot, 0.0);
|
|
|
|
UpdateSayAnimSlot(iSlot);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::StartAimAnimSlot(int slot, int anim)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:42:49 +02:00
|
|
|
int iSlot = GetActionSlot(slot);
|
|
|
|
|
|
|
|
m_weightCrossBlend[iSlot] = 0.0;
|
|
|
|
m_weightType[iSlot] = 7;
|
|
|
|
|
|
|
|
NewAnim(anim, iSlot, 1.0);
|
|
|
|
|
|
|
|
SetTime(iSlot, 0.0);
|
|
|
|
|
|
|
|
UpdateNormalAnimSlot(iSlot);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::SetBlendedWeight(int slot)
|
|
|
|
{
|
|
|
|
m_bUpdateAnimDoneFlags |= 1 << slot;
|
|
|
|
if (m_weightCrossBlend[slot] < 1.0) {
|
|
|
|
edict->s.frameInfo[slot].weight = (3.0 - m_weightCrossBlend[slot] - m_weightCrossBlend[slot])
|
|
|
|
* Square(m_weightCrossBlend[slot]) * m_weightBase[slot];
|
|
|
|
} else {
|
|
|
|
m_weightCrossBlend[slot] = 1.0;
|
|
|
|
edict->s.frameInfo[slot].weight = m_weightBase[slot];
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::UpdateNormalAnimSlot(int slot)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:42:49 +02:00
|
|
|
m_weightCrossBlend[slot] += m_fCrossblendTime == 0.0 ? 1.0 : level.frametime / m_fCrossblendTime;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
SetBlendedWeight(slot);
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::UpdateCrossBlendAnimSlot(int slot)
|
|
|
|
{
|
|
|
|
m_weightCrossBlend[slot] -= m_fCrossblendTime == 0.0 ? 1.0 : level.frametime / m_fCrossblendTime;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
if (m_weightCrossBlend[slot] > 0.0) {
|
|
|
|
SetBlendedWeight(slot);
|
|
|
|
} else {
|
|
|
|
m_weightType[slot] = 8;
|
|
|
|
edict->s.frameInfo[slot].weight = 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::UpdateCrossBlendDialogAnimSlot(int slot)
|
|
|
|
{
|
|
|
|
m_weightCrossBlend[slot] -= m_iSaySlot < 0 ? level.frametime + level.frametime : level.frametime / 0.1;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
if (m_weightCrossBlend[slot] > 0.0) {
|
|
|
|
SetBlendedWeight(slot);
|
|
|
|
} else {
|
|
|
|
m_weightType[slot] = 8;
|
|
|
|
edict->s.frameInfo[slot].weight = 0.0;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::UpdateSayAnimSlot(int slot)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:42:49 +02:00
|
|
|
m_weightCrossBlend[slot] += m_iSaySlot < 0 ? level.frametime + level.frametime : level.frametime / 0.1;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
SetBlendedWeight(slot);
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::UpdateLastFrameSlot(int slot)
|
|
|
|
{
|
|
|
|
StopAnimating(slot);
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::UpdateAnimSlot(int slot)
|
|
|
|
{
|
|
|
|
int weightType = m_weightType[slot];
|
|
|
|
switch (weightType) {
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
case 2:
|
|
|
|
case 7:
|
|
|
|
UpdateNormalAnimSlot(slot);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
case 4:
|
|
|
|
UpdateCrossBlendAnimSlot(slot);
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
UpdateCrossBlendDialogAnimSlot(slot);
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
UpdateSayAnimSlot(slot);
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
UpdateLastFrameSlot(slot);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(weightType && !"impleActor::UpdateAnimSlot: Bad weight type.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::StopAllAnimating(void)
|
|
|
|
{
|
|
|
|
SetSyncTime(0);
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
for (int slot = 0; slot < MAX_FRAMEINFOS; slot++) {
|
|
|
|
StopAnimating(slot);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::UpdateAim(void)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
float dir;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
int aimForwardSlot;
|
|
|
|
int aimUpSlot;
|
|
|
|
int aimDownSlot;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
if (m_bAimAnimSet) {
|
|
|
|
dir = AngleNormalize180(-m_DesiredGunDir[0]);
|
2023-10-12 19:42:49 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
aimForwardSlot = GetActionSlot(0);
|
|
|
|
aimUpSlot = aimForwardSlot + 1;
|
|
|
|
aimDownSlot = aimForwardSlot + 2;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
float factor;
|
|
|
|
if (dir < 0) {
|
|
|
|
if (dir < m_fAimLimit_down) {
|
|
|
|
dir = m_fAimLimit_down;
|
|
|
|
}
|
2023-10-12 19:42:49 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
factor = dir / m_fAimLimit_down;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
m_weightBase[aimForwardSlot] = 0;
|
|
|
|
m_weightBase[aimUpSlot] = 1 - factor;
|
|
|
|
m_weightBase[aimDownSlot] = factor;
|
|
|
|
} else {
|
|
|
|
if (dir > m_fAimLimit_up) {
|
|
|
|
dir = m_fAimLimit_up;
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
factor = dir / m_fAimLimit_up;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
m_weightBase[aimForwardSlot] = factor;
|
|
|
|
m_weightBase[aimUpSlot] = 1 - factor;
|
|
|
|
m_weightBase[aimDownSlot] = 0;
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
2023-10-12 19:54:00 +02:00
|
|
|
SetControllerAngles(TORSO_TAG, vec_origin);
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
2023-10-12 19:54:00 +02:00
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::UpdateAimMotion(void)
|
|
|
|
{
|
|
|
|
int slot = GetMotionSlot(0);
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
if (m_fCrouchWeight < 0.0) {
|
|
|
|
m_weightBase[slot] = 0.0;
|
|
|
|
m_weightBase[slot + 1] = m_fCrouchWeight + 1.0;
|
|
|
|
m_weightBase[slot + 2] = -m_fCrouchWeight;
|
|
|
|
} else {
|
|
|
|
m_weightBase[slot] = m_fCrouchWeight;
|
|
|
|
m_weightBase[slot + 1] = 1.0 - m_fCrouchWeight;
|
|
|
|
m_weightBase[slot + 2] = 0.0;
|
|
|
|
}
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::EventGetPosition(Event *ev)
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
ev->AddConstString(m_csCurrentPosition);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::EventSetPosition(Event *ev)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
m_csCurrentPosition = ev->GetConstString(1);
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::EventSetEmotion(Event *ev)
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
switch (ev->GetConstString(1)) {
|
|
|
|
case STRING_EMOTION_NONE:
|
|
|
|
Anim_Emotion(EMOTION_NONE);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_NEUTRAL:
|
|
|
|
Anim_Emotion(EMOTION_NEUTRAL);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_WORRY:
|
|
|
|
Anim_Emotion(EMOTION_WORRY);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_PANIC:
|
|
|
|
Anim_Emotion(EMOTION_PANIC);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_FEAR:
|
|
|
|
Anim_Emotion(EMOTION_FEAR);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_DISGUST:
|
|
|
|
Anim_Emotion(EMOTION_DISGUST);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_ANGER:
|
|
|
|
Anim_Emotion(EMOTION_ANGER);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_AIMING:
|
|
|
|
Anim_Emotion(EMOTION_AIMING);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_DETERMINED:
|
|
|
|
Anim_Emotion(EMOTION_DETERMINED);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_DEAD:
|
|
|
|
Anim_Emotion(EMOTION_DEAD);
|
|
|
|
break;
|
|
|
|
case STRING_EMOTION_CURIOUS:
|
|
|
|
Anim_Emotion(EMOTION_CURIOUS);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(!"Unknown emotion mode specified in script.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int SimpleActor::GetEmotionAnim(void)
|
|
|
|
{
|
|
|
|
const char *emotionanim = NULL;
|
|
|
|
int anim;
|
|
|
|
|
|
|
|
if (m_eEmotionMode) {
|
|
|
|
switch (m_eEmotionMode) {
|
|
|
|
case EMOTION_NEUTRAL:
|
|
|
|
case EMOTION_AIMING:
|
|
|
|
emotionanim = "facial_idle_neutral";
|
|
|
|
break;
|
|
|
|
case EMOTION_WORRY:
|
|
|
|
emotionanim = "facial_idle_worry";
|
|
|
|
break;
|
|
|
|
case EMOTION_PANIC:
|
|
|
|
emotionanim = "facial_idle_panic";
|
|
|
|
break;
|
|
|
|
case EMOTION_FEAR:
|
|
|
|
emotionanim = "facial_idle_fear";
|
|
|
|
break;
|
|
|
|
case EMOTION_DISGUST:
|
|
|
|
emotionanim = "facial_idle_disgust";
|
|
|
|
break;
|
|
|
|
case EMOTION_ANGER:
|
|
|
|
emotionanim = "facial_idle_anger";
|
|
|
|
break;
|
|
|
|
case EMOTION_DETERMINED:
|
|
|
|
case EMOTION_CURIOUS:
|
|
|
|
emotionanim = "facial_idle_determined";
|
|
|
|
break;
|
|
|
|
case EMOTION_DEAD:
|
|
|
|
emotionanim = "facial_idle_dead";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
|
|
|
|
char assertStr[16317] = {0};
|
|
|
|
strcpy(assertStr, "\"Unknown value for m_EmotionMode in SimpleActor::GetEmotionAnim\"\n\tMessage: ");
|
|
|
|
Q_strcat(assertStr, sizeof(assertStr), DumpCallTrace(""));
|
|
|
|
assert(!assertStr);
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
}
|
2023-10-12 19:42:49 +02:00
|
|
|
} else {
|
2023-10-12 19:54:00 +02:00
|
|
|
if (m_csMood == STRING_NERVOUS) {
|
|
|
|
emotionanim = "facial_idle_determined";
|
|
|
|
} else if (m_csMood <= STRING_NERVOUS) {
|
|
|
|
if (m_csMood != STRING_BORED) {
|
|
|
|
assert(!"Unknown value for m_csMood");
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
emotionanim = "facial_idle_neutral";
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (m_csMood == STRING_CURIOUS) {
|
|
|
|
emotionanim = "facial_idle_determined";
|
|
|
|
} else if (m_csMood != STRING_ALERT) {
|
|
|
|
assert(!"Unknown value for m_csMood");
|
|
|
|
return -1;
|
|
|
|
} else {
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
}
|
2023-10-12 19:54:00 +02:00
|
|
|
|
|
|
|
if (emotionanim == NULL) {
|
|
|
|
emotionanim = "facial_idle_anger";
|
|
|
|
//assert(!"Unexpected behaviour in SimpleActor::GetEmotionAnim");
|
|
|
|
}
|
|
|
|
|
|
|
|
anim = gi.Anim_NumForName(edict->tiki, emotionanim);
|
|
|
|
if (anim == -1) {
|
|
|
|
Com_Printf(
|
|
|
|
"^~^~^ SimpleActor::GetEmotionAnim: unknown animation '%s' in '%s'\n", emotionanim, edict->tiki->a->name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return anim;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventSetAnimFinal(Event *ev)
|
|
|
|
{
|
|
|
|
ScriptError("animfinal is obsolete");
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::EventGetWeaponType(Event *ev)
|
|
|
|
{
|
|
|
|
Weapon *weapon;
|
|
|
|
const_str csWeaponType;
|
|
|
|
|
|
|
|
if (!m_pTurret) {
|
|
|
|
weapon = GetActiveWeapon(WEAPON_MAIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!weapon) {
|
|
|
|
csWeaponType = STRING_RIFLE;
|
|
|
|
} else {
|
|
|
|
int iWeaponClass = weapon->GetWeaponClass();
|
|
|
|
|
|
|
|
switch (iWeaponClass) {
|
|
|
|
case WEAPON_CLASS_PISTOL:
|
|
|
|
csWeaponType = STRING_PISTOL;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_RIFLE:
|
|
|
|
csWeaponType = STRING_RIFLE;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_SMG:
|
|
|
|
csWeaponType = STRING_SMG;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_MG:
|
|
|
|
csWeaponType = STRING_MG;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_GRENADE:
|
|
|
|
csWeaponType = STRING_GRENADE;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_HEAVY:
|
|
|
|
csWeaponType = STRING_HEAVY;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_CANNON:
|
|
|
|
csWeaponType = STRING_CANNON;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_ITEM:
|
|
|
|
csWeaponType = STRING_ITEM;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_ITEM2:
|
|
|
|
csWeaponType = STRING_ITEM2;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_ITEM3:
|
|
|
|
csWeaponType = STRING_ITEM3;
|
|
|
|
break;
|
|
|
|
case WEAPON_CLASS_ITEM4:
|
|
|
|
csWeaponType = STRING_ITEM4;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
csWeaponType = STRING_EMPTY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
ev->AddConstString(csWeaponType);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::EventGetWeaponGroup(Event *ev)
|
|
|
|
{
|
|
|
|
const_str csWeaponGroup;
|
|
|
|
Weapon *weapon = GetActiveWeapon(WEAPON_MAIN);
|
|
|
|
if (!weapon) {
|
|
|
|
csWeaponGroup = STRING_UNARMED;
|
|
|
|
} else {
|
|
|
|
csWeaponGroup = weapon->GetWeaponGroup();
|
|
|
|
if (csWeaponGroup == STRING_EMPTY) {
|
|
|
|
csWeaponGroup = STRING_UNARMED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ev->AddConstString(csWeaponGroup);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventAIOn(Event *ev)
|
|
|
|
{
|
|
|
|
m_bDoAI = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventAIOff(Event *ev)
|
|
|
|
{
|
|
|
|
m_bDoAI = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::AnimFinished(int slot)
|
|
|
|
{
|
|
|
|
assert(!DumpCallTrace(""));
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::EventGetPainHandler(Event *ev)
|
|
|
|
{
|
|
|
|
ScriptVariable var;
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
m_PainHandler.GetScriptValue(&var);
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
ev->AddValue(var);
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
void SimpleActor::EventSetPainHandler(Event *ev)
|
|
|
|
{
|
|
|
|
if (ev->IsFromScript()) {
|
|
|
|
ScriptVariable var = ev->GetValue(1);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-10-12 19:42:49 +02:00
|
|
|
m_PainHandler.SetScript(var);
|
|
|
|
} else {
|
|
|
|
str varString = ev->GetString(1);
|
|
|
|
m_PainHandler.SetScript(varString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventGetDeathHandler(Event *ev)
|
|
|
|
{
|
|
|
|
ScriptVariable var;
|
|
|
|
|
|
|
|
m_DeathHandler.GetScriptValue(&var);
|
|
|
|
|
|
|
|
ev->AddValue(var);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventSetDeathHandler(Event *ev)
|
|
|
|
{
|
|
|
|
if (ev->IsFromScript()) {
|
|
|
|
ScriptVariable var = ev->GetValue(1);
|
|
|
|
|
|
|
|
m_DeathHandler.SetScript(var);
|
|
|
|
} else {
|
|
|
|
str varString = ev->GetString(1);
|
|
|
|
m_DeathHandler.SetScript(varString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventGetAttackHandler(Event *ev)
|
|
|
|
{
|
|
|
|
ScriptVariable var;
|
|
|
|
|
|
|
|
m_AttackHandler.GetScriptValue(&var);
|
|
|
|
|
|
|
|
ev->AddValue(var);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleActor::EventSetAttackHandler(Event *ev)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:42:49 +02:00
|
|
|
if (ev->IsFromScript()) {
|
|
|
|
ScriptVariable var = ev->GetValue(1);
|
|
|
|
|
|
|
|
m_AttackHandler.SetScript(var);
|
|
|
|
} else {
|
|
|
|
str varString = ev->GetString(1);
|
|
|
|
m_AttackHandler.SetScript(varString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
bool SimpleActor::UpdateSelectedAnimation(void)
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
if (m_csNextAnimString == STRING_NULL) {
|
|
|
|
if (m_bNextForceStart) {
|
|
|
|
m_Anim = m_NextAnimLabel;
|
|
|
|
m_eAnimMode = m_eNextAnimMode;
|
2023-10-14 14:10:16 +02:00
|
|
|
if (m_eNextAnimMode != ANIM_MODE_PATH_GOAL) {
|
2023-10-12 19:54:00 +02:00
|
|
|
SetPathGoalEndAnim(STRING_EMPTY);
|
|
|
|
}
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
return true;
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
if (m_pAnimThread) {
|
|
|
|
if (m_eAnimMode == m_eNextAnimMode) {
|
|
|
|
if (m_Anim == m_NextAnimLabel) {
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_Anim = m_NextAnimLabel;
|
|
|
|
m_eAnimMode = m_eNextAnimMode;
|
2023-10-14 14:10:16 +02:00
|
|
|
if (m_eNextAnimMode != ANIM_MODE_PATH_GOAL) {
|
2023-10-12 19:54:00 +02:00
|
|
|
SetPathGoalEndAnim(STRING_EMPTY);
|
|
|
|
}
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
return true;
|
|
|
|
}
|
2018-09-05 16:55:10 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
if (m_bNextForceStart) {
|
|
|
|
Com_Printf("UpdateSelectedAnimation\n");
|
|
|
|
m_Anim.TrySetScript(m_csNextAnimString);
|
|
|
|
m_eAnimMode = m_eNextAnimMode;
|
2023-10-14 14:10:16 +02:00
|
|
|
if (m_eNextAnimMode != ANIM_MODE_PATH_GOAL) {
|
2023-10-12 19:54:00 +02:00
|
|
|
SetPathGoalEndAnim(STRING_EMPTY);
|
|
|
|
}
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_pAnimThread || m_eAnimMode != m_eNextAnimMode) {
|
|
|
|
m_Anim.TrySetScript(m_csNextAnimString);
|
|
|
|
m_eAnimMode = m_eNextAnimMode;
|
2023-10-14 14:10:16 +02:00
|
|
|
if (m_eNextAnimMode != ANIM_MODE_PATH_GOAL) {
|
2023-10-12 19:54:00 +02:00
|
|
|
SetPathGoalEndAnim(STRING_EMPTY);
|
|
|
|
}
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
return true;
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
2023-10-12 19:54:00 +02:00
|
|
|
|
|
|
|
if (m_fPathGoalTime <= level.time) {
|
|
|
|
if (!m_Anim.IsFile(m_csNextAnimString)) {
|
|
|
|
m_Anim.TrySetScript(m_csNextAnimString);
|
|
|
|
m_eAnimMode = m_eNextAnimMode;
|
2023-10-14 14:10:16 +02:00
|
|
|
if (m_eNextAnimMode != ANIM_MODE_PATH_GOAL) {
|
2023-10-12 19:54:00 +02:00
|
|
|
SetPathGoalEndAnim(STRING_EMPTY);
|
|
|
|
}
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_eNextAnimMode = -1;
|
|
|
|
if (m_bStartPathGoalEndAnim) {
|
|
|
|
m_bStartPathGoalEndAnim = false;
|
|
|
|
|
|
|
|
if (!m_Anim.IsFile(m_csPathGoalEndAnimScript)) {
|
|
|
|
m_Anim.TrySetScript(m_csPathGoalEndAnimScript);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 21:30:53 +02:00
|
|
|
void SimpleActor::EventNoAnimLerp(Event *ev)
|
2023-10-12 19:57:46 +02:00
|
|
|
{
|
|
|
|
edict->s.eFlags |= EF_NO_LERP;
|
|
|
|
NoLerpThisFrame();
|
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
const char *SimpleActor::DumpCallTrace(const char *pszFmt, ...) const
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
OVERLOADED_ERROR();
|
|
|
|
return "overloaded version should always get called";
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::EventGetSniperHandler(Event *ev)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
ScriptVariable var;
|
|
|
|
|
|
|
|
m_SniperHandler.GetScriptValue(&var);
|
|
|
|
|
|
|
|
ev->AddValue(var);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-10-12 19:42:49 +02:00
|
|
|
|
2023-10-12 19:54:00 +02:00
|
|
|
void SimpleActor::EventSetSniperHandler(Event *ev)
|
2023-10-12 19:42:49 +02:00
|
|
|
{
|
2023-10-12 19:54:00 +02:00
|
|
|
if (ev->IsFromScript()) {
|
|
|
|
ScriptVariable var = ev->GetValue(1);
|
|
|
|
|
|
|
|
m_SniperHandler.SetScript(var);
|
|
|
|
} else {
|
|
|
|
str varString = ev->GetString(1);
|
|
|
|
m_SniperHandler.SetScript(varString);
|
|
|
|
}
|
2023-10-12 19:42:49 +02:00
|
|
|
}
|
|
|
|
|
2023-10-12 21:30:53 +02:00
|
|
|
void SimpleActor::EventGetAnimMode(Event *ev) {}
|
2023-10-12 19:42:49 +02:00
|
|
|
|
2023-10-12 21:30:53 +02:00
|
|
|
void SimpleActor::EventSetAnimMode(Event *ev) {}
|