scummvm/engines/hpl1/penumbra-overture/GameEnemy.cpp
2024-08-10 11:43:44 +03:00

1676 lines
50 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* Copyright (C) 2006-2010 - Frictional Games
*
* This file is part of Penumbra Overture.
*/
#include "hpl1/penumbra-overture/GameEnemy.h"
#include "hpl1/engine/engine.h"
#include "hpl1/penumbra-overture/GameEnemy_Dog.h"
#include "hpl1/penumbra-overture/GameEnemy_Spider.h"
#include "hpl1/penumbra-overture/GameEnemy_Worm.h"
#include "hpl1/penumbra-overture/AttackHandler.h"
#include "hpl1/penumbra-overture/EffectHandler.h"
#include "hpl1/penumbra-overture/GameMusicHandler.h"
#include "hpl1/penumbra-overture/GameObject.h"
#include "hpl1/penumbra-overture/Init.h"
#include "hpl1/penumbra-overture/MapHandler.h"
#include "hpl1/penumbra-overture/Player.h"
#include "hpl1/penumbra-overture/PlayerHelper.h"
#include "hpl1/penumbra-overture/Triggers.h"
#include "hpl1/penumbra-overture/CharacterMove.h"
#include "hpl1/penumbra-overture/GlobalInit.h"
constexpr const char *gvStateName[STATE_NUM] = {
"IDLE",
"HUNT",
"ATTACK",
"FLEE",
"KNOCKDOWN",
"DEAD",
"PATROL",
"INVESTIGATE",
"BREAKDOOR",
"CALLBACKUP",
"MOVETO",
"EAT",
"ATTENTION",
};
//////////////////////////////////////////////////////////////////////////
// GAME ENEMY STATE
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
iGameEnemyState::iGameEnemyState(int alId, cInit *apInit, iGameEnemy *apEnemy) {
mlId = alId;
mpInit = apInit;
mpPlayer = mpInit->mpPlayer;
mpEnemy = apEnemy;
mpMover = mpEnemy->GetMover();
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// RAY INTERSECT
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cLineOfSightRayCallback::Reset() {
mbIntersected = false;
if (gpInit->mpPlayer->GetState() == ePlayerState_Grab)
mpGrabBody = gpInit->mpPlayer->GetPushBody();
else
mpGrabBody = NULL;
}
//-----------------------------------------------------------------------
bool cLineOfSightRayCallback::Intersected() {
return mbIntersected;
}
//-----------------------------------------------------------------------
static bool BodyIsTransperant(iPhysicsBody *apBody) {
iGameEntity *pEntity = (iGameEntity *)apBody->GetUserData();
if (pEntity && pEntity->GetMeshEntity()) {
cMeshEntity *pMeshEntity = pEntity->GetMeshEntity();
bool bFoundSolid = false;
for (int i = 0; i < pMeshEntity->GetSubMeshEntityNum(); ++i) {
iMaterial *pMaterial = pMeshEntity->GetSubMeshEntity(i)->GetMaterial();
if (pMaterial &&
(pMaterial->IsTransperant() == false && pMaterial->HasAlpha() == false)) {
bFoundSolid = true;
break;
}
}
if (bFoundSolid == false)
return true;
}
return false;
}
bool cLineOfSightRayCallback::OnIntersect(iPhysicsBody *pBody, cPhysicsRayParams *apParams) {
if (pBody->GetCollide() == false)
return true;
if (pBody->IsCharacter())
return true;
if (mpGrabBody == pBody)
return true;
if (BodyIsTransperant(pBody))
return true;
mbIntersected = true;
return false;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// GROUND FINDER
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
bool cEnemyFindGround::GetGround(const cVector3f &avStartPos, const cVector3f &avDir,
cVector3f *apDestPosition, cVector3f *apDestNormal,
float afMaxDistance) {
mbIntersected = false;
mfMinDist = afMaxDistance;
mfMaxDistance = afMaxDistance;
iPhysicsWorld *pPhysicsWorld = gpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
pPhysicsWorld->CastRay(this, avStartPos, avStartPos + avDir * mfMaxDistance, true, true, true);
if (mbIntersected) {
if (apDestPosition)
*apDestPosition = mvPos;
if (apDestNormal)
*apDestNormal = mvNormal;
return true;
}
return false;
}
bool cEnemyFindGround::OnIntersect(iPhysicsBody *pBody, cPhysicsRayParams *apParams) {
if (apParams->mfT < 0)
return true;
if (pBody->GetCollideCharacter() == false || pBody->IsCharacter())
return true;
if (mbIntersected == false || mfMinDist > apParams->mfDist) {
mbIntersected = true;
mfMinDist = apParams->mfDist;
mvPos = apParams->mvPoint;
mvNormal = apParams->mvNormal;
}
return true;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// DOOR CHECKER
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
bool cEnemyCheckForDoor::CheckDoor(const cVector3f &avStart, const cVector3f &avEnd) {
mbIntersected = false;
iPhysicsWorld *pPhysicsWorld = gpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
pPhysicsWorld->CastRay(this, avStart, avEnd, false, false, false);
return mbIntersected;
}
static bool BodyCanBeBroken(iPhysicsBody *pBody) {
if (pBody->GetUserData() == NULL)
return false;
iGameEntity *pEntity = (iGameEntity *)pBody->GetUserData();
if (pEntity->GetType() == eGameEntityType_SwingDoor) {
return true;
}
if (pEntity->GetType() == eGameEntityType_Object) {
cGameObject *pObject = static_cast<cGameObject *>(pEntity);
if (pObject->IsBreakable())
return true;
}
return false;
}
bool cEnemyCheckForDoor::BeforeIntersect(iPhysicsBody *pBody) {
if (BodyCanBeBroken(pBody))
return true;
return false;
}
bool cEnemyCheckForDoor::OnIntersect(iPhysicsBody *pBody, cPhysicsRayParams *apParams) {
if (apParams->mfT < 0 || apParams->mfT > 1)
return true;
if (BodyCanBeBroken(pBody)) {
mbIntersected = true;
return false;
}
return true;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
iGameEnemy::iGameEnemy(cInit *apInit, const tString &asName, TiXmlElement *apGameElem) : iGameEntity(apInit, asName) {
mType = eGameEntityType_Enemy;
mTriggerTypes = eGameTriggerType_Sound;
mpMover = hplNew(cCharacterMove, (mpInit));
mlCurrentState = -1;
mbHasBeenActivated = false;
mbSetFeetAtGroundOnStart = true;
mbAttachMeshToBody = true;
mbRemoveAttackerOnDisable = true;
// States
mvStates.resize(100);
for (size_t i = 0; i < mvStates.size(); ++i)
mvStates[i] = NULL;
// Player find init
mvLastPlayerPos = cVector3f(0, 0, 0);
mbCanSeePlayer = false;
mfCanSeePlayerCount = 0;
mlPlayerInLOSCount = 0;
mlMaxPlayerInLOSCount = 3;
mfCheckForPlayerRate = 0.55f;
mfCheckForPlayerCount = cMath::RandRectf(0, mfCheckForPlayerRate);
mfDamageSoundTimer = 0;
msOnDeathCallback = "";
msOnAttackCallback = "";
m_mtxStartPose = cMatrixf::Identity;
m_mtxGoalPose = cMatrixf::Identity;
mfPoseCount = 0;
// TODO: Should not be here
mpAStarAir = NULL;
mpAStarGround = NULL;
mpNodeContainerAir = NULL;
mpNodeContainerGround = NULL;
// Patroling
mlCurrentPatrolNode = 0;
mfWaitTime = 0;
mfWaitTimeCount = 0;
mfDoorBreakCount = 0;
// Default body settings
mfDisappearTime = 0;
mbDisappearActive = false;
mbHasDisappeared = false;
msCloseMusic = "";
mlCloseMusicPrio = 0;
msAttackMusic = "";
mlAttackMusicPrio = 0;
mbShowDebug = false;
msGroundNodeType = "ground";
mvBodySize = cVector3f(0.5f, 1.4f, 0.5f);
mfBodyMass = 10;
mfMaxForwardSpeed = 1.0f;
mfMaxBackwardSpeed = 1.0f;
mfAcceleration = 1;
mfDeacceleration = 1;
mfMaxTurnSpeed = 8.5f;
mfAngleDistTurnMul = 2.3f;
mfMinBreakAngle = cMath::ToRad(16);
mfBreakAngleMul = 1.5f;
mfSpeedMoveAnimMul = 4.7f;
mfTurnSpeedMoveAnimMul = 4.0f;
mfMaxPushMass = 10.0f;
mfPushForce = 19.0f;
mfMaxSeeDist = 10.0f;
mfMinAttackDist = 1.6f;
mfStoppedToWalkSpeed = 0.05f;
mfWalkToStoppedSpeed = 0.02f;
mfWalkToRunSpeed = 1.2f;
mfRunToWalkSpeed = 1.0f;
mfMoveAnimSpeedMul = 1.0f;
msBackwardAnim = "Backward";
msStoppedAnim = "Idle";
msWalkAnim = "Walk";
msRunAnim = "Run";
m_mtxModelOffset = cMatrixf::Identity;
mvModelOffsetAngles = 0;
mfFOV = cMath::ToRad(90.0f);
mfFOVXMul = 0.7f;
// trigger init
mfTriggerUpdateCount = 0;
mfTriggerUpdateRate = 1.0f / 60.0f;
mfSkipSoundTriggerCount = 0;
mpCurrentAnimation = NULL;
mbAnimationIsSpeedDependant = false;
mfAnimationSpeedMul = 1.0f;
msHitPS = "";
mbOverideMoveState = false;
mMoveState = eEnemyMoveState_LastEnum;
mbLoading = false;
mbIsAttracted = false;
mbUsesTriggers = true;
mfCalcPlayerHiddenPosCount = 0;
}
//-----------------------------------------------------------------------
iGameEnemy::~iGameEnemy(void) {
hplDelete(mpMover);
for (size_t i = 0; i < mvStates.size(); ++i) {
if (mvStates[i])
hplDelete(mvStates[i]);
}
mvStates.clear();
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////
void iGameEnemy::LoadBaseProperties(TiXmlElement *apGameElem) {
////////////////////////////////////////
// Load settings from XML
mbShowDebug = cString::ToBool(apGameElem->Attribute("ShowDebug"), false);
mbDisappear = cString::ToBool(apGameElem->Attribute("Disappear"), false);
mfDisappearMinTime = cString::ToFloat(apGameElem->Attribute("DisappearMinTime"), 0);
mfDisappearMaxTime = cString::ToFloat(apGameElem->Attribute("DisappearMaxTime"), 0);
mfDisappearMinDistance = cString::ToFloat(apGameElem->Attribute("DisappearMinDistance"), 0);
msDisappearPS = cString::ToString(apGameElem->Attribute("DisappearPS"), "");
msDisappearSound = cString::ToString(apGameElem->Attribute("DisappearSound"), "");
mbDisappearFreezesRagdoll = cString::ToBool(apGameElem->Attribute("DisappearFreezesRagdoll"), false);
msCloseMusic = cString::ToString(apGameElem->Attribute("CloseMusic"), "");
mlCloseMusicPrio = cString::ToInt(apGameElem->Attribute("CloseMusicPrio"), 0);
mfCloseMusicStartDist = cString::ToFloat(apGameElem->Attribute("CloseMusicStartDist"), 0);
mfCloseMusicStopDist = cString::ToFloat(apGameElem->Attribute("CloseMusicStopDist"), 0);
msAttackMusic = cString::ToString(apGameElem->Attribute("AttackMusic"), "");
mlAttackMusicPrio = cString::ToInt(apGameElem->Attribute("AttackMusicPrio"), 0);
mfFOV = cMath::ToRad(90);
mfMaxPushMass = cString::ToFloat(apGameElem->Attribute("MaxPushMass"), 0);
mfPushForce = cString::ToFloat(apGameElem->Attribute("PushForce"), 0);
mfMaxHealth = cString::ToFloat(apGameElem->Attribute("MaxHealth"), 0);
mfHealth = mfMaxHealth;
mfMaxSeeDist = cString::ToFloat(apGameElem->Attribute("MaxSeeDist"), 0);
mfMaxForwardSpeed = cString::ToFloat(apGameElem->Attribute("MaxForwardSpeed"), 0);
mfAcceleration = cString::ToFloat(apGameElem->Attribute("Acceleration"), 0);
mfDeacceleration = cString::ToFloat(apGameElem->Attribute("Deacceleration"), 0);
mfMaxTurnSpeed = cString::ToFloat(apGameElem->Attribute("MaxTurnSpeed"), mfMaxTurnSpeed);
mfAngleDistTurnMul = cString::ToFloat(apGameElem->Attribute("AngleDistTurnMul"), mfAngleDistTurnMul);
mfMinBreakAngle = cMath::ToRad(cString::ToFloat(apGameElem->Attribute("MinBreakAngle"), mfMinBreakAngle));
mfBreakAngleMul = cString::ToFloat(apGameElem->Attribute("BreakAngleMul"), mfBreakAngleMul);
mfStoppedToWalkSpeed = cString::ToFloat(apGameElem->Attribute("StoppedToWalkSpeed"), 0);
mfWalkToStoppedSpeed = cString::ToFloat(apGameElem->Attribute("WalkToStoppedSpeed"), 0);
mfWalkToRunSpeed = cString::ToFloat(apGameElem->Attribute("WalkToRunSpeed"), 0);
mfRunToWalkSpeed = cString::ToFloat(apGameElem->Attribute("RunToWalkSpeed"), 0);
mfMoveAnimSpeedMul = cString::ToFloat(apGameElem->Attribute("MoveAnimSpeedMul"), 0);
mvBodySize = cString::ToVector3f(apGameElem->Attribute("BodySize"), 0);
mfBodyMass = cString::ToFloat(apGameElem->Attribute("BodyMass"), 10);
cVector3f vRot = cString::ToVector3f(apGameElem->Attribute("ModelOffset_Rot"), 0);
vRot = cVector3f(cMath::ToRad(vRot.x), cMath::ToRad(vRot.y), cMath::ToRad(vRot.z));
cVector3f vPos = cString::ToVector3f(apGameElem->Attribute("ModelOffset_Pos"), 0);
mvModelOffsetAngles = vRot;
m_mtxModelOffset = cMath::MatrixRotate(vRot, eEulerRotationOrder_XYZ);
m_mtxModelOffset.SetTranslation(vPos);
mbAlignToGroundNormal = cString::ToBool(apGameElem->Attribute("AlignToGroundNormal"), false);
msHitPS = cString::ToString(apGameElem->Attribute("HitPS"), "");
}
//-----------------------------------------------------------------------
void iGameEnemy::OnPlayerInteract() {
}
//-----------------------------------------------------------------------
void iGameEnemy::OnPlayerPick() {
// SKIp this for now since this means that enemies in ragdoll can take damage.
/*mpInit->mpPlayer->mbPickAtPoint = false;
mpInit->mpPlayer->mbRotateWithPlayer = true;
mpInit->mpPlayer->mbUseNormalMass = false;
mpInit->mpPlayer->mfGrabMassMul = (float)mvBodies.size();
mpInit->mpPlayer->SetPushBody(mpInit->mpPlayer->GetPickedBody());
mpInit->mpPlayer->ChangeState(ePlayerState_Grab);*/
}
//-----------------------------------------------------------------------
void iGameEnemy::Setup(cWorld3D *apWorld) {
/////////////////////////////////////////////
// Create body
iCharacterBody *pBody = apWorld->GetPhysicsWorld()->CreateCharacterBody("Enemy",
mvBodySize);
pBody->SetEntityOffset(m_mtxModelOffset);
pBody->SetMass(mfBodyMass);
pBody->SetMaxStepSize(0.35f);
pBody->SetStepClimbSpeed(3.35f);
pBody->SetCustomGravity(cVector3f(0, -13.0f, 0));
pBody->SetEntitySmoothPosNum(10);
pBody->SetGroundFriction(10);
mpMover->SetCharBody(pBody);
SetCharBody(pBody);
SetupBody();
}
//-----------------------------------------------------------------------
void iGameEnemy::OnWorldLoad() {
//////////////////////////////////
// Setup EnemyMove
cWorld3D *pWorld = mpInit->mpGame->GetScene()->GetWorld3D();
mpNodeContainerGround = pWorld->CreateAINodeContainer(msEnemyType, msGroundNodeType,
mvBodySize,
false, 2, 6, 5.0f, 0.41f);
if (mpNodeContainerGround)
mpAStarGround = pWorld->CreateAStarHandler(mpNodeContainerGround);
else
mpAStarGround = NULL;
//////////////////////////////////
// Set up the body
if (mbAttachMeshToBody && mfHealth > 0)
mpMover->GetCharBody()->SetEntity(mpMeshEntity);
mpMover->GetCharBody()->GetBody()->SetUserData(this);
mpMover->GetCharBody()->Update(0.001f);
mpMover->SetAStar(mpAStarGround);
mpMover->SetNodeContainer(mpNodeContainerGround);
//////////////////////////////////
// Stop all animations
mpMeshEntity->Stop();
mpMeshEntity->UpdateLogic(0.005f);
//////////////////////////////////
// Preload data
// Sounds
for (size_t i = 0; i < mvPreloadSounds.size(); ++i) {
mpInit->PreloadSoundEntityData(mvPreloadSounds[i]);
}
// Particle system
mpInit->PreloadParticleSystem(msHitPS);
//////////////////////////////////
// Implemented load
OnLoad();
//////////////////////////////////
// Check if dead
mbLoading = true;
if (mfHealth <= 0) {
ChangeState(STATE_DEAD);
mpMeshEntity->SetSkeletonPhysicsCanSleep(false);
mpMeshEntity->UpdateLogic(1.0f / 60.0f);
mpMeshEntity->SetSkeletonPhysicsCanSleep(true);
}
mbLoading = false;
mpMeshEntity->ResetGraphicsUpdated();
}
//-----------------------------------------------------------------------
void iGameEnemy::OnPostLoadScripts() {
// Randomize start pos
if (IsActive() && mvPatrolNodes.size() > 0 && mbHasBeenActivated) {
int lStartNode = cMath::RandRectl(0, (int)mvPatrolNodes.size() - 1);
tString sNode = mvPatrolNodes[lStartNode].msNodeName;
cAINode *pNode = mpMover->GetNodeContainer()->GetNodeFromName(sNode);
mpMover->GetCharBody()->SetFeetPosition(pNode->GetPosition());
} else {
mbHasBeenActivated = true;
}
}
//-----------------------------------------------------------------------
void iGameEnemy::OnWorldExit() {
if (mfHealth <= 0) {
SetActive(false);
}
mpInit->mpMusicHandler->RemoveAttacker(this);
}
//-----------------------------------------------------------------------
float gfAngle = 0;
float gfCurrentViewDist = 0;
float gfCurrentMaxViewDist = 0;
void iGameEnemy::OnDraw() {
return;
if (mbActive == false)
return;
if (mbCanSeePlayer) {
mpInit->mpDefaultFont->draw(cVector3f(5, 15, 100), 14, cColor(1, 1, 1, 1), eFontAlign_Left,
_W("Player is seen!"));
} else {
mpInit->mpDefaultFont->draw(cVector3f(5, 15, 100), 14, cColor(1, 1, 1, 1), eFontAlign_Left,
_W("Can NOT see player..."));
}
// mpInit->mpDefaultFont->Draw(cVector3f(5,29,100),14,cColor(1,1,1,1),eFontAlign_Left,
// "State: %s",mStateMachine.CurrentState()->GetName().c_str());
tWString sStateName = _W("NONE");
if (mlCurrentState >= 0)
sStateName = cString::To16Char(gvStateName[mlCurrentState]);
mpInit->mpDefaultFont->draw(cVector3f(5, 48, 100), 14, cColor(1, 1, 1, 1), eFontAlign_Left,
Common::U32String::format("Health: %f State: %S Moving: %d Stuck: %f MaxViewDist: %f", mfHealth,
sStateName.c_str(),
mpMover->IsMoving(),
mpMover->GetStuckCounter(),
gfCurrentMaxViewDist));
mpInit->mpDefaultFont->draw(cVector3f(5, 64, 100), 14, cColor(1, 1, 1, 1), eFontAlign_Left,
Common::U32String::format("Speed: %f", mpMover->GetCharBody()->GetMoveSpeed(eCharDir_Forward)));
mpMover->OnDraw(mpInit);
mvStates[mlCurrentState]->OnDraw();
/*mpInit->mpDefaultFont->Draw(cVector3f(5,15,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Active: %d",mbActive);
mpInit->mpDefaultFont->Draw(cVector3f(5,30,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Yaw: %f",cMath::ToDeg(mpMover->GetCharBody()->GetYaw()));
mpInit->mpDefaultFont->Draw(cVector3f(5,45,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Pos: %s",mpMover->GetCharBody()->GetPosition().ToString().c_str());*/
}
//-----------------------------------------------------------------------
void iGameEnemy::OnPostSceneDraw() {
if (IsActive() == false)
return;
if (mbShowDebug == false)
return;
iLowLevelGraphics *pLowLevelGfx = mpInit->mpGame->GetGraphics()->GetLowLevel();
mpMover->OnPostSceneDraw(pLowLevelGfx);
ExtraPostSceneDraw();
/////////////////////////////////////
// Begin debug pos
/*pLowLevelGfx->SetDepthTestActive(false);
pLowLevelGfx->SetDepthWriteActive(false);
cVector3f vNormal(0,1,0);
cVector3f vUp(0,1,0);
cVector3f vStartPos = mpMover->GetCharBody()->GetFeetPosition() + cVector3f(0,0.05f,0);
cVector3f vPosition = vStartPos;
mFindGround.GetGround(vStartPos,cVector3f(0,-1,0),NULL,&vNormal);
vNormal.Normalise();
float fAngle = cMath::Vector3Angle(vUp,vNormal);
cVector3f vRotateAxis = cMath::Vector3Cross(vUp,vNormal);
//cVector3f vRotateAxis2 = cMath::Vector3Cross(vUp,vRotateAxis);
vRotateAxis.Normalise();
cQuaternion qRotation = cQuaternion(fAngle, vRotateAxis);
cMatrixf mtxPoseRotation = cMath::MatrixQuaternion(qRotation);
cMatrixf mtxFinalOffset = cMath::MatrixMul(mtxPoseRotation,m_mtxModelOffset);
cVector3f vCenter = mpMover->GetCharBody()->GetPosition();
cVector3f vRot = cMath::MatrixMul(mtxPoseRotation,cVector3f(0,1,0));
pLowLevelGfx->DrawLine(vStartPos, vStartPos + vNormal,cColor(1,0,1,1));
pLowLevelGfx->DrawLine(vCenter, vCenter + vRotateAxis,cColor(1,0.5,0.5,1));
// pLowLevelGfx->DrawLine(vCenter, vCenter + vRotateAxis2,cColor(1,0.5,0.5,1));
pLowLevelGfx->DrawLine(vCenter, vCenter + vRot,cColor(0,1,1,1));
gfAngle =cMath::ToDeg(fAngle);
pLowLevelGfx->SetDepthTestActive(true);
pLowLevelGfx->SetDepthWriteActive(true);*/
// End debug pose
//////////////////////////////
/*pLowLevelGfx->SetDepthTestActive(false);
pLowLevelGfx->SetDepthWriteActive(false);
pLowLevelGfx->DrawSphere(mpMover->GetCharBody()->GetPosition(),0.1f,cColor(1,0.5,0));
pLowLevelGfx->DrawSphere(GetMeshEntity()->GetWorldPosition(),0.1f,cColor(0,0.5,1));
pLowLevelGfx->SetDepthTestActive(true);
pLowLevelGfx->SetDepthWriteActive(true);*/
/*for(size_t i=0; i< mvRayStartPoses.size(); ++i)
{
pLowLevelGfx->DrawLine(mvRayStartPoses[i], mvRayEndPoses[i],cColor(1,0,1,1));
pLowLevelGfx->DrawSphere(mvRayStartPoses[i],0.2f,cColor(0,1,1,1));
pLowLevelGfx->DrawSphere(mvRayEndPoses[i],0.2f,cColor(0,1,1,1));
}*/
// mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld()->RenderDebugGeometry(pLowLevelGfx,cColor(1,1));
mvStates[mlCurrentState]->OnPostSceneDraw();
}
//-----------------------------------------------------------------------
void iGameEnemy::Update(float afTimeStep) {
if (mbActive == false)
return;
START_TIMING_EX(GetName().c_str(), enemy);
if (mpMeshEntity->GetSkeletonPhysicsActive() && mpCharBody->IsActive() == false &&
mfHealth <= 0) {
mbHasInteraction = true;
} else {
mbHasInteraction = false;
}
START_TIMING_TAB(pose);
UpdateEnemyPose(afTimeStep);
STOP_TIMING_TAB(pose);
START_TIMING_TAB(checkforplayer);
UpdateCheckForPlayer(afTimeStep);
STOP_TIMING_TAB(checkforplayer);
START_TIMING_TAB(MoverUpdate);
mpMover->Update(afTimeStep);
STOP_TIMING_TAB(MoverUpdate);
START_TIMING_TAB(Animations);
UpdateAnimations(afTimeStep);
STOP_TIMING_TAB(Animations);
OnUpdate(afTimeStep);
#ifdef UPDATE_TIMING_ENABLED
LogUpdate("\tState: %d\n", mlCurrentState);
#endif
START_TIMING_TAB(State);
mvStates[mlCurrentState]->OnUpdate(afTimeStep);
STOP_TIMING_TAB(State);
if (mfDamageSoundTimer > 0)
mfDamageSoundTimer -= afTimeStep;
if (mfSkipSoundTriggerCount > 0)
mfSkipSoundTriggerCount -= afTimeStep;
if (mfDoorBreakCount > 0)
mfDoorBreakCount -= afTimeStep;
//////////////////////////////////////////////
// Disappear
if (mbDisappear && GetHealth() <= 0 && mbHasDisappeared == false) {
if (mbDisappearActive) {
if (mfDisappearTime <= 0) {
mbHasDisappeared = true;
cWorld3D *pWorld = mpInit->mpGame->GetScene()->GetWorld3D();
cVector3f vPostion = mpMeshEntity->GetBoundingVolume()->GetWorldCenter();
if (msDisappearSound != "") {
cSoundEntity *pSound = pWorld->CreateSoundEntity("Disappear", msDisappearSound, true);
if (pSound)
pSound->SetPosition(vPostion);
}
if (msDisappearPS != "") {
pWorld->CreateParticleSystem("Disappear", msDisappearPS, cVector3f(1, 1, 1),
cMath::MatrixTranslate(vPostion));
}
if (mbDisappearFreezesRagdoll) {
mpMeshEntity->ResetGraphicsUpdated();
for (int i = 0; i < mpMeshEntity->GetBoneStateNum(); ++i) {
cBoneState *pBone = mpMeshEntity->GetBoneState(i);
iPhysicsBody *pBody = pBone->GetBody();
if (pBody) {
pBody->SetMass(0);
}
}
} else {
SetActive(false);
}
} else {
mfDisappearTime -= afTimeStep;
}
} else {
mbDisappearActive = true;
mfDisappearTime = cMath::RandRectf(mfDisappearMinTime, mfDisappearMaxTime);
}
}
//////////////////////////////////////////////
// Outside of map
iPhysicsWorld *pPhysicsWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
cBoundingVolume worldBV;
worldBV.SetLocalMinMax(pPhysicsWorld->GetWorldSizeMin(), pPhysicsWorld->GetWorldSizeMax());
if (cMath::CheckCollisionBV(worldBV, *mpMover->GetCharBody()->GetBody()->GetBV()) == false) {
SetHealth(0);
SetActive(false);
}
STOP_TIMING(enemy);
}
//-----------------------------------------------------------------------
void iGameEnemy::AddState(iGameEnemyState *apState) {
mvStates[apState->GetId()] = apState;
}
void iGameEnemy::ChangeState(int alId) {
if (mlCurrentState == alId)
return;
/*char sStr[512];
tString sStateName1 = "NONE";
if(mlCurrentState >=0) sStateName1 = gvStateName[mlCurrentState];
tString sStateName2 = "NONE";
if(mlCurrentState >=0) sStateName2 = gvStateName[alId];
snprintf(sStr, 512, "%s State %s -> %s",msName.c_str(),sStateName1.c_str(),sStateName2.c_str());
//mpInit->mpEffectHandler->GetSubTitle()->Add(cString::To16Char(sStr),1.2f,false);
Log("%s\n",sStr);*/
// Log("Leave old...");
if (mlCurrentState >= 0)
mvStates[mlCurrentState]->OnLeaveState(mvStates[alId]);
int lPrevState = mlCurrentState;
iGameEnemyState *pPrevState = NULL;
if (mlCurrentState >= 0)
pPrevState = mvStates[mlCurrentState];
mlCurrentState = alId;
mbCanSeePlayer = false;
// Log("enter newer\n");
mvStates[mlCurrentState]->SetPreviousState(lPrevState);
mvStates[mlCurrentState]->OnEnterState(pPrevState);
}
iGameEnemyState *iGameEnemy::GetState(int alId) {
return mvStates[mlCurrentState];
}
//-----------------------------------------------------------------------
bool iGameEnemy::HandleTrigger(cGameTrigger *apTrigger) {
switch (apTrigger->GetType()) {
case eGameTriggerType_Sound:
return HandleSoundTrigger(apTrigger);
}
return true;
}
//-----------------------------------------------------------------------
bool iGameEnemy::HandleSoundTrigger(cGameTrigger *apTrigger) {
if (mfSkipSoundTriggerCount > 0)
return false;
cGameTrigger_Sound *pSoundTrigger = static_cast<cGameTrigger_Sound *>(apTrigger);
//////////////////////////////////////
// Calculate volume of sound
float fDistance = cMath::Vector3Dist(GetPosition(), pSoundTrigger->GetWorldPosition());
float fMin = pSoundTrigger->mpSound->GetMinDistance();
float fMax = pSoundTrigger->mpSound->GetMaxDistance();
float fHearVolume = 1.0f - cMath::Clamp((fDistance - fMin) / (fMax - fMin), 0.0f, 1.0f);
fHearVolume *= pSoundTrigger->mpSound->GetVolume();
// If not audible return
if (fHearVolume <= 0)
return false;
return mvStates[mlCurrentState]->OnHearNoise(pSoundTrigger->GetWorldPosition(), fHearVolume);
return true;
}
//-----------------------------------------------------------------------
cVector3f iGameEnemy::GetPosition() {
return mpMover->GetCharBody()->GetPosition();
}
//-----------------------------------------------------------------------
void iGameEnemy::PlayAnim(const tString &asName, bool abLoop, float afFadeTime,
bool abDependsOnSpeed, float afSpeedMul,
bool abSyncWithPrevFrame,
bool abOverideMoveState) {
// Check if the animation is already playing.
if (mpCurrentAnimation != NULL &&
mpCurrentAnimation->GetName() == asName &&
mpCurrentAnimation->IsActive() &&
mpCurrentAnimation->IsOver() == false) {
return;
}
cAnimationState *pNewAnim = mpMeshEntity->GetAnimationStateFromName(asName);
if (pNewAnim == NULL) {
// Warning("Animation '%s' does not exist!\n",asName.c_str());
return;
}
pNewAnim->SetActive(true);
if (mpCurrentAnimation && mpCurrentAnimation != pNewAnim) {
mpCurrentAnimation->FadeOut(afFadeTime);
if (pNewAnim->IsFading() == false)
pNewAnim->SetWeight(0);
pNewAnim->FadeIn(afFadeTime);
} else {
pNewAnim->SetWeight(1.0f);
}
pNewAnim->SetLoop(abLoop);
/////////////////////////////////////////
// Check if this animation should start at the same place as the previous
if (abSyncWithPrevFrame && mpCurrentAnimation) {
pNewAnim->SetRelativeTimePosition(mpCurrentAnimation->GetRelativeTimePosition());
} else {
pNewAnim->SetTimePosition(0);
}
mpCurrentAnimation = pNewAnim;
mbAnimationIsSpeedDependant = abDependsOnSpeed;
mfAnimationSpeedMul = afSpeedMul;
mbOverideMoveState = abOverideMoveState;
}
//-----------------------------------------------------------------------
void iGameEnemy::UseMoveStateAnimations() {
if (mbOverideMoveState) {
mbOverideMoveState = false;
mMoveState = eEnemyMoveState_LastEnum;
}
}
//-----------------------------------------------------------------------
void iGameEnemy::PlaySound(const tString &asName) {
if (asName == "")
return;
cWorld3D *pWorld = mpInit->mpGame->GetScene()->GetWorld3D();
cSoundEntity *pSound = pWorld->CreateSoundEntity("Enemy", asName, true);
if (pSound) {
pSound->SetPosition(mpMover->GetCharBody()->GetPosition());
// TODO: Attach instead...
} else {
Warning("Couldn't play sound '%s'\n", asName.c_str());
}
}
//-----------------------------------------------------------------------
void iGameEnemy::AddPatrolNode(const tString &asNode, float afTime, const tString &asAnimation) {
mvPatrolNodes.push_back(cEnemyPatrolNode(asNode, afTime, asAnimation));
}
//-----------------------------------------------------------------------
void iGameEnemy::ClearPatrolNodes() {
mvPatrolNodes.clear();
mlCurrentPatrolNode = 0;
if (mbActive && mfHealth > 0)
ChangeState(STATE_IDLE);
}
//-----------------------------------------------------------------------
void iGameEnemy::OnDeath(float afX) {
// PlaySound("temp_roach_death");
if (msOnDeathCallback != "") {
tString sCommand = msOnDeathCallback + "(\"" + msName + "\")";
msOnDeathCallback = "";
mpInit->RunScriptCommand(sCommand);
}
mvStates[mlCurrentState]->OnDeath(afX);
}
//-----------------------------------------------------------------------
void iGameEnemy::OnDamage(float afX) {
if (mfDamageSoundTimer <= 0) {
// PlaySound("temp_roach_damage");
mfDamageSoundTimer = 0.8f;
}
mvStates[mlCurrentState]->OnTakeHit(afX);
}
//-----------------------------------------------------------------------
void iGameEnemy::OnFlashlight(const cVector3f &avPos) {
mvStates[mlCurrentState]->OnFlashlight(avPos);
}
//-----------------------------------------------------------------------
void iGameEnemy::OnSetActive(bool abX) {
// This will do for now:
for (size_t i = 0; i < mvBodies.size(); ++i) {
mvBodies[i]->SetActive(false);
}
// Make sure it is on the ground
if (mfHealth > 0 && mbSetFeetAtGroundOnStart) {
cVector3f vGroundPosition = mpMover->GetCharBody()->GetFeetPosition();
mFindGround.GetGround(mpMover->GetCharBody()->GetPosition(), cVector3f(0, -1, 0),
&vGroundPosition, NULL);
mpMover->GetCharBody()->SetFeetPosition(vGroundPosition);
}
if (mbActive == false) {
if (mbRemoveAttackerOnDisable)
mpInit->mpMusicHandler->RemoveAttacker(this);
if (mfHealth > 0)
ChangeState(STATE_IDLE);
} else {
mbHasBeenActivated = true;
}
}
//-----------------------------------------------------------------------
bool iGameEnemy::CanSeePlayer() {
if (mpInit->mpMapHandler->IsPreUpdating() || mpInit->mpPlayer->IsDead())
return false;
return mbCanSeePlayer;
}
//-----------------------------------------------------------------------
bool iGameEnemy::CheckForDoor() {
iCharacterBody *pBody = mpMover->GetCharBody();
float fRadius = pBody->GetSize().x / 2.0f - 0.1f;
cVector3f vStart = pBody->GetPosition() + pBody->GetForward() * fRadius;
cVector3f vEnd = vStart + pBody->GetForward() * 0.4f;
bool bRet = mDoorCheck.CheckDoor(vStart, vEnd);
Log("CheckDoor: %d\n", bRet);
return bRet;
}
bool iGameEnemy::CheckForTeamMate(float afMaxDist, bool abCheckIfFighting) {
cVector3f vPosition = mpMover->GetCharBody()->GetFeetPosition();
tGameEnemyIterator it = mpInit->mpMapHandler->GetGameEnemyIterator();
while (it.HasNext()) {
iGameEnemy *pEnemy = it.Next();
if (GetEnemyType() != pEnemy->GetEnemyType())
continue;
if (pEnemy == this || pEnemy->IsActive() == false || pEnemy->GetHealth() <= 0)
continue;
if (abCheckIfFighting && pEnemy->IsFighting() == false)
continue;
float fDist = cMath::Vector3Dist(pEnemy->GetMover()->GetCharBody()->GetPosition(),
vPosition);
if (fDist <= afMaxDist) {
return true;
}
}
return false;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PROTECTED METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void iGameEnemy::UpdateEnemyPose(float afTimeStep) {
if (mbAlignToGroundNormal == false)
return;
if (mfPoseCount == 0) {
m_mtxStartPose = m_mtxGoalPose;
cVector3f vNormal(0, 1, 0);
cVector3f vStartPos = mpMover->GetCharBody()->GetFeetPosition() + cVector3f(0, 0.05f, 0);
cVector3f vPosition = vStartPos;
mFindGround.GetGround(vStartPos, cVector3f(0, -1, 0), &vPosition, &vNormal);
cVector3f vUp(0, 1, 0);
float fDist = vStartPos.y - vPosition.y;
vNormal.Normalise();
float fAngle = cMath::Vector3Angle(vUp, vNormal);
cVector3f vRotateAxis = cMath::Vector3Cross(vUp, vNormal);
// cVector3f vRotateAxis = cMath::Vector3Cross(vUp,mpMover->GetCharBody()->GetForward());
// cVector3f vRotateAxis2 = cMath::Vector3Cross(vUp,vRotateAxis);
vRotateAxis.Normalise();
cQuaternion qRotation = cQuaternion(fAngle, vRotateAxis);
cMatrixf mtxPoseRotation = cMath::MatrixQuaternion(qRotation);
// cVector3f vDelta = vPosition - mpMover->GetCharBody()->GetFeetPosition();
// mtxPoseRotation.SetTranslation(cVector3f(0,-fabs(vDelta.y),0));
// mtxPoseRotation.SetTranslation(vNormal * -fabs(vDelta.y));
if (vNormal != cVector3f(0, 1, 0)) {
mFindGround.GetGround(mpMover->GetCharBody()->GetPosition(), vNormal * -1.0f, &vPosition, NULL);
fDist = cMath::Vector3Dist(mpMover->GetCharBody()->GetPosition(), vPosition);
// So there is no warp to the ground.
float fLimit = mpMover->GetCharBody()->GetSize().y * 0.82f;
if (fDist > fLimit)
vNormal = cVector3f(0, 1, 0);
}
///////////////////////////
// Get the offest
if (vNormal != cVector3f(0, 1, 0)) {
fDist -= mpMover->GetCharBody()->GetSize().y / 2.0f;
mtxPoseRotation.SetTranslation(vNormal * -fDist);
} else {
mtxPoseRotation.SetTranslation(0);
}
m_mtxGoalPose = mtxPoseRotation;
} else {
cMatrixf mtxPoseRotation = cMath::MatrixSlerp(mfPoseCount, m_mtxStartPose, m_mtxGoalPose, true);
mpMover->GetCharBody()->SetEntityPostOffset(mtxPoseRotation);
}
mfPoseCount += 6.5f * afTimeStep;
if (mfPoseCount > 1.0f)
mfPoseCount = 0;
}
//-----------------------------------------------------------------------
void iGameEnemy::UpdateCheckForPlayer(float afTimeStep) {
// Do not check for player at pre update.
if (mpInit->mpMapHandler->IsPreUpdating() ||
mpInit->mpPlayer->IsDead() ||
mbUsesTriggers == false ||
mfHealth <= 0) {
mbCanSeePlayer = false;
return;
}
/*if(mfCanSeePlayerCount>0)
{
mfCanSeePlayerCount -= afTimeStep;
if(mfCanSeePlayerCount<=0) mbCanSeePlayer = false;
}*/
if (mfCalcPlayerHiddenPosCount > 0)
mfCalcPlayerHiddenPosCount -= afTimeStep;
// Check if it is time to check for player.
if (mfCheckForPlayerCount < mfCheckForPlayerRate) {
mfCheckForPlayerCount += afTimeStep;
return;
}
mfCheckForPlayerCount = 0;
iCharacterBody *pPlayerBody = mpInit->mpPlayer->GetCharacterBody();
float fDist = cMath::Vector3Dist(mpMover->GetCharBody()->GetPosition(), pPlayerBody->GetPosition());
float fMinLength = mpMover->GetCharBody()->GetBody()->GetBV()->GetRadius() +
pPlayerBody->GetBody()->GetBV()->GetRadius();
// Lower some stuff if player is hidden
float fStartFOV = mfFOV;
float fStartMaxSeeDist = mfMaxSeeDist;
if (mbCanSeePlayer == false && fDist > 1.3f) // 1.3 = really close, remove all handicap.
{
if (mpInit->mDifficulty == eGameDifficulty_Easy) {
mfFOV *= 0.6f;
mfMaxSeeDist *= 0.6f;
}
if (mpInit->mpPlayer->GetHidden()->IsHidden()) {
mfFOV *= 0.36f;
mfMaxSeeDist *= 0.25f;
} else if (mpInit->mpPlayer->GetHidden()->InShadows()) {
if (mpInit->mpPlayer->GetMoveState() == ePlayerMoveState_Crouch) {
mfFOV *= 0.6f;
mfMaxSeeDist *= 0.65f;
} else {
mfFOV *= 0.8f;
mfMaxSeeDist *= 0.85f;
}
}
}
gfCurrentViewDist = fDist;
gfCurrentMaxViewDist = mfMaxSeeDist;
if ((fDist <= mfMaxSeeDist && LineOfSight(pPlayerBody->GetPosition(), pPlayerBody->GetSize())) ||
fDist <= fMinLength) {
// Increase LOS counter,
mlPlayerInLOSCount++;
// Player must have been in LOS mlMaxPlayerInLOSCount times before it is considered seen.
if (mlPlayerInLOSCount >= mlMaxPlayerInLOSCount) {
mlPlayerInLOSCount = mlMaxPlayerInLOSCount;
float fChance = 0;
if (fDist > mfMaxSeeDist)
fChance = 0;
else
fChance = 1 - (fDist / mfMaxSeeDist);
if (mbCanSeePlayer == false) {
mvStates[mlCurrentState]->OnSeePlayer(pPlayerBody->GetPosition(), fChance);
mpInit->mpPlayer->GetHidden()->UnHide();
}
mvLastPlayerPos = pPlayerBody->GetFeetPosition();
mbCanSeePlayer = true;
mfCanSeePlayerCount = 1.0f / 3.0f;
mfCalcPlayerHiddenPosCount = 1.5f;
}
} else {
// Reset LOS counter,
mlPlayerInLOSCount--;
if (mlPlayerInLOSCount < 0)
mlPlayerInLOSCount = 0;
// this is so that the enemy get a little better last pos
// and thus improving path finding.
if (mfCalcPlayerHiddenPosCount > 0) {
mvLastPlayerPos = pPlayerBody->GetFeetPosition();
}
mbCanSeePlayer = false;
}
mfFOV = fStartFOV;
mfMaxSeeDist = fStartMaxSeeDist;
}
//-----------------------------------------------------------------------
void iGameEnemy::UpdateAnimations(float afTimeStep) {
iCharacterBody *pBody = mpMover->GetCharBody();
float fMoveSpeed = pBody->GetMoveSpeed(eCharDir_Forward);
float fSpeed = pBody->GetVelocity(afTimeStep).Length();
if (fMoveSpeed < 0)
fSpeed = -fSpeed;
float fTurnSpeed = mpMover->GetTurnSpeed();
////////////////////////////////
// Override animation
if (mbOverideMoveState && mpCurrentAnimation != NULL) {
if (mpCurrentAnimation->IsOver()) {
mvStates[mlCurrentState]->OnAnimationOver(mpCurrentAnimation->GetName());
}
if (mbAnimationIsSpeedDependant) {
if (ABS(fSpeed) > 0.05f)
mpCurrentAnimation->SetSpeed(ABS(fSpeed) * mfAnimationSpeedMul);
else
mpCurrentAnimation->SetSpeed(ABS(fTurnSpeed) * mfAnimationSpeedMul * 2);
}
}
////////////////////////////////
// Move state animation
else {
eEnemyMoveState prevMoveState = mMoveState;
switch (mMoveState) {
// Backward
case eEnemyMoveState_Backward:
if (fSpeed >= 0)
mMoveState = eEnemyMoveState_Stopped;
break;
// Stopped State
case eEnemyMoveState_Stopped:
if (fSpeed < -0.05f)
mMoveState = eEnemyMoveState_Backward;
else if (fSpeed >= mfStoppedToWalkSpeed)
mMoveState = eEnemyMoveState_Walking;
else if (ABS(fTurnSpeed) > 0.07f)
mMoveState = eEnemyMoveState_Walking;
break;
// Walking State
case eEnemyMoveState_Walking:
if (fSpeed >= mfWalkToRunSpeed)
mMoveState = eEnemyMoveState_Running;
else if (fSpeed <= mfWalkToStoppedSpeed) {
if (ABS(fTurnSpeed) < 0.03f)
mMoveState = eEnemyMoveState_Stopped;
}
break;
// Running State
case eEnemyMoveState_Running:
if (fSpeed <= mfRunToWalkSpeed)
mMoveState = eEnemyMoveState_Walking;
break;
// NULL
case eEnemyMoveState_LastEnum:
mMoveState = eEnemyMoveState_Stopped;
break;
}
//////////////////////////////////////////////
// If move state has changed, change animation
if (prevMoveState != mMoveState) {
// Backward
if (mMoveState == eEnemyMoveState_Backward) {
PlayAnim(msBackwardAnim, true, 0.4f, true, mfMoveAnimSpeedMul, false, false);
}
// Stopped
else if (mMoveState == eEnemyMoveState_Stopped) {
PlayAnim(msStoppedAnim, true, 0.7f, false, 1.0f, false, false);
}
// Walking
else if (mMoveState == eEnemyMoveState_Walking) {
bool bSync = prevMoveState == eEnemyMoveState_Running ? true : false;
PlayAnim(msWalkAnim, true, 0.2f, true, mfMoveAnimSpeedMul, bSync, false);
}
// Running
else if (mMoveState == eEnemyMoveState_Running) {
bool bSync = prevMoveState == eEnemyMoveState_Walking ? true : false;
PlayAnim(msRunAnim, true, 0.2f, true, mfMoveAnimSpeedMul, bSync, false);
}
}
/////////////////////////////////
// Update animation speed
if (mbAnimationIsSpeedDependant && mpCurrentAnimation) {
if (ABS(fSpeed) > 0.05f)
mpCurrentAnimation->SetSpeed(ABS(fSpeed) * mfMoveAnimSpeedMul);
else
mpCurrentAnimation->SetSpeed(ABS(fTurnSpeed) * mfMoveAnimSpeedMul * 2);
}
}
}
//-----------------------------------------------------------------------
void iGameEnemy::SetupBody() {
mpMover->GetCharBody()->SetMaxPositiveMoveSpeed(eCharDir_Forward, mfMaxForwardSpeed);
mpMover->GetCharBody()->SetMaxNegativeMoveSpeed(eCharDir_Forward, -mfMaxBackwardSpeed);
mpMover->GetCharBody()->SetMoveAcc(eCharDir_Forward, mfAcceleration);
mpMover->GetCharBody()->SetMaxPushMass(mfMaxPushMass);
mpMover->GetCharBody()->SetPushForce(mfPushForce);
mpMover->SetMaxTurnSpeed(mfMaxTurnSpeed);
mpMover->SetAngleDistTurnMul(mfAngleDistTurnMul);
mpMover->SetMinBreakAngle(mfMinBreakAngle);
mpMover->SetBreakAngleMul(mfBreakAngleMul);
mpMover->SetMaxPushMass(mfMaxPushMass);
}
//-----------------------------------------------------------------------
static const cVector2f gvPosAdds[] = {cVector2f(0, 0),
cVector2f(1, 0),
cVector2f(-1, 0),
cVector2f(0, 1),
cVector2f(0, -1)};
bool iGameEnemy::LineOfSight(const cVector3f &avPos, const cVector3f &avSize) {
// Setup debug
// if(mvRayStartPoses.size()<5) mvRayStartPoses.resize(5);
// if(mvRayEndPoses.size()<5) mvRayEndPoses.resize(5);
iPhysicsWorld *pPhysicsWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
cVector3f vStartCenter = mpMover->GetCharBody()->GetPosition();
cVector3f vEndCenter = avPos;
/////////////////////////////
// Calculate the right vector
const cVector3f vForward = cMath::Vector3Normalize(vEndCenter - vStartCenter);
const cVector3f vUp = cVector3f(0, 1.0f, 0);
const cVector3f vRight = cMath::Vector3Cross(vForward, vUp);
////////////////////////////////////
// Check if the pos is within FOV
if (mfFOV < k2Pif) {
cVector3f vEnemyForward = mpMover->GetCharBody()->GetForward();
// float fAngle = cMath::Vector3Angle(vEnemyForward, vForward);
// if(fAngle > mfFOV*0.5f) return false;
cVector3f vToPlayerAngle = cMath::GetAngleFromPoints3D(0, vForward);
cVector3f vEnemyAngle = cMath::GetAngleFromPoints3D(0, vEnemyForward);
float fAngleX = cMath::Abs(cMath::GetAngleDistanceRad(vToPlayerAngle.x, vEnemyAngle.x));
float fAngleY = cMath::Abs(cMath::GetAngleDistanceRad(vToPlayerAngle.y, vEnemyAngle.y));
// Log("X:%f Y:%f\n",cMath::ToDeg(fAngleX), cMath::ToDeg(fAngleY));
if (fAngleY > mfFOV * 0.5f)
return false;
if (fAngleX > mfFOV * mfFOVXMul * 0.5f)
return false;
}
// Get the half with and height. Make them a little smaller so that player can slide over funk on floor.
const float fHalfWidth = avSize.x * 0.4f;
const float fHalfHeight = avSize.y * 0.4f;
// Count of 2 is needed for a line of sight success.
int lCount = 0;
// Iterate through all the rays.
for (int i = 0; i < 5; ++i) {
cVector3f vAdd = vRight * (gvPosAdds[i].x * fHalfWidth) + vUp * (gvPosAdds[i].y * fHalfHeight);
cVector3f vStart = vStartCenter + vAdd;
cVector3f vEnd = vEndCenter + vAdd;
// mvRayStartPoses[i] = vStart;
// mvRayEndPoses[i] =vEnd;
mRayCallback.Reset();
pPhysicsWorld->CastRay(&mRayCallback, vStart, vEnd, false, false, false);
if (mRayCallback.Intersected() == false)
lCount++;
if (lCount == 2)
return true;
}
return false;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// LOADER
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cEntityLoader_GameEnemy::cEntityLoader_GameEnemy(const tString &asName, cInit *apInit)
: cEntityLoader_Object(asName) {
mpInit = apInit;
}
cEntityLoader_GameEnemy::~cEntityLoader_GameEnemy() {
}
//-----------------------------------------------------------------------
void cEntityLoader_GameEnemy::BeforeLoad(TiXmlElement *apRootElem, const cMatrixf &a_mtxTransform,
cWorld3D *apWorld) {
}
//-----------------------------------------------------------------------
void cEntityLoader_GameEnemy::AfterLoad(TiXmlElement *apRootElem, const cMatrixf &a_mtxTransform,
cWorld3D *apWorld) {
iGameEnemy *pEnemy = NULL;
tString sSubtype = "";
tString sName = "";
///////////////////////////////////
// Load game properties
TiXmlElement *pMainElem = apRootElem->FirstChildElement("MAIN");
if (pMainElem) {
sSubtype = cString::ToString(pMainElem->Attribute("Subtype"), "");
sName = cString::ToString(pMainElem->Attribute("Name"), "");
} else {
Error("Couldn't find main element for entity '%s'\n", mpEntity->GetName().c_str());
}
///////////////////////////////////
// Load the enemy type
TiXmlElement *pGameElem = apRootElem->FirstChildElement("GAME");
if (sSubtype == "Dog") {
pEnemy = hplNew(cGameEnemy_Dog, (mpInit, mpEntity->GetName(), pGameElem));
}
#ifndef DEMO_VERSION
else if (sSubtype == "Spider") {
pEnemy = hplNew(cGameEnemy_Spider, (mpInit, mpEntity->GetName(), pGameElem));
} else if (sSubtype == "Worm") {
pEnemy = hplNew(cGameEnemy_Worm, (mpInit, mpEntity->GetName(), pGameElem));
}
#endif
pEnemy->msSubType = sSubtype;
pEnemy->msEnemyType = msName;
pEnemy->msFileName = msFileName;
pEnemy->m_mtxOnLoadTransform = a_mtxTransform;
// Do stuff that is not done when loading from savegame.
pEnemy->SetMeshEntity(mpEntity);
pEnemy->SetBodies(mvBodies);
pEnemy->Setup(apWorld);
/////////////////////////////////
// Add to map handler
mpInit->mpMapHandler->AddGameEntity(pEnemy);
mpInit->mpMapHandler->AddGameEnemy(pEnemy);
iCharacterBody *pBody = pEnemy->mpMover->GetCharBody();
pBody->SetPosition(mpEntity->GetWorldPosition() + cVector3f(0, pBody->GetSize().y / 2, 0));
// Set the correct heading
cMatrixf mtxInv = cMath::MatrixInverse(mpEntity->GetWorldMatrix());
cVector3f vBodyRotation = cMath::GetAngleFromPoints3D(cVector3f(0, 0, 0), mtxInv.GetForward() * -1);
pBody->SetYaw(vBodyRotation.y);
// Log("Loaded enemy!\n");
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// SAVE OBJECT STUFF
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
kBeginSerializeBase(cEnemyPatrolNode)
kSerializeVar(msNodeName, eSerializeType_String)
kSerializeVar(mfWaitTime, eSerializeType_Float32)
kSerializeVar(msAnimation, eSerializeType_String)
kEndSerialize()
kBeginSerialize(iGameEnemy_SaveData, iGameEntity_SaveData)
kSerializeVar(mbHasBeenActivated, eSerializeType_Bool)
kSerializeVar(mvCharBodyPosition, eSerializeType_Vector3f)
kSerializeVar(mvCharBodyRotation, eSerializeType_Vector3f)
kSerializeVar(mlCurrentPatrolNode, eSerializeType_Int32)
kSerializeVar(mfDisappearTime, eSerializeType_Float32)
kSerializeVar(mbDisappearActive, eSerializeType_Bool)
kSerializeVar(mbHasDisappeared, eSerializeType_Bool)
kSerializeVar(mbUsesTriggers, eSerializeType_Bool)
kSerializeVar(mvLastPlayerPos, eSerializeType_Vector3f)
kSerializeVar(msOnDeathCallback, eSerializeType_String)
kSerializeVar(msOnAttackCallback, eSerializeType_String)
kSerializeClassContainer(mvPatrolNodes, cEnemyPatrolNode, eSerializeType_Class)
kEndSerialize()
//-----------------------------------------------------------------------
iGameEntity *iGameEnemy_SaveData::CreateEntity() {
return NULL;
}
//-----------------------------------------------------------------------
iGameEntity_SaveData *iGameEnemy::CreateSaveData() {
return hplNew(iGameEnemy_SaveData, ());
}
//-----------------------------------------------------------------------
void iGameEnemy::SaveToSaveData(iGameEntity_SaveData *apSaveData) {
super::SaveToSaveData(apSaveData);
iGameEnemy_SaveData *pData = static_cast<iGameEnemy_SaveData *>(apSaveData);
kCopyToVar(pData, mbHasBeenActivated);
pData->mvCharBodyPosition = mpMover->GetCharBody()->GetPosition();
pData->mvCharBodyRotation.x = mpMover->GetCharBody()->GetPitch();
pData->mvCharBodyRotation.y = mpMover->GetCharBody()->GetYaw();
kCopyToVar(pData, mlCurrentPatrolNode);
kCopyToVar(pData, mvLastPlayerPos);
kCopyToVar(pData, msOnDeathCallback);
kCopyToVar(pData, msOnAttackCallback);
kCopyToVar(pData, mfDisappearTime);
kCopyToVar(pData, mbDisappearActive);
kCopyToVar(pData, mbHasDisappeared);
kCopyToVar(pData, mbUsesTriggers);
pData->mvPatrolNodes.Resize(mvPatrolNodes.size());
for (size_t i = 0; i < mvPatrolNodes.size(); ++i) {
pData->mvPatrolNodes[i].msNodeName = mvPatrolNodes[i].msNodeName;
pData->mvPatrolNodes[i].mfWaitTime = mvPatrolNodes[i].mfWaitTime;
pData->mvPatrolNodes[i].msAnimation = mvPatrolNodes[i].msAnimation;
}
}
//-----------------------------------------------------------------------
void iGameEnemy::LoadFromSaveData(iGameEntity_SaveData *apSaveData) {
super::LoadFromSaveData(apSaveData);
iGameEnemy_SaveData *pData = static_cast<iGameEnemy_SaveData *>(apSaveData);
kCopyFromVar(pData, mbHasBeenActivated);
mpMover->GetCharBody()->SetPosition(pData->mvCharBodyPosition);
mpMover->GetCharBody()->SetPitch(pData->mvCharBodyRotation.x);
mpMover->GetCharBody()->SetYaw(pData->mvCharBodyRotation.y);
mpMover->GetCharBody()->UpdateMoveMarix();
kCopyFromVar(pData, mlCurrentPatrolNode);
kCopyFromVar(pData, mvLastPlayerPos);
kCopyFromVar(pData, msOnDeathCallback);
kCopyFromVar(pData, msOnAttackCallback);
kCopyFromVar(pData, mfDisappearTime);
kCopyFromVar(pData, mbDisappearActive);
kCopyFromVar(pData, mbHasDisappeared);
kCopyFromVar(pData, mbUsesTriggers);
mvPatrolNodes.resize(pData->mvPatrolNodes.Size());
for (size_t i = 0; i < mvPatrolNodes.size(); ++i) {
mvPatrolNodes[i].msNodeName = pData->mvPatrolNodes[i].msNodeName;
mvPatrolNodes[i].mfWaitTime = pData->mvPatrolNodes[i].mfWaitTime;
mvPatrolNodes[i].msAnimation = pData->mvPatrolNodes[i].msAnimation;
}
// Log("Load Save Data!\n");
}
//-----------------------------------------------------------------------