From e87efbaf6e6e5e692381d80c0df0128f87627ce0 Mon Sep 17 00:00:00 2001
From: mikx <themike0785@gmail.com>
Date: Mon, 24 Mar 2025 23:53:45 -0400
Subject: [PATCH] mxwow modules

---
 .../mod-mxwow-bosskill/src/mxwow_bosskill.cpp |   2 +-
 modules/mod-mxwow-bounty/src/mxwow_bounty.cpp |  15 +-
 .../src/mxwow-honorable.cpp                   |  49 +-
 .../mod-mxwow-levelup/src/mxwow_levelup.cpp   |   2 +-
 .../src/mxwow_portalmaster.cpp                |   3 +-
 .../mod-mxwow-servant/src/mxwow_servant.cpp   |  40 +-
 .../src/mxwow-toonmaster.cpp                  |  12 +-
 modules/mod-mxwow-webhelper/.editorconfig     |   8 +
 modules/mod-mxwow-webhelper/.gitattributes    | 105 +++
 .../.github/workflows/core-build.yml          |  12 +
 modules/mod-mxwow-webhelper/.gitignore        |  48 ++
 modules/mod-mxwow-webhelper/LICENSE           | 661 +++++++++++++++
 modules/mod-mxwow-webhelper/README.md         |  11 +
 modules/mod-mxwow-webhelper/include.sh        |   0
 .../mod-mxwow-webhelper/sql/world/world.sql   |   3 +
 .../src/mxwow-webhelper.cpp                   |  57 ++
 .../src/mxwow-webhelper_loader.cpp            |  12 +
 .../mod-no-hearthstone-cooldown/.editorconfig |   8 +
 .../.git_commit_template.txt                  |  45 +
 .../.gitattributes                            | 105 +++
 .../.github/workflows/core-build.yml          |  10 +
 .../mod-no-hearthstone-cooldown/.gitignore    |  48 ++
 modules/mod-no-hearthstone-cooldown/LICENSE   | 661 +++++++++++++++
 modules/mod-no-hearthstone-cooldown/README.md |  44 +
 .../conf/conf.sh.dist                         |  32 +
 .../mod_no_hearthstone_cooldown.conf.dist     |  24 +
 .../mod-no-hearthstone-cooldown/include.sh    |  10 +
 .../setup_git_commit_template.sh              |   4 +
 .../src/NHC_loader.cpp                        |  12 +
 .../src/NoHearthstoneCooldown.cpp             |  65 ++
 .../updates/db_playerbots/2025_03_03_00.sql   |  18 +-
 .../mod-playerbots/src/LootObjectStack.cpp    |   2 +-
 modules/mod-playerbots/src/PlayerbotAI.cpp    | 219 ++++-
 modules/mod-playerbots/src/PlayerbotAI.h      |  11 +-
 modules/mod-playerbots/src/PlayerbotMgr.cpp   |  71 +-
 .../mod-playerbots/src/RandomPlayerbotMgr.cpp | 255 +++---
 .../mod-playerbots/src/RandomPlayerbotMgr.h   |   7 +
 .../src/factory/PlayerbotFactory.cpp          |  35 +-
 .../strategy/actions/AcceptQuestAction.cpp    |   2 +-
 .../src/strategy/actions/ActionContext.h      |   4 +
 .../src/strategy/actions/ChatActionContext.h  |   8 +
 .../strategy/actions/ChatShortcutActions.cpp  |  43 +-
 .../strategy/actions/ChatShortcutActions.h    |  26 +-
 .../strategy/actions/ChooseTargetActions.cpp  |  19 +-
 .../src/strategy/actions/DropQuestAction.cpp  |   1 +
 .../src/strategy/actions/OpenItemAction.cpp   |  53 +-
 .../src/strategy/actions/OpenItemAction.h     |   3 -
 .../src/strategy/actions/PositionAction.cpp   |  22 +
 .../src/strategy/actions/PositionAction.h     |   7 +
 .../src/strategy/actions/QuestAction.cpp      | 126 ++-
 .../src/strategy/actions/QuestAction.h        |   7 +
 .../strategy/actions/ReachTargetActions.cpp   |  12 +
 .../src/strategy/actions/SeeSpellAction.cpp   |  10 +
 .../src/strategy/actions/StayActions.cpp      |  12 +
 .../actions/TalkToQuestGiverAction.cpp        |   1 -
 .../strategy/actions/TradeStatusAction.cpp    |  11 +-
 .../actions/TradeStatusExtendedAction.cpp     |  84 ++
 .../actions/TradeStatusExtendedAction.h       |  17 +
 .../src/strategy/actions/UnlockItemAction.cpp |  38 +
 .../src/strategy/actions/UnlockItemAction.h   |  19 +
 .../actions/UnlockTradedItemAction.cpp        |  98 +++
 .../strategy/actions/UnlockTradedItemAction.h |  20 +
 .../actions/UseMeetingStoneAction.cpp         |  11 +
 .../actions/WorldPacketActionContext.h        |   7 +-
 .../generic/ChatCommandHandlerStrategy.cpp    |   7 +
 .../src/strategy/generic/CombatStrategy.cpp   |   3 +-
 .../src/strategy/generic/GrindingStrategy.cpp |  13 +-
 .../generic/LootNonCombatStrategy.cpp         |   4 +-
 .../src/strategy/generic/StayStrategy.cpp     |   7 +
 .../src/strategy/generic/StayStrategy.h       |   5 +-
 .../generic/WorldPacketHandlerStrategy.cpp    |   9 +-
 .../src/strategy/rogue/RogueActions.cpp       |  10 +
 .../src/strategy/rogue/RogueActions.h         |   2 +-
 .../src/strategy/rpg/NewRpgAction.cpp         | 596 +++++++------
 .../src/strategy/rpg/NewRpgAction.h           |  89 +-
 .../src/strategy/rpg/NewRpgBaseAction.cpp     | 801 ++++++++++++++++++
 .../src/strategy/rpg/NewRpgBaseAction.h       |  58 ++
 .../src/strategy/rpg/NewRpgInfo.cpp           | 119 +++
 .../src/strategy/rpg/NewRpgInfo.h             | 136 +++
 .../src/strategy/rpg/NewRpgStrategy.cpp       |  16 +-
 .../src/strategy/rpg/NewRpgStrategy.h         |  81 +-
 .../src/strategy/rpg/NewRpgTriggers.h         |   2 +-
 .../strategy/triggers/ChatTriggerContext.h    |   6 +
 .../src/strategy/triggers/GenericTriggers.cpp |  26 +-
 .../src/strategy/triggers/GenericTriggers.h   |   8 +
 .../src/strategy/triggers/LootTriggers.cpp    |   4 +-
 .../src/strategy/triggers/TriggerContext.h    |  12 +-
 .../triggers/WorldPacketTriggerContext.h      |   2 +
 .../strategy/values/AvailableLootValue.cpp    |   2 +-
 .../src/strategy/values/GrindTargetValue.cpp  |  74 +-
 .../src/strategy/values/ItemForSpellValue.cpp |   3 +
 .../src/strategy/values/LastMovementValue.h   |   1 +
 .../strategy/values/NearestGameObjects.cpp    |  18 -
 .../src/strategy/values/NearestGameObjects.h  |  21 +-
 .../src/strategy/values/PositionValue.cpp     |  22 +
 .../src/strategy/values/PositionValue.h       |  14 +
 .../values/PossibleRpgTargetsValue.cpp        | 125 +++
 .../strategy/values/PossibleRpgTargetsValue.h |  34 +
 .../src/strategy/values/ValueContext.h        |   6 +
 99 files changed, 4909 insertions(+), 814 deletions(-)
 create mode 100644 modules/mod-mxwow-webhelper/.editorconfig
 create mode 100644 modules/mod-mxwow-webhelper/.gitattributes
 create mode 100644 modules/mod-mxwow-webhelper/.github/workflows/core-build.yml
 create mode 100644 modules/mod-mxwow-webhelper/.gitignore
 create mode 100644 modules/mod-mxwow-webhelper/LICENSE
 create mode 100644 modules/mod-mxwow-webhelper/README.md
 create mode 100644 modules/mod-mxwow-webhelper/include.sh
 create mode 100644 modules/mod-mxwow-webhelper/sql/world/world.sql
 create mode 100644 modules/mod-mxwow-webhelper/src/mxwow-webhelper.cpp
 create mode 100644 modules/mod-mxwow-webhelper/src/mxwow-webhelper_loader.cpp
 create mode 100644 modules/mod-no-hearthstone-cooldown/.editorconfig
 create mode 100644 modules/mod-no-hearthstone-cooldown/.git_commit_template.txt
 create mode 100644 modules/mod-no-hearthstone-cooldown/.gitattributes
 create mode 100644 modules/mod-no-hearthstone-cooldown/.github/workflows/core-build.yml
 create mode 100644 modules/mod-no-hearthstone-cooldown/.gitignore
 create mode 100644 modules/mod-no-hearthstone-cooldown/LICENSE
 create mode 100644 modules/mod-no-hearthstone-cooldown/README.md
 create mode 100644 modules/mod-no-hearthstone-cooldown/conf/conf.sh.dist
 create mode 100644 modules/mod-no-hearthstone-cooldown/conf/mod_no_hearthstone_cooldown.conf.dist
 create mode 100644 modules/mod-no-hearthstone-cooldown/include.sh
 create mode 100644 modules/mod-no-hearthstone-cooldown/setup_git_commit_template.sh
 create mode 100644 modules/mod-no-hearthstone-cooldown/src/NHC_loader.cpp
 create mode 100644 modules/mod-no-hearthstone-cooldown/src/NoHearthstoneCooldown.cpp
 create mode 100644 modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.cpp
 create mode 100644 modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.h
 create mode 100644 modules/mod-playerbots/src/strategy/actions/UnlockItemAction.cpp
 create mode 100644 modules/mod-playerbots/src/strategy/actions/UnlockItemAction.h
 create mode 100644 modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.cpp
 create mode 100644 modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.h
 create mode 100644 modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.cpp
 create mode 100644 modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.h
 create mode 100644 modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.cpp
 create mode 100644 modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.h

diff --git a/modules/mod-mxwow-bosskill/src/mxwow_bosskill.cpp b/modules/mod-mxwow-bosskill/src/mxwow_bosskill.cpp
index 85d8fc3..b3fe628 100644
--- a/modules/mod-mxwow-bosskill/src/mxwow_bosskill.cpp
+++ b/modules/mod-mxwow-bosskill/src/mxwow_bosskill.cpp
@@ -18,7 +18,7 @@ public:
 
     void OnPlayerLogin(Player* player) override
     {
-        ChatHandler(player->GetSession()).SendSysMessage("This server is running the |cff4CFF00MxW Boss Kill |rmodule.");
+        
     }
 
     void OnPlayerCreatureKill(Player* player, Creature* boss) {
diff --git a/modules/mod-mxwow-bounty/src/mxwow_bounty.cpp b/modules/mod-mxwow-bounty/src/mxwow_bounty.cpp
index 435d2b7..ff3be49 100644
--- a/modules/mod-mxwow-bounty/src/mxwow_bounty.cpp
+++ b/modules/mod-mxwow-bounty/src/mxwow_bounty.cpp
@@ -18,8 +18,7 @@ mxwow_bounty() : PlayerScript("mxwow_bounty") { }
 
     void OnPlayerLogin(Player* player) override
     {
-        if (sConfigMgr->GetOption<bool>("MxWoW_Bounty.Enabled", true)) { return; }
-        ChatHandler(player->GetSession()).SendSysMessage("This server is running the |cff4CFF00MxW Bounty |rmodule.");
+        
     }
 
     void OnPlayerCreatureKill(Player* player, Creature* creature) {
@@ -75,10 +74,9 @@ mxwow_bounty() : PlayerScript("mxwow_bounty") { }
             }
             if (formatedAmountGold == 0 && formatedAmountSilver == 0 && formatedAmountCopper > 0) {
                 ss << "|cffabeeff[MxW] Vous obtenez une récompense de |cffb87333" << formatedAmountCopper << "c |cffabeeffpour avoir tué " << cName << " (" << cLevel << ").";
-            }
-            ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str());
-
+            }           
             player->ModifyMoney(+int32(giveAmount));
+            ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str());
         }
         else {
             Group* group = player->GetGroup();
@@ -94,12 +92,11 @@ mxwow_bounty() : PlayerScript("mxwow_bounty") { }
                 }
                 if (formatedAmountGold == 0 && formatedAmountSilver == 0 && formatedAmountCopper > 0) {
                     ss << "|cffabeeff[MxW] Vous obtenez une récompense de |cffb87333" << formatedAmountCopper << "c |cffabeeffpour avoir tué " << cName << " (" << cLevel << ").";
-                }
-                ChatHandler(p->GetSession()).PSendSysMessage(ss.str().c_str());
-
+                }                
                 p->ModifyMoney(+int32(giveAmount));
+                ChatHandler(p->GetSession()).PSendSysMessage(ss.str().c_str());
             }
-        }                   
+        }        
     }
 };
 
diff --git a/modules/mod-mxwow-honorable/src/mxwow-honorable.cpp b/modules/mod-mxwow-honorable/src/mxwow-honorable.cpp
index ec34c24..d605727 100644
--- a/modules/mod-mxwow-honorable/src/mxwow-honorable.cpp
+++ b/modules/mod-mxwow-honorable/src/mxwow-honorable.cpp
@@ -19,8 +19,7 @@ mxwow_honorable() : PlayerScript("mxwow_honorable") { }
 
     void OnPlayerLogin(Player* player) override
     {
-        if (!sConfigMgr->GetOption<bool>("MxWoW_Honorable.Enabled", true)) { return; }
-        ChatHandler(player->GetSession()).SendSysMessage("This server is running the |cff4CFF00MxW Honorable |rmodule.");
+        
     }
 
     void OnPlayerPVPKill(Player* player, Player* victim) {
@@ -41,7 +40,7 @@ mxwow_honorable() : PlayerScript("mxwow_honorable") { }
         std::string pName = player->GetName();
         uint32 pLevel = player->GetLevel();
         uint32 piLevel = player->GetAverageItemLevelForDF();
-        float pLife = player->GetHealthPct();
+        float pLife = round(player->GetHealthPct());
         bool pGrouped = player->GetGroup();
 
         std::string vName = victim->GetName();
@@ -56,6 +55,7 @@ mxwow_honorable() : PlayerScript("mxwow_honorable") { }
         else {
             ss << "|cffabeeff[MxW][|cfffc0303PvP|cffabeeff][|cfffc0303" << mapName << "|cffabeeff] [" << GetPlayerFactionByRace(player) << "|cffabeeff][" << pLevel << "][" << GetPlayerColor(player) << pName << "|cffabeeff][iL" << piLevel << "][" << pLife << "%] |cfffc0303a tué|cffabeeff [" << GetPlayerFactionByRace(victim) << "|cffabeeff][" << vLevel << "][" << GetPlayerColor(victim) << vName << "|cffabeeff][iL" << viLevel << "].";
             player->ModifyMoney(+int32(base));
+            ManageHonorableKill(player, victim);
         }
               
         sWorldSessionMgr->SendServerMessage(SERVER_MSG_STRING, ss.str().c_str());       
@@ -127,6 +127,43 @@ mxwow_honorable() : PlayerScript("mxwow_honorable") { }
         }
     }
 
+    void ManageHonorableKill(Player* player, Player* victim) {
+        uint32 base = sConfigMgr->GetOption<int>("MxWoW_Honorable.Kill.Copper", true);
+        uint32 killQty;
+        std::ostringstream ss;
+        std::ostringstream ssV;
+        uint32 kId = player->GetGUID().GetRawValue();
+        std::string kName = player->GetName();
+        uint32 kGuildId = 0;
+        std::string kGuildName = "";
+        uint32 vId = victim->GetGUID().GetRawValue();
+        std::string vName = victim->GetName();
+        uint32 vGuildId = 0;
+        std::string vGuildName = "";
+        uint32 kLevel = player->GetLevel();
+        uint32 vLevel = victim->GetLevel();
+
+        QueryResult queryPlayerHonorable = LoginDatabase.Query("SELECT * FROM mxw_honorable_kill WHERE kguid = {} AND vguid = {}", kId, vId);
+        if (queryPlayerHonorable) {
+            killQty = (*queryPlayerHonorable)[4].Get<int>();
+            killQty++;
+            if (player->GetGuild()) { kGuildId = player->GetGuildId(); kGuildName = player->GetGuildName(); }
+            if (victim->GetGuild()) { vGuildId = victim->GetGuildId(); vGuildName = victim->GetGuildName(); }
+            LoginDatabase.Execute("UPDATE mxw_honorable_kill SET kguildid='{}',vguildid='{}',killcount='{}',last_kill_klevel='{}',last_kill_vlevel='{}' WHERE kguid='{}' AND vguid='{}'", kGuildId, vGuildId, killQty, kLevel, vLevel, kId, vId);
+            ss << "|cffabeeff[MxW][|cfffc0303PvP|cffabeeff] Vous avez tué " << GetPlayerColor(victim) << vName << " |cffabeeffde façon honorable à |cfffc0303" << killQty << " |cffabeeffreprises. Ça vous rapporte "<< FormatMoney(base) << "g.";
+            ssV << "|cffabeeff[MxW][|cfffc0303PvP|cffabeeff] Vous avez été tué |cfffc0303" << killQty << " |cffabeefffois par " << GetPlayerColor(player) << vName << "|cffabeeffde.";
+        }
+        else {
+            if(player->GetGuild()){ kGuildId = player->GetGuildId(); kGuildName = player->GetGuildName(); }
+            if (victim->GetGuild()) { vGuildId = victim->GetGuildId(); vGuildName = victim->GetGuildName(); }
+            LoginDatabase.Execute("INSERT INTO mxw_honorable_kill (kguid, kguildid, vguid, vguildid, killcount, last_kill_klevel, last_kill_vlevel) VALUES ({}, {}, {}, {}, {}, {}, {})", kId, kGuildId, vId, vGuildId, 1, kLevel, vLevel);
+            ss << "|cffabeeff[MxW][|cfffc0303PvP|cffabeeff] Vous avez tué " << GetPlayerColor(victim) << vName << " |cffabeeffde façon honorable à |cfffc03031 |cffabeeffreprise. Ça vous rapporte " << FormatMoney(base) << "g.";
+            ssV << "|cffabeeff[MxW][|cfffc0303PvP|cffabeeff] Vous avez été tué |cfffc03031 |cffabeefffois par " << GetPlayerColor(player) << vName << "|cffabeeffde.";
+        }
+        ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str());
+        ChatHandler(victim->GetSession()).PSendSysMessage(ssV.str().c_str());
+    }
+
     void ManageDishonorableKill(Player* player, const Player* victim)
     {
         uint32 killQty;
@@ -136,18 +173,18 @@ mxwow_honorable() : PlayerScript("mxwow_honorable") { }
         uint32 vId = victim->GetGUID().GetRawValue();
         uint64 kCopper = GetCharCopper(kId);
         uint32 loss;
-        QueryResult queryPlayerDishonorable = LoginDatabase.Query("SELECT * FROM mxw_dishonorable_penalty WHERE kguid = {} AND vguid = {}", kId, vId);
+        QueryResult queryPlayerDishonorable = LoginDatabase.Query("SELECT * FROM mxw_dishonorable_kill WHERE kguid = {} AND vguid = {}", kId, vId);
         if (queryPlayerDishonorable)
         {
             killQty = (*queryPlayerDishonorable)[2].Get<int>();
             killQty++;
             loss = (kCopper * killQty) / 100;
-            LoginDatabase.Execute("UPDATE mxw_dishonorable_penalty SET killcount='{}' WHERE kguid='{}' AND vguid='{}'", killQty, kId, vId);
+            LoginDatabase.Execute("UPDATE mxw_dishonorable_kill SET killcount='{}' WHERE kguid='{}' AND vguid='{}'", killQty, kId, vId);
             ss << "|cffabeeff[MxW] Vous avez tué "<< GetPlayerColor(victim) << vName << " |cffabeeffde façon déshonorable à |cfffc0303"<< killQty <<" |cffabeeffreprises. Vous subissez une perte de " << FormatMoney(loss) << "g";           
         }
         else {
             loss = (kCopper * 1) / 100;
-            LoginDatabase.Execute("INSERT INTO mxw_dishonorable_penalty (kguid, vguid, killcount) VALUES ({}, {}, {})", kId, vId, 1);
+            LoginDatabase.Execute("INSERT INTO mxw_dishonorable_kill (kguid, vguid, killcount) VALUES ({}, {}, {})", kId, vId, 1);
             ss << "|cffabeeff[MxW] Vous avez tué " << GetPlayerColor(victim) << vName << " |cffabeeffde façon déshonorable à |cfffc03031 |cffabeeffreprise. Vous subissez une perte de " << FormatMoney(loss) << "g";           
         }
         player->ModifyMoney(-int32(loss));
diff --git a/modules/mod-mxwow-levelup/src/mxwow_levelup.cpp b/modules/mod-mxwow-levelup/src/mxwow_levelup.cpp
index 7b2cb17..e61adf1 100644
--- a/modules/mod-mxwow-levelup/src/mxwow_levelup.cpp
+++ b/modules/mod-mxwow-levelup/src/mxwow_levelup.cpp
@@ -18,7 +18,7 @@ public:
 
     void OnPlayerLogin(Player* player) override
     {
-        ChatHandler(player->GetSession()).SendSysMessage("This server is running the |cff4CFF00MxW Level Up |rmodule.");
+        
     }
 
     void OnPlayerLevelChanged(Player * player, uint8 oldLevel)
diff --git a/modules/mod-mxwow-portalmaster/src/mxwow_portalmaster.cpp b/modules/mod-mxwow-portalmaster/src/mxwow_portalmaster.cpp
index 173bbbc..086ae6a 100644
--- a/modules/mod-mxwow-portalmaster/src/mxwow_portalmaster.cpp
+++ b/modules/mod-mxwow-portalmaster/src/mxwow_portalmaster.cpp
@@ -20,8 +20,7 @@ class mxwow_portalmasterAnnounce : public PlayerScript
     mxwow_portalmasterAnnounce() : PlayerScript("mxwow_portalmasterAnnounce") {}
     void OnPlayerLogin(Player* player) override
     {
-        // Announce Module
-        ChatHandler(player->GetSession()).SendSysMessage("This server is running the |cff4CFF00MxW Portal Master|r module.");
+        
     }
 };
 
diff --git a/modules/mod-mxwow-servant/src/mxwow_servant.cpp b/modules/mod-mxwow-servant/src/mxwow_servant.cpp
index e880569..245ca13 100644
--- a/modules/mod-mxwow-servant/src/mxwow_servant.cpp
+++ b/modules/mod-mxwow-servant/src/mxwow_servant.cpp
@@ -30,7 +30,7 @@ public:
 
     void OnPlayerLogin(Player* player) override
     {
-        ChatHandler(player->GetSession()).SendSysMessage("This server is running the |cff4CFF00MxW Servant |rmodule.");
+        
     }       
 };
 
@@ -47,14 +47,15 @@ public:
 
         AddGossipItemFor(player, 10, "|TInterface/Icons/inv_hammer_08:30:30:-18|t Réparer (Tout)", GOSSIP_SENDER_MAIN, 1);
         AddGossipItemFor(player, 10, "|TInterface/Icons/inv_misc_enggizmos_18:30:30:-18|t Banque", GOSSIP_SENDER_MAIN, 2);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/INV_Letter_11:30:30:-18|t Courrier", GOSSIP_SENDER_MAIN, 3);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/spell_shadow_teleport:30:30:-18|t Buff", GOSSIP_SENDER_MAIN, 4);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/Achievement_BG_AB_defendflags:30:30:-18|t Mettre fin au combat", GOSSIP_SENDER_MAIN, 5);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/inv_misc_bag_11:30:30:-18|t Marchant", GOSSIP_SENDER_MAIN, 6);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/inv_misc_coin_17:30:30:-18|t Enchère", GOSSIP_SENDER_MAIN, 7);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/Ability_paladin_beaconoflight:30:30:-18|t Transmo", GOSSIP_SENDER_MAIN, 8);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/inv_crate_01:30:30:-18|t Matériaux", GOSSIP_SENDER_MAIN, 9);
-        AddGossipItemFor(player, 10, "|TInterface/Icons/spell_nature_polymorph:30:30:-18|t Étable", GOSSIP_SENDER_MAIN, 10);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/inv_misc_enggizmos_18:30:30:-18|t Banque de Guilde", GOSSIP_SENDER_MAIN, 3);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/INV_Letter_11:30:30:-18|t Courrier", GOSSIP_SENDER_MAIN, 4);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/spell_shadow_teleport:30:30:-18|t Buff", GOSSIP_SENDER_MAIN, 5);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/Achievement_BG_AB_defendflags:30:30:-18|t Mettre fin au combat", GOSSIP_SENDER_MAIN, 6);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/inv_misc_bag_11:30:30:-18|t Marchant", GOSSIP_SENDER_MAIN, 7);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/inv_misc_coin_17:30:30:-18|t Enchère", GOSSIP_SENDER_MAIN, 8);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/Ability_paladin_beaconoflight:30:30:-18|t Transmo", GOSSIP_SENDER_MAIN, 9);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/inv_crate_01:30:30:-18|t Matériaux", GOSSIP_SENDER_MAIN, 10);
+        AddGossipItemFor(player, 10, "|TInterface/Icons/spell_nature_polymorph:30:30:-18|t Étable", GOSSIP_SENDER_MAIN, 11);
 
         SendGossipMenuFor(player, 20000000, creature->GetGUID());
 
@@ -80,9 +81,14 @@ public:
             break;
         case 3:
             CloseGossipMenuFor(player);
-            player->GetSession()->SendShowMailBox(player->GetGUID());
+            SummonTempNPC(player, 9000003, 300000);
+            player->CastSpell(player, 31726);
             break;
         case 4:
+            CloseGossipMenuFor(player);
+            player->GetSession()->SendShowMailBox(player->GetGUID());
+            break;
+        case 5:
             CloseGossipMenuFor(player);
             if (player->GetMap()->IsDungeon() || player->GetMap()->IsRaid()) {
                 if (pLevel < 10)
@@ -173,33 +179,33 @@ public:
             }
             player->CastSpell(player, 31726);
             break;
-        case 5:
+        case 6:
             CloseGossipMenuFor(player);
             player->CombatStop();
             player->CastSpell(player, 31726);
             break;
-        case 6:
+        case 7:
             CloseGossipMenuFor(player);
             player->GetSession()->SendListInventory(creature->GetGUID());
             player->CastSpell(player, 31726);
             break;
-        case 7:
+        case 8:
             CloseGossipMenuFor(player);
             //SummonTempNPC(player, 8670, 300000);
             player->GetSession()->SendAuctionHello(creature->GetGUID(), creature);
             player->CastSpell(player, 31726);
             break;
-        case 8:
+        case 9:
             CloseGossipMenuFor(player);
             SummonTempNPC(player, 190010, 300000);
             player->CastSpell(player, 31726);
             break;
-        case 9:
+        case 10:
             CloseGossipMenuFor(player);
-            SummonTempNPC(player, 190011, 300000);
+            SummonTempNPC(player, 290011, 300000);
             player->CastSpell(player, 31726);
             break;
-        case 10:
+        case 11:
             CloseGossipMenuFor(player);
             player->GetSession()->SendStablePet(creature->GetGUID());
             player->CastSpell(player, 31726);
diff --git a/modules/mod-mxwow-toonmaster/src/mxwow-toonmaster.cpp b/modules/mod-mxwow-toonmaster/src/mxwow-toonmaster.cpp
index e394916..6a181a2 100644
--- a/modules/mod-mxwow-toonmaster/src/mxwow-toonmaster.cpp
+++ b/modules/mod-mxwow-toonmaster/src/mxwow-toonmaster.cpp
@@ -19,13 +19,7 @@ class mxwow_toonmasterAnnounce : public PlayerScript
     mxwow_toonmasterAnnounce() : PlayerScript("mxwow_toonmasterAnnounce") {}
     void OnPlayerLogin(Player* player) override
     {
-        // Announce Module
-        if (sConfigMgr->GetOption<bool>("MxWoW_ToonMaster.Enabled", true))
-        {
-            std::ostringstream ss;
-            ss << "This server is running the |cff4CFF00MxW Toon Master|r module.";
-            ChatHandler(player->GetSession()).SendSysMessage(ss.str().c_str());
-        }
+        
     }
 };
 
@@ -82,8 +76,8 @@ public:
                         amount = amount * expMulti;
                         if (sConfigMgr->GetOption<bool>("MxWoW_ToonMaster.Verbose", true) && plevel < 80)
                         {
-                            ss << "|cffabeeff[MxW][ToonMaster][%ixNiv.80] Bonus: EXPx%i";
-                            ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str(), cMPL, expMulti);
+                            ss << "|cffabeeff[MxW][ToonMaster]["<< cMPL <<"xNiv.80] Bonus: EXPx"<<expMulti;
+                            ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str());
                         }
                     }
                 }               
