Jump to content

Playerbot (archive)


Recommended Posts

  • Replies 1.8k
  • Created
  • Last Reply

Top Posters In This Topic

Hi Guys,

The hunter bot was not attacking correctly; being caught in some kind of code loop, not moving, but repeatedly drawing it's weapon, like a gunslinger on speed. The problem was that there was no range check on the spell being cast, such as AUTO SHOT

Thanks to KiriX :), his patch corrects this and the hunter bot now attacks properly.

I will update both repos shortly, please provide feedback, thanks!

EDIT: Any thanks should be given to KiriX :)

Hope this helps

Link to comment
Share on other sites

Unfortunately AI was designed without clear view on ranged combat, and all class AIs do not expect to move bot into spell range before casting. Dragon's Breath casted from 25 yards looks funny and ineffective.

This patch does its job, but we forget about range modifying talents. I think bot has to build a spell range map during spell initialization and fill in corrected range values for all its spells. I'm going to implement it as soon as I have free time.

Mixing movement and casting without target change should go to TODO list, as it will improve caster bot performance a lot.

Link to comment
Share on other sites

I was coding the same stuff when you posted your patch, Kirix :) The idea in your patch is fine, but there is one problem in it. You use FindCurrentSpellBySpellId() before you actually cast a spell. Didn't tried it, but it should fail imho. Also calculating spell range every spell cast is a waste. It is much better to calculate ranges once and use it later. I've pushed new branch to portal repo called spell-range-map with my version of range checking.

Anyway, I want to thank you for your efforts. The more we discuss and try, the better the code.

Link to comment
Share on other sites

Have no git by my hand at the moment, so here is github fancy commit view: http://github.com/blueboy/portal/commit/602064d66ed9cc8917fa224fe4fd6466e861017b

Regarding your patch, you moved spell finder above

m_bot->CastSpell(pTarget, pSpellInfo, false);

so it should not return you a valid Spell class instance pointer because such instance is not created yet.

Btw I see

if (!m_bot->IsWithinLOSInMap(pTarget))

in your patch, which was not committed to repository. Guess we should fix it :) It would be great to implement bot movement when there is a LOS problem, but I have no idea where to start here.

Link to comment
Share on other sites

I do not know if anyone has ever thought and suggested adding a function to

switch the player controlled a robot relied group, for example, which would

take temporary control of it

Hi,

Like druid 'possession'. Hmmm nice idea, not sure how practical this would be, even if it was possible. I believe this spell only gives you movement control. I doubt whether you access the robot's inventory or get it to cast spells etc..

Thanks for the idea, though

Cheers

Link to comment
Share on other sites

Warning !!! Do not use it on public server EVER!

Ok, you've been warned. Here's a patch which adds ability to add bots from other accounts without any restrictions. Stability was not tested. Use it on your own risk. Botguy won't work.

diff --git a/src/game/CharacterHandler.cpp b/src/game/CharacterHandler.cpp
index 69ef07b..d12d320 100644
--- a/src/game/CharacterHandler.cpp
+++ b/src/game/CharacterHandler.cpp
@@ -601,16 +601,17 @@ void WorldSession::HandlePlayerLoginOpcode( WorldPacket & recv_data )

// Playerbot mod. Can't easily reuse HandlePlayerLoginOpcode for logging in bots because it assumes
// a WorldSession exists for the bot. The WorldSession for a bot is created after the character is loaded.
-void PlayerbotMgr::AddPlayerBot(uint64 playerGuid)
+void PlayerbotMgr::AddPlayerBot(uint64 playerGuid, uint32 accountId)
{
    // has bot already been added?
    if (sObjectMgr.GetPlayer(playerGuid))
        return;

+/*
    uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(playerGuid);
    if (accountId == 0)
        return;
-
+*/
    LoginQueryHolder *holder = new LoginQueryHolder(accountId, playerGuid);
    if(!holder->Initialize())
    {
diff --git a/src/game/Player.cpp b/src/game/Player.cpp
index 066e77c..b2a5f1d 100644
--- a/src/game/Player.cpp
+++ b/src/game/Player.cpp
@@ -13119,7 +13119,7 @@ void Player::OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 me
                }
                delete resultlvl;

-                GetPlayerbotMgr()->AddPlayerBot(guidlo);
+                GetPlayerbotMgr()->AddPlayerBot(guidlo, 0); // stub, won't work
                this->ModifyMoney(-(int32)cost);
            }
            break;
@@ -15129,7 +15129,7 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
    Field *fields = result->Fetch();

    uint32 dbAccountId = fields[1].GetUInt32();
