mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
1676 lines
50 KiB
C++
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");
|
|
}
|
|
//-----------------------------------------------------------------------
|