diff --git a/modules/mod-mxwow-webhelper/.editorconfig b/modules/mod-mxwow-webhelper/.editorconfig
new file mode 100644
index 0000000..eb64e2f
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/.editorconfig
@@ -0,0 +1,8 @@
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+tab_width = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+max_line_length = 80
diff --git a/modules/mod-mxwow-webhelper/.gitattributes b/modules/mod-mxwow-webhelper/.gitattributes
new file mode 100644
index 0000000..7ef9001
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/.gitattributes
@@ -0,0 +1,105 @@
+## AUTO-DETECT
+##   Handle line endings automatically for files detected as
+##   text and leave all files detected as binary untouched.
+##   This will handle all files NOT defined below.
+*   text=auto eol=lf
+
+# Text
+*.conf text
+*.conf.dist text
+*.cmake text
+
+## Scripts
+*.sh text
+*.fish text
+*.lua text
+
+## SQL
+*.sql text
+
+## C++
+*.c text
+*.cc text
+*.cxx text
+*.cpp text
+*.c++ text
+*.hpp text
+*.h text
+*.h++ text
+*.hh text
+
+
+## For documentation
+
+# Documents
+*.doc	 diff=astextplain
+*.DOC	 diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot  diff=astextplain
+*.DOT  diff=astextplain
+*.pdf  diff=astextplain
+*.PDF	 diff=astextplain
+*.rtf	 diff=astextplain
+*.RTF	 diff=astextplain
+
+## DOCUMENTATION
+*.markdown   text
+*.md         text
+*.mdwn       text
+*.mdown      text
+*.mkd        text
+*.mkdn       text
+*.mdtxt      text
+*.mdtext     text
+*.txt        text
+AUTHORS      text
+CHANGELOG    text
+CHANGES      text
+CONTRIBUTING text
+COPYING      text
+copyright    text
+*COPYRIGHT*  text
+INSTALL      text
+license      text
+LICENSE      text
+NEWS         text
+readme       text
+*README*     text
+TODO         text
+
+## GRAPHICS
+*.ai   binary
+*.bmp  binary
+*.eps  binary
+*.gif  binary
+*.ico  binary
+*.jng  binary
+*.jp2  binary
+*.jpg  binary
+*.jpeg binary
+*.jpx  binary
+*.jxr  binary
+*.pdf  binary
+*.png  binary
+*.psb  binary
+*.psd  binary
+*.svg  text
+*.svgz binary
+*.tif  binary
+*.tiff binary
+*.wbmp binary
+*.webp binary
+
+
+## ARCHIVES
+*.7z  binary
+*.gz  binary
+*.jar binary
+*.rar binary
+*.tar binary
+*.zip binary
+
+## EXECUTABLES
+*.exe binary
+*.pyc binary
diff --git a/modules/mod-mxwow-webhelper/.github/workflows/core-build.yml b/modules/mod-mxwow-webhelper/.github/workflows/core-build.yml
new file mode 100644
index 0000000..921c9eb
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/.github/workflows/core-build.yml
@@ -0,0 +1,12 @@
+name: core-build
+on:
+  push:
+    branches:
+      - 'master'
+  pull_request:
+
+jobs:
+  build:
+    uses: azerothcore/reusable-workflows/.github/workflows/core_build_modules.yml@main
+    with:
+      module_repo: ${{ github.event.repository.name }}
diff --git a/modules/mod-mxwow-webhelper/.gitignore b/modules/mod-mxwow-webhelper/.gitignore
new file mode 100644
index 0000000..c6e1299
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/.gitignore
@@ -0,0 +1,48 @@
+!.gitignore
+
+#
+#Generic
+#
+
+.directory
+.mailmap
+*.orig
+*.rej
+*.*~
+.hg/
+*.kdev*
+.DS_Store
+CMakeLists.txt.user
+*.bak
+*.patch
+*.diff
+*.REMOTE.*
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+
+#
+# IDE & other softwares
+#
+/.settings/
+/.externalToolBuilders/*
+# exclude in all levels
+nbproject/
+.sync.ffs_db
+*.kate-swp
+
+#
+# Eclipse
+#
+*.pydevproject
+.metadata
+.gradle
+tmp/
+*.tmp
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.project
+.cproject
diff --git a/modules/mod-mxwow-webhelper/LICENSE b/modules/mod-mxwow-webhelper/LICENSE
new file mode 100644
index 0000000..dbbe355
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/modules/mod-mxwow-webhelper/README.md b/modules/mod-mxwow-webhelper/README.md
new file mode 100644
index 0000000..df18060
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/README.md
@@ -0,0 +1,11 @@
+# ![logo](https://raw.githubusercontent.com/azerothcore/azerothcore.github.io/master/images/logo-github.png) AzerothCore
+## MxWoW - Portal Master
+(https://mxgit.ovh/MxWoW/mod_mxwow_portalmaster)
+
+
+This is a module for [AzerothCore](http://www.azerothcore.org)
+
+## Dev(s)
+- mikx
+## Features
+- Cast portals to different key location.
\ No newline at end of file
diff --git a/modules/mod-mxwow-webhelper/include.sh b/modules/mod-mxwow-webhelper/include.sh
new file mode 100644
index 0000000..e69de29
diff --git a/modules/mod-mxwow-webhelper/sql/world/world.sql b/modules/mod-mxwow-webhelper/sql/world/world.sql
new file mode 100644
index 0000000..1d37b60
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/sql/world/world.sql
@@ -0,0 +1,3 @@
+DELETE FROM `item_template` WHERE (`entry` = 701001);
+INSERT INTO `item_template` (`entry`, `class`, `subclass`, `SoundOverrideSubclass`, `name`, `displayid`, `Quality`, `Flags`, `FlagsExtra`, `BuyCount`, `BuyPrice`, `SellPrice`, `InventoryType`, `AllowableClass`, `AllowableRace`, `ItemLevel`, `RequiredLevel`, `RequiredSkill`, `RequiredSkillRank`, `requiredspell`, `requiredhonorrank`, `RequiredCityRank`, `RequiredReputationFaction`, `RequiredReputationRank`, `maxcount`, `stackable`, `ContainerSlots`, `StatsCount`, `stat_type1`, `stat_value1`, `stat_type2`, `stat_value2`, `stat_type3`, `stat_value3`, `stat_type4`, `stat_value4`, `stat_type5`, `stat_value5`, `stat_type6`, `stat_value6`, `stat_type7`, `stat_value7`, `stat_type8`, `stat_value8`, `stat_type9`, `stat_value9`, `stat_type10`, `stat_value10`, `ScalingStatDistribution`, `ScalingStatValue`, `dmg_min1`, `dmg_max1`, `dmg_type1`, `dmg_min2`, `dmg_max2`, `dmg_type2`, `armor`, `holy_res`, `fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `delay`, `ammo_type`, `RangedModRange`, `spellid_1`, `spelltrigger_1`, `spellcharges_1`, `spellppmRate_1`, `spellcooldown_1`, `spellcategory_1`, `spellcategorycooldown_1`, `spellid_2`, `spelltrigger_2`, `spellcharges_2`, `spellppmRate_2`, `spellcooldown_2`, `spellcategory_2`, `spellcategorycooldown_2`, `spellid_3`, `spelltrigger_3`, `spellcharges_3`, `spellppmRate_3`, `spellcooldown_3`, `spellcategory_3`, `spellcategorycooldown_3`, `spellid_4`, `spelltrigger_4`, `spellcharges_4`, `spellppmRate_4`, `spellcooldown_4`, `spellcategory_4`, `spellcategorycooldown_4`, `spellid_5`, `spelltrigger_5`, `spellcharges_5`, `spellppmRate_5`, `spellcooldown_5`, `spellcategory_5`, `spellcategorycooldown_5`, `bonding`, `description`, `PageText`, `LanguageID`, `PageMaterial`, `startquest`, `lockid`, `Material`, `sheath`, `RandomProperty`, `RandomSuffix`, `block`, `itemset`, `MaxDurability`, `area`, `Map`, `BagFamily`, `TotemCategory`, `socketColor_1`, `socketContent_1`, `socketColor_2`, `socketContent_2`, `socketColor_3`, `socketContent_3`, `socketBonus`, `GemProperties`, `RequiredDisenchantSkill`, `ArmorDamageModifier`, `duration`, `ItemLimitCategory`, `HolidayId`, `ScriptName`, `DisenchantID`, `FoodType`, `minMoneyLoot`, `maxMoneyLoot`, `flagsCustom`, `VerifiedBuild`) VALUES
+(701001, 15, 4, -1, 'MxWoW Portal Master', 28862, 4, 64, 0, 1, 50000000, 10000000, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 1, 'Used to summon portals.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 'mxwow_portalmaster', 0, 0, 0, 0, 0, 0);
diff --git a/modules/mod-mxwow-webhelper/src/mxwow-webhelper.cpp b/modules/mod-mxwow-webhelper/src/mxwow-webhelper.cpp
new file mode 100644
index 0000000..45b96de
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/src/mxwow-webhelper.cpp
@@ -0,0 +1,57 @@
+//// MxWoW Official Module
+//// Web Helper
+//// Dev:       mikx
+//// Git:       https://mxgit.ovh/MxWoW/mod-mxwow-webhelper
+
+#include "Define.h"
+#include "GossipDef.h"
+#include "Item.h"
+#include "Player.h"
+#include "ScriptedGossip.h"
+#include "ScriptMgr.h"
+#include "Spell.h"
+#include "Configuration/Config.h"
+#include "Chat.h"
+
+class mxwow_webhelper : public PlayerScript
+{
+public:
+    mxwow_webhelper() : PlayerScript("mxwow_webhelper") {}
+
+    void OnPlayerLogin(Player* player) override
+    {
+        QueryResult queryAccount = LoginDatabase.Query("SELECT * FROM account WHERE id = {}", player->GetSession()->GetAccountId());
+        if(queryAccount){
+            std::string userName = (*queryAccount)[1].Get<std::string>();
+            if(userName.find("RNDBOT") != std::string::npos){
+                // Is a bot
+                LoginDatabase.Execute("INSERT INTO mxw_web_online (aid, cid, isBot) VALUES ({}, {}, {})", player->GetSession()->GetAccountId(), player->GetGUID().GetRawValue(), 1);
+            } else {
+                // Is a real player
+                LoginDatabase.Execute("INSERT INTO mxw_web_online (aid, cid, isBot) VALUES ({}, {}, {})", player->GetSession()->GetAccountId(), player->GetGUID().GetRawValue(), 0);
+            }
+        }
+    }
+
+    void OnPlayerLogout(Player* player) override
+    {
+        LoginDatabase.Execute("DELETE FROM mxw_web_online WHERE aid = {} AND cid = {}", player->GetSession()->GetAccountId(), player->GetGUID().GetRawValue());
+    }
+};
+
+class mxwow_webhelperWorld : public WorldScript
+{
+public:
+    mxwow_webhelperWorld() : WorldScript("mxwow_webhelperWorld") {}
+
+    void OnShutdown() override
+    {
+        LoginDatabase.Execute("DELETE FROM mxw_web_online");
+    }
+};
+
+void AddMxWoWWebHelperScripts()
+{
+    new mxwow_webhelper();
+    new mxwow_webhelperWorld();
+}
diff --git a/modules/mod-mxwow-webhelper/src/mxwow-webhelper_loader.cpp b/modules/mod-mxwow-webhelper/src/mxwow-webhelper_loader.cpp
new file mode 100644
index 0000000..f2bb255
--- /dev/null
+++ b/modules/mod-mxwow-webhelper/src/mxwow-webhelper_loader.cpp
@@ -0,0 +1,12 @@
+//// MxWoW Official Module
+//// QoL Master
+//// Dev:       mikx
+//// Git:       https://mxgit.ovh/MxWoW/mod_mxwow_portalmaster
+//// Credits:   Based on https://github.com/azerothcore/mod-character-tools
+
+void AddMxWoWWebHelperScripts();
+
+void Addmod_mxwow_webhelperScripts()
+{
+    AddMxWoWWebHelperScripts();
+}
diff --git a/modules/mod-no-hearthstone-cooldown/.editorconfig b/modules/mod-no-hearthstone-cooldown/.editorconfig
new file mode 100644
index 0000000..eb64e2f
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/.editorconfig
@@ -0,0 +1,8 @@
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+tab_width = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+max_line_length = 80
diff --git a/modules/mod-no-hearthstone-cooldown/.git_commit_template.txt b/modules/mod-no-hearthstone-cooldown/.git_commit_template.txt
new file mode 100644
index 0000000..5c5d933
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/.git_commit_template.txt
@@ -0,0 +1,45 @@
+### TITLE
+## Type(Scope/Subscope): Commit ultra short explanation
+## |---- Write below the examples with a maximum of 50 characters ----|
+## Example 1: fix(DB/SAI): Missing spell to NPC Hogger
+## Example 2: fix(CORE/Raid): Phase 2 of Ragnaros
+## Example 3: feat(CORE/Commands): New GM command to do something
+
+### DESCRIPTION
+## Explain why this change is being made, what does it fix etc...
+## |---- Write below the examples with a maximum of 72 characters per lines ----|
+## Example: Hogger (id: 492) was not charging player when being engaged.
+
+## Provide links to any issue, commit, pull request or other resource
+## Example 1: Closes issue #23
+## Example 2: Ported from other project's commit (link)
+## Example 3: References taken from wowpedia / wowhead / wowwiki / https://wowgaming.altervista.org/aowow/
+
+## =======================================================
+##                     EXTRA INFOS
+## =======================================================
+## "Type" can be:
+##    feat     (new feature)
+##    fix      (bug fix)
+##    refactor (refactoring production code)
+##    style    (formatting, missing semi colons, etc; no code change)
+##    docs     (changes to documentation)
+##    test     (adding or refactoring tests; no production code change)
+##    chore    (updating bash scripts, git files etc; no production code change)
+## --------------------
+## Remember to
+##    Capitalize the subject line
+##    Use the imperative mood in the subject line
+##    Do not end the subject line with a period
+##    Separate subject from body with a blank line
+##    Use the body to explain what and why rather than how
+##    Can use multiple lines with "-" for bullet points in body
+## --------------------
+## More info here https://www.conventionalcommits.org/en/v1.0.0-beta.2/
+## =======================================================
+## "Scope" can be:
+##    CORE     (core related, c++)
+##    DB       (database related, sql)
+## =======================================================
+## "Subscope" is optional and depends on the nature of the commit.
+## =======================================================
diff --git a/modules/mod-no-hearthstone-cooldown/.gitattributes b/modules/mod-no-hearthstone-cooldown/.gitattributes
new file mode 100644
index 0000000..7ef9001
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/.gitattributes
@@ -0,0 +1,105 @@
+## AUTO-DETECT
+##   Handle line endings automatically for files detected as
+##   text and leave all files detected as binary untouched.
+##   This will handle all files NOT defined below.
+*   text=auto eol=lf
+
+# Text
+*.conf text
+*.conf.dist text
+*.cmake text
+
+## Scripts
+*.sh text
+*.fish text
+*.lua text
+
+## SQL
+*.sql text
+
+## C++
+*.c text
+*.cc text
+*.cxx text
+*.cpp text
+*.c++ text
+*.hpp text
+*.h text
+*.h++ text
+*.hh text
+
+
+## For documentation
+
+# Documents
+*.doc	 diff=astextplain
+*.DOC	 diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot  diff=astextplain
+*.DOT  diff=astextplain
+*.pdf  diff=astextplain
+*.PDF	 diff=astextplain
+*.rtf	 diff=astextplain
+*.RTF	 diff=astextplain
+
+## DOCUMENTATION
+*.markdown   text
+*.md         text
+*.mdwn       text
+*.mdown      text
+*.mkd        text
+*.mkdn       text
+*.mdtxt      text
+*.mdtext     text
+*.txt        text
+AUTHORS      text
+CHANGELOG    text
+CHANGES      text
+CONTRIBUTING text
+COPYING      text
+copyright    text
+*COPYRIGHT*  text
+INSTALL      text
+license      text
+LICENSE      text
+NEWS         text
+readme       text
+*README*     text
+TODO         text
+
+## GRAPHICS
+*.ai   binary
+*.bmp  binary
+*.eps  binary
+*.gif  binary
+*.ico  binary
+*.jng  binary
+*.jp2  binary
+*.jpg  binary
+*.jpeg binary
+*.jpx  binary
+*.jxr  binary
+*.pdf  binary
+*.png  binary
+*.psb  binary
+*.psd  binary
+*.svg  text
+*.svgz binary
+*.tif  binary
+*.tiff binary
+*.wbmp binary
+*.webp binary
+
+
+## ARCHIVES
+*.7z  binary
+*.gz  binary
+*.jar binary
+*.rar binary
+*.tar binary
+*.zip binary
+
+## EXECUTABLES
+*.exe binary
+*.pyc binary
diff --git a/modules/mod-no-hearthstone-cooldown/.github/workflows/core-build.yml b/modules/mod-no-hearthstone-cooldown/.github/workflows/core-build.yml
new file mode 100644
index 0000000..1eec897
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/.github/workflows/core-build.yml
@@ -0,0 +1,10 @@
+name: core-build
+on:
+  push:
+  pull_request:
+
+jobs:
+  build:
+    uses: azerothcore/reusable-workflows/.github/workflows/core_build_modules.yml@main
+    with:
+      module_repo: ${{ github.event.repository.name }}
diff --git a/modules/mod-no-hearthstone-cooldown/.gitignore b/modules/mod-no-hearthstone-cooldown/.gitignore
new file mode 100644
index 0000000..c6e1299
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/.gitignore
@@ -0,0 +1,48 @@
+!.gitignore
+
+#
+#Generic
+#
+
+.directory
+.mailmap
+*.orig
+*.rej
+*.*~
+.hg/
+*.kdev*
+.DS_Store
+CMakeLists.txt.user
+*.bak
+*.patch
+*.diff
+*.REMOTE.*
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+
+#
+# IDE & other softwares
+#
+/.settings/
+/.externalToolBuilders/*
+# exclude in all levels
+nbproject/
+.sync.ffs_db
+*.kate-swp
+
+#
+# Eclipse
+#
+*.pydevproject
+.metadata
+.gradle
+tmp/
+*.tmp
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.project
+.cproject
diff --git a/modules/mod-no-hearthstone-cooldown/LICENSE b/modules/mod-no-hearthstone-cooldown/LICENSE
new file mode 100644
index 0000000..0ad25db
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<https://www.gnu.org/licenses/>.
diff --git a/modules/mod-no-hearthstone-cooldown/README.md b/modules/mod-no-hearthstone-cooldown/README.md
new file mode 100644
index 0000000..1afb3b7
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/README.md
@@ -0,0 +1,44 @@
+# ![logo](https://raw.githubusercontent.com/azerothcore/azerothcore.github.io/master/images/logo-github.png) AzerothCore
+## mod-no-hearthstone-cooldown
+### This is a module for [AzerothCore](http://www.azerothcore.org)
+
+# Module info
+
+- Name: No Hearthstone cooldown
+- Author: BytesGalore
+- Module:
+  + Repository: https://github.com/BytesGalore/mod-no-hearthstone-cooldown
+  + Download: https://github.com/BytesGalore/mod-no-hearthstone-cooldown/archive/refs/heads/main.zip
+- License: AGPL
+
+# Module integration
+
+- **AzerothCore hash/commit compliance:** [5af98783](https://github.com/azerothcore/azerothcore-wotlk/commit/5af98783c9f61f059914b3304bb26785502a6924)
+- Includes configuration (.conf)?: Yes, copied by CMake
+- Includes SQL patches?: No
+- Core hooks used:
+    + PlayerScript: OnLogin
+    + PlayerScript: OnBeforeTeleport
+    + WorldScript: OnAfterConfigLoad
+
+# Description
+A module that simply skips the cooldown of the Hearthstone
+#### Features:
+- It resets the Hearthstone cooldown immediately
+
+### This module currently requires:
+- AzerothCore v4.0.0+
+
+### How to install
+1. Simply place the module under the `modules` folder of your AzerothCore source folder.
+2. Re-run cmake and launch a clean build of AzerothCore
+3. that's all
+
+### (Optional) Edit module configuration
+- You can turn the module **on** and **off**, default it is **on**
+- You can also turn the announcement of the module on player-login **on** and **off**, default it is **on**
+- If you want change the settings, go to your server configuration folder (e.g. **etc**), copy `mod_no_hearthstone_cooldown.conf.dist` to `mod_no_hearthstone_cooldown.conf` and edit the settings
+
+## Credits
+* [BytesGalore](https://github.com/BytesGalore) (author of the module)
+* AzerothCore: [repository](https://github.com/azerothcore) - [website](http://azerothcore.org/) - [discord chat community](https://discord.gg/PaqQRkd)
diff --git a/modules/mod-no-hearthstone-cooldown/conf/conf.sh.dist b/modules/mod-no-hearthstone-cooldown/conf/conf.sh.dist
new file mode 100644
index 0000000..06a5f89
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/conf/conf.sh.dist
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+## CUSTOM SQL - Important file used by the db_assembler.sh
+## Keep only the required variables (base sql files or updates, depending on the DB)
+
+## BASE SQL
+
+DB_AUTH_CUSTOM_PATHS+=(
+	$MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/sql/auth/base/"
+)
+
+DB_CHARACTERS_CUSTOM_PATHS+=(
+	$MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/sql/characters/base/"
+)
+
+DB_WORLD_CUSTOM_PATHS+=(
+	$MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/sql/world/base/"
+)
+
+## UPDATES
+
+DB_AUTH_UPDATES_PATHS+=(
+	$MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/sql/auth/updates/"
+)
+
+DB_CHARACTERS_UPDATES_PATHS+=(
+	$MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/sql/characters/updates/"
+)
+
+DB_WORLD_UPDATES_PATHS+=(
+	$MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/sql/world/updates/"
+)
diff --git a/modules/mod-no-hearthstone-cooldown/conf/mod_no_hearthstone_cooldown.conf.dist b/modules/mod-no-hearthstone-cooldown/conf/mod_no_hearthstone_cooldown.conf.dist
new file mode 100644
index 0000000..137dfa6
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/conf/mod_no_hearthstone_cooldown.conf.dist
@@ -0,0 +1,24 @@
+#
+# Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
+#
+
+[worldserver]
+
+#####################################################
+# No Hearthstone cooldown module configuration
+#####################################################
+#
+#    NoHearthstoneCooldownConfig.Enable
+#        Description: Enable to skip the Hearthstone cooldown
+#        Default:     1 - Enabled
+#                     0 - Disabled
+#
+#    NoHearthstoneCooldownConfig.Announce 
+#        Description: Inform the player about the loaded module
+#        Default:     1 - Enabled
+#                     0 - Disabled
+#
+
+NoHearthstoneCooldown.Enable = 1
+
+NoHearthstoneCooldown.Announce = 1
diff --git a/modules/mod-no-hearthstone-cooldown/include.sh b/modules/mod-no-hearthstone-cooldown/include.sh
new file mode 100644
index 0000000..d2fdbc6
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/include.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+## GETS THE CURRENT MODULE ROOT DIRECTORY
+MOD_NO_HEARTHSTONE_COOLDOWN_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/" && pwd )"
+
+source $MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/conf/conf.sh.dist"
+
+if [ -f $MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/conf/conf.sh" ]; then
+    source $MOD_NO_HEARTHSTONE_COOLDOWN_ROOT"/conf/conf.sh"
+fi
diff --git a/modules/mod-no-hearthstone-cooldown/setup_git_commit_template.sh b/modules/mod-no-hearthstone-cooldown/setup_git_commit_template.sh
new file mode 100644
index 0000000..7b52062
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/setup_git_commit_template.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+## Set a local git commit template
+git config --local commit.template ".git_commit_template.txt" ;
diff --git a/modules/mod-no-hearthstone-cooldown/src/NHC_loader.cpp b/modules/mod-no-hearthstone-cooldown/src/NHC_loader.cpp
new file mode 100644
index 0000000..178c3d8
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/src/NHC_loader.cpp
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
+ */
+
+// From SC
+void AddNoHearthstoneCooldownScripts();
+
+// Add all
+void Addmod_no_hearthstone_cooldownScripts()
+{
+    AddNoHearthstoneCooldownScripts();
+}
diff --git a/modules/mod-no-hearthstone-cooldown/src/NoHearthstoneCooldown.cpp b/modules/mod-no-hearthstone-cooldown/src/NoHearthstoneCooldown.cpp
new file mode 100644
index 0000000..6ea04f0
--- /dev/null
+++ b/modules/mod-no-hearthstone-cooldown/src/NoHearthstoneCooldown.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
+ */
+
+#include "ScriptMgr.h"
+#include "Player.h"
+#include "Config.h"
+#include "Chat.h"
+
+// Add player scripts
+class NoHearthstoneCooldown : public PlayerScript, public WorldScript
+{
+public:
+    NoHearthstoneCooldown(): PlayerScript("NoHearthstoneCooldown"), WorldScript("NoHearthstoneCooldown") {}
+
+    bool OnPlayerBeforeTeleport(Player* player, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
+    {
+        ClearHearthstoneCooldown(player);
+        return true; 
+    }
+
+    void OnPlayerLogin(Player* player) override
+    {
+        if (sConfigMgr->GetOption<bool>("NoHearthstoneCooldown.Announce", true))
+        {
+            ChatHandler(player->GetSession()).SendSysMessage("This server is running the |cff4CFF00No Hearthstone cooldown|r module.");
+        }
+        ClearHearthstoneCooldown(player);
+    }
+
+    void OnAfterConfigLoad(bool /*reload*/) override 
+    { 
+        m_bNHCModuleEnabled = sConfigMgr->GetOption<bool>("NoHearthstoneCooldown.Enable", true);
+    }
+
+private:
+    void ClearHearthstoneCooldown(Player* player)
+    {
+        if (m_bNHCModuleEnabled)
+        {
+            // only clear the cooldown if the player actually casted the Hearthstone spell
+            if (player->FindCurrentSpellBySpellId(m_nSpellIDHearthstone))
+            {
+                // we remove the cooldown from the list of the player current cooldowns
+                player->RemoveSpellCooldown(m_nSpellIDHearthstone, true);
+                player->RemoveSpellCooldown(m_nSpellIDNoPlaceLikeHome, true);
+                // then we clear the cooldown and send an update to the player client 
+                player->SendClearCooldown(m_nSpellIDHearthstone, player);
+                player->SendClearCooldown(m_nSpellIDNoPlaceLikeHome, player);
+            }
+        }
+    }
+
+    bool m_bNHCModuleEnabled = false;
+    // https://www.wowhead.com/spell=8690/hearthstone
+    const uint32 m_nSpellIDHearthstone = 8690;
+    // https://tbc.wowhead.com/spell=39937/theres-no-place-like-home
+    const uint32 m_nSpellIDNoPlaceLikeHome = 39937;
+};
+
+// Add all scripts in one
+void AddNoHearthstoneCooldownScripts()
+{
+    new NoHearthstoneCooldown();
+}
diff --git a/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2025_03_03_00.sql b/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2025_03_03_00.sql
index cc1f4f0..29d8afb 100644
--- a/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2025_03_03_00.sql
+++ b/modules/mod-playerbots/data/sql/playerbots/updates/db_playerbots/2025_03_03_00.sql
@@ -1,12 +1,22 @@
-INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) VALUES
-(3780, 'Highlord Mograine', 533, 2524.32, -2951.28, 245.633, 1);
+-- Delete existing entries from playerbots_travelnode, playerbots_travelnode_path, and playerbots_travelnode_link tables
+DELETE FROM `playerbots_travelnode_link` WHERE `node_id` = 3780;
+DELETE FROM `playerbots_travelnode_path` WHERE `node_id` = 3780;
+DELETE FROM `playerbots_travelnode` WHERE `id` = 3780;
 
-INSERT INTO `playerbots_travelnode_path` (`node_id`, `to_node_id`, `nr`, `map_id`, `x`, `y`, `z`) VALUES
+-- Insert new entries into playerbots_travelnode
+INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) 
+VALUES (3780, 'Highlord Mograine', 533, 2524.32, -2951.28, 245.633, 1);
+
+-- Insert new entries into playerbots_travelnode_path
+INSERT INTO `playerbots_travelnode_path` (`node_id`, `to_node_id`, `nr`, `map_id`, `x`, `y`, `z`) 
+VALUES
 (3780, 472, 0, 533, 2524.32, -2951.28, 245.633),
 (3780, 472, 1, 533, 2528.79, -2948.58, 245.633),
 (3780, 757, 0, 533, 2524.32, -2951.28, 245.633),
 (3780, 757, 1, 533, 2517.62, -2959.38, 245.636);
 
-INSERT INTO `playerbots_travelnode_link` (`node_id`, `to_node_id`, `type`, `object`, `distance`, `swim_distance`, `extra_cost`, `calculated`, `max_creature_0`, `max_creature_1`, `max_creature_2`) VALUES
+-- Insert new entries into playerbots_travelnode_link
+INSERT INTO `playerbots_travelnode_link` (`node_id`, `to_node_id`, `type`, `object`, `distance`, `swim_distance`, `extra_cost`, `calculated`, `max_creature_0`, `max_creature_1`, `max_creature_2`) 
+VALUES
 (3780, 472, 1, 0, 5.3221, 0, 0, 1, 83, 0, 0),
 (3780, 757, 1, 0, 10.6118, 0, 0, 1, 83, 0, 0);
diff --git a/modules/mod-playerbots/src/LootObjectStack.cpp b/modules/mod-playerbots/src/LootObjectStack.cpp
index 196e986..3d34987 100644
--- a/modules/mod-playerbots/src/LootObjectStack.cpp
+++ b/modules/mod-playerbots/src/LootObjectStack.cpp
@@ -9,7 +9,7 @@
 #include "Playerbots.h"
 #include "Unit.h"
 
-#define MAX_LOOT_OBJECT_COUNT 10
+#define MAX_LOOT_OBJECT_COUNT 200
 
 LootTarget::LootTarget(ObjectGuid guid) : guid(guid), asOfTime(time(nullptr)) {}
 
diff --git a/modules/mod-playerbots/src/PlayerbotAI.cpp b/modules/mod-playerbots/src/PlayerbotAI.cpp
index 87ef92a..8c154b1 100644
--- a/modules/mod-playerbots/src/PlayerbotAI.cpp
+++ b/modules/mod-playerbots/src/PlayerbotAI.cpp
@@ -14,10 +14,13 @@
 #include "BudgetValues.h"
 #include "ChannelMgr.h"
 #include "CharacterPackets.h"
+#include "Common.h"
 #include "CreatureAIImpl.h"
+#include "CreatureData.h"
 #include "EmoteAction.h"
 #include "Engine.h"
 #include "ExternalEventHelper.h"
+#include "GameObjectData.h"
 #include "GameTime.h"
 #include "GuildMgr.h"
 #include "GuildTaskMgr.h"
@@ -32,6 +35,7 @@
 #include "MoveSplineInit.h"
 #include "NewRpgStrategy.h"
 #include "ObjectGuid.h"
+#include "ObjectMgr.h"
 #include "PerformanceMonitor.h"
 #include "Player.h"
 #include "PlayerbotAIConfig.h"
@@ -174,6 +178,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
     botOutgoingPacketHandlers.AddHandler(SMSG_RESURRECT_REQUEST, "resurrect request");
     botOutgoingPacketHandlers.AddHandler(SMSG_INVENTORY_CHANGE_FAILURE, "cannot equip");
     botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS, "trade status");
+    botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS_EXTENDED, "trade status extended");
     botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_RESPONSE, "loot response");
     botOutgoingPacketHandlers.AddHandler(SMSG_ITEM_PUSH_RESULT, "item push result");
     botOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command");
@@ -204,7 +209,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
     masterIncomingPacketHandlers.AddHandler(CMSG_PUSHQUESTTOPARTY, "quest share");
     botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_COMPLETE, "quest update complete");
     botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_KILL, "quest update add kill");
-    botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item");
+    // botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item"); // SMSG_QUESTUPDATE_ADD_ITEM no longer used
     botOutgoingPacketHandlers.AddHandler(SMSG_QUEST_CONFIRM_ACCEPT, "confirm quest");
 }
 
@@ -1100,9 +1105,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
                         }
                     }
 
-                    QueueChatResponse(
-                        ChatQueuedReply{msgtype, guid1.GetCounter(), guid2.GetCounter(), message, chanName,
-                                                  name, time(nullptr) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15)});
+                    QueueChatResponse(ChatQueuedReply{msgtype, guid1.GetCounter(), guid2.GetCounter(), message,
+                                                      chanName, name,
+                                                      time(nullptr) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15)});
                     GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Set(time(0) + urand(5, 25));
                     return;
                 }
@@ -1239,10 +1244,10 @@ void PlayerbotAI::ChangeEngine(BotState type)
         switch (type)
         {
             case BOT_STATE_COMBAT:
-                // LOG_DEBUG("playerbots",  "=== {} COMBAT ===", bot->GetName().c_str());
+                ChangeEngineOnCombat();
                 break;
             case BOT_STATE_NON_COMBAT:
-                // LOG_DEBUG("playerbots",  "=== {} NON-COMBAT ===", bot->GetName().c_str());
+                ChangeEngineOnNonCombat();
                 break;
             case BOT_STATE_DEAD:
                 // LOG_DEBUG("playerbots",  "=== {} DEAD ===", bot->GetName().c_str());
@@ -1253,6 +1258,23 @@ void PlayerbotAI::ChangeEngine(BotState type)
     }
 }
 
+void PlayerbotAI::ChangeEngineOnCombat()
+{
+    if (HasStrategy("stay", BOT_STATE_COMBAT))
+    {
+        aiObjectContext->GetValue<PositionInfo>("pos", "stay")
+            ->Set(PositionInfo(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId()));
+    }
+}
+
+void PlayerbotAI::ChangeEngineOnNonCombat()
+{
+    if (HasStrategy("stay", BOT_STATE_NON_COMBAT))
+    {
+        aiObjectContext->GetValue<PositionInfo>("pos", "stay")->Reset();
+    }
+}
+
 void PlayerbotAI::DoNextAction(bool min)
 {
     if (!bot->IsInWorld() || bot->IsBeingTeleported() || (GetMaster() && GetMaster()->IsBeingTeleported()))
@@ -2287,10 +2309,46 @@ const AreaTableEntry* PlayerbotAI::GetCurrentZone()
 
 std::string PlayerbotAI::GetLocalizedAreaName(const AreaTableEntry* entry)
 {
+    std::string name;
     if (entry)
-        return entry->area_name[sWorld->GetDefaultDbcLocale()];
+    {
+        name = entry->area_name[sWorld->GetDefaultDbcLocale()];
+        if (name.empty())
+            name = entry->area_name[LOCALE_enUS];
+    }
 
-    return "";
+    return name;
+}
+
+std::string PlayerbotAI::GetLocalizedCreatureName(uint32 entry)
+{
+    std::string name;
+    const CreatureLocale* cl = sObjectMgr->GetCreatureLocale(entry);
+    if (cl)
+        ObjectMgr::GetLocaleString(cl->Name, sWorld->GetDefaultDbcLocale(), name);
+    if (name.empty())
+    {
+        CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(entry);
+        if (ct)
+            name = ct->Name;
+    }
+    return name;
+}
+
+
+std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
+{
+    std::string name;
+    const GameObjectLocale* gl = sObjectMgr->GetGameObjectLocale(entry);
+    if (gl)
+        ObjectMgr::GetLocaleString(gl->Name, sWorld->GetDefaultDbcLocale(), name);
+    if (name.empty())
+    {
+        GameObjectTemplate const* gt = sObjectMgr->GetGameObjectTemplate(entry);
+        if (gt)
+            name = gt->name;
+    }
+    return name;
 }
 
 std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
@@ -2807,7 +2865,6 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
     if (!target)
         target = bot;
 
-
     if (Pet* pet = bot->GetPet())
         if (pet->HasSpell(spellid))
             return true;
@@ -3000,8 +3057,7 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, GameObject* goTarget, bool checkH
     return false;
 }
 
-bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell,
-                               Item* itemTarget)
+bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell, Item* itemTarget)
 {
     if (!spellid)
         return false;
@@ -3029,7 +3085,7 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool c
     Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
 
     spell->m_targets.SetDst(x, y, z, 0.f);
-    
+
     Item* item = itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellid)->Get();
     spell->m_targets.SetItemTarget(item);
 
@@ -3133,7 +3189,8 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
     Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
 
     SpellCastTargets targets;