-
+/*
    // check if the character's account in the db and the logged in account match.
    // player should be able to load/delete character only with correct account!
    if( dbAccountId != GetSession()->GetAccountId() )
@@ -15138,7 +15138,7 @@ bool Player::LoadFromDB( uint32 guid, SqlQueryHolder *holder )
        delete result;
        return false;
    }
-
+*/
    Object::_Create( guid, 0, HIGHGUID_PLAYER );

    m_name = fields[2].GetCppString();
@@ -16843,6 +16843,14 @@ void Player::SaveToDB()
    DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "The value of player %s at save: ", m_name.c_str());
    outDebugStatsValues();

+    uint32 account_id = GetSession()->GetAccountId();
+    QueryResult *result = CharacterDatabase.PQuery("SELECT account FROM characters WHERE guid = '%u'", GetGUIDLow());
+    if (result)
+    {
+        account_id = (*result)[0].GetUInt32();
+        delete result;
+    }
+
    CharacterDatabase.BeginTransaction();

    CharacterDatabase.PExecute("DELETE FROM characters WHERE guid = '%u'",GetGUIDLow());
@@ -16860,7 +16868,7 @@ void Player::SaveToDB()
        "todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk, health, power1, power2, power3, "
        "power4, power5, power6, power7, specCount, activeSpec, exploredZones, equipmentCache, ammoId, knownTitles, actionBars) VALUES
("
        << GetGUIDLow() << ", "
-        << GetSession()->GetAccountId() << ", '"
+        << account_id << ", '"
        << sql_name << "', "
        << (uint32)getRace() << ", "
        << (uint32)getClass() << ", "
diff --git a/src/game/playerbot/PlayerbotMgr.cpp b/src/game/playerbot/PlayerbotMgr.cpp
index 217cc0d..94ee885 100644
--- a/src/game/playerbot/PlayerbotMgr.cpp
+++ b/src/game/playerbot/PlayerbotMgr.cpp
@@ -688,7 +688,7 @@ bool ChatHandler::HandlePlayerbotCommand(char* args)
        SetSentErrorMessage(true);
        return false;
    }
-
+    /*
    uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(guid);
    if (accountId != m_session->GetAccountId())
    {
@@ -696,7 +696,7 @@ bool ChatHandler::HandlePlayerbotCommand(char* args)
        SetSentErrorMessage(true);
        return false;
    }
-
+     */
    // create the playerbot manager if it doesn't already exist
    PlayerbotMgr* mgr = m_session->GetPlayer()->GetPlayerbotMgr();
    if (!mgr)
@@ -748,7 +748,7 @@ bool ChatHandler::HandlePlayerbotCommand(char* args)
            return false;
        }
        CharacterDatabase.DirectPExecute("UPDATE characters SET online = 1 WHERE guid = '%lu'", guid);
-        mgr->AddPlayerBot(guid);
+        mgr->AddPlayerBot(guid, m_session->GetAccountId());
        PSendSysMessage("Bot added successfully.");
    }
    else if (cmdStr == "remove" || cmdStr == "logout")
diff --git a/src/game/playerbot/PlayerbotMgr.h b/src/game/playerbot/PlayerbotMgr.h
index b169023..ab6d504 100644
--- a/src/game/playerbot/PlayerbotMgr.h
+++ b/src/game/playerbot/PlayerbotMgr.h
@@ -29,7 +29,7 @@ class MANGOS_DLL_SPEC PlayerbotMgr
        void HandleMasterIncomingPacket(const WorldPacket& packet);
        void HandleMasterOutgoingPacket(const WorldPacket& packet);

-        void AddPlayerBot(uint64 guid);
+        void AddPlayerBot(uint64 guid, uint32 accountId);
        void LogoutPlayerBot(uint64 guid);
        Player* GetPlayerBot (uint64 guid) const;
        Player* GetMaster() const { return m_master; };

Have fun :D

Link to comment
Share on other sites

  • 2 weeks later...
  • 2 weeks later...

Hi Guys,

KiriX has brought to my attention a long standing issue with stealthed rogue attack and I believe I have found a solution.

Issue:

Both warrior and rogue attacks require an inital mode change, before attacking a target. The warrior attack (Battle stance or Defensive stance) works perfectly, but for some unknown reason rogues (Stealth mode) refuse to approach the target (code loop). Presently the solution to this is to command the rogue to attack the same target again, whilst stealthed. This works fine if the rogue attacks solo, but becomes inpractical if you wish the rogue to attack with the group.

Objective: Find a way for rogues to get (while stealthed) within attack distance of the target, in a single operation

/w botname attack

or as a group attack

/point

Test:

I commented out the stealth mode code in PlayerbotRogueAI.cpp, the attack works fine (unstealthed).

