scummvm/engines/hpl1/penumbra-overture/GameObject.cpp
2023-01-26 10:17:22 +01:00

996 lines
35 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/GameObject.h"
#include "hpl1/engine/engine.h"
#include "hpl1/penumbra-overture/AttackHandler.h"
#include "hpl1/penumbra-overture/EffectHandler.h"
#include "hpl1/penumbra-overture/GameEnemy.h"
#include "hpl1/penumbra-overture/GameStickArea.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/GlobalInit.h"
//////////////////////////////////////////////////////////////////////////
// CALLBACK
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cGameObjectBodyCallback::cGameObjectBodyCallback(cInit *apInit, cGameObject *apObject) {
mpInit = apInit;
mpObject = apObject;
}
//-----------------------------------------------------------------------
bool cGameObjectBodyCallback::OnBeginCollision(iPhysicsBody *apBody, iPhysicsBody *apCollideBody) {
return true;
}
//-----------------------------------------------------------------------
void cGameObjectBodyCallback::OnCollide(iPhysicsBody *apBody, iPhysicsBody *apCollideBody,
cPhysicsContactData *apContactData) {
// Log("OnCollide %s vs %s\n",apBody->GetName().c_str(),apCollideBody->GetName().c_str());
/////////////////////////////////////////
// Damage on enemies
if (apCollideBody->IsCharacter()) {
// Check if there is an enemy.
iGameEnemy *pEnemy = (iGameEnemy *)apCollideBody->GetUserData();
if (pEnemy && apBody->GetMass() > 4) {
float fSpeed = apBody->GetLinearVelocity().Length();
float fImpulseSize = fSpeed * apBody->GetMass();
if (fSpeed > 4.5f && fImpulseSize > 25 && ABS(apContactData->mfMaxContactNormalSpeed) >= 1.0f) {
pEnemy->Damage(fImpulseSize * 0.1f, 1);
}
}
}
/////////////////////////////////////////
// Check if the object breaks
if (mpObject->mBreakProps.mbActive) {
float fImpulseSize = apBody->GetLinearVelocity().Length() * apBody->GetMass() +
apCollideBody->GetLinearVelocity().Length() * apCollideBody->GetMass();
if (fImpulseSize >= mpObject->mBreakProps.mfMinImpulse &&
ABS(apContactData->mfMaxContactNormalSpeed) >= mpObject->mBreakProps.mfMinNormalSpeed) {
if (mpInit->mbDebugInteraction) {
Log("------ Breakage ----------\n");
Log(" Body '%s' by Body '%s'\n", apBody->GetName().c_str(), apCollideBody->GetName().c_str());
Log(" Impulse: %f (%fm/s * %fkg) + (%fm/s * %fkg)\n", fImpulseSize,
apBody->GetLinearVelocity().Length(), apBody->GetMass(),
apCollideBody->GetLinearVelocity().Length(), apCollideBody->GetMass());
Log("-------------------------\n");
}
mpObject->Break();
}
}
////////////////////////////////////////////
// Do Some Damage
if (mpObject->mDamageProps.mbActive) {
float fDamage = 0;
// Damage by linear velocity.
float fSpeed = apBody->GetLinearVelocity().Length();
if (fSpeed > mpObject->mDamageProps.mfMinLinearDamageSpeed) {
if (fSpeed > mpObject->mDamageProps.mfMaxLinearDamageSpeed)
fSpeed = mpObject->mDamageProps.mfMaxLinearDamageSpeed;
fDamage = mpObject->mDamageProps.mfMinDamage +
(mpObject->mDamageProps.mfMaxDamage - mpObject->mDamageProps.mfMinDamage) *
((fSpeed - mpObject->mDamageProps.mfMinLinearDamageSpeed) /
(mpObject->mDamageProps.mfMaxLinearDamageSpeed - mpObject->mDamageProps.mfMinLinearDamageSpeed));
}
// Damage by angular velocity.
fSpeed = apBody->GetAngularVelocity().Length();
if (fSpeed > mpObject->mDamageProps.mfMinAngularDamageSpeed) {
if (fSpeed > mpObject->mDamageProps.mfMaxAngularDamageSpeed)
fSpeed = mpObject->mDamageProps.mfMaxAngularDamageSpeed;
float fTempDamage = mpObject->mDamageProps.mfMinDamage +
(mpObject->mDamageProps.mfMaxDamage - mpObject->mDamageProps.mfMinDamage) *
((fSpeed - mpObject->mDamageProps.mfMinAngularDamageSpeed) /
(mpObject->mDamageProps.mfMaxAngularDamageSpeed - mpObject->mDamageProps.mfMinAngularDamageSpeed));
if (fTempDamage > fDamage)
fDamage = fTempDamage;
}
//////////////////////////
// Do the damage
if (fDamage > 0) {
// Player
if (apCollideBody == mpInit->mpPlayer->GetCharacterBody()->GetBody()) {
mpInit->mpPlayer->Damage(fDamage, ePlayerDamageType_BloodSplash);
}
// Entity
else {
iGameEntity *pEntity = (iGameEntity *)apCollideBody->GetUserData();
if (pEntity) {
pEntity->Damage(fDamage, mpObject->mDamageProps.mlDamageStrength);
}
}
}
}
/*if(apCollideBody->IsCharacter()==false) return;
//Check if there is an enemy.
iGameEnemy *pEnemy = NULL;
if(apCollideBody->GetUserData())
{
pEnemy = (iGameEnemy*)apCollideBody->GetUserData();
}
////////////////////////////////////////////
// Stun
if(pEnemy && mpObject->GetInteractMode() != eObjectInteractMode_Push)
{
float fSpeed = apBody->GetLinearVelocity().Length();
float fMass = apBody->GetMass();
float fImpulse = fSpeed * fMass;
if(pEnemy->mfMinStunSpeed <= fSpeed && pEnemy->mfMinStunImpulse <= fImpulse)
{
//Log("Stun by %s speed: %f mass: %f\n",apBody->GetName().c_str(),fSpeed,fMass);
pEnemy->Stun(pEnemy->mfObjectCollideStun, true);
}
}*/
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// LOADER
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cEntityLoader_GameObject::cEntityLoader_GameObject(const tString &asName, cInit *apInit)
: cEntityLoader_Object(asName) {
mpInit = apInit;
}
cEntityLoader_GameObject::~cEntityLoader_GameObject() {
}
//-----------------------------------------------------------------------
void cEntityLoader_GameObject::BeforeLoad(TiXmlElement *apRootElem, const cMatrixf &a_mtxTransform,
cWorld3D *apWorld) {
}
//-----------------------------------------------------------------------
void cEntityLoader_GameObject::AfterLoad(TiXmlElement *apRootElem, const cMatrixf &a_mtxTransform,
cWorld3D *apWorld) {
cGameObject *pObject = hplNew(cGameObject, (mpInit, mpEntity->GetName()));
pObject->msFileName = msFileName;
pObject->m_mtxOnLoadTransform = a_mtxTransform;
// Set the engine objects to the object
pObject->SetBodies(mvBodies);
pObject->SetBeams(mvBeams);
pObject->SetMeshEntity(mpEntity);
pObject->SetParticleSystems(mvParticleSystems);
pObject->SetSoundEntities(mvSoundEntities);
pObject->SetLights(mvLights);
///////////////////////////////////
// Load game properties
TiXmlElement *pGameElem = apRootElem->FirstChildElement("GAME");
if (pGameElem) {
////////////////////////////////////////////
// General
pObject->mInteractMode = ToInteractMode(pGameElem->Attribute("InteractMode"));
pObject->mfHealth = cString::ToFloat(pGameElem->Attribute("Health"), 0);
pObject->mlToughness = cString::ToInt(pGameElem->Attribute("Toughness"), 0);
pObject->mfForwardUpMul = cString::ToFloat(pGameElem->Attribute("ForwardUpMul"), 1);
pObject->mfForwardRightMul = cString::ToFloat(pGameElem->Attribute("ForwardRightMul"), 1);
pObject->mfUpMul = cString::ToFloat(pGameElem->Attribute("UpMul"), 1);
pObject->mfRightMul = cString::ToFloat(pGameElem->Attribute("RightMul"), 1);
pObject->mbPickAtPoint = cString::ToBool(pGameElem->Attribute("PickAtPoint"), false);
pObject->mbRotateWithPlayer = cString::ToBool(pGameElem->Attribute("RotateWithPlayer"), true);
pObject->mbUseNormalMass = cString::ToBool(pGameElem->Attribute("UseNormalMass"), false);
pObject->mfGrabMassMul = cString::ToFloat(pGameElem->Attribute("GrabMassMul"), 1.0f);
pObject->mbCanBeThrown = cString::ToBool(pGameElem->Attribute("CanBeThrown"), true);
pObject->mbCanBePulled = cString::ToBool(pGameElem->Attribute("CanBePulled"), true);
pObject->mbDestroyable = cString::ToBool(pGameElem->Attribute("Destroyable"), false);
pObject->mfDestroyStrength = cString::ToFloat(pGameElem->Attribute("DestroyStrength"), 0.0f);
pObject->msDestoySound = cString::ToString(pGameElem->Attribute("DestoySound"), "");
pObject->mbPauseControllers = cString::ToBool(pGameElem->Attribute("PauseControllers"), true);
pObject->mbPauseGravity = cString::ToBool(pGameElem->Attribute("PauseGravity"), true);
pObject->mbForceLightOffset = cString::ToBool(pGameElem->Attribute("ForceLightOffset"), false);
pObject->mvLightOffset = cString::ToVector3f(pGameElem->Attribute("LightOffset"), 0);
pObject->mfHapticTorqueMul = cString::ToFloat(pGameElem->Attribute("HapticTorqueMul"), 1.0f);
////////////////////////////////////////////
// Disappear
pObject->mDisappearProps.mbActive = cString::ToBool(pGameElem->Attribute("Disappear"), false);
if (pObject->mDisappearProps.mbActive) {
pObject->mDisappearProps.mfMinTime = cString::ToFloat(pGameElem->Attribute("DisappearMinTime"), 0);
pObject->mDisappearProps.mfMaxTime = cString::ToFloat(pGameElem->Attribute("DisappearMaxTime"), 0);
pObject->mDisappearProps.mfMinDistance = cString::ToFloat(pGameElem->Attribute("DisappearMinDist"), 0);
pObject->mDisappearProps.mfMinCloseDistance = cString::ToFloat(pGameElem->Attribute("DisappearMinCloseDist"), 0);
pObject->mDisappearProps.mfTime = cMath::RandRectf(pObject->mDisappearProps.mfMinTime,
pObject->mDisappearProps.mfMaxTime);
}
////////////////////////////////////////////
// Breakable
pObject->mBreakProps.mbActive = cString::ToBool(pGameElem->Attribute("Breakable"), false);
if (pObject->mBreakProps.mbActive || pObject->mDisappearProps.mbActive) {
pObject->mBreakProps.msSound = cString::ToString(pGameElem->Attribute("BreakSound"), "");
pObject->mBreakProps.msEntity = cString::ToString(pGameElem->Attribute("BreakEntity"), "");
pObject->mBreakProps.msPS = cString::ToString(pGameElem->Attribute("BreakPS"), "");
pObject->mBreakProps.mfMinImpulse = cString::ToFloat(pGameElem->Attribute("BreakImpulse"), 99999);
pObject->mBreakProps.mfMinNormalSpeed = cString::ToFloat(pGameElem->Attribute("BreakNormalSpeed"), 99999);
pObject->mBreakProps.mfMinPlayerImpulse = cString::ToFloat(pGameElem->Attribute("BreakPlayerImpulse"), 99999);
pObject->mBreakProps.mfCenterForce = cString::ToFloat(pGameElem->Attribute("BreakCenterForce"), 0);
pObject->mBreakProps.mbExplosion = cString::ToBool(pGameElem->Attribute("BreakExplosion"), false);
pObject->mBreakProps.mfExpl_Radius = cString::ToFloat(pGameElem->Attribute("BreakExpl_Radius"), 0);
pObject->mBreakProps.mfExpl_MinDamage = cString::ToFloat(pGameElem->Attribute("BreakExpl_MinDamage"), 0);
pObject->mBreakProps.mfExpl_MaxDamage = cString::ToFloat(pGameElem->Attribute("BreakExpl_MaxDamage"), 0);
pObject->mBreakProps.mfExpl_MinForce = cString::ToFloat(pGameElem->Attribute("BreakExpl_MinForce"), 0);
pObject->mBreakProps.mfExpl_MaxForce = cString::ToFloat(pGameElem->Attribute("BreakExpl_MaxForce"), 0);
pObject->mBreakProps.mfExpl_MaxImpulse = cString::ToFloat(pGameElem->Attribute("BreakExpl_MaxImpulse"), 0);
pObject->mBreakProps.mfExpl_MinMass = cString::ToFloat(pGameElem->Attribute("BreakExpl_MinMass"), 0);
pObject->mBreakProps.mlExpl_Strength = cString::ToInt(pGameElem->Attribute("BreakExpl_Strength"), 0);
pObject->mBreakProps.mbLightFlash = cString::ToBool(pGameElem->Attribute("LightFlash"), false);
pObject->mBreakProps.mLight_Color = cString::ToColor(pGameElem->Attribute("LightFlash_Color"), cColor(0, 0));
pObject->mBreakProps.mfLight_Radius = cString::ToFloat(pGameElem->Attribute("LightFlash_Radius"), 0);
pObject->mBreakProps.mfLight_AddTime = cString::ToFloat(pGameElem->Attribute("LightFlash_AddTime"), 0);
pObject->mBreakProps.mfLight_NegTime = cString::ToFloat(pGameElem->Attribute("LightFlash_NegTime"), 0);
pObject->mBreakProps.mvLight_Offset = cString::ToVector3f(pGameElem->Attribute("LightFlash_Offset"), 0);
pObject->mBreakProps.mbEarRing = cString::ToBool(pGameElem->Attribute("EarRing"), false);
pObject->mBreakProps.mfEarRing_MaxDist = cString::ToFloat(pGameElem->Attribute("EarRing_MaxDist"), 0);
pObject->mBreakProps.mfEarRing_Time = cString::ToFloat(pGameElem->Attribute("EarRing_Time"), 0);
// Set all bodies as volatile
for (size_t i = 0; i < mvBodies.size(); ++i) {
mvBodies[i]->SetVolatile(true);
}
}
////////////////////////////////////////////
// Attracts Enemies
pObject->mAttractProps.mbActive = cString::ToBool(pGameElem->Attribute("AttractEnemies"), false);
if (pObject->mAttractProps.mbActive) {
pObject->mAttractProps.mfDistance = cString::ToFloat(pGameElem->Attribute("AttractDistance"), 0);
tString sSubTypes = cString::ToString(pGameElem->Attribute("AttractSubtypes"), "");
cString::GetStringVec(sSubTypes, pObject->mAttractProps.mvSubtypes, NULL);
pObject->mAttractProps.mbIsEaten = cString::ToBool(pGameElem->Attribute("AttractIsEaten"), false);
pObject->mAttractProps.mfEatLength = cString::ToFloat(pGameElem->Attribute("AttractEatLength"), 0);
}
////////////////////////////////////////////
// Mode specific
// Push mode
if (pObject->mInteractMode == eObjectInteractMode_Push) {
pObject->mfMaxInteractDist = cString::ToFloat(pGameElem->Attribute("MaxInteractDist"), mpInit->mpPlayer->GetMaxPushDist());
pObject->mbHasInteraction = true;
}
// Move Mode
else if (pObject->mInteractMode == eObjectInteractMode_Move) {
pObject->mfMaxInteractDist = cString::ToFloat(pGameElem->Attribute("MaxInteractDist"), mpInit->mpPlayer->GetMaxMoveDist());
pObject->mbHasInteraction = true;
}
// Grab Mode
else if (pObject->mInteractMode == eObjectInteractMode_Grab) {
pObject->mfMaxInteractDist = cString::ToFloat(pGameElem->Attribute("MaxInteractDist"), mpInit->mpPlayer->GetMaxGrabDist());
pObject->mbHasInteraction = true;
}
/////////////////////////////////////
// Damage
pObject->mDamageProps.mbActive = cString::ToBool(pGameElem->Attribute("DamageCharacter"), false);
if (pObject->mDamageProps.mbActive) {
pObject->mDamageProps.mfMinLinearDamageSpeed = cString::ToFloat(pGameElem->Attribute("MinLinearDamageSpeed"), 0);
pObject->mDamageProps.mfMinAngularDamageSpeed = cString::ToFloat(pGameElem->Attribute("MinAngularDamageSpeed"), 0);
pObject->mDamageProps.mfMaxLinearDamageSpeed = cString::ToFloat(pGameElem->Attribute("MaxLinearDamageSpeed"), 0);
pObject->mDamageProps.mfMaxAngularDamageSpeed = cString::ToFloat(pGameElem->Attribute("MaxAngularDamageSpeed"), 0);
pObject->mDamageProps.mfMinDamage = cString::ToFloat(pGameElem->Attribute("MinDamage"), 0);
pObject->mDamageProps.mfMaxDamage = cString::ToFloat(pGameElem->Attribute("MaxDamage"), 0);
pObject->mDamageProps.mlDamageStrength = cString::ToInt(pGameElem->Attribute("DamageStrength"), 0);
}
// Add callback for all bodies
pObject->mpBodyCallback = hplNew(cGameObjectBodyCallback, (mpInit, pObject));
for (size_t i = 0; i < mvBodies.size(); ++i)
mvBodies[i]->AddBodyCallback(pObject->mpBodyCallback);
} else {
Error("Couldn't find game element for entity '%s'\n", mpEntity->GetName().c_str());
}
///////////////////////////////////
// Add a the object as user data to the body, to get the obejct later on.
for (size_t i = 0; i < mvBodies.size(); ++i) {
if (mvBodies[i]) {
mvBodies[i]->SetUserData((void *)pObject);
}
}
/////////////////////////////////
// Add to map handler
mpInit->mpMapHandler->AddGameEntity(pObject);
pObject->SetUpTransMaterials();
pObject->SetupBreakObject();
pObject->SetupForceOffset();
/////////////////////////////////
// Add to map handler
}
//-----------------------------------------------------------------------
eObjectInteractMode cEntityLoader_GameObject::ToInteractMode(const char *apString) {
if (apString == NULL)
return eObjectInteractMode_Static;
tString sName = cString::ToLowerCase(apString);
if (sName == "grab")
return eObjectInteractMode_Grab;
if (sName == "move")
return eObjectInteractMode_Move;
if (sName == "push")
return eObjectInteractMode_Push;
if (sName == "static")
return eObjectInteractMode_Static;
Warning("Invalid object interact mode '%s'\n", apString);
return eObjectInteractMode_Static;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cGameObject::cGameObject(cInit *apInit, const tString &asName) : iGameEntity(apInit, asName) {
mType = eGameEntityType_Object;
mInteractMode = eObjectInteractMode_Static;
mpBodyCallback = NULL;
mpCurrentAttraction = NULL;
mfAttractCount = 4.0f;
mbIsMover = false;
mfCloseToSameCount = cMath::RandRectf(0, 5.5f);
}
//-----------------------------------------------------------------------
cGameObject::~cGameObject(void) {
if (mpBodyCallback)
hplDelete(mpBodyCallback);
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cGameObject::OnPlayerPick() {
if (mvCallbackScripts[eGameEntityScriptType_PlayerInteract] &&
mpInit->mpPlayer->GetPickedDist() < mfMaxInteractDist &&
mpInit->mpPlayer->mbProxyTouching) {
mpInit->mpPlayer->SetCrossHairState(eCrossHairState_Active);
} else if (mInteractMode == eObjectInteractMode_Static && msDescription == _W("")) {
if (mpInit->mpPlayer->GetState() == ePlayerState_InteractMode)
mpInit->mpPlayer->SetCrossHairState(eCrossHairState_Inactive);
else
mpInit->mpPlayer->SetCrossHairState(eCrossHairState_None);
} else {
// mpInit->mpPlayer->SetCrossHairState(eCrossHairState_None);
}
}
//-----------------------------------------------------------------------
void cGameObject::OnPlayerInteract() {
iPhysicsBody *pBody = mpInit->mpPlayer->GetPickedBody();
cGameStickArea *pStickArea = mpInit->mpMapHandler->GetBodyStickArea(pBody);
if ((pBody->GetMass() == 0 && pStickArea == NULL) ||
(pStickArea && pStickArea->GetCanDeatch() == false)) {
return;
}
switch (mInteractMode) {
case eObjectInteractMode_Push:
PushObject();
break;
case eObjectInteractMode_Move:
MoveObject();
break;
case eObjectInteractMode_Grab:
GrabObject();
break;
case eObjectInteractMode_Static:
break;
case eObjectInteractMode_LastEnum:
break;
}
}
//-----------------------------------------------------------------------
void cGameObject::Update(float afTimeStep) {
UpdateAttraction(afTimeStep);
////////////////////////////////
// Disappear
if (mDisappearProps.mbActive) {
// TODO: Check per body!
float fMinDist = cMath::Vector3Dist(mpInit->mpPlayer->GetCamera()->GetPosition(),
mvBodies[0]->GetWorldPosition());
for (size_t i = 1; i < mvBodies.size(); ++i) {
if (mvBodies[i] == NULL)
continue;
float fDist = cMath::Vector3Dist(mpInit->mpPlayer->GetCamera()->GetPosition(),
mvBodies[i]->GetWorldPosition());
if (fDist < fMinDist)
fMinDist = fDist;
}
if (fMinDist >= mDisappearProps.mfMinDistance) {
mDisappearProps.mfTime -= afTimeStep;
if (mDisappearProps.mfTime <= 0) {
Break();
}
}
if (mDisappearProps.mfMinCloseDistance > 0) {
cVector3f vPos = mpMeshEntity->GetBoundingVolume()->GetWorldCenter();
float fMinSqrDist = mDisappearProps.mfMinCloseDistance *
mDisappearProps.mfMinCloseDistance;
if (mfCloseToSameCount <= 0) {
mfCloseToSameCount = 5.5f;
tGameEntityIterator it = mpInit->mpMapHandler->GetGameEntityIterator();
while (it.HasNext()) {
iGameEntity *pEntity = it.Next();
if (pEntity == this)
continue;
if (pEntity->GetType() != eGameEntityType_Object)
continue;
if (pEntity->GetFileName() != GetFileName())
continue;
cGameObject *pObject = static_cast<cGameObject *>(pEntity);
float fSqrDist = cMath::Vector3DistSqr(
pObject->GetMeshEntity()->GetBoundingVolume()->GetWorldCenter(),
vPos);
if (fSqrDist < fMinSqrDist) {
mDisappearProps.mfTime = 0;
Break();
}
}
} else {
mfCloseToSameCount -= afTimeStep;
}
}
}
///////////////////////////////
// Force offset
if (mbForceLightOffset) {
for (size_t i = 0; i < mvLights.size(); ++i) {
iLight3D *pLight = mvLights[i];
pLight->SetMatrix(cMath::MatrixMul(mpMeshEntity->GetWorldMatrix(),
mvLightLocalOffsets[i]));
pLight->SetPosition(pLight->GetWorldPosition() + mvLightOffset);
}
}
}
//-----------------------------------------------------------------------
void cGameObject::OnPlayerGravityCollide(iCharacterBody *apCharBody, cCollideData *apCollideData) {
if (mBreakProps.mbActive) {
///////////////////////////////////////////////
// Check so that the player is really on top of the
// object and not just sliding
bool bPushDown = false;
for (int i = 0; i < apCollideData->mlNumOfPoints; i++) {
cCollidePoint &point = apCollideData->mvContactPoints[i];
if (point.mvNormal.y > 0.001f &&
point.mvNormal.y > ABS(point.mvNormal.x) &&
point.mvNormal.y > ABS(point.mvNormal.z)) {
bPushDown = true;
}
}
if (bPushDown) {
////////////////////////////////////
// Check the impulse created by the player
float fImpulse = apCharBody->GetMass() * ABS(apCharBody->GetForceVelocity().y);
if (mBreakProps.mfMinPlayerImpulse <= fImpulse) {
if (mpInit->mbDebugInteraction) {
Log("------ Breakage ----------\n");
Log(" Body '%s' by Player\n", mvBodies[0]->GetName().c_str());
Log(" Impulse: %f : %fm/s * %fkg (from Player)\n", fImpulse,
ABS(apCharBody->GetForceVelocity().y), apCharBody->GetMass());
Log("-------------------------\n");
}
Break();
}
}
}
}
//-----------------------------------------------------------------------
void cGameObject::SetInteractMode(eObjectInteractMode aInteractMode) {
mInteractMode = aInteractMode;
if (mInteractMode == eObjectInteractMode_Static) {
mbHasInteraction = false;
} else {
mbHasInteraction = true;
}
}
//-----------------------------------------------------------------------
void cGameObject::BreakAction() {
if (mvBodies.empty())
return;
iPhysicsBody *pParentBody = mvBodies[0];
for (size_t i = 1; i < mvBodies.size(); ++i) {
if (pParentBody->GetMass() != 0) {
if (mvBodies[i]->GetMass() > pParentBody->GetMass() || mvBodies[i]->GetMass() == 0)
pParentBody = mvBodies[i];
}
}
//////////////////
// Script
if (mvCallbackScripts[eGameEntityScriptType_OnBreak]) {
tString sCommand = GetScriptCommand(eGameEntityScriptType_OnBreak);
mpInit->RunScriptCommand(sCommand);
}
cWorld3D *pWorld = mpInit->mpGame->GetScene()->GetWorld3D();
//////////////////
// Check if player os holding object
if ((mpInit->mpPlayer->GetState() == ePlayerState_Grab ||
mpInit->mpPlayer->GetState() == ePlayerState_Move ||
mpInit->mpPlayer->GetState() == ePlayerState_Push) &&
mpInit->mpPlayer->GetPushBody() == pParentBody) {
mpInit->mpPlayer->ChangeState(ePlayerState_Normal);
}
//////////////////
// Sound
if (mBreakProps.msSound != "") {
cSoundEntity *pSound = pWorld->CreateSoundEntity("Break", mBreakProps.msSound, true);
if (pSound)
pSound->SetPosition(mpMeshEntity->GetWorldPosition());
}
//////////////////
// Particle System
if (mBreakProps.msPS != "") {
/*cParticleSystem3D *pPS = */pWorld->CreateParticleSystem("Break", mBreakProps.msPS, cVector3f(1, 1, 1),
pParentBody->GetWorldMatrix());
}
//////////////////
// Entity
if (mBreakProps.msEntity != "") {
iEntity3D *pEntity = pWorld->CreateEntity(mpMeshEntity->GetName() + "_broken",
pParentBody->GetWorldMatrix(),
mBreakProps.msEntity, true);
if (pEntity) {
iGameEntity *pGameEntity = mpInit->mpMapHandler->GetLatestEntity();
for (int i = 0; i < pGameEntity->GetBodyNum(); ++i) {
// Add the object velocity
iPhysicsBody *pBody = pGameEntity->GetBody(i);
pBody->SetLinearVelocity(pParentBody->GetLinearVelocity());
// TODO: Add torque
// Force from center
cVector3f vBodyCentre = cMath::MatrixMul(pBody->GetWorldMatrix(), pBody->GetMassCentre());
cVector3f vForceDir = vBodyCentre - pParentBody->GetLocalPosition();
vForceDir.Normalise();
pBody->AddForce(vForceDir * mBreakProps.mfCenterForce);
}
}
}
//////////////////
// Explosion
if (mBreakProps.mbExplosion) {
mpInit->mpAttackHandler->CreateSplashDamage(
pParentBody->GetWorldPosition(),
mBreakProps.mfExpl_Radius,
mBreakProps.mfExpl_MinDamage, mBreakProps.mfExpl_MaxDamage,
mBreakProps.mfExpl_MinForce, mBreakProps.mfExpl_MaxForce,
mBreakProps.mfExpl_MaxImpulse,
eAttackTargetFlag_Bodies | eAttackTargetFlag_Player | eAttackTargetFlag_Enemy,
mBreakProps.mfExpl_MinMass,
mBreakProps.mlExpl_Strength);
}
//////////////////
// Light flash
if (mBreakProps.mbLightFlash) {
mpInit->mpMapHandler->AddLightFlash(pParentBody->GetWorldPosition() + mBreakProps.mvLight_Offset,
mBreakProps.mfLight_Radius,
mBreakProps.mLight_Color,
mBreakProps.mfLight_AddTime,
mBreakProps.mfLight_NegTime);
}
//////////////////
// Ear ring
if (mBreakProps.mbEarRing) {
cPlayer *pPlayer = mpInit->mpPlayer;
float fDist = cMath::Vector3Dist(pParentBody->GetWorldPosition(),
pPlayer->GetCamera()->GetPosition());
// Log("Ear ring dist: %f max: %f\n",fDist, mBreakProps.mfEarRing_MaxDist);
if (fDist <= mBreakProps.mfEarRing_MaxDist) {
pPlayer->GetEarRing()->Start(mBreakProps.mfEarRing_Time);
}
}
mpInit->mpGame->ResetLogicTimer();
}
//-----------------------------------------------------------------------
void cGameObject::OnDeath(float afDamage) {
if (mBreakProps.mbActive) {
Break();
}
}
//-----------------------------------------------------------------------
void cGameObject::SetupBreakObject() {
if (mBreakProps.mbActive == false)
return;
if (mBreakProps.msEntity != "")
PreloadModel(mBreakProps.msEntity);
if (mBreakProps.msPS != "") {
cParticleSystem3D *pPS = mpInit->mpGame->GetResources()->GetParticleManager()->CreatePS3D(
"", mBreakProps.msPS, 1, cMatrixf::Identity);
hplDelete(pPS);
}
if (mBreakProps.msSound != "") {
mpInit->PreloadSoundEntityData(mBreakProps.msSound);
}
}
//-----------------------------------------------------------------------
void cGameObject::SetupForceOffset() {
if (mbForceLightOffset == false)
return;
mvLightLocalOffsets.resize(mvLights.size());
for (size_t i = 0; i < mvLights.size(); ++i) {
mvLightLocalOffsets[i] = mvLights[i]->GetLocalMatrix();
if (mvLights[i]->GetParent())
mvLights[i]->GetParent()->RemoveEntity(mvLights[i]);
if (mvLights[i]->GetEntityParent())
mvLights[i]->GetEntityParent()->RemoveChild(mvLights[i]);
}
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cGameObject::GrabObject() {
if (mpInit->mpPlayer->GetPickedDist() > mfMaxInteractDist) {
if (mpInit->mpPlayer->GetState() == ePlayerState_InteractMode)
mpInit->mpEffectHandler->GetSubTitle()->Add(kTranslate("Player", "ObjectTooFar"), 2.0f, true);
return;
}
// Set some properties
mpInit->mpPlayer->mbGrabbingMoveBody = mbIsMover;
mpInit->mpPlayer->mfHapticTorqueMul = mfHapticTorqueMul;
mpInit->mpPlayer->mbPickAtPoint = mbPickAtPoint;
mpInit->mpPlayer->mbRotateWithPlayer = mbRotateWithPlayer;
mpInit->mpPlayer->mbUseNormalMass = mbUseNormalMass;
mpInit->mpPlayer->mfGrabMassMul = mfGrabMassMul;
mpInit->mpPlayer->mbCanBeThrown = mbCanBeThrown;
mpInit->mpPlayer->mfCurrentMaxInteractDist = mfMaxInteractDist;
mpInit->mpPlayer->SetPushBody(mpInit->mpPlayer->GetPickedBody());
mpInit->mpPlayer->ChangeState(ePlayerState_Grab);
}
//-----------------------------------------------------------------------
void cGameObject::MoveObject() {
float fDist = GetMoveDist();
if (fDist > mfMaxInteractDist) {
if (mpInit->mpPlayer->GetState() == ePlayerState_InteractMode)
mpInit->mpEffectHandler->GetSubTitle()->Add(kTranslate("Player", "ObjectTooFar"), 2.0f, true);
return;
}
// Set some properties
mpInit->mpPlayer->mfForwardUpMul = mfForwardUpMul;
mpInit->mpPlayer->mfForwardRightMul = mfForwardRightMul;
mpInit->mpPlayer->mfUpMul = mfUpMul;
mpInit->mpPlayer->mfRightMul = mfRightMul;
mpInit->mpPlayer->mbCanBeThrown = mbCanBeThrown;
mpInit->mpPlayer->mfCurrentMaxInteractDist = mfMaxInteractDist;
mpInit->mpPlayer->SetPushBody(mpInit->mpPlayer->GetPickedBody());
mpInit->mpPlayer->ChangeState(ePlayerState_Move);
}
float cGameObject::GetMoveDist() {
/*cVector3f vBodyPos = mpInit->mpPlayer->GetPickedPos();
cVector3f vPlayerPos = mpInit->mpPlayer->GetCamera()->GetPosition();
float fDist = cMath::Vector3Dist(vBodyPos, vPlayerPos);
return fDist;*/
return mpInit->mpPlayer->GetPickedDist();
}
//-----------------------------------------------------------------------
void cGameObject::PushObject() {
float fDist = GetPushDist();
if (fDist > mfMaxInteractDist) {
if (mpInit->mpPlayer->GetState() == ePlayerState_InteractMode)
mpInit->mpEffectHandler->GetSubTitle()->Add(kTranslate("Player", "ObjectTooFar"), 2.0f, true);
return;
}
mpInit->mpPlayer->mbPickAtPoint = mbPickAtPoint;
mpInit->mpPlayer->mbCanBeThrown = mbCanBeThrown;
mpInit->mpPlayer->mbCanBePulled = mbCanBePulled;
mpInit->mpPlayer->mfCurrentMaxInteractDist = mfMaxInteractDist;
mpInit->mpPlayer->SetPushBody(mpInit->mpPlayer->GetPickedBody());
mpInit->mpPlayer->ChangeState(ePlayerState_Push);
}
float cGameObject::GetPushDist() {
/*cVector3f vBodyPos = mpInit->mpPlayer->GetPickedPos();
cVector3f vPlayerPos = mpInit->mpPlayer->GetCamera()->GetPosition();
float fDist = cMath::Dist2D(cVector2f(vBodyPos.x,vBodyPos.z),cVector2f(vPlayerPos.x,vPlayerPos.z));
*/
return mpInit->mpPlayer->GetPickedDist();
}
//-----------------------------------------------------------------------
void cGameObject::UpdateAttraction(float afTimeStep) {
if (mAttractProps.mbActive == false)
return;
///////////////////////////////////////////////
// Check if the current attraction is near enough
if (mpCurrentAttraction) {
////////////////////////////////////////
// Check for the attracted enemy
iCharacterBody *pBody = mpCurrentAttraction->GetMover()->GetCharBody();
float fDist = cMath::Vector3Dist(pBody->GetFeetPosition(),
mvBodies[0]->GetWorldPosition());
// Check if the enemy have become busy.
if ((mpCurrentAttraction->GetCurrentStateId() != STATE_MOVETO && fDist < pBody->GetSize().x * 0.75f) ||
(mAttractProps.mbIsEaten && fDist < pBody->GetSize().x * 0.75f)) {
// Use half the width here.
if (mAttractProps.mbIsEaten) {
mpCurrentAttraction->SetTempFloat(mAttractProps.mfEatLength);
mpCurrentAttraction->ChangeState(STATE_EAT);
}
mpCurrentAttraction->SetAttracted(false);
mpCurrentAttraction = NULL;
mbDestroyMe = true;
return;
} else if (mpCurrentAttraction->GetCurrentStateId() != STATE_MOVETO) {
mpCurrentAttraction->SetAttracted(false);
mpCurrentAttraction = NULL;
return;
}
}
///////////////////////////////////////////////
// Check if any enemy is near enough to be attracted
else {
if (mfAttractCount <= 0) {
float fClosestDist = 10000.0f;
iGameEnemy *pChosenEnemy = NULL;
tGameEnemyIterator enemyIt = mpInit->mpMapHandler->GetGameEnemyIterator();
while (enemyIt.HasNext()) {
iGameEnemy *pEnemy = enemyIt.Next();
if (pEnemy->GetHealth() <= 0 || pEnemy->IsActive() == false ||
pEnemy->IsAttracted()) {
continue;
}
bool bCorrectSub = false;
for (size_t i = 0; i < mAttractProps.mvSubtypes.size(); ++i) {
if (mAttractProps.mvSubtypes[i] == pEnemy->GetSubType())
bCorrectSub = true;
}
if (bCorrectSub == false)
continue;
// Check if the enemy has already been attracted by this object
if (m_setAttractedEnemies.find(pEnemy) != m_setAttractedEnemies.end())
continue;
float fDist = cMath::Vector3Dist(pEnemy->GetMover()->GetCharBody()->GetFeetPosition(),
mvBodies[0]->GetWorldPosition());
if (fDist < mAttractProps.mfDistance && fDist < fClosestDist) {
fClosestDist = fDist;
pChosenEnemy = pEnemy;
}
}
if (pChosenEnemy) {
if (pChosenEnemy->MoveToPos(mvBodies[0]->GetWorldPosition() + cVector3f(0, 0.2f, 0))) {
mpCurrentAttraction = pChosenEnemy;
pChosenEnemy->SetAttracted(true);
m_setAttractedEnemies.insert(pChosenEnemy);
}
}
mfAttractCount = 0.2f;
} else {
mfAttractCount -= afTimeStep;
}
}
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// SAVE OBJECT STUFF
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
kBeginSerialize(cGameObject_SaveData, iGameEntity_SaveData)
kSerializeVar(mInteractMode, eSerializeType_Int32)
kSerializeVar(mfMaxInteractDist, eSerializeType_Float32)
kEndSerialize()
//-----------------------------------------------------------------------
iGameEntity *cGameObject_SaveData::CreateEntity() {
return NULL;
}
//-----------------------------------------------------------------------
iGameEntity_SaveData *cGameObject::CreateSaveData() {
return hplNew(cGameObject_SaveData, ());
}
//-----------------------------------------------------------------------
void cGameObject::SaveToSaveData(iGameEntity_SaveData *apSaveData) {
super::SaveToSaveData(apSaveData);
cGameObject_SaveData *pData = static_cast<cGameObject_SaveData *>(apSaveData);
kCopyToVar(pData, mInteractMode);
}
//-----------------------------------------------------------------------
void cGameObject::LoadFromSaveData(iGameEntity_SaveData *apSaveData) {
super::LoadFromSaveData(apSaveData);
cGameObject_SaveData *pData = static_cast<cGameObject_SaveData *>(apSaveData);
kCopyFromVar(pData, mInteractMode);
}
//-----------------------------------------------------------------------