diff --git a/src/game/WorldHandlers/Spell.cpp b/src/game/WorldHandlers/Spell.cpp index 937de784..10391916 100644 --- a/src/game/WorldHandlers/Spell.cpp +++ b/src/game/WorldHandlers/Spell.cpp @@ -4929,24 +4929,28 @@ SpellCastResult Spell::CheckCast(bool strict) case SPELL_EFFECT_LEAP: case SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER: { - float dis = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellInfo->EffectRadiusIndex[i])); - float fx = m_caster->GetPositionX() + dis * cos(m_caster->GetOrientation()); - float fy = m_caster->GetPositionY() + dis * sin(m_caster->GetOrientation()); - // teleport a bit above terrain level to avoid falling below it - float fz = m_caster->GetMap()->GetHeight(fx, fy, m_caster->GetPositionZ()); - if (fz <= INVALID_HEIGHT) // note: this also will prevent use effect in instances without vmaps height enabled - { return SPELL_FAILED_TRY_AGAIN; } + if (!m_caster || m_caster->IsTaxiFlying()) + return SPELL_FAILED_NOT_ON_TAXI; - float caster_pos_z = m_caster->GetPositionZ(); - // Control the caster to not climb or drop when +-fz > 8 - if (!(fz <= caster_pos_z + 8 && fz >= caster_pos_z - 8)) - { return SPELL_FAILED_TRY_AGAIN; } + // Blink has leap first and then removing of auras with root effect + // need further research with this + if (m_spellInfo->Effect[i] != SPELL_EFFECT_LEAP) + { + if (m_caster->hasUnitState(UNIT_STAT_ROOT)) + return SPELL_FAILED_ROOTED; + } - // not allow use this effect at battleground until battleground start if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + if (((Player*)m_caster)->HasMovementFlag(MOVEFLAG_ONTRANSPORT)) + return SPELL_FAILED_NOT_ON_TRANSPORT; + + // not allow use this effect at battleground until battleground start if (BattleGround const* bg = ((Player*)m_caster)->GetBattleGround()) if (bg->GetStatus() != STATUS_IN_PROGRESS) - { return SPELL_FAILED_TRY_AGAIN; } + return SPELL_FAILED_TRY_AGAIN; + } + break; } default: break; diff --git a/src/game/WorldHandlers/SpellEffects.cpp b/src/game/WorldHandlers/SpellEffects.cpp index e04426de..1d295548 100644 --- a/src/game/WorldHandlers/SpellEffects.cpp +++ b/src/game/WorldHandlers/SpellEffects.cpp @@ -56,6 +56,7 @@ #include "Util.h" #include "TemporarySummon.h" #include "ScriptMgr.h" +#include "G3D/Vector3.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" #endif /* ENABLE_ELUNA */ @@ -4360,24 +4361,160 @@ void Spell::EffectBlock(SpellEffectIndex /*eff_idx*/) void Spell::EffectLeapForward(SpellEffectIndex eff_idx) { - if (unitTarget->IsTaxiFlying()) - { return; } + float dist = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellInfo->EffectRadiusIndex[eff_idx])); + const float IN_OR_UNDER_LIQUID_RANGE = 0.8f; // range to make player under liquid or on liquid surface from liquid level - if (m_spellInfo->rangeIndex == SPELL_RANGE_IDX_SELF_ONLY) + G3D::Vector3 prevPos, nextPos; + float orientation = unitTarget->GetOrientation(); + + prevPos.x = unitTarget->GetPositionX(); + prevPos.y = unitTarget->GetPositionY(); + prevPos.z = unitTarget->GetPositionZ(); + + float groundZ = prevPos.z; + bool isPrevInLiquid = false; + + // falling case + if (!unitTarget->GetMap()->GetHeightInRange(prevPos.x, prevPos.y, groundZ, 3.0f) && unitTarget->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLING)) { - float dis = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellInfo->EffectRadiusIndex[eff_idx])); + nextPos.x = prevPos.x + dist * cos(orientation); + nextPos.y = prevPos.y + dist * sin(orientation); + nextPos.z = prevPos.z - 2.0f; // little hack to avoid the impression to go up when teleporting instead of continue to fall. This value may need some tweak - // before caster - float fx, fy, fz; - unitTarget->GetClosePoint(fx, fy, fz, unitTarget->GetObjectBoundingRadius(), dis); - float ox, oy, oz; - unitTarget->GetPosition(ox, oy, oz); + // + GridMapLiquidData liquidData; + if (unitTarget->GetMap()->GetTerrain()->IsInWater(nextPos.x, nextPos.y, nextPos.z, &liquidData)) + { + if (fabs(nextPos.z - liquidData.level) < 10.0f) + nextPos.z = liquidData.level - IN_OR_UNDER_LIQUID_RANGE; + } + else + { + // fix z to ground if near of it + unitTarget->GetMap()->GetHeightInRange(nextPos.x, nextPos.y, nextPos.z, 10.0f); + } - if (unitTarget->GetMap()->GetHitPosition(ox, oy, oz + 0.5f, fx, fy, fz, -0.5f)) - { unitTarget->UpdateAllowedPositionZ(fx, fy, fz); } + // check any obstacle and fix coords + unitTarget->GetMap()->GetHitPosition(prevPos.x, prevPos.y, prevPos.z + 0.5f, nextPos.x, nextPos.y, nextPos.z, -0.5f); - unitTarget->NearTeleportTo(fx, fy, fz, unitTarget->GetOrientation(), unitTarget == m_caster); + // teleport + unitTarget->NearTeleportTo(nextPos.x, nextPos.y, nextPos.z, orientation, unitTarget == m_caster); + + //sLog.outString("Falling BLINK!"); + return; } + + // fix origin position if player was jumping and near of the ground but not in ground + if (fabs(prevPos.z - groundZ) > 0.5f) + prevPos.z = groundZ; + + //check if in liquid + isPrevInLiquid = unitTarget->GetMap()->GetTerrain()->IsInWater(prevPos.x, prevPos.y, prevPos.z); + + const float step = 2.0f; // step length before next check slope/edge/water + const float maxSlope = 50.0f; // 50(degree) max seem best value for walkable slope + const float MAX_SLOPE_IN_RADIAN = maxSlope / 180.0f * M_PI_F; + float nextZPointEstimation = 1.0f; + float destx = prevPos.x + dist * cos(orientation); + float desty = prevPos.y + dist * sin(orientation); + const uint32 numChecks = ceil(fabs(dist / step)); + const float DELTA_X = (destx - prevPos.x) / numChecks; + const float DELTA_Y = (desty - prevPos.y) / numChecks; + + for (uint32 i = 1; i < numChecks + 1; ++i) + { + // compute next point average position + nextPos.x = prevPos.x + DELTA_X; + nextPos.y = prevPos.y + DELTA_Y; + nextPos.z = prevPos.z + nextZPointEstimation; + + bool isInLiquid = false; + bool isInLiquidTested = false; + bool isOnGround = false; + GridMapLiquidData liquidData; + + // try fix height for next position + if (!unitTarget->GetMap()->GetHeightInRange(nextPos.x, nextPos.y, nextPos.z)) + { + // we cant so test if we are on water + if (!unitTarget->GetMap()->GetTerrain()->IsInWater(nextPos.x, nextPos.y, nextPos.z, &liquidData)) + { + // not in water and cannot get correct height, maybe flying? + //sLog.outString("Can't get height of point %u, point value %s", i, nextPos.toString().c_str()); + nextPos = prevPos; + break; + } + else + { + isInLiquid = true; + isInLiquidTested = true; + } + } + else + isOnGround = true; // player is on ground + + if (isInLiquid || (!isInLiquidTested && unitTarget->GetMap()->GetTerrain()->IsInWater(nextPos.x, nextPos.y, nextPos.z, &liquidData))) + { + if (!isPrevInLiquid && fabs(liquidData.level - prevPos.z) > 2.0f) + { + // on edge of water with difference a bit to high to continue + //sLog.outString("Ground vs liquid edge detected!"); + nextPos = prevPos; + break; + } + + if ((liquidData.level - IN_OR_UNDER_LIQUID_RANGE) > nextPos.z) + nextPos.z = prevPos.z; // we are under water so next z equal prev z + else + nextPos.z = liquidData.level - IN_OR_UNDER_LIQUID_RANGE; // we are on water surface, so next z equal liquid level + + isInLiquid = true; + + float ground = nextPos.z; + if (unitTarget->GetMap()->GetHeightInRange(nextPos.x, nextPos.y, ground)) + { + if (nextPos.z < ground) + { + nextPos.z = ground; + isOnGround = true; // player is on ground of the water + } + } + } + + //unitTarget->SummonCreature(VISUAL_WAYPOINT, nextPos.x, nextPos.y, nextPos.z, 0, TEMPSUMMON_TIMED_DESPAWN, 15000); + float hitZ = nextPos.z + 1.5f; + if (unitTarget->GetMap()->GetHitPosition(prevPos.x, prevPos.y, prevPos.z + 1.5f, nextPos.x, nextPos.y, hitZ, -1.0f)) + { + //sLog.outString("Blink collision detected!"); + nextPos = prevPos; + break; + } + + if (isOnGround) + { + // project vector to get only positive value + float ac = fabs(prevPos.z - nextPos.z); + + // compute slope (in radian) + float slope = atan(ac / step); + + // check slope value + if (slope > MAX_SLOPE_IN_RADIAN) + { + //sLog.outString("bad slope detected! %4.2f max %4.2f, ac(%4.2f)", slope * 180 / M_PI_F, maxSlope, ac); + nextPos = prevPos; + break; + } + //sLog.outString("slope is ok! %4.2f max %4.2f, ac(%4.2f)", slope * 180 / M_PI_F, maxSlope, ac); + } + + //sLog.outString("point %u is ok, coords %s", i, nextPos.toString().c_str()); + nextZPointEstimation = (nextPos.z - prevPos.z) / 2.0f; + isPrevInLiquid = isInLiquid; + prevPos = nextPos; + } + + unitTarget->NearTeleportTo(nextPos.x, nextPos.y, nextPos.z, orientation, unitTarget == m_caster); } void Spell::EffectReputation(SpellEffectIndex eff_idx)