void CLocalPlayerComponent::UpdateScreenFadeEffect()
{
	IMaterialEffects* pMaterialEffects = gEnv->pGame->GetIGameFramework()->GetIMaterialEffects();

	if(pMaterialEffects)
	{
		m_screenFadeEffectId = pMaterialEffects->GetEffectIdByName("cw2_player_fx", "c2mp_fallDeath_fadeOut");
	}
}
//------------------------------------------------------------------------
/* static */ void CMelee::PlayHitMaterialEffect(const Vec3 &position, const Vec3 &normal, bool bBoostedMelee, int surfaceIdx)
{
	//Play Material FX
	const char* meleeFXType = bBoostedMelee ? "melee_combat" : "melee";  //Benito: Check with fx guys to update names

	IMaterialEffects* pMaterialEffects = gEnv->pGame->GetIGameFramework()->GetIMaterialEffects();

	TMFXEffectId effectId = pMaterialEffects->GetEffectId(meleeFXType, surfaceIdx);
	if (effectId != InvalidEffectId)
	{
		SMFXRunTimeEffectParams params;
		params.pos = position;
		params.normal = normal;
		params.playflags = MFX_PLAY_ALL | MFX_DISABLE_DELAY;
		params.soundSemantic = eSoundSemantic_Player_Foley;
		pMaterialEffects->ExecuteEffect(effectId, params);
	}
}
//--------------------------------------------------------------------------------------------------
// Name: SpawnMaterialEffect
// Desc: Spawns material effect
//--------------------------------------------------------------------------------------------------
void CExplosionGameEffect::SpawnMaterialEffect(const SExplosionContainer &explosionContainer)
{
	// Disclaimer: this code was originally from GameRulesClientServer::ProcessExplosionMaterialFX()
	const ExplosionInfo& explosionInfo = explosionContainer.m_explosionInfo;

	// impact stuff here
	SMFXRunTimeEffectParams params;
	//params.soundSemantic = eSoundSemantic_Explosion;
	params.pos = params.decalPos = explosionInfo.pos;
	params.trg = 0;
	params.trgRenderNode = 0;
	params.trgSurfaceId = 0;

	if(explosionInfo.impact && (explosionInfo.impact_velocity.len2() > 0.000001f))
	{
		params.dir[0] = explosionInfo.impact_velocity.normalized();
		params.normal = explosionInfo.impact_normal;
	}
	else
	{
		const Vec3 gravityDir = Vec3(0.0f, 0.0f, -1.0f);

		params.dir[0] = gravityDir;
		params.normal = -gravityDir;
	}

	const SDeferredMfxExplosion& mfxInfo = explosionContainer.m_mfxInfo;
	if(mfxInfo.m_state == eDeferredMfxExplosionState_ResultImpact)
	{
		params.trgSurfaceId = mfxInfo.m_mfxTargetSurfaceId;

		if (mfxInfo.m_pMfxTargetPhysEnt.get())
		{
			if (mfxInfo.m_pMfxTargetPhysEnt->GetiForeignData() == PHYS_FOREIGN_ID_STATIC)
			{
				params.trgRenderNode = (IRenderNode*)mfxInfo.m_pMfxTargetPhysEnt->GetForeignData(PHYS_FOREIGN_ID_STATIC);
			}
		}
	}

	// Create query name
	stack_string effectClass = explosionInfo.effect_class;
	if(effectClass.empty())
		effectClass = "generic";

	const float waterLevel = gEnv->p3DEngine->GetWaterLevel(&params.pos); 

	stack_string query = effectClass + "_explode";
	if(waterLevel > explosionInfo.pos.z)
		query = query + "_underwater";

	// Get material effect id
	IMaterialEffects* pMaterialEffects = gEnv->pGame->GetIGameFramework()->GetIMaterialEffects();
	TMFXEffectId effectId = pMaterialEffects->GetEffectId(query.c_str(), params.trgSurfaceId);

	if(effectId == InvalidEffectId)
	{
		// Get default surface id
		effectId = pMaterialEffects->GetEffectId(query.c_str(), pMaterialEffects->GetDefaultSurfaceIndex());
	}

	// Execute material effect
	if(effectId != InvalidEffectId)
	{
		pMaterialEffects->ExecuteEffect(effectId, params);

		bool hasFlashBangEffect = explosionInfo.blindAmount > 0.0f;
		if(hasFlashBangEffect)
		{
			// Calc screen pos
			Vec3 screenspace;
			gEnv->pRenderer->ProjectToScreen(explosionInfo.pos.x, explosionInfo.pos.y, explosionInfo.pos.z, &screenspace.x, &screenspace.y, &screenspace.z);

			// Pass screen pos to flow graph node
			SMFXCustomParamValue paramPosX;
			paramPosX.fValue = screenspace.x*0.01f;
			pMaterialEffects->SetCustomParameter(effectId,"Intensity",paramPosX); // Use intensity param to pass x pos

			SMFXCustomParamValue paramPosY;
			paramPosY.fValue = screenspace.y*0.01f;
			pMaterialEffects->SetCustomParameter(effectId,"BlendOutTime",paramPosY); // Use blendOutTime param to pass y pos
		}
	}
}//-------------------------------------------------------------------------------------------------
//---------------------------------------------------
void CGameRules::ProcessExplosionMaterialFX(const ExplosionInfo &explosionInfo)
{
	// if an effect was specified, don't use MFX
	if (explosionInfo.pParticleEffect)
		return;

	// impact stuff here
	SMFXRunTimeEffectParams params;
	params.soundSemantic = eSoundSemantic_Explosion;
	params.pos = params.decalPos = explosionInfo.pos;
	params.trg = 0;
	params.trgRenderNode = 0;

	Vec3 gravity;
	pe_params_buoyancy buoyancy;
	gEnv->pPhysicalWorld->CheckAreas(params.pos, gravity, &buoyancy);

	// 0 for water, 1 for air
	Vec3 pos=params.pos;
	params.inWater = (buoyancy.waterPlane.origin.z > params.pos.z) && (gEnv->p3DEngine->GetWaterLevel(&pos)>=params.pos.z);
	params.inZeroG = (gravity.len2() < 0.0001f);
	params.trgSurfaceId = 0;

	static const int objTypes = ent_all;    
	static const unsigned int flags = rwi_stop_at_pierceable|rwi_colltype_any;

	ray_hit ray;

	if (explosionInfo.impact)
	{
		params.dir[0] = explosionInfo.impact_velocity.normalized();
		params.normal = explosionInfo.impact_normal;

		if (gEnv->pPhysicalWorld->RayWorldIntersection(params.pos-params.dir[0]*0.0125f, params.dir[0]*0.25f, objTypes, flags, &ray, 1))
		{
			params.trgSurfaceId = ray.surface_idx;
			if (ray.pCollider->GetiForeignData()==PHYS_FOREIGN_ID_STATIC)
				params.trgRenderNode = (IRenderNode*)ray.pCollider->GetForeignData(PHYS_FOREIGN_ID_STATIC);
		}
	}
	else
	{
		params.dir[0] = gravity;
		params.normal = -gravity.normalized();

		if (gEnv->pPhysicalWorld->RayWorldIntersection(params.pos, gravity, objTypes, flags, &ray, 1))
		{
			params.trgSurfaceId = ray.surface_idx;
			if (ray.pCollider->GetiForeignData()==PHYS_FOREIGN_ID_STATIC)
				params.trgRenderNode = (IRenderNode*)ray.pCollider->GetForeignData(PHYS_FOREIGN_ID_STATIC);
		}
	}

	string effectClass = explosionInfo.effect_class;
	if (effectClass.empty())
		effectClass = "generic";

	string query = effectClass + "_explode";
	if(gEnv->p3DEngine->GetWaterLevel(&explosionInfo.pos)>explosionInfo.pos.z)
		query = query + "_underwater";

	IMaterialEffects* pMaterialEffects = gEnv->pGame->GetIGameFramework()->GetIMaterialEffects();
	TMFXEffectId effectId = pMaterialEffects->GetEffectId(query.c_str(), params.trgSurfaceId);

	if (effectId == InvalidEffectId)
		effectId = pMaterialEffects->GetEffectId(query.c_str(), pMaterialEffects->GetDefaultSurfaceIndex());

	if (effectId != InvalidEffectId)
		pMaterialEffects->ExecuteEffect(effectId, params);
}
Exemple #5
0
//------------------------------------------------------------------------
void CMelee::Hit(const Vec3 &pt, const Vec3 &dir, const Vec3 &normal, IPhysicalEntity *pCollider, int partId, int ipart, int surfaceIdx, float damageScale, bool remote)
{
	// generate the damage
	IEntity *pTarget = gEnv->pEntitySystem->GetEntityFromPhysics(pCollider);

	// Report punch to AI system.
	// The AI notification must come before the game rules are 
	// called so that the death handler in AIsystem understands that the hit
	// came from the player.
	CActor *pActor = m_pWeapon->GetOwnerActor();
	if (pActor && pActor->GetActorClass() == CPlayer::GetActorClassType())
	{
		CPlayer *pPlayer = (CPlayer *)pActor;
		if (pPlayer && pPlayer->GetNanoSuit())
		{
			if (pPlayer->GetEntity() && pPlayer->GetEntity()->GetAI())
			{
				SAIEVENT AIevent;
				AIevent.targetId = pTarget ? pTarget->GetId() : 0;
				// pPlayer->GetNanoSuit()->GetMode() == NANOMODE_STRENGTH
				pPlayer->GetEntity()->GetAI()->Event(AIEVENT_PLAYER_STUNT_PUNCH, &AIevent);
			}
		}
	}

	bool ok = true;
	if(pTarget)
	{
		if(!gEnv->bMultiplayer && pActor && pActor->IsPlayer())
		{
			IActor* pAITarget = g_pGame->GetIGameFramework()->GetIActorSystem()->GetActor(pTarget->GetId());
			if(pAITarget && pTarget->GetAI() && !pTarget->GetAI()->IsHostile(pActor->GetEntity()->GetAI(),false))
			{
				ok = false;
				m_noImpulse = true;
			}
		}

		if(ok)
		{
			CGameRules *pGameRules = g_pGame->GetGameRules();

			HitInfo info(m_pWeapon->GetOwnerId(), pTarget->GetId(), m_pWeapon->GetEntityId(),
				m_meleeparams.damage*damageScale*m_meleeScale, 0.0f, pGameRules->GetHitMaterialIdFromSurfaceId(surfaceIdx), partId,
				pGameRules->GetHitTypeId(m_meleeparams.hit_type.c_str()), pt, dir, normal);

			info.remote = remote;

			if (m_pWeapon->GetForcedHitMaterial() != -1)
				info.material=pGameRules->GetHitMaterialIdFromSurfaceId(m_pWeapon->GetForcedHitMaterial());

			pGameRules->ClientHit(info);
		}
	}

	// play effects
	if(ok)
	{
		IMaterialEffects* pMaterialEffects = gEnv->pGame->GetIGameFramework()->GetIMaterialEffects();

		TMFXEffectId effectId = pMaterialEffects->GetEffectId("melee", surfaceIdx);
		if (effectId != InvalidEffectId)
		{
			SMFXRunTimeEffectParams params;
			params.pos = pt;
			params.playflags = MFX_PLAY_ALL | MFX_DISABLE_DELAY;
			params.soundSemantic = eSoundSemantic_Player_Foley;
			pMaterialEffects->ExecuteEffect(effectId, params);
		}
	}

	ApplyCameraShake(true);

	m_pWeapon->PlayAction(m_meleeactions.hit.c_str());
}
Exemple #6
0
void CPlayerStateJump::Landed(CPlayer& player, const bool isHeavyWeapon, float fallSpeed)
{
#ifdef STATE_DEBUG
	bool remoteControlled = false;
	IVehicle* pVehicle = player.GetLinkedVehicle();
	if(pVehicle)
	{
		IVehicleSeat* pVehicleSeat = pVehicle->GetSeatForPassenger(player.GetEntityId());
		if(pVehicleSeat && pVehicleSeat->IsRemoteControlled())
		{
			remoteControlled = true;
		}
	}
	CRY_ASSERT_MESSAGE( player.GetLinkedEntity()==NULL || remoteControlled, "Cannot 'land' when you're linked to another entity!" );
#endif

	const SPlayerStats& stats = player.m_stats;

	Vec3 playerPosition = player.GetEntity()->GetWorldPos();
	IPhysicalEntity *phys = player.GetEntity()->GetPhysics();
	IMaterialEffects *mfx = gEnv->pGame->GetIGameFramework()->GetIMaterialEffects();

	const SActorPhysics& actorPhysics = player.GetActorPhysics();
	int matID = actorPhysics.groundMaterialIdx != -1 ? actorPhysics.groundMaterialIdx : mfx->GetDefaultSurfaceIndex();

	const float fHeightofEntity = playerPosition.z;
	const float worldWaterLevel = player.m_playerStateSwim_WaterTestProxy.GetWaterLevel();
	
	TMFXEffectId effectId = mfx->GetEffectId("bodyfall", matID);
	if (effectId != InvalidEffectId)
	{
		SMFXRunTimeEffectParams params;
		Vec3 direction = Vec3(0,0,0);
		if (IMovementController *pMV = player.GetMovementController())
		{
			SMovementState state;
			pMV->GetMovementState(state);
			direction = state.aimDirection;
		}
		params.pos = playerPosition + direction;
		//params.soundSemantic = eSoundSemantic_Player_Foley;

		float landFallParamVal = (float)__fsel( -(fallSpeed - 7.5f), 0.25f, 0.75f);
		params.AddAudioRtpc("landfall", landFallParamVal);

		const float speedParamVal = min(fabsf((actorPhysics.velocity.z * 0.1f)), 1.0f);
		params.AddAudioRtpc("speed", speedParamVal);

		mfx->ExecuteEffect(effectId, params);
	}

	bool heavyLanded = false;

	IItem* pCurrentItem = player.GetCurrentItem();
	CWeapon* pCurrentWeapon = pCurrentItem ? static_cast<CWeapon*>(pCurrentItem->GetIWeapon()) : NULL;

	if (fallSpeed > 0.0f && player.IsPlayer())
	{
		if(!gEnv->bMultiplayer)
		{
			const float verticalSpeed = fabs(fallSpeed);
			const float speedForHeavyLand = g_pGameCVars->pl_health.fallSpeed_HeavyLand;
			if ((verticalSpeed >= speedForHeavyLand) && (player.GetPickAndThrowEntity() == 0) && !player.IsDead())
			{
				if ( !isHeavyWeapon )
				{
					if (pCurrentWeapon)
					{
						pCurrentWeapon->FumbleGrenade();
						pCurrentWeapon->CancelCharge();
					}

					player.StartInteractiveActionByName("HeavyLand", false);
				}
				heavyLanded = true;
			}
		}
	}

	if(player.m_isClient)
	{
		if (fallSpeed > 0.0f)
		{
			const float fallIntensityMultiplier = stats.wasHit ? g_pGameCVars->pl_fall_intensity_hit_multiplier : g_pGameCVars->pl_fall_intensity_multiplier;
			const float fallIntensityMax = g_pGameCVars->pl_fall_intensity_max;
			const float fallTimeMultiplier = g_pGameCVars->pl_fall_time_multiplier;
			const float fallTimeMax = g_pGameCVars->pl_fall_time_max;
			const float zoomMultiplayer = (pCurrentWeapon && pCurrentWeapon->IsZoomed()) ? 0.2f : 1.0f;
			const float direction = ((cry_rand()%2)==0) ? -1.0f : 1.0f;
			const float intensity = clamp_tpl(fallIntensityMultiplier*fallSpeed*zoomMultiplayer, 0.0f, fallIntensityMax);
			const float shakeTime = clamp_tpl(fallTimeMultiplier*fallSpeed*zoomMultiplayer, 0.0f, fallTimeMax);
			const Vec3 rotation = Vec3(-0.5f, 0.15f*direction, 0.05f*direction);

			if (CScreenEffects* pGameScreenEffects = g_pGame->GetScreenEffects())
			{
				pGameScreenEffects->CamShake(rotation*intensity, Vec3(0, 0, 0), shakeTime, shakeTime, 0.05f, CScreenEffects::eCS_GID_Player);
			}

			IForceFeedbackSystem* pForceFeedback = g_pGame->GetIGameFramework()->GetIForceFeedbackSystem();
			assert(pForceFeedback);

			ForceFeedbackFxId fxId = pForceFeedback->GetEffectIdByName("landFF");
			pForceFeedback->PlayForceFeedbackEffect(fxId, SForceFeedbackRuntimeParams(intensity, 0.0f));

			if(fallSpeed > 7.0f)
			{
				player.PlaySound(CPlayer::ESound_Fall_Drop);
			}

			CPlayer::EPlayerSounds playerSound = heavyLanded ? CPlayer::ESound_Gear_HeavyLand : CPlayer::ESound_Gear_Land;
			player.PlaySound(playerSound, true);
		}
		CCCPOINT(PlayerMovement_LocalPlayerLanded);
	}
	
	if( gEnv->pAISystem )
	{
		// Notify AI
		//If silent feet active, ignore here
		const float noiseSupression = 0.0f;
		const float fAISoundRadius = (g_pGameCVars->ai_perception.landed_baseRadius + (g_pGameCVars->ai_perception.landed_speedMultiplier * fallSpeed)) * (1.0f - noiseSupression);
		SAIStimulus stim(AISTIM_SOUND, AISOUND_MOVEMENT_LOUD, player.GetEntityId(), 0,
			player.GetEntity()->GetWorldPos() + player.GetEyeOffset(), ZERO, fAISoundRadius);
		gEnv->pAISystem->RegisterStimulus(stim);
	}

	// Record 'Land' telemetry stats.

	CStatsRecordingMgr::TryTrackEvent(&player, eGSE_Land, fallSpeed);

	if (fallSpeed > 0.0f)
	{
		player.CreateScriptEvent( heavyLanded ? "heavylanded" : "landed",stats.fallSpeed);
	}
}
Exemple #7
0
//------------------------------------------------------------------------
void CTornado::Update(SEntityUpdateContext &ctx, int updateSlot)
{
	if (g_pGame->GetIGameFramework()->IsEditing())
		return;

	// wandering
	Matrix34 m = GetEntity()->GetWorldTM();
	Vec3 dir(m.GetColumn(1));
	Vec3 pos(GetEntity()->GetWorldPos());

	if(!gEnv->bServer)
		pos = m_currentPos;

	Vec3 wanderPos(dir * 1.414214f);
	float wanderStrength(1.0f);
	float wanderRate(0.6f);
	Vec3 wanderOffset;
	wanderOffset.SetRandomDirection();
	wanderOffset.z = 0.0f;
	wanderOffset.NormalizeSafe(Vec3(1,0,0));
	m_wanderDir += wanderOffset * wanderRate + (m_wanderDir - wanderPos) * wanderStrength;
	m_wanderDir = (m_wanderDir - wanderPos).GetNormalized() + wanderPos;

	Vec3 wanderSteer = (dir + m_wanderDir * gEnv->pTimer->GetFrameTime());
	wanderSteer.z = 0;
	wanderSteer.NormalizeSafe(Vec3(1,0,0));

	Vec3 targetSteer(0,0,0);
	// go to target
	if (m_pTargetEntity)
	{
		Vec3 target = m_pTargetEntity->GetWorldPos() - pos;
		if (target.GetLength() < 10.0f)
		{
			// emit target reached event
			SEntityEvent event( ENTITY_EVENT_SCRIPT_EVENT );
			event.nParam[0] = (INT_PTR)"TargetReached";
			event.nParam[1] = IEntityClass::EVT_BOOL;
			bool bValue = true;
			event.nParam[2] = (INT_PTR)&bValue;
			GetEntity()->SendEvent( event );
			if (m_pTargetCallback)
				m_pTargetCallback->Done();

			m_pTargetEntity = 0;
			m_pTargetCallback = 0;
		}

		targetSteer = (target - dir);
		targetSteer.z = 0;
		targetSteer.NormalizeSafe(Vec3(1,0,0));
	}

	Vec3 steerDir = (0.4f * wanderSteer + 0.6f * targetSteer).GetNormalized();
	Matrix34 tm = Matrix34(Matrix33::CreateRotationVDir(steerDir));
	pos = pos + steerDir * gEnv->pTimer->GetFrameTime() * m_wanderSpeed;
	pos.z = gEnv->p3DEngine->GetTerrainElevation(pos.x, pos.y);
	float waterLevel = gEnv->p3DEngine->GetWaterLevel(&pos);

	bool prevIsOnWater = m_isOnWater;
	m_isOnWater = (pos.z < waterLevel);
	if (m_isOnWater)
	{
		pos.z = waterLevel;
	}

	// raycast does not work for oceans
	if (prevIsOnWater != m_isOnWater && m_isOnWater)
	{
			m_pGroundEffect->SetParticleEffect("weather.tornado.water");
	}
	else if (!m_isOnWater)
	{		
		IMaterialEffects *mfx = gEnv->pGame->GetIGameFramework()->GetIMaterialEffects();
		Vec3 down = Vec3(0,0,-1.0f);
		int matID = mfx->GetDefaultSurfaceIndex();

		static const int objTypes = ent_all;    
		static const unsigned int flags = rwi_stop_at_pierceable|rwi_colltype_any;
		ray_hit hit;
		int col = gEnv->pPhysicalWorld->RayWorldIntersection(pos, (down * 5.0f), objTypes, flags, &hit, 1, GetEntity()->GetPhysics());
		if (col)
		{
			matID = hit.surface_idx;
		}

		if (m_curMatID != matID)
		{
			TMFXEffectId effectId = mfx->GetEffectId("tornado", matID);
			
			SMFXResourceListPtr pList = mfx->GetResources(effectId);
			if (pList && pList->m_particleList)
			{
				m_pGroundEffect->SetParticleEffect(pList->m_particleList->m_particleParams.name);
			}
			m_curMatID = matID;
		}
	}

	if(gEnv->bServer)
	{
		tm.SetTranslation(pos);
		m_currentPos = pos;
		CHANGED_NETWORK_STATE(this, POSITION_ASPECT);
		GetEntity()->SetWorldTM(tm);
	}
	else
	{
		tm.SetTranslation(m_currentPos);
		GetEntity()->SetWorldTM(tm);
	}

	UpdateParticleEmitters();
	UpdateTornadoSpline();

	UpdateFlow();
}
void CClientHitEffectsMP::ProcessEffectInfo(SHitEffectInfoSet& hitEffectSet, XmlNodeRef xmlNode, const char* libraryName)
{
    bool foundDefault = false;
    bool foundMelee = false;
    const uint numEffects = xmlNode->getChildCount();
    IMaterialEffects* pMaterialEffects = gEnv->pMaterialEffects;
    IEntityClassRegistry* pClassRegistry = gEnv->pEntitySystem->GetClassRegistry();

    hitEffectSet.m_effectInfos.reserve(numEffects);

    for (uint i = 0; i < numEffects; i++)
        {
            if(XmlNodeRef childNode = xmlNode->getChild(i))
                {
                    if(const char* nameTag = childNode->getTag())
                        {
                            if(!foundDefault && !strcmp("default", nameTag))
                                {
                                    const char* effectName = childNode->getAttr("effect");

                                    if(effectName)
                                        {
                                            hitEffectSet.m_default = pMaterialEffects->GetEffectIdByName(libraryName, effectName);
                                        }

                                    foundDefault = true;
                                }
                            else if(!foundMelee && !strcmp("melee", nameTag))
                                {
                                    const char* effectName = childNode->getAttr("effect");

                                    if(effectName)
                                        {
                                            hitEffectSet.m_melee = pMaterialEffects->GetEffectIdByName(libraryName, effectName);
                                        }

                                    foundMelee = true;
                                }
                            else
                                {
                                    SHitEffectInfo newInfo;

                                    newInfo.pAmmoClass = pClassRegistry->FindClass(nameTag);

                                    const char* effectName = childNode->getAttr("effect");

                                    if(effectName)
                                        {
                                            newInfo.effectId = pMaterialEffects->GetEffectIdByName(libraryName, effectName);
                                        }

                                    if(newInfo.pAmmoClass && newInfo.effectId)
                                        {
                                            hitEffectSet.m_effectInfos.push_back(newInfo);
                                        }
                                    else
                                        {
                                            if(!newInfo.pAmmoClass)
                                                {
                                                    GameWarning("Class type %s does not exist", nameTag);
                                                }

                                            if(!newInfo.effectId)
                                                {
                                                    GameWarning("Material Effect %s does not exist", effectName ? effectName : "");
                                                }
                                        }
                                }
                        }
                }
        }

    if(!hitEffectSet.m_melee)
        {
            hitEffectSet.m_melee = hitEffectSet.m_default;
        }
}
void CCannonBall::HandlePierceableSurface( const EventPhysCollision* pCollision, IEntity* pHitTarget, const Vec3& hitDirection, bool bProcessedCollisionEvent )
{
	FUNCTION_PROFILER(GetISystem(), PROFILE_GAME);

	const SPierceabilityParams& pierceabilityParams = m_pAmmoParams->pierceabilityParams;
	
	const int maxPenetrationCount = 4;
	const float entryAngleDot = pCollision->n.Dot(hitDirection);
	bool backFace = (entryAngleDot >= 0);

#ifdef DEBUG_CannonBall_PENETRATION
	bool debugCannonBallPenetration = (g_pGameCVars->g_bulletPenetrationDebug != 0);
#endif

	if (backFace == false)
	{
		//Front face hit, accumulate damage falloff after penetration
		float bouncy, friction;
		uint32 pierceabilityMat;
		gEnv->pPhysicalWorld->GetSurfaceParameters(pCollision->idmat[1], bouncy, friction, pierceabilityMat);
		pierceabilityMat &= sf_pierceable_mask;

#ifdef DEBUG_CannonBall_PENETRATION
		const float damageBeforePenetration = GetDamageAfterPenetrationFallOff();
#endif

		m_penetrationCount++;

		//1- Check if collided surface might stop the Cannon Ball
		const bool collisionStopsCannonBall = (!bProcessedCollisionEvent) || 
			(pCollision->idCollider == -1) || 
			((int16)pierceabilityMat <= GetCannonBallPierceability()) || 
			(m_penetrationCount >= maxPenetrationCount);
		
		if (collisionStopsCannonBall)
		{
#ifdef DEBUG_CannonBall_PENETRATION
			if (debugCannonBallPenetration)
			{
				s_debugCannonBallPenetration.AddCannonBallHit(pCollision->pt, hitDirection, damageBeforePenetration, (pCollision->idCollider == -1) ? -1 : pierceabilityMat, false, true, false);
			}
#endif
			m_accumulatedDamageFallOffAfterPenetration += (float)m_damage;
			return;
		}

		//2- If not stopped, add fall off damage, and see if can still penetrate
		m_accumulatedDamageFallOffAfterPenetration += (float)m_damage * (pierceabilityParams.GetDamageFallOffForPierceability(pierceabilityMat) * 0.01f);
		
		bool needsBackFaceCheck = (GetDamageAfterPenetrationFallOff() > 0.0f) && pierceabilityParams.SurfaceRequiresBackFaceCheck(pierceabilityMat);

#ifdef DEBUG_CannonBall_PENETRATION
		if (debugCannonBallPenetration)
		{
			if (ShouldDestroyCannonBall())
			{
				s_debugCannonBallPenetration.AddCannonBallHit(pCollision->pt, hitDirection, damageBeforePenetration, pierceabilityMat, false, true, false);
			}
		}
#endif

		if (needsBackFaceCheck)
		{
			//3- Raytrace backwards, to check thickness & exit point if any
			const float angleFactor = 1.0f/max(0.2f, -entryAngleDot);
			const float distCheck = pierceabilityParams.maxPenetrationThickness * angleFactor;

			SBackHitInfo hit;
			bool exitPointFound = RayTraceGeometry(pCollision, pCollision->pt + (hitDirection * (distCheck + 0.035f)), -hitDirection * distCheck ,&hit);

			if (exitPointFound)
			{
				//Exit point found
				if(ShouldSpawnBackSideEffect(pHitTarget))
				{
					//Spawn effect
					IMaterialEffects* pMaterialEffects = g_pGame->GetIGameFramework()->GetIMaterialEffects();
					TMFXEffectId effectId = pMaterialEffects->GetEffectId(GetEntity()->GetClass(), pCollision->idmat[1]);
					if (effectId != InvalidEffectId)
					{
						SMFXRunTimeEffectParams params;
						params.src = GetEntityId();
						params.trg = pHitTarget ? pHitTarget->GetId() : 0;
						params.srcSurfaceId = pCollision->idmat[0];
						params.trgSurfaceId = pCollision->idmat[1]; 
						params.soundSemantic = eSoundSemantic_Physics_Collision;
						params.srcRenderNode = (pCollision->iForeignData[0] == PHYS_FOREIGN_ID_STATIC) ? (IRenderNode*)pCollision->pForeignData[0] : NULL;
						params.trgRenderNode = (pCollision->iForeignData[1] == PHYS_FOREIGN_ID_STATIC) ? (IRenderNode*)pCollision->pForeignData[1] : NULL;
						params.pos = hit.pt;
						params.normal = hitDirection; //Use Cannon direction, more readable for exits than normal
						params.partID = pCollision->partid[1];
						params.dir[0] = -hitDirection;
						params.playflags = MFX_PLAY_ALL&(~MFX_PLAY_SOUND); //Do not play the sound on backface
						params.playflags &= ~MFX_PLAY_DECAL; //We disable also decals, since hit.pt is not refined with render mesh
						params.fDecalPlacementTestMaxSize = pCollision->fDecalPlacementTestMaxSize;

						pMaterialEffects->ExecuteEffect(effectId, params);
					}
				}

#ifdef DEBUG_CannonBall_PENETRATION
				if (debugCannonBallPenetration)
				{
					s_debugCannonBallPenetration.AddCannonBallHit(pCollision->pt, hitDirection, damageBeforePenetration, pierceabilityMat, false, false, false);
					s_debugCannonBallPenetration.AddCannonBallHit(hit.pt, hitDirection, GetDamageAfterPenetrationFallOff(), pierceabilityMat, true, false, false);
				}
#endif
			}
			else
			{
#ifdef DEBUG_CannonBall_PENETRATION
				if (debugCannonBallPenetration)
				{
					s_debugCannonBallPenetration.AddCannonBallHit(pCollision->pt, hitDirection, damageBeforePenetration, pierceabilityMat, false, true, true);
				}
#endif
				//Surface must be too thick, add enough fall off to destroy the Cannon Ball
				m_accumulatedDamageFallOffAfterPenetration += (float)m_damage;
			}
		}
	}
}