ef2gamesource/dlls/game/actor_headwatcher.cpp

334 lines
8.1 KiB
C++

//-----------------------------------------------------------------------------
//
// $Logfile:: /Code/DLLs/game/actor_headwatcher.cpp $
// $Revision:: 21 $
// $Author:: Sketcher $
// $Date:: 5/04/03 5:49p $
//
// Copyright (C) 2001 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
//
#include "actor_enemymanager.h"
#include "_pch_cpp.h"
HeadWatcher::HeadWatcher()
{
// Should always use other constructor
gi.Error(ERR_FATAL, "HeadWatcher::HeadWatcher -- Default Constructor Called");
}
HeadWatcher::HeadWatcher(Actor *actor)
{
//Initialize our Actor
if (actor)
act = actor;
else
gi.Error(ERR_DROP, "HeadWatcher::HeadWatcher -- actor is NULL");
_init();
}
HeadWatcher::~HeadWatcher()
{
}
void HeadWatcher::_init()
{
act->SetControllerTag(ActorHeadTag, gi.Tag_NumForName(act->edict->s.modelindex, "Bip01 Head"));
_currentHeadAngles = act->GetControllerAngles(ActorHeadTag);
_watchTarget = nullptr;
// Numbers here for testing, should come from events so they can be set via tiki or script
_maxHeadTurnSpeed = 30.0f;
_turnThreshold = 0.0f;
_maxHeadYaw = 60.0f;
_maxHeadPitch = 45.0f;
_twitchHead = false;
_nextTwitchHeadTime = 0.0f;
_maxDistance = -1.0;
_explicitSet = false;
_ignoreWatchTarget = false;
}
void HeadWatcher::SetWatchTarget(Entity *ent)
{
_watchTarget = ent;
_explicitSet = true;
}
void HeadWatcher::SetWatchTarget(const str &targetName)
{
auto tlist = world->GetTargetList(targetName);
auto numObjects = tlist->list.NumObjects();
if (numObjects == 0)
{
//Hey, No targets with that name
gi.WDPrintf("No target with target name %s specified\n", targetName.c_str());
return;
}
if (numObjects > 1)
{
//Uh Oh... We have more than one target... Let's throw up an error
gi.WDPrintf("More than one target with target name %s specified, grabbing first one\n", targetName.c_str());
}
_watchTarget = tlist->list.ObjectAt(1);
_explicitSet = true;
}
void HeadWatcher::SetWatchSpeed(float speed)
{
_maxHeadTurnSpeed = speed;
}
void HeadWatcher::ClearWatchTarget()
{
_watchTarget = nullptr;
_explicitSet = false;
}
Entity *HeadWatcher::GetWatchTarget()
{
return _watchTarget;
}
void HeadWatcher::HeadWatchTarget()
{
auto tagNum = gi.Tag_NumForName(act->edict->s.modelindex, "Bip01 Head");
if (tagNum < 0)
return;
//Check if we even have an animation set yet
if (act->animate->CurrentAnim(legs) < 0)
return;
Vector tagPos;
act->GetTag("Bip01 Head", &tagPos);
if (!_watchTarget || _ignoreWatchTarget)
{
if (_twitchHead)
{
twitchHead();
return;
}
LerpHeadBySpeed(vec_zero, false);
return;
}
if (act->GetActorFlag(ACTOR_FLAG_DIALOG_PLAYING) && act->GetActorFlag(ACTOR_FLAG_USING_HUD))
{
LerpHeadBySpeed(vec_zero, false);
return;
}
// Check if our _watchTarget is within distance )
if (_maxDistance > 0)
{
auto selfToTarget = _watchTarget->origin - act->origin;
auto dist = selfToTarget.length();
if (dist > _maxDistance)
{
LerpHeadBySpeed(vec_zero, false);
return;
}
}
Actor* actTarget = nullptr;
if (_watchTarget->isSubclassOf(Actor))
{
actTarget = dynamic_cast<Actor *>(static_cast<Entity *>(_watchTarget));
// Don't watch if the target is dead.
if (!actTarget->isThinkOn())
{
_watchTarget = nullptr;
return;
}
}
Vector watchPosition;
if (actTarget && actTarget->watch_offset != vec_zero)
{
MatrixTransformVector(actTarget->watch_offset, _watchTarget->orientation, watchPosition);
watchPosition += _watchTarget->origin;
} else
{
tagNum = gi.Tag_NumForName(_watchTarget->edict->s.modelindex, "Bip01 Head");
if (tagNum < 0)
watchPosition = _watchTarget->centroid;
else
{
_watchTarget->GetTag("Bip01 Head", &watchPosition);
}
}
AdjustHeadAngles(tagPos, watchPosition);
}
void HeadWatcher::AdjustHeadAngles(const Vector &tagPos, const Vector &watchPosition)
{
auto dir = watchPosition - tagPos;
auto angles = dir.toAngles();
auto anglesDiff = angles - act->angles;
anglesDiff[YAW] = AngleNormalize180(anglesDiff[YAW]);
anglesDiff[PITCH] = AngleNormalize180(anglesDiff[PITCH]);
auto yawChange = anglesDiff[YAW];
auto pitchChange = anglesDiff[PITCH];
if (_turnThreshold && yawChange < _turnThreshold && yawChange > -_turnThreshold && pitchChange < _turnThreshold && pitchChange > -_turnThreshold)
{
return;
}
// Make sure we don't turn neck too far
if (anglesDiff[YAW] < -_maxHeadYaw)
anglesDiff[YAW] = -_maxHeadYaw;
else if (anglesDiff[YAW] > _maxHeadYaw)
anglesDiff[YAW] = _maxHeadYaw;
if (anglesDiff[PITCH] < -_maxHeadPitch)
anglesDiff[PITCH] = -_maxHeadPitch;
else if (anglesDiff[PITCH] > _maxHeadPitch)
anglesDiff[PITCH] = _maxHeadPitch;
anglesDiff[ROLL] = 0.0f;
LerpHeadBySpeed(anglesDiff);
}
void HeadWatcher::LerpHeadBySpeed(const Vector &angleDelta, bool useTorsoAngles)
{
auto anglesDiff = angleDelta;
// Get our Torso Angles
act->SetControllerTag(ActorTorsoTag, gi.Tag_NumForName(act->edict->s.modelindex, "Bip01 Spine1"));
auto currentTorsoAngles = act->GetControllerAngles(ActorTorsoTag);
//Reset our Controller Tag
act->SetControllerTag(ActorHeadTag, gi.Tag_NumForName(act->edict->s.modelindex, "Bip01 Head"));
// Make sure we don't change our head angles too much at once
auto change = anglesDiff - _currentHeadAngles;
if (change[YAW] > _maxHeadTurnSpeed)
anglesDiff[YAW] = _currentHeadAngles[YAW] + _maxHeadTurnSpeed;
else if (change[YAW] < -_maxHeadTurnSpeed)
anglesDiff[YAW] = _currentHeadAngles[YAW] - _maxHeadTurnSpeed;
if (change[PITCH] > _maxHeadTurnSpeed)
anglesDiff[PITCH] = _currentHeadAngles[PITCH] + _maxHeadTurnSpeed;
else if (change[PITCH] < -_maxHeadTurnSpeed)
anglesDiff[PITCH] = _currentHeadAngles[PITCH] - _maxHeadTurnSpeed;
if (change[ROLL] > _maxHeadTurnSpeed)
anglesDiff[ROLL] = _currentHeadAngles[ROLL] + _maxHeadTurnSpeed;
else if (change[ROLL] < -_maxHeadTurnSpeed)
anglesDiff[ROLL] = _currentHeadAngles[ROLL] - _maxHeadTurnSpeed;
auto finalAngles = anglesDiff;
if (useTorsoAngles)
finalAngles[YAW] = anglesDiff[YAW] - currentTorsoAngles[YAW];
else
finalAngles[YAW] = anglesDiff[YAW];
act->SetControllerAngles(ActorHeadTag, finalAngles);
act->real_head_pitch = anglesDiff[PITCH];
_currentHeadAngles = anglesDiff;
}
void HeadWatcher::setHeadTwitch(bool twitchHead)
{
_twitchHead = twitchHead;
if (_twitchHead)
{
_nextTwitchHeadTime = 0.0f;
}
}
void HeadWatcher::twitchHead(void)
{
if (level.time > _nextTwitchHeadTime)
{
_headTwitchAngles = Vector(G_CRandom(4.0f), G_CRandom(4.0f), G_CRandom(4.0f));
_nextTwitchHeadTime = level.time + 1.0f + G_CRandom(0.5f);
}
auto oldMaxHeadTurnSpeed = _maxHeadTurnSpeed;
_maxHeadTurnSpeed = 0.25f;
LerpHeadBySpeed(_headTwitchAngles, false);
_maxHeadTurnSpeed = oldMaxHeadTurnSpeed;
}
//
// Name: DoArchive()
// Parameters: Archiver &arc
// Actor *actor
// Description: Sets the Actor Pointer and Calls Archive()
//
void HeadWatcher::DoArchive(Archiver &arc, Actor *actor)
{
Archive(arc);
if (actor)
act = actor;
else
gi.Error(ERR_FATAL, "HeadWatcher::DoArchive -- actor is NULL");
}
//
// Name: Archive()
// Parameters: Archiver &arc
// Description: Archives Class Data
//
void HeadWatcher::Archive(Archiver &arc)
{
arc.ArchiveSafePointer(&_watchTarget);
arc.ArchiveVector(&_currentHeadAngles);
arc.ArchiveFloat(&_maxHeadTurnSpeed);
arc.ArchiveFloat(&_turnThreshold);
arc.ArchiveBoolean(&_explicitSet);
arc.ArchiveFloat(&_maxHeadYaw);
arc.ArchiveFloat(&_maxHeadPitch);
arc.ArchiveBool(&_twitchHead);
arc.ArchiveFloat(&_nextTwitchHeadTime);
arc.ArchiveVector(&_headTwitchAngles);
arc.ArchiveFloat(&_maxDistance);
arc.ArchiveBool(&_ignoreWatchTarget);
}