/*
* Copyright (C) 2006-2010 - Frictional Games
*
* This file is part of Penumbra Overture.
*
* Penumbra Overture 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.
*
* Penumbra Overture 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 Penumbra Overture. If not, see .
*/
#include "AttackHandler.h"
#include "Init.h"
#include "MapHandler.h"
#include "Player.h"
#include "GameEnemy.h"
#include "GameObject.h"
#include "GameSwingDoor.h"
//////////////////////////////////////////////////////////////////////////
// RAY CALLBACK
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cAttackRayCallback::cAttackRayCallback()
{
mbSkipCharacter = false;
}
//-----------------------------------------------------------------------
void cAttackRayCallback::Reset()
{
mpClosestBody = NULL;
mbSkipCharacter = false;
}
//-----------------------------------------------------------------------
bool cAttackRayCallback::OnIntersect(iPhysicsBody *pBody,cPhysicsRayParams *apParams)
{
if(pBody->GetCollide()==false) return true;
if(pBody == mpSkipBody) return true;
if(mbSkipCharacter && pBody->IsCharacter()) return true;
if(apParams->mfDist < mfShortestDist || mpClosestBody == NULL)
{
mpClosestBody = pBody;
mfShortestDist = apParams->mfDist;
mvPosition = apParams->mvPoint;
}
return true;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// SPLASH DAMAGE BLOCK CHECK
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cSplashDamageBlockCheck::cSplashDamageBlockCheck(cInit *apInit)
{
mpInit = apInit;
mbIntersected = false;
}
//-----------------------------------------------------------------------
bool cSplashDamageBlockCheck::CheckBlock(const cVector3f& avStart, const cVector3f&avEnd)
{
mbIntersected = false;
iPhysicsWorld *pWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
pWorld->CastRay(this,avStart,avEnd,false,false,false, true);
return mbIntersected;
}
//-----------------------------------------------------------------------
bool cSplashDamageBlockCheck::BeforeIntersect(iPhysicsBody *pBody)
{
if(pBody->IsCharacter() || pBody->GetMass() != 0 || pBody->GetBlocksSound()==false)
{
return false;
}
return true;
}
//-----------------------------------------------------------------------
bool cSplashDamageBlockCheck::OnIntersect(iPhysicsBody *pBody,cPhysicsRayParams *apParams)
{
mbIntersected = true;
return false;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cAttackHandler::cAttackHandler(cInit *apInit) : iUpdateable("AttackHandler")
{
mpInit = apInit;
mpSplashBlockCheck = hplNew( cSplashDamageBlockCheck,(apInit) );
}
//-----------------------------------------------------------------------
cAttackHandler::~cAttackHandler(void)
{
hplDelete( mpSplashBlockCheck );
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
bool cAttackHandler::CreateLineAttack(const cVector3f& avStart, const cVector3f& avEnd, float afDamage,
eAttackTargetFlag aTarget, iPhysicsBody *apSkipBody,
iPhysicsBody **apPickedBody)
{
iPhysicsWorld *pPhysicsWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
mRayCallback.mTarget = aTarget;
mRayCallback.mpSkipBody = apSkipBody;
if(apPickedBody)*apPickedBody = NULL;
mRayCallback.Reset();
pPhysicsWorld->CastRay(&mRayCallback,avStart,avEnd,true,false,true);
if(mRayCallback.mpClosestBody == NULL) return false;
if(apPickedBody) *apPickedBody = mRayCallback.mpClosestBody;
if(aTarget & eAttackTargetFlag_Player)
{
if(mpInit->mpPlayer->GetCharacterBody()->GetBody() == mRayCallback.mpClosestBody)
{
if(afDamage>0) mpInit->mpPlayer->Damage(afDamage,ePlayerDamageType_BloodSplash);
return true;
}
}
if(aTarget & eAttackTargetFlag_Enemy)
{
}
return false;
}
//-----------------------------------------------------------------------
bool cAttackHandler::CreateShapeAttack( iCollideShape *apShape, const cMatrixf& a_mtxOffset,
const cVector3f &avOrigin, float afDamage,
float afMinMass, float afMaxMass, float afMinImpulse, float afMaxImpulse,
int alStrength,
eAttackTargetFlag aTarget, iPhysicsBody *apSkipBody)
{
bool bHit = false;
tPhysicsBodyList lstBodies;
mpLastSwingDoor = NULL;
///////////////////////////////
//Set up boudning box
cBoundingVolume tempBV = apShape->GetBoundingVolume();
tempBV.SetTransform(a_mtxOffset);
///////////////////////////////
//Iterate bodies
cCollideData collideData;
collideData.SetMaxSize(1);
cWorld3D *pWorld = mpInit->mpGame->GetScene()->GetWorld3D();
iPhysicsWorld *pPhysicsWorld = pWorld->GetPhysicsWorld();
//Get bodies and add to list, this incase the portal contaniner gets changed.
std::list lstTempBodies;
cPhysicsBodyIterator bodyIt = pPhysicsWorld->GetBodyIterator();
while(bodyIt.HasNext())
{
iPhysicsBody *pBody = static_cast(bodyIt.Next());
lstTempBodies.push_back(pBody);
}
std::list::iterator it = lstTempBodies.begin();
for(; it != lstTempBodies.end(); ++it)
{
iPhysicsBody *pBody = *it;
float fMass = pBody->GetMass();
if(pBody->IsActive()==false) continue;
if(pBody->GetCollide()==false) continue;
if(cMath::CheckCollisionBV(tempBV, *pBody->GetBV()))
{
iGameEntity *pEntity = (iGameEntity*)pBody->GetUserData();
///////////////////////////////
//Check for collision
if(pPhysicsWorld->CheckShapeCollision(pBody->GetShape(),pBody->GetLocalMatrix(),
apShape, a_mtxOffset,collideData,1)==false)
{
continue;
}
///////////////////////////
//Player
if(aTarget & eAttackTargetFlag_Player)
{
if(mpInit->mpPlayer->GetCharacterBody()->GetBody() == pBody)
{
//Check with line if there is a free path, if not skip damage.
cVector3f vEnd = pBody->GetWorldPosition();
mRayCallback.Reset();
mRayCallback.mbSkipCharacter = true;
pPhysicsWorld->CastRay(&mRayCallback,avOrigin,vEnd,true,false,false);
mRayCallback.mbSkipCharacter = false;
//Damage
if(mRayCallback.mpClosestBody==NULL)
{
if(afDamage>0)
{
mpInit->mpPlayer->Damage(afDamage,ePlayerDamageType_BloodSplash);
}
}
//Impulse
float fMass = mpInit->mpPlayer->GetCharacterBody()->GetMass();
float fForceSize =0;
if(fMass > afMaxMass*10) fForceSize =0;
else if(fMass <= afMinMass*10) fForceSize = afMaxImpulse*10;
else{
float fT = (fMass - afMinMass*10) / (afMaxMass*10 - afMinMass*10);
fForceSize = afMinImpulse*10 * fT + afMaxImpulse*10 * (1-fT);
}
cVector3f vForceDir = mpInit->mpPlayer->GetCharacterBody()->GetPosition() - avOrigin;
vForceDir.Normalise();
vForceDir += cVector3f(0,0.1f,0);
mpInit->mpPlayer->GetCharacterBody()->AddForce(vForceDir * fForceSize * 300);
bHit = true;
continue;
}
}
///////////////////////////
//Enemy
if(aTarget & eAttackTargetFlag_Enemy)
{
}
///////////////////////////
//Bodies
if(aTarget & eAttackTargetFlag_Bodies)
{
if(pBody->IsCharacter()==false)
{
lstBodies.push_back(pBody);
if(pEntity)
{
pEntity->Damage(afDamage,alStrength);
if(pEntity->GetType() == eGameEntityType_SwingDoor)
{
mpLastSwingDoor = static_cast(pEntity);
}
}
bHit = true;
}
}
}
}
////////////////////////////////
//Iterate bodies hit, this to
//spread out the impulse.
float fAmount = (float)lstBodies.size();
for(tPhysicsBodyListIt it = lstBodies.begin(); it != lstBodies.end(); ++it)
{
iPhysicsBody *pBody = *it;
//Calculate force
float fMass = pBody->GetMass();
float fForceSize =0;
if(fMass > afMaxMass) fForceSize =0;
else if(fMass <= afMinMass) fForceSize = afMaxImpulse;
else{
float fT = (fMass - afMinMass) / (afMaxMass - afMinMass);
fForceSize = afMinImpulse * fT + afMaxImpulse * (1-fT);
}
fForceSize /= fAmount;
if(fMass >0 && fForceSize >0)
{
cVector3f vDir = pBody->GetWorldPosition() - avOrigin;
vDir.Normalise();
pBody->AddImpulse(vDir * fForceSize);
}
}
return bHit;
}
//-----------------------------------------------------------------------
bool cAttackHandler::CreateLineDestroyBody(const cVector3f& avStart, const cVector3f& avEnd,
float afStrength,float afForce,iPhysicsBody *apSkipBody,
iPhysicsBody **apPickedBody)
{
iPhysicsWorld *pPhysicsWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
mRayCallback.mpSkipBody = apSkipBody;
if(apPickedBody)*apPickedBody = NULL;
mRayCallback.Reset();
pPhysicsWorld->CastRay(&mRayCallback,avStart,avEnd,true,false,true);
if(mRayCallback.mpClosestBody == NULL) return false;
if(apPickedBody) *apPickedBody = mRayCallback.mpClosestBody;
iPhysicsBody *pBody = mRayCallback.mpClosestBody;
if(pBody->IsCharacter()==false && pBody->GetMass()>0 && pBody->GetUserData())
{
iGameEntity *pEntity = (iGameEntity*)pBody->GetUserData();
if(pEntity->GetType() != eGameEntityType_Object) return false;
cGameObject *pObject = static_cast(pEntity);
cVector3f vForward = avEnd - avStart;
vForward.Normalise();
pBody->AddForce(vForward * afForce);
//Destroy object if possible.
if(pObject->IsDestroyable() && pObject->GetDestroyStrength() <= afStrength)
{
for(int i=0; iGetJointNum(); ++i)
{
iPhysicsJoint *pJoint = pBody->GetJoint(i);
pJoint->Break();
}
if(pObject->GetInteractMode() == eObjectInteractMode_Move)
pObject->SetInteractMode(eObjectInteractMode_Grab);
if(pObject->GetDestroySound()!="")
{
cSoundEntity *pSound = mpInit->mpGame->GetScene()->GetWorld3D()->CreateSoundEntity(
"Destroy",pObject->GetDestroySound(),
true);
if(pSound) pSound->SetPosition(pBody->GetWorldPosition());
}
}
}
return false;
}
//----------------------------------------------------------------------
static float CalcSize(float afDist, float afMaxDist, float afMinVal,float afMaxVal)
{
float fMul = 1 - (afDist / afMaxDist);
if(fMul<0) return 0;
return afMinVal + (afMaxVal - afMinVal)*fMul;
}
//----------------------------------------------------------------------
void cAttackHandler::CreateSplashDamage(const cVector3f& avCenter, float afRadius, float afMinDamage,
float afMaxDamge,float afMinForce, float afMaxForce,
float afMaxImpulse,
unsigned int aTarget, float afMinMass,
int alStrength)
{
cBoundingVolume tempBV;
tempBV.SetSize(afRadius * 2);
tempBV.SetPosition(avCenter);
if(aTarget & eAttackTargetFlag_Player)
{
cBoundingVolume* pCharBV = mpInit->mpPlayer->GetCharacterBody()->GetBody()->GetBV();
if(cMath::CheckCollisionBV(tempBV, *pCharBV) &&
mpSplashBlockCheck->CheckBlock(pCharBV->GetWorldCenter(), avCenter)==false)
{
float fDist = cMath::Vector3Dist(pCharBV->GetPosition(),avCenter);
mpInit->mpPlayer->Damage(CalcSize(fDist,afRadius,afMinDamage,afMaxDamge),ePlayerDamageType_BloodSplash);
float fForceSize = CalcSize(fDist,afRadius,afMinForce,afMaxForce);
cVector3f vForceDir = mpInit->mpPlayer->GetCharacterBody()->GetPosition() - avCenter;
vForceDir.Normalise();
mpInit->mpPlayer->GetCharacterBody()->AddForce(vForceDir * fForceSize * 10);
}
}
//Enemies now have the user data added to the body, this is not needed.
/*if(aTarget & eAttackTargetFlag_Enemy)
{
tGameEnemyIterator it = mpMapHandler->GetGameEnemyIterator();
while(it.HasNext())
{
iGameEnemy *pEnemy = it.Next();
cBoundingVolume* pCharBV = pEnemy->GetMover()->GetCharBody()->GetBody()->GetBV();
if(cMath::CheckCollisionBV(tempBV, *pCharBV))
{
float fDist = cMath::Vector3Dist(pCharBV->GetPosition(),avCenter);
pEnemy->Damage(CalcSize(fDist,afRadius,afMinDamage,afMaxDamge),-1);
}
}
}*/
if(aTarget & eAttackTargetFlag_Bodies)
{
iPhysicsWorld *pWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
std::list lstBodies;
cPhysicsBodyIterator bodyIt = pWorld->GetBodyIterator();
while(bodyIt.HasNext())
{
lstBodies.push_back(bodyIt.Next());
}
//////////////////////////
//Damage Iteration
std::list::iterator it = lstBodies.begin();
for(; it != lstBodies.end(); ++it)
{
iPhysicsBody *pBody = *it;
//if(pBody->IsCharacter() || pBody->GetMass()==0) continue;
//if(pBody->IsCharacter()) continue;
if(pBody->IsActive()==false) continue;
//if(pBody->GetMass() <= afMinMass) continue;
iGameEntity *pEntity = (iGameEntity*)pBody->GetUserData();
if(pEntity && pEntity->IsActive() && cMath::CheckCollisionBV(tempBV, *pBody->GetBV())
&& mpSplashBlockCheck->CheckBlock(pBody->GetWorldPosition(), avCenter)==false)
{
//If enemies are not to be target, skip.
if(!(aTarget & eAttackTargetFlag_Enemy) && pEntity->GetType() == eGameEntityType_Enemy)
{
continue;
}
float fDist = cMath::Vector3Dist(avCenter,pBody->GetLocalPosition());
/*float fForceSize = CalcSize(fDist,afRadius,afMinForce,afMaxForce);
cVector3f vForceDir = pBody->GetLocalPosition() - avCenter;
vForceDir.Normalise();
if(fForceSize / pBody->GetMass() > afMaxImpulse)
{
fForceSize = afMaxImpulse * pBody->GetMass();
}
pBody->AddForce(vForceDir * fForceSize);*/
if(pEntity)
{
float fDamage = CalcSize(fDist,afRadius,afMinDamage,afMaxDamge);
pEntity->Damage(fDamage,alStrength);
}
}
}
//Update map handler so all stuff that should be broken is.
//mpInit->mpMapHandler->Update(1.0f/60.0f);
//////////////////////////
//Force Iteration
it = lstBodies.begin();
for(; it != lstBodies.end(); ++it)
{
iPhysicsBody *pBody = *it;
if(pBody->IsCharacter() || pBody->GetMass()==0) continue;
if(pBody->IsActive()==false) continue;
if(pBody->GetMass() <= afMinMass) continue;
iGameEntity *pEntity = (iGameEntity*)pBody->GetUserData();
if(cMath::CheckCollisionBV(tempBV, *pBody->GetBV()))
{
cVector3f vBodyPos = pBody->GetLocalPosition() +
cMath::MatrixMul( pBody->GetLocalMatrix().GetRotation(),
pBody->GetMassCentre());
float fDist = cMath::Vector3Dist(avCenter,vBodyPos);
float fForceSize = CalcSize(fDist,afRadius,afMinForce,afMaxForce);
cVector3f vForceDir = vBodyPos - avCenter;
vForceDir.Normalise();
if(fForceSize / pBody->GetMass() > afMaxImpulse)
{
fForceSize = afMaxImpulse * pBody->GetMass();
}
pBody->AddForce(vForceDir * fForceSize);
if(pEntity)
{
float fImpulse = (fForceSize/pBody->GetMass())*1.0f/60.0f;
if(fImpulse > 15)fImpulse = 15;
pEntity->SetLastImpulse(vForceDir * fImpulse);
}
}
}
}
}
//-----------------------------------------------------------------------
void cAttackHandler::OnStart()
{
mpMapHandler = mpInit->mpMapHandler;
}
//-----------------------------------------------------------------------
void cAttackHandler::Update(float afTimeStep)
{
}
//-----------------------------------------------------------------------
void cAttackHandler::Reset()
{
}