/* 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 . * */ /* * Copyright (C) 2006-2010 - Frictional Games * * This file is part of Penumbra Overture. */ #include "hpl1/penumbra-overture/AttackHandler.h" #include "hpl1/penumbra-overture/GameEnemy.h" #include "hpl1/penumbra-overture/GameObject.h" #include "hpl1/penumbra-overture/GameSwingDoor.h" #include "hpl1/penumbra-overture/Init.h" #include "hpl1/penumbra-overture/MapHandler.h" #include "hpl1/penumbra-overture/Player.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. Common::List lstTempBodies; cPhysicsBodyIterator bodyIt = pPhysicsWorld->GetBodyIterator(); while (bodyIt.HasNext()) { iPhysicsBody *pBody = static_cast(bodyIt.Next()); lstTempBodies.push_back(pBody); } Common::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 fMass2 = mpInit->mpPlayer->GetCharacterBody()->GetMass(); float fForceSize = 0; if (fMass2 > afMaxMass * 10) fForceSize = 0; else if (fMass2 <= afMinMass * 10) fForceSize = afMaxImpulse * 10; else { float fT = (fMass2 - 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 it2 = lstBodies.begin(); it2 != lstBodies.end(); ++it2) { iPhysicsBody *pBody = *it2; // 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; i < pBody->GetJointNum(); ++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(); Common::List lstBodies; cPhysicsBodyIterator bodyIt = pWorld->GetBodyIterator(); while (bodyIt.HasNext()) { lstBodies.push_back(bodyIt.Next()); } ////////////////////////// // Damage Iteration Common::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() { }