-    if (spellInfo->Targets & TARGET_FLAG_ITEM)
+    if (spellInfo->Effects[0].Effect != SPELL_EFFECT_OPEN_LOCK &&
+        (spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM))
     {
         Item* item = itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellId)->Get();
         targets.SetItemTarget(item);
@@ -3176,6 +3233,20 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
             targets.SetGOTarget(go);
             faceTo = go;
         }
+        else if (itemTarget)
+        {
+            Player* trader = bot->GetTrader();
+            if (trader)
+            {
+                targets.SetTradeItemTarget(bot);
+                targets.SetUnitTarget(bot);
+                faceTo = trader;
+            }
+            else
+            {
+                targets.SetItemTarget(itemTarget);
+            }
+        }
         else
         {
             if (Unit* creature = GetUnit(loot.guid))
@@ -3212,6 +3283,58 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
         //     LOG_DEBUG("playerbots", "Spell cast failed. - target name: {}, spellid: {}, bot name: {}, result: {}",
         //         target->GetName(), spellId, bot->GetName(), result);
         // }
+        if (HasStrategy("debug spell", BOT_STATE_NON_COMBAT))
+        {
+            std::ostringstream out;
+            out << "Spell cast failed - ";
+            out << "Spell ID: " << spellId << " (" << ChatHelper::FormatSpell(spellInfo) << "), ";
+            out << "Error Code: " << static_cast<int>(result) << " (0x" << std::hex << static_cast<int>(result) << std::dec << "), ";
+            out << "Bot: " << bot->GetName() << ", ";
+    
+            // Check spell target type
+            if (targets.GetUnitTarget())
+            {
+                out << "Target: Unit (" << targets.GetUnitTarget()->GetName() 
+                    << ", Low GUID: " << targets.GetUnitTarget()->GetGUID().GetCounter()
+                    << ", High GUID: " << static_cast<uint32>(targets.GetUnitTarget()->GetGUID().GetHigh()) << "), ";
+            }
+
+            if (targets.GetGOTarget())
+            {
+                out << "Target: GameObject (Low GUID: " << targets.GetGOTarget()->GetGUID().GetCounter()
+                    << ", High GUID: " << static_cast<uint32>(targets.GetGOTarget()->GetGUID().GetHigh()) << "), ";
+            }
+
+            if (targets.GetItemTarget())
+            {
+                out << "Target: Item (Low GUID: " << targets.GetItemTarget()->GetGUID().GetCounter()
+                    << ", High GUID: " << static_cast<uint32>(targets.GetItemTarget()->GetGUID().GetHigh()) << "), ";
+            }
+    
+            // Check if bot is in trade mode
+            if (bot->GetTradeData())
+            {
+                out << "Trade Mode: Active, ";
+                Item* tradeItem = bot->GetTradeData()->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED);
+                if (tradeItem)
+                {
+                    out << "Trade Item: " << tradeItem->GetEntry() 
+                        << " (Low GUID: " << tradeItem->GetGUID().GetCounter()
+                        << ", High GUID: " << static_cast<uint32>(tradeItem->GetGUID().GetHigh()) << "), ";
+                }
+                else
+                {
+                    out << "Trade Item: None, ";
+                }
+            }
+            else
+            {
+                out << "Trade Mode: Inactive, ";
+            }
+    
+            TellMasterNoFacing(out);
+        }
+
         return false;
     }
     // if (spellInfo->Effects[0].Effect == SPELL_EFFECT_OPEN_LOCK || spellInfo->Effects[0].Effect ==
@@ -3308,7 +3431,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, float x, float y, float z, Item* ite
     Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
 
     SpellCastTargets targets;
-    if (spellInfo->Targets & TARGET_FLAG_ITEM)
+    if (spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM)
     {
         Item* item = itemTarget ? itemTarget : aiObjectContext->GetValue<Item*>("item for spell", spellId)->Get();
         targets.SetItemTarget(item);
@@ -4188,8 +4311,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
         mod = AutoScaleActivity(mod);
     }
 
-    uint32 ActivityNumber = GetFixedBotNumer(100,
-                                    sPlayerbotAIConfig->botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
+    uint32 ActivityNumber =
+        GetFixedBotNumer(100, sPlayerbotAIConfig->botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
 
     return ActivityNumber <=
            (sPlayerbotAIConfig->botActiveAlone * mod) /
@@ -4248,7 +4371,8 @@ void PlayerbotAI::RemoveShapeshift()
     RemoveAura("moonkin form");
     RemoveAura("travel form");
     RemoveAura("cat form");
-    RemoveAura("flight form"); bot->RemoveAura(33943);  // The latter added for now as RemoveAura("flight form") currently does not work.
+    RemoveAura("flight form");
+    bot->RemoveAura(33943);  // The latter added for now as RemoveAura("flight form") currently does not work.
     RemoveAura("swift flight form");
     RemoveAura("aquatic form");
     RemoveAura("ghost wolf");
@@ -4835,11 +4959,9 @@ Item* PlayerbotAI::FindAmmo() const
         }
 
         // Search inventory for the correct ammo type
-        return FindItemInInventory([requiredAmmoType](ItemTemplate const* pItemProto) -> bool
-                                   {
-                                       return pItemProto->Class == ITEM_CLASS_PROJECTILE && 
-                                              pItemProto->SubClass == requiredAmmoType;
-                                   });
+        return FindItemInInventory(
+            [requiredAmmoType](ItemTemplate const* pItemProto) -> bool
+            { return pItemProto->Class == ITEM_CLASS_PROJECTILE && pItemProto->SubClass == requiredAmmoType; });
     }
 
     return nullptr;  // No ranged weapon equipped
@@ -4864,6 +4986,52 @@ Item* PlayerbotAI::FindBandage() const
         { return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE; });
 }
 
+Item* PlayerbotAI::FindOpenableItem() const
+{
+    return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
+    {
+        return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) &&
+               (itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked());
+    });
+}
+
+Item* PlayerbotAI::FindLockedItem() const
+{
+    return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
+    {
+        if (!this->bot->HasSkill(SKILL_LOCKPICKING))  // Ensure bot has Lockpicking skill
+            return false;
+
+        if (itemTemplate->LockID == 0)  // Ensure the item is actually locked
+            return false;
+
+        Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId);
+        if (!item || !item->IsLocked())  // Ensure item instance is locked
+            return false;
+
+        // Check if bot has enough Lockpicking skill
+        LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID);
+        if (!lockInfo)
+            return false;
+
+        for (uint8 j = 0; j < 8; ++j)
+        {
+            if (lockInfo->Type[j] == LOCK_KEY_SKILL)
+            {
+                uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j]));
+                if (skillId == SKILL_LOCKPICKING)
+                {
+                    uint32 requiredSkill = lockInfo->Skill[j];
+                    uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING);
+                    return botSkill >= requiredSkill;
+                }
+            }
+        }
+
+        return false;
+    });
+}
+
 static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID,
                                                   ELEMENTAL_SHARPENING_DISPLAYID,  DENSE_SHARPENING_DISPLAYID,
                                                   SOLID_SHARPENING_DISPLAYID,      HEAVY_SHARPENING_DISPLAYID,
@@ -6024,9 +6192,10 @@ bool PlayerbotAI::IsHealingSpell(uint32 spellFamilyName, flag96 spellFalimyFlags
     return spellFalimyFlags & healingFlags;
 }
 
-
-SpellFamilyNames PlayerbotAI::Class2SpellFamilyName(uint8 cls) {
-    switch (cls) {
+SpellFamilyNames PlayerbotAI::Class2SpellFamilyName(uint8 cls)
+{
+    switch (cls)
+    {
         case CLASS_WARRIOR:
             return SPELLFAMILY_WARRIOR;
         case CLASS_PALADIN:
diff --git a/modules/mod-playerbots/src/PlayerbotAI.h b/modules/mod-playerbots/src/PlayerbotAI.h
index b6a71c7..4e7c5a5 100644
--- a/modules/mod-playerbots/src/PlayerbotAI.h
+++ b/modules/mod-playerbots/src/PlayerbotAI.h
@@ -13,8 +13,10 @@
 #include "ChatFilter.h"
 #include "ChatHelper.h"
 #include "Common.h"
+#include "CreatureData.h"
 #include "Event.h"
 #include "Item.h"
+#include "NewRpgInfo.h"
 #include "NewRpgStrategy.h"
 #include "PlayerbotAIBase.h"
 #include "PlayerbotAIConfig.h"
@@ -390,6 +392,8 @@ public:
     void HandleMasterOutgoingPacket(WorldPacket const& packet);
     void HandleTeleportAck();
     void ChangeEngine(BotState type);
+    void ChangeEngineOnCombat();
+    void ChangeEngineOnNonCombat();
     void DoNextAction(bool minimal = false);
     virtual bool DoSpecificAction(std::string const name, Event event = Event(), bool silent = false,
                                   std::string const qualifier = "");
@@ -435,7 +439,8 @@ public:
     const AreaTableEntry* GetCurrentArea();
     const AreaTableEntry* GetCurrentZone();
     static std::string GetLocalizedAreaName(const AreaTableEntry* entry);
-
+    static std::string GetLocalizedCreatureName(uint32 entry);
+    static std::string GetLocalizedGameObjectName(uint32 entry);
     bool TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
     bool TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
     bool TellMasterNoFacing(std::ostringstream& stream,
@@ -464,6 +469,8 @@ public:
     Item* FindPoison() const;
     Item* FindAmmo() const;
     Item* FindBandage() const;
+    Item* FindOpenableItem() const;
+    Item* FindLockedItem() const;
     Item* FindConsumable(uint32 displayId) const;
     Item* FindStoneFor(Item* weapon) const;
     Item* FindOilFor(Item* weapon) const;
@@ -579,6 +586,8 @@ public:
     static bool IsHealingSpell(uint32 spellFamilyName, flag96 spelFalimyFlags);
     static SpellFamilyNames Class2SpellFamilyName(uint8 cls);
     NewRpgInfo rpgInfo;
+    NewRpgStatistic rpgStatistic;
+    std::unordered_set<uint32> lowPriorityQuest;
 
 private:
     static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
diff --git a/modules/mod-playerbots/src/PlayerbotMgr.cpp b/modules/mod-playerbots/src/PlayerbotMgr.cpp
index a4faaea..ba3acea 100644
--- a/modules/mod-playerbots/src/PlayerbotMgr.cpp
+++ b/modules/mod-playerbots/src/PlayerbotMgr.cpp
@@ -91,7 +91,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
             LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
             return;
         }
-        uint32 count = mgr->GetPlayerbotsCount();
+        uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
         if (count >= sPlayerbotAIConfig->maxAddedBots)
         {
             allowed = false;
@@ -638,11 +638,22 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
     bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(botAccount);
     bool isMasterAccount = (masterAccountId == botAccount);
 
-    if (cmd == "add" || cmd == "login")
+    if (cmd == "add" || cmd == "addaccount" || cmd == "login")
     {
         if (ObjectAccessor::FindPlayer(guid))
             return "player already logged in";
 
+        // For addaccount command, verify it's an account name
+        if (cmd == "addaccount")
+        {
+            uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid);
+            if (!accountId)
+                return "character not found";
+
+            if (!sPlayerbotAIConfig->allowAccountBots && accountId != masterAccountId)
+                return "you can only add bots from your own account";
+        }
+
         AddPlayerBot(guid, masterAccountId);
         return "ok";
     }
@@ -816,7 +827,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
 
     if (!*args)
     {
-        messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME\n");
+        messages.push_back("usage: list/reload/tweak/self or add/addaccount/init/remove PLAYERNAME\n");
         messages.push_back("usage: addclass CLASSNAME");
         return messages;
     }
@@ -1130,22 +1141,48 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
     {
         std::string const s = *i;
 
-        uint32 accountId = GetAccountId(s);
-        if (!accountId)
+        if (!strcmp(cmd, "addaccount"))
         {
-            bots.insert(s);
-            continue;
-        }
-
-        QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
-        if (results)
-        {
-            do
+            // When using addaccount, first try to get account ID directly
+            uint32 accountId = GetAccountId(s);
+            if (!accountId)
             {
-                Field* fields = results->Fetch();
-                std::string const charName = fields[0].Get<std::string>();
-                bots.insert(charName);
-            } while (results->NextRow());
+                // If not found, try to get account ID from character name
+                ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
+                if (!charGuid)
+                {
+                    messages.push_back("Neither account nor character '" + s + "' found");
+                    continue;
+                }
+                accountId = sCharacterCache->GetCharacterAccountIdByGuid(charGuid);
+                if (!accountId)
+                {
+                    messages.push_back("Could not find account for character '" + s + "'");
+                    continue;
+                }
+            }
+
+            QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
+            if (results)
+            {
+                do
+                {
+                    Field* fields = results->Fetch();
+                    std::string const charName = fields[0].Get<std::string>();
+                    bots.insert(charName);
+                } while (results->NextRow());
+            }
+        }
+        else
+        {
+            // For regular add command, only add the specific character
+            ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
+            if (!charGuid)
+            {
+                messages.push_back("Character '" + s + "' not found");
+                continue;
+            }
+            bots.insert(s);
         }
     }
 
diff --git a/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp b/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp
index fd8e3fa..ca160a6 100644
--- a/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp
+++ b/modules/mod-playerbots/src/RandomPlayerbotMgr.cpp
@@ -31,6 +31,7 @@
 #include "GuildTaskMgr.h"
 #include "LFGMgr.h"
 #include "MapMgr.h"
+#include "NewRpgInfo.h"
 #include "NewRpgStrategy.h"
 #include "PerformanceMonitor.h"
 #include "Player.h"
@@ -49,6 +50,12 @@
 #include "World.h"
 #include "RandomPlayerbotFactory.h"
 
+struct GuidClassRaceInfo {
+    ObjectGuid::LowType guid;
+    uint32 rClass;
+    uint32 rRace;
+};
+
 void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); }
 
 void activatePrintStatsThread()
@@ -371,7 +378,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
             sRandomPlayerbotMgr->CheckLfgQueue();
     }
 
-    if (time(nullptr) > (printStatsTimer + 300))
+    if (sPlayerbotAIConfig->randomBotAutologin && time(nullptr) > (printStatsTimer + 300))
     {
         if (!printStatsTimer)
         {
@@ -464,9 +471,17 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
     {
         maxAllowedBotCount -= currentBots.size();
         maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount);
+        
+        uint32 totalRatio = sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio;
+        uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio;
+
+        uint32 remainder = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) % totalRatio;
+
+        // Fix #1082: Randomly add one based on reminder
+        if (remainder && urand(1, totalRatio) <= remainder) {
+            allowedAllianceCount++;
+        }
 
-        uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) /
-            (sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio);
         uint32 allowedHordeCount = maxAllowedBotCount - allowedAllianceCount;
 
         for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotAccounts.begin();
@@ -492,11 +507,28 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
             PreparedQueryResult result = CharacterDatabase.Query(stmt);
             if (!result)
                 continue;
-            std::vector<uint32> guids;
-            do
-            {
+            
+            std::vector<GuidClassRaceInfo> allGuidInfos;
+
+            do {
                 Field* fields = result->Fetch();
-                ObjectGuid::LowType guid = fields[0].Get<uint32>();
+                GuidClassRaceInfo info;
+                info.guid = fields[0].Get<uint32>();
+                info.rClass = fields[1].Get<uint8>();
+                info.rRace = fields[2].Get<uint8>();
+                allGuidInfos.push_back(info);
+            } while (result->NextRow());
+
+            // random shuffle for class balance
+            std::mt19937 rnd(time(0));
+            std::shuffle(allGuidInfos.begin(), allGuidInfos.end(), rnd);
+
+            std::vector<uint32> guids;
+            for (const auto& info : allGuidInfos) {
+                ObjectGuid::LowType guid = info.guid;
+                uint32 rClass = info.rClass;
+                uint32 rRace = info.rRace;
+
                 if (GetEventValue(guid, "add"))
                     continue;
 
@@ -509,40 +541,24 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
                 if (std::find(currentBots.begin(), currentBots.end(), guid) != currentBots.end())
                     continue;
 
-                if (sPlayerbotAIConfig->disableDeathKnightLogin)
-                {
-                    uint32 rClass = fields[1].Get<uint8>();
-                    if (rClass == CLASS_DEATH_KNIGHT)
-                    {
+                if (sPlayerbotAIConfig->disableDeathKnightLogin) {
+                    if (rClass == CLASS_DEATH_KNIGHT) {
                         continue;
                     }
                 }
-                uint32 rRace = fields[2].Get<uint8>();
+
                 uint32 isAlliance = IsAlliance(rRace);
-                if (!allowedAllianceCount && isAlliance)
-                {
+                bool factionNotAllowed = (!allowedAllianceCount && isAlliance) || (!allowedHordeCount && !isAlliance);
+
+                if (factionNotAllowed)
                     continue;
-                }
-                if (!allowedHordeCount && !isAlliance)
-                {
-                    continue;
-                }
-                if (isAlliance)
-                {
+
+                if (isAlliance) {
                     allowedAllianceCount--;
-                }
-                else
-                {
+                } else {
                     allowedHordeCount--;
                 }
-                guids.push_back(guid);
-            } while (result->NextRow());
 
-            std::mt19937 rnd(time(0));
-            std::shuffle(guids.begin(), guids.end(), rnd);
-
-            for (uint32& guid : guids)
-            {
                 uint32 add_time = sPlayerbotAIConfig->enablePeriodicOnlineOffline
                                       ? urand(sPlayerbotAIConfig->minRandomBotInWorldTime,
                                               sPlayerbotAIConfig->maxRandomBotInWorldTime)
@@ -1466,6 +1482,82 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
               tlocs.size());
 }
 
+void RandomPlayerbotMgr::PrepareZone2LevelBracket()
+{
+    // Classic WoW - Low - level zones
+    zone2LevelBracket[1] = {5, 12}; // Dun Morogh
+    zone2LevelBracket[12] = {5, 12}; // Elwynn Forest
+    zone2LevelBracket[14] = {5, 12}; // Durotar
+    zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades
+    zone2LevelBracket[141] = {5, 12}; // Teldrassil
+    zone2LevelBracket[215] = {5, 12}; // Mulgore
+    zone2LevelBracket[3430] = {5, 12}; // Eversong Woods
+    zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle
+
+    // Classic WoW - Mid - level zones
+    zone2LevelBracket[17] = {10, 25}; // Barrens
+    zone2LevelBracket[38] = {10, 20}; // Loch Modan
+    zone2LevelBracket[40] = {10, 21}; // Westfall
+    zone2LevelBracket[130] = {10, 23}; // Silverpine Forest
+    zone2LevelBracket[148] = {10, 21}; // Darkshore
+    zone2LevelBracket[3433] = {10, 22}; // Ghostlands
+    zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle
+
+    // Classic WoW - High - level zones
+    zone2LevelBracket[10] = {19, 33}; // Deadwind Pass
+    zone2LevelBracket[11] = {21, 30}; // Wetlands
+    zone2LevelBracket[44] = {16, 28}; // Redridge Mountains
+    zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills
+    zone2LevelBracket[331] = {18, 33}; // Ashenvale
+    zone2LevelBracket[400] = {24, 36}; // Thousand Needles
+    zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains
+
+    // Classic WoW - Higher - level zones
+    zone2LevelBracket[3] = {36, 46}; // Badlands
+    zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows
+    zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh
+    zone2LevelBracket[16] = {45, 52}; // Azshara
+    zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale
+    zone2LevelBracket[45] = {30, 42}; // Arathi Highlands
+    zone2LevelBracket[47] = {42, 51}; // Hinterlands
+    zone2LevelBracket[51] = {45, 51}; // Searing Gorge
+    zone2LevelBracket[357] = {40, 52}; // Feralas
+    zone2LevelBracket[405] = {30, 41}; // Desolace
+    zone2LevelBracket[440] = {41, 52}; // Tanaris
+
+    // Classic WoW - Top - level zones
+    zone2LevelBracket[4] = {52, 57}; // Blasted Lands
+    zone2LevelBracket[28] = {50, 60}; // Western Plaguelands
+    zone2LevelBracket[46] = {51, 60}; // Burning Steppes
+    zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands
+    zone2LevelBracket[361] = {47, 57}; // Felwood
+    zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater
+    zone2LevelBracket[618] = {54, 61}; // Winterspring
+    zone2LevelBracket[1377] = {54, 63}; // Silithus
+
+    // The Burning Crusade - Zones
+    zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula
+    zone2LevelBracket[3518] = {64, 70}; // Nagrand
+    zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest
+    zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley
+    zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh
+    zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains
+    zone2LevelBracket[3523] = {67, 73}; // Netherstorm
+    zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas
+
+    // Wrath of the Lich King - Zones
+    zone2LevelBracket[65] = {71, 77}; // Dragonblight
+    zone2LevelBracket[66] = {74, 80}; // Zul'Drak
+    zone2LevelBracket[67] = {77, 80}; // Storm Peaks
+    zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier
+    zone2LevelBracket[394] = {72, 78}; // Grizzly Hills
+    zone2LevelBracket[495] = {68, 74}; // Howling Fjord
+    zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest
+    zone2LevelBracket[3537] = {68, 75}; // Borean Tundra
+    zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin
+    zone2LevelBracket[4197] = {79, 80}; // Wintergrasp
+}
+
 void RandomPlayerbotMgr::PrepareTeleportCache()
 {
     uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
@@ -1542,6 +1634,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
 
     if (sPlayerbotAIConfig->enableNewRpgStrategy)
     {
+        PrepareZone2LevelBracket();
         LOG_INFO("playerbots", "Preparing innkeepers locations for level...");
         results = WorldDatabase.Query(
         "SELECT "
@@ -1550,8 +1643,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
         "position_y, "
         "position_z, "
         "orientation, "
-        "t.faction, "
-        "t.entry "
+        "t.faction "
         "FROM "
         "creature c "
         "INNER JOIN creature_template t on c.id1 = t.entry "
@@ -1573,7 +1665,6 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
                 float z = fields[3].Get<float>();
                 float orient = fields[4].Get<float>();
                 uint32 faction = fields[5].Get<uint32>();
-                uint32 c_entry = fields[6].Get<uint32>();
                 const FactionTemplateEntry* entry = sFactionTemplateStore.LookupEntry(faction);
 
                 WorldLocation loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
@@ -1581,35 +1672,13 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
                 Map* map = sMapMgr->FindMap(loc.GetMapId(), 0);
                 if (!map)
                     continue;
-                const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(1, x, y, z));
+                const AreaTableEntry* area = sAreaTableStore.LookupEntry(map->GetAreaId(PHASEMASK_NORMAL, x, y, z));
                 uint32 zoneId = area->zone ? area->zone : area->ID;
-                uint32 level = area->area_level;
-                for (int i = 5; i <= maxLevel; i++)
+                if (zone2LevelBracket.find(zoneId) == zone2LevelBracket.end())
+                    continue;
+                LevelBracket bracket = zone2LevelBracket[zoneId];
+                for (int i = bracket.low; i <= bracket.high; i++)
                 {
-                    std::vector<WorldLocation>& locs = locsPerLevelCache[i];
-                    int counter = 0;
-                    WorldLocation levelLoc;
-                    for (auto& checkLoc : locs)
-                    {
-                        if (loc.GetMapId() != checkLoc.GetMapId())
-                            continue;
-                        
-                        if (loc.GetExactDist(checkLoc) > 1500.0f)
-                            continue;
-                        
-                        if (zoneId != 
-                            map->GetZoneId(1, checkLoc.GetPositionX(), checkLoc.GetPositionY(), checkLoc.GetPositionZ()))
-                            continue;
-
-                        counter++;
-                        levelLoc = checkLoc;
-                        if (counter >= 15)
-                            break;
-                    }
-
-                    if (counter < 15)
-                        continue;
-
                     if (!(entry->hostileMask & 4))
                     {
                         hordeStarterPerLevelCache[i].push_back(loc);
@@ -1618,12 +1687,10 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
                     {
                         allianceStarterPerLevelCache[i].push_back(loc);
                     }
-                    LOG_DEBUG("playerbots", "Area: {} Level: {} creature_entry: {} add to: {} {}({},{},{},{})", area->ID,
-                            level, c_entry, i, counter, levelLoc.GetPositionX(), levelLoc.GetPositionY(),
-                            levelLoc.GetPositionZ(), levelLoc.GetMapId());
                 }
             } while (results->NextRow());
         }
+
         // add all initial position
         for (uint32 i = 1; i < MAX_RACES; i++)
         {
@@ -2629,9 +2696,8 @@ void RandomPlayerbotMgr::PrintStats()
     uint32 engine_noncombat = 0;
     uint32 engine_combat = 0;
     uint32 engine_dead = 0;
-    uint32 stateCount[MAX_TRAVEL_STATE + 1] = {0};
-    std::vector<std::pair<Quest const*, int32>> questCount;
     std::unordered_map<NewRpgStatus, int> rpgStatusCount;
+    NewRpgStatistic rpgStasticTotal;
     std::unordered_map<uint32, int> zoneCount;
     uint8 maxBotLevel = 0;
     for (PlayerBotMap::iterator i = playerBots.begin(); i != playerBots.end(); ++i)
@@ -2706,41 +2772,19 @@ void RandomPlayerbotMgr::PrintStats()
         zoneCount[bot->GetZoneId()]++;
 
         if (sPlayerbotAIConfig->enableNewRpgStrategy)
-            rpgStatusCount[botAI->rpgInfo.status]++;
-
-        if (TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get())
         {
-            TravelState state = target->getTravelState();
-            stateCount[state]++;
-
-            Quest const* quest;
-            if (target->getDestination())
-                quest = target->getDestination()->GetQuestTemplate();
-
-            if (quest)
-            {
-                bool found = false;
-
-                for (auto& q : questCount)
-                {
-                    if (q.first != quest)
-                        continue;
-
-                    q.second++;
-                    found = true;
-                }
-
-                if (!found)
-                    questCount.push_back(std::make_pair(quest, 1));
-            }
+            rpgStatusCount[botAI->rpgInfo.status]++;
+            rpgStasticTotal += botAI->rpgStatistic;
         }
     }
 
+    
     LOG_INFO("playerbots", "Bots level:");
     // uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
-    uint32 currentAlliance = 0, currentHorde = 0;
-    uint32 step = std::max(1, (maxBotLevel + 4) / 8);
-    uint32 from = 1;
+    uint32_t currentAlliance = 0, currentHorde = 0;
+    uint32_t step = std::max(1, static_cast<int>((maxBotLevel + 4) / 8));
+    uint32_t from = 1;
+
     for (uint8 i = 1; i <= maxBotLevel; ++i)
     {
         currentAlliance += alliance[i];
@@ -2800,19 +2844,18 @@ void RandomPlayerbotMgr::PrintStats()
     
     if (sPlayerbotAIConfig->enableNewRpgStrategy)
     {
-        LOG_INFO("playerbots", "Bots rpg status:", dead);
-        LOG_INFO("playerbots", "    IDLE: {}", rpgStatusCount[NewRpgStatus::IDLE]);
-        LOG_INFO("playerbots", "    REST: {}", rpgStatusCount[NewRpgStatus::REST]);
-        LOG_INFO("playerbots", "    GO_GRIND: {}", rpgStatusCount[NewRpgStatus::GO_GRIND]);
-        LOG_INFO("playerbots", "    GO_INNKEEPER: {}", rpgStatusCount[NewRpgStatus::GO_INNKEEPER]);
-        LOG_INFO("playerbots", "    NEAR_RANDOM: {}", rpgStatusCount[NewRpgStatus::NEAR_RANDOM]);
-        LOG_INFO("playerbots", "    NEAR_NPC: {}", rpgStatusCount[NewRpgStatus::NEAR_NPC]);
+        LOG_INFO("playerbots", "Bots rpg status:");
+        LOG_INFO("playerbots", "    Idle: {}, Rest: {}, GoGrind: {}, GoInnkeeper: {}, MoveRandom: {}, MoveNpc: {}, DoQuest: {}",
+            rpgStatusCount[RPG_IDLE], rpgStatusCount[RPG_REST], rpgStatusCount[RPG_GO_GRIND], rpgStatusCount[RPG_GO_INNKEEPER],
+            rpgStatusCount[RPG_NEAR_RANDOM], rpgStatusCount[RPG_NEAR_NPC], rpgStatusCount[RPG_DO_QUEST]);
+
+        LOG_INFO("playerbots", "Bots total quests:");
+        LOG_INFO("playerbots", "    Accepted: {}, Rewarded: {}, Dropped: {}",
+            rpgStasticTotal.questAccepted, rpgStasticTotal.questRewarded, rpgStasticTotal.questDropped);
     }
 
     LOG_INFO("playerbots", "Bots engine:", dead);
-    LOG_INFO("playerbots", "    Non-combat: {}", engine_noncombat);
-    LOG_INFO("playerbots", "    Combat: {}", engine_combat);
-    LOG_INFO("playerbots", "    Dead: {}", engine_dead);
+    LOG_INFO("playerbots", "    Non-combat: {}, Combat: {}, Dead: {}", engine_noncombat, engine_combat, engine_dead);
 
     // LOG_INFO("playerbots", "Bots questing:");
     // LOG_INFO("playerbots", "    Picking quests: {}",
diff --git a/modules/mod-playerbots/src/RandomPlayerbotMgr.h b/modules/mod-playerbots/src/RandomPlayerbotMgr.h
index 4206a97..fc9ab95 100644
--- a/modules/mod-playerbots/src/RandomPlayerbotMgr.h
+++ b/modules/mod-playerbots/src/RandomPlayerbotMgr.h
@@ -171,12 +171,19 @@ public:
     static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; }
 
     void PrepareAddclassCache();
+    void PrepareZone2LevelBracket();
     void PrepareTeleportCache();
     void Init();
     std::map<uint8, std::unordered_set<ObjectGuid>> addclassCache;
     std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
     std::map<uint8, std::vector<WorldLocation>> allianceStarterPerLevelCache;
     std::map<uint8, std::vector<WorldLocation>> hordeStarterPerLevelCache;
+    struct LevelBracket {
+        uint32 low;
+        uint32 high;
+        bool InsideBracket(uint32 val) { return val >= low && val <= high; }
+    };
+    std::map<uint32, LevelBracket> zone2LevelBracket;
     std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
 protected:
     void OnBotLoginInternal(Player* const bot) override;
diff --git a/modules/mod-playerbots/src/factory/PlayerbotFactory.cpp b/modules/mod-playerbots/src/factory/PlayerbotFactory.cpp
index c98bf5c..f45b525 100644
--- a/modules/mod-playerbots/src/factory/PlayerbotFactory.cpp
+++ b/modules/mod-playerbots/src/factory/PlayerbotFactory.cpp
@@ -31,6 +31,7 @@
 #include "PlayerbotAIConfig.h"
 #include "PlayerbotDbStore.h"
 #include "Playerbots.h"
+#include "QuestDef.h"
 #include "RandomItemMgr.h"
 #include "RandomPlayerbotFactory.h"
 #include "ReputationMgr.h"
@@ -979,30 +980,23 @@ void PlayerbotFactory::ClearSpells()
 
 void PlayerbotFactory::ResetQuests()
 {
+    for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
+    {
+        bot->SetQuestSlot(slot, 0);
+    }
     ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
     for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
     {
         Quest const* quest = i->second;
 
         uint32 entry = quest->GetQuestId();
+        if (bot->GetQuestStatus(entry) == QUEST_STATUS_NONE)
+            continue;
+        
+        bot->RemoveRewardedQuest(entry);
+        bot->RemoveActiveQuest(entry, false);
 
-        // remove all quest entries for 'entry' from quest log
-        for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
-        {
-            uint32 quest = bot->GetQuestSlotQuestId(slot);
-            if (quest == entry)
-            {
-                bot->SetQuestSlot(slot, 0);
-            }
-        }
-
-        // reset rewarded for restart repeatable quest
-        bot->getQuestStatusMap().erase(entry);
-        // bot->getQuestStatusMap()[entry].m_rewarded = false;
-        // bot->getQuestStatusMap()[entry].m_status = QUEST_STATUS_NONE;
     }
-    // bot->UpdateForQuestWorldObjects();
-    CharacterDatabase.Execute("DELETE FROM character_queststatus WHERE guid = {}", bot->GetGUID().GetCounter());
 }
 
 void PlayerbotFactory::InitSpells() { InitAvailableSpells(); }
@@ -1618,6 +1612,11 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
         if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
             continue;
 
+        if (level < 5 && (slot != EQUIPMENT_SLOT_MAINHAND) && (slot != EQUIPMENT_SLOT_OFFHAND) &&
+            (slot != EQUIPMENT_SLOT_FEET) && (slot != EQUIPMENT_SLOT_LEGS) && (slot != EQUIPMENT_SLOT_CHEST) &&
+            (slot != EQUIPMENT_SLOT_RANGED))
+            continue;
+
         Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
 
         if (second_chance && oldItem)
@@ -1782,6 +1781,10 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
 
             if (level < 20 && (slot == EQUIPMENT_SLOT_FINGER1 || slot == EQUIPMENT_SLOT_FINGER2))
                 continue;
+            
+            if (level < 5 && (slot != EQUIPMENT_SLOT_MAINHAND) && (slot != EQUIPMENT_SLOT_OFFHAND) &&
+                (slot != EQUIPMENT_SLOT_FEET) && (slot != EQUIPMENT_SLOT_LEGS) && (slot != EQUIPMENT_SLOT_CHEST))
+                continue;
 
             if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
                 bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
diff --git a/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp b/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp
index 7408503..82b63c6 100644
--- a/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/AcceptQuestAction.cpp
@@ -87,7 +87,7 @@ bool AcceptQuestAction::Execute(Event event)
     {
         std::stringstream ss;
         ss << "AcceptQuestAction [" << qInfo->GetTitle() << "] - [" << std::to_string(qInfo->GetQuestId()) << "]";
-        LOG_INFO("playerbots", "{}", ss.str().c_str());
+        LOG_DEBUG("playerbots", "{}", ss.str().c_str());
         // botAI->TellMaster(ss.str());
     }
 
diff --git a/modules/mod-playerbots/src/strategy/actions/ActionContext.h b/modules/mod-playerbots/src/strategy/actions/ActionContext.h
index 0ffe08e..a56e711 100644
--- a/modules/mod-playerbots/src/strategy/actions/ActionContext.h
+++ b/modules/mod-playerbots/src/strategy/actions/ActionContext.h
@@ -135,6 +135,7 @@ public:
         creators["move to loot"] = &ActionContext::move_to_loot;
         creators["open loot"] = &ActionContext::open_loot;
         creators["guard"] = &ActionContext::guard;
+        creators["return to stay position"] = &ActionContext::return_to_stay_position;
         creators["move out of enemy contact"] = &ActionContext::move_out_of_enemy_contact;
         creators["set facing"] = &ActionContext::set_facing;
         creators["set behind"] = &ActionContext::set_behind;
@@ -247,6 +248,7 @@ public:
         creators["new rpg go innkeeper"] = &ActionContext::new_rpg_go_innkeeper;
         creators["new rpg move random"] = &ActionContext::new_rpg_move_random;
         creators["new rpg move npc"] = &ActionContext::new_rpg_move_npc;
+        creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
     }
 
 private:
@@ -270,6 +272,7 @@ private:
     static Action* drop_target(PlayerbotAI* botAI) { return new DropTargetAction(botAI); }
     static Action* attack_duel_opponent(PlayerbotAI* botAI) { return new AttackDuelOpponentAction(botAI); }
     static Action* guard(PlayerbotAI* botAI) { return new GuardAction(botAI); }
+    static Action* return_to_stay_position(PlayerbotAI* botAI) { return new ReturnToStayPositionAction(botAI); }
     static Action* open_loot(PlayerbotAI* botAI) { return new OpenLootAction(botAI); }
     static Action* move_to_loot(PlayerbotAI* botAI) { return new MoveToLootAction(botAI); }
     static Action* _return(PlayerbotAI* botAI) { return new ReturnAction(botAI); }
@@ -428,6 +431,7 @@ private:
     static Action* new_rpg_go_innkeeper(PlayerbotAI* ai) { return new NewRpgGoInnKeeperAction(ai); }
     static Action* new_rpg_move_random(PlayerbotAI* ai) { return new NewRpgMoveRandomAction(ai); }
     static Action* new_rpg_move_npc(PlayerbotAI* ai) { return new NewRpgMoveNpcAction(ai); }
+    static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
 };
 
 #endif
diff --git a/modules/mod-playerbots/src/strategy/actions/ChatActionContext.h b/modules/mod-playerbots/src/strategy/actions/ChatActionContext.h
index f4cd9cb..7b7e10b 100644
--- a/modules/mod-playerbots/src/strategy/actions/ChatActionContext.h
+++ b/modules/mod-playerbots/src/strategy/actions/ChatActionContext.h
@@ -75,6 +75,8 @@
 #include "WhoAction.h"
 #include "WtsAction.h"
 #include "OpenItemAction.h"
+#include "UnlockItemAction.h"
+#include "UnlockTradedItemAction.h"
 
 class ChatActionContext : public NamedObjectContext<Action>
 {
@@ -82,6 +84,8 @@ public:
     ChatActionContext()
     {
         creators["open items"] = &ChatActionContext::open_items;
+        creators["unlock items"] = &ChatActionContext::unlock_items;
+        creators["unlock traded item"] = &ChatActionContext::unlock_traded_item;
         creators["range"] = &ChatActionContext::range;
         creators["stats"] = &ChatActionContext::stats;
         creators["quests"] = &ChatActionContext::quests;
@@ -90,6 +94,7 @@ public:
         creators["log"] = &ChatActionContext::log;
         creators["los"] = &ChatActionContext::los;
         creators["rpg status"] = &ChatActionContext::rpg_status;
+        creators["rpg do quest"] = &ChatActionContext::rpg_do_quest;
         creators["aura"] = &ChatActionContext::aura;
         creators["drop"] = &ChatActionContext::drop;
         creators["clean quest log"] = &ChatActionContext::clean_quest_log;
@@ -183,6 +188,8 @@ public:
 
 private:
     static Action* open_items(PlayerbotAI* botAI) { return new OpenItemAction(botAI); }
+    static Action* unlock_items(PlayerbotAI* botAI) { return new UnlockItemAction(botAI); }
+    static Action* unlock_traded_item(PlayerbotAI* botAI) { return new UnlockTradedItemAction(botAI); }
     static Action* range(PlayerbotAI* botAI) { return new RangeAction(botAI); }
     static Action* flag(PlayerbotAI* botAI) { return new FlagAction(botAI); }
     static Action* craft(PlayerbotAI* botAI) { return new SetCraftAction(botAI); }
@@ -261,6 +268,7 @@ private:
     static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); }
     static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); }
     static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); }