bool PlayerbotRogueAI::DoFirstCombatManeuver(Unit *pTarget)
{
   PlayerbotAI *ai = GetAI();
   Player *m_bot = GetPlayerBot();

/*    if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_INDEX_0) && ai->CastSpell(STEALTH, *m_bot))
   {
       if (ai->GetManager()->m_confDebugWhisper)
           ai->TellMaster("First > Stealth (%d)", STEALTH);

       return false; // m_targetChanged flag
   }
*/
   return false;
}

Solution:

Realistic stealth attack ( rogue waits for an opportunity)

This fix will place the rogue at the limit of the targets attack distance. If the target should move away from the rogue, the bot will wait until the target returns. So sometimes the attack will be immediate or after a short period of time (You might feel that the waiting time drives you :mad: . If so, in the code, replace ATTACK_DISTANCE with a (float) value less than 5.0f. Careful, if this value is too small it may cause the target to detect the bots presence too soon, and thus prevent stealthed attack).

I have checked the code on my server and the rogues do use the appropriate stealth attack spells (cheap shot,ambush,garrote,etc) as a first maneuver and it then switches to normal combat, once detected by the target (activate PlayerbotAI.DebugWhisper in playerbot.conf and check this yourself).

I have introduced an AI update delay (ai->SetIgnoreUpdateTime(10)) to allow the visual stealth effect to display, prior to the attack. The delay time can again be reduced if you feel it's too long.

You can test the code out from the portal repo. I would be grateful for your comments and any suggestion.

Hope this helps

Link to comment
Share on other sites

Rogue bots will now pickpocket ;)

Rogues are unscrupulous characters that will look after their own interests. It seems reasonable then, that a rogue bot, if ordered to attack a target, will take the opportunity lighten the targets purse first.

I have revised the rogue stealth attack to facilitate pickpocketing as commited to portal.

Please ensure that your using the latest code from portal, else it won't work ;) Here is the revised patch for you to try. As always, I would be grateful for your comments so I can improve the code.

Revised Rogue Attack Patch

diff --git a/src/game/playerbot/PlayerbotRogueAI.cpp b/src/game/playerbot/PlayerbotRogueAI.cpp
index e62b09a..e043680 100644
--- a/src/game/playerbot/PlayerbotRogueAI.cpp
+++ b/src/game/playerbot/PlayerbotRogueAI.cpp
@@ -30,6 +30,7 @@ PlayerbotRogueAI::PlayerbotRogueAI(Player* const master, Player* const bot, Play
    DISTRACT                 = ai->initSpell(DISTRACT_1);
    PREPARATION              = ai->initSpell(PREPARATION_1);
    PREMEDITATION            = ai->initSpell(PREMEDITATION_1);
+    PICK_POCKET              = ai->initSpell(PICK_POCKET_1);

    EVISCERATE               = ai->initSpell(EVISCERATE_1);
    KIDNEY_SHOT              = ai->initSpell(KIDNEY_SHOT_1);
@@ -61,21 +62,80 @@ bool PlayerbotRogueAI::DoFirstCombatManeuver(Unit *pTarget)
    PlayerbotAI* ai = GetAI();
    Player * m_bot = GetPlayerBot();

-    if (STEALTH > 0 && !m_bot->HasAura(STEALTH) && ai->CastSpell(STEALTH, *m_bot))
+    if (STEALTH > 0 && !m_bot->HasAura(STEALTH, EFFECT_INDEX_0) && ai->CastSpell(STEALTH, *m_bot))
    {
        if (ai->GetManager()->m_confDebugWhisper)
            ai->TellMaster("First > Stealth (%d)", STEALTH);

-        ai->SetIgnoreUpdateTime(10);
+        ai->SetIgnoreUpdateTime(8);

        return true;
    }
-    else if (STEALTH > 0 && m_bot->HasAura(STEALTH))
+    else if (m_bot->HasAura(STEALTH, EFFECT_INDEX_0))
    {
        float x,y,z;
-        pTarget->GetContactPoint(m_bot, x, y, z, ATTACK_DISTANCE);
+        bool looted = false;
+
+        pTarget->GetContactPoint(m_bot, x, y, z, 4.5f);
        m_bot->Relocate(x, y, z);

+        if (PICK_POCKET > 0 && ai->CastSpell(PICK_POCKET, *pTarget))
+        {
+
+            ObjectGuid markGuid = pTarget->GetObjectGuid();
+            Creature *c = m_bot->GetMap()->GetCreature(markGuid);
+            m_bot->SendLoot(markGuid, LOOT_PICKPOCKETING);
+            Loot *loot = &c->loot;
+            uint32 lootNum = loot->GetMaxSlotInLootFor(m_bot);
+
+            if (ai->GetManager()->m_confDebugWhisper)
+            {
+                std::ostringstream out;
+
+                // calculate how much money bot loots
+        uint32 copper = loot->gold;
+                uint32 gold = uint32(copper / 10000);
+                copper -= (gold * 10000);
+                uint32 silver = uint32(copper / 100);
+                copper -= (silver * 100);
+
+                out << "|r|cff009900" << m_bot->GetName() << " loots: " << "|h|cffffffff[|r|cff00ff00" << gold
+                << "|r|cfffffc00g|r|cff00ff00" << silver
+                << "|r|cffcdcdcds|r|cff00ff00" << copper
+                << "|r|cff993300c"
+                << "|h|cffffffff]";
+        
+                ai->TellMaster(out.str().c_str());
+            }
+    
+            if (loot->gold)
+            {
+                m_bot->ModifyMoney( loot->gold );
+                m_bot->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, loot->gold);
+                loot->gold = 0;
+                loot->NotifyMoneyRemoved();
+            }
+
+            for (uint32 l = 0; l < lootNum; l++)
+            {
+                QuestItem *qitem = 0, *ffaitem = 0, *conditem = 0;
+                LootItem *item = loot->LootItemInSlot(l, m_bot, &qitem, &ffaitem, &conditem);
+                if (!item)
+                    continue;
+
+                ItemPosCountVec dest;
+                if (m_bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK)
+                {
+                    Item* pItem = m_bot->StoreNewItem (dest, item->itemid, true, item->randomPropertyId);
+                    m_bot->SendNewItem(pItem, uint32(item->count), false, false, true);
+                    --loot->unlootedCount;
+                    looted = true;
+                }
+            }
+            // release loot
+            if (looted)
+                m_bot->GetSession()->DoLootRelease(markGuid);
+        }
        return false;
    }
    return false;
