From 2ce096ff034750377ddb534bafa4572807e2977b Mon Sep 17 00:00:00 2001 From: bo Date: Sat, 14 Mar 2026 20:53:50 -0500 Subject: [PATCH] Directed Bot Jumping --- src/game/Object/Unit.h | 5 + src/modules/Bots/playerbot/PlayerbotAI.cpp | 143 +++++++++++++++++- src/modules/Bots/playerbot/PlayerbotAI.h | 15 ++ .../strategy/actions/ActionContext.h | 4 + .../strategy/actions/MovementActions.cpp | 18 +++ .../strategy/actions/MovementActions.h | 14 ++ .../generic/ChatCommandHandlerStrategy.cpp | 10 ++ .../strategy/triggers/ChatTriggerContext.h | 4 + 8 files changed, 211 insertions(+), 2 deletions(-) diff --git a/src/game/Object/Unit.h b/src/game/Object/Unit.h index 32452e107..60a2d2753 100644 --- a/src/game/Object/Unit.h +++ b/src/game/Object/Unit.h @@ -671,6 +671,11 @@ class MovementInfo }; JumpInfo const& GetJumpInfo() const { return jump; } + void SetJumpInfo(float vel, float sinA, float cosA, float xyspd) + { + jump.velocity = vel; jump.sinAngle = sinA; jump.cosAngle = cosA; jump.xyspeed = xyspd; + } + void SetFallTime(uint32 t) { fallTime = t; } private: // common uint32 moveFlags; // see enum MovementFlags diff --git a/src/modules/Bots/playerbot/PlayerbotAI.cpp b/src/modules/Bots/playerbot/PlayerbotAI.cpp index 10be6482d..dc935d8b8 100644 --- a/src/modules/Bots/playerbot/PlayerbotAI.cpp +++ b/src/modules/Bots/playerbot/PlayerbotAI.cpp @@ -78,7 +78,12 @@ void PacketHandlingHelper::AddPacket(const WorldPacket& packet) */ PlayerbotAI::PlayerbotAI() : PlayerbotAIBase(), bot(NULL), aiObjectContext(NULL), currentEngine(NULL), chatHelper(this), chatFilter(this), accountId(0), security(NULL), master(NULL), currentState(BOT_STATE_NON_COMBAT), - m_eatingUntil(0), m_drinkingUntil(0) + m_eatingUntil(0), m_drinkingUntil(0), + m_isJumping(false), m_jumpStartTime(0), + m_jumpStartX(0.f), m_jumpStartY(0.f), m_jumpStartZ(0.f), + m_jumpSinAngle(0.f), m_jumpCosAngle(1.f), m_jumpXYSpeed(0.f), + m_pendingJump(false), m_jumpRequestTime(0), + m_jumpTargetX(0.f), m_jumpTargetY(0.f), m_jumpTargetZ(0.f), m_jumpTargetO(0.f) { for (int i = 0 ; i < BOT_STATE_MAX; i++) { @@ -92,7 +97,12 @@ PlayerbotAI::PlayerbotAI() : PlayerbotAIBase(), bot(NULL), aiObjectContext(NULL) */ PlayerbotAI::PlayerbotAI(Player* bot) : PlayerbotAIBase(), chatHelper(this), chatFilter(this), security(bot), master(NULL), - m_eatingUntil(0), m_drinkingUntil(0) + m_eatingUntil(0), m_drinkingUntil(0), + m_isJumping(false), m_jumpStartTime(0), + m_jumpStartX(0.f), m_jumpStartY(0.f), m_jumpStartZ(0.f), + m_jumpSinAngle(0.f), m_jumpCosAngle(1.f), m_jumpXYSpeed(0.f), + m_pendingJump(false), m_jumpRequestTime(0), + m_jumpTargetX(0.f), m_jumpTargetY(0.f), m_jumpTargetZ(0.f), m_jumpTargetO(0.f) { this->bot = bot; @@ -162,6 +172,132 @@ PlayerbotAI::~PlayerbotAI() } } +static const float BOT_JUMP_VELOCITY = 7.9557f; +static const float BOT_JUMP_GRAVITY = 19.2911f; + +void PlayerbotAI::RequestJump() +{ + if (m_pendingJump || m_isJumping) + return; + + Player* master = GetMaster(); + if (!master) + return; + + m_jumpTargetX = master->GetPositionX(); + m_jumpTargetY = master->GetPositionY(); + m_jumpTargetZ = master->GetPositionZ(); + m_jumpTargetO = master->GetOrientation(); + m_pendingJump = true; + m_jumpRequestTime = getMSTime(); +} + +void PlayerbotAI::StartJump(bool forward, float orientation) +{ + if (m_isJumping || bot->IsDead()) + return; + + bot->GetMotionMaster()->Clear(); + bot->GetMotionMaster()->MoveIdle(); + + m_jumpStartTime = getMSTime(); + m_jumpStartX = bot->GetPositionX(); + m_jumpStartY = bot->GetPositionY(); + m_jumpStartZ = bot->GetPositionZ(); + + float o = (orientation >= 0.f) ? orientation : bot->GetOrientation(); + m_jumpCosAngle = cosf(o); + m_jumpSinAngle = sinf(o); + m_jumpXYSpeed = forward ? bot->GetSpeed(MOVE_RUN) : 0.f; + m_isJumping = true; + + bot->SetFallInformation(0, m_jumpStartZ); + + bot->m_movementInfo.SetMovementFlags(MOVEFLAG_FALLING); + if (forward) + bot->m_movementInfo.AddMovementFlag(MOVEFLAG_FORWARD); + bot->m_movementInfo.SetFallTime(0); + bot->m_movementInfo.SetJumpInfo(-BOT_JUMP_VELOCITY, m_jumpCosAngle, m_jumpSinAngle, m_jumpXYSpeed); + bot->m_movementInfo.ChangePosition(m_jumpStartX, m_jumpStartY, m_jumpStartZ, o); + bot->m_movementInfo.UpdateTime(m_jumpStartTime); + + WorldPacket data(MSG_MOVE_JUMP, 64); + data << bot->GetPackGUID(); + bot->m_movementInfo.Write(data); + bot->SendMessageToSet(&data, false); +} + +void PlayerbotAI::UpdateJump() +{ + if (m_pendingJump && !m_isJumping) + { + if (getMSTime() - m_jumpRequestTime > 10000) + { + m_pendingJump = false; + } + else + { + float dx = m_jumpTargetX - bot->GetPositionX(); + float dy = m_jumpTargetY - bot->GetPositionY(); + float dist2d = sqrtf(dx * dx + dy * dy); + if (dist2d <= 0.5f) + { + m_pendingJump = false; + StartJump(true, m_jumpTargetO); + } + else + { + bot->GetMotionMaster()->MovePoint(0, m_jumpTargetX, m_jumpTargetY, m_jumpTargetZ); + } + } + return; + } + + if (!m_isJumping) + return; + + uint32 now = getMSTime(); + uint32 fallTimeMs = now - m_jumpStartTime; + float t = fallTimeMs / 1000.f; + + float z = m_jumpStartZ + BOT_JUMP_VELOCITY * t - 0.5f * BOT_JUMP_GRAVITY * t * t; + float x = m_jumpStartX + m_jumpCosAngle * m_jumpXYSpeed * t; + float y = m_jumpStartY + m_jumpSinAngle * m_jumpXYSpeed * t; + + float maxDuration = 2.f * BOT_JUMP_VELOCITY / BOT_JUMP_GRAVITY * 1000.f + 100.f; + bool landed = fallTimeMs > 200 && ((z <= m_jumpStartZ + 0.05f) || (float(fallTimeMs) >= maxDuration)); + + bot->m_movementInfo.UpdateTime(now); + bot->m_movementInfo.SetFallTime(fallTimeMs); + bot->m_movementInfo.ChangePosition(x, y, z, bot->GetOrientation()); + + if (landed) + { + m_isJumping = false; + + float landZ = m_jumpStartZ; + if (Map* map = bot->GetMap()) + { + float terrainZ = map->GetHeight(x, y, z > m_jumpStartZ ? z : m_jumpStartZ); + if (terrainZ > INVALID_HEIGHT) + landZ = terrainZ; + } + + bot->m_movementInfo.RemoveMovementFlag(MovementFlags(MOVEFLAG_FALLING | MOVEFLAG_FORWARD)); + bot->m_movementInfo.ChangePosition(x, y, landZ, bot->GetOrientation()); + + WorldPacket data(MSG_MOVE_FALL_LAND, 64); + data << bot->GetPackGUID(); + bot->m_movementInfo.Write(data); + bot->SendMessageToSet(&data, false); + + bot->SetFallInformation(fallTimeMs, landZ); + bot->GetMotionMaster()->Clear(); + bot->GetMotionMaster()->MoveIdle(); + bot->GetMap()->PlayerRelocation(bot, x, y, landZ, bot->GetOrientation()); + } +} + void PlayerbotAI::UpdateAI(uint32 elapsed) { if (bot->IsBeingTeleported()) @@ -206,6 +342,9 @@ void PlayerbotAI::UpdateAI(uint32 elapsed) } } + if (m_isJumping || m_pendingJump) + UpdateJump(); + PlayerbotAIBase::UpdateAI(elapsed); } diff --git a/src/modules/Bots/playerbot/PlayerbotAI.h b/src/modules/Bots/playerbot/PlayerbotAI.h index 1c40f7122..af554e4d0 100644 --- a/src/modules/Bots/playerbot/PlayerbotAI.h +++ b/src/modules/Bots/playerbot/PlayerbotAI.h @@ -180,6 +180,11 @@ class PlayerbotAI : public PlayerbotAIBase static bool IsOpposing(uint8 race1, uint8 race2); PlayerbotSecurity* GetSecurity() { return &security; } + void StartJump(bool forward, float orientation = -1.f); + void RequestJump(); + bool IsJumping() const { return m_isJumping; } + bool IsPendingJump() const { return m_pendingJump; } + bool IsEating() const { return m_eatingUntil && time(0) <= m_eatingUntil @@ -213,5 +218,15 @@ class PlayerbotAI : public PlayerbotAIBase PlayerbotSecurity security; time_t m_eatingUntil; time_t m_drinkingUntil; + + bool m_isJumping; + uint32 m_jumpStartTime; + float m_jumpStartX, m_jumpStartY, m_jumpStartZ; + float m_jumpSinAngle, m_jumpCosAngle, m_jumpXYSpeed; + bool m_pendingJump; + uint32 m_jumpRequestTime; + float m_jumpTargetX, m_jumpTargetY, m_jumpTargetZ, m_jumpTargetO; + + void UpdateJump(); }; diff --git a/src/modules/Bots/playerbot/strategy/actions/ActionContext.h b/src/modules/Bots/playerbot/strategy/actions/ActionContext.h index 15d7b1fef..a5fb82f98 100644 --- a/src/modules/Bots/playerbot/strategy/actions/ActionContext.h +++ b/src/modules/Bots/playerbot/strategy/actions/ActionContext.h @@ -66,6 +66,8 @@ namespace ai creators["set facing"] = &ActionContext::set_facing; creators["attack duel opponent"] = &ActionContext::attack_duel_opponent; creators["drop target"] = &ActionContext::drop_target; + creators["jump"] = &ActionContext::jump; + creators["jump up"] = &ActionContext::jump_up; } private: @@ -112,5 +114,7 @@ namespace ai static Action* healthstone(PlayerbotAI* ai) { return new UseItemAction(ai, "healthstone"); } static Action* move_out_of_enemy_contact(PlayerbotAI* ai) { return new MoveOutOfEnemyContactAction(ai); } static Action* set_facing(PlayerbotAI* ai) { return new SetFacingTargetAction(ai); } + static Action* jump(PlayerbotAI* ai) { return new JumpAction(ai); } + static Action* jump_up(PlayerbotAI* ai) { return new JumpInPlaceAction(ai); } }; }; diff --git a/src/modules/Bots/playerbot/strategy/actions/MovementActions.cpp b/src/modules/Bots/playerbot/strategy/actions/MovementActions.cpp index 56dd150e0..8491276e0 100644 --- a/src/modules/Bots/playerbot/strategy/actions/MovementActions.cpp +++ b/src/modules/Bots/playerbot/strategy/actions/MovementActions.cpp @@ -626,3 +626,21 @@ bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "current target"); } + +bool JumpAction::Execute(Event event) +{ + if (ai->IsJumping() || ai->IsPendingJump()) + return false; + + ai->RequestJump(); + return ai->IsPendingJump(); +} + +bool JumpInPlaceAction::Execute(Event event) +{ + if (ai->IsJumping()) + return false; + + ai->StartJump(false); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/MovementActions.h b/src/modules/Bots/playerbot/strategy/actions/MovementActions.h index 1414f4e54..8f8401ad6 100644 --- a/src/modules/Bots/playerbot/strategy/actions/MovementActions.h +++ b/src/modules/Bots/playerbot/strategy/actions/MovementActions.h @@ -95,4 +95,18 @@ namespace ai virtual bool isUseful(); }; + class JumpAction : public MovementAction + { + public: + JumpAction(PlayerbotAI* ai) : MovementAction(ai, "jump") {} + virtual bool Execute(Event event); + }; + + class JumpInPlaceAction : public MovementAction + { + public: + JumpInPlaceAction(PlayerbotAI* ai) : MovementAction(ai, "jump up") {} + virtual bool Execute(Event event); + }; + } diff --git a/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.cpp index efc3d703f..29e4e2130 100644 --- a/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -131,6 +131,14 @@ void ChatCommandHandlerStrategy::InitTriggers(std::list &triggers) triggers.push_back(new TriggerNode( "attackers", NextAction::array(0, new NextAction("tell attackers", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "jump", + NextAction::array(0, new NextAction("jump", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "jump up", + NextAction::array(0, new NextAction("jump up", relevance), NULL))); } @@ -173,4 +181,6 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* ai) : PassTr supported.push_back("summon"); supported.push_back("who"); supported.push_back("save mana"); + supported.push_back("jump"); + supported.push_back("jump up"); } diff --git a/src/modules/Bots/playerbot/strategy/triggers/ChatTriggerContext.h b/src/modules/Bots/playerbot/strategy/triggers/ChatTriggerContext.h index 4a6397b24..6fa14b97a 100644 --- a/src/modules/Bots/playerbot/strategy/triggers/ChatTriggerContext.h +++ b/src/modules/Bots/playerbot/strategy/triggers/ChatTriggerContext.h @@ -71,10 +71,14 @@ namespace ai creators["save mana"] = &ChatTriggerContext::save_mana; creators["max dps"] = &ChatTriggerContext::max_dps; creators["attackers"] = &ChatTriggerContext::attackers; + creators["jump"] = &ChatTriggerContext::jump; + creators["jump up"] = &ChatTriggerContext::jump_up; } private: static Trigger* attackers(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "attackers"); } + static Trigger* jump(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "jump"); } + static Trigger* jump_up(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "jump up"); } static Trigger* max_dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "max dps"); } static Trigger* save_mana(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "save mana"); } static Trigger* who(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "who"); }