+    static Action* rpg_do_quest(PlayerbotAI* botAI) { return new StartRpgDoQuestAction(botAI); }
     static Action* aura(PlayerbotAI* ai) { return new TellAuraAction(ai); }
     static Action* ll(PlayerbotAI* botAI) { return new LootStrategyAction(botAI); }
     static Action* ss(PlayerbotAI* botAI) { return new SkipSpellsListAction(botAI); }
diff --git a/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.cpp b/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.cpp
index 30d4c2f..f7e0216 100644
--- a/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.cpp
@@ -10,7 +10,7 @@
 #include "Playerbots.h"
 #include "PositionValue.h"
 
-void ReturnPositionResetAction::ResetReturnPosition()
+void PositionsResetAction::ResetReturnPosition()
 {
     PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
     PositionInfo pos = posMap["return"];
@@ -18,7 +18,7 @@ void ReturnPositionResetAction::ResetReturnPosition()
     posMap["return"] = pos;
 }
 
-void ReturnPositionResetAction::SetReturnPosition(float x, float y, float z)
+void PositionsResetAction::SetReturnPosition(float x, float y, float z)
 {
     PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
     PositionInfo pos = posMap["return"];
@@ -26,6 +26,22 @@ void ReturnPositionResetAction::SetReturnPosition(float x, float y, float z)
     posMap["return"] = pos;
 }
 
+void PositionsResetAction::ResetStayPosition()
+{
+    PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
+    PositionInfo pos = posMap["stay"];
+    pos.Reset();
+    posMap["stay"] = pos;
+}
+
+void PositionsResetAction::SetStayPosition(float x, float y, float z)
+{
+    PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
+    PositionInfo pos = posMap["stay"];
+    pos.Set(x, y, z, botAI->GetBot()->GetMapId());
+    posMap["stay"] = pos;
+}
+
 bool FollowChatShortcutAction::Execute(Event event)
 {
     Player* master = GetMaster();
@@ -34,7 +50,7 @@ bool FollowChatShortcutAction::Execute(Event event)
 
     // botAI->Reset();
     botAI->ChangeStrategy("+follow,-passive,-grind,-move from group", BOT_STATE_NON_COMBAT);
-    botAI->ChangeStrategy("-follow,-passive,-grind,-move from group", BOT_STATE_COMBAT);
+    botAI->ChangeStrategy("-stay,-follow,-passive,-grind,-move from group", BOT_STATE_COMBAT);
     botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Reset();
 
     PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
@@ -42,6 +58,10 @@ bool FollowChatShortcutAction::Execute(Event event)
     pos.Reset();
     posMap["return"] = pos;
 
+    pos = posMap["stay"];
+    pos.Reset();
+    posMap["stay"] = pos;
+
     if (bot->IsInCombat())
     {
         Formation* formation = AI_VALUE(Formation*, "formation");
@@ -103,9 +123,10 @@ bool StayChatShortcutAction::Execute(Event event)
 
     botAI->Reset();
     botAI->ChangeStrategy("+stay,-passive,-move from group", BOT_STATE_NON_COMBAT);
-    botAI->ChangeStrategy("-follow,-passive,-move from group", BOT_STATE_COMBAT);
+    botAI->ChangeStrategy("+stay,-follow,-passive,-move from group", BOT_STATE_COMBAT);
 
     SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
+    SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
 
     botAI->TellMaster("Staying");
     return true;
@@ -133,10 +154,11 @@ bool FleeChatShortcutAction::Execute(Event event)
         return false;
 
     botAI->Reset();
-    botAI->ChangeStrategy("+follow,+passive", BOT_STATE_NON_COMBAT);
-    botAI->ChangeStrategy("+follow,+passive", BOT_STATE_COMBAT);
+    botAI->ChangeStrategy("+follow,-stay,+passive", BOT_STATE_NON_COMBAT);
+    botAI->ChangeStrategy("+follow,-stay,+passive", BOT_STATE_COMBAT);
 
     ResetReturnPosition();
+    ResetStayPosition();
 
     if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig->sightDistance)
     {
@@ -155,10 +177,11 @@ bool GoawayChatShortcutAction::Execute(Event event)
         return false;
 
     botAI->Reset();
-    botAI->ChangeStrategy("+runaway", BOT_STATE_NON_COMBAT);
-    botAI->ChangeStrategy("+runaway", BOT_STATE_COMBAT);
+    botAI->ChangeStrategy("+runaway,-stay", BOT_STATE_NON_COMBAT);
+    botAI->ChangeStrategy("+runaway,-stay", BOT_STATE_COMBAT);
 
     ResetReturnPosition();
+    ResetStayPosition();
 
     botAI->TellMaster("Running away");
     return true;
@@ -171,9 +194,10 @@ bool GrindChatShortcutAction::Execute(Event event)
         return false;
 
     botAI->Reset();
-    botAI->ChangeStrategy("+grind,-passive", BOT_STATE_NON_COMBAT);
+    botAI->ChangeStrategy("+grind,-passive,-stay", BOT_STATE_NON_COMBAT);
 
     ResetReturnPosition();
+    ResetStayPosition();
 
     botAI->TellMaster("Grinding");
     return true;
@@ -193,6 +217,7 @@ bool TankAttackChatShortcutAction::Execute(Event event)
     botAI->ChangeStrategy("-passive", BOT_STATE_COMBAT);
 
     ResetReturnPosition();
+    ResetStayPosition();
 
     botAI->TellMaster("Attacking");
     return true;
diff --git a/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.h b/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.h
index 3ee0240..9089543 100644
--- a/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.h
+++ b/modules/mod-playerbots/src/strategy/actions/ChatShortcutActions.h
@@ -10,13 +10,15 @@
 
 class PlayerbotAI;
 
-class ReturnPositionResetAction : public Action
+class PositionsResetAction : public Action
 {
 public:
-    ReturnPositionResetAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) {}
+    PositionsResetAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) {}
 
     void ResetReturnPosition();
     void SetReturnPosition(float x, float y, float z);
+    void ResetStayPosition();
+    void SetStayPosition(float x, float y, float z);
 };
 
 class FollowChatShortcutAction : public MovementAction
@@ -27,10 +29,10 @@ public:
     bool Execute(Event event) override;
 };
 
-class StayChatShortcutAction : public ReturnPositionResetAction
+class StayChatShortcutAction : public PositionsResetAction
 {
 public:
-    StayChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "stay chat shortcut") {}
+    StayChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "stay chat shortcut") {}
 
     bool Execute(Event event) override;
 };
@@ -43,34 +45,34 @@ public:
     bool Execute(Event event) override;
 };
 
-class FleeChatShortcutAction : public ReturnPositionResetAction
+class FleeChatShortcutAction : public PositionsResetAction
 {
 public:
-    FleeChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "flee chat shortcut") {}
+    FleeChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "flee chat shortcut") {}
 
     bool Execute(Event event) override;
 };
 
-class GoawayChatShortcutAction : public ReturnPositionResetAction
+class GoawayChatShortcutAction : public PositionsResetAction
 {
 public:
-    GoawayChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "runaway chat shortcut") {}
+    GoawayChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "runaway chat shortcut") {}
 
     bool Execute(Event event) override;
 };
 
-class GrindChatShortcutAction : public ReturnPositionResetAction
+class GrindChatShortcutAction : public PositionsResetAction
 {
 public:
-    GrindChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "grind chat shortcut") {}
+    GrindChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "grind chat shortcut") {}
 
     bool Execute(Event event) override;
 };
 
-class TankAttackChatShortcutAction : public ReturnPositionResetAction
+class TankAttackChatShortcutAction : public PositionsResetAction
 {
 public:
-    TankAttackChatShortcutAction(PlayerbotAI* botAI) : ReturnPositionResetAction(botAI, "tank attack chat shortcut") {}
+    TankAttackChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "tank attack chat shortcut") {}
 
     bool Execute(Event event) override;
 };
diff --git a/modules/mod-playerbots/src/strategy/actions/ChooseTargetActions.cpp b/modules/mod-playerbots/src/strategy/actions/ChooseTargetActions.cpp
index 5481376..e4df1e0 100644
--- a/modules/mod-playerbots/src/strategy/actions/ChooseTargetActions.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/ChooseTargetActions.cpp
@@ -34,28 +34,17 @@ bool AttackAnythingAction::isUseful()
     if (!botAI->AllowActivity(GRIND_ACTIVITY))  // Bot not allowed to be active
         return false;
 
-    if (!AI_VALUE(bool, "can move around"))
+    if (botAI->HasStrategy("stay", BOT_STATE_NON_COMBAT))
+        return false;
+
+    if (bot->IsInCombat())
         return false;
-    
-        
-    // if (context->GetValue<TravelTarget*>("travel target")->Get()->isTraveling() &&
-    //     ChooseRpgTargetAction::isFollowValid(
-    //         bot, *context->GetValue<TravelTarget*>("travel target")->Get()->getPosition()))  // Bot is traveling
-    //     return false;
 
     Unit* target = GetTarget();
 
     if (!target)
         return false;
 
-    bool inactiveGrindStatus = botAI->rpgInfo.status == NewRpgStatus::GO_GRIND ||
-                               botAI->rpgInfo.status == NewRpgStatus::NEAR_NPC ||
-                               botAI->rpgInfo.status == NewRpgStatus::REST ||
-                               botAI->rpgInfo.status == NewRpgStatus::GO_INNKEEPER;
-
-    if (inactiveGrindStatus && bot->GetDistance(target) > 25.0f)
-        return false;
-
     std::string const name = std::string(target->GetName());
     // Check for invalid targets: Dummy, Charge Target, Melee Target, Ranged Target
     if (!name.empty() &&
diff --git a/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp b/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp
index eea9337..672358c 100644
--- a/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/DropQuestAction.cpp
@@ -132,6 +132,7 @@ bool CleanQuestLogAction::Execute(Event event)
             }
 
             // Remove quest
+            botAI->rpgStatistic.questDropped++;
             bot->SetQuestSlot(slot, 0);
             bot->TakeQuestSourceItem(questId, false);
             bot->SetQuestStatus(questId, QUEST_STATUS_NONE);
diff --git a/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp b/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp
index 2076b84..ff8407c 100644
--- a/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp
@@ -4,64 +4,23 @@
 #include "WorldPacket.h"
 #include "Player.h"
 #include "ObjectMgr.h"
-
 bool OpenItemAction::Execute(Event event)
 {
     bool foundOpenable = false;
 
-    // Check main inventory slots
-    for (uint8 slot = EQUIPMENT_SLOT_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
+    Item* item = botAI->FindOpenableItem();
+    if (item)
     {
-        Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
+        uint8 bag = item->GetBagSlot();  // Retrieves the bag slot (0 for main inventory)
+        uint8 slot = item->GetSlot();    // Retrieves the actual slot inside the bag
 
-        if (item && CanOpenItem(item))
-        {
-            OpenItem(item, INVENTORY_SLOT_BAG_0, slot);
-            foundOpenable = true;
-        }
-    }
-
-    // Check items in the bags
-    for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
-    {
-        Bag* bagItem = bot->GetBagByPos(bag);
-        if (!bagItem)
-            continue;
-
-        for (uint32 slot = 0; slot < bagItem->GetBagSize(); ++slot)
-        {
-            Item* item = bot->GetItemByPos(bag, slot);
-
-            if (item && CanOpenItem(item))
-            {
-                OpenItem(item, bag, slot);
-                foundOpenable = true;
-            }
-        }
-    }
-
-    // If no openable items found
-    if (!foundOpenable)
-    {
-        botAI->TellError("No openable items in inventory.");
+        OpenItem(item, bag, slot);
+        foundOpenable = true;
     }
 
     return foundOpenable;
 }
 
-bool OpenItemAction::CanOpenItem(Item* item)
-{
-    if (!item)
-        return false;
-
-    ItemTemplate const* itemTemplate = item->GetTemplate();
-    if (!itemTemplate)
-        return false;
-
-    // Check if the item has the openable flag
-    return itemTemplate->Flags & ITEM_FLAG_HAS_LOOT;
-}
-
 void OpenItemAction::OpenItem(Item* item, uint8 bag, uint8 slot)
 {
     WorldPacket packet(CMSG_OPEN_ITEM);
diff --git a/modules/mod-playerbots/src/strategy/actions/OpenItemAction.h b/modules/mod-playerbots/src/strategy/actions/OpenItemAction.h
index e60dd33..6ab73b7 100644
--- a/modules/mod-playerbots/src/strategy/actions/OpenItemAction.h
+++ b/modules/mod-playerbots/src/strategy/actions/OpenItemAction.h
@@ -21,9 +21,6 @@ public:
     bool Execute(Event event) override;
 
 private:
-    // Checks if the given item can be opened (i.e., has the openable flag)
-    bool CanOpenItem(Item* item);
-
     // Performs the action of opening the item
     void OpenItem(Item* item, uint8 bag, uint8 slot);
 };
diff --git a/modules/mod-playerbots/src/strategy/actions/PositionAction.cpp b/modules/mod-playerbots/src/strategy/actions/PositionAction.cpp
index df5fd50..3888f28 100644
--- a/modules/mod-playerbots/src/strategy/actions/PositionAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/PositionAction.cpp
@@ -159,3 +159,25 @@ bool ReturnAction::isUseful()
     PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier];
     return pos.isSet() && AI_VALUE2(float, "distance", "position_random") > sPlayerbotAIConfig->followDistance;
 }
+
+bool ReturnToStayPositionAction::isPossible()
+{
+    PositionMap& posMap = AI_VALUE(PositionMap&, "position");
+    PositionInfo stayPosition = posMap["stay"];
+    if (stayPosition.isSet())
+    {
+        const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
+        if (distance > sPlayerbotAIConfig->reactDistance)
+        {
+            botAI->TellMaster("The stay position is too far to return. I am going to stay where I am now");
+
+            // Set the stay position to current position
+            stayPosition.Set(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId());
+            posMap["stay"] = stayPosition;
+        }
+
+        return true;
+    }
+
+    return false;
+}
diff --git a/modules/mod-playerbots/src/strategy/actions/PositionAction.h b/modules/mod-playerbots/src/strategy/actions/PositionAction.h
index b53ba1b..16a671d 100644
--- a/modules/mod-playerbots/src/strategy/actions/PositionAction.h
+++ b/modules/mod-playerbots/src/strategy/actions/PositionAction.h
@@ -40,6 +40,13 @@ public:
     GuardAction(PlayerbotAI* botAI) : MoveToPositionAction(botAI, "move to position", "guard") {}
 };
 
+class ReturnToStayPositionAction : public MoveToPositionAction
+{
+public:
+    ReturnToStayPositionAction(PlayerbotAI* ai) : MoveToPositionAction(ai, "move to position", "stay") {}
+    virtual bool isPossible();
+};
+
 class SetReturnPositionAction : public Action
 {
 public:
diff --git a/modules/mod-playerbots/src/strategy/actions/QuestAction.cpp b/modules/mod-playerbots/src/strategy/actions/QuestAction.cpp
index 4ecb4ff..2920194 100644
--- a/modules/mod-playerbots/src/strategy/actions/QuestAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/QuestAction.cpp
@@ -4,9 +4,14 @@
  */
 
 #include "QuestAction.h"
+#include <sstream>
 
+#include "Chat.h"
 #include "ChatHelper.h"
 #include "Event.h"
+#include "ItemTemplate.h"
+#include "ObjectGuid.h"
+#include "ObjectMgr.h"
 #include "Playerbots.h"
 #include "ReputationMgr.h"
 #include "ServerFacade.h"
@@ -258,6 +263,7 @@ bool QuestAction::AcceptQuest(Quest const* quest, ObjectGuid questGiver)
 
 bool QuestUpdateCompleteAction::Execute(Event event)
 {
+    // the action can hardly be triggered
     WorldPacket p(event.getPacket());
     p.rpos(0);
 
@@ -265,22 +271,26 @@ bool QuestUpdateCompleteAction::Execute(Event event)
     p >> questId;
 
     p.print_storage();
-    LOG_INFO("playerbots", "Packet: empty{} questId{}", p.empty(), questId);
+    // LOG_INFO("playerbots", "Packet: empty{} questId{}", p.empty(), questId);
 
     Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId);
     if (qInfo)
     {
-        std::map<std::string, std::string> placeholders;
+        // std::map<std::string, std::string> placeholders;
+        // placeholders["%quest_link"] = format;
+        
+        // if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
+        // {
+            //     LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), qInfo->GetTitle());
+            //     bot->Say("Quest [ " + format + " ] completed", LANG_UNIVERSAL);
+            // }
         const auto format = ChatHelper::FormatQuest(qInfo);
-        placeholders["%quest_link"] = format;
-
-        if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
-        {
-            LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), qInfo->GetTitle());
-            bot->Say("Quest [ " + format + " ] completed", LANG_UNIVERSAL);
-        }
-        botAI->TellMasterNoFacing("Quest completed " + format);
+        if (botAI->GetMaster())
+            botAI->TellMasterNoFacing("Quest completed " + format);
         BroadcastHelper::BroadcastQuestUpdateComplete(botAI, bot, qInfo);
+        botAI->rpgStatistic.questCompleted++;
+        // LOG_DEBUG("playerbots", "[New rpg] {} complete quest {}", bot->GetName(), qInfo->GetQuestId());
+        // botAI->rpgStatistic.questCompleted++;
     }
 
     return true;
@@ -296,39 +306,39 @@ bool QuestUpdateAddKillAction::Execute(Event event)
 
     uint32 entry, questId, available, required;
     p >> questId >> entry >> available >> required;
-
+    // LOG_INFO("playerbots", "[New rpg] Quest {} -> Creature {} ({}/{})", questId, entry, available, required);
     const Quest* qInfo = sObjectMgr->GetQuestTemplate(questId);
     if (qInfo && (entry & 0x80000000))
     {
         entry &= 0x7FFFFFFF;
         const GameObjectTemplate* info = sObjectMgr->GetGameObjectTemplate(entry);
         if (info)
-            BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, info->name);
+        {
+            std::string infoName = botAI->GetLocalizedGameObjectName(entry);
+            BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, infoName);
+            if (botAI->GetMaster())
+            {
+                std::ostringstream out;
+                out << infoName << " " << available << "/" << required << " " << ChatHelper::FormatQuest(qInfo);
+                botAI->TellMasterNoFacing(out.str());
+            }
+        }
     }
     else if (qInfo)
     {
         CreatureTemplate const* info = sObjectMgr->GetCreatureTemplate(entry);
         if (info)
         {
-            BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, info->Name);
+            std::string infoName = botAI->GetLocalizedCreatureName(entry);
+            BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, infoName);
+            if (botAI->GetMaster())
+            {
+                std::ostringstream out;
+                out << infoName << " " << available << "/" << required << " " << ChatHelper::FormatQuest(qInfo);
+                botAI->TellMasterNoFacing(out.str());
+            }
         }
-    }
-    else
-    {
-        std::map<std::string, std::string> placeholders;
-        placeholders["%quest_id"] = questId;
-        placeholders["%available"] = available;
-        placeholders["%required"] = required;
-
-        if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT))
-        {
-            LOG_INFO("playerbots", "{} => {}", bot->GetName(), BOT_TEXT2("%available/%required for questId: %quest_id", placeholders));
-            botAI->Say(BOT_TEXT2("%available/%required for questId: %quest_id", placeholders));
-        }
-
-        botAI->TellMasterNoFacing(BOT_TEXT2("%available/%required for questId: %quest_id", placeholders));
-    }
-    
+    }    
     return false;
 }
 
@@ -363,8 +373,62 @@ bool QuestUpdateAddItemAction::Execute(Event event)
 
             BroadcastHelper::BroadcastQuestUpdateAddItem(botAI, bot, pair.first, availableItemsCount, requiredItemsCount, itemPrototype);
         }
-    }
+    } 
+    return false;
+}
+
+bool QuestItemPushResultAction::Execute(Event event)
+{
+    WorldPacket packet = event.getPacket();
+    ObjectGuid guid;
+    uint32 received, created, sendChatMessage, itemSlot, itemEntry, itemSuffixFactory, count, itemCount;
+    uint8 bagSlot;
+    int32 itemRandomPropertyId;
+    packet >> guid >> received >> created >> sendChatMessage;
+    packet >> bagSlot >> itemSlot >> itemEntry >> itemSuffixFactory >> itemRandomPropertyId;
+    packet >> count >> itemCount;
+
+    if (guid != bot->GetGUID())
+        return false;
     
+    const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemEntry);
+    if (!proto)
+        return false;
+
+    for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
+    {
+        uint32 questId = bot->GetQuestSlotQuestId(i);
+        if (!questId)
+            continue;
+
+        const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
+        if (!quest)
+            return false;
+
+        const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
+        
+        for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
+        {
+            uint32 itemId = quest->RequiredItemId[i];
+            if (!itemId)
+                continue;
+            
+            int32 previousCount = itemCount - count;
+            if (itemId == itemEntry && previousCount < quest->RequiredItemCount[i])
+            {
+                if (botAI->GetMaster())
+                {
+                    std::string itemLink = ChatHelper::FormatItem(proto);
+                    std::ostringstream out;
+                    int32 required = quest->RequiredItemCount[i];
+                    int32 available = std::min((int32)itemCount, required);
+                    out << itemLink << " " << available << "/" << required << " " << ChatHelper::FormatQuest(quest);
+                    botAI->TellMasterNoFacing(out.str());
+                }
+            }
+        }
+    }
+
     return false;
 }
 
diff --git a/modules/mod-playerbots/src/strategy/actions/QuestAction.h b/modules/mod-playerbots/src/strategy/actions/QuestAction.h
index 1d2497e..680154c 100644
--- a/modules/mod-playerbots/src/strategy/actions/QuestAction.h
+++ b/modules/mod-playerbots/src/strategy/actions/QuestAction.h
@@ -66,4 +66,11 @@ public:
     bool Execute(Event event) override;
 };
 
+class QuestItemPushResultAction : public Action
+{
+public:
+    QuestItemPushResultAction(PlayerbotAI* ai) : Action(ai, "quest item push result") {}
+    bool Execute(Event event) override;;
+};
+
 #endif