diff --git a/src/game/playerbot/PlayerbotRogueAI.h b/src/game/playerbot/PlayerbotRogueAI.h
index b15cc67..a720268 100644
--- a/src/game/playerbot/PlayerbotRogueAI.h
+++ b/src/game/playerbot/PlayerbotRogueAI.h
@@ -88,7 +88,7 @@ private:
    uint32 SINISTER_STRIKE, BACKSTAB, GOUGE, EVASION, SPRINT, KICK, FEINT, SHIV, FAN_OF_KNIVES;

    // SUBTLETY
-    uint32 SHADOWSTEP, STEALTH, VANISH, HEMORRHAGE, BLIND, SHADOW_DANCE, CLOAK_OF_SHADOWS, TRICK_TRADE, CRIPPLING_POISON, DEADLY_POISON, MIND_NUMBING_POISON, GHOSTLY_STRIKE, DISTRACT, PREPARATION, PREMEDITATION;
+    uint32 SHADOWSTEP, STEALTH, VANISH, HEMORRHAGE, BLIND, SHADOW_DANCE, PICK_POCKET, CLOAK_OF_SHADOWS, TRICK_TRADE, CRIPPLING_POISON, DEADLY_POISON, MIND_NUMBING_POISON, GHOSTLY_STRIKE, DISTRACT, PREPARATION, PREMEDITATION;

    // ASSASSINATION
    uint32 EVISCERATE, SLICE_DICE, GARROTE, EXPOSE_ARMOR, AMBUSH, RUPTURE, DISMANTLE, CHEAP_SHOT, KIDNEY_SHOT, MUTILATE, ENVENOM, DEADLY_THROW;

Known Issue:

I am currently testing this patch and It's not working 100%. For some unknown reason it works fine for some rogues but not for others.

Alliance

dwarf rogue bot (level 27) has spells: stealth, pick pocket, ambush, cheap shot, etc

human warlock player (level 26)

This works fine. The rogue casts 'stealth' on itself, pickpockets target and then attacks the target

Horde

undead rogue bot (level 29) has spells: stealth, pick pocket, ambush, cheap shot, etc

orc hunter player (level 30)

The rogue attacks, but does not cast 'stealth' on itself and thus does not pickpocket.

It appears to be a racial issue, perhap a language barrier? I know humans and dwares use a common language, but I'm not sure whether orcs and undead understand one another. If anyone can suggest a solution, I would be very pleased :)

Hope this helps

Link to comment
Share on other sites

Hi, blueboy.

It is always nice to see improvements to Playerbot :) I wish I could help you with your rogue stuff, but time is problem for me now. Anyway today I had couple of hours to fix multiple items trading when linking several items at once. Code is in the portal repo.

PS. I really miss PMs and AJAX here. Mangos crew were spartans to choose this BB.

Link to comment
Share on other sites

More Elegant Solution to Stealthed Rogue Attack. :D

Hi Guys,

I wasn't happy with the patch I posted. Relocate() gave a very unrealistic (too fast, too direct route) movement for the rogue. I prefer to see the rogue stealthly inch it's way to the target. MoveChase() wouldn't work, so I decided to look at MoveFollow(). To my surprise it worked without any problems. I also tidied things up and put the pickpocket code into a separate boolean function. This allowed me to control combat sequence better, if I can explain,

Program Flow

1. cast Stealth on rogue

2. MoveFollow(target, distance, angle) // approach and remain a 'distance' & at an 'angle' from the 'target'

3. if not close enough to the target, updateAI. // cycle attack AI

4. bool PickPocket(target) // I used a false boolean return value to ensure that the PickPocket function is only used once per target. The false value also allows another option (see below) to be considered on the first AI pass.

Stealth spells are available only at certain levels. Stealth (level 1) and Pickpocketing (level 4) are both given early on, but attack spells like Garrote (level 14), Ambush (level 18) and Cheap Shot (level 26) are unavailable to less skilled rogues. I needed to find a way to switch SpellSequence, should these attack spells not be available. I replaced the (m_confDebugWhisper) string (out << " > None!") with a call to a function that forceably removes of stealth aura. Once exposed, the target will then attack the rogue and the combat sequence will switch to either RogueThreat, RogueSpellPreventing, RogueCombat or default.

 
   switch (SpellSequence)
   {

     case RogueStealth:
       out << "Case Stealth";
           if (PICK_POCKET > 0 && ai->CastSpell(PICK_POCKET, *pTarget) && ai->PickPocket(pTarget))
               out << "First > Pick Pocket"; // Should never display, as PickPocket will always return false
       else if (PREMEDITATION > 0 && ai->CastSpell(PREMEDITATION, *pTarget))
               out << " > Premeditation";
           else if (AMBUSH > 0 && ai->GetEnergyAmount() >= 60 && ai->CastSpell(AMBUSH, *pTarget))
               out << " > Ambush";
           else if (CHEAP_SHOT > 0 && !pTarget->HasAura(CHEAP_SHOT, EFFECT_INDEX_0) && ai->GetEnergyAmount() >= 60 && ai->CastSpell(CHEAP_SHOT, *pTarget))
               out << " > Cheap Shot";
           else if (GARROTE > 0 && ai->GetEnergyAmount() >= 50 && ai->CastSpell(GARROTE, *pTarget))
               out << " > Garrote";
           else
               // out << " > None!";             
               m_bot->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
       break;

     case RogueThreat:

        ...

     case RogueSpellPreventing:

        ...

     case RogueCombat:

        ...      

     default: 

        ...

}

The patch is too large to post, so I will update the code on portal shortly, for you to try. I have tested this and it appears to work fine.

Warning! the pickpocket code could make your rogue very rich :) Once again any suggestions or comments would be most welcome.

Hope this helps

Link to comment
Share on other sites

There appears to be a problem with the bots mounting up after the master mounts. The problem code in PlayerBotAI.cpp is:

        case SMSG_FORCE_RUN_SPEED_CHANGE:
        {
            WorldPacket p(packet);
-            uint64 guid = extractGuid(p);
+            uint64 guid = p.readPackGUID();
            if (guid != GetMaster()->GetGUID())
                return;
            if (GetMaster()->IsMounted() && !m_bot->IsMounted())

There might be some other instances of where a packed GUID is sent in the packet, but is not correctly read in these packets. I think it was working before.

Link to comment
Share on other sites

There appears to be a problem with the bots mounting up after the master mounts. The problem code in PlayerBotAI.cpp is:

        case SMSG_FORCE_RUN_SPEED_CHANGE:
        {
            WorldPacket p(packet);
-            uint64 guid = extractGuid(p);
+            uint64 guid = p.readPackGUID();
            if (guid != GetMaster()->GetGUID())
                return;
            if (GetMaster()->IsMounted() && !m_bot->IsMounted())

There might be some other instances of where a packed GUID is sent in the packet, but is not correctly read in these packets. I think it was working before.

Hi,

kyle1: I believe we should drop extractGuid() in favor of packet method.

Yes I agree. I have checked the code and 'extractGuid' is used several times in PlayerbotAI.cpp only. I have changed all instances with p.readPackGUID and it works a treat. I will update the portal repo shortly.

Thanks Bthallid for that and don't be a stranger ;)

Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. Privacy Policy Terms of Use