diff --git a/modules/mod-playerbots/src/strategy/actions/ReachTargetActions.cpp b/modules/mod-playerbots/src/strategy/actions/ReachTargetActions.cpp
index 9184336..f5e5101 100644
--- a/modules/mod-playerbots/src/strategy/actions/ReachTargetActions.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/ReachTargetActions.cpp
@@ -14,6 +14,12 @@ bool ReachTargetAction::Execute(Event event) { return ReachCombatTo(AI_VALUE(Uni
 
 bool ReachTargetAction::isUseful()
 {
+    // do not move while staying
+    if (botAI->HasStrategy("stay", botAI->GetState()))
+    {
+        return false;
+    }
+
     // do not move while casting
     if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
     {
@@ -30,6 +36,12 @@ std::string const ReachTargetAction::GetTargetName() { return "current target";
 
 bool CastReachTargetSpellAction::isUseful()
 {
+    // do not move while staying
+    if (botAI->HasStrategy("stay", botAI->GetState()))
+    {
+        return false;
+    }
+
     return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "current target"),
                                                 (distance + sPlayerbotAIConfig->contactDistance));
 }
diff --git a/modules/mod-playerbots/src/strategy/actions/SeeSpellAction.cpp b/modules/mod-playerbots/src/strategy/actions/SeeSpellAction.cpp
index dcce695..0fd90bc 100644
--- a/modules/mod-playerbots/src/strategy/actions/SeeSpellAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/SeeSpellAction.cpp
@@ -11,6 +11,7 @@
 #include "Playerbots.h"
 #include "RTSCValues.h"
 #include "RtscAction.h"
+#include "PositionValue.h"
 
 Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
                                     bool important)
@@ -123,6 +124,15 @@ bool SeeSpellAction::MoveToSpell(WorldPosition& spellPosition, bool inFormation)
     if (inFormation)
         SetFormationOffset(spellPosition);
 
+    if (botAI->HasStrategy("stay", botAI->GetState()))
+    {
+        PositionMap& posMap = AI_VALUE(PositionMap&, "position");
+        PositionInfo stayPosition = posMap["stay"];
+
+        stayPosition.Set(spellPosition.getX(), spellPosition.getY(), spellPosition.getZ(), spellPosition.getMapId());
+        posMap["stay"] = stayPosition;
+    }
+
     if (bot->IsWithinLOS(spellPosition.getX(), spellPosition.getY(), spellPosition.getZ()))
         return MoveNear(spellPosition.getMapId(), spellPosition.getX(), spellPosition.getY(), spellPosition.getZ(), 0);
 
diff --git a/modules/mod-playerbots/src/strategy/actions/StayActions.cpp b/modules/mod-playerbots/src/strategy/actions/StayActions.cpp
index b041f4e..cca32c1 100644
--- a/modules/mod-playerbots/src/strategy/actions/StayActions.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/StayActions.cpp
@@ -8,6 +8,7 @@
 #include "Event.h"
 #include "LastMovementValue.h"
 #include "Playerbots.h"
+#include "PositionValue.h"
 
 bool StayActionBase::Stay()
 {
@@ -42,6 +43,17 @@ bool StayAction::Execute(Event event) { return Stay(); }
 
 bool StayAction::isUseful()
 {
+    // Check if the bots is in stay position
+    PositionInfo stayPosition = AI_VALUE(PositionMap&, "position")["stay"];
+    if (stayPosition.isSet())
+    {
+        const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
+        if (sPlayerbotAIConfig->followDistance)
+        {
+            return false;
+        }
+    }
+
     // move from group takes priority over stay as it's added and removed automatically
     // (without removing/adding stay)
     if (botAI->HasStrategy("move from group", BOT_STATE_COMBAT) ||
diff --git a/modules/mod-playerbots/src/strategy/actions/TalkToQuestGiverAction.cpp b/modules/mod-playerbots/src/strategy/actions/TalkToQuestGiverAction.cpp
index 772d3ff..93f85d9 100644
--- a/modules/mod-playerbots/src/strategy/actions/TalkToQuestGiverAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/TalkToQuestGiverAction.cpp
@@ -173,7 +173,6 @@ void TalkToQuestGiverAction::RewardMultipleItem(Quest const* quest, Object* ques
     std::ostringstream outid;
     if (!botAI->IsAlt() || sPlayerbotAIConfig->autoPickReward == "yes")
     {
-        // Pick the first item of the best rewards.
         bestIds = BestRewards(quest);
         if (!bestIds.empty())
         {
diff --git a/modules/mod-playerbots/src/strategy/actions/TradeStatusAction.cpp b/modules/mod-playerbots/src/strategy/actions/TradeStatusAction.cpp
index 577d46d..ad42136 100644
--- a/modules/mod-playerbots/src/strategy/actions/TradeStatusAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/TradeStatusAction.cpp
@@ -24,12 +24,17 @@ bool TradeStatusAction::Execute(Event event)
         return false;
 
     PlayerbotAI* traderBotAI = GET_PLAYERBOT_AI(trader);
-    if (trader != master && !traderBotAI)
+    
+    // Allow the master and group members to trade
+    if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())))
     {
         bot->Whisper("I'm kind of busy now", LANG_UNIVERSAL, trader);
+        return false;
     }
 
-    if ((trader != master || !botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, true, master)) &&
+    // Allow trades from group members or bots
+    if ((!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())) &&
+        (trader != master || !botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, true, master)) &&
         !traderBotAI)
     {
         WorldPacket p;
@@ -109,9 +114,9 @@ bool TradeStatusAction::Execute(Event event)
             bot->SetFacingToObject(trader);
 
         BeginTrade();
+
         return true;
     }
-
     return false;
 }
 
diff --git a/modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.cpp b/modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.cpp
new file mode 100644
index 0000000..ada779e
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.cpp
@@ -0,0 +1,84 @@
+#include "TradeStatusExtendedAction.h"
+#include "Event.h"
+#include "Player.h"
+#include "PlayerbotAI.h"
+#include "WorldPacket.h"
+#include "TradeData.h"
+
+bool TradeStatusExtendedAction::Execute(Event event)
+{
+    Player* trader = bot->GetTrader();
+    if (!trader)
+        return false;
+
+    TradeData* tradeData = trader->GetTradeData();
+    if (!tradeData)
+        return false;
+
+    WorldPacket p(event.getPacket());
+    p.rpos(0);
+
+    uint8 isTraderData;
+    uint32 unknown1, slotCount1, slotCount2, tradeGold, spellCast;
+    p >> isTraderData;
+    p >> unknown1;
+    p >> slotCount1;
+    p >> slotCount2;
+    p >> tradeGold;
+    p >> spellCast;
+
+    for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i)
+    {
+        uint8 tradeSlot;
+        p >> tradeSlot;
+
+        if (tradeSlot >= TRADE_SLOT_COUNT)
+            break; // End of packet
+
+        uint32 itemId, displayId, count, wrapped, lockId;
+        uint64 giftCreator, creator;
+        uint32 permEnchant, gem1, gem2, gem3;
+        uint32 spellCharges, suffixFactor, randomProp, maxDurability, durability;
+
+        p >> itemId;
+        p >> displayId;
+        p >> count;
+        p >> wrapped;
+        p >> giftCreator;
+        p >> permEnchant;
+        p >> gem1;
+        p >> gem2;
+        p >> gem3;
+        p >> creator;
+        p >> spellCharges;
+        p >> suffixFactor;
+        p >> randomProp;
+        p >> lockId;
+        p >> maxDurability;
+        p >> durability;
+
+        // Check for locked items in "Do Not Trade" slot
+        if (tradeSlot == TRADE_SLOT_NONTRADED && lockId > 0)
+        {
+            // Get the actual item reference from TradeData
+            Item* lockbox = tradeData->GetItem(TRADE_SLOT_NONTRADED);
+            if (!lockbox)
+            {
+                return false;
+            }
+
+            if (bot->getClass() == CLASS_ROGUE && bot->HasSpell(1804) && lockbox->IsLocked()) // Pick Lock spell
+            {
+                // botAI->CastSpell(1804, bot, lockbox); // Attempt to cast Pick Lock on the lockbox
+                botAI->DoSpecificAction("unlock traded item");
+                botAI->SetNextCheckDelay(4000); // Delay before accepting trade
+            }
+            else
+            {
+                botAI->TellMaster("I can't unlock this item.");
+            }
+        }
+    }
+
+    return true;
+}
diff --git a/modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.h b/modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.h
new file mode 100644
index 0000000..8797d8d
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/actions/TradeStatusExtendedAction.h
@@ -0,0 +1,17 @@
+#ifndef _PLAYERBOT_TRADESTATUSEXTENDEDACTION_H
+#define _PLAYERBOT_TRADESTATUSEXTENDEDACTION_H
+
+#include "QueryItemUsageAction.h"
+
+class Player;
+class PlayerbotAI;
+
+class TradeStatusExtendedAction : public QueryItemUsageAction
+{
+public:
+    TradeStatusExtendedAction(PlayerbotAI* botAI) : QueryItemUsageAction(botAI, "trade status extended") {}
+
+    bool Execute(Event event) override;
+};
+
+#endif
diff --git a/modules/mod-playerbots/src/strategy/actions/UnlockItemAction.cpp b/modules/mod-playerbots/src/strategy/actions/UnlockItemAction.cpp
new file mode 100644
index 0000000..367a4fc
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/actions/UnlockItemAction.cpp
@@ -0,0 +1,38 @@
+#include "UnlockItemAction.h"
+#include "PlayerbotAI.h"
+#include "ItemTemplate.h"
+#include "WorldPacket.h"
+#include "Player.h"
+#include "ObjectMgr.h"
+#include "SpellInfo.h"
+
+#define PICK_LOCK_SPELL_ID 1804
+
+bool UnlockItemAction::Execute(Event event)
+{
+    bool foundLockedItem = false;
+
+    Item* item = botAI->FindLockedItem();
+    if (item)
+    {
+        UnlockItem(item);
+        foundLockedItem = true;
+    }
+
+    return foundLockedItem;
+}
+
+void UnlockItemAction::UnlockItem(Item* item)
+{
+    // Use CastSpell to unlock the item
+    if (botAI->CastSpell(PICK_LOCK_SPELL_ID, bot, item))
+    {
+        std::ostringstream out;
+        out << "Used Pick Lock on: " << item->GetTemplate()->Name1;
+        botAI->TellMaster(out.str());
+    }
+    else
+    {
+        botAI->TellError("Failed to cast Pick Lock.");
+    }
+}
diff --git a/modules/mod-playerbots/src/strategy/actions/UnlockItemAction.h b/modules/mod-playerbots/src/strategy/actions/UnlockItemAction.h
new file mode 100644
index 0000000..a4738da
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/actions/UnlockItemAction.h
@@ -0,0 +1,19 @@
+#ifndef _PLAYERBOT_UNLOCKITEMACTION_H
+#define _PLAYERBOT_UNLOCKITEMACTION_H
+
+#include "Action.h"
+
+class PlayerbotAI;
+
+class UnlockItemAction : public Action
+{
+public:
+    UnlockItemAction(PlayerbotAI* botAI) : Action(botAI, "unlock item") { }
+
+    bool Execute(Event event) override;
+
+private:
+    void UnlockItem(Item* item);
+};
+
+#endif
diff --git a/modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.cpp b/modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.cpp
new file mode 100644
index 0000000..22d9c14
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.cpp
@@ -0,0 +1,98 @@
+#include "UnlockTradedItemAction.h"
+#include "Playerbots.h"
+#include "TradeData.h"
+#include "SpellInfo.h"
+
+#define PICK_LOCK_SPELL_ID 1804
+
+bool UnlockTradedItemAction::Execute(Event event)
+{
+    Player* trader = bot->GetTrader();
+    if (!trader)
+        return false; // No active trade session
+
+    TradeData* tradeData = trader->GetTradeData();
+    if (!tradeData)
+        return false; // No trade data available
+
+    Item* lockbox = tradeData->GetItem(TRADE_SLOT_NONTRADED);
+    if (!lockbox)
+    {
+        botAI->TellError("No item in the Do Not Trade slot.");
+        return false;
+    }
+
+    if (!CanUnlockItem(lockbox))
+    {
+        botAI->TellError("Cannot unlock this item.");
+        return false;
+    }
+
+    UnlockItem(lockbox);
+    return true;
+}
+
+bool UnlockTradedItemAction::CanUnlockItem(Item* item)
+{
+    if (!item)
+        return false;
+
+    ItemTemplate const* itemTemplate = item->GetTemplate();
+    if (!itemTemplate)
+        return false;
+
+    // Ensure the bot is a rogue and has Lockpicking skill
+    if (bot->getClass() != CLASS_ROGUE || !botAI->HasSkill(SKILL_LOCKPICKING))
+        return false;
+
+    // Ensure the item is actually locked
+    if (itemTemplate->LockID == 0 || !item->IsLocked())
+        return false;
+
+    // Check if the bot's Lockpicking skill is high enough
+    uint32 lockId = itemTemplate->LockID;
+    LockEntry const* lockInfo = sLockStore.LookupEntry(lockId);
+    if (!lockInfo)
+        return false;
+
+    uint32 botSkill = bot->GetSkillValue(SKILL_LOCKPICKING);
+    for (uint8 j = 0; j < 8; ++j)
+    {
+        if (lockInfo->Type[j] == LOCK_KEY_SKILL && SkillByLockType(LockType(lockInfo->Index[j])) == SKILL_LOCKPICKING)
+        {
+            uint32 requiredSkill = lockInfo->Skill[j];
+            if (botSkill >= requiredSkill)
+                return true;
+            else
+            {
+                std::ostringstream out;
+                out << "Lockpicking skill too low (" << botSkill << "/" << requiredSkill << ") to unlock: " 
+                    << item->GetTemplate()->Name1;
+                botAI->TellMaster(out.str());
+            }
+        }
+    }
+
+    return false;
+}
+
+void UnlockTradedItemAction::UnlockItem(Item* item)
+{
+    if (!bot->HasSpell(PICK_LOCK_SPELL_ID))
+    {
+        botAI->TellError("Cannot unlock, Pick Lock spell is missing.");
+        return;
+    }
+
+    // Use CastSpell to unlock the item
+    if (botAI->CastSpell(PICK_LOCK_SPELL_ID, bot->GetTrader(), item)) // Unit target is trader
+    {
+        std::ostringstream out;
+        out << "Picking Lock on traded item: " << item->GetTemplate()->Name1;
+        botAI->TellMaster(out.str());
+    }
+    else
+    {
+        botAI->TellError("Failed to cast Pick Lock.");
+    }
+}
diff --git a/modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.h b/modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.h
new file mode 100644
index 0000000..6349ab5
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/actions/UnlockTradedItemAction.h
@@ -0,0 +1,20 @@
+#ifndef _PLAYERBOT_UNLOCKTRADEDITEMACTION_H
+#define _PLAYERBOT_UNLOCKTRADEDITEMACTION_H
+
+#include "Action.h"
+
+class PlayerbotAI;
+
+class UnlockTradedItemAction : public Action
+{
+public:
+    UnlockTradedItemAction(PlayerbotAI* botAI) : Action(botAI, "unlock traded item") {}
+
+    bool Execute(Event event) override;
+
+private:
+    bool CanUnlockItem(Item* item);
+    void UnlockItem(Item* item);
+};
+
+#endif
diff --git a/modules/mod-playerbots/src/strategy/actions/UseMeetingStoneAction.cpp b/modules/mod-playerbots/src/strategy/actions/UseMeetingStoneAction.cpp
index 071b5b4..82ab1f7 100644
--- a/modules/mod-playerbots/src/strategy/actions/UseMeetingStoneAction.cpp
+++ b/modules/mod-playerbots/src/strategy/actions/UseMeetingStoneAction.cpp
@@ -11,6 +11,7 @@
 #include "GridNotifiersImpl.h"
 #include "PlayerbotAIConfig.h"
 #include "Playerbots.h"
+#include "PositionValue.h"
 
 bool UseMeetingStoneAction::Execute(Event event)
 {
@@ -224,6 +225,16 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
                 player->GetMotionMaster()->Clear();
                 AI_VALUE(LastMovement&, "last movement").clear();
                 player->TeleportTo(mapId, x, y, z, 0);
+
+                if (botAI->HasStrategy("stay", botAI->GetState()))
+                {
+                    PositionMap& posMap = AI_VALUE(PositionMap&, "position");
+                    PositionInfo stayPosition = posMap["stay"];
+
+                    stayPosition.Set(x,y, z, mapId);
+                    posMap["stay"] = stayPosition;
+                }
+
                 return true;
             }
         }
diff --git a/modules/mod-playerbots/src/strategy/actions/WorldPacketActionContext.h b/modules/mod-playerbots/src/strategy/actions/WorldPacketActionContext.h
index 947b482..2c32d11 100644
--- a/modules/mod-playerbots/src/strategy/actions/WorldPacketActionContext.h
+++ b/modules/mod-playerbots/src/strategy/actions/WorldPacketActionContext.h
@@ -37,6 +37,7 @@
 #include "TellCastFailedAction.h"
 #include "TellMasterAction.h"
 #include "TradeStatusAction.h"
+#include "TradeStatusExtendedAction.h"
 #include "UseMeetingStoneAction.h"
 #include "NamedObjectContext.h"
 
@@ -65,6 +66,7 @@ public:
         creators["check mount state"] = &WorldPacketActionContext::check_mount_state;
         creators["remember taxi"] = &WorldPacketActionContext::remember_taxi;
         creators["accept trade"] = &WorldPacketActionContext::accept_trade;
+        creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended;
         creators["store loot"] = &WorldPacketActionContext::store_loot;
 
         // quest
@@ -79,7 +81,8 @@ public:
         creators["quest update failed timer"] = &WorldPacketActionContext::quest_update_failed_timer;
         creators["quest update complete"] = &WorldPacketActionContext::quest_update_complete;
         creators["turn in query quest"] = &WorldPacketActionContext::turn_in_query_quest;
-
+        creators["quest item push result"] = &WorldPacketActionContext::quest_item_push_result;
+        
         creators["party command"] = &WorldPacketActionContext::party_command;
         creators["tell cast failed"] = &WorldPacketActionContext::tell_cast_failed;
         creators["accept duel"] = &WorldPacketActionContext::accept_duel;
@@ -117,6 +120,7 @@ private:
     static Action* party_command(PlayerbotAI* botAI) { return new PartyCommandAction(botAI); }
     static Action* store_loot(PlayerbotAI* botAI) { return new StoreLootAction(botAI); }
     static Action* accept_trade(PlayerbotAI* botAI) { return new TradeStatusAction(botAI); }
+    static Action* trade_status_extended(PlayerbotAI* botAI) { return new TradeStatusExtendedAction(botAI); }
     static Action* remember_taxi(PlayerbotAI* botAI) { return new RememberTaxiAction(botAI); }
     static Action* check_mount_state(PlayerbotAI* botAI) { return new CheckMountStateAction(botAI); }
     static Action* area_trigger(PlayerbotAI* botAI) { return new AreaTriggerAction(botAI); }
@@ -139,6 +143,7 @@ private:
     static Action* quest_update_failed(PlayerbotAI* ai) { return new QuestUpdateFailedAction(ai); }
     static Action* quest_update_failed_timer(PlayerbotAI* ai) { return new QuestUpdateFailedTimerAction(ai); }
     static Action* quest_update_complete(PlayerbotAI* botAI) { return new QuestUpdateCompleteAction(botAI); }
+    static Action* quest_item_push_result(PlayerbotAI* ai) { return new QuestItemPushResultAction(ai); }
 
     static Action* turn_in_quest(PlayerbotAI* botAI) { return new TalkToQuestGiverAction(botAI); }
     static Action* accept_quest(PlayerbotAI* botAI) { return new AcceptQuestAction(botAI); }
diff --git a/modules/mod-playerbots/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/modules/mod-playerbots/src/strategy/generic/ChatCommandHandlerStrategy.cpp
index 072814a..27f46f9 100644
--- a/modules/mod-playerbots/src/strategy/generic/ChatCommandHandlerStrategy.cpp
+++ b/modules/mod-playerbots/src/strategy/generic/ChatCommandHandlerStrategy.cpp
@@ -96,6 +96,10 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
 	    new TriggerNode("open items", NextAction::array(0, new NextAction("open items", relevance), nullptr)));
     triggers.push_back(
         new TriggerNode("qi", NextAction::array(0, new NextAction("query item usage", relevance), nullptr)));
+    triggers.push_back(
+	    new TriggerNode("unlock items", NextAction::array(0, new NextAction("unlock items", relevance), nullptr)));
+    triggers.push_back(
+	    new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr)));
 }
 
 ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@@ -109,6 +113,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
     supported.push_back("log");
     supported.push_back("los");
     supported.push_back("rpg status");
+    supported.push_back("rpg do quest");
     supported.push_back("aura");
     supported.push_back("drop");
     supported.push_back("share");
@@ -171,4 +176,6 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
     supported.push_back("calc");
     supported.push_back("open items");
     supported.push_back("qi");
+    supported.push_back("unlock items");
+    supported.push_back("unlock traded item");
 }
diff --git a/modules/mod-playerbots/src/strategy/generic/CombatStrategy.cpp b/modules/mod-playerbots/src/strategy/generic/CombatStrategy.cpp
index 6bbf09c..5026d3b 100644
--- a/modules/mod-playerbots/src/strategy/generic/CombatStrategy.cpp
+++ b/modules/mod-playerbots/src/strategy/generic/CombatStrategy.cpp
@@ -12,8 +12,9 @@ void CombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
 {
     triggers.push_back(new TriggerNode("enemy out of spell",
                                        NextAction::array(0, new NextAction("reach spell", ACTION_HIGH), nullptr)));
+    // drop target relevance 99 (lower than Worldpacket triggers)
     triggers.push_back(
-        new TriggerNode("invalid target", NextAction::array(0, new NextAction("drop target", 100), nullptr)));
+        new TriggerNode("invalid target", NextAction::array(0, new NextAction("drop target", 99), nullptr)));
     triggers.push_back(
         new TriggerNode("mounted", NextAction::array(0, new NextAction("check mount state", 54), nullptr)));
     // triggers.push_back(new TriggerNode("out of react range", NextAction::array(0, new NextAction("flee to master",
diff --git a/modules/mod-playerbots/src/strategy/generic/GrindingStrategy.cpp b/modules/mod-playerbots/src/strategy/generic/GrindingStrategy.cpp
index 8797063..e20b7fb 100644
--- a/modules/mod-playerbots/src/strategy/generic/GrindingStrategy.cpp
+++ b/modules/mod-playerbots/src/strategy/generic/GrindingStrategy.cpp
@@ -7,14 +7,19 @@
 
 #include "Playerbots.h"
 
-NextAction** GrindingStrategy::getDefaultActions() { return nullptr; }
+NextAction** GrindingStrategy::getDefaultActions()
+{
+    return NextAction::array(0,
+        new NextAction("drink", 4.2f),
+        new NextAction("food", 4.1f),
+        nullptr);
+}
 
 void GrindingStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
 {
-    triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("drink", 10.2f), nullptr)));
-    triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("food", 10.1f), nullptr)));
+    // reduce lower than loot
     triggers.push_back(
-        new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 10.0f), nullptr)));
+        new TriggerNode("no target", NextAction::array(0, new NextAction("attack anything", 4.0f), nullptr)));
 }
 
 void MoveRandomStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
diff --git a/modules/mod-playerbots/src/strategy/generic/LootNonCombatStrategy.cpp b/modules/mod-playerbots/src/strategy/generic/LootNonCombatStrategy.cpp
index 57cc296..89fb6b5 100644
--- a/modules/mod-playerbots/src/strategy/generic/LootNonCombatStrategy.cpp
+++ b/modules/mod-playerbots/src/strategy/generic/LootNonCombatStrategy.cpp
@@ -13,13 +13,13 @@ void LootNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
     triggers.push_back(
         new TriggerNode("far from loot target", NextAction::array(0, new NextAction("move to loot", 7.0f), nullptr)));
     triggers.push_back(new TriggerNode("can loot", NextAction::array(0, new NextAction("open loot", 8.0f), nullptr)));
-    triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("add all loot", 1.0f), nullptr)));
+    triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("add all loot", 5.0f), nullptr)));
 }
 
 void GatherStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
 {
     triggers.push_back(
-        new TriggerNode("timer", NextAction::array(0, new NextAction("add gathering loot", 2.0f), nullptr)));
+        new TriggerNode("timer", NextAction::array(0, new NextAction("add gathering loot", 5.0f), nullptr)));
 }
 
 void RevealStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
diff --git a/modules/mod-playerbots/src/strategy/generic/StayStrategy.cpp b/modules/mod-playerbots/src/strategy/generic/StayStrategy.cpp
index 8809971..b980275 100644
--- a/modules/mod-playerbots/src/strategy/generic/StayStrategy.cpp
+++ b/modules/mod-playerbots/src/strategy/generic/StayStrategy.cpp
@@ -7,6 +7,13 @@
 
 #include "Playerbots.h"
 
+void StayStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
+{
+    triggers.push_back(new TriggerNode(
+        "return to stay position",
+        NextAction::array(0, new NextAction("return to stay position", ACTION_MOVE), nullptr)));
+}
+
 NextAction** StayStrategy::getDefaultActions() { return NextAction::array(0, new NextAction("stay", 1.0f), nullptr); }
 
 void SitStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
diff --git a/modules/mod-playerbots/src/strategy/generic/StayStrategy.h b/modules/mod-playerbots/src/strategy/generic/StayStrategy.h
index 0d9d638..7667485 100644
--- a/modules/mod-playerbots/src/strategy/generic/StayStrategy.h
+++ b/modules/mod-playerbots/src/strategy/generic/StayStrategy.h
@@ -10,12 +10,13 @@
 
 class PlayerbotAI;
 
-class StayStrategy : public NonCombatStrategy
+class StayStrategy : public Strategy
 {
 public:
-    StayStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {}
+    StayStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
 
     std::string const getName() override { return "stay"; }
+    void InitTriggers(std::vector<TriggerNode*>& triggers) override;
     NextAction** getDefaultActions() override;
 };
 
diff --git a/modules/mod-playerbots/src/strategy/generic/WorldPacketHandlerStrategy.cpp b/modules/mod-playerbots/src/strategy/generic/WorldPacketHandlerStrategy.cpp
index d68cea4..dacac30 100644
--- a/modules/mod-playerbots/src/strategy/generic/WorldPacketHandlerStrategy.cpp
+++ b/modules/mod-playerbots/src/strategy/generic/WorldPacketHandlerStrategy.cpp
@@ -35,10 +35,15 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
                                                                           new NextAction("taxi", relevance), nullptr)));
     triggers.push_back(new TriggerNode("taxi done", NextAction::array(0, new NextAction("taxi", relevance), nullptr)));
     triggers.push_back(new TriggerNode("trade status", NextAction::array(0, new NextAction("accept trade", relevance), new NextAction("equip upgrades", relevance), nullptr)));
+    triggers.push_back(new TriggerNode("trade status extended", NextAction::array(0, new NextAction("trade status extended", relevance), nullptr)));
     triggers.push_back(new TriggerNode("area trigger", NextAction::array(0, new NextAction("reach area trigger", relevance), nullptr)));
     triggers.push_back(new TriggerNode("within area trigger", NextAction::array(0, new NextAction("area trigger", relevance), nullptr)));
     triggers.push_back(new TriggerNode("loot response", NextAction::array(0, new NextAction("store loot", relevance), nullptr)));
-    triggers.push_back(new TriggerNode("item push result", NextAction::array(0, new NextAction("query item usage", relevance), new NextAction("equip upgrades", relevance), nullptr)));
+    triggers.push_back(new TriggerNode("item push result", NextAction::array(0, new NextAction("unlock items", relevance),
+                                                                                new NextAction("open items", relevance),
+                                                                                new NextAction("query item usage", relevance), 
+                                                                                new NextAction("equip upgrades", relevance), nullptr)));
+    triggers.push_back(new TriggerNode("item push result", NextAction::array(0, new NextAction("quest item push result", relevance), nullptr)));
     triggers.push_back(new TriggerNode("ready check finished", NextAction::array(0, new NextAction("finish ready check", relevance), nullptr)));
     // triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("security check", relevance), new NextAction("check mail", relevance), nullptr)));
     triggers.push_back(new TriggerNode("guild invite", NextAction::array(0, new NextAction("guild accept", relevance), nullptr)));
@@ -79,7 +84,7 @@ WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* botAI) : Pas
 
     // quests
     supported.push_back("quest update add kill");
-    supported.push_back("quest update add item");
+    // supported.push_back("quest update add item");
     supported.push_back("quest update failed");
     supported.push_back("quest update failed timer");
     supported.push_back("quest update complete");
diff --git a/modules/mod-playerbots/src/strategy/rogue/RogueActions.cpp b/modules/mod-playerbots/src/strategy/rogue/RogueActions.cpp
index d037347..89d744c 100644
--- a/modules/mod-playerbots/src/strategy/rogue/RogueActions.cpp
+++ b/modules/mod-playerbots/src/strategy/rogue/RogueActions.cpp
@@ -8,8 +8,18 @@
 #include "Event.h"
 #include "ObjectGuid.h"
 #include "Player.h"
+#include "PlayerbotAIConfig.h"
 #include "Playerbots.h"
 
+bool CastStealthAction::isUseful()
+{
+    Unit* target = AI_VALUE(Unit*, "current target");
+    if (target && bot->GetDistance(target) >= sPlayerbotAIConfig->spellDistance)
+        return false;
+    return true;
+}
+
+
 bool CastStealthAction::isPossible()
 {
     // do not use with WSG flag or EYE flag
diff --git a/modules/mod-playerbots/src/strategy/rogue/RogueActions.h b/modules/mod-playerbots/src/strategy/rogue/RogueActions.h
index c6fc4b5..9b276cc 100644
--- a/modules/mod-playerbots/src/strategy/rogue/RogueActions.h
+++ b/modules/mod-playerbots/src/strategy/rogue/RogueActions.h
@@ -44,7 +44,7 @@ public:
     CastStealthAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "stealth") {}
 
     std::string const GetTargetName() override { return "self target"; }
-
+    bool isUseful() override;
     bool isPossible() override;
 };
 
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.cpp b/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.cpp
index 0f6697d..ecf795f 100644
--- a/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.cpp
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.cpp
@@ -2,18 +2,32 @@
 
 #include <cmath>
 #include <cstdint>
+#include <cstdlib>
 
+#include "ChatHelper.h"
+#include "G3D/Vector2.h"
+#include "GossipDef.h"
+#include "IVMapMgr.h"
+#include "NewRpgInfo.h"
 #include "NewRpgStrategy.h"
+#include "Object.h"
+#include "ObjectAccessor.h"
 #include "ObjectDefines.h"
 #include "ObjectGuid.h"
+#include "ObjectMgr.h"
 #include "PathGenerator.h"
 #include "Player.h"
 #include "PlayerbotAI.h"
 #include "Playerbots.h"
+#include "Position.h"
+#include "QuestDef.h"
 #include "Random.h"
 #include "RandomPlayerbotMgr.h"
+#include "SharedDefines.h"
+#include "StatsWeightCalculator.h"
 #include "Timer.h"
 #include "TravelMgr.h"
+#include "BroadcastHelper.h"
 #include "World.h"
 
 bool TellRpgStatusAction::Execute(Event event)
@@ -26,118 +40,156 @@ bool TellRpgStatusAction::Execute(Event event)
     return true;
 }
 
+bool StartRpgDoQuestAction::Execute(Event event)
+{
+    Player* owner = event.getOwner();
+    if (!owner)
+        return false;
+
+    std::string const text = event.getParam();
+    PlayerbotChatHandler ch(owner);
+    uint32 questId = ch.extractQuestId(text);
+    const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
+    if (quest) 
+    {
+        botAI->rpgInfo.ChangeToDoQuest(questId, quest);
+        bot->Whisper("Start to do quest " + std::to_string(questId), LANG_UNIVERSAL, owner);
+        return true;
+    }
+    bot->Whisper("Invalid quest " + text, LANG_UNIVERSAL, owner);
+    return false;
+}
+
 bool NewRpgStatusUpdateAction::Execute(Event event)
 {
     NewRpgInfo& info = botAI->rpgInfo;
+    /// @TODO: Refactor by transition probability
     switch (info.status)
     {
-        case NewRpgStatus::IDLE:
+        case RPG_IDLE:
         {
             uint32 roll = urand(1, 100);
             // IDLE -> NEAR_NPC
-            // if ((!info.lastNearNpc || info.lastNearNpc + setNpcInterval < getMSTime()) && roll <= 30)
             if (roll <= 30)
             {
-                GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
-                if (!possibleTargets.empty())
+                GuidVector possibleTargets = AI_VALUE(GuidVector, "possible new rpg targets");
+                if (possibleTargets.size() >= 3)
                 {
-                    info.Reset();
-                    info.lastNearNpc = getMSTime();
-                    info.status = NewRpgStatus::NEAR_NPC;
+                    info.ChangeToNearNpc();
                     return true;
                 }
             }
             // IDLE -> GO_INNKEEPER
             else if (roll <= 45)
             {
-                WorldPosition pos = SelectRandomInnKeeperPos();
+                WorldPosition pos = SelectRandomInnKeeperPos(bot);
                 if (pos != WorldPosition() && bot->GetExactDist(pos) > 50.0f)
                 {
-                    info.Reset();
-                    info.lastGoInnKeeper = getMSTime();
-                    info.status = NewRpgStatus::GO_INNKEEPER;
-                    info.innKeeperPos = pos;
+                    info.ChangeToGoInnkeeper(pos);
                     return true;
                 }
             }
             // IDLE -> GO_GRIND
-            else if (roll <= 90)
+            else if (roll <= 100)
             {
-                WorldPosition pos = SelectRandomGrindPos();
+                if (roll >= 60)
+                {
+                    std::vector<uint32> availableQuests;
+                    for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
+                    {
+                        uint32 questId = bot->GetQuestSlotQuestId(slot);
+                        if (botAI->lowPriorityQuest.find(questId) != botAI->lowPriorityQuest.end())
+                            continue;
+
+                        std::vector<POIInfo> poiInfo;
+                        if (GetQuestPOIPosAndObjectiveIdx(questId, poiInfo, true))
+                        {
+                            availableQuests.push_back(questId);
+                        }
+                    }
+                    if (availableQuests.size())
+                    {
+                        uint32 questId = availableQuests[urand(0, availableQuests.size() - 1)];
+                        const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
+                        if (quest)
+                        {
+                            // IDLE -> DO_QUEST
+                            info.ChangeToDoQuest(questId, quest);
+                            return true;
+                        }
+                    }
+                }
+                WorldPosition pos = SelectRandomGrindPos(bot);
                 if (pos != WorldPosition())
                 {
-                    info.Reset();
-                    info.lastGoGrind = getMSTime();
-                    info.status = NewRpgStatus::GO_GRIND;
-                    info.grindPos = pos;
+                    info.ChangeToGoGrind(pos);
                     return true;
                 }
             }
             // IDLE -> REST
-            info.Reset();
-            info.status = NewRpgStatus::REST;
-            info.lastRest = getMSTime();
+            info.ChangeToRest();
             bot->SetStandState(UNIT_STAND_STATE_SIT);
             return true;
         }
-        case NewRpgStatus::GO_GRIND:
+        case RPG_GO_GRIND:
         {
-            WorldPosition& originalPos = info.grindPos;
-            assert(info.grindPos != WorldPosition());
+            WorldPosition& originalPos = info.go_grind.pos;
+            assert(info.go_grind.pos != WorldPosition());
             // GO_GRIND -> NEAR_RANDOM
             if (bot->GetExactDist(originalPos) < 10.0f)
             {
-                info.Reset();
-                info.status = NewRpgStatus::NEAR_RANDOM;
-                info.lastNearRandom = getMSTime();
-                info.grindPos = WorldPosition();
+                info.ChangeToNearRandom();
                 return true;
             }
             break;
         }
-        case NewRpgStatus::GO_INNKEEPER:
+        case RPG_GO_INNKEEPER:
         {
-            WorldPosition& originalPos = info.innKeeperPos;
-            assert(info.innKeeperPos != WorldPosition());
+            WorldPosition& originalPos = info.go_innkeeper.pos;
+            assert(info.go_innkeeper.pos != WorldPosition());
             // GO_INNKEEPER -> NEAR_NPC
             if (bot->GetExactDist(originalPos) < 10.0f)
             {
-                info.Reset();
-                info.lastNearNpc = getMSTime();
-                info.status = NewRpgStatus::NEAR_NPC;
-                info.innKeeperPos = WorldPosition();
+                info.ChangeToNearNpc();
                 return true;
             }
             break;
         }
-        case NewRpgStatus::NEAR_RANDOM:
+        case RPG_NEAR_RANDOM:
         {
             // NEAR_RANDOM -> IDLE
-            if (info.lastNearRandom + statusNearRandomDuration < getMSTime())
+            if (info.HasStatusPersisted(statusNearRandomDuration))
             {
-                info.Reset();
-                info.status = NewRpgStatus::IDLE;
+                info.ChangeToIdle();
                 return true;
             }
             break;
         }
-        case NewRpgStatus::NEAR_NPC:
+        case RPG_DO_QUEST:
         {
-            if (info.lastNearNpc + statusNearNpcDuration < getMSTime())
+            // DO_QUEST -> IDLE
+            if (info.HasStatusPersisted(statusDoQuestDuration))
             {
-                info.Reset();
-                info.status = NewRpgStatus::IDLE;
+                info.ChangeToIdle();
                 return true;
             }
             break;
         }
-        case NewRpgStatus::REST:
+        case RPG_NEAR_NPC:
+        {
+            if (info.HasStatusPersisted(statusNearNpcDuration))
+            {
+                info.ChangeToIdle();
+                return true;
+            }
+            break;
+        }
+        case RPG_REST:
         {
             // REST -> IDLE
-            if (info.lastRest + statusRestDuration < getMSTime())
+            if (info.HasStatusPersisted(statusRestDuration))
             {
-                info.Reset();
-                info.status = NewRpgStatus::IDLE;
+                info.ChangeToIdle();
                 return true;
             }
             break;
@@ -148,258 +200,260 @@ bool NewRpgStatusUpdateAction::Execute(Event event)
     return false;
 }
 
-WorldPosition NewRpgStatusUpdateAction::SelectRandomGrindPos()
+bool NewRpgGoGrindAction::Execute(Event event)
 {
-    const std::vector<WorldLocation>& locs = sRandomPlayerbotMgr->locsPerLevelCache[bot->GetLevel()];
-    std::vector<WorldLocation> lo_prepared_locs, hi_prepared_locs;
-    for (auto& loc : locs)
-    {
-        if (bot->GetMapId() != loc.GetMapId())
-            continue;
+    if (SearchQuestGiverAndAcceptOrReward())
+        return true;
 
-        if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
-            bot->GetZoneId())
-            continue;
-
-        if (bot->GetExactDist(loc) < 500.0f)
-        {
-            hi_prepared_locs.push_back(loc);
-        }
-
-        if (bot->GetExactDist(loc) < 2500.0f)
-        {
-            lo_prepared_locs.push_back(loc);
-        }
-    }
-    WorldPosition dest{};
-    if (urand(1, 100) <= 50 && !hi_prepared_locs.empty())
-    {
-        uint32 idx = urand(0, hi_prepared_locs.size() - 1);
-        dest = hi_prepared_locs[idx];
-    }
-    else if (!lo_prepared_locs.empty())
-    {
-        uint32 idx = urand(0, lo_prepared_locs.size() - 1);
-        dest = lo_prepared_locs[idx];
-    }
-    LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})",
-              bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
-              hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size());
-    return dest;
+    return MoveFarTo(botAI->rpgInfo.go_grind.pos);
 }
 
-WorldPosition NewRpgStatusUpdateAction::SelectRandomInnKeeperPos()
+bool NewRpgGoInnKeeperAction::Execute(Event event)
 {
-    const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
-                                                 ? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()]
-                                                 : sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()];
-    std::vector<WorldLocation> prepared_locs;
-    for (auto& loc : locs)
-    {
-        if (bot->GetMapId() != loc.GetMapId())
-            continue;
-        
-        if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
-            bot->GetZoneId())
-            continue;
-            
-        float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f;
-        if (bot->GetExactDist(loc) < range)
-        {
-            prepared_locs.push_back(loc);
-        }
-    }
-    WorldPosition dest{};
-    if (!prepared_locs.empty())
-    {
-        uint32 idx = urand(0, prepared_locs.size() - 1);
-        dest = prepared_locs[idx];
-    }
-    LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})",
-              bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
-              prepared_locs.size(), locs.size());
-    return dest;
+    if (SearchQuestGiverAndAcceptOrReward())
+        return true;
+
+    return MoveFarTo(botAI->rpgInfo.go_innkeeper.pos);
 }
 
-bool NewRpgGoFarAwayPosAction::MoveFarTo(WorldPosition dest)
-{
-    if (dest == WorldPosition())
-        return false;
-
-    float dis = bot->GetExactDist(dest);
-    if (dis < pathFinderDis)
-    {
-        return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
-                      false, true);
-    }
-
-    // performance optimization
-    if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
-    {
-        return false;
-    }
-
-    // stuck check
-    float disToDest = bot->GetDistance(dest);
-    if (disToDest + 1.0f < botAI->rpgInfo.nearestMoveFarDis)
-    {
-        botAI->rpgInfo.nearestMoveFarDis = disToDest;
-        botAI->rpgInfo.stuckTs = getMSTime();
-        botAI->rpgInfo.stuckAttempts = 0;
-    }
-    else if (++botAI->rpgInfo.stuckAttempts >= 10 && botAI->rpgInfo.stuckTs + stuckTime < getMSTime())
-    {
-        // Unfortunately we've been stuck here for over 5 mins, fallback to teleporting directly to the destination
-        botAI->rpgInfo.stuckTs = getMSTime();
-        botAI->rpgInfo.stuckAttempts = 0;
-        const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
-        std::string zone_name = PlayerbotAI::GetLocalizedAreaName(entry);
-        LOG_DEBUG("playerbots", "[New Rpg] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", bot->GetName(),
-            bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(),
-            dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name);
-        return bot->TeleportTo(dest);
-    }
-
-    float minDelta = M_PI;
-    const float x = bot->GetPositionX();
-    const float y = bot->GetPositionY();
-    const float z = bot->GetPositionZ();
-    float rx, ry, rz;
-    bool found = false;
-    int attempt = 3;
-    while (--attempt)
-    {
-        float angle = bot->GetAngle(&dest);
-        float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
-        angle += delta;
-        float dis = rand_norm() * pathFinderDis;
-        float dx = x + cos(angle) * dis;
-        float dy = y + sin(angle) * dis;
-        float dz = z + 0.5f;
-        PathGenerator path(bot);
-        path.CalculatePath(dx, dy, dz);
-        PathType type = path.GetPathType();
-        uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
-        bool canReach = !(type & (~typeOk));
-
-        if (canReach && fabs(delta) <= minDelta)
-        {
-            found = true;
-            const G3D::Vector3& endPos = path.GetActualEndPosition();
-            rx = endPos.x;
-            ry = endPos.y;
-            rz = endPos.z;
-            minDelta = fabs(delta);
-        }
-    }
-    if (found)
-    {
-        return MoveTo(bot->GetMapId(), rx, ry, rz, false, false, false, true);
-    }
-    return false;
-}
-
-bool NewRpgGoGrindAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.grindPos); }
-
-bool NewRpgGoInnKeeperAction::Execute(Event event) { return MoveFarTo(botAI->rpgInfo.innKeeperPos); }
-
 bool NewRpgMoveRandomAction::Execute(Event event)
 {
-    float distance = rand_norm() * moveStep;
-    Map* map = bot->GetMap();
-    const float x = bot->GetPositionX();
-    const float y = bot->GetPositionY();
-    const float z = bot->GetPositionZ();
-    int attempts = 5;
-    while (--attempts)
-    {
-        float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
-        float dx = x + distance * cos(angle);
-        float dy = y + distance * sin(angle);
-        float dz = z;
-        if (!map->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
-                                                  dx, dy, dz))
-            continue;
-
-        if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight()))
-            continue;
-
-        bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true);
-        if (moved)
-            return true;
-    }
-
-    return false;
+    if (SearchQuestGiverAndAcceptOrReward())
+        return true;
+    
+    return MoveRandomNear();
 }
 
 bool NewRpgMoveNpcAction::Execute(Event event)
 {
     NewRpgInfo& info = botAI->rpgInfo;
-    if (!info.npcPos)
+    if (!info.near_npc.npcOrGo)
     {
-        GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
-        if (possibleTargets.empty())
-            return false;
-        int idx = urand(0, possibleTargets.size() - 1);
-        ObjectGuid guid = possibleTargets[idx];
-        Unit* unit = botAI->GetUnit(guid);
-        if (unit)
+        // No npc can be found, switch to IDLE
+        ObjectGuid npcOrGo = ChooseNpcOrGameObjectToInteract();
+        if (npcOrGo.IsEmpty())
         {
-            info.npcPos = GuidPosition(unit);
-            info.lastReachNpc = 0;
+            info.ChangeToIdle();
+            return true;
         }
-        else
-            return false;
+        info.near_npc.npcOrGo = npcOrGo;
+        info.near_npc.lastReach = 0;
+        return true;
     }
 
-    if (bot->GetDistance(info.npcPos) <= INTERACTION_DISTANCE)
+    WorldObject* object = ObjectAccessor::GetWorldObject(*bot, info.near_npc.npcOrGo);
+    if (object && bot->GetDistance(object) <= INTERACTION_DISTANCE)
     {
-        if (!info.lastReachNpc)
+        if (!info.near_npc.lastReach)
         {
-            info.lastReachNpc = getMSTime();
+            info.near_npc.lastReach = getMSTime();
+            if (bot->CanInteractWithQuestGiver(object))
+                InteractWithNpcOrGameObjectForQuest(info.near_npc.npcOrGo);
             return true;
         }
 
-        if (info.lastReachNpc && info.lastReachNpc + stayTime > getMSTime())
+        if (info.near_npc.lastReach && GetMSTimeDiffToNow(info.near_npc.lastReach) < npcStayTime)
             return false;
 
-        info.npcPos = GuidPosition();
-        info.lastReachNpc = 0;
+        // has reached the npc for more than `npcStayTime`, select the next target
+        info.near_npc.npcOrGo = ObjectGuid();
+        info.near_npc.lastReach = 0;
     }
     else
     {
-        assert(info.npcPos);
-        Unit* unit = botAI->GetUnit(info.npcPos);
-        if (!unit)
-            return false;
-        float x = unit->GetPositionX();
-        float y = unit->GetPositionY();
-        float z = unit->GetPositionZ();
-        float mapId = unit->GetMapId();
-        float angle = 0.f;
-        if (bot->IsWithinLOS(x, y, z))
-        {
-            if (!unit->isMoving())
-                angle = unit->GetAngle(bot) + (M_PI * irand(-25, 25) / 100.0);  // Closest 45 degrees towards the target
-            else
-                angle = unit->GetOrientation() +
-                        (M_PI * irand(-25, 25) / 100.0);  // 45 degrees infront of target (leading it's movement)
-        }
-        else
-            angle = 2 * M_PI * rand_norm();  // A circle around the target.
-        float rnd = rand_norm();
-        x += cos(angle) * INTERACTION_DISTANCE * rnd;
-        y += sin(angle) * INTERACTION_DISTANCE * rnd;
-        // bool exact = true;
-        if (!unit->GetMap()->CheckCollisionAndGetValidCoords(unit, unit->GetPositionX(), unit->GetPositionY(),
-                                                             unit->GetPositionZ(), x, y, z))
-        {
-            x = unit->GetPositionX();
-            y = unit->GetPositionY();
-            z = unit->GetPositionZ();
-            // exact = false;
-        }
-        return MoveTo(mapId, x, y, z, false, false, false, true);
+        return MoveWorldObjectTo(info.near_npc.npcOrGo);
     }
     return true;
-}
\ No newline at end of file
+}
+
+bool NewRpgDoQuestAction::Execute(Event event)
+{
+    if (SearchQuestGiverAndAcceptOrReward())
+        return true;
+
+    NewRpgInfo& info = botAI->rpgInfo;
+    uint32 questId = RPG_INFO(quest, questId);
+    const Quest* quest = RPG_INFO(quest, quest);
+    uint8 questStatus = bot->GetQuestStatus(questId);
+    switch (questStatus)
+    {
+        case QUEST_STATUS_INCOMPLETE:
+            return DoIncompleteQuest();
+        case QUEST_STATUS_COMPLETE:
+            return DoCompletedQuest();
+        default:
+            break;
+    }
+    botAI->rpgInfo.ChangeToIdle();
+    return true;
+}
+
+bool NewRpgDoQuestAction::DoIncompleteQuest()
+{
+    uint32 questId = RPG_INFO(do_quest, questId);
+    if (botAI->rpgInfo.do_quest.pos != WorldPosition())
+    {
+        /// @TODO: extract to a new function
+        int32 currentObjective = botAI->rpgInfo.do_quest.objectiveIdx;
+        // check if the objective has completed
+        Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
+        const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
+        bool completed = true;
+        if (currentObjective < QUEST_OBJECTIVES_COUNT)
+        {
+            if (q_status.CreatureOrGOCount[currentObjective] < quest->RequiredNpcOrGoCount[currentObjective])
+                completed = false;
+        }
+        else if (currentObjective < QUEST_OBJECTIVES_COUNT + QUEST_ITEM_OBJECTIVES_COUNT)
+        {
+            if (q_status.ItemCount[currentObjective - QUEST_OBJECTIVES_COUNT] <
+                quest->RequiredItemCount[currentObjective - QUEST_OBJECTIVES_COUNT])
+                completed = false;
+        }
+        // the current objective is completed, clear and find a new objective later
+        if (completed)
+        {
+            botAI->rpgInfo.do_quest.lastReachPOI = 0;
+            botAI->rpgInfo.do_quest.pos = WorldPosition();
+            botAI->rpgInfo.do_quest.objectiveIdx = 0;
+        }
+    }
+    if (botAI->rpgInfo.do_quest.pos == WorldPosition())
+    {
+        std::vector<POIInfo> poiInfo;
+        if (!GetQuestPOIPosAndObjectiveIdx(questId, poiInfo))
+        {
+            // can't find a poi pos to go, stop doing quest for now
+            botAI->rpgInfo.ChangeToIdle();
+            return true;
+        }
+        uint32 rndIdx = urand(0, poiInfo.size() - 1);
+        G3D::Vector2 nearestPoi = poiInfo[rndIdx].pos;
+        int32 objectiveIdx = poiInfo[rndIdx].objectiveIdx;
+
+        float dx = nearestPoi.x, dy = nearestPoi.y;
+
+        // z = MAX_HEIGHT as we do not know accurate z
+        float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
+
+        // double check for GetQuestPOIPosAndObjectiveIdx
+        if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
+            return false;
+
+        WorldPosition pos(bot->GetMapId(), dx, dy, dz);
+        botAI->rpgInfo.do_quest.lastReachPOI = 0;
+        botAI->rpgInfo.do_quest.pos = pos;
+        botAI->rpgInfo.do_quest.objectiveIdx = objectiveIdx;
+    }
+
+    if (bot->GetDistance(botAI->rpgInfo.do_quest.pos) > 10.0f && !botAI->rpgInfo.do_quest.lastReachPOI)
+    {
+        return MoveFarTo(botAI->rpgInfo.do_quest.pos);
+    }
+    // Now we are near the quest objective
+    // kill mobs and looting quest should be done automatically by grind strategy
+
+    if (!botAI->rpgInfo.do_quest.lastReachPOI)
+    {
+        botAI->rpgInfo.do_quest.lastReachPOI = getMSTime();
+        return true;
+    }
+    // stayed at this POI for more than 5 minutes
+    if (GetMSTimeDiffToNow(botAI->rpgInfo.do_quest.lastReachPOI) >= poiStayTime)
+    {
+        bool hasProgression = false;
+        int32 currentObjective = botAI->rpgInfo.do_quest.objectiveIdx;
+        // check if the objective has progression
+        Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
+        const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
+        if (currentObjective < QUEST_OBJECTIVES_COUNT)
+        {
+            if (q_status.CreatureOrGOCount[currentObjective] != 0 && quest->RequiredNpcOrGoCount[currentObjective])
+                hasProgression = true;
+        }
+        else if (currentObjective < QUEST_OBJECTIVES_COUNT + QUEST_ITEM_OBJECTIVES_COUNT)
+        {
+            if (q_status.ItemCount[currentObjective - QUEST_OBJECTIVES_COUNT] != 0 &&
+                quest->RequiredItemCount[currentObjective - QUEST_OBJECTIVES_COUNT])
+                hasProgression = true;
+        }
+        if (!hasProgression)
+        {
+            // we has reach the poi for more than 5 mins but no progession
+            // may not be able to complete this quest, marked as abandoned
+            /// @TODO: It may be better to make lowPriorityQuest a global set shared by all bots (or saved in db)
+            botAI->lowPriorityQuest.insert(questId);
+            botAI->rpgStatistic.questAbandoned++;
+            LOG_DEBUG("playerbots", "[New rpg] {} marked as abandoned quest {}", bot->GetName(), questId);
+            botAI->rpgInfo.ChangeToIdle();
+            return true;
+        }
+        // clear and select another poi later
+        botAI->rpgInfo.do_quest.lastReachPOI = 0;
+        botAI->rpgInfo.do_quest.pos = WorldPosition();
+        botAI->rpgInfo.do_quest.objectiveIdx = 0;
+        return true;
+    }
+
+    return MoveRandomNear(20.0f);
+}
+
+bool NewRpgDoQuestAction::DoCompletedQuest()
+{
+    uint32 questId = RPG_INFO(quest, questId);
+    const Quest* quest = RPG_INFO(quest, quest);
+    
+    if (RPG_INFO(quest, objectiveIdx) != -1)
+    {
+        // if quest is completed, back to poi with -1 idx to reward
+        BroadcastHelper::BroadcastQuestUpdateComplete(botAI, bot, quest);
+        botAI->rpgStatistic.questCompleted++;
+        std::vector<POIInfo> poiInfo;
+        if (!GetQuestPOIPosAndObjectiveIdx(questId, poiInfo, true))
+        {
+            // can't find a poi pos to reward, stop doing quest for now
+            botAI->rpgInfo.ChangeToIdle();
+            return false;
+        }
+        assert(poiInfo.size() > 0);
+        // now we get the place to get rewarded
+        float dx = poiInfo[0].pos.x, dy = poiInfo[0].pos.y;
+        // z = MAX_HEIGHT as we do not know accurate z
+        float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
+
+        // double check for GetQuestPOIPosAndObjectiveIdx
+        if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
+            return false;
+        
+        WorldPosition pos(bot->GetMapId(), dx, dy, dz);
+        botAI->rpgInfo.do_quest.lastReachPOI = 0;
+        botAI->rpgInfo.do_quest.pos = pos;
+        botAI->rpgInfo.do_quest.objectiveIdx = -1;
+    }
+
+    if (botAI->rpgInfo.do_quest.pos == WorldPosition())
+        return false;
+
+    if (bot->GetDistance(botAI->rpgInfo.do_quest.pos) > 10.0f && !botAI->rpgInfo.do_quest.lastReachPOI)
+        return MoveFarTo(botAI->rpgInfo.do_quest.pos);
+
+    // Now we are near the qoi of reward
+    // the quest should be rewarded by SearchQuestGiverAndAcceptOrReward
+    if (!botAI->rpgInfo.do_quest.lastReachPOI)
+    {
+        botAI->rpgInfo.do_quest.lastReachPOI = getMSTime();
+        return true;
+    }
+    // stayed at this POI for more than 5 minutes
+    if (GetMSTimeDiffToNow(botAI->rpgInfo.do_quest.lastReachPOI) >= poiStayTime)
+    {
+        // e.g. Can not reward quest to gameobjects
+        /// @TODO: It may be better to make lowPriorityQuest a global set shared by all bots (or saved in db)
+        botAI->lowPriorityQuest.insert(questId);
+        botAI->rpgStatistic.questAbandoned++;
+        LOG_DEBUG("playerbots", "[New rpg] {} marked as abandoned quest {}", bot->GetName(), questId);
+        botAI->rpgInfo.ChangeToIdle();
+        return true;
+    }
+    return false;
+}
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.h b/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.h
index 83cb604..422d577 100644
--- a/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.h
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgAction.h
@@ -3,9 +3,15 @@
 
 #include "Duration.h"
 #include "MovementActions.h"
+#include "NewRpgInfo.h"
 #include "NewRpgStrategy.h"
+#include "Object.h"
+#include "ObjectDefines.h"
+#include "ObjectGuid.h"
+#include "QuestDef.h"
 #include "TravelMgr.h"
 #include "PlayerbotAI.h"
+#include "NewRpgBaseAction.h"
 
 class TellRpgStatusAction : public Action
 {
@@ -15,65 +21,78 @@ public:
     bool Execute(Event event) override;
 };
 
-class NewRpgStatusUpdateAction : public Action
+class StartRpgDoQuestAction : public Action
 {
 public:
-    NewRpgStatusUpdateAction(PlayerbotAI* botAI) : Action(botAI, "new rpg status update") {}
+    StartRpgDoQuestAction(PlayerbotAI* botAI) : Action(botAI, "start rpg do quest") {}
+
+    bool Execute(Event event) override;
+};
+
+class NewRpgStatusUpdateAction : public NewRpgBaseAction
+{
+public:
+    NewRpgStatusUpdateAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg status update")
+    {
+        // int statusCount = RPG_STATUS_END - 1;
+
+        // transitionMat.resize(statusCount, std::vector<int>(statusCount, 0));
+
+        // transitionMat[RPG_IDLE][RPG_GO_GRIND] = 20;
+        // transitionMat[RPG_IDLE][RPG_GO_INNKEEPER] = 15;
+        // transitionMat[RPG_IDLE][RPG_NEAR_NPC] = 30;
+        // transitionMat[RPG_IDLE][RPG_DO_QUEST] = 35;
+    }
     bool Execute(Event event) override;
 protected:
-    // const int32 setGrindInterval = 5 * 60 * 1000;
-    // const int32 setNpcInterval = 1 * 60 * 1000;
+    // static NewRpgStatusTransitionProb transitionMat;
     const int32 statusNearNpcDuration = 5 * 60 * 1000;
     const int32 statusNearRandomDuration = 5 * 60 * 1000;
     const int32 statusRestDuration = 30 * 1000;
-    WorldPosition SelectRandomGrindPos();
-    WorldPosition SelectRandomInnKeeperPos();
+    const int32 statusDoQuestDuration = 30 * 60 * 1000;
 };
 
-class NewRpgGoFarAwayPosAction : public MovementAction
+class NewRpgGoGrindAction : public NewRpgBaseAction
 {
 public:
-    NewRpgGoFarAwayPosAction(PlayerbotAI* botAI, std::string name) : MovementAction(botAI, name) {}
-    // bool Execute(Event event) override;
-    bool MoveFarTo(WorldPosition dest);
-
-protected:
-    // WorldPosition dest;
-    const float pathFinderDis = 70.0f; // path finder
-    const uint32 stuckTime = 5 * 60 * 1000;
-};
-
-class NewRpgGoGrindAction : public NewRpgGoFarAwayPosAction
-{
-public:
-    NewRpgGoGrindAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go grind") {}
+    NewRpgGoGrindAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg go grind") {}
     bool Execute(Event event) override;
 };
 
-class NewRpgGoInnKeeperAction : public NewRpgGoFarAwayPosAction
+class NewRpgGoInnKeeperAction : public NewRpgBaseAction
 {
 public:
-    NewRpgGoInnKeeperAction(PlayerbotAI* botAI) : NewRpgGoFarAwayPosAction(botAI, "new rpg go innkeeper") {}
+    NewRpgGoInnKeeperAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg go innkeeper") {}
     bool Execute(Event event) override;
 };
 
 
-class NewRpgMoveRandomAction : public MovementAction
+class NewRpgMoveRandomAction : public NewRpgBaseAction
 {
 public:
-    NewRpgMoveRandomAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move random") {}
+    NewRpgMoveRandomAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg move random") {}
+    bool Execute(Event event) override;
+};
+
+class NewRpgMoveNpcAction : public NewRpgBaseAction
+{
+public:
+    NewRpgMoveNpcAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg move npcs") {}
+    bool Execute(Event event) override;
+
+    const uint32 npcStayTime = 8 * 1000;
+};
+
+class NewRpgDoQuestAction : public NewRpgBaseAction
+{
+public:
+    NewRpgDoQuestAction(PlayerbotAI* botAI) : NewRpgBaseAction(botAI, "new rpg do quest") {}
     bool Execute(Event event) override;
 protected:
-    const float moveStep = 50.0f;
-};
-
-class NewRpgMoveNpcAction : public MovementAction
-{
-public:
-    NewRpgMoveNpcAction(PlayerbotAI* botAI) : MovementAction(botAI, "new rpg move npcs") {}
-    bool Execute(Event event) override;
-protected:
-    const uint32 stayTime = 8 * 1000;
+    bool DoIncompleteQuest();
+    bool DoCompletedQuest();
+    
+    const uint32 poiStayTime = 5 * 60 * 1000;
 };
 
 #endif
\ No newline at end of file
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.cpp b/modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.cpp
new file mode 100644
index 0000000..3d387c1
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.cpp
@@ -0,0 +1,801 @@
+#include "NewRpgBaseAction.h"
+#include "ChatHelper.h"
+#include "G3D/Vector2.h"
+#include "GameObject.h"
+#include "GossipDef.h"
+#include "GridTerrainData.h"
+#include "IVMapMgr.h"
+#include "NewRpgInfo.h"
+#include "NewRpgStrategy.h"
+#include "Object.h"
+#include "ObjectAccessor.h"
+#include "ObjectDefines.h"
+#include "ObjectGuid.h"
+#include "ObjectMgr.h"
+#include "PathGenerator.h"
+#include "Player.h"
+#include "PlayerbotAI.h"
+#include "Playerbots.h"
+#include "Position.h"
+#include "QuestDef.h"
+#include "Random.h"
+#include "RandomPlayerbotMgr.h"
+#include "SharedDefines.h"
+#include "StatsWeightCalculator.h"
+#include "Timer.h"
+#include "TravelMgr.h"
+#include "BroadcastHelper.h"
+
+bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
+{
+    if (dest == WorldPosition())
+        return false;
+
+    if (dest != botAI->rpgInfo.moveFarPos)
+    {
+        // clear stuck information if it's a new dest
+        botAI->rpgInfo.SetMoveFarTo(dest);
+    }
+
+    float dis = bot->GetExactDist(dest);
+    if (dis < pathFinderDis)
+    {
+        return MoveTo(dest.getMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false,
+                      false, true);
+    }
+
+    // performance optimization
+    if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
+    {
+        return false;
+    }
+
+    // stuck check
+    float disToDest = bot->GetDistance(dest);
+    if (disToDest + 1.0f < botAI->rpgInfo.nearestMoveFarDis)
+    {
+        botAI->rpgInfo.nearestMoveFarDis = disToDest;
+        botAI->rpgInfo.stuckTs = getMSTime();
+        botAI->rpgInfo.stuckAttempts = 0;
+    }
+    else if (++botAI->rpgInfo.stuckAttempts >= 10 && GetMSTimeDiffToNow(botAI->rpgInfo.stuckTs) >= stuckTime)
+    {
+        // Unfortunately we've been stuck here for over 5 mins, fallback to teleporting directly to the destination
+        botAI->rpgInfo.stuckTs = getMSTime();
+        botAI->rpgInfo.stuckAttempts = 0;
+        const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
+        std::string zone_name = PlayerbotAI::GetLocalizedAreaName(entry);
+        LOG_DEBUG("playerbots", "[New Rpg] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", bot->GetName(),
+            bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(),
+            dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name);
+        return bot->TeleportTo(dest);
+    }
+
+    float minDelta = M_PI;
+    const float x = bot->GetPositionX();
+    const float y = bot->GetPositionY();
+    const float z = bot->GetPositionZ();
+    float rx, ry, rz;
+    bool found = false;
+    int attempt = 3;
+    while (--attempt)
+    {
+        float angle = bot->GetAngle(&dest);
+        float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
+        angle += delta;
+        float dis = rand_norm() * pathFinderDis;
+        float dx = x + cos(angle) * dis;
+        float dy = y + sin(angle) * dis;
+        float dz = z + 0.5f;
+        PathGenerator path(bot);
+        path.CalculatePath(dx, dy, dz);
+        PathType type = path.GetPathType();
+        uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
+        bool canReach = !(type & (~typeOk));
+
+        if (canReach && fabs(delta) <= minDelta)
+        {
+            found = true;
+            const G3D::Vector3& endPos = path.GetActualEndPosition();
+            rx = endPos.x;
+            ry = endPos.y;
+            rz = endPos.z;
+            minDelta = fabs(delta);
+        }
+    }
+    if (found)
+    {
+        return MoveTo(bot->GetMapId(), rx, ry, rz, false, false, false, true);
+    }
+    return false;
+}
+
+bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
+{
+    if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL))
+    {
+        return false;
+    }
+
+    WorldObject* object = botAI->GetWorldObject(guid);
+    if (!object)
+        return false;
+    float x = object->GetPositionX();
+    float y = object->GetPositionY();
+    float z = object->GetPositionZ();
+    float mapId = object->GetMapId();
+    float angle = 0.f;
+
+    if (!object->ToUnit() || !object->ToUnit()->isMoving())
+        angle = object->GetAngle(bot) + (M_PI * irand(-25, 25) / 100.0);  // Closest 45 degrees towards the target
+    else
+        angle = object->GetOrientation() +
+                (M_PI * irand(-25, 25) / 100.0);  // 45 degrees infront of target (leading it's movement)
+                
+    float rnd = rand_norm();
+    x += cos(angle) * distance * rnd;
+    y += sin(angle) * distance * rnd;
+    if (!object->GetMap()->CheckCollisionAndGetValidCoords(object, object->GetPositionX(), object->GetPositionY(),
+                                                           object->GetPositionZ(), x, y, z))
+    {
+        x = object->GetPositionX();
+        y = object->GetPositionY();
+        z = object->GetPositionZ();
+    }
+    return MoveTo(mapId, x, y, z, false, false, false, true);
+}
+
+bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority)
+{
+    if (IsWaitingForLastMove(priority))
+    {
+        return false;
+    }
+    
+    float distance = rand_norm() * moveStep;
+    Map* map = bot->GetMap();
+    const float x = bot->GetPositionX();
+    const float y = bot->GetPositionY();
+    const float z = bot->GetPositionZ();
+    int attempts = 5;
+    while (--attempts)
+    {
+        float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
+        float dx = x + distance * cos(angle);
+        float dy = y + distance * sin(angle);
+        float dz = z;
+
+        PathGenerator path(bot);
+        path.CalculatePath(dx, dy, dz);
+        PathType type = path.GetPathType();
+        uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
+        bool canReach = !(type & (~typeOk));
+
+        if (!canReach)
+            continue;
+
+        if (!map->CanReachPositionAndGetValidCoords(bot, dx, dy, dz))
+            continue;
+
+        if (map->IsInWater(bot->GetPhaseMask(), dx, dy, dz, bot->GetCollisionHeight()))
+            continue;
+
+        bool moved = MoveTo(bot->GetMapId(), dx, dy, dz, false, false, false, true, priority);
+        if (moved)
+            return true;
+    }
+
+    return false;
+}
+
+bool NewRpgBaseAction::ForceToWait(uint32 duration, MovementPriority priority)
+{
+    AI_VALUE(LastMovement&, "last movement").Set(bot->GetMapId(), 
+        bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), duration, priority);
+    return true;
+}
+
+/// @TODO: Fix redundant code
+/// Quest related method refer to TalkToQuestGiverAction.h
+bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid)
+{
+    WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
+    if (!object || !bot->CanInteractWithQuestGiver(object))
+        return false;
+
+    // Creature* creature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
+    // if (creature)
+    // {
+    //     WorldPacket packet(CMSG_GOSSIP_HELLO);
+    //     packet << guid;
+    //     bot->GetSession()->HandleGossipHelloOpcode(packet);
+    // }
+
+    bot->PrepareQuestMenu(guid);
+    const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu();
+    if (menu.Empty())
+        return true;
+
+    for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
+    {
+        const QuestMenuItem &item = menu.GetItem(idx);
+        const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
+        if (!quest)
+            continue;
+
+        const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
+        if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) &&
+            bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest))
+        {
+            AcceptQuest(quest, guid);
+            if (botAI->GetMaster())
+                botAI->TellMasterNoFacing("Quest accepted " + ChatHelper::FormatQuest(quest));
+            BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest);
+            botAI->rpgStatistic.questAccepted++;
+            LOG_DEBUG("playerbots", "[New rpg] {} accept quest {}", bot->GetName(), quest->GetQuestId());
+        }
+        if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false))
+        {
+            TurnInQuest(quest, guid);
+            if (botAI->GetMaster())
+                botAI->TellMasterNoFacing("Quest rewarded " + ChatHelper::FormatQuest(quest));
+            BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest);
+            botAI->rpgStatistic.questRewarded++;
+            LOG_DEBUG("playerbots", "[New rpg] {} turned in quest {}", bot->GetName(), quest->GetQuestId());
+        }
+    }
+    return true;
+}
+
+bool NewRpgBaseAction::AcceptQuest(Quest const* quest, ObjectGuid guid)
+{
+    WorldPacket p(CMSG_QUESTGIVER_ACCEPT_QUEST);
+    uint32 unk1 = 0;
+    p << guid << quest->GetQuestId() << unk1;
+    p.rpos(0);
+    bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p);
+
+    return true;
+}
+
+bool NewRpgBaseAction::TurnInQuest(Quest const* quest, ObjectGuid guid)
+{
+    uint32 questID = quest->GetQuestId();
+
+    if (bot->GetQuestRewardStatus(questID))
+    {
+        return false;
+    }
+
+    if (!bot->CanRewardQuest(quest, false))
+    {
+        return false;
+    }
+
+    bot->PlayDistanceSound(621);
+
+    WorldPacket p(CMSG_QUESTGIVER_CHOOSE_REWARD);
+    p << guid << quest->GetQuestId();
+    if (quest->GetRewChoiceItemsCount() <= 1)
+    {
+        p << 0;
+        bot->GetSession()->HandleQuestgiverChooseRewardOpcode(p);
+    }
+    else
+    {
+        uint32 bestId = BestReward(quest);
+        p << bestId;
+        bot->GetSession()->HandleQuestgiverChooseRewardOpcode(p);
+    }
+
+    return true;
+}
+
+uint32 NewRpgBaseAction::BestReward(Quest const* quest)
+{
+    ItemIds returnIds;
+    ItemUsage bestUsage = ITEM_USAGE_NONE;
+    if (quest->GetRewChoiceItemsCount() <= 1)
+        return 0;
+    else
+    {
+        for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
+        {
+            ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", quest->RewardChoiceItemId[i]);
+            if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE)
+                bestUsage = ITEM_USAGE_EQUIP;
+            else if (usage == ITEM_USAGE_BAD_EQUIP && bestUsage != ITEM_USAGE_EQUIP)
+                bestUsage = usage;
+            else if (usage != ITEM_USAGE_NONE && bestUsage == ITEM_USAGE_NONE)
+                bestUsage = usage;
+        }
+        StatsWeightCalculator calc(bot);
+        uint32 best = 0;
+        float bestScore = 0;
+        for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
+        {
+            ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", quest->RewardChoiceItemId[i]);
+            if (usage == bestUsage || usage == ITEM_USAGE_REPLACE)
+            {
+                float score = calc.CalculateItem(quest->RewardChoiceItemId[i]);
+                if (score > bestScore)
+                {
+                    bestScore = score;
+                    best = i;
+                }
+            }
+        }
+        return best;
+    }
+}
+
+bool NewRpgBaseAction::IsQuestWorthDoing(Quest const* quest)
+{
+    bool isLowLevelQuest = bot->GetLevel() > (bot->GetQuestLevel(quest) + sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF));
+
+    if (isLowLevelQuest)
+        return false;
+
+    if (quest->IsRepeatable())
+        return false;
+
+    if (quest->IsSeasonal())
+        return false;
+
+    return true;
+}
+
+bool NewRpgBaseAction::IsQuestCapableDoing(Quest const* quest)
+{
+    bool highLevelQuest = bot->GetLevel() + 3 < bot->GetQuestLevel(quest);
+    if (highLevelQuest)
+        return false;
+
+    // Elite quest and dungeon quest etc
+    if (quest->GetType() != 0)
+        return false;
+
+    // now we only capable of doing solo quests
+    if (quest->GetSuggestedPlayers() >= 2)
+        return false;
+
+    return true;
+}
+
+bool NewRpgBaseAction::OrganizeQuestLog()
+{
+    int32 freeSlotNum = 0;
+
+    for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
+    {
+        uint32 questId = bot->GetQuestSlotQuestId(i);
+        if (!questId)
+            freeSlotNum++;
+    }
+    
+    // it's ok if we have two more free slots
+    if (freeSlotNum >= 2)
+        return false;
+
+    int32 dropped = 0;
+    // remove quests that not worth doing or not capable of doing
+    for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
+    {
+        uint32 questId = bot->GetQuestSlotQuestId(i);
+        if (!questId)
+            continue;
+
+        const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
+        if (!IsQuestWorthDoing(quest) ||
+            !IsQuestCapableDoing(quest) ||
+            bot->GetQuestStatus(questId) == QUEST_STATUS_FAILED)
+        {
+            LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
+            WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
+            packet << (uint8)i;
+            bot->GetSession()->HandleQuestLogRemoveQuest(packet);
+            if (botAI->GetMaster())
+                botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
+            botAI->rpgStatistic.questDropped++;
+            dropped++;
+        }
+    }
+    
+    // drop more than 8 quests at once to avoid repeated accept and drop
+    if (dropped >= 8)
+        return true;
+
+    // remove festival/class quests and quests in different zone
+    for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
+    {
+        uint32 questId = bot->GetQuestSlotQuestId(i);
+        if (!questId)
+            continue;
+
+        const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
+        if (quest->GetZoneOrSort() < 0 ||
+            (quest->GetZoneOrSort() > 0 && quest->GetZoneOrSort() != bot->GetZoneId()))
+        {
+            LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
+            WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
+            packet << (uint8)i;
+            bot->GetSession()->HandleQuestLogRemoveQuest(packet);
+            if (botAI->GetMaster())
+                botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
+            botAI->rpgStatistic.questDropped++;
+            dropped++;
+        }
+    }
+
+    if (dropped >= 8)
+        return true;
+
+    // clear quests log
+    for (uint16 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
+    {
+        uint32 questId = bot->GetQuestSlotQuestId(i);
+        if (!questId)
+            continue;
+
+        const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
+        LOG_DEBUG("playerbots", "[New rpg] {} drop quest {}", bot->GetName(), questId);
+        WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);
+        packet << (uint8)i;
+        bot->GetSession()->HandleQuestLogRemoveQuest(packet);
+        if (botAI->GetMaster())
+            botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
+        botAI->rpgStatistic.questDropped++;
+    }
+
+    return true;
+}
+
+bool NewRpgBaseAction::SearchQuestGiverAndAcceptOrReward()
+{
+    OrganizeQuestLog();
+    if (ObjectGuid npcOrGo = ChooseNpcOrGameObjectToInteract(true, 80.0f))
+    {
+        WorldObject* object = ObjectAccessor::GetWorldObject(*bot, npcOrGo);
+        if (bot->CanInteractWithQuestGiver(object))
+        {
+            InteractWithNpcOrGameObjectForQuest(npcOrGo);
+            ForceToWait(5000);
+            return true;
+        }
+        return MoveWorldObjectTo(npcOrGo);
+    }
+    return false;
+}
+
+ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly, float distanceLimit)
+{
+    GuidVector possibleTargets = AI_VALUE(GuidVector, "possible new rpg targets");
+    GuidVector possibleGameObjects = AI_VALUE(GuidVector, "possible new rpg game objects");
+
+    if (possibleTargets.empty() && possibleGameObjects.empty())
+        return ObjectGuid();
+
+    WorldObject* nearestObject = nullptr;
+    for (ObjectGuid& guid: possibleTargets)
+    {
+        WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
+
+        if (!object || !object->IsInWorld())
+            continue;
+
+        if (distanceLimit && bot->GetDistance(object) > distanceLimit)
+            continue;
+        
+        if (HasQuestToAcceptOrReward(object))
+        {
+            if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object))
+                nearestObject = object;
+            break;
+        }
+    }
+
+    for (ObjectGuid& guid: possibleGameObjects)
+    {
+        WorldObject* object = ObjectAccessor::GetWorldObject(*bot, guid);
+
+        if (!object || !object->IsInWorld())
+            continue;
+
+        if (distanceLimit && bot->GetDistance(object) > distanceLimit)
+            continue;
+        
+        if (HasQuestToAcceptOrReward(object))
+        {
+            if (!nearestObject || bot->GetExactDist(nearestObject) > bot->GetExactDist(object))
+                nearestObject = object;
+            break;
+        }
+    }
+
+    if (nearestObject)
+        return nearestObject->GetGUID();
+
+    // No questgiver to accept or reward
+    if (questgiverOnly)
+        return ObjectGuid();
+
+    if (possibleTargets.empty())
+        return ObjectGuid();
+    
+    int idx = urand(0, possibleTargets.size() - 1);
+    ObjectGuid guid = possibleTargets[idx];
+    WorldObject* object = ObjectAccessor::GetCreatureOrPetOrVehicle(*bot, guid);
+    if (!object)
+        object = ObjectAccessor::GetGameObject(*bot, guid);
+
+    if (object && object->IsInWorld())
+    {
+        return object->GetGUID();
+    }
+    return ObjectGuid();
+}
+
+bool NewRpgBaseAction::HasQuestToAcceptOrReward(WorldObject* object)
+{
+    ObjectGuid guid = object->GetGUID();
+    bot->PrepareQuestMenu(guid);
+    const QuestMenu &menu = bot->PlayerTalkClass->GetQuestMenu();
+    if (menu.Empty())
+        return false;
+    
+    for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
+    {
+        const QuestMenuItem &item = menu.GetItem(idx);
+        const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
+        if (!quest)
+            continue;
+        const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
+        if (status == QUEST_STATUS_COMPLETE && bot->CanRewardQuest(quest, 0, false))
+        {
+            return true;
+        }
+    }
+    for (uint8 idx = 0; idx < menu.GetMenuItemCount(); idx++)
+    {
+        const QuestMenuItem &item = menu.GetItem(idx);
+        const Quest* quest = sObjectMgr->GetQuestTemplate(item.QuestId);
+        if (!quest)
+            continue;
+
+        const QuestStatus &status = bot->GetQuestStatus(item.QuestId);
+        if (status == QUEST_STATUS_NONE && bot->CanTakeQuest(quest, false) &&
+            bot->CanAddQuest(quest, false) && IsQuestWorthDoing(quest) && IsQuestCapableDoing(quest))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+static std::vector<float> GenerateRandomWeights(int n) {
+    std::vector<float> weights(n);
+    float sum = 0.0;
+
+    for (int i = 0; i < n; ++i) {
+        weights[i] = rand_norm();
+        sum += weights[i];
+    }
+    for (int i = 0; i < n; ++i) {
+        weights[i] /= sum;
+    }
+    return weights;
+}
+
+bool NewRpgBaseAction::GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo> &poiInfo, bool toComplete)
+{
+    Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
+    if (!quest)
+        return false;
+    
+    const QuestPOIVector* poiVector = sObjectMgr->GetQuestPOIVector(questId);
+    if (!poiVector)
+    {
+        return false;
+    }
+
+    const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
+
+    if (toComplete && q_status.Status == QUEST_STATUS_COMPLETE)
+    {
+        for (const QuestPOI &qPoi : *poiVector)
+        {
+            if (qPoi.MapId != bot->GetMapId())
+                continue;
+            
+            // not the poi pos to reward quest
+            if (qPoi.ObjectiveIndex != -1)
+                continue;
+            
+            if (qPoi.points.size() == 0)
+                continue;
+
+            float dx = 0, dy = 0;
+            std::vector<float> weights = GenerateRandomWeights(qPoi.points.size());
+            for (size_t i = 0; i < qPoi.points.size(); i++)
+            {
+                const QuestPOIPoint &point = qPoi.points[i];
+                dx += point.x * weights[i];
+                dy += point.y * weights[i];
+            }
+    
+            if (bot->GetDistance2d(dx, dy) >= 1500.0f)
+                continue;
+            
+            float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
+            
+            if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
+                continue;
+    
+            if (bot->GetZoneId() != bot->GetMap()->GetZoneId(bot->GetPhaseMask(), dx, dy, dz))
+                continue;
+    
+            poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex});
+        }
+
+        if (poiInfo.empty())
+            return false;
+
+        return true;
+    }
+
+    if (q_status.Status != QUEST_STATUS_INCOMPLETE)
+        return false;
+
+    // Get incomplete quest objective index
+    std::vector<int32> incompleteObjectiveIdx;
+    for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
+    {
+        int32 npcOrGo = quest->RequiredNpcOrGo[i];
+        if (!npcOrGo)
+            continue;
+        
+        if (q_status.CreatureOrGOCount[i] < quest->RequiredNpcOrGoCount[i])
+            incompleteObjectiveIdx.push_back(i);
+    }
+    for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
+    {
+        uint32 itemId = quest->RequiredItemId[i];
+        if (!itemId)
+            continue;
+
+        if (q_status.ItemCount[i] < quest->RequiredItemCount[i])
+            incompleteObjectiveIdx.push_back(QUEST_OBJECTIVES_COUNT + i);
+    }
+
+    // Get POIs to go
+    for (const QuestPOI &qPoi : *poiVector)
+    {
+        if (qPoi.MapId != bot->GetMapId())
+            continue;
+
+        bool inComplete = false;
+        for (uint32 objective : incompleteObjectiveIdx)
+        {
+            if (qPoi.ObjectiveIndex == objective)
+            {
+                inComplete = true;
+                break;
+            }
+        }
+        if (!inComplete)
+            continue;
+        if (qPoi.points.size() == 0)
+            continue;
+        float dx = 0, dy = 0;
+        std::vector<float> weights = GenerateRandomWeights(qPoi.points.size());
+        for (size_t i = 0; i < qPoi.points.size(); i++)
+        {
+            const QuestPOIPoint &point = qPoi.points[i];
+            dx += point.x * weights[i];
+            dy += point.y * weights[i];
+        }
+
+        if (bot->GetDistance2d(dx, dy) >= 1500.0f)
+            continue;
+        
+        float dz = std::max(bot->GetMap()->GetHeight(dx, dy, MAX_HEIGHT), bot->GetMap()->GetWaterLevel(dx, dy));
+        
+        if (dz == INVALID_HEIGHT || dz == VMAP_INVALID_HEIGHT_VALUE)
+            continue;
+
+        if (bot->GetZoneId() != bot->GetMap()->GetZoneId(bot->GetPhaseMask(), dx, dy, dz))
+            continue;
+
+        poiInfo.push_back({{dx, dy}, qPoi.ObjectiveIndex});
+    }
+
+    if (poiInfo.size() == 0) {
+        // LOG_DEBUG("playerbots", "[New rpg] {}: No available poi can be found for quest {}", bot->GetName(), questId);
+        return false;
+    }
+
+    return true;
+}
+
+WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot)
+{
+    const std::vector<WorldLocation>& locs = sRandomPlayerbotMgr->locsPerLevelCache[bot->GetLevel()];
+    float hiRange = 500.0f;
+    float loRange = 2500.0f;
+    if (bot->GetLevel() < 5)
+    {
+        hiRange /= 10;
+        loRange /= 10;
+    }
+    std::vector<WorldLocation> lo_prepared_locs, hi_prepared_locs;
+    for (auto& loc : locs)
+    {
+        if (bot->GetMapId() != loc.GetMapId())
+            continue;
+
+        if (bot->GetExactDist(loc) > 2500.0f)
+            continue;
+        
+        if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
+            bot->GetZoneId())
+            continue;
+
+        if (bot->GetExactDist(loc) < 500.0f)
+        {
+            hi_prepared_locs.push_back(loc);
+        }
+
+        if (bot->GetExactDist(loc) < 2500.0f)
+        {
+            lo_prepared_locs.push_back(loc);
+        }
+    }
+    WorldPosition dest{};
+    if (urand(1, 100) <= 50 && !hi_prepared_locs.empty())
+    {
+        uint32 idx = urand(0, hi_prepared_locs.size() - 1);
+        dest = hi_prepared_locs[idx];
+    }
+    else if (!lo_prepared_locs.empty())
+    {
+        uint32 idx = urand(0, lo_prepared_locs.size() - 1);
+        dest = lo_prepared_locs[idx];
+    }
+    LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})",
+              bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
+              hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size());
+    return dest;
+}
+
+WorldPosition NewRpgBaseAction::SelectRandomInnKeeperPos(Player* bot)
+{
+    const std::vector<WorldLocation>& locs = IsAlliance(bot->getRace())
+                                                 ? sRandomPlayerbotMgr->allianceStarterPerLevelCache[bot->GetLevel()]
+                                                 : sRandomPlayerbotMgr->hordeStarterPerLevelCache[bot->GetLevel()];
+    std::vector<WorldLocation> prepared_locs;
+    for (auto& loc : locs)
+    {
+        if (bot->GetMapId() != loc.GetMapId())
+            continue;
+        
+        float range = bot->GetLevel() <= 5 ? 500.0f : 2500.0f;
+        if (bot->GetExactDist(loc) > range)
+            continue;
+
+        if (bot->GetMap()->GetZoneId(bot->GetPhaseMask(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()) !=
+            bot->GetZoneId())
+            continue;
+        
+        prepared_locs.push_back(loc);
+    }
+    WorldPosition dest{};
+    if (!prepared_locs.empty())
+    {
+        uint32 idx = urand(0, prepared_locs.size() - 1);
+        dest = prepared_locs[idx];
+    }
+    LOG_DEBUG("playerbots", "[New Rpg] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})",
+              bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(),
+              prepared_locs.size(), locs.size());
+    return dest;
+}
\ No newline at end of file
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.h b/modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.h
new file mode 100644
index 0000000..62c415a
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgBaseAction.h
@@ -0,0 +1,58 @@
+#ifndef _PLAYERBOT_NEWRPGBASEACTION_H
+#define _PLAYERBOT_NEWRPGBASEACTION_H
+
+#include "Duration.h"
+#include "LastMovementValue.h"
+#include "MovementActions.h"
+#include "NewRpgStrategy.h"
+#include "Object.h"
+#include "ObjectDefines.h"
+#include "ObjectGuid.h"
+#include "QuestDef.h"
+#include "TravelMgr.h"
+#include "PlayerbotAI.h"
+
+struct POIInfo {
+    G3D::Vector2 pos;
+    int32 objectiveIdx;
+};
+
+/// A base (composition) class for all new rpg actions
+/// All functions that may be shared by multiple actions should be declared here
+/// And we should make all actions composable instead of inheritable
+class NewRpgBaseAction : public MovementAction
+{
+public:
+    NewRpgBaseAction(PlayerbotAI* botAI, std::string name) : MovementAction(botAI, name) {}
+    
+protected:
+    // MOVEMENT RELATED
+    bool MoveFarTo(WorldPosition dest);
+    bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE);
+    bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
+    bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
+
+    // QUEST RELATED
+    bool SearchQuestGiverAndAcceptOrReward();
+    ObjectGuid ChooseNpcOrGameObjectToInteract(bool questgiverOnly = false, float distanceLimit = 0.0f);
+    bool HasQuestToAcceptOrReward(WorldObject* object);
+    bool InteractWithNpcOrGameObjectForQuest(ObjectGuid guid);
+    bool AcceptQuest(Quest const* quest, ObjectGuid guid);
+    bool TurnInQuest(Quest const* quest, ObjectGuid guid);
+    uint32 BestReward(Quest const* quest);
+    bool IsQuestWorthDoing(Quest const* quest);
+    bool IsQuestCapableDoing(Quest const* quest);
+    bool OrganizeQuestLog();
+
+protected:
+    bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo> &poiInfo, bool toComplete = false);
+    static WorldPosition SelectRandomGrindPos(Player* bot);
+    static WorldPosition SelectRandomInnKeeperPos(Player* bot);
+
+protected:
+    // WorldPosition dest;
+    const float pathFinderDis = 70.0f; // path finder
+    const uint32 stuckTime = 5 * 60 * 1000;
+};
+
+#endif
\ No newline at end of file
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.cpp b/modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.cpp
new file mode 100644
index 0000000..a9c8264
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.cpp
@@ -0,0 +1,119 @@
+#include "NewRpgInfo.h"
+#include "Timer.h"
+
+void NewRpgInfo::ChangeToGoGrind(WorldPosition pos)
+{
+    Reset();
+    status = RPG_GO_GRIND;
+    go_grind = GoGrind();
+    go_grind.pos = pos;
+}
+
+void NewRpgInfo::ChangeToGoInnkeeper(WorldPosition pos)
+{
+    Reset();
+    status = RPG_GO_INNKEEPER;
+    go_innkeeper = GoInnkeeper();
+    go_innkeeper.pos = pos;
+}
+
+void NewRpgInfo::ChangeToNearNpc()
+{
+    Reset();
+    status = RPG_NEAR_NPC;
+    near_npc = NearNpc();
+}
+
+void NewRpgInfo::ChangeToNearRandom()
+{
+    Reset();
+    status = RPG_NEAR_RANDOM;
+    near_random = NearRandom();
+}
+
+void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
+{
+    Reset();
+    status = RPG_DO_QUEST;
+    do_quest = DoQuest();
+    do_quest.questId = questId;
+    do_quest.quest = quest;
+}
+
+void NewRpgInfo::ChangeToRest()
+{
+    Reset();
+    status = RPG_REST;
+    rest = Rest();
+}
+
+void NewRpgInfo::ChangeToIdle()
+{
+    Reset();
+    status = RPG_IDLE;
+}
+
+bool NewRpgInfo::CanChangeTo(NewRpgStatus status)
+{
+    return true;
+}
+
+void NewRpgInfo::Reset()
+{
+    *this = NewRpgInfo();
+    startT = getMSTime();
+}
+
+void NewRpgInfo::SetMoveFarTo(WorldPosition pos)
+{
+    nearestMoveFarDis = FLT_MAX;
+    stuckTs = 0;
+    stuckAttempts = 0;
+    moveFarPos = pos;
+}
+
+std::string NewRpgInfo::ToString()
+{
+    std::stringstream out;
+    out << "Status: ";
+    switch (status)
+    {
+        case RPG_GO_GRIND:
+            out << "GO_GRIND";
+            out << "\nGrindPos: " << go_grind.pos.GetMapId() << " " << go_grind.pos.GetPositionX() << " " << go_grind.pos.GetPositionY() << " " << go_grind.pos.GetPositionZ();
+            out << "\nlastGoGrind: " << startT;
+            break;
+        case RPG_GO_INNKEEPER:
+            out << "GO_INNKEEPER";
+            out << "\nInnKeeperPos: " << go_innkeeper.pos.GetMapId() << " " << go_innkeeper.pos.GetPositionX() << " " << go_innkeeper.pos.GetPositionY() << " " << go_innkeeper.pos.GetPositionZ();
+            out << "\nlastGoInnKeeper: " << startT;
+            break;
+        case RPG_NEAR_NPC:
+            out << "NEAR_NPC";
+            out << "\nnpcOrGoEntry: " << near_npc.npcOrGo.GetCounter();
+            out << "\nlastNearNpc: " << startT;
+            out << "\nlastReachNpcOrGo: " << near_npc.lastReach;
+            break;
+        case RPG_NEAR_RANDOM:
+            out << "NEAR_RANDOM";
+            out << "\nlastNearRandom: " << startT;
+            break;
+        case RPG_IDLE:
+            out << "IDLE";
+            break;
+        case RPG_REST:
+            out << "REST";
+            out << "\nlastRest: " << startT;
+            break;
+        case RPG_DO_QUEST:
+            out << "DO_QUEST";
+            out << "\nquestId: " << do_quest.questId;
+            out << "\nobjectiveIdx: " << do_quest.objectiveIdx;
+            out << "\npoiPos: " << do_quest.pos.GetMapId() << " " << do_quest.pos.GetPositionX() << " " << do_quest.pos.GetPositionY() << " " << do_quest.pos.GetPositionZ();
+            out << "\nlastReachPOI: " << do_quest.lastReachPOI;
+            break;
+        default:
+            out << "UNKNOWN";
+    }
+    return out.str();
+}
\ No newline at end of file
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.h b/modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.h
new file mode 100644
index 0000000..d4b39b2
--- /dev/null
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgInfo.h
@@ -0,0 +1,136 @@
+#ifndef _PLAYERBOT_NEWRPGINFO_H
+#define _PLAYERBOT_NEWRPGINFO_H
+
+#include "Define.h"
+#include "ObjectGuid.h"
+#include "ObjectMgr.h"
+#include "QuestDef.h"
+#include "Strategy.h"
+#include "Timer.h"
+#include "TravelMgr.h"
+
+enum NewRpgStatus: int
+{
+    RPG_STATUS_START = 0,
+    // Going to far away place
+    RPG_GO_GRIND = 0,
+    RPG_GO_INNKEEPER = 1,
+    // Exploring nearby
+    RPG_NEAR_RANDOM = 2,
+    RPG_NEAR_NPC = 3,
+    // Do Quest (based on quest status)
+    RPG_DO_QUEST = 4,
+    // Taking a break
+    RPG_REST = 5,
+    // Initial status
+    RPG_IDLE = 6,
+    RPG_STATUS_END = 7
+};
+
+using NewRpgStatusTransitionProb = std::vector<std::vector<int>>;
+
+struct NewRpgInfo
+{
+    NewRpgInfo() {}
+    
+    // RPG_GO_GRIND
+    struct GoGrind {
+        GoGrind() = default;
+        WorldPosition pos{};
+    };
+    // RPG_GO_INNKEEPER
+    struct GoInnkeeper {
+        GoInnkeeper() = default;
+        WorldPosition pos{};
+    };
+    // RPG_NEAR_NPC
+    struct NearNpc {
+        NearNpc() = default;
+        ObjectGuid npcOrGo{};
+        uint32 lastReach{0};
+    };
+    // RPG_NEAR_RANDOM
+    struct NearRandom {
+        NearRandom() = default;
+    };
+    // NewRpgStatus::QUESTING
+    struct DoQuest {
+        const Quest* quest{nullptr};
+        uint32 questId{0};
+        int32 objectiveIdx{0};
+        WorldPosition pos{};
+        uint32 lastReachPOI{0};
+    };
+    // RPG_REST
+    struct Rest {
+        Rest() = default;
+    };
+    struct Idle {
+    };
+    NewRpgStatus status{RPG_IDLE};
+
+    uint32 startT{0}; // start timestamp of the current status
+
+    // MOVE_FAR
+    float nearestMoveFarDis{FLT_MAX};
+    uint32 stuckTs{0};
+    uint32 stuckAttempts{0};
+    WorldPosition moveFarPos;
+    // END MOVE_FAR
+
+    union {
+        GoGrind go_grind;
+        GoInnkeeper go_innkeeper;
+        NearNpc near_npc;
+        NearRandom near_random;
+        DoQuest do_quest;
+        Rest rest;
+        DoQuest quest;
+    };
+
+    bool HasStatusPersisted(uint32 maxDuration) { return GetMSTimeDiffToNow(startT) > maxDuration; }
+    void ChangeToGoGrind(WorldPosition pos);
+    void ChangeToGoInnkeeper(WorldPosition pos);
+    void ChangeToNearNpc();
+    void ChangeToNearRandom();
+    void ChangeToDoQuest(uint32 questId, const Quest* quest);
+    void ChangeToRest();
+    void ChangeToIdle();
+    bool CanChangeTo(NewRpgStatus status);
+    void Reset();
+    void SetMoveFarTo(WorldPosition pos);
+    std::string ToString();
+};
+
+struct NewRpgStatistic
+{
+    uint32 questAccepted{0};
+    uint32 questCompleted{0};
+    uint32 questAbandoned{0};
+    uint32 questRewarded{0};
+    uint32 questDropped{0};
+    NewRpgStatistic operator+(const NewRpgStatistic& other) const
+    {
+        NewRpgStatistic result;
+        result.questAccepted = this->questAccepted + other.questAccepted;
+        result.questCompleted = this->questCompleted + other.questCompleted;
+        result.questAbandoned = this->questAbandoned + other.questAbandoned;
+        result.questRewarded = this->questRewarded + other.questRewarded;
+        result.questDropped = this->questDropped + other.questDropped;
+        return result;
+    }
+    NewRpgStatistic& operator+=(const NewRpgStatistic& other)
+    {
+        this->questAccepted += other.questAccepted;
+        this->questCompleted += other.questCompleted;
+        this->questAbandoned += other.questAbandoned;
+        this->questRewarded += other.questRewarded;
+        this->questDropped += other.questDropped;
+        return *this;
+    }
+};
+
+// not sure is it necessary but keep it for now
+#define RPG_INFO(x, y) botAI->rpgInfo.x.y
+
+#endif
\ No newline at end of file
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.cpp b/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.cpp
index 82bd03b..9a078d4 100644
--- a/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.cpp
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.cpp
@@ -11,22 +11,28 @@ NewRpgStrategy::NewRpgStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
 
 NextAction** NewRpgStrategy::getDefaultActions()
 {
-    return NextAction::array(0, new NextAction("new rpg status update", 5.0f), nullptr);
+    // the releavance should be greater than grind
+    return NextAction::array(0,
+        new NextAction("new rpg status update", 11.0f), 
+        nullptr);
 }
 
 void NewRpgStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
 {
     triggers.push_back(
-        new TriggerNode("go grind status", NextAction::array(0, new NextAction("new rpg go grind", 1.0f), nullptr)));
+        new TriggerNode("go grind status", NextAction::array(0, new NextAction("new rpg go grind", 3.0f), nullptr)));
     
     triggers.push_back(
-        new TriggerNode("go innkeeper status", NextAction::array(0, new NextAction("new rpg go innkeeper", 1.0f), nullptr)));
+        new TriggerNode("go innkeeper status", NextAction::array(0, new NextAction("new rpg go innkeeper", 3.0f), nullptr)));
 
     triggers.push_back(
-        new TriggerNode("near random status", NextAction::array(0, new NextAction("new rpg move random", 1.0f), nullptr)));
+        new TriggerNode("near random status", NextAction::array(0, new NextAction("new rpg move random", 3.0f), nullptr)));
 
     triggers.push_back(
-        new TriggerNode("near npc status", NextAction::array(0, new NextAction("new rpg move npc", 1.0f), nullptr)));
+        new TriggerNode("near npc status", NextAction::array(0, new NextAction("new rpg move npc", 3.0f), nullptr)));
+    
+    triggers.push_back(
+        new TriggerNode("do quest status", NextAction::array(0, new NextAction("new rpg do quest", 3.0f), nullptr)));
 }
 
 void NewRpgStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.h b/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.h
index 1c5a642..59fa61f 100644
--- a/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.h
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgStrategy.h
@@ -6,91 +6,12 @@
 #ifndef _PLAYERBOT_NEWRPGSTRATEGY_H
 #define _PLAYERBOT_NEWRPGSTRATEGY_H
 
-#include <cstdint>
 #include "Strategy.h"
 #include "TravelMgr.h"
+#include "NewRpgInfo.h"
 
 class PlayerbotAI;
 
-enum class NewRpgStatus
-{
-    // Going to far away place
-    GO_GRIND,
-    GO_INNKEEPER,
-    // Exploring nearby
-    NEAR_RANDOM,
-    NEAR_NPC,
-    // Taking a break
-    REST,
-    // Initial status
-    IDLE
-};
-
-struct NewRpgInfo
-{
-    NewRpgStatus status{NewRpgStatus::IDLE};
-    // NewRpgStatus::GO_GRIND
-    WorldPosition grindPos{};
-    uint32 lastGoGrind{0};
-    // NewRpgStatus::GO_INNKEEPER
-    WorldPosition innKeeperPos{};
-    uint32 lastGoInnKeeper{0};
-    // NewRpgStatus::NEAR_NPC
-    GuidPosition npcPos{};
-    uint32 lastNearNpc{0};
-    uint32 lastReachNpc{0};
-    // NewRpgStatus::NEAR_RANDOM
-    uint32 lastNearRandom{0};
-    // NewRpgStatus::REST
-    uint32 lastRest{0};
-    // MOVE_FAR
-    float nearestMoveFarDis{FLT_MAX};
-    uint32 stuckTs{0};
-    uint32 stuckAttempts{0};
-    std::string ToString()
-    {
-        std::stringstream out;
-        out << "Status: ";
-        switch (status)
-        {
-            case NewRpgStatus::GO_GRIND:
-                out << "GO_GRIND";
-                out << "\nGrindPos: " << grindPos.GetMapId() << " " << grindPos.GetPositionX() << " " << grindPos.GetPositionY() << " " << grindPos.GetPositionZ();
-                out << "\nlastGoGrind: " << lastGoGrind;
-                break;
-            case NewRpgStatus::GO_INNKEEPER:
-                out << "GO_INNKEEPER";
-                out << "\nInnKeeperPos: " << innKeeperPos.GetMapId() << " " << innKeeperPos.GetPositionX() << " " << innKeeperPos.GetPositionY() << " " << innKeeperPos.GetPositionZ();
-                out << "\nlastGoInnKeeper: " << lastGoInnKeeper;
-                break;
-            case NewRpgStatus::NEAR_NPC:
-                out << "NEAR_NPC";
-                out << "\nNpcPos: " << npcPos.GetMapId() << " " << npcPos.GetPositionX() << " " << npcPos.GetPositionY() << " " << npcPos.GetPositionZ();
-                out << "\nlastNearNpc: " << lastNearNpc;
-                out << "\nlastReachNpc: " << lastReachNpc;
-                break;
-            case NewRpgStatus::NEAR_RANDOM:
-                out << "NEAR_RANDOM";
-                out << "\nlastNearRandom: " << lastNearRandom;
-                break;
-            case NewRpgStatus::IDLE:
-                out << "IDLE";
-                break;
-            case NewRpgStatus::REST:
-                out << "REST";
-                out << "\nlastRest: " << lastRest;
-                break;
-            default:
-                out << "UNKNOWN";
-        }
-        return out.str();
-    }
-    void Reset()
-    {
-        *this = NewRpgInfo();
-    }
-};
-
 class NewRpgStrategy : public Strategy
 {
 public:
diff --git a/modules/mod-playerbots/src/strategy/rpg/NewRpgTriggers.h b/modules/mod-playerbots/src/strategy/rpg/NewRpgTriggers.h
index 9827d57..fe2efe3 100644
--- a/modules/mod-playerbots/src/strategy/rpg/NewRpgTriggers.h
+++ b/modules/mod-playerbots/src/strategy/rpg/NewRpgTriggers.h
@@ -7,7 +7,7 @@
 class NewRpgStatusTrigger : public Trigger
 {
 public:
-    NewRpgStatusTrigger(PlayerbotAI* botAI, NewRpgStatus status = NewRpgStatus::IDLE)
+    NewRpgStatusTrigger(PlayerbotAI* botAI, NewRpgStatus status = RPG_IDLE)
         : Trigger(botAI, "new rpg status"), status(status)
     {
     }
diff --git a/modules/mod-playerbots/src/strategy/triggers/ChatTriggerContext.h b/modules/mod-playerbots/src/strategy/triggers/ChatTriggerContext.h
index 5f7ab84..ebc1121 100644
--- a/modules/mod-playerbots/src/strategy/triggers/ChatTriggerContext.h
+++ b/modules/mod-playerbots/src/strategy/triggers/ChatTriggerContext.h
@@ -17,6 +17,8 @@ public:
     ChatTriggerContext()
     {
         creators["open items"] = &ChatTriggerContext::open_items;
+        creators["unlock items"] = &ChatTriggerContext::unlock_items;
+        creators["unlock traded item"] = &ChatTriggerContext::unlock_traded_item;
         creators["quests"] = &ChatTriggerContext::quests;
         creators["stats"] = &ChatTriggerContext::stats;
         creators["leave"] = &ChatTriggerContext::leave;
@@ -25,6 +27,7 @@ public:
         creators["log"] = &ChatTriggerContext::log;
         creators["los"] = &ChatTriggerContext::los;
         creators["rpg status"] = &ChatTriggerContext::rpg_status;
+        creators["rpg do quest"] = &ChatTriggerContext::rpg_do_quest;
         creators["aura"] = &ChatTriggerContext::aura;
         creators["drop"] = &ChatTriggerContext::drop;
         creators["share"] = &ChatTriggerContext::share;
@@ -132,6 +135,8 @@ public:
 
 private:
     static Trigger* open_items(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "open items"); }
+    static Trigger* unlock_items(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "unlock items"); }
+    static Trigger* unlock_traded_item(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "unlock traded item"); }
     static Trigger* ra(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "ra"); }
     static Trigger* range(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "range"); }
     static Trigger* flag(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "flag"); }
@@ -214,6 +219,7 @@ private:
     static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); }
     static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); }
     static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); }
+    static Trigger* rpg_do_quest(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg do quest"); }
     static Trigger* aura(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "aura"); }
     static Trigger* loot_all(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "add all loot"); }
     static Trigger* release(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "release"); }
diff --git a/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.cpp b/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.cpp
index bbda4c9..4f0038f 100644
--- a/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.cpp
+++ b/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.cpp
@@ -15,6 +15,7 @@
 #include "ObjectGuid.h"
 #include "PlayerbotAIConfig.h"
 #include "Playerbots.h"
+#include "PositionValue.h"
 #include "SharedDefines.h"
 #include "TemporarySummon.h"
 #include "ThreatMgr.h"
@@ -507,11 +508,22 @@ bool IsBehindTargetTrigger::IsActive()
 
 bool IsNotBehindTargetTrigger::IsActive()
 {
+    if (botAI->HasStrategy("stay", botAI->GetState()))
+    {
+        return false;
+    }
     Unit* target = AI_VALUE(Unit*, "current target");
     return target && !AI_VALUE2(bool, "behind", "current target");
 }
 
-bool IsNotFacingTargetTrigger::IsActive() { return !AI_VALUE2(bool, "facing", "current target"); }
+bool IsNotFacingTargetTrigger::IsActive()
+{
+    if (botAI->HasStrategy("stay", botAI->GetState()))
+    {
+        return false;
+    }
+    return !AI_VALUE2(bool, "facing", "current target");
+}
 
 bool HasCcTargetTrigger::IsActive()
 {
@@ -599,6 +611,18 @@ bool NewPlayerNearbyTrigger::IsActive() { return AI_VALUE(ObjectGuid, "new playe
 
 bool CollisionTrigger::IsActive() { return AI_VALUE2(bool, "collision", "self target"); }
 
+bool ReturnToStayPositionTrigger::IsActive()
+{
+    PositionInfo stayPosition = AI_VALUE(PositionMap&, "position")["stay"];
+    if (stayPosition.isSet())
+    {
+        const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
+        return distance > sPlayerbotAIConfig->followDistance;
+    }
+
+    return false;
+}
+
 bool GiveItemTrigger::IsActive()
 {
     return AI_VALUE2(Unit*, "party member without item", item) && AI_VALUE2(uint32, "item count", item);
diff --git a/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.h b/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.h
index dd4e465..cee6b18 100644
--- a/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.h
+++ b/modules/mod-playerbots/src/strategy/triggers/GenericTriggers.h
@@ -827,6 +827,14 @@ public:
     SitTrigger(PlayerbotAI* botAI) : StayTimeTrigger(botAI, sPlayerbotAIConfig->sitDelay, "sit") {}
 };
 
+class ReturnToStayPositionTrigger : public Trigger
+{
+public:
+    ReturnToStayPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "return to stay position", 2) {}
+
+    virtual bool IsActive() override;
+};
+
 class ReturnTrigger : public StayTimeTrigger
 {
 public:
diff --git a/modules/mod-playerbots/src/strategy/triggers/LootTriggers.cpp b/modules/mod-playerbots/src/strategy/triggers/LootTriggers.cpp
index 17c1304..eed1a3e 100644
--- a/modules/mod-playerbots/src/strategy/triggers/LootTriggers.cpp
+++ b/modules/mod-playerbots/src/strategy/triggers/LootTriggers.cpp
@@ -12,10 +12,10 @@
 bool LootAvailableTrigger::IsActive()
 {
     return AI_VALUE(bool, "has available loot") &&
+            // if loot target if empty, always pass distance check
            (sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"),
                                                      INTERACTION_DISTANCE - 2.0f) ||
-            AI_VALUE(GuidVector, "all targets").empty()) &&
-           !AI_VALUE2(bool, "combat", "self target");
+            AI_VALUE(GuidVector, "all targets").empty());
 }
 
 bool FarFromCurrentLootTrigger::IsActive()
diff --git a/modules/mod-playerbots/src/strategy/triggers/TriggerContext.h b/modules/mod-playerbots/src/strategy/triggers/TriggerContext.h
index d47b75b..1b2c86f 100644
--- a/modules/mod-playerbots/src/strategy/triggers/TriggerContext.h
+++ b/modules/mod-playerbots/src/strategy/triggers/TriggerContext.h
@@ -30,6 +30,7 @@ public:
     {
         creators["return"] = &TriggerContext::_return;
         creators["sit"] = &TriggerContext::sit;
+        creators["return to stay position"] = &TriggerContext::return_to_stay_position;
         creators["collision"] = &TriggerContext::collision;
 
         creators["timer"] = &TriggerContext::Timer;
@@ -220,6 +221,7 @@ public:
         creators["go innkeeper status"] = &TriggerContext::go_innkeeper_status;
         creators["near random status"] = &TriggerContext::near_random_status;
         creators["near npc status"] = &TriggerContext::near_npc_status;
+        creators["do quest status"] = &TriggerContext::do_quest_status;
     }
 
 private:
@@ -227,6 +229,7 @@ private:
     static Trigger* give_water(PlayerbotAI* botAI) { return new GiveWaterTrigger(botAI); }
     static Trigger* no_rti(PlayerbotAI* botAI) { return new NoRtiTrigger(botAI); }
     static Trigger* _return(PlayerbotAI* botAI) { return new ReturnTrigger(botAI); }
+    static Trigger* return_to_stay_position(PlayerbotAI* ai) { return new ReturnToStayPositionTrigger(ai); }
     static Trigger* sit(PlayerbotAI* botAI) { return new SitTrigger(botAI); }
     static Trigger* far_from_rpg_target(PlayerbotAI* botAI) { return new FarFromRpgTargetTrigger(botAI); }
     static Trigger* near_rpg_target(PlayerbotAI* botAI) { return new NearRpgTargetTrigger(botAI); }
@@ -410,10 +413,11 @@ private:
     static Trigger* rpg_craft(PlayerbotAI* botAI) { return new RpgCraftTrigger(botAI); }
     static Trigger* rpg_trade_useful(PlayerbotAI* botAI) { return new RpgTradeUsefulTrigger(botAI); }
     static Trigger* rpg_duel(PlayerbotAI* botAI) { return new RpgDuelTrigger(botAI); }
-    static Trigger* go_grind_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_GRIND); }
-    static Trigger* go_innkeeper_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::GO_INNKEEPER); }
-    static Trigger* near_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_RANDOM); }
-    static Trigger* near_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, NewRpgStatus::NEAR_NPC); }
+    static Trigger* go_grind_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_GO_GRIND); }
+    static Trigger* go_innkeeper_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_GO_INNKEEPER); }
+    static Trigger* near_random_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_NEAR_RANDOM); }
+    static Trigger* near_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_NEAR_NPC); }
+    static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
 };
 
 #endif
diff --git a/modules/mod-playerbots/src/strategy/triggers/WorldPacketTriggerContext.h b/modules/mod-playerbots/src/strategy/triggers/WorldPacketTriggerContext.h
index 72ad3e5..5404a22 100644
--- a/modules/mod-playerbots/src/strategy/triggers/WorldPacketTriggerContext.h
+++ b/modules/mod-playerbots/src/strategy/triggers/WorldPacketTriggerContext.h
@@ -29,6 +29,7 @@ public:
         creators["check mount state"] = &WorldPacketTriggerContext::check_mount_state;
         creators["activate taxi"] = &WorldPacketTriggerContext::taxi;
         creators["trade status"] = &WorldPacketTriggerContext::trade_status;
+        creators["trade status extended"] = &WorldPacketTriggerContext::trade_status_extended;
         creators["loot response"] = &WorldPacketTriggerContext::loot_response;
         creators["out of react range"] = &WorldPacketTriggerContext::out_of_react_range;
 
@@ -108,6 +109,7 @@ private:
     static Trigger* out_of_react_range(PlayerbotAI* botAI) { return new OutOfReactRangeTrigger(botAI); }
     static Trigger* loot_response(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "loot response"); }
     static Trigger* trade_status(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "trade status"); }
+    static Trigger* trade_status_extended(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "trade status extended"); }
     static Trigger* cannot_equip(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "cannot equip"); }
     static Trigger* check_mount_state(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "check mount state"); }
     static Trigger* area_trigger(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "area trigger"); }
diff --git a/modules/mod-playerbots/src/strategy/values/AvailableLootValue.cpp b/modules/mod-playerbots/src/strategy/values/AvailableLootValue.cpp
index 2adb410..a0506c4 100644
--- a/modules/mod-playerbots/src/strategy/values/AvailableLootValue.cpp
+++ b/modules/mod-playerbots/src/strategy/values/AvailableLootValue.cpp
@@ -26,5 +26,5 @@ bool CanLootValue::Calculate()
 {
     LootObject loot = AI_VALUE(LootObject, "loot target");
     return !loot.IsEmpty() && loot.GetWorldObject(bot) && loot.IsLootPossible(bot) &&
-           sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"), INTERACTION_DISTANCE);
+           sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"), INTERACTION_DISTANCE - 2);
 }
diff --git a/modules/mod-playerbots/src/strategy/values/GrindTargetValue.cpp b/modules/mod-playerbots/src/strategy/values/GrindTargetValue.cpp
index b21f62a..da8fed8 100644
--- a/modules/mod-playerbots/src/strategy/values/GrindTargetValue.cpp
+++ b/modules/mod-playerbots/src/strategy/values/GrindTargetValue.cpp
@@ -5,6 +5,7 @@
 
 #include "GrindTargetValue.h"
 
+#include "NewRpgInfo.h"
 #include "Playerbots.h"
 #include "ReputationMgr.h"
 #include "SharedDefines.h"
@@ -52,7 +53,7 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
 
     float distance = 0;
     Unit* result = nullptr;
-    // std::unordered_map<uint32, bool> needForQuestMap;
+    std::unordered_map<uint32, bool> needForQuestMap;
 
     for (ObjectGuid const guid : targets)
     {
@@ -99,19 +100,6 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
 		if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer())
 		    continue;
 
-        // if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
-        //     needForQuestMap[unit->GetEntry()] = needForQuest(unit);
-
-        // if (!needForQuestMap[unit->GetEntry()])
-        // {
-        //     Creature* creature = dynamic_cast<Creature*>(unit);
-        //     if ((urand(0, 100) < 60 || (context->GetValue<TravelTarget*>("travel target")->Get()->isWorking() &&
-        //         context->GetValue<TravelTarget*>("travel target")->Get()->getDestination()->getName() != "GrindTravelDestination")))
-        //     {
-        //         continue;
-        //     }
-        // }
-
         if (Creature* creature = unit->ToCreature())
             if (CreatureTemplate const* CreatureTemplate = creature->GetCreatureTemplate())
                 if (CreatureTemplate->rank > CREATURE_ELITE_NORMAL && !AI_VALUE(bool, "can fight elite"))
@@ -122,6 +110,26 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
             continue;
         }
 
+        bool inactiveGrindStatus = botAI->rpgInfo.status == RPG_GO_GRIND ||
+            botAI->rpgInfo.status == RPG_NEAR_NPC ||
+            botAI->rpgInfo.status == RPG_REST ||
+            botAI->rpgInfo.status == RPG_GO_INNKEEPER ||
+            botAI->rpgInfo.status == RPG_DO_QUEST;
+
+        bool notHostile = !bot->IsHostileTo(unit); /*|| (unit->ToCreature() && unit->ToCreature()->IsCivilian());*/
+        float aggroRange = 30.0f;
+        if (unit->ToCreature())
+            aggroRange = std::min(30.0f, unit->ToCreature()->GetAggroRange(bot) + 10.0f);
+        bool outOfAggro = unit->ToCreature() && bot->GetDistance(unit) > aggroRange;
+        if (inactiveGrindStatus && (outOfAggro || notHostile))
+        {
+            if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
+                needForQuestMap[unit->GetEntry()] = needForQuest(unit);
+
+            if (!needForQuestMap[unit->GetEntry()])
+                continue;
+        }
+
         if (group)
         {
             Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
@@ -155,8 +163,6 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
 
 bool GrindTargetValue::needForQuest(Unit* target)
 {
-    bool justCheck = (bot->GetGUID() == target->GetGUID());
-
     QuestStatusMap& questMap = bot->getQuestStatusMap();
     for (auto& quest : questMap)
     {
@@ -170,16 +176,9 @@ bool GrindTargetValue::needForQuest(Unit* target)
 
         QuestStatus status = bot->GetQuestStatus(questId);
 
-        if ((status == QUEST_STATUS_COMPLETE && !bot->GetQuestRewardStatus(questId)))
+        if (status == QUEST_STATUS_INCOMPLETE)
         {
-            if (!justCheck && !target->hasInvolvedQuest(questId))
-                continue;
-
-            return true;
-        }
-        else if (status == QUEST_STATUS_INCOMPLETE)
-        {
-            QuestStatusData* questStatus = sTravelMgr->getQuestStatus(bot, questId);
+            const QuestStatusData* questStatus = &bot->getQuestStatusMap()[questId];
 
             if (questTemplate->GetQuestLevel() > bot->GetLevel() + 5)
                 continue;
@@ -193,33 +192,18 @@ bool GrindTargetValue::needForQuest(Unit* target)
                     int required = questTemplate->RequiredNpcOrGoCount[j];
                     int available = questStatus->CreatureOrGOCount[j];
 
-                    if (required && available < required && (target->GetEntry() == entry || justCheck))
+                    if (required && available < required && target->GetEntry() == entry)
                         return true;
                 }
-
-                if (justCheck)
-                {
-                    int32 itemId = questTemplate->RequiredItemId[j];
-
-                    if (itemId && itemId > 0)
-                    {
-                        uint32 required = questTemplate->RequiredItemCount[j];
-                        uint32 available = questStatus->ItemCount[j];
-
-                        if (required && available < required)
-                            return true;
-                    }
-                }
             }
 
-            if (!justCheck)
+            if (CreatureTemplate const* data = sObjectMgr->GetCreatureTemplate(target->GetEntry()))
             {
-                if (CreatureTemplate const* data = sObjectMgr->GetCreatureTemplate(target->GetEntry()))
+                if (uint32 lootId = data->lootid)
                 {
-                    if (uint32 lootId = data->lootid)
+                    if (LootTemplates_Creature.HaveQuestLootForPlayer(lootId, bot))
                     {
-                        if (LootTemplates_Creature.HaveQuestLootForPlayer(lootId, bot))
-                            return true;
+                        return true;
                     }
                 }
             }
diff --git a/modules/mod-playerbots/src/strategy/values/ItemForSpellValue.cpp b/modules/mod-playerbots/src/strategy/values/ItemForSpellValue.cpp
index 95751b4..366f38a 100644
--- a/modules/mod-playerbots/src/strategy/values/ItemForSpellValue.cpp
+++ b/modules/mod-playerbots/src/strategy/values/ItemForSpellValue.cpp
@@ -71,6 +71,9 @@ Item* ItemForSpellValue::Calculate()
     if (!strcmpi(spellInfo->SpellName[0], "disenchant"))
         return nullptr;
 
+    if (!strcmpi(spellInfo->SpellName[0], "pick lock"))
+        return nullptr;
+
     for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
     {
         itemForSpell = GetItemFitsToSpellRequirements(slot, spellInfo);
diff --git a/modules/mod-playerbots/src/strategy/values/LastMovementValue.h b/modules/mod-playerbots/src/strategy/values/LastMovementValue.h
index a22debb..f3ecf91 100644
--- a/modules/mod-playerbots/src/strategy/values/LastMovementValue.h
+++ b/modules/mod-playerbots/src/strategy/values/LastMovementValue.h
@@ -17,6 +17,7 @@ class Unit;
 enum class MovementPriority
 {
     MOVEMENT_IDLE,
+    MOVEMENT_WANDER,
     MOVEMENT_NORMAL,
     MOVEMENT_COMBAT,
     MOVEMENT_FORCED
diff --git a/modules/mod-playerbots/src/strategy/values/NearestGameObjects.cpp b/modules/mod-playerbots/src/strategy/values/NearestGameObjects.cpp
index a600bc6..85a3428 100644
--- a/modules/mod-playerbots/src/strategy/values/NearestGameObjects.cpp
+++ b/modules/mod-playerbots/src/strategy/values/NearestGameObjects.cpp
@@ -12,24 +12,6 @@
 #include "SharedDefines.h"
 #include "SpellMgr.h"
 
-class AnyGameObjectInObjectRangeCheck
-{
-public:
-    AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {}
-    WorldObject const& GetFocusObject() const { return *i_obj; }
-    bool operator()(GameObject* u)
-    {
-        if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo())
-            return true;
-
-        return false;
-    }
-
-private:
-    WorldObject const* i_obj;
-    float i_range;
-};
-
 GuidVector NearestGameObjects::Calculate()
 {
     std::list<GameObject*> targets;
diff --git a/modules/mod-playerbots/src/strategy/values/NearestGameObjects.h b/modules/mod-playerbots/src/strategy/values/NearestGameObjects.h
index 04d1648..e29467d 100644
--- a/modules/mod-playerbots/src/strategy/values/NearestGameObjects.h
+++ b/modules/mod-playerbots/src/strategy/values/NearestGameObjects.h
@@ -8,15 +8,34 @@
 
 #include "PlayerbotAIConfig.h"
 #include "Value.h"
+#include "GameObject.h"
 
 class PlayerbotAI;
 
+class AnyGameObjectInObjectRangeCheck
+{
+public:
+    AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {}
+    WorldObject const& GetFocusObject() const { return *i_obj; }
+    bool operator()(GameObject* u)
+    {
+        if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo())
+            return true;
+
+        return false;
+    }
+
+private:
+    WorldObject const* i_obj;
+    float i_range;
+};
+
 class NearestGameObjects : public ObjectGuidListCalculatedValue
 {
 public:
     NearestGameObjects(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->sightDistance, bool ignoreLos = false,
                        std::string const name = "nearest game objects")
-        : ObjectGuidListCalculatedValue(botAI, name, 2 * 1000), range(range), ignoreLos(ignoreLos)
+        : ObjectGuidListCalculatedValue(botAI, name, 1 * 1000), range(range), ignoreLos(ignoreLos)
     {
     }
 
diff --git a/modules/mod-playerbots/src/strategy/values/PositionValue.cpp b/modules/mod-playerbots/src/strategy/values/PositionValue.cpp
index a914ef3..e299c98 100644
--- a/modules/mod-playerbots/src/strategy/values/PositionValue.cpp
+++ b/modules/mod-playerbots/src/strategy/values/PositionValue.cpp
@@ -63,3 +63,25 @@ bool PositionValue::Load(std::string const text)
 }
 
 WorldPosition CurrentPositionValue::Calculate() { return WorldPosition(bot); }
+
+PositionInfo SinglePositionValue::Calculate()
+{
+    PositionMap& posMap = AI_VALUE(PositionMap&, "position");
+    return posMap[getQualifier()];
+}
+
+void SinglePositionValue::Set(PositionInfo value)
+{
+    PositionMap& posMap = AI_VALUE(PositionMap&, "position");
+    PositionInfo pos = posMap[getQualifier()];
+    pos = value;
+    posMap[getQualifier()] = pos;
+}
+
+void SinglePositionValue::Reset()
+{
+    PositionMap& posMap = AI_VALUE(PositionMap&, "position");
+    PositionInfo pos = posMap[getQualifier()];
+    pos.Reset();
+    posMap[getQualifier()] = pos;
+}
diff --git a/modules/mod-playerbots/src/strategy/values/PositionValue.h b/modules/mod-playerbots/src/strategy/values/PositionValue.h
index 42b0aaf..8919324 100644
--- a/modules/mod-playerbots/src/strategy/values/PositionValue.h
+++ b/modules/mod-playerbots/src/strategy/values/PositionValue.h
@@ -6,6 +6,7 @@
 #ifndef _PLAYERBOT_POSITIONVALUE_H
 #define _PLAYERBOT_POSITIONVALUE_H
 
+#include "NamedObjectContext.h"
 #include "TravelMgr.h"
 #include "Value.h"
 
@@ -15,6 +16,10 @@ class PositionInfo
 {
 public:
     PositionInfo() : valueSet(false), x(0), y(0), z(0), mapId(0) {}
+    PositionInfo(float x, float y, float z, uint32 mapId, bool valueSet = true)
+        : valueSet(valueSet), x(x), y(y), z(z), mapId(mapId)
+    {
+    }
     PositionInfo(PositionInfo const& other)
         : valueSet(other.valueSet), x(other.x), y(other.y), z(other.z), mapId(other.mapId)
     {
@@ -72,4 +77,13 @@ public:
     WorldPosition Calculate() override;
 };
 
+class SinglePositionValue : public CalculatedValue<PositionInfo>, public Qualified
+{
+public:
+    SinglePositionValue(PlayerbotAI* ai, std::string name = "pos") : CalculatedValue(ai, name), Qualified() {};
+    virtual PositionInfo Calculate() override;
+    virtual void Set(PositionInfo value) override;
+    virtual void Reset() override;
+};
+
 #endif
diff --git a/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.cpp b/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.cpp
index 55ee94b..cee20d9 100644
--- a/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.cpp
+++ b/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.cpp
@@ -8,8 +8,11 @@
 #include "CellImpl.h"
 #include "GridNotifiers.h"
 #include "GridNotifiersImpl.h"
+#include "ObjectGuid.h"
 #include "Playerbots.h"
 #include "ServerFacade.h"
+#include "SharedDefines.h"
+#include "NearestGameObjects.h"
 
 std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
 
@@ -78,3 +81,125 @@ bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
 
     return false;
 }
+
+
+std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags;
+
+PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range)
+    : NearestUnitsValue(botAI, "possible new rpg targets", range, true)
+{
+    if (allowedNpcFlags.empty())
+    {
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_INNKEEPER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_GOSSIP);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_QUESTGIVER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_FLIGHTMASTER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_BANKER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_GUILD_BANKER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_TRAINER_CLASS);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_TRAINER_PROFESSION);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_AMMO);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_FOOD);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_POISON);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR_REAGENT);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_AUCTIONEER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_STABLEMASTER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_PETITIONER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_TABARDDESIGNER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_BATTLEMASTER);
+
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_TRAINER);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR);
+        allowedNpcFlags.push_back(UNIT_NPC_FLAG_REPAIR);
+    }
+}
+
+GuidVector PossibleNewRpgTargetsValue::Calculate()
+{
+    std::list<Unit*> targets;
+    FindUnits(targets);
+
+    GuidVector results;
+    std::vector<std::pair<ObjectGuid, float>> guidDistancePairs;
+    for (Unit* unit : targets)
+    {
+        if (AcceptUnit(unit) && (ignoreLos || bot->IsWithinLOSInMap(unit)))
+            guidDistancePairs.push_back({unit->GetGUID(), bot->GetExactDist(unit)});
+    }
+    // Override to sort by distance
+    std::sort(guidDistancePairs.begin(), guidDistancePairs.end(), [](const auto& a, const auto& b) {
+        return a.second < b.second;
+    });
+    
+    for (const auto& pair : guidDistancePairs) {
+        results.push_back(pair.first);
+    }
+    return results;
+}
+
+void PossibleNewRpgTargetsValue::FindUnits(std::list<Unit*>& targets)
+{
+    Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
+    Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
+    Cell::VisitAllObjects(bot, searcher, range);
+}
+
+bool PossibleNewRpgTargetsValue::AcceptUnit(Unit* unit)
+{
+    if (unit->IsHostileTo(bot) || unit->GetTypeId() == TYPEID_PLAYER)
+        return false;
+
+    if (unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPIRITHEALER))
+        return false;
+
+    for (uint32 npcFlag : allowedNpcFlags)
+    {
+        if (unit->HasFlag(UNIT_NPC_FLAGS, npcFlag))
+            return true;
+    }
+
+    return false;
+}
+
+std::vector<GameobjectTypes> PossibleNewRpgGameObjectsValue::allowedGOFlags;
+
+GuidVector PossibleNewRpgGameObjectsValue::Calculate()
+{
+    std::list<GameObject*> targets;
+    AnyGameObjectInObjectRangeCheck u_check(bot, range);
+    Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
+    Cell::VisitAllObjects(bot, searcher, range);
+
+    
+    std::vector<std::pair<ObjectGuid, float>> guidDistancePairs;
+    for (GameObject* go : targets)
+    {
+        bool flagCheck = false;
+        for (uint32 goFlag : allowedGOFlags)
+        {
+            if (go->GetGoType() == goFlag)
+            {
+                flagCheck = true;
+                break;
+            }
+        }
+        if (!flagCheck)
+            continue;
+        
+        if (!ignoreLos && !bot->IsWithinLOSInMap(go))
+            continue;
+        
+        guidDistancePairs.push_back({go->GetGUID(), bot->GetExactDist(go)});
+    }
+    GuidVector results;
+
+    // Sort by distance
+    std::sort(guidDistancePairs.begin(), guidDistancePairs.end(), [](const auto& a, const auto& b) {
+        return a.second < b.second;
+    });
+    
+    for (const auto& pair : guidDistancePairs) {
+        results.push_back(pair.first);
+    }
+    return results;
+}
diff --git a/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.h b/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.h
index 287f5db..2312480 100644
--- a/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.h
+++ b/modules/mod-playerbots/src/strategy/values/PossibleRpgTargetsValue.h
@@ -6,8 +6,10 @@
 #ifndef _PLAYERBOT_POSSIBLERPGTARGETSVALUE_H
 #define _PLAYERBOT_POSSIBLERPGTARGETSVALUE_H
 
+#include "NearestGameObjects.h"
 #include "NearestUnitsValue.h"
 #include "PlayerbotAIConfig.h"
+#include "SharedDefines.h"
 
 class PlayerbotAI;
 
@@ -23,4 +25,36 @@ protected:
     bool AcceptUnit(Unit* unit) override;
 };
 
+class PossibleNewRpgTargetsValue : public NearestUnitsValue
+{
+public:
+    PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range = 150.0f);
+
+    static std::vector<uint32> allowedNpcFlags;
+    GuidVector Calculate() override;
+protected:
+    void FindUnits(std::list<Unit*>& targets) override;
+    bool AcceptUnit(Unit* unit) override;
+};
+
+class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue
+{
+public:
+    PossibleNewRpgGameObjectsValue(PlayerbotAI* botAI, float range = 150.0f, bool ignoreLos = true)
+        : ObjectGuidListCalculatedValue(botAI, "possible new rpg game objects"), range(range), ignoreLos(ignoreLos)
+    {
+        if (allowedGOFlags.empty())
+        {
+            allowedGOFlags.push_back(GAMEOBJECT_TYPE_QUESTGIVER);
+        }
+    }
+    
+    static std::vector<GameobjectTypes> allowedGOFlags;
+    GuidVector Calculate() override;
+
+private:
+    float range;
+    bool ignoreLos;
+};
+
 #endif
diff --git a/modules/mod-playerbots/src/strategy/values/ValueContext.h b/modules/mod-playerbots/src/strategy/values/ValueContext.h
index f57c808..c9dcdb7 100644
--- a/modules/mod-playerbots/src/strategy/values/ValueContext.h
+++ b/modules/mod-playerbots/src/strategy/values/ValueContext.h
@@ -118,6 +118,8 @@ public:
         creators["prioritized targets"] = &ValueContext::prioritized_targets;
         creators["all targets"] = &ValueContext::all_targets;
         creators["possible rpg targets"] = &ValueContext::possible_rpg_targets;
+        creators["possible new rpg targets"] = &ValueContext::possible_new_rpg_targets;
+        creators["possible new rpg game objects"] = &ValueContext::possible_new_rpg_game_objects;
         creators["nearest adds"] = &ValueContext::nearest_adds;
         creators["nearest corpses"] = &ValueContext::nearest_corpses;
         creators["log level"] = &ValueContext::log_level;
@@ -192,6 +194,7 @@ public:
         creators["rti cc"] = &ValueContext::rti_cc;
         creators["rti"] = &ValueContext::rti;
         creators["position"] = &ValueContext::position;
+        creators["pos"] = &ValueContext::pos;
         creators["current position"] = &ValueContext::current_position;
         creators["threat"] = &ValueContext::threat;
 
@@ -340,6 +343,7 @@ private:
     static UntypedValue* attackers(PlayerbotAI* botAI) { return new AttackersValue(botAI); }
 
     static UntypedValue* position(PlayerbotAI* botAI) { return new PositionValue(botAI); }
+    static UntypedValue* pos(PlayerbotAI* ai) { return new SinglePositionValue(ai); }
     static UntypedValue* current_position(PlayerbotAI* botAI) { return new CurrentPositionValue(botAI); }
     static UntypedValue* rti(PlayerbotAI* botAI) { return new RtiValue(botAI); }
     static UntypedValue* rti_cc(PlayerbotAI* botAI) { return new RtiCcValue(botAI); }
@@ -406,6 +410,8 @@ private:
     static UntypedValue* nearest_enemy_players(PlayerbotAI* botAI) { return new NearestEnemyPlayersValue(botAI); }
     static UntypedValue* nearest_corpses(PlayerbotAI* botAI) { return new NearestCorpsesValue(botAI); }
     static UntypedValue* possible_rpg_targets(PlayerbotAI* botAI) { return new PossibleRpgTargetsValue(botAI); }
+    static UntypedValue* possible_new_rpg_targets(PlayerbotAI* botAI) { return new PossibleNewRpgTargetsValue(botAI); }
+    static UntypedValue* possible_new_rpg_game_objects(PlayerbotAI* botAI) { return new PossibleNewRpgGameObjectsValue(botAI); }
     static UntypedValue* possible_targets(PlayerbotAI* botAI) { return new PossibleTargetsValue(botAI); }
     static UntypedValue* possible_triggers(PlayerbotAI* botAI) { return new PossibleTriggersValue(botAI); }
     static UntypedValue* possible_targets_no_los(PlayerbotAI* botAI)