mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 17:09:08 +00:00
Compare commits
183 Commits
revert-136
...
opcode-cra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5f4f32799 | ||
|
|
11b96b51b7 | ||
|
|
a41c1912ac | ||
|
|
21d8f32d24 | ||
|
|
bf56154eee | ||
|
|
e46269920a | ||
|
|
1881ef1fe0 | ||
|
|
3c442a6b71 | ||
|
|
750d557e6a | ||
|
|
8057a5d7ac | ||
|
|
c218dbe653 | ||
|
|
6588ca5878 | ||
|
|
df6b1490b1 | ||
|
|
179c34e3a9 | ||
|
|
45d046f427 | ||
|
|
31f2c6a20d | ||
|
|
37458f0dc5 | ||
|
|
bc737ecc68 | ||
|
|
704e02e9cc | ||
|
|
5f00b9bbd5 | ||
|
|
5469333465 | ||
|
|
2dad8bf01d | ||
|
|
78116fe37e | ||
|
|
c9b4cfa184 | ||
|
|
957a60cd1d | ||
|
|
b661264c53 | ||
|
|
77c2354c3f | ||
|
|
b369b1f9ae | ||
|
|
fa7b863035 | ||
|
|
4f5f7d286e | ||
|
|
6cb9f56c4e | ||
|
|
3e0f23536d | ||
|
|
12065a6ad5 | ||
|
|
8d51092d42 | ||
|
|
3fff58df1a | ||
|
|
ca2e2ef0db | ||
|
|
4e3ac609bd | ||
|
|
c6b0424c29 | ||
|
|
2e0a161623 | ||
|
|
e4ea8e2694 | ||
|
|
ddfa919154 | ||
|
|
380312ffd2 | ||
|
|
872e417613 | ||
|
|
3d28a81508 | ||
|
|
bcd6f5bc06 | ||
|
|
c5010b3809 | ||
|
|
15f138aab0 | ||
|
|
5759a98d5a | ||
|
|
13fca4398d | ||
|
|
86dbf54584 | ||
|
|
a307eb2f08 | ||
|
|
966bf1d6af | ||
|
|
2144c95311 | ||
|
|
f5ef5bd1c2 | ||
|
|
28de238422 | ||
|
|
0afcf29490 | ||
|
|
ab345b8847 | ||
|
|
683c6e39e4 | ||
|
|
a6c07ca16d | ||
|
|
ee99b66d04 | ||
|
|
548746c25f | ||
|
|
f7e64589e8 | ||
|
|
8545225923 | ||
|
|
ede7697784 | ||
|
|
ba9cb5a256 | ||
|
|
a1dd6f6fc5 | ||
|
|
e950f65a83 | ||
|
|
938872564a | ||
|
|
baa1aa9e9d | ||
|
|
65a3bf481c | ||
|
|
ffc96b664e | ||
|
|
aa6f8153a1 | ||
|
|
989b48f491 | ||
|
|
40874624a8 | ||
|
|
f76435b4c4 | ||
|
|
df3c44419d | ||
|
|
b7b8c60d17 | ||
|
|
e5f1446b9f | ||
|
|
e92029dd6e | ||
|
|
b8c0a54f92 | ||
|
|
18d1821dab | ||
|
|
8a8571c54f | ||
|
|
21bcbece7a | ||
|
|
e40c2b21f2 | ||
|
|
8a9a833c98 | ||
|
|
101c7f3046 | ||
|
|
d8d94f33ee | ||
|
|
eef2e8c1ef | ||
|
|
a675e74d97 | ||
|
|
de9f8fbbea | ||
|
|
66b326d79e | ||
|
|
0d8e8fbd61 | ||
|
|
66c88d4815 | ||
|
|
4c3906c243 | ||
|
|
64b09fd3ca | ||
|
|
db7a17ffde | ||
|
|
1e33b28abe | ||
|
|
e59bad26c4 | ||
|
|
a632fa2194 | ||
|
|
c6005449e0 | ||
|
|
2aca50c1c7 | ||
|
|
179e3bbf71 | ||
|
|
00b03bd29d | ||
|
|
e62da73706 | ||
|
|
5108f709c7 | ||
|
|
2beee4aec9 | ||
|
|
1e128ea24f | ||
|
|
e64da42f87 | ||
|
|
e0ef04e1b9 | ||
|
|
c5c1274d3c | ||
|
|
849b21f916 | ||
|
|
4c9e4e7b0f | ||
|
|
961629f4ce | ||
|
|
1801d7c314 | ||
|
|
237c0cffc4 | ||
|
|
ee245f73b5 | ||
|
|
c04477b54d | ||
|
|
b65646170c | ||
|
|
55a37c48eb | ||
|
|
a33bb3b51e | ||
|
|
feda619066 | ||
|
|
564bb198fb | ||
|
|
5ac6e8751c | ||
|
|
7d7edbd961 | ||
|
|
4a00c954ed | ||
|
|
e150b8281b | ||
|
|
0b03b277c2 | ||
|
|
d6b7693b8b | ||
|
|
551c698e37 | ||
|
|
45694ad6e6 | ||
|
|
761ef634da | ||
|
|
96cc0daea6 | ||
|
|
5202ac8db3 | ||
|
|
fa79fff4f4 | ||
|
|
6d07d6febe | ||
|
|
8ca4ab1344 | ||
|
|
bc5d602326 | ||
|
|
3611cfbdd3 | ||
|
|
0400c1c8b3 | ||
|
|
393a65a15b | ||
|
|
8b9bcce3bc | ||
|
|
8ed053ca01 | ||
|
|
51ed9c4649 | ||
|
|
86390f90fd | ||
|
|
3f39a57fe2 | ||
|
|
102aa24bb8 | ||
|
|
50792e5646 | ||
|
|
78f4bd6d29 | ||
|
|
b558e86df0 | ||
|
|
f0c6aaff0b | ||
|
|
1a20d549fe | ||
|
|
64df7439d8 | ||
|
|
05a3c318d6 | ||
|
|
1c69490290 | ||
|
|
8fd188ff3b | ||
|
|
7fa6e5833a | ||
|
|
305f769a84 | ||
|
|
c0aa55416b | ||
|
|
59af34809c | ||
|
|
326783ed4f | ||
|
|
9503d32d46 | ||
|
|
edc9241fa3 | ||
|
|
40535f6e55 | ||
|
|
9a980c171e | ||
|
|
ce91c335b3 | ||
|
|
605fa223ce | ||
|
|
309d177dd8 | ||
|
|
36fd5b8f15 | ||
|
|
720c063716 | ||
|
|
57a2c9a742 | ||
|
|
3f7814abb4 | ||
|
|
3dd0f11453 | ||
|
|
00cc2468f1 | ||
|
|
cf8253579e | ||
|
|
2000c06167 | ||
|
|
453925153f | ||
|
|
9a10f07c74 | ||
|
|
7d6f44ab09 | ||
|
|
bb004825aa | ||
|
|
097bd00f38 | ||
|
|
290bf50faf | ||
|
|
e739f7820b | ||
|
|
6fbaf6510b |
26
README.md
26
README.md
@@ -2,6 +2,8 @@
|
||||
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README.md">English</a>
|
||||
|
|
||||
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README_CN.md">中文</a>
|
||||
|
|
||||
<a href="https://github.com/brighton-chi/mod-playerbots/blob/readme/README_ES.md">Español</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -16,23 +18,25 @@
|
||||
</div>
|
||||
|
||||
# Playerbots Module
|
||||
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot). Features include:
|
||||
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot) and requires a custom branch of AzerothCore to compile and run: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot).
|
||||
|
||||
- Bots that utilize real player data, allowing players to interact with their other characters, form parties, level up, and more;
|
||||
- Random bots that wander through the world and behave like players, simulating the MMO experience;
|
||||
- Bots capable of running raids and battlegrounds;
|
||||
Features include:
|
||||
|
||||
- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more;
|
||||
- Random bots that wander through the world, complete quests, and otherwise behave like players, simulating the MMO experience;
|
||||
- Bots capable of running most raids and battlegrounds;
|
||||
- Highly configurable settings to define how bots behave;
|
||||
- Excellent performance, even when running thousands of bots.
|
||||
|
||||
**This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/liyunfan1223/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
||||
|
||||
**Playerbots Module** has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project.
|
||||
`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
||||
|
||||
## Installation
|
||||
|
||||
### Classic Installation
|
||||
|
||||
`mod-playerbots` requires a custom branch of AzerothCore to work: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run:
|
||||
As noted above, `mod-playerbots` requires a custom branch of AzerothCore: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
|
||||
@@ -81,21 +85,21 @@ Use `docker compose up -d --build` to build and run the server. For more informa
|
||||
|
||||
## Documentation
|
||||
|
||||
The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, and recommended configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome.
|
||||
The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
- **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present.
|
||||
- **What platforms are supported?** We support Ubuntu, Windows, and macOS. Other Linux distros may work, but will not receive support.
|
||||
- **Why isn't my source compiling?** Please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue.
|
||||
- **Why isn't my source compiling?** Please ensure that you are compiling with the required [custom branch of AzerothCore](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). Additionally, please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue.
|
||||
|
||||
## Addons
|
||||
|
||||
Typically, bots are controlled via chat commands. For larger bot groups, this can be unwieldy. As an alternative, community members have developed client Add-Ons to allow controlling bots through the in-game UI. We recommend you check out their projects:
|
||||
|
||||
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio)
|
||||
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan)
|
||||
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision)
|
||||
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio), which includes English, Chinese, French, German, Korean, Russian, and Spanish support [note: active development is temporarily continuing on a fork in Macx-Lio's absence (https://github.com/Wishmaster117/MultiBot)]
|
||||
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) [note: no longer under active development]
|
||||
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development]
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
# ACTIVITIES
|
||||
# SPELLS
|
||||
# STRATEGIES
|
||||
# RPG STRATEGY
|
||||
# TELEPORTS
|
||||
# BATTLEGROUND & ARENA & PVP
|
||||
# INTERVALS
|
||||
@@ -173,10 +174,6 @@ AiPlayerbot.SummonWhenGroup = 1
|
||||
# Selfbot permission level (0 = disabled, 1 = GM only (default), 2 = all players, 3 = activate on login)
|
||||
AiPlayerbot.SelfBotLevel = 1
|
||||
|
||||
# Give free food to bots
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.FreeFood = 1
|
||||
|
||||
# Non-GM player can only use init=auto to initialize bots based on their own level and gearscore
|
||||
# Default: 0 (non-GM player can use any intialization commands)
|
||||
AiPlayerbot.AutoInitOnly = 0
|
||||
@@ -483,8 +480,8 @@ AiPlayerbot.AutoGearQualityLimit = 3
|
||||
|
||||
# Equipment item level (not gearscore) limitation for autogear command (0 = no limit)
|
||||
# Classic
|
||||
# Max iLVL Tier 1 = 66 | Tier 2 = 76 | Tier 2.5 = 81 | Tier 3 = 99
|
||||
# Max iLVL Phase 1 = 71(MC, ONY, ZG) | Phase 2(BWL) = 77 | Phase 2.5(AQ) = 88 | Phase 3(NAXX) = 100 (NOT RECOMMENDED SINCE ILVL OVERLAPS BETWEEN TIERS)
|
||||
# Max iLVL Tier 1 = 66 | Tier 2 = 76 | Tier 2.5 = 88 | Tier 3 = 92
|
||||
# Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92
|
||||
# TBC
|
||||
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
|
||||
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
|
||||
@@ -495,14 +492,16 @@ AiPlayerbot.AutoGearQualityLimit = 3
|
||||
AiPlayerbot.AutoGearScoreLimit = 0
|
||||
|
||||
# Enable/Disable cheats for bots
|
||||
# "food" (bots eat or drink without using food or drinks from their inventory)
|
||||
# "gold" (bots have infinite gold)
|
||||
# "health" (bots have infinite health)
|
||||
# "mana" (bots have infinite mana)
|
||||
# "power" (bots have infinite energy, rage, and runic power)
|
||||
# "taxi" (bots may use all flight paths, though they will not actually learn them)
|
||||
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,taxi")
|
||||
# Default: taxi is enabled
|
||||
AiPlayerbot.BotCheats = "taxi"
|
||||
# "raid" (bots use cheats implemented into raid strategies)
|
||||
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
|
||||
# Default: taxi and raid are enabled
|
||||
AiPlayerbot.BotCheats = "food,taxi,raid"
|
||||
|
||||
#
|
||||
#
|
||||
@@ -588,6 +587,9 @@ AiPlayerbot.LimitTalentsExpansion = 0
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.EnableRandomBotTrading = 1
|
||||
|
||||
# Configure message prefixes which will be excluded in analysis in trade action to open trade window
|
||||
AiPlayerbot.TradeActionExcludedPrefixes = "RPLL_H_,DBMv4,{звезда} Questie,{rt1} Questie"
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
@@ -642,8 +644,8 @@ AiPlayerbot.RandomGearQualityLimit = 3
|
||||
|
||||
# Equipment item level (not gearscore) limitation for randombots (0 = no limit)
|
||||
# Classic
|
||||
# Max iLVL Tier 1 = 66 | Tier 2 = 76 | Tier 2.5 = 81 | Tier 3 = 99
|
||||
# Max iLVL Phase 1 = 71(MC, ONY, ZG) | Phase 2(BWL) = 77 | Phase 2.5(AQ) = 88 | Phase 3(NAXX) = 100 (NOT RECOMMENDED SINCE ILVL OVERLAPS BETWEEN TIERS)
|
||||
# Max iLVL Tier 1 = 66 | Tier 2 = 76 | Tier 2.5 = 88 | Tier 3 = 92
|
||||
# Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92
|
||||
# TBC
|
||||
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
|
||||
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
|
||||
@@ -653,12 +655,16 @@ AiPlayerbot.RandomGearQualityLimit = 3
|
||||
# Default: 0 (no limit)
|
||||
AiPlayerbot.RandomGearScoreLimit = 0
|
||||
|
||||
# Set minimum level of bots that will enchant their equipment (Maxlevel + 1 to disable)
|
||||
# If disabled, random bots can only upgrade equipment through looting and quests
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.IncrementalGearInit = 1
|
||||
|
||||
# Set minimum level of bots that will enchant their equipment (if greater than RandomBotMaxlevel, bots will not enchant equipment)
|
||||
# Default: 60
|
||||
AiPlayerbot.MinEnchantingBotLevel = 60
|
||||
|
||||
# Enable expansion limitation for bot enchants
|
||||
# If enabled, bots will not use TBC enchants until level 61 or WotLK enchanges until level 71
|
||||
# If enabled, bots will not use TBC enchants until level 61 or WotLK enchants until level 71
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.LimitEnchantExpansion = 1
|
||||
|
||||
@@ -690,6 +696,20 @@ AiPlayerbot.AutoUpgradeEquip = 1
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.HunterWolfPet = 0
|
||||
|
||||
# Default pet stance when a bot summons a pet
|
||||
# 0 = Passive, 1 = Defensive, 2 = Aggressive
|
||||
# Default: 1 (Defensive)
|
||||
AiPlayerbot.DefaultPetStance = 1
|
||||
|
||||
# Enable/disable debug messages about pet commands
|
||||
# 0 = Disabled, 1 = Enabled
|
||||
# Default = 0 (disabled)
|
||||
AiPlayerbot.PetChatCommandDebug = 0
|
||||
|
||||
# Prohibit hunter bots from creating pets with any family ID listed below in ExcludedHunterPetFamilies
|
||||
# See the creature_family database table for all pet families by ID (note: ID for spiders is 3)
|
||||
AiPlayerbot.ExcludedHunterPetFamilies = ""
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
@@ -717,8 +737,8 @@ AiPlayerbot.BotActiveAloneForceWhenInGuild = 1
|
||||
# The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
|
||||
# (The scaling will be overruled by the BotActiveAloneForceWhen...rules)
|
||||
#
|
||||
# Limitfloor - when DIFF (latency) above floor, activity scaling is applied starting with 90%
|
||||
# LimitCeiling - when DIFF (latency) above ceiling, activity is 0%;
|
||||
# Limitfloor - when DIFF (latency) is above floor, activity scaling begins
|
||||
# LimitCeiling - when DIFF (latency) is above ceiling, activity is 0%
|
||||
#
|
||||
# MinLevel - only apply scaling when level is above or equal to min(bot)Level
|
||||
# MaxLevel - only apply scaling when level is lower or equal of max(bot)Level
|
||||
@@ -749,11 +769,6 @@ AiPlayerbot.RandomBotGroupNearby = 0
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.AutoDoQuests = 1
|
||||
|
||||
# Randombots will behave more like real players (experimental)
|
||||
# This option will override AiPlayerbot.AutoDoQuests, RandomBotTeleLowerLevel, and RandomBotTeleHigherLevel
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.EnableNewRpgStrategy = 1
|
||||
|
||||
# Quest items to keep in bots' inventories (do not destroy)
|
||||
AiPlayerbot.RandomBotQuestItems = "5175,5176,5177,5178,6948,11000,12382,13704,16309"
|
||||
|
||||
@@ -796,51 +811,58 @@ AiPlayerbot.OpenGoSpell = 6477
|
||||
#
|
||||
|
||||
# Additional randombot strategies
|
||||
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec-based default strategies. These rules are processed after the defaults.
|
||||
AiPlayerbot.RandomBotCombatStrategies = "+dps,+dps assist,-threat"
|
||||
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
||||
# Example: "+threat,-potions"
|
||||
AiPlayerbot.RandomBotCombatStrategies = ""
|
||||
AiPlayerbot.RandomBotNonCombatStrategies = ""
|
||||
|
||||
# Additional altbot strategies
|
||||
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec-based default strategies. These rules are processed after the defaults.
|
||||
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
||||
AiPlayerbot.CombatStrategies = ""
|
||||
AiPlayerbot.NonCombatStrategies = ""
|
||||
|
||||
# Remove "healer dps" strategy on specified maps.
|
||||
# Default: 0 (disabled)
|
||||
AiPlayerbot.HealerDPSMapRestriction = 0
|
||||
|
||||
# List of Map IDs where "healer dps" strategy will be removed if AiPlayerbot.HealerDPSMapRestriction is enabled
|
||||
# Default: (Dungeon and Raid maps) "33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509,531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"
|
||||
AiPlayerbot.RestrictedHealerDPSMaps = "33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509,531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# TELEPORTS
|
||||
# RPG STRATEGY
|
||||
#
|
||||
#
|
||||
|
||||
# Maps where bots can be teleported to
|
||||
AiPlayerbot.RandomBotMaps = 0,1,530,571
|
||||
|
||||
# Probabilty bots teleport to banker (city)
|
||||
# Default: 0.25
|
||||
AiPlayerbot.ProbTeleToBankers = 0.25
|
||||
|
||||
# How far randombots are teleported after death
|
||||
AiPlayerbot.RandomBotTeleportDistance = 100
|
||||
|
||||
# How many levels below the lowest-level creature in a zone, can a bot be
|
||||
# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled
|
||||
# Default: 1 (randombot will leave if they are more than 1 level lower)
|
||||
AiPlayerbot.RandomBotTeleLowerLevel = 1
|
||||
|
||||
# How many levels above the highest-level creature in a zone, can a bot be
|
||||
# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled
|
||||
# Default: 3 (randombot will leave if they are more than 3 levels higher)
|
||||
AiPlayerbot.RandomBotTeleHigherLevel = 3
|
||||
|
||||
# Bots automatically teleport to another place for leveling on levelup
|
||||
# Randombots will behave more like real players (experimental)
|
||||
# This option will override AiPlayerbot.AutoDoQuests, RandomBotTeleLowerLevel, and RandomBotTeleHigherLevel
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.AutoTeleportForLevel = 1
|
||||
AiPlayerbot.EnableNewRpgStrategy = 1
|
||||
|
||||
# Control probability weights for RPG status of bots. Takes effect only when the status meets its premise.
|
||||
# Sum of weights need not be 100. Set to 0 to disable the status.
|
||||
#
|
||||
# WanderRandom (Default: 15 Move randomly nearby to find and kill mobs)
|
||||
# WanderNpc (Default: 20 Randomly interact with nearby NPCs)
|
||||
# GoGrind (Default: 15 Go to nearby level-appropriate locations to grind for killing mobs)
|
||||
# GoCamp (Default: 10 Return to a nearby camp depending on innkeeper/flightmaster)
|
||||
# DoQuest (Default: 60 Select quest from the quest log and head to the location to attempt completion)
|
||||
# TravelFlight (Default: 15 Go to the nearest flightmaster and fly to a level-appropriate area)
|
||||
# Rest (Default: 5 Take a break for a while and do nothing)
|
||||
AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15
|
||||
AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20
|
||||
AiPlayerbot.RpgStatusProbWeight.GoGrind = 15
|
||||
AiPlayerbot.RpgStatusProbWeight.GoCamp = 10
|
||||
AiPlayerbot.RpgStatusProbWeight.DoQuest = 60
|
||||
AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15
|
||||
AiPlayerbot.RpgStatusProbWeight.Rest = 5
|
||||
|
||||
# Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy
|
||||
# Requires EnableNewRpgStrategy enabled
|
||||
# Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel
|
||||
#
|
||||
# Classic WoW - Low-level zones:
|
||||
@@ -980,6 +1002,54 @@ AiPlayerbot.ZoneBracket.4197 = 79,80
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# TELEPORTS
|
||||
#
|
||||
#
|
||||
|
||||
# Maps where bots can be teleported to
|
||||
AiPlayerbot.RandomBotMaps = 0,1,530,571
|
||||
|
||||
# Probabilty bots teleport to banker (city)
|
||||
# Default: 0.25
|
||||
AiPlayerbot.ProbTeleToBankers = 0.25
|
||||
|
||||
# Control probability weights for bots teleporting to Capital city bankers
|
||||
# Sum of weights need not be 100. Set to 0 to disable teleporting to the city.
|
||||
AiPlayerbot.EnableWeightTeleToCityBankers = 1
|
||||
AiPlayerbot.TeleToStormwindWeight = 2
|
||||
AiPlayerbot.TeleToIronforgeWeight = 1
|
||||
AiPlayerbot.TeleToDarnassusWeight = 1
|
||||
AiPlayerbot.TeleToExodarWeight = 1
|
||||
AiPlayerbot.TeleToOrgrimmarWeight = 2
|
||||
AiPlayerbot.TeleToUndercityWeight = 1
|
||||
AiPlayerbot.TeleToThunderBluffWeight = 1
|
||||
AiPlayerbot.TeleToSilvermoonCityWeight = 1
|
||||
AiPlayerbot.TeleToShattrathCityWeight = 1
|
||||
AiPlayerbot.TeleToDalaranWeight = 1
|
||||
|
||||
# How far randombots are teleported after death
|
||||
AiPlayerbot.RandomBotTeleportDistance = 100
|
||||
|
||||
# How many levels below the lowest-level creature in a zone, can a bot be
|
||||
# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled
|
||||
# Default: 1 (randombot will leave if they are more than 1 level lower)
|
||||
AiPlayerbot.RandomBotTeleLowerLevel = 1
|
||||
|
||||
# How many levels above the highest-level creature in a zone, can a bot be
|
||||
# This will have no effect if AiPlayerbot.EnableNewRpgStrategy is enabled
|
||||
# Default: 3 (randombot will leave if they are more than 3 levels higher)
|
||||
AiPlayerbot.RandomBotTeleHigherLevel = 3
|
||||
|
||||
# Bots automatically teleport to another place for leveling on levelup
|
||||
# Default: 1 (enabled)
|
||||
AiPlayerbot.AutoTeleportForLevel = 1
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
####################################################################################################
|
||||
# BATTLEGROUNDS & ARENAS & PVP
|
||||
#
|
||||
@@ -1059,10 +1129,10 @@ AiPlayerbot.RandomBotArenaTeamMinRating = 1000
|
||||
AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
||||
|
||||
# PvP Restricted Zones (bots don't pvp)
|
||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139"
|
||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
|
||||
|
||||
# PvP Restricted Areas (bots don't pvp)
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312"
|
||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
|
||||
|
||||
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
||||
AiPlayerbot.FastReactInBG = 1
|
||||
@@ -1136,6 +1206,18 @@ AiPlayerbot.PremadeSpecName.1.2 = prot pve
|
||||
AiPlayerbot.PremadeSpecGlyph.1.2 = 43424,43395,43425,43399,49084,45793
|
||||
AiPlayerbot.PremadeSpecLink.1.2.60 = --053351225000210521030113321
|
||||
AiPlayerbot.PremadeSpecLink.1.2.80 = 3500030023-301-053351225000210521030113321
|
||||
AiPlayerbot.PremadeSpecName.1.3 = arms pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.1.3 = 43417,43397,43423,43396,49084,43421
|
||||
AiPlayerbot.PremadeSpecLink.1.3.60 = 0320232023331100032212012221251
|
||||
AiPlayerbot.PremadeSpecLink.1.3.80 = 0320332023335100232212013231251-3250001
|
||||
AiPlayerbot.PremadeSpecName.1.4 = fury pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.1.4 = 43432,43397,43417,43395,43396,43418
|
||||
AiPlayerbot.PremadeSpecLink.1.4.60 = -325000131500212250120511351
|
||||
AiPlayerbot.PremadeSpecLink.1.4.80 = 03220300233-325000131500212250122511351
|
||||
AiPlayerbot.PremadeSpecName.1.5 = prot pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.1.5 = 43425,43397,43415,43396,49084,45792
|
||||
AiPlayerbot.PremadeSpecLink.1.5.60 = --250031220223012520332113321
|
||||
AiPlayerbot.PremadeSpecLink.1.5.80 = 0502300123-3-250031220223012521332113321
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1160,6 +1242,18 @@ AiPlayerbot.PremadeSpecGlyph.2.2 = 41092,43367,41099,43369,43365,43869
|
||||
AiPlayerbot.PremadeSpecLink.2.2.60 = --05230051203331302133231131
|
||||
AiPlayerbot.PremadeSpecLink.2.2.65 = -05-05230051203331302133231131
|
||||
AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331
|
||||
AiPlayerbot.PremadeSpecName.2.3 = holy pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.2.3 = 41110,43367,45746,43366,43365,45747
|
||||
AiPlayerbot.PremadeSpecLink.2.3.60 = 50332150300013050133215221
|
||||
AiPlayerbot.PremadeSpecLink.2.3.80 = 50332150300013050133315221-5032013122
|
||||
AiPlayerbot.PremadeSpecName.2.4 = prot pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.2.4 = 41092,43369,41101,43368,43365,45745
|
||||
AiPlayerbot.PremadeSpecLink.2.4.60 = -15320130223122311323311321
|
||||
AiPlayerbot.PremadeSpecLink.2.4.80 = -15320130223122321333312321-052300502
|
||||
AiPlayerbot.PremadeSpecName.2.5 = ret pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.2.5 = 41095,43369,41102,43368,43365,45747
|
||||
AiPlayerbot.PremadeSpecLink.2.5.60 = --05230250203331222133201321
|
||||
AiPlayerbot.PremadeSpecLink.2.5.80 = -1532013022-05230250203331322133201321
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1172,17 +1266,31 @@ AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331
|
||||
#
|
||||
|
||||
AiPlayerbot.PremadeSpecName.3.0 = bm pve
|
||||
AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,45732
|
||||
AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112243110531051
|
||||
AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112243120531251-025305101
|
||||
AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,42914
|
||||
AiPlayerbot.PremadeSpecLink.3.0.40 = 512002015051122301
|
||||
AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112233110531151
|
||||
AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112243130531351-005305101
|
||||
AiPlayerbot.PremadeSpecName.3.1 = mm pve
|
||||
AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42914,43351,43338,45732
|
||||
AiPlayerbot.PremadeSpecLink.3.1.60 = -025315101030013233125031051
|
||||
AiPlayerbot.PremadeSpecLink.3.1.80 = 502-025335101030013233135031351-5000002
|
||||
AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,45625,43351,43338,42914
|
||||
AiPlayerbot.PremadeSpecLink.3.1.60 = -035305101030013233115031151
|
||||
AiPlayerbot.PremadeSpecLink.3.1.80 = 502-035305101230013233135031351-5000002
|
||||
AiPlayerbot.PremadeSpecName.3.2 = surv pve
|
||||
AiPlayerbot.PremadeSpecGlyph.3.2 = 42912,43350,45731,43351,43338,45732
|
||||
AiPlayerbot.PremadeSpecGlyph.3.2 = 45733,43350,45731,43351,43338,45732
|
||||
AiPlayerbot.PremadeSpecLink.3.2.60 = --5000032500033330502135201311
|
||||
AiPlayerbot.PremadeSpecLink.3.2.80 = -005305101-5000032500033330532135301321
|
||||
AiPlayerbot.PremadeSpecName.3.3 = bm pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.3.3 = 42897,42900,42902,43356,43338,42900
|
||||
AiPlayerbot.PremadeSpecLink.3.3.60 = 05203201505012233100531151
|
||||
AiPlayerbot.PremadeSpecLink.3.3.80 = 05203201505012233100531351-005305101-03
|
||||
AiPlayerbot.PremadeSpecName.3.4 = mm pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.3.4 = 42912,43351,42897,43338,43356,42904
|
||||
AiPlayerbot.PremadeSpecLink.3.4.60 = -034305101030213231135031051
|
||||
AiPlayerbot.PremadeSpecLink.3.4.80 = -035305101030213233135031051-53013020102
|
||||
AiPlayerbot.PremadeSpecName.3.5 = surv pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.3.5 = 42912,43350,42904,43356,43338,45731
|
||||
AiPlayerbot.PremadeSpecLink.3.5.60 = --2300302410233030533135001031
|
||||
AiPlayerbot.PremadeSpecLink.3.5.80 = -005305201-2300302510233330533135001031
|
||||
|
||||
|
||||
# HUNTER PET
|
||||
#
|
||||
@@ -1218,6 +1326,18 @@ AiPlayerbot.PremadeSpecName.4.2 = subtlety pve
|
||||
AiPlayerbot.PremadeSpecGlyph.4.2 = 42967,43379,45764,43380,43378,45767
|
||||
AiPlayerbot.PremadeSpecLink.4.2.60 = --5022012030321121350115031151
|
||||
AiPlayerbot.PremadeSpecLink.4.2.80 = 30532010114--5022012030321121350115031151
|
||||
AiPlayerbot.PremadeSpecName.4.3 = as pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.4.3 = 42974,43380,45768,43379,43376,42971
|
||||
AiPlayerbot.PremadeSpecLink.4.3.60 = 005303103342102522103031--50002
|
||||
AiPlayerbot.PremadeSpecLink.4.3.80 = 005303103342102522103031-004-532023203000012
|
||||
AiPlayerbot.PremadeSpecName.4.4 = combat pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.4.4 = 42972,43380,45762,43376,43378,42971
|
||||
AiPlayerbot.PremadeSpecLink.4.4.60 = -3250002050225010223102321251
|
||||
AiPlayerbot.PremadeSpecLink.4.4.80 = 305120105-3250002050235010223102521251
|
||||
AiPlayerbot.PremadeSpecName.4.5 = subtlety pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.4.5 = 42968,43376,45764,43380,43379,42971
|
||||
AiPlayerbot.PremadeSpecLink.4.5.60 = --5120212030320121330133221251
|
||||
AiPlayerbot.PremadeSpecLink.4.5.80 = 3023031-3-5120212030320121350135231251
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1241,6 +1361,18 @@ AiPlayerbot.PremadeSpecName.5.2 = shadow pve
|
||||
AiPlayerbot.PremadeSpecGlyph.5.2 = 42406,43371,42407,43374,43342,42415
|
||||
AiPlayerbot.PremadeSpecLink.5.2.60 = --325003041203010323150301351
|
||||
AiPlayerbot.PremadeSpecLink.5.2.80 = 0503203--325023051223010323152301351
|
||||
AiPlayerbot.PremadeSpecName.5.3 = disc pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.5.3 = 42408,43371,45760,43370,43374,45756
|
||||
AiPlayerbot.PremadeSpecLink.5.3.60 = 5003203130320512201323031051
|
||||
AiPlayerbot.PremadeSpecLink.5.3.80 = 5003203130322512331013231151-23050113
|
||||
AiPlayerbot.PremadeSpecName.5.4 = holy pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.5.4 = 42411,43371,42408,43370,43374,45755
|
||||
AiPlayerbot.PremadeSpecLink.5.4.60 = -235501031000152430320031151
|
||||
AiPlayerbot.PremadeSpecLink.5.4.80 = 500320313-235501031000152530320031351
|
||||
AiPlayerbot.PremadeSpecName.5.5 = shadow pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.5.5 = 42407,43371,45753,43370,43374,42408
|
||||
AiPlayerbot.PremadeSpecLink.5.5.60 = --005323241223112003102311351
|
||||
AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1268,6 +1400,19 @@ AiPlayerbot.PremadeSpecName.6.3 = double aura blood pve
|
||||
AiPlayerbot.PremadeSpecGlyph.6.3 = 45805,43673,43827,43544,43672,43554
|
||||
AiPlayerbot.PremadeSpecLink.6.3.60 = 005512153330030320102013-305
|
||||
AiPlayerbot.PremadeSpecLink.6.3.80 = 005512153330030320102013-3050505002023001-002
|
||||
AiPlayerbot.PremadeSpecName.6.4 = blood pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.6.4 = 43534,43535,45799,43673,43672,45805
|
||||
AiPlayerbot.PremadeSpecLink.6.4.60 = 2305021503003313201222101351
|
||||
AiPlayerbot.PremadeSpecLink.6.4.80 = 2305021503003313201222101351--032232300023
|
||||
AiPlayerbot.PremadeSpecName.6.5 = frost pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.6.5 = 43543,43539,45800,43673,43672,45806
|
||||
AiPlayerbot.PremadeSpecLink.6.5.60 = -32015351022203012001233101251
|
||||
AiPlayerbot.PremadeSpecLink.6.5.80 = 0055-32015351052203012001233131351-03
|
||||
AiPlayerbot.PremadeSpecName.6.6 = unholy pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.6.6 = 45804,43539,43549,43673,43672,45805
|
||||
AiPlayerbot.PremadeSpecLink.6.6.60 = --2301323301002152230101203103151
|
||||
AiPlayerbot.PremadeSpecLink.6.6.80 = -320050410002-2301323301002152230101203133151
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1291,6 +1436,19 @@ AiPlayerbot.PremadeSpecName.7.2 = resto pve
|
||||
AiPlayerbot.PremadeSpecGlyph.7.2 = 41517,43385,41527,43386,44923,45775
|
||||
AiPlayerbot.PremadeSpecLink.7.2.60 = --50005301235310501102321251
|
||||
AiPlayerbot.PremadeSpecLink.7.2.80 = -00502033-50005331335310501122331251
|
||||
AiPlayerbot.PremadeSpecName.7.3 = ele pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.7.3 = 45778,43388,45770,43725,43386,41524
|
||||
AiPlayerbot.PremadeSpecLink.7.3.60 = 0533001503213051322301341
|
||||
AiPlayerbot.PremadeSpecLink.7.3.80 = 0533051503213051322331351-023212001
|
||||
AiPlayerbot.PremadeSpecName.7.4 = enh pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.7.4 = 45778,43388,41526,43725,43344,45771
|
||||
AiPlayerbot.PremadeSpecLink.7.4.60 = -02305203105001333201131131151
|
||||
AiPlayerbot.PremadeSpecLink.7.4.80 = 0503351-02305203105001333211131231251
|
||||
AiPlayerbot.PremadeSpecName.7.5 = resto pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.7.5 = 45778,43388,45775,43725,43344,41535
|
||||
AiPlayerbot.PremadeSpecLink.7.5.60 = --05032331331013501120321251
|
||||
AiPlayerbot.PremadeSpecLink.7.5.80 = -023222301004-05032331331013501120331251
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1304,8 +1462,8 @@ AiPlayerbot.PremadeSpecLink.7.2.80 = -00502033-50005331335310501122331251
|
||||
|
||||
AiPlayerbot.PremadeSpecName.8.0 = arcane pve
|
||||
AiPlayerbot.PremadeSpecGlyph.8.0 = 42735,43339,44955,43364,43361,42751
|
||||
AiPlayerbot.PremadeSpecLink.8.0.60 = 23000503110033014032310150532
|
||||
AiPlayerbot.PremadeSpecLink.8.0.80 = 23000523310033015032310250532-03-203203001
|
||||
AiPlayerbot.PremadeSpecLink.8.0.60 = 230005231100330150323102500321
|
||||
AiPlayerbot.PremadeSpecLink.8.0.80 = 230005231100330150323102505321-03-203303001
|
||||
AiPlayerbot.PremadeSpecName.8.1 = fire pve
|
||||
AiPlayerbot.PremadeSpecGlyph.8.1 = 42739,43339,45737,43364,44920,42751
|
||||
AiPlayerbot.PremadeSpecLink.8.1.60 = -0055030011302231053120321341
|
||||
@@ -1317,7 +1475,20 @@ AiPlayerbot.PremadeSpecLink.8.2.80 = 23002303110003--053303031320310003015223135
|
||||
AiPlayerbot.PremadeSpecName.8.3 = frostfire pve
|
||||
AiPlayerbot.PremadeSpecGlyph.8.3 = 44684,44920,42751,43339,43364,45737
|
||||
AiPlayerbot.PremadeSpecLink.8.3.60 = -2305032012303331053120300051
|
||||
AiPlayerbot.PremadeSpecLink.8.3.80 = -2305032012303331053120311351-023303031
|
||||
AiPlayerbot.PremadeSpecLink.8.3.80 = -2305032012303331053120321351-023302031
|
||||
AiPlayerbot.PremadeSpecName.8.4 = arcane pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.8.4 = 42735,43364,42738,43360,43357,42752
|
||||
AiPlayerbot.PremadeSpecLink.8.4.60 = 205323200122032103303102015221
|
||||
AiPlayerbot.PremadeSpecLink.8.4.80 = 205323200122032103303102015321-23002-303020301
|
||||
AiPlayerbot.PremadeSpecName.8.5 = fire pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.8.5 = 42738,43364,42752,43360,43357,45737
|
||||
AiPlayerbot.PremadeSpecLink.8.5.60 = -2305202312020031223122301351
|
||||
AiPlayerbot.PremadeSpecLink.8.5.80 = 230321030122-2305212312020031223122301351
|
||||
AiPlayerbot.PremadeSpecName.8.6 = frost pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.8.6 = 42738,43364,45740,43357,43360,42752
|
||||
AiPlayerbot.PremadeSpecLink.8.6.60 = --3533203210203100232102231151
|
||||
AiPlayerbot.PremadeSpecLink.8.6.80 = 23032103010203--3533203210203100232102231151
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1333,7 +1504,7 @@ AiPlayerbot.PremadeSpecName.9.0 = affli pve
|
||||
AiPlayerbot.PremadeSpecGlyph.9.0 = 45785,43390,50077,43394,43393,45779
|
||||
AiPlayerbot.PremadeSpecLink.9.0.60 = 2350022001113510053500131151
|
||||
AiPlayerbot.PremadeSpecLink.9.0.70 = 2350022001113510053500131151--55
|
||||
AiPlayerbot.PremadeSpecLink.9.0.80 = 2350022001113510253500331151--5500000501
|
||||
AiPlayerbot.PremadeSpecLink.9.0.80 = 2350022001123510253500331151--55000005
|
||||
AiPlayerbot.PremadeSpecName.9.1 = demo pve
|
||||
AiPlayerbot.PremadeSpecGlyph.9.1 = 45785,43390,50077,43394,43393,42459
|
||||
AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530135201051
|
||||
@@ -1341,8 +1512,21 @@ AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55
|
||||
AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005
|
||||
AiPlayerbot.PremadeSpecName.9.2 = destro pve
|
||||
AiPlayerbot.PremadeSpecGlyph.9.2 = 45785,43390,50077,43394,43393,42454
|
||||
AiPlayerbot.PremadeSpecLink.9.2.60 = --05203205210131051313230341
|
||||
AiPlayerbot.PremadeSpecLink.9.2.80 = -03310030003-05203205210331051335230351
|
||||
AiPlayerbot.PremadeSpecLink.9.2.60 = --05203215200231051305031151
|
||||
AiPlayerbot.PremadeSpecLink.9.2.80 = 23-0302-05203215220331051335231351
|
||||
AiPlayerbot.PremadeSpecName.9.3 = affli pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.9.3 = 50077,43392,42455,43390,43389,45783
|
||||
AiPlayerbot.PremadeSpecLink.9.3.60 = 0350002231223011053502301151
|
||||
AiPlayerbot.PremadeSpecLink.9.3.80 = 2350002231223111053502301151-2032003011302
|
||||
AiPlayerbot.PremadeSpecName.9.4 = demo pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.9.4 = 42459,43392,45780,43390,43389,45783
|
||||
AiPlayerbot.PremadeSpecLink.9.4.60 = -003203301135202530135001251
|
||||
AiPlayerbot.PremadeSpecLink.9.4.80 = -003203301135202530135011351-052300152
|
||||
AiPlayerbot.PremadeSpecName.9.5 = destro pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.9.5 = 42471,43392,42454,43390,43389,45783
|
||||
AiPlayerbot.PremadeSpecLink.9.5.60 = --05230015220331351005031051
|
||||
AiPlayerbot.PremadeSpecLink.9.5.80 = -2032003311302-05230015220331351005031051
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1370,6 +1554,19 @@ AiPlayerbot.PremadeSpecName.11.3 = cat pve
|
||||
AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,44922,45604
|
||||
AiPlayerbot.PremadeSpecLink.11.3.60 = -552202032322010053100030310501
|
||||
AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012
|
||||
AiPlayerbot.PremadeSpecName.11.4 = balance pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.11.4 = 40921,43331,45622,43674,43335,45623
|
||||
AiPlayerbot.PremadeSpecLink.11.4.60 = 5012203115331002213032311231
|
||||
AiPlayerbot.PremadeSpecLink.11.4.80 = 5022203125331003213035311231--230033012
|
||||
AiPlayerbot.PremadeSpecName.11.5 = cat pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.11.5 = 40902,43331,45601,43674,43335,40901
|
||||
AiPlayerbot.PremadeSpecLink.11.5.60 = -513202032322010053103030310501
|
||||
AiPlayerbot.PremadeSpecLink.11.5.80 = -523202032322010053103030310511-205503012
|
||||
AiPlayerbot.PremadeSpecName.11.6 = resto pvp
|
||||
AiPlayerbot.PremadeSpecGlyph.11.6 = 40913,43331,40906,43335,43674,45623
|
||||
AiPlayerbot.PremadeSpecLink.11.6.60 = --230033312031500511350013051
|
||||
AiPlayerbot.PremadeSpecLink.11.6.80 = 05320021--230033312031500531353013251
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1389,41 +1586,12 @@ AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012
|
||||
|
||||
# Applies a permanent buff to all bots simulating effects of spells, flasks, food, runes, etc.
|
||||
# Requires sending the command "nc +worldbuff" in chat to a bot (or a group of bots) to enable
|
||||
# Numbers after the equal signs are spell IDs and may be customized
|
||||
# See Randombots Default Talent Specs for more info on each spec; they are listed in that section by the names in the parentheticals (e.g., arms pve, fury pve)
|
||||
# Each entry in the matrix should be formatted as follows: Entry:FactionID,ClassID,SpecID,MinimumLevel,MaximumLevel:SpellID1,SpellID2,etc.;
|
||||
# Use 0 for any field to make it agnostic (e.g., 0 for FactionID means the entry will apply buffs to either faction)
|
||||
# The default entries create a cross-faction list of level 80 buffs for each implemented pve spec from the "Premade Specs" section
|
||||
# The default entries may be deleted or modified, and new custom entries may be added
|
||||
|
||||
AiPlayerbot.WorldBuff.0.1.0.80.80 = 53760,57358 #WARRIOR ARMS (arms pve)
|
||||
AiPlayerbot.WorldBuff.0.1.1.80.80 = 53760,57358 #WARRIOR FURY (fury pve)
|
||||
AiPlayerbot.WorldBuff.0.1.2.80.80 = 53758,57356 #WARRIOR PROTECTION (prot pve)
|
||||
AiPlayerbot.WorldBuff.0.2.0.80.80 = 60347,53749,57332 #PALADIN HOLY (holy pve)
|
||||
AiPlayerbot.WorldBuff.0.2.1.80.80 = 53758,57356 #PALADIN PROTECTION (prot pve)
|
||||
AiPlayerbot.WorldBuff.0.2.2.80.80 = 53760,57371 #PALADIN RETRIBUTION (ret pve)
|
||||
AiPlayerbot.WorldBuff.0.3.0.80.80 = 53760,57325 #HUNTER BEAST (bm pve)
|
||||
AiPlayerbot.WorldBuff.0.3.1.80.80 = 53760,57358 #HUNTER MARKSMANSHIP (mm pve)
|
||||
AiPlayerbot.WorldBuff.0.3.2.80.80 = 53760,57367 #HUNTER SURVIVAL (surv pve)
|
||||
AiPlayerbot.WorldBuff.0.4.0.80.80 = 53760,57325 #ROGUE ASSASINATION (as pve)
|
||||
AiPlayerbot.WorldBuff.0.4.1.80.80 = 53760,57358 #ROGUE COMBAT (combat pve)
|
||||
AiPlayerbot.WorldBuff.0.4.2.80.80 = 53760,57367 #ROGUE SUBTLETY (subtlety pve)
|
||||
AiPlayerbot.WorldBuff.0.5.0.80.80 = 53755,57327 #PRIEST DISCIPLINE (disc pve)
|
||||
AiPlayerbot.WorldBuff.0.5.1.80.80 = 53755,57327 #PRIEST HOLY (holy pve)
|
||||
AiPlayerbot.WorldBuff.0.5.2.80.80 = 53755,57327 #PRIEST SHADOW (shadow pve)
|
||||
AiPlayerbot.WorldBuff.0.6.0.80.80 = 53758,57356 #DEATH KNIGHT BLOOD (blood pve)
|
||||
AiPlayerbot.WorldBuff.0.6.1.80.80 = 53760,57358 #DEATH KNIGHT FROST (frost pve)
|
||||
AiPlayerbot.WorldBuff.0.6.2.80.80 = 53760,57358 #DEATH KNIGHT UNHOLY (unholy pve)
|
||||
AiPlayerbot.WorldBuff.0.6.3.80.80 = 53760,57371 #DEATH KNIGHT BLOOD DPS (double aura blood pve)
|
||||
AiPlayerbot.WorldBuff.0.7.0.80.80 = 53755,57327 #SHAMAN ELEMENTAL (ele pve)
|
||||
AiPlayerbot.WorldBuff.0.7.1.80.80 = 53760,57325 #SHAMAN ENHANCEMENT (enh pve)
|
||||
AiPlayerbot.WorldBuff.0.7.2.80.80 = 53755,57327 #SHAMAN RESTORATION (resto pve)
|
||||
AiPlayerbot.WorldBuff.0.8.0.80.80 = 53755,57327 #MAGE ARCANE (arcane pve)
|
||||
AiPlayerbot.WorldBuff.0.8.1.80.80 = 53755,57327 #MAGE FIRE (fire pve)
|
||||
AiPlayerbot.WorldBuff.0.8.2.80.80 = 53755,57327 #MAGE FROST (frost pve)
|
||||
AiPlayerbot.WorldBuff.0.9.0.80.80 = 53755,57327 #WARLOCK AFFLICTION (affli pve)
|
||||
AiPlayerbot.WorldBuff.0.9.1.80.80 = 53755,57327 #WARLOCK DEMONOLOGY (demo pve)
|
||||
AiPlayerbot.WorldBuff.0.9.2.80.80 = 53755,57327 #WARLOCK DESTRUCTION (destro pve)
|
||||
AiPlayerbot.WorldBuff.0.11.0.80.80 = 53755,57327 #DRUID BALANCE (balance pve)
|
||||
AiPlayerbot.WorldBuff.0.11.1.80.80 = 53749,53763,57367 #DRUID FERAL BEAR (bear pve)
|
||||
AiPlayerbot.WorldBuff.0.11.2.80.80 = 54212,57334 #DRUID RESTORATION (resto pve)
|
||||
AiPlayerbot.WorldBuff.0.11.3.80.80 = 53760,57358 #DRUID FERAL CAT (cat pve)
|
||||
AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIOR FURY 2:0,1,1,80,80:53760,57358; # WARRIOR PROTECTION 3:0,1,2,80,80:53758,57356; # PALADIN HOLY 4:0,2,0,80,80:53749,57332,60347; # PALADIN PROTECTION 5:0,2,1,80,80:53758,57356; # PALADIN RETRIBUTION 6:0,2,2,80,80:53760,57371; # HUNTER BEAST 7:0,3,0,80,80:53760,57325; # HUNTER MARKSMANSHIP 8:0,3,1,80,80:53760,57358; # HUNTER SURVIVAL 9:0,3,2,80,80:53760,57367; # ROGUE ASSASSINATION 10:0,4,0,80,80:53760,57325; # ROGUE COMBAT 11:0,4,1,80,80:53760,57358; # ROGUE SUBTLETY 12:0,4,2,80,80:53760,57367; # PRIEST DISCIPLINE 13:0,5,0,80,80:53755,57327; # PRIEST HOLY 14:0,5,1,80,80:53755,57327; # PRIEST SHADOW 15:0,5,2,80,80:53755,57327; # DEATH KNIGHT BLOOD 16:0,6,0,80,80:53758,57356; # DEATH KNIGHT FROST 17:0,6,1,80,80:53760,57358; # DEATH KNIGHT UNHOLY 18:0,6,2,80,80:53760,57358; # DEATH KNIGHT BLOOD DPS 19:0,6,3,80,80:53760,57371; # SHAMAN ELEMENTAL 20:0,7,0,80,80:53755,57327; # SHAMAN ENHANCEMENT 21:0,7,1,80,80:53760,57325; # SHAMAN RESTORATION 22:0,7,2,80,80:53755,57327; # MAGE ARCANE 23:0,8,0,80,80:53755,57327; # MAGE FIRE 24:0,8,1,80,80:53755,57327; # MAGE FROST 25:0,8,2,80,80:53755,57327; # WARLOCK AFFLICTION 26:0,9,0,80,80:53755,57327; # WARLOCK DEMONOLOGY 27:0,9,1,80,80:53755,57327; # WARLOCK DESTRUCTION 28:0,9,2,80,80:53755,57327; # DRUID BALANCE 29:0,11,0,80,80:53755,57327; # DRUID FERAL BEAR 30:0,11,1,80,80:53749,53763,57367; # DRUID RESTORATION 31:0,11,2,80,80:54212,57334; # DRUID FERAL CAT 32:0,11,3,80,80:53760,57358
|
||||
|
||||
#
|
||||
#
|
||||
@@ -1590,11 +1758,11 @@ AiPlayerbot.RandomClassSpecIndex.8.2 = 2
|
||||
#
|
||||
#
|
||||
|
||||
AiPlayerbot.RandomClassSpecProb.9.0 = 45
|
||||
AiPlayerbot.RandomClassSpecProb.9.0 = 33
|
||||
AiPlayerbot.RandomClassSpecIndex.9.0 = 0
|
||||
AiPlayerbot.RandomClassSpecProb.9.1 = 45
|
||||
AiPlayerbot.RandomClassSpecProb.9.1 = 34
|
||||
AiPlayerbot.RandomClassSpecIndex.9.1 = 1
|
||||
AiPlayerbot.RandomClassSpecProb.9.2 = 10
|
||||
AiPlayerbot.RandomClassSpecProb.9.2 = 33
|
||||
AiPlayerbot.RandomClassSpecIndex.9.2 = 2
|
||||
|
||||
#
|
||||
@@ -1828,9 +1996,17 @@ AiPlayerbot.AllowedLogFiles = ""
|
||||
|
||||
####################################################################################################
|
||||
# A list of gameObject GUID's that are not allowed for bots to interact with.
|
||||
# Example: 176213 = Blood of Heroes
|
||||
# Example: 17155 = Defias Gunpowder
|
||||
AiPlayerbot.DisallowedGameObjects = 176213,17155
|
||||
#
|
||||
AiPlayerbot.DisallowedGameObjects = 176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,2857,179490,141596,160836,160845,179516,176224,181085,176112,128308,128403,165739,165738,175245,175970,176325,176327,123329
|
||||
#
|
||||
# List of GUID's:
|
||||
# QuestItems:
|
||||
# 176213 = Blood of Heroes, 17155 = Defias Gunpowder, 2656 = Waterlogged Envelope, 123329 = Baelogs Chest
|
||||
# Chests:
|
||||
# Large Solid Chest = 74448, Box of Assorted Parts = 19020, Food Crate = 3719, Water Barrel = 3658, Barrel of Milk = 3705, Barrel of sweet Nectar = 3706, Tattered Chest = 105579, Large bettered Chest = 75293, Solid Chest = 2857, Battered Foodlocker = 179490, Witch Doctor's Chest = 141596, Relic Coffer = 160836, Dark Coffer = 160845, Fengus's Chest = 179516, Supply Crate = 176224/181085, Malor's Strongbox = 176112
|
||||
# Other:
|
||||
# Shallow Grave (Zul'Farrak) = 128308/128403, Grim Guzzler Boar (Blackrock Depths) = 165739, Dark Iron Ale Mug (Blackrock Depths) = 165738, Father Flame (Blackrock Spire) = 175245, Unforged Runic Breastplate (Blackrock Spire) = 175970, Blacksmithing Plans (Stratholme) = 176325/176327
|
||||
# Feel free to edit and help to complete.
|
||||
#
|
||||
####################################################################################################
|
||||
|
||||
@@ -1888,3 +2064,9 @@ AiPlayerbot.TargetPosRecalcDistance = 0.1
|
||||
|
||||
# Allow bots to be summoned near innkeepers
|
||||
AiPlayerbot.SummonAtInnkeepersEnabled = 1
|
||||
|
||||
# Enable buffs in ICC to make Heroic easier and more casual.
|
||||
# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for non tank bots, increased threat for tank bots.
|
||||
# Buffs will be applied on PP, Sindragosa and Lich King
|
||||
|
||||
AiPlayerbot.EnableICCBuffs = 1
|
||||
|
||||
0
data/sql/characters/updates/.gitkeep
Normal file
0
data/sql/characters/updates/.gitkeep
Normal file
8
data/sql/playerbots/base/playerbots_account_type.sql
Normal file
8
data/sql/playerbots/base/playerbots_account_type.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
DROP TABLE IF EXISTS `playerbots_account_type`;
|
||||
CREATE TABLE `playerbots_account_type` (
|
||||
`account_id` int unsigned NOT NULL,
|
||||
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
|
||||
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`account_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Create playerbots_account_type table for tracking accounts assignments
|
||||
DROP TABLE IF EXISTS `playerbots_account_type`;
|
||||
CREATE TABLE `playerbots_account_type` (
|
||||
`account_id` int unsigned NOT NULL,
|
||||
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
|
||||
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`account_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';
|
||||
|
||||
3
data/sql/world/updates/2025_08_27_03.sql
Normal file
3
data/sql/world/updates/2025_08_27_03.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
DELETE FROM spell_dbc WHERE ID = 30758;
|
||||
INSERT INTO spell_dbc (`ID`,`Category`,`DispelType`,`Mechanic`,`Attributes`,`AttributesEx`,`AttributesEx2`,`AttributesEx3`,`AttributesEx4`,`AttributesEx5`,`AttributesEx6`,`AttributesEx7`,`ShapeshiftMask`,`unk_320_2`,`ShapeshiftExclude`,`unk_320_3`,`Targets`,`TargetCreatureType`,`RequiresSpellFocus`,`FacingCasterFlags`,`CasterAuraState`,`TargetAuraState`,`ExcludeCasterAuraState`,`ExcludeTargetAuraState`,`CasterAuraSpell`,`TargetAuraSpell`,`ExcludeCasterAuraSpell`,`ExcludeTargetAuraSpell`,`CastingTimeIndex`,`RecoveryTime`,`CategoryRecoveryTime`,`InterruptFlags`,`AuraInterruptFlags`,`ChannelInterruptFlags`,`ProcTypeMask`,`ProcChance`,`ProcCharges`,`MaxLevel`,`BaseLevel`,`SpellLevel`,`DurationIndex`,`PowerType`,`ManaCost`,`ManaCostPerLevel`,`ManaPerSecond`,`ManaPerSecondPerLevel`,`RangeIndex`,`Speed`,`ModalNextSpell`,`CumulativeAura`,`Totem_1`,`Totem_2`,`Reagent_1`,`Reagent_2`,`Reagent_3`,`Reagent_4`,`Reagent_5`,`Reagent_6`,`Reagent_7`,`Reagent_8`,`ReagentCount_1`,`ReagentCount_2`,`ReagentCount_3`,`ReagentCount_4`,`ReagentCount_5`,`ReagentCount_6`,`ReagentCount_7`,`ReagentCount_8`,`EquippedItemClass`,`EquippedItemSubclass`,`EquippedItemInvTypes`,`Effect_1`,`Effect_2`,`Effect_3`,`EffectDieSides_1`,`EffectDieSides_2`,`EffectDieSides_3`,`EffectRealPointsPerLevel_1`,`EffectRealPointsPerLevel_2`,`EffectRealPointsPerLevel_3`,`EffectBasePoints_1`,`EffectBasePoints_2`,`EffectBasePoints_3`,`EffectMechanic_1`,`EffectMechanic_2`,`EffectMechanic_3`,`ImplicitTargetA_1`,`ImplicitTargetA_2`,`ImplicitTargetA_3`,`ImplicitTargetB_1`,`ImplicitTargetB_2`,`ImplicitTargetB_3`,`EffectRadiusIndex_1`,`EffectRadiusIndex_2`,`EffectRadiusIndex_3`,`EffectAura_1`,`EffectAura_2`,`EffectAura_3`,`EffectAuraPeriod_1`,`EffectAuraPeriod_2`,`EffectAuraPeriod_3`,`EffectMultipleValue_1`,`EffectMultipleValue_2`,`EffectMultipleValue_3`,`EffectChainTargets_1`,`EffectChainTargets_2`,`EffectChainTargets_3`,`EffectItemType_1`,`EffectItemType_2`,`EffectItemType_3`,`EffectMiscValue_1`,`EffectMiscValue_2`,`EffectMiscValue_3`,`EffectMiscValueB_1`,`EffectMiscValueB_2`,`EffectMiscValueB_3`,`EffectTriggerSpell_1`,`EffectTriggerSpell_2`,`EffectTriggerSpell_3`,`EffectPointsPerCombo_1`,`EffectPointsPerCombo_2`,`EffectPointsPerCombo_3`,`EffectSpellClassMaskA_1`,`EffectSpellClassMaskA_2`,`EffectSpellClassMaskA_3`,`EffectSpellClassMaskB_1`,`EffectSpellClassMaskB_2`,`EffectSpellClassMaskB_3`,`EffectSpellClassMaskC_1`,`EffectSpellClassMaskC_2`,`EffectSpellClassMaskC_3`,`SpellVisualID_1`,`SpellVisualID_2`,`SpellIconID`,`ActiveIconID`,`SpellPriority`,`Name_Lang_enUS`,`Name_Lang_enGB`,`Name_Lang_koKR`,`Name_Lang_frFR`,`Name_Lang_deDE`,`Name_Lang_enCN`,`Name_Lang_zhCN`,`Name_Lang_enTW`,`Name_Lang_zhTW`,`Name_Lang_esES`,`Name_Lang_esMX`,`Name_Lang_ruRU`,`Name_Lang_ptPT`,`Name_Lang_ptBR`,`Name_Lang_itIT`,`Name_Lang_Unk`,`Name_Lang_Mask`,`NameSubtext_Lang_enUS`,`NameSubtext_Lang_enGB`,`NameSubtext_Lang_koKR`,`NameSubtext_Lang_frFR`,`NameSubtext_Lang_deDE`,`NameSubtext_Lang_enCN`,`NameSubtext_Lang_zhCN`,`NameSubtext_Lang_enTW`,`NameSubtext_Lang_zhTW`,`NameSubtext_Lang_esES`,`NameSubtext_Lang_esMX`,`NameSubtext_Lang_ruRU`,`NameSubtext_Lang_ptPT`,`NameSubtext_Lang_ptBR`,`NameSubtext_Lang_itIT`,`NameSubtext_Lang_Unk`,`NameSubtext_Lang_Mask`,`Description_Lang_enUS`,`Description_Lang_enGB`,`Description_Lang_koKR`,`Description_Lang_frFR`,`Description_Lang_deDE`,`Description_Lang_enCN`,`Description_Lang_zhCN`,`Description_Lang_enTW`,`Description_Lang_zhTW`,`Description_Lang_esES`,`Description_Lang_esMX`,`Description_Lang_ruRU`,`Description_Lang_ptPT`,`Description_Lang_ptBR`,`Description_Lang_itIT`,`Description_Lang_Unk`,`Description_Lang_Mask`,`AuraDescription_Lang_enUS`,`AuraDescription_Lang_enGB`,`AuraDescription_Lang_koKR`,`AuraDescription_Lang_frFR`,`AuraDescription_Lang_deDE`,`AuraDescription_Lang_enCN`,`AuraDescription_Lang_zhCN`,`AuraDescription_Lang_enTW`,`AuraDescription_Lang_zhTW`,`AuraDescription_Lang_esES`,`AuraDescription_Lang_esMX`,`AuraDescription_Lang_ruRU`,`AuraDescription_Lang_ptPT`,`AuraDescription_Lang_ptBR`,`AuraDescription_Lang_itIT`,`AuraDescription_Lang_Unk`,`AuraDescription_Lang_Mask`,`ManaCostPct`,`StartRecoveryCategory`,`StartRecoveryTime`,`MaxTargetLevel`,`SpellClassSet`,`SpellClassMask_1`,`SpellClassMask_2`,`SpellClassMask_3`,`MaxTargets`,`DefenseType`,`PreventionType`,`StanceBarOrder`,`EffectChainAmplitude_1`,`EffectChainAmplitude_2`,`EffectChainAmplitude_3`,`MinFactionID`,`MinReputation`,`RequiredAuraVision`,`RequiredTotemCategoryID_1`,`RequiredTotemCategoryID_2`,`RequiredAreasID`,`SchoolMask`,`RuneCostID`,`SpellMissileID`,`PowerDisplayID`,`EffectBonusMultiplier_1`,`EffectBonusMultiplier_2`,`EffectBonusMultiplier_3`,`SpellDescriptionVariableID`,`SpellDifficultyID`)
|
||||
VALUES (30758,0,0,0,696254720,132128,268976133,269680640,8388736,393224,4100,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,0,0,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,52,0,0,0,0,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,'aedm','','','','','','','','','','','','','','','',16712190,'','','','','','','','','','','','','','','','',16712172,'','','','','','','','','','','','','','','','',16712188,'','','','','','','','','','','','','','','','',16712188,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0);
|
||||
@@ -80,13 +80,16 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot)
|
||||
switch (bot->getClass())
|
||||
{
|
||||
case CLASS_MAGE:
|
||||
tab = 1;
|
||||
tab = MAGE_TAB_FROST;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
tab = 2;
|
||||
tab = PALADIN_TAB_RETRIBUTION;
|
||||
break;
|
||||
case CLASS_PRIEST:
|
||||
tab = 1;
|
||||
tab = PRIEST_TAB_HOLY;
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
tab = WARLOCK_TAB_DEMONOLOGY;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -302,22 +305,22 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (tab == 0)
|
||||
engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr);
|
||||
engine->addStrategiesNoInit("arcane", nullptr);
|
||||
else if (tab == 1)
|
||||
{
|
||||
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
|
||||
{
|
||||
engine->addStrategiesNoInit("frostfire", "frostfire aoe", nullptr);
|
||||
engine->addStrategiesNoInit("frostfire", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
|
||||
engine->addStrategiesNoInit("fire", nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
engine->addStrategiesNoInit("frost", "frost aoe", nullptr);
|
||||
engine->addStrategiesNoInit("frost", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", nullptr);
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
@@ -367,12 +370,14 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
engine->addStrategiesNoInit("dps", "aoe", "bdps", "dps assist", nullptr);
|
||||
engine->addStrategy("dps debuff", false);
|
||||
// if (tab == HUNTER_TAB_SURVIVAL)
|
||||
// {
|
||||
// engine->addStrategy("trap weave", false);
|
||||
// }
|
||||
if (tab == 0) // Beast Mastery
|
||||
engine->addStrategiesNoInit("bm", nullptr);
|
||||
else if (tab == 1) // Marksmanship
|
||||
engine->addStrategiesNoInit("mm", nullptr);
|
||||
else if (tab == 2) // Survival
|
||||
engine->addStrategiesNoInit("surv", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_ROGUE:
|
||||
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
|
||||
@@ -385,8 +390,16 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
engine->addStrategiesNoInit("dps assist", "dps", "dps debuff", "aoe", nullptr);
|
||||
if (tab == 0) // Affliction
|
||||
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
|
||||
else if (tab == 1) // Demonology
|
||||
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
|
||||
else if (tab == 2) // Destruction
|
||||
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
engine->addStrategiesNoInit("blood", "tank assist", nullptr);
|
||||
@@ -407,7 +420,8 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
{
|
||||
if (sPlayerbotAIConfig->autoSaveMana)
|
||||
engine->addStrategy("save mana", false);
|
||||
engine->addStrategy("healer dps", false);
|
||||
if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId()))
|
||||
engine->addStrategy("healer dps", false);
|
||||
}
|
||||
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
@@ -458,6 +472,10 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies);
|
||||
@@ -588,17 +606,17 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == WARLOCK_TAB_AFFLICATION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bmana", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bdps", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DESTRUCTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bhealth", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
|
||||
}
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
|
||||
@@ -46,6 +46,14 @@ public:
|
||||
if (melee && botAI->IsRanged(bot))
|
||||
return "";
|
||||
|
||||
bool rangeddps = message.find("@rangeddps") == 0;
|
||||
if (rangeddps && (!botAI->IsRanged(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
|
||||
return "";
|
||||
|
||||
bool meleedps = message.find("@meleedps") == 0;
|
||||
if (meleedps && (!botAI->IsMelee(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
|
||||
return "";
|
||||
|
||||
if (tank || dps || heal || ranged || melee)
|
||||
return ChatFilter::Filter(message);
|
||||
|
||||
@@ -246,21 +254,41 @@ public:
|
||||
|
||||
if (message.find("@group") == 0)
|
||||
{
|
||||
std::string const pnum = message.substr(6, message.find(" "));
|
||||
uint32 from = atoi(pnum.c_str());
|
||||
uint32 to = from;
|
||||
if (pnum.find("-") != std::string::npos)
|
||||
size_t spacePos = message.find(" ");
|
||||
if (spacePos == std::string::npos)
|
||||
return message;
|
||||
|
||||
std::string pnum = message.substr(6, spacePos - 6);
|
||||
std::string actualMessage = message.substr(spacePos + 1);
|
||||
|
||||
std::set<uint32> targets;
|
||||
std::istringstream ss(pnum);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(ss, token, ','))
|
||||
{
|
||||
from = atoi(pnum.substr(pnum.find("@") + 1, pnum.find("-")).c_str());
|
||||
to = atoi(pnum.substr(pnum.find("-") + 1, pnum.find(" ")).c_str());
|
||||
size_t dashPos = token.find("-");
|
||||
if (dashPos != std::string::npos)
|
||||
{
|
||||
uint32 from = atoi(token.substr(0, dashPos).c_str());
|
||||
uint32 to = atoi(token.substr(dashPos + 1).c_str());
|
||||
if (from > to) std::swap(from, to);
|
||||
for (uint32 i = from; i <= to; ++i)
|
||||
targets.insert(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 index = atoi(token.c_str());
|
||||
targets.insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bot->GetGroup())
|
||||
return message;
|
||||
|
||||
uint32 sg = bot->GetSubGroup() + 1;
|
||||
if (sg >= from && sg <= to)
|
||||
return ChatFilter::Filter(message);
|
||||
if (targets.find(sg) != targets.end())
|
||||
return ChatFilter::Filter(actualMessage);
|
||||
}
|
||||
|
||||
return message;
|
||||
|
||||
@@ -525,6 +525,11 @@ uint32 GuildTaskMgr::GetMaxItemTaskCount(uint32 itemId)
|
||||
|
||||
bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->guildTaskEnabled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 value = 0;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
@@ -548,6 +553,11 @@ bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId)
|
||||
std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string const type,
|
||||
[[maybe_unused]] uint32* validIn /* = nullptr */)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->guildTaskEnabled)
|
||||
{
|
||||
return std::map<uint32, uint32>();
|
||||
}
|
||||
|
||||
std::map<uint32, uint32> results;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
@@ -576,6 +586,11 @@ std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string c
|
||||
|
||||
uint32 GuildTaskMgr::GetTaskValue(uint32 owner, uint32 guildId, std::string const type, [[maybe_unused]] uint32* validIn /* = nullptr */)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->guildTaskEnabled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 value = 0;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "LootObjectStack.h"
|
||||
|
||||
#include "LootMgr.h"
|
||||
#include "Object.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Unit.h"
|
||||
|
||||
@@ -287,7 +289,7 @@ bool LootObject::IsLootPossible(Player* bot)
|
||||
if (reqItem && !bot->HasItemCount(reqItem, 1))
|
||||
return false;
|
||||
|
||||
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE -2.0f)
|
||||
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE - 2.0f)
|
||||
return false;
|
||||
|
||||
Creature* creature = botAI->GetCreature(guid);
|
||||
@@ -297,9 +299,10 @@ bool LootObject::IsLootPossible(Player* bot)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event)
|
||||
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event) or on
|
||||
// respawn time
|
||||
GameObject* go = botAI->GetGameObject(guid);
|
||||
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE))
|
||||
if (go && (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE) || !go->isSpawned()))
|
||||
return false;
|
||||
|
||||
if (skillId == SKILL_NONE)
|
||||
@@ -317,29 +320,17 @@ bool LootObject::IsLootPossible(Player* bot)
|
||||
uint32 skillValue = uint32(bot->GetSkillValue(skillId));
|
||||
if (reqSkillValue > skillValue)
|
||||
return false;
|
||||
|
||||
if (skillId == SKILL_MINING &&
|
||||
!bot->HasItemCount(756, 1) &&
|
||||
!bot->HasItemCount(778, 1) &&
|
||||
!bot->HasItemCount(1819, 1) &&
|
||||
!bot->HasItemCount(1893, 1) &&
|
||||
!bot->HasItemCount(1959, 1) &&
|
||||
!bot->HasItemCount(2901, 1) &&
|
||||
!bot->HasItemCount(9465, 1) &&
|
||||
!bot->HasItemCount(20723, 1) &&
|
||||
!bot->HasItemCount(40772, 1) &&
|
||||
!bot->HasItemCount(40892, 1) &&
|
||||
!bot->HasItemCount(40893, 1))
|
||||
|
||||
if (skillId == SKILL_MINING && !bot->HasItemCount(756, 1) && !bot->HasItemCount(778, 1) &&
|
||||
!bot->HasItemCount(1819, 1) && !bot->HasItemCount(1893, 1) && !bot->HasItemCount(1959, 1) &&
|
||||
!bot->HasItemCount(2901, 1) && !bot->HasItemCount(9465, 1) && !bot->HasItemCount(20723, 1) &&
|
||||
!bot->HasItemCount(40772, 1) && !bot->HasItemCount(40892, 1) && !bot->HasItemCount(40893, 1))
|
||||
{
|
||||
return false; // Bot is missing a mining pick
|
||||
}
|
||||
|
||||
if (skillId == SKILL_SKINNING &&
|
||||
!bot->HasItemCount(7005, 1) &&
|
||||
!bot->HasItemCount(40772, 1) &&
|
||||
!bot->HasItemCount(40893, 1) &&
|
||||
!bot->HasItemCount(12709, 1) &&
|
||||
!bot->HasItemCount(19901, 1))
|
||||
|
||||
if (skillId == SKILL_SKINNING && !bot->HasItemCount(7005, 1) && !bot->HasItemCount(40772, 1) &&
|
||||
!bot->HasItemCount(40893, 1) && !bot->HasItemCount(12709, 1) && !bot->HasItemCount(19901, 1))
|
||||
{
|
||||
return false; // Bot is missing a skinning knife
|
||||
}
|
||||
@@ -376,42 +367,45 @@ void LootObjectStack::Clear() { availableLoot.clear(); }
|
||||
|
||||
bool LootObjectStack::CanLoot(float maxDistance)
|
||||
{
|
||||
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
|
||||
return !ordered.empty();
|
||||
LootObject nearest = GetNearest(maxDistance);
|
||||
return !nearest.IsEmpty();
|
||||
}
|
||||
|
||||
LootObject LootObjectStack::GetLoot(float maxDistance)
|
||||
{
|
||||
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
|
||||
return ordered.empty() ? LootObject() : *ordered.begin();
|
||||
LootObject nearest = GetNearest(maxDistance);
|
||||
return nearest.IsEmpty() ? LootObject() : nearest;
|
||||
}
|
||||
std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance)
|
||||
|
||||
LootObject LootObjectStack::GetNearest(float maxDistance)
|
||||
{
|
||||
availableLoot.shrink(time(nullptr) - 30);
|
||||
|
||||
std::map<float, LootObject> sortedMap;
|
||||
LootObject nearest;
|
||||
float nearestDistance = std::numeric_limits<float>::max();
|
||||
|
||||
LootTargetList safeCopy(availableLoot);
|
||||
for (LootTargetList::iterator i = safeCopy.begin(); i != safeCopy.end(); i++)
|
||||
{
|
||||
ObjectGuid guid = i->guid;
|
||||
LootObject lootObject(bot, guid);
|
||||
if (!lootObject.IsLootPossible(bot)) // Ensure loot object is valid
|
||||
continue;
|
||||
|
||||
WorldObject* worldObj = lootObject.GetWorldObject(bot);
|
||||
if (!worldObj) // Prevent null pointer dereference
|
||||
{
|
||||
WorldObject* worldObj = ObjectAccessor::GetWorldObject(*bot, guid);
|
||||
if (!worldObj)
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = bot->GetDistance(worldObj);
|
||||
if (!maxDistance || distance <= maxDistance)
|
||||
sortedMap[distance] = lootObject;
|
||||
|
||||
if (distance >= nearestDistance || (maxDistance && distance > maxDistance))
|
||||
continue;
|
||||
|
||||
LootObject lootObject(bot, guid);
|
||||
|
||||
if (!lootObject.IsLootPossible(bot))
|
||||
continue;
|
||||
|
||||
nearestDistance = distance;
|
||||
nearest = lootObject;
|
||||
}
|
||||
|
||||
std::vector<LootObject> result;
|
||||
for (auto& [_, lootObject] : sortedMap)
|
||||
result.push_back(lootObject);
|
||||
|
||||
return result;
|
||||
return nearest;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
LootObject GetLoot(float maxDistance = 0);
|
||||
|
||||
private:
|
||||
std::vector<LootObject> OrderByDistance(float maxDistance = 0);
|
||||
LootObject GetNearest(float maxDistance = 0);
|
||||
|
||||
Player* bot;
|
||||
LootTargetList availableLoot;
|
||||
|
||||
@@ -212,7 +212,8 @@ 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"); // SMSG_QUESTUPDATE_ADD_ITEM no longer used
|
||||
// SMSG_QUESTUPDATE_ADD_ITEM no longer used
|
||||
// botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item");
|
||||
botOutgoingPacketHandlers.AddHandler(SMSG_QUEST_CONFIRM_ACCEPT, "confirm quest");
|
||||
}
|
||||
|
||||
@@ -721,6 +722,8 @@ void PlayerbotAI::HandleTeleportAck()
|
||||
// SetNextCheckDelay(urand(2000, 5000));
|
||||
if (sPlayerbotAIConfig->applyInstanceStrategies)
|
||||
ApplyInstanceStrategies(bot->GetMapId(), true);
|
||||
if (sPlayerbotAIConfig->restrictHealerDPS)
|
||||
EvaluateHealerDpsStrategy();
|
||||
Reset(true);
|
||||
}
|
||||
|
||||
@@ -1048,6 +1051,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (chanName == "World")
|
||||
return;
|
||||
|
||||
// do not reply to self but always try to reply to real player
|
||||
if (guid1 != bot->GetGUID())
|
||||
@@ -1291,11 +1297,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
return;
|
||||
}
|
||||
|
||||
if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||
{
|
||||
SetNextCheckDelay(sPlayerbotAIConfig->passiveDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
// Change engine if just died
|
||||
bool isBotAlive = bot->IsAlive();
|
||||
@@ -1425,8 +1426,8 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
master = newMaster;
|
||||
botAI->SetMaster(newMaster);
|
||||
botAI->ResetStrategies();
|
||||
|
||||
if (!bot->InBattleground())
|
||||
|
||||
if (!bot->InBattleground())
|
||||
{
|
||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||
|
||||
@@ -1437,7 +1438,7 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're in a battleground, stay with the pack and focus on objective
|
||||
// we're in a battleground, stay with the pack and focus on objective
|
||||
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
|
||||
}
|
||||
}
|
||||
@@ -1765,7 +1766,7 @@ bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(pla
|
||||
|
||||
bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return false;
|
||||
@@ -1777,6 +1778,11 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsHeal(member)) // Check if the member is a healer
|
||||
{
|
||||
bool isAssistant = group->IsAssistant(member->GetGUID());
|
||||
@@ -1796,7 +1802,7 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index)
|
||||
|
||||
bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return false;
|
||||
@@ -1808,6 +1814,11 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsRangedDps(member)) // Check if the member is a ranged DPS
|
||||
{
|
||||
bool isAssistant = group->IsAssistant(member->GetGUID());
|
||||
@@ -1840,6 +1851,35 @@ bool PlayerbotAI::HasAggro(Unit* unit)
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 PlayerbotAI::GetAssistTankIndex(Player* player)
|
||||
{
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int counter = 0;
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
}
|
||||
if (IsTank(member, true) && group->IsAssistant(member->GetGUID()))
|
||||
{
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 PlayerbotAI::GetGroupSlotIndex(Player* player)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
@@ -1851,6 +1891,12 @@ int32 PlayerbotAI::GetGroupSlotIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1875,6 +1921,12 @@ int32 PlayerbotAI::GetRangedIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1902,6 +1954,12 @@ int32 PlayerbotAI::GetClassIndex(Player* player, uint8 cls)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1928,6 +1986,12 @@ int32 PlayerbotAI::GetRangedDpsIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -1955,6 +2019,12 @@ int32 PlayerbotAI::GetMeleeIndex(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player == member)
|
||||
{
|
||||
return counter;
|
||||
@@ -2126,6 +2196,12 @@ bool PlayerbotAI::IsMainTank(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTank(member) && member->IsAlive())
|
||||
{
|
||||
return player->GetGUID() == member->GetGUID();
|
||||
@@ -2134,6 +2210,62 @@ bool PlayerbotAI::IsMainTank(Player* player)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::IsBotMainTank(Player* player)
|
||||
{
|
||||
if (!player->GetSession()->IsBot() || !IsTank(player))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsMainTank(player))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return true; // If no group, consider the bot as main tank
|
||||
}
|
||||
|
||||
uint32 botAssistTankIndex = GetAssistTankIndex(player);
|
||||
|
||||
if (botAssistTankIndex == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* member = gref->GetSource();
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 memberAssistTankIndex = GetAssistTankIndex(member);
|
||||
|
||||
if (memberAssistTankIndex == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (memberAssistTankIndex == botAssistTankIndex && player == member)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (memberAssistTankIndex < botAssistTankIndex && member->GetSession()->IsBot())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 PlayerbotAI::GetGroupTankNum(Player* player)
|
||||
{
|
||||
Group* group = player->GetGroup();
|
||||
@@ -2145,6 +2277,12 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTank(member) && member->IsAlive())
|
||||
{
|
||||
result++;
|
||||
@@ -2157,7 +2295,7 @@ bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMai
|
||||
|
||||
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
{
|
||||
Group* group = bot->GetGroup();
|
||||
Group* group = player->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return false;
|
||||
@@ -2166,6 +2304,12 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2179,6 +2323,12 @@ bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index)
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member))
|
||||
{
|
||||
if (index == counter)
|
||||
@@ -2358,7 +2508,6 @@ std::string PlayerbotAI::GetLocalizedCreatureName(uint32 entry)
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
|
||||
{
|
||||
std::string name;
|
||||
@@ -2387,6 +2536,11 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
|
||||
if (!member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GET_PLAYERBOT_AI(member) && !GET_PLAYERBOT_AI(member)->IsRealPlayer())
|
||||
continue;
|
||||
|
||||
@@ -2937,6 +3091,18 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((bot->GetShapeshiftForm() == FORM_FLIGHT || bot->GetShapeshiftForm() == FORM_FLIGHT_EPIC) && !bot->IsInCombat())
|
||||
{
|
||||
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
|
||||
{
|
||||
LOG_DEBUG(
|
||||
"playerbots",
|
||||
"Can cast spell failed. In flight form (not in combat). - target name: {}, spellid: {}, bot name: {}",
|
||||
target->GetName(), spellid, bot->GetName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(bot) : spellInfo->GetDuration();
|
||||
// bool interruptOnMove = spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT;
|
||||
if ((CastingTime || spellInfo->IsAutoRepeatRangedSpell()) && bot->isMoving())
|
||||
@@ -2951,14 +3117,19 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
|
||||
|
||||
if (!itemTarget)
|
||||
{
|
||||
// Exception for Deep Freeze (44572) - allow cast for damage on immune targets (e.g., bosses)
|
||||
if (target->IsImmunedToSpell(spellInfo))
|
||||
{
|
||||
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
|
||||
if (spellid != 44572) // Deep Freeze
|
||||
{
|
||||
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
|
||||
target->GetName(), spellid, bot->GetName());
|
||||
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
|
||||
target->GetName(), spellid, bot->GetName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
// Otherwise, allow Deep Freeze even if immune
|
||||
}
|
||||
|
||||
if (bot != target && sServerFacade->GetDistance2d(bot, target) > sPlayerbotAIConfig->sightDistance)
|
||||
@@ -3154,22 +3325,41 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (pet && pet->HasSpell(spellId))
|
||||
{
|
||||
bool autocast = false;
|
||||
for (unsigned int& m_autospell : pet->m_autospells)
|
||||
// List of spell IDs for which we do NOT want to toggle auto-cast or send message
|
||||
// We are excluding Spell Lock and Devour Magic because they are casted in the GenericWarlockStrategy
|
||||
// Without this exclusion, the skill would be togged for auto-cast and the player would
|
||||
// be spammed with messages about enabling/disabling auto-cast
|
||||
switch (spellId)
|
||||
{
|
||||
if (m_autospell == spellId)
|
||||
{
|
||||
autocast = true;
|
||||
case 19244: // Spell Lock rank 1
|
||||
case 19647: // Spell Lock rank 2
|
||||
case 19505: // Devour Magic rank 1
|
||||
case 19731: // Devour Magic rank 2
|
||||
case 19734: // Devour Magic rank 3
|
||||
case 19736: // Devour Magic rank 4
|
||||
case 27276: // Devour Magic rank 5
|
||||
case 27277: // Devour Magic rank 6
|
||||
case 48011: // Devour Magic rank 7
|
||||
// No message - just break out of the switch and let normal cast logic continue
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
bool autocast = false;
|
||||
for (unsigned int& m_autospell : pet->m_autospells)
|
||||
{
|
||||
if (m_autospell == spellId)
|
||||
{
|
||||
autocast = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pet->ToggleAutocast(spellInfo, !autocast);
|
||||
std::ostringstream out;
|
||||
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
|
||||
out << chatHelper.FormatSpell(spellInfo);
|
||||
TellMaster(out);
|
||||
return true;
|
||||
pet->ToggleAutocast(spellInfo, !autocast);
|
||||
std::ostringstream out;
|
||||
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
|
||||
out << chatHelper.FormatSpell(spellInfo);
|
||||
TellMaster(out);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
|
||||
@@ -3311,13 +3501,14 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
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 << "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()
|
||||
out << "Target: Unit (" << targets.GetUnitTarget()->GetName()
|
||||
<< ", Low GUID: " << targets.GetUnitTarget()->GetGUID().GetCounter()
|
||||
<< ", High GUID: " << static_cast<uint32>(targets.GetUnitTarget()->GetGUID().GetHigh()) << "), ";
|
||||
}
|
||||
@@ -3333,7 +3524,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
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())
|
||||
{
|
||||
@@ -3341,7 +3532,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
Item* tradeItem = bot->GetTradeData()->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED);
|
||||
if (tradeItem)
|
||||
{
|
||||
out << "Trade Item: " << tradeItem->GetEntry()
|
||||
out << "Trade Item: " << tradeItem->GetEntry()
|
||||
<< " (Low GUID: " << tradeItem->GetGUID().GetCounter()
|
||||
<< ", High GUID: " << static_cast<uint32>(tradeItem->GetGUID().GetHigh()) << "), ";
|
||||
}
|
||||
@@ -3354,7 +3545,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
{
|
||||
out << "Trade Mode: Inactive, ";
|
||||
}
|
||||
|
||||
|
||||
TellMasterNoFacing(out);
|
||||
}
|
||||
|
||||
@@ -4293,7 +4484,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
if (!player || !player->IsInWorld())
|
||||
continue;
|
||||
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
if (!connectedPlayer)
|
||||
continue;
|
||||
|
||||
@@ -4365,26 +4556,28 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
|
||||
|
||||
uint32 PlayerbotAI::AutoScaleActivity(uint32 mod)
|
||||
{
|
||||
// Current max server update time (ms), and the configured floor/ceiling values for bot scaling
|
||||
uint32 maxDiff = sWorldUpdateTime.GetMaxUpdateTimeOfCurrentTable();
|
||||
uint32 diffLimitFloor = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitfloor;
|
||||
uint32 diffLimitCeiling = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitCeiling;
|
||||
double spreadSize = (double)(diffLimitCeiling - diffLimitFloor) / 6;
|
||||
|
||||
// apply scaling
|
||||
if (diffLimitCeiling <= diffLimitFloor)
|
||||
{
|
||||
// Perfrom binary decision if ceiling <= floor: Either all bots are active or none are
|
||||
return (maxDiff > diffLimitCeiling) ? 0 : mod;
|
||||
}
|
||||
|
||||
if (maxDiff > diffLimitCeiling)
|
||||
return 0;
|
||||
if (maxDiff > diffLimitFloor + (4 * spreadSize))
|
||||
return (mod * 1) / 10;
|
||||
if (maxDiff > diffLimitFloor + (3 * spreadSize))
|
||||
return (mod * 3) / 10;
|
||||
if (maxDiff > diffLimitFloor + (2 * spreadSize))
|
||||
return (mod * 5) / 10;
|
||||
if (maxDiff > diffLimitFloor + (1 * spreadSize))
|
||||
return (mod * 7) / 10;
|
||||
if (maxDiff > diffLimitFloor)
|
||||
return (mod * 9) / 10;
|
||||
|
||||
return mod;
|
||||
if (maxDiff <= diffLimitFloor)
|
||||
return mod;
|
||||
|
||||
// Calculate lag progress from floor to ceiling (0 to 1)
|
||||
double lagProgress = (maxDiff - diffLimitFloor) / (double)(diffLimitCeiling - diffLimitFloor);
|
||||
|
||||
// Apply the percentage of active bots (the complement of lag progress) to the mod value
|
||||
return static_cast<uint32>(mod * (1 - lagProgress));
|
||||
}
|
||||
|
||||
bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); }
|
||||
@@ -4409,12 +4602,51 @@ void PlayerbotAI::RemoveShapeshift()
|
||||
// RemoveAura("tree of life");
|
||||
}
|
||||
|
||||
// NOTE : function rewritten as flags "withBags" and "withBank" not used, and _fillGearScoreData sometimes attribute
|
||||
// one-hand/2H Weapon in wrong slots
|
||||
// Mirrors Blizzard’s GetAverageItemLevel rules :
|
||||
// https://wowpedia.fandom.com/wiki/API_GetAverageItemLevel
|
||||
uint32 PlayerbotAI::GetEquipGearScore(Player* player)
|
||||
{
|
||||
constexpr uint8 TOTAL_SLOTS = 17; // every slot except Body & Tabard
|
||||
uint32 sumLevel = 0;
|
||||
|
||||
/* ---------- 0. Detect “ignore off-hand” situations --------- */
|
||||
Item* main = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
Item* off = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
||||
|
||||
bool ignoreOffhand = false; // true → divisor = 16
|
||||
if (main)
|
||||
{
|
||||
bool twoHand = (main->GetTemplate()->InventoryType == INVTYPE_2HWEAPON);
|
||||
if (twoHand && !player->HasAura(SPELL_TITAN_GRIP))
|
||||
ignoreOffhand = true; // classic 2-hander
|
||||
}
|
||||
else if (!off) // both hands empty
|
||||
ignoreOffhand = true;
|
||||
|
||||
/* ---------- 1. Sum up item-levels -------------------------- */
|
||||
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||
{
|
||||
if (slot == EQUIPMENT_SLOT_BODY || slot == EQUIPMENT_SLOT_TABARD)
|
||||
continue; // Blizzard never counts these
|
||||
|
||||
if (ignoreOffhand && slot == EQUIPMENT_SLOT_OFFHAND)
|
||||
continue; // skip off-hand in 2-H case
|
||||
|
||||
if (Item* it = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
sumLevel += it->GetTemplate()->ItemLevel; // missing items add 0
|
||||
}
|
||||
|
||||
/* ---------- 2. Divide by 17 or 16 -------------------------- */
|
||||
const uint8 divisor = ignoreOffhand ? TOTAL_SLOTS - 1 : TOTAL_SLOTS; // 16 or 17
|
||||
return sumLevel / divisor;
|
||||
}
|
||||
|
||||
// NOTE : function rewritten as flags "withBags" and "withBank" not used, and _fillGearScoreData sometimes attribute
|
||||
// one-hand/2H Weapon in wrong slots
|
||||
/*uint32 PlayerbotAI::GetEquipGearScore(Player* player)
|
||||
{
|
||||
// This function aims to calculate the equipped gear score
|
||||
|
||||
|
||||
uint32 sum = 0;
|
||||
uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots
|
||||
uint8 mh_type = 0;
|
||||
@@ -4423,21 +4655,21 @@ uint32 PlayerbotAI::GetEquipGearScore(Player* player)
|
||||
{
|
||||
Item* item =player->GetItemByPos(INVENTORY_SLOT_BAG_0, i);
|
||||
if (item && i != EQUIPMENT_SLOT_BODY && i != EQUIPMENT_SLOT_TABARD)
|
||||
{
|
||||
{
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
sum += proto->ItemLevel;
|
||||
|
||||
|
||||
// If character is not warfury and have 2 hand weapon equipped, main hand will be counted twice
|
||||
if (i == SLOT_MAIN_HAND)
|
||||
mh_type = item->GetTemplate()->InventoryType;
|
||||
if (!player->HasAura(SPELL_TITAN_GRIP) && mh_type == INVTYPE_2HWEAPON && i == SLOT_MAIN_HAND)
|
||||
sum += item->GetTemplate()->ItemLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32 gs = uint32(sum / count);
|
||||
return gs;
|
||||
}
|
||||
}*/
|
||||
|
||||
/*uint32 PlayerbotAI::GetEquipGearScore(Player* player, bool withBags, bool withBank)
|
||||
{
|
||||
@@ -4670,7 +4902,7 @@ void PlayerbotAI::_fillGearScoreData(Player* player, Item* item, std::vector<uin
|
||||
case INVTYPE_SHOULDERS:
|
||||
(*gearScore)[EQUIPMENT_SLOT_SHOULDERS] = std::max((*gearScore)[EQUIPMENT_SLOT_SHOULDERS], level);
|
||||
break;
|
||||
case INVTYPE_BODY: //Shouldn't be considered when calculating average ilevel
|
||||
case INVTYPE_BODY: // Shouldn't be considered when calculating average ilevel
|
||||
(*gearScore)[EQUIPMENT_SLOT_BODY] = std::max((*gearScore)[EQUIPMENT_SLOT_BODY], level);
|
||||
break;
|
||||
case INVTYPE_CHEST:
|
||||
@@ -5028,13 +5260,13 @@ Item* PlayerbotAI::FindAmmo() const
|
||||
}
|
||||
|
||||
// Find Consumable
|
||||
Item* PlayerbotAI::FindConsumable(uint32 displayId) const
|
||||
Item* PlayerbotAI::FindConsumable(uint32 itemId) const
|
||||
{
|
||||
return FindItemInInventory(
|
||||
[displayId](ItemTemplate const* pItemProto) -> bool
|
||||
[itemId](ItemTemplate const* pItemProto) -> bool
|
||||
{
|
||||
return (pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) &&
|
||||
pItemProto->DisplayInfoID == displayId;
|
||||
pItemProto->ItemId == itemId;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5048,80 +5280,80 @@ Item* PlayerbotAI::FindBandage() const
|
||||
|
||||
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());
|
||||
});
|
||||
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)
|
||||
return FindItemInInventory(
|
||||
[this](ItemTemplate const* itemTemplate) -> bool
|
||||
{
|
||||
if (lockInfo->Type[j] == LOCK_KEY_SKILL)
|
||||
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)
|
||||
{
|
||||
uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j]));
|
||||
if (skillId == SKILL_LOCKPICKING)
|
||||
if (lockInfo->Type[j] == LOCK_KEY_SKILL)
|
||||
{
|
||||
uint32 requiredSkill = lockInfo->Skill[j];
|
||||
uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING);
|
||||
return botSkill >= requiredSkill;
|
||||
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;
|
||||
});
|
||||
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,
|
||||
COARSE_SHARPENING_DISPLAYID, ROUGH_SHARPENING_DISPLAYID};
|
||||
|
||||
static const uint32 uPriorizedWeightStoneIds[7] = {ADAMANTITE_WEIGHTSTONE_DISPLAYID, FEL_WEIGHTSTONE_DISPLAYID,
|
||||
DENSE_WEIGHTSTONE_DISPLAYID, SOLID_WEIGHTSTONE_DISPLAYID,
|
||||
HEAVY_WEIGHTSTONE_DISPLAYID, COARSE_WEIGHTSTONE_DISPLAYID,
|
||||
ROUGH_WEIGHTSTONE_DISPLAYID};
|
||||
|
||||
/**
|
||||
* FindStoneFor()
|
||||
* return Item* Returns sharpening/weight stone item eligible to enchant a bot weapon
|
||||
*
|
||||
* params:weapon Item* the weap<61>n the function should search and return a enchanting item for
|
||||
* return nullptr if no relevant item is found in bot inventory, else return a sharpening or weight
|
||||
* stone based on the weapon subclass
|
||||
*
|
||||
*/
|
||||
Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
{
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
const ItemTemplate* item_template = weapon->GetTemplate();
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
|
||||
};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
|
||||
};
|
||||
|
||||
Item* stone = nullptr;
|
||||
ItemTemplate const* pProto = weapon->GetTemplate();
|
||||
if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2 ||
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER))
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || pProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM))
|
||||
{
|
||||
for (uint8 i = 0; i < std::size(uPriorizedSharpStoneIds); ++i)
|
||||
for (uint8 i = 0; i < std::size(uPrioritizedSharpStoneIds); ++i)
|
||||
{
|
||||
stone = FindConsumable(uPriorizedSharpStoneIds[i]);
|
||||
stone = FindConsumable(uPrioritizedSharpStoneIds[i]);
|
||||
if (stone)
|
||||
{
|
||||
return stone;
|
||||
@@ -5129,11 +5361,12 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
}
|
||||
}
|
||||
else if (pProto &&
|
||||
(pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2))
|
||||
(pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || pProto->SubClass == ITEM_SUBCLASS_WEAPON_FIST))
|
||||
{
|
||||
for (uint8 i = 0; i < std::size(uPriorizedWeightStoneIds); ++i)
|
||||
for (uint8 i = 0; i < std::size(uPrioritizedWeightStoneIds); ++i)
|
||||
{
|
||||
stone = FindConsumable(uPriorizedWeightStoneIds[i]);
|
||||
stone = FindConsumable(uPrioritizedWeightStoneIds[i]);
|
||||
if (stone)
|
||||
{
|
||||
return stone;
|
||||
@@ -5146,6 +5379,7 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
|
||||
Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
{
|
||||
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
@@ -5153,35 +5387,60 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
// static const will only get created once whatever the call amout
|
||||
static const std::vector<uint32_t> uPriorizedWizardOilIds = {
|
||||
MINOR_WIZARD_OIL, MINOR_MANA_OIL, LESSER_WIZARD_OIL, LESSER_MANA_OIL, BRILLIANT_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, WIZARD_OIL, SUPERIOR_MANA_OIL, SUPERIOR_WIZARD_OIL};
|
||||
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
|
||||
// static const will only get created once whatever the call amout
|
||||
static const std::vector<uint32_t> uPriorizedManaOilIds = {
|
||||
MINOR_MANA_OIL, MINOR_WIZARD_OIL, LESSER_MANA_OIL, LESSER_WIZARD_OIL, BRILLIANT_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_MANA_OIL, WIZARD_OIL, SUPERIOR_WIZARD_OIL};
|
||||
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
|
||||
Item* oil = nullptr;
|
||||
if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_SWORD ||
|
||||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || item_template->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
|
||||
int botClass = bot->getClass();
|
||||
int specTab = AiFactory::GetPlayerSpecTab(bot);
|
||||
|
||||
const std::vector<uint32_t>* prioritizedOils = nullptr;
|
||||
switch (botClass)
|
||||
{
|
||||
for (const auto& id : uPriorizedWizardOilIds)
|
||||
{
|
||||
oil = FindConsumable(id);
|
||||
if (oil)
|
||||
return oil;
|
||||
}
|
||||
case CLASS_PRIEST:
|
||||
prioritizedOils = (specTab == 2) ? &uPrioritizedWizardOilIds : &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (specTab == 0) // Balance
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 1) // Feral
|
||||
prioritizedOils = nullptr;
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (specTab == 1) // Protection
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 2) // Retribution
|
||||
prioritizedOils = nullptr;
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
default:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
}
|
||||
else if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE ||
|
||||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE2)
|
||||
|
||||
if (prioritizedOils)
|
||||
{
|
||||
for (const auto& id : uPriorizedManaOilIds)
|
||||
for (const auto& id : *prioritizedOils)
|
||||
{
|
||||
oil = FindConsumable(id);
|
||||
if (oil)
|
||||
{
|
||||
return oil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6012,6 +6271,35 @@ ChatChannelSource PlayerbotAI::GetChatChannelSource(Player* bot, uint32 type, st
|
||||
return ChatChannelSource::SRC_UNDEFINED;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::CheckLocationDistanceByLevel(Player* player, const WorldLocation& loc, bool fromStartUp)
|
||||
{
|
||||
if (player->GetLevel() > 16)
|
||||
return true;
|
||||
|
||||
float dis = 0.0f;
|
||||
if (fromStartUp)
|
||||
{
|
||||
PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(player->getRace(true), player->getClass());
|
||||
if (loc.GetMapId() != pInfo->mapId)
|
||||
return false;
|
||||
dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loc.GetMapId() != player->GetMapId())
|
||||
return false;
|
||||
dis = loc.GetExactDist(player);
|
||||
}
|
||||
|
||||
float bound = 10000.0f;
|
||||
if (player->GetLevel() <= 4)
|
||||
bound = 500.0f;
|
||||
else if (player->GetLevel() <= 10)
|
||||
bound = 2500.0f;
|
||||
|
||||
return dis <= bound;
|
||||
}
|
||||
|
||||
std::vector<const Quest*> PlayerbotAI::GetAllCurrentQuests()
|
||||
{
|
||||
std::vector<const Quest*> result;
|
||||
@@ -6287,17 +6575,27 @@ void PlayerbotAI::AddTimedEvent(std::function<void()> callback, uint32 delayMs)
|
||||
class LambdaEvent final : public BasicEvent
|
||||
{
|
||||
std::function<void()> _cb;
|
||||
|
||||
public:
|
||||
explicit LambdaEvent(std::function<void()> cb) : _cb(std::move(cb)) {}
|
||||
bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override
|
||||
{
|
||||
_cb();
|
||||
return true; // remove after execution
|
||||
return true; // remove after execution
|
||||
}
|
||||
};
|
||||
|
||||
// Every Player already owns an EventMap called m_Events
|
||||
bot->m_Events.AddEvent(
|
||||
new LambdaEvent(std::move(callback)),
|
||||
bot->m_Events.CalculateTime(delayMs));
|
||||
}
|
||||
bot->m_Events.AddEvent(new LambdaEvent(std::move(callback)), bot->m_Events.CalculateTime(delayMs));
|
||||
}
|
||||
|
||||
void PlayerbotAI::EvaluateHealerDpsStrategy()
|
||||
{
|
||||
if (!IsHeal(bot, true))
|
||||
return;
|
||||
|
||||
if (sPlayerbotAIConfig->IsRestrictedHealerDPSMap(bot->GetMapId()))
|
||||
ChangeStrategy("-healer dps", BOT_STATE_COMBAT);
|
||||
else
|
||||
ChangeStrategy("+healer dps", BOT_STATE_COMBAT);
|
||||
}
|
||||
|
||||
@@ -46,27 +46,27 @@ struct GameObjectData;
|
||||
|
||||
enum StrategyType : uint32;
|
||||
|
||||
enum HealingItemDisplayId
|
||||
enum HealingItemId
|
||||
{
|
||||
HEALTHSTONE_DISPLAYID = 8026,
|
||||
MAJOR_HEALING_POTION = 24152,
|
||||
WHIPPER_ROOT_TUBER = 21974,
|
||||
NIGHT_DRAGON_BREATH = 21975,
|
||||
LIMITED_INVULNERABILITY_POTION = 24213,
|
||||
GREATER_DREAMLESS_SLEEP_POTION = 17403,
|
||||
SUPERIOR_HEALING_POTION = 15714,
|
||||
CRYSTAL_RESTORE = 2516,
|
||||
DREAMLESS_SLEEP_POTION = 17403,
|
||||
GREATER_HEALING_POTION = 15713,
|
||||
HEALING_POTION = 15712,
|
||||
LESSER_HEALING_POTION = 15711,
|
||||
DISCOLORED_HEALING_POTION = 15736,
|
||||
MINOR_HEALING_POTION = 15710,
|
||||
VOLATILE_HEALING_POTION = 24212,
|
||||
SUPER_HEALING_POTION = 37807,
|
||||
CRYSTAL_HEALING_POTION = 47132,
|
||||
FEL_REGENERATION_POTION = 37864,
|
||||
MAJOR_DREAMLESS_SLEEP_POTION = 37845
|
||||
HEALTHSTONE = 5512,
|
||||
MAJOR_HEALING_POTION = 13446,
|
||||
WHIPPER_ROOT_TUBER = 11951,
|
||||
NIGHT_DRAGON_BREATH = 11952,
|
||||
LIMITED_INVULNERABILITY_POTION = 3387,
|
||||
GREATER_DREAMLESS_SLEEP_POTION = 22886,
|
||||
SUPERIOR_HEALING_POTION = 3928,
|
||||
CRYSTAL_RESTORE = 11564,
|
||||
DREAMLESS_SLEEP_POTION = 12190,
|
||||
GREATER_HEALING_POTION = 1710,
|
||||
HEALING_POTION = 929,
|
||||
LESSER_HEALING_POTION = 858,
|
||||
DISCOLORED_HEALING_POTION = 3391,
|
||||
MINOR_HEALING_POTION = 118,
|
||||
VOLATILE_HEALING_POTION = 28100,
|
||||
SUPER_HEALING_POTION = 22829,
|
||||
CRYSTAL_HEALING_POTION = 13462,
|
||||
FEL_REGENERATION_POTION = 28101,
|
||||
MAJOR_DREAMLESS_SLEEP_POTION = 20002
|
||||
};
|
||||
|
||||
enum BotState
|
||||
@@ -152,6 +152,7 @@ static std::map<ChatChannelSource, std::string> ChatChannelSourceStr = {
|
||||
{SRC_RAID, "SRC_RAID"},
|
||||
|
||||
{SRC_UNDEFINED, "SRC_UNDEFINED"}};
|
||||
|
||||
enum ChatChannelId
|
||||
{
|
||||
GENERAL = 1,
|
||||
@@ -162,60 +163,66 @@ enum ChatChannelId
|
||||
GUILD_RECRUITMENT = 25,
|
||||
};
|
||||
|
||||
enum RoguePoisonDisplayId
|
||||
enum RoguePoisonId
|
||||
{
|
||||
DEADLY_POISON_DISPLAYID = 13707,
|
||||
INSTANT_POISON_DISPLAYID = 13710,
|
||||
WOUND_POISON_DISPLAYID = 37278
|
||||
INSTANT_POISON = 6947,
|
||||
INSTANT_POISON_II = 6949,
|
||||
INSTANT_POISON_III = 6950,
|
||||
INSTANT_POISON_IV = 8926,
|
||||
INSTANT_POISON_V = 8927,
|
||||
INSTANT_POISON_VI = 8928,
|
||||
INSTANT_POISON_VII = 21927,
|
||||
INSTANT_POISON_VIII = 43230,
|
||||
INSTANT_POISON_IX = 43231,
|
||||
DEADLY_POISON = 2892,
|
||||
DEADLY_POISON_II = 2893,
|
||||
DEADLY_POISON_III = 8984,
|
||||
DEADLY_POISON_IV = 8985,
|
||||
DEADLY_POISON_V = 20844,
|
||||
DEADLY_POISON_VI = 22053,
|
||||
DEADLY_POISON_VII = 22054,
|
||||
DEADLY_POISON_VIII = 43232,
|
||||
DEADLY_POISON_IX = 43233
|
||||
};
|
||||
|
||||
enum SharpeningStoneDisplayId
|
||||
enum SharpeningStoneId
|
||||
{
|
||||
ROUGH_SHARPENING_DISPLAYID = 24673,
|
||||
COARSE_SHARPENING_DISPLAYID = 24674,
|
||||
HEAVY_SHARPENING_DISPLAYID = 24675,
|
||||
SOLID_SHARPENING_DISPLAYID = 24676,
|
||||
DENSE_SHARPENING_DISPLAYID = 24677,
|
||||
CONSECRATED_SHARPENING_DISPLAYID =
|
||||
24674, // will not be used because bot can not know if it will face undead targets
|
||||
ELEMENTAL_SHARPENING_DISPLAYID = 21072,
|
||||
FEL_SHARPENING_DISPLAYID = 39192,
|
||||
ADAMANTITE_SHARPENING_DISPLAYID = 39193
|
||||
ROUGH_SHARPENING_STONE = 2862,
|
||||
COARSE_SHARPENING_STONE = 2863,
|
||||
HEAVY_SHARPENING_STONE = 2871,
|
||||
SOLID_SHARPENING_STONE = 7964,
|
||||
DENSE_SHARPENING_STONE = 12404,
|
||||
ELEMENTAL_SHARPENING_STONE = 18262,
|
||||
FEL_SHARPENING_STONE = 23528,
|
||||
ADAMANTITE_SHARPENING_STONE = 23529
|
||||
};
|
||||
|
||||
enum WeightStoneDisplayId
|
||||
enum WeightstoneId
|
||||
{
|
||||
ROUGH_WEIGHTSTONE_DISPLAYID = 24683,
|
||||
COARSE_WEIGHTSTONE_DISPLAYID = 24684,
|
||||
HEAVY_WEIGHTSTONE_DISPLAYID = 24685,
|
||||
SOLID_WEIGHTSTONE_DISPLAYID = 24686,
|
||||
DENSE_WEIGHTSTONE_DISPLAYID = 24687,
|
||||
FEL_WEIGHTSTONE_DISPLAYID = 39548,
|
||||
ADAMANTITE_WEIGHTSTONE_DISPLAYID = 39549
|
||||
ROUGH_WEIGHTSTONE = 3239,
|
||||
COARSE_WEIGHTSTONE = 3240,
|
||||
HEAVY_WEIGHTSTONE = 3241,
|
||||
SOLID_WEIGHTSTONE = 7965,
|
||||
DENSE_WEIGHTSTONE = 12643,
|
||||
FEL_WEIGHTSTONE = 28420,
|
||||
ADAMANTITE_WEIGHTSTONE = 28421
|
||||
};
|
||||
|
||||
enum WizardOilDisplayId
|
||||
enum WizardOilId
|
||||
{
|
||||
MINOR_WIZARD_OIL = 9731,
|
||||
LESSER_WIZARD_OIL = 47903,
|
||||
BRILLIANT_WIZARD_OIL = 47901,
|
||||
WIZARD_OIL = 47905,
|
||||
SUPERIOR_WIZARD_OIL = 47904,
|
||||
/// Blessed Wizard Oil = 26865 //scourge inv
|
||||
MINOR_WIZARD_OIL = 20744,
|
||||
LESSER_WIZARD_OIL = 20746,
|
||||
WIZARD_OIL = 20750,
|
||||
BRILLIANT_WIZARD_OIL = 20749,
|
||||
SUPERIOR_WIZARD_OIL = 22522
|
||||
};
|
||||
|
||||
enum ManaOilDisplayId
|
||||
enum ManaOilId
|
||||
{
|
||||
MINOR_MANA_OIL = 34492,
|
||||
LESSER_MANA_OIL = 47902,
|
||||
BRILLIANT_MANA_OIL = 41488,
|
||||
SUPERIOR_MANA_OIL = 36862
|
||||
};
|
||||
|
||||
enum ShieldWardDisplayId
|
||||
{
|
||||
LESSER_WARD_OFSHIELDING = 38759,
|
||||
GREATER_WARD_OFSHIELDING = 38760
|
||||
MINOR_MANA_OIL = 20745,
|
||||
LESSER_MANA_OIL = 20747,
|
||||
BRILLIANT_MANA_OIL = 20748,
|
||||
SUPERIOR_MANA_OIL = 22521
|
||||
};
|
||||
|
||||
enum class BotTypeNumber : uint8
|
||||
@@ -401,6 +408,7 @@ public:
|
||||
void ClearStrategies(BotState type);
|
||||
std::vector<std::string> GetStrategies(BotState type);
|
||||
void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false);
|
||||
void EvaluateHealerDpsStrategy();
|
||||
bool ContainsStrategy(StrategyType type);
|
||||
bool HasStrategy(std::string const name, BotState type);
|
||||
BotState GetState() { return currentState; };
|
||||
@@ -415,13 +423,15 @@ public:
|
||||
static bool IsCaster(Player* player, bool bySpec = false);
|
||||
static bool IsRangedDps(Player* player, bool bySpec = false);
|
||||
static bool IsCombo(Player* player);
|
||||
static bool IsBotMainTank(Player* player);
|
||||
static bool IsMainTank(Player* player);
|
||||
static uint32 GetGroupTankNum(Player* player);
|
||||
bool IsAssistTank(Player* player);
|
||||
bool IsAssistTankOfIndex(Player* player, int index);
|
||||
bool IsHealAssistantOfIndex(Player* player, int index);
|
||||
bool IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
static bool IsAssistTank(Player* player);
|
||||
static bool IsAssistTankOfIndex(Player* player, int index);
|
||||
static bool IsHealAssistantOfIndex(Player* player, int index);
|
||||
static bool IsRangedDpsAssistantOfIndex(Player* player, int index);
|
||||
bool HasAggro(Unit* unit);
|
||||
static int32 GetAssistTankIndex(Player* player);
|
||||
int32 GetGroupSlotIndex(Player* player);
|
||||
int32 GetRangedIndex(Player* player);
|
||||
int32 GetClassIndex(Player* player, uint8 cls);
|
||||
@@ -471,7 +481,7 @@ public:
|
||||
Item* FindBandage() const;
|
||||
Item* FindOpenableItem() const;
|
||||
Item* FindLockedItem() const;
|
||||
Item* FindConsumable(uint32 displayId) const;
|
||||
Item* FindConsumable(uint32 itemId) const;
|
||||
Item* FindStoneFor(Item* weapon) const;
|
||||
Item* FindOilFor(Item* weapon) const;
|
||||
void ImbueItem(Item* item, uint32 targetFlag, ObjectGuid targetGUID);
|
||||
@@ -544,6 +554,8 @@ public:
|
||||
bool IsSafe(Player* player);
|
||||
bool IsSafe(WorldObject* obj);
|
||||
ChatChannelSource GetChatChannelSource(Player* bot, uint32 type, std::string channelName);
|
||||
|
||||
bool CheckLocationDistanceByLevel(Player* player, const WorldLocation &loc, bool fromStartUp = false);
|
||||
|
||||
bool HasCheat(BotCheatMask mask)
|
||||
{
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
*/
|
||||
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "Config.h"
|
||||
#include "NewRpgInfo.h"
|
||||
#include "PlayerbotDungeonSuggestionMgr.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
@@ -119,6 +118,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", false);
|
||||
|
||||
randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f);
|
||||
incrementalGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.IncrementalGearInit", true);
|
||||
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
|
||||
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
|
||||
|
||||
@@ -139,6 +139,17 @@ bool PlayerbotAIConfig::Initialize()
|
||||
randomBotMapsAsString = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotMaps", "0,1,530,571");
|
||||
LoadList<std::vector<uint32>>(randomBotMapsAsString, randomBotMaps);
|
||||
probTeleToBankers = sConfigMgr->GetOption<float>("AiPlayerbot.ProbTeleToBankers", 0.25f);
|
||||
enableWeightTeleToCityBankers = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableWeightTeleToCityBankers", false);
|
||||
weightTeleToStormwind = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToStormwindWeight", 1);
|
||||
weightTeleToIronforge = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToIronforgeWeight", 1);
|
||||
weightTeleToDarnassus = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToDarnassusWeight", 1);
|
||||
weightTeleToExodar = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToExodarWeight", 1);
|
||||
weightTeleToOrgrimmar = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToOrgrimmarWeight", 1);
|
||||
weightTeleToUndercity = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToUndercityWeight", 1);
|
||||
weightTeleToThunderBluff = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToThunderBluffWeight", 1);
|
||||
weightTeleToSilvermoonCity = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToSilvermoonCityWeight", 1);
|
||||
weightTeleToShattrathCity = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToShattrathCityWeight", 1);
|
||||
weightTeleToDalaran = sConfigMgr->GetOption<int>("AiPlayerbot.TeleToDalaranWeight", 1);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestItems",
|
||||
"6948,5175,5176,5177,5178,16309,12382,13704,11000"),
|
||||
@@ -148,17 +159,23 @@ bool PlayerbotAIConfig::Initialize()
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedZoneIds",
|
||||
"2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,"
|
||||
"3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395"),
|
||||
"3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"),
|
||||
pvpProhibitedZoneIds);
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", "976,35"),
|
||||
pvpProhibitedAreaIds);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
|
||||
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
|
||||
pvpProhibitedAreaIds);
|
||||
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
|
||||
randomBotQuestIds);
|
||||
|
||||
LoadSet<std::set<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects", "176213,17155"),
|
||||
disallowedGameObjects);
|
||||
|
||||
LoadSet<std::set<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects",
|
||||
"176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,2857,"
|
||||
"179490,141596,160836,160845,179516,176224,181085,176112,128308,128403,"
|
||||
"165739,165738,175245,175970,176325,176327,123329"),
|
||||
disallowedGameObjects);
|
||||
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
|
||||
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
|
||||
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50);
|
||||
@@ -190,6 +207,19 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotsPriceChangeInterval", 48 * HOUR);
|
||||
randomBotJoinLfg = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotJoinLfg", true);
|
||||
|
||||
restrictHealerDPS = sConfigMgr->GetOption<bool>("AiPlayerbot.HealerDPSMapRestriction", false);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RestrictedHealerDPSMaps",
|
||||
"33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,"
|
||||
"1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,"
|
||||
"575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509,"
|
||||
"531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"),
|
||||
restrictedHealerDPSMaps);
|
||||
|
||||
//////////////////////////// ICC
|
||||
|
||||
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
|
||||
|
||||
//////////////////////////// CHAT
|
||||
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
|
||||
randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false);
|
||||
@@ -341,7 +371,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
{
|
||||
std::string setting = "AiPlayerbot.ZoneBracket." + std::to_string(zoneId);
|
||||
std::string value = sConfigMgr->GetOption<std::string>(setting, "");
|
||||
|
||||
|
||||
if (!value.empty())
|
||||
{
|
||||
size_t commaPos = value.find(',');
|
||||
@@ -356,7 +386,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
|
||||
randomChangeMultiplier = sConfigMgr->GetOption<float>("AiPlayerbot.RandomChangeMultiplier", 1.0);
|
||||
|
||||
randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", "-threat");
|
||||
randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", "");
|
||||
randomBotNonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotNonCombatStrategies", "");
|
||||
combatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.CombatStrategies", "+custom::say");
|
||||
nonCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.NonCombatStrategies", "+custom::say,+return");
|
||||
@@ -443,11 +473,13 @@ bool PlayerbotAIConfig::Initialize()
|
||||
}
|
||||
|
||||
botCheats.clear();
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi"),
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "food,taxi,raid"),
|
||||
botCheats);
|
||||
|
||||
botCheatMask = 0;
|
||||
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "food") != botCheats.end())
|
||||
botCheatMask |= (uint32)BotCheatMask::food;
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "taxi") != botCheats.end())
|
||||
botCheatMask |= (uint32)BotCheatMask::taxi;
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "gold") != botCheats.end())
|
||||
@@ -458,30 +490,17 @@ bool PlayerbotAIConfig::Initialize()
|
||||
botCheatMask |= (uint32)BotCheatMask::mana;
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "power") != botCheats.end())
|
||||
botCheatMask |= (uint32)BotCheatMask::power;
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "raid") != botCheats.end())
|
||||
botCheatMask |= (uint32)BotCheatMask::raid;
|
||||
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""),
|
||||
allowedLogFiles);
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.TradeActionExcludedPrefixes", ""),
|
||||
tradeActionExcludedPrefixes);
|
||||
|
||||
worldBuffs.clear();
|
||||
|
||||
LOG_INFO("playerbots", "Loading Worldbuff...");
|
||||
for (uint32 factionId = 0; factionId < 3; factionId++)
|
||||
{
|
||||
for (uint32 classId = 0; classId < MAX_CLASSES; classId++)
|
||||
{
|
||||
for (uint32 specId = 0; specId <= MAX_WORLDBUFF_SPECNO; specId++)
|
||||
{
|
||||
for (uint32 minLevel = 0; minLevel <= randomBotMaxLevel; minLevel++)
|
||||
{
|
||||
for (uint32 maxLevel = minLevel; maxLevel <= randomBotMaxLevel; maxLevel++)
|
||||
{
|
||||
loadWorldBuff(factionId, classId, specId, minLevel, maxLevel);
|
||||
}
|
||||
loadWorldBuff(factionId, classId, specId, minLevel, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loadWorldBuff();
|
||||
LOG_INFO("playerbots", "Loading World Buff Feature...");
|
||||
|
||||
randomBotAccountPrefix = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAccountPrefix", "rndbot");
|
||||
randomBotAccountCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAccountCount", 0);
|
||||
@@ -575,13 +594,23 @@ bool PlayerbotAIConfig::Initialize()
|
||||
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
|
||||
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
|
||||
hunterWolfPet = sConfigMgr->GetOption<int32>("AiPlayerbot.HunterWolfPet", 0);
|
||||
defaultPetStance = sConfigMgr->GetOption<int32>("AiPlayerbot.DefaultPetStance", 1);
|
||||
petChatCommandDebug = sConfigMgr->GetOption<bool>("AiPlayerbot.PetChatCommandDebug", 0);
|
||||
autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true);
|
||||
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
|
||||
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
|
||||
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", true);
|
||||
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", false);
|
||||
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", true);
|
||||
|
||||
RpgStatusProbWeight[RPG_WANDER_RANDOM] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderRandom", 15);
|
||||
RpgStatusProbWeight[RPG_WANDER_NPC] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderNpc", 20);
|
||||
RpgStatusProbWeight[RPG_GO_GRIND] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.GoGrind", 15);
|
||||
RpgStatusProbWeight[RPG_GO_CAMP] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.GoCamp", 10);
|
||||
RpgStatusProbWeight[RPG_DO_QUEST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.DoQuest", 60);
|
||||
RpgStatusProbWeight[RPG_TRAVEL_FLIGHT] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.TravelFlight", 15);
|
||||
RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.Rest", 5);
|
||||
|
||||
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
|
||||
freeFood = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeFood", true);
|
||||
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true);
|
||||
|
||||
// arena
|
||||
@@ -600,6 +629,9 @@ bool PlayerbotAIConfig::Initialize()
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assign account types after accounts are created
|
||||
sRandomPlayerbotMgr->AssignAccountTypes();
|
||||
|
||||
if (sPlayerbotAIConfig->enabled)
|
||||
{
|
||||
sRandomPlayerbotMgr->Init();
|
||||
@@ -611,18 +643,16 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sPlayerbotTextMgr->LoadBotTextChance();
|
||||
PlayerbotFactory::Init();
|
||||
|
||||
if (!sPlayerbotAIConfig->autoDoQuests)
|
||||
{
|
||||
LOG_INFO("server.loading", "Loading Quest Detail Data...");
|
||||
sTravelMgr->LoadQuestTravelTable();
|
||||
}
|
||||
|
||||
AiObjectContext::BuildAllSharedContexts();
|
||||
|
||||
if (sPlayerbotAIConfig->randomBotSuggestDungeons)
|
||||
{
|
||||
sPlayerbotDungeonSuggestionMgr->LoadDungeonSuggestions();
|
||||
}
|
||||
|
||||
excludedHunterPetFamilies.clear();
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.ExcludedHunterPetFamilies", ""), excludedHunterPetFamilies);
|
||||
|
||||
LOG_INFO("server.loading", "---------------------------------------");
|
||||
LOG_INFO("server.loading", " AI Playerbots initialized ");
|
||||
LOG_INFO("server.loading", "---------------------------------------");
|
||||
@@ -655,6 +685,12 @@ bool PlayerbotAIConfig::IsInPvpProhibitedArea(uint32 id)
|
||||
return find(pvpProhibitedAreaIds.begin(), pvpProhibitedAreaIds.end(), id) != pvpProhibitedAreaIds.end();
|
||||
}
|
||||
|
||||
bool PlayerbotAIConfig::IsRestrictedHealerDPSMap(uint32 mapId) const
|
||||
{
|
||||
return restrictHealerDPS &&
|
||||
std::find(restrictedHealerDPSMaps.begin(), restrictedHealerDPSMaps.end(), mapId) != restrictedHealerDPSMaps.end();
|
||||
}
|
||||
|
||||
std::string const PlayerbotAIConfig::GetTimestampStr()
|
||||
{
|
||||
time_t t = time(nullptr);
|
||||
@@ -727,88 +763,62 @@ void PlayerbotAIConfig::log(std::string const fileName, char const* str, ...)
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1)
|
||||
void PlayerbotAIConfig::loadWorldBuff()
|
||||
{
|
||||
std::vector<uint32> buffs;
|
||||
std::string matrix = sConfigMgr->GetOption<std::string>("AiPlayerbot.WorldBuffMatrix", "", true);
|
||||
if (matrix.empty())
|
||||
return;
|
||||
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1 << "." << maxLevel1;
|
||||
std::istringstream entryStream(matrix);
|
||||
std::string entry;
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
while (std::getline(entryStream, entry, ';'))
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
}
|
||||
|
||||
if (maxLevel1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1;
|
||||
entry.erase(0, entry.find_first_not_of(" \t\r\n"));
|
||||
entry.erase(entry.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
size_t firstColon = entry.find(':');
|
||||
size_t secondColon = entry.find(':', firstColon + 1);
|
||||
|
||||
for (auto buff : buffs)
|
||||
if (firstColon == std::string::npos || secondColon == std::string::npos)
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
LOG_ERROR("playerbots", "Malformed entry: [{}]", entry);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxLevel1 == 0 && minLevel1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1 << "." << specId1;
|
||||
std::string metaPart = entry.substr(firstColon + 1, secondColon - firstColon - 1);
|
||||
std::string spellPart = entry.substr(secondColon + 1);
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
std::vector<uint32> ids;
|
||||
std::istringstream metaStream(metaPart);
|
||||
std::string token;
|
||||
while (std::getline(metaStream, token, ','))
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
try {
|
||||
ids.push_back(static_cast<uint32>(std::stoi(token)));
|
||||
} catch (...) {
|
||||
LOG_ERROR("playerbots", "Invalid meta token in [{}]", entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1;
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
if (ids.size() != 5)
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
LOG_ERROR("playerbots", "Entry [{}] has incomplete meta block", entry);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1;
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
std::istringstream spellStream(spellPart);
|
||||
while (std::getline(spellStream, token, ','))
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
}
|
||||
}
|
||||
|
||||
if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff";
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
try {
|
||||
uint32 spellId = static_cast<uint32>(std::stoi(token));
|
||||
worldBuff wb = { spellId, ids[0], ids[1], ids[2], ids[3], ids[4] };
|
||||
worldBuffs.push_back(wb);
|
||||
} catch (...) {
|
||||
LOG_ERROR("playerbots", "Invalid spell ID in [{}]", entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define _PLAYERBOT_PLAYERbotAICONFIG_H
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Common.h"
|
||||
#include "DBCEnums.h"
|
||||
@@ -21,7 +22,9 @@ enum class BotCheatMask : uint32
|
||||
health = 4,
|
||||
mana = 8,
|
||||
power = 16,
|
||||
maxMask = 32
|
||||
raid = 32,
|
||||
food = 64,
|
||||
maxMask = 128
|
||||
};
|
||||
|
||||
enum class HealingManaEfficiency : uint8
|
||||
@@ -34,8 +37,27 @@ enum class HealingManaEfficiency : uint8
|
||||
SUPERIOR = 32
|
||||
};
|
||||
|
||||
enum NewRpgStatus : int
|
||||
{
|
||||
RPG_STATUS_START = 0,
|
||||
// Going to far away place
|
||||
RPG_GO_GRIND = 0,
|
||||
RPG_GO_CAMP = 1,
|
||||
// Exploring nearby
|
||||
RPG_WANDER_RANDOM = 2,
|
||||
RPG_WANDER_NPC = 3,
|
||||
// Do Quest (based on quest status)
|
||||
RPG_DO_QUEST = 4,
|
||||
// Travel
|
||||
RPG_TRAVEL_FLIGHT = 5,
|
||||
// Taking a break
|
||||
RPG_REST = 6,
|
||||
// Initial status
|
||||
RPG_IDLE = 7,
|
||||
RPG_STATUS_END = 8
|
||||
};
|
||||
|
||||
#define MAX_SPECNO 20
|
||||
#define MAX_WORLDBUFF_SPECNO 3
|
||||
|
||||
class PlayerbotAIConfig
|
||||
{
|
||||
@@ -56,6 +78,7 @@ public:
|
||||
|
||||
bool enabled;
|
||||
bool disabledWithoutRealPlayer;
|
||||
bool EnableICCBuffs;
|
||||
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
|
||||
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
|
||||
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
|
||||
@@ -79,6 +102,17 @@ public:
|
||||
bool botAutologin;
|
||||
std::string randomBotMapsAsString;
|
||||
float probTeleToBankers;
|
||||
bool enableWeightTeleToCityBankers;
|
||||
int weightTeleToStormwind;
|
||||
int weightTeleToIronforge;
|
||||
int weightTeleToDarnassus;
|
||||
int weightTeleToExodar;
|
||||
int weightTeleToOrgrimmar;
|
||||
int weightTeleToUndercity;
|
||||
int weightTeleToThunderBluff;
|
||||
int weightTeleToSilvermoonCity;
|
||||
int weightTeleToShattrathCity;
|
||||
int weightTeleToDalaran;
|
||||
std::vector<uint32> randomBotMaps;
|
||||
std::vector<uint32> randomBotQuestItems;
|
||||
std::vector<uint32> randomBotAccounts;
|
||||
@@ -86,6 +120,7 @@ public:
|
||||
std::vector<uint32> randomBotQuestIds;
|
||||
uint32 randomBotTeleportDistance;
|
||||
float randomGearLoweringChance;
|
||||
bool incrementalGearInit;
|
||||
int32 randomGearQualityLimit;
|
||||
int32 randomGearScoreLimit;
|
||||
float randomBotMinLevelChance, randomBotMaxLevelChance;
|
||||
@@ -254,6 +289,7 @@ public:
|
||||
uint32 iterationsPerTick;
|
||||
|
||||
std::mutex m_logMtx;
|
||||
std::vector<std::string> tradeActionExcludedPrefixes;
|
||||
std::vector<std::string> allowedLogFiles;
|
||||
std::unordered_map<std::string, std::pair<FILE*, bool>> logFiles;
|
||||
|
||||
@@ -263,11 +299,11 @@ public:
|
||||
struct worldBuff
|
||||
{
|
||||
uint32 spellId;
|
||||
uint32 factionId = 0;
|
||||
uint32 classId = 0;
|
||||
uint32 specId = 0;
|
||||
uint32 minLevel = 0;
|
||||
uint32 maxLevel = 0;
|
||||
uint32 factionId;
|
||||
uint32 classId;
|
||||
uint32 specId;
|
||||
uint32 minLevel;
|
||||
uint32 maxLevel;
|
||||
};
|
||||
|
||||
std::vector<worldBuff> worldBuffs;
|
||||
@@ -309,11 +345,13 @@ public:
|
||||
bool autoPickTalents;
|
||||
bool autoUpgradeEquip;
|
||||
int32 hunterWolfPet;
|
||||
int32 defaultPetStance;
|
||||
int32 petChatCommandDebug;
|
||||
bool autoLearnTrainerSpells;
|
||||
bool autoDoQuests;
|
||||
bool enableNewRpgStrategy;
|
||||
std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight;
|
||||
bool syncLevelWithPlayers;
|
||||
bool freeFood;
|
||||
bool autoLearnQuestSpells;
|
||||
bool autoTeleportForLevel;
|
||||
bool randomBotGroupNearby;
|
||||
@@ -373,9 +411,16 @@ public:
|
||||
}
|
||||
void log(std::string const fileName, const char* str, ...);
|
||||
|
||||
void loadWorldBuff(uint32 factionId, uint32 classId, uint32 specId, uint32 minLevel, uint32 maxLevel);
|
||||
void loadWorldBuff();
|
||||
|
||||
static std::vector<std::vector<uint32>> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order);
|
||||
static std::vector<std::vector<uint32>> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order);
|
||||
|
||||
bool restrictHealerDPS = false;
|
||||
std::vector<uint32> restrictedHealerDPSMaps;
|
||||
bool IsRestrictedHealerDPSMap(uint32 mapId) const;
|
||||
|
||||
std::vector<uint32> excludedHunterPetFamilies;
|
||||
};
|
||||
|
||||
#define sPlayerbotAIConfig PlayerbotAIConfig::instance()
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
#include "BroadcastHelper.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "DatabaseEnv.h" // Added for gender choice
|
||||
#include <algorithm> // Added for gender choice
|
||||
|
||||
class BotInitGuard
|
||||
{
|
||||
@@ -166,7 +168,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
uint32 botAccountId = holder.GetAccountId();
|
||||
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
|
||||
// allows channels to work as intended)
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
|
||||
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
|
||||
@@ -584,8 +586,8 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
}
|
||||
|
||||
bot->SaveToDB(false, false);
|
||||
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter());
|
||||
if (addClassBot && master && isRandomAccount && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
|
||||
bool addClassBot = sRandomPlayerbotMgr->IsAccountType(accountId, 2);
|
||||
if (addClassBot && master && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
|
||||
{
|
||||
// PlayerbotFactory factory(bot, master->GetLevel());
|
||||
// factory.Randomize(false);
|
||||
@@ -837,6 +839,18 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
|
||||
return "unknown command";
|
||||
}
|
||||
|
||||
// Added for gender choice : Returns the gender of an offline character: 0 = male, 1 = female.
|
||||
static uint8 GetOfflinePlayerGender(ObjectGuid guid)
|
||||
{
|
||||
QueryResult result = CharacterDatabase.Query(
|
||||
"SELECT gender FROM characters WHERE guid = {}", guid.GetCounter());
|
||||
|
||||
if (result)
|
||||
return (*result)[0].Get<uint8>(); // 0 = male, 1 = female
|
||||
|
||||
return GENDER_MALE; // fallback value
|
||||
}
|
||||
|
||||
bool PlayerbotMgr::HandlePlayerbotMgrCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->enabled)
|
||||
@@ -879,15 +893,17 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
if (!*args)
|
||||
{
|
||||
messages.push_back("usage: list/reload/tweak/self or add/addaccount/init/remove PLAYERNAME\n");
|
||||
messages.push_back("usage: addclass CLASSNAME");
|
||||
messages.push_back("usage: addclass CLASSNAME [male|female|0|1]");
|
||||
return messages;
|
||||
}
|
||||
|
||||
char* cmd = strtok((char*)args, " ");
|
||||
char* charname = strtok(nullptr, " ");
|
||||
char* genderArg = strtok(nullptr, " "); // Added for gender choice [male|female|0|1] optionnel
|
||||
|
||||
if (!cmd)
|
||||
{
|
||||
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME or addclass CLASSNAME");
|
||||
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME or addclass CLASSNAME [male|female]");
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -1110,6 +1126,24 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
messages.push_back("Error: Invalid Class. Try again.");
|
||||
return messages;
|
||||
}
|
||||
// Added for gender choice : Parsing gender
|
||||
int8 gender = -1; // -1 = gender will be random
|
||||
if (genderArg)
|
||||
{
|
||||
std::string g = genderArg;
|
||||
std::transform(g.begin(), g.end(), g.begin(), ::tolower);
|
||||
|
||||
if (g == "male" || g == "0")
|
||||
gender = GENDER_MALE; // 0
|
||||
else if (g == "female" || g == "1")
|
||||
gender = GENDER_FEMALE; // 1
|
||||
else
|
||||
{
|
||||
messages.push_back("Unknown gender : " + g + " (male/female/0/1)");
|
||||
return messages;
|
||||
}
|
||||
} //end
|
||||
|
||||
if (claz == 6 && master->GetLevel() < sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
|
||||
{
|
||||
messages.push_back("Your level is too low to summon Deathknight");
|
||||
@@ -1119,6 +1153,9 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
|
||||
for (const ObjectGuid &guid: guidCache)
|
||||
{
|
||||
// If the user requested a specific gender, skip any character that doesn't match.
|
||||
if (gender != -1 && GetOfflinePlayerGender(guid) != gender)
|
||||
continue;
|
||||
if (botLoading.find(guid) != botLoading.end())
|
||||
continue;
|
||||
if (ObjectAccessor::FindConnectedPlayer(guid))
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "cs_playerbots.h"
|
||||
#include "cmath"
|
||||
#include "BattleGroundTactics.h"
|
||||
|
||||
class PlayerbotsDatabaseScript : public DatabaseScript
|
||||
{
|
||||
@@ -196,36 +197,42 @@ public:
|
||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeCriteriaProgress(Player* player, AchievementCriteriaEntry const* /*criteria*/) override
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||
{
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player) && (achievement->flags == 256 || achievement->flags == 768))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* /*achievement*/) override
|
||||
{
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
|
||||
{
|
||||
if (!player->GetSession()->IsBot())
|
||||
return;
|
||||
|
||||
if (!sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
// early return
|
||||
if (sPlayerbotAIConfig->randomBotXPRate == 1.0 || !player)
|
||||
return;
|
||||
|
||||
if (sPlayerbotAIConfig->randomBotXPRate != 1.0)
|
||||
// no XP multiplier, when player is no bot.
|
||||
if (!player->GetSession()->IsBot() || !sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
return;
|
||||
|
||||
// no XP multiplier, when bot has group where leader is a real player.
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
|
||||
Player* leader = group->GetLeader();
|
||||
if (leader && leader != player)
|
||||
{
|
||||
if (PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader))
|
||||
{
|
||||
if (leaderBotAI->HasRealPlayerMaster())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise apply bot XP multiplier.
|
||||
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -389,6 +396,43 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerBotsBGScript : public BGScript
|
||||
{
|
||||
public:
|
||||
PlayerBotsBGScript() : BGScript("PlayerBotsBGScript") {}
|
||||
|
||||
void OnBattlegroundStart(Battleground* bg) override
|
||||
{
|
||||
BGStrategyData data;
|
||||
|
||||
switch (bg->GetBgTypeID())
|
||||
{
|
||||
case BATTLEGROUND_WS:
|
||||
data.allianceStrategy = urand(0, WS_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, WS_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_AB:
|
||||
data.allianceStrategy = urand(0, AB_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, AB_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_AV:
|
||||
data.allianceStrategy = urand(0, AV_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, AV_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_EY:
|
||||
data.allianceStrategy = urand(0, EY_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, EY_STRATEGY_MAX - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bgStrategies[bg->GetInstanceID()] = data;
|
||||
}
|
||||
|
||||
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
|
||||
};
|
||||
|
||||
void AddPlayerbotsScripts()
|
||||
{
|
||||
new PlayerbotsDatabaseScript();
|
||||
@@ -397,6 +441,7 @@ void AddPlayerbotsScripts()
|
||||
new PlayerbotsServerScript();
|
||||
new PlayerbotsWorldScript();
|
||||
new PlayerbotsScript();
|
||||
new PlayerBotsBGScript();
|
||||
|
||||
AddSC_playerbots_commandscript();
|
||||
}
|
||||
|
||||
@@ -393,37 +393,118 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
|
||||
return std::move(botName);
|
||||
}
|
||||
|
||||
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
|
||||
// or determining it dynamically based on MaxRandomBots, EnablePeriodicOnlineOffline and its ratio,
|
||||
// and AddClassAccountPoolSize. The system also factors in the types of existing account, as assigned by
|
||||
// AssignAccountTypes()
|
||||
uint32 RandomPlayerbotFactory::CalculateTotalAccountCount()
|
||||
{
|
||||
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
|
||||
// or determining it dynamically based on the WOTLK condition, max random bots, rotation pool size,
|
||||
// and additional class account pool size.
|
||||
// Reset account types if features are disabled
|
||||
// Reset is done here to precede needed accounts calculations
|
||||
if (sPlayerbotAIConfig->maxRandomBots == 0 || sPlayerbotAIConfig->addClassAccountPoolSize == 0)
|
||||
{
|
||||
if (sPlayerbotAIConfig->maxRandomBots == 0)
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 1");
|
||||
LOG_INFO("playerbots", "MaxRandomBots set to 0, any RNDbot accounts (type 1) will be unassigned (type 0)");
|
||||
}
|
||||
if (sPlayerbotAIConfig->addClassAccountPoolSize == 0)
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 2");
|
||||
LOG_INFO("playerbots", "AddClassAccountPoolSize set to 0, any AddClass accounts (type 2) will be unassigned (type 0)");
|
||||
}
|
||||
|
||||
// Wait for DB to reflect the change, up to 1 second max. This is needed to make sure other logs don't show wrong info
|
||||
for (int waited = 0; waited < 1000; waited += 50)
|
||||
{
|
||||
QueryResult res = PlayerbotsDatabase.Query("SELECT COUNT(*) FROM playerbots_account_type WHERE account_type IN ({}, {})",
|
||||
sPlayerbotAIConfig->maxRandomBots == 0 ? 1 : -1,
|
||||
sPlayerbotAIConfig->addClassAccountPoolSize == 0 ? 2 : -1);
|
||||
|
||||
if (!res || res->Fetch()[0].Get<uint64>() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Extra 50ms fixed delay for safety.
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if randomBotAccountCount is set, otherwise calculate it dynamically.
|
||||
if (sPlayerbotAIConfig->randomBotAccountCount > 0)
|
||||
return sPlayerbotAIConfig->randomBotAccountCount;
|
||||
|
||||
// Avoid creating accounts if both maxRandom & ClassBots are set to zero.
|
||||
if (sPlayerbotAIConfig->maxRandomBots == 0 &&
|
||||
sPlayerbotAIConfig->addClassAccountPoolSize == 0)
|
||||
return 0;
|
||||
// Check existing account types
|
||||
uint32 existingRndBotAccounts = 0;
|
||||
uint32 existingAddClassAccounts = 0;
|
||||
uint32 existingUnassignedAccounts = 0;
|
||||
|
||||
//bool isWOTLK = sWorld->getIntConfig(CONFIG_EXPANSION) == EXPANSION_WRATH_OF_THE_LICH_KING; //not used, line marked for removal.
|
||||
QueryResult typeCheck = PlayerbotsDatabase.Query("SELECT account_type, COUNT(*) FROM playerbots_account_type GROUP BY account_type");
|
||||
if (typeCheck)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = typeCheck->Fetch();
|
||||
uint8 accountType = fields[0].Get<uint8>();
|
||||
uint32 count = fields[1].Get<uint32>();
|
||||
|
||||
// Determine divisor based on WOTLK condition
|
||||
if (accountType == 0) existingUnassignedAccounts = count;
|
||||
else if (accountType == 1) existingRndBotAccounts = count;
|
||||
else if (accountType == 2) existingAddClassAccounts = count;
|
||||
} while (typeCheck->NextRow());
|
||||
}
|
||||
|
||||
// Determine divisor based on Death Knight login eligibility and requested A&H faction ratio
|
||||
int divisor = CalculateAvailableCharsPerAccount();
|
||||
|
||||
// Calculate max bots
|
||||
int maxBots = sPlayerbotAIConfig->maxRandomBots;
|
||||
// Take perodic online - offline into account
|
||||
// Take periodic online - offline into account
|
||||
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
|
||||
{
|
||||
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
|
||||
}
|
||||
|
||||
// Calculate base accounts, add class account pool size, and add 1 as a fixed offset
|
||||
uint32 baseAccounts = maxBots / divisor;
|
||||
return baseAccounts + sPlayerbotAIConfig->addClassAccountPoolSize + 1;
|
||||
// Calculate number of accounts needed for RNDbots
|
||||
// Result is rounded up for maxBots not cleanly divisible by the divisor
|
||||
uint32 neededRndBotAccounts = (maxBots + divisor - 1) / divisor;
|
||||
uint32 neededAddClassAccounts = sPlayerbotAIConfig->addClassAccountPoolSize;
|
||||
|
||||
// Start with existing total
|
||||
uint32 existingTotal = existingRndBotAccounts + existingAddClassAccounts + existingUnassignedAccounts;
|
||||
|
||||
// Calculate shortfalls after using unassigned accounts
|
||||
uint32 availableUnassigned = existingUnassignedAccounts;
|
||||
uint32 additionalAccountsNeeded = 0;
|
||||
|
||||
// Check RNDbot needs
|
||||
if (neededRndBotAccounts > existingRndBotAccounts)
|
||||
{
|
||||
uint32 rndBotShortfall = neededRndBotAccounts - existingRndBotAccounts;
|
||||
if (rndBotShortfall <= availableUnassigned)
|
||||
availableUnassigned -= rndBotShortfall;
|
||||
else
|
||||
{
|
||||
additionalAccountsNeeded += (rndBotShortfall - availableUnassigned);
|
||||
availableUnassigned = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check AddClass needs
|
||||
if (neededAddClassAccounts > existingAddClassAccounts)
|
||||
{
|
||||
uint32 addClassShortfall = neededAddClassAccounts - existingAddClassAccounts;
|
||||
if (addClassShortfall <= availableUnassigned)
|
||||
availableUnassigned -= addClassShortfall;
|
||||
else
|
||||
{
|
||||
additionalAccountsNeeded += (addClassShortfall - availableUnassigned);
|
||||
availableUnassigned = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Return existing total plus any additional accounts needed
|
||||
return existingTotal + additionalAccountsNeeded;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotFactory::CalculateAvailableCharsPerAccount()
|
||||
@@ -475,8 +556,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
LOG_INFO("playerbots", "Deleting all random bot characters and accounts...");
|
||||
|
||||
// First execute all the cleanup SQL commands
|
||||
// Clear playerbots_random_bots table
|
||||
// Clear playerbots_random_bots and playerbots_account_type
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots");
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbots_account_type");
|
||||
|
||||
// Get the database names dynamically
|
||||
std::string loginDBName = LoginDatabase.GetConnectionInfo()->database;
|
||||
@@ -486,6 +568,13 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
CharacterDatabase.Execute("DELETE FROM characters WHERE account IN (SELECT id FROM " + loginDBName + ".account WHERE username LIKE '{}%%')",
|
||||
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
|
||||
|
||||
// Wait for the characters to be deleted before proceeding to dependent deletes
|
||||
while (CharacterDatabase.QueueSize())
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
|
||||
|
||||
// Clean up orphaned entries in playerbots_guild_tasks
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbots_guild_tasks WHERE owner NOT IN (SELECT guid FROM " + characterDBName + ".characters)");
|
||||
|
||||
@@ -500,11 +589,13 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
CharacterDatabase.Execute("DELETE FROM character_achievement WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_achievement_progress WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_action WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_arena_stats WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_aura WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_entry_point WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_glyphs WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_homebind WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
|
||||
CharacterDatabase.Execute("DELETE FROM character_inventory WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
|
||||
|
||||
// Clean up pet data
|
||||
CharacterDatabase.Execute("DELETE FROM character_pet WHERE owner NOT IN (SELECT guid FROM characters)");
|
||||
@@ -559,11 +650,23 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
|
||||
uint32 timer = getMSTime();
|
||||
|
||||
// After ALL deletions, make sure data is commited to DB
|
||||
LoginDatabase.Execute("COMMIT");
|
||||
CharacterDatabase.Execute("COMMIT");
|
||||
PlayerbotsDatabase.Execute("COMMIT");
|
||||
|
||||
// Wait for all pending database operations to complete
|
||||
while (LoginDatabase.QueueSize() || CharacterDatabase.QueueSize() || PlayerbotsDatabase.QueueSize())
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
|
||||
|
||||
// Flush tables to ensure all data in memory are written to disk
|
||||
LoginDatabase.Execute("FLUSH TABLES");
|
||||
CharacterDatabase.Execute("FLUSH TABLES");
|
||||
PlayerbotsDatabase.Execute("FLUSH TABLES");
|
||||
|
||||
LOG_INFO("playerbots", ">> Random bot accounts and data deleted in {} ms", GetMSTimeDiffToNow(timer));
|
||||
LOG_INFO("playerbots", "Please reset the AiPlayerbot.DeleteRandomBotAccounts to 0 and restart the server...");
|
||||
World::StopNow(SHUTDOWN_EXIT_CODE);
|
||||
@@ -682,7 +785,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
LOG_DEBUG("playerbots", "Creating random bot characters for account: [{}/{}]", accountNumber + 1, totalAccountCount);
|
||||
RandomPlayerbotFactory factory(accountId);
|
||||
|
||||
WorldSession* session = new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
WorldSession* session = new WorldSession(accountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), LOCALE_enUS, 0, false, false, 0, true);
|
||||
sessionBots.push_back(session);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,6 @@ public:
|
||||
|
||||
bool IsEmpty() { return !lastChangeTime; }
|
||||
|
||||
public:
|
||||
uint32 value;
|
||||
uint32 lastChangeTime;
|
||||
uint32 validIn;
|
||||
@@ -104,10 +103,6 @@ public:
|
||||
void LogPlayerLocation();
|
||||
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
|
||||
|
||||
private:
|
||||
//void ScaleBotActivity();
|
||||
|
||||
public:
|
||||
uint32 activeBots = 0;
|
||||
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
|
||||
bool IsRandomBot(Player* bot);
|
||||
@@ -180,13 +175,24 @@ public:
|
||||
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> allianceStarterPerLevelCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> hordeStarterPerLevelCache;
|
||||
std::vector<uint32> allianceFlightMasterCache;
|
||||
std::vector<uint32> hordeFlightMasterCache;
|
||||
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;
|
||||
struct BankerLocation {
|
||||
WorldLocation loc;
|
||||
uint32 entry;
|
||||
};
|
||||
std::map<uint8, std::vector<BankerLocation>> bankerLocsPerLevelCache;
|
||||
|
||||
// Account type management
|
||||
void AssignAccountTypes();
|
||||
bool IsAccountType(uint32 accountId, uint8 accountType);
|
||||
|
||||
protected:
|
||||
void OnBotLoginInternal(Player* const bot) override;
|
||||
|
||||
@@ -216,10 +222,8 @@ private:
|
||||
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
|
||||
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
|
||||
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
|
||||
|
||||
std::vector<Player*> players;
|
||||
uint32 processTicks;
|
||||
|
||||
|
||||
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
|
||||
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
|
||||
@@ -228,6 +232,12 @@ private:
|
||||
std::list<uint32> currentBots;
|
||||
uint32 bgBotsCount;
|
||||
uint32 playersLevel;
|
||||
|
||||
// Account lists
|
||||
std::vector<uint32> rndBotTypeAccounts; // Accounts marked as RNDbot (type 1)
|
||||
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
|
||||
|
||||
//void ScaleBotActivity(); // Deprecated function
|
||||
};
|
||||
|
||||
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()
|
||||
|
||||
@@ -3336,7 +3336,7 @@ void TravelMgr::LoadQuestTravelTable()
|
||||
uint32 accountId = fields[0].Get<uint32>();
|
||||
|
||||
WorldSession* session =
|
||||
new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
new WorldSession(accountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
LOCALE_enUS, 0, false, false, 0, true);
|
||||
|
||||
std::vector<std::pair<std::pair<uint32, uint32>, uint32>> classSpecLevel;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,63 +45,6 @@ enum spec : uint8
|
||||
ROLE_CDPS = 3
|
||||
};*/
|
||||
|
||||
enum PriorizedConsumables
|
||||
{
|
||||
CONSUM_ID_ROUGH_WEIGHTSTONE = 3239,
|
||||
CONSUM_ID_COARSE_WEIGHTSTONE = 3239,
|
||||
CONSUM_ID_HEAVY_WEIGHTSTONE = 3241,
|
||||
CONSUM_ID_SOLID_WEIGHTSTONE = 7965,
|
||||
CONSUM_ID_DENSE_WEIGHTSTONE = 12643,
|
||||
CONSUM_ID_FEL_WEIGHTSTONE = 28420,
|
||||
CONSUM_ID_ADAMANTITE_WEIGHTSTONE = 28421,
|
||||
CONSUM_ID_ROUGH_SHARPENING_STONE = 2862,
|
||||
CONSUM_ID_COARSE_SHARPENING_STONE = 2863,
|
||||
CONSUM_ID_HEAVY_SHARPENING_STONE = 2871,
|
||||
CONSUM_ID_SOL_SHARPENING_STONE = 7964,
|
||||
CONSUM_ID_DENSE_SHARPENING_STONE = 12404,
|
||||
CONSUM_ID_ELEMENTAL_SHARPENING_STONE = 18262,
|
||||
CONSUM_ID_CONSECRATED_SHARPENING_STONE = 23122,
|
||||
CONSUM_ID_FEL_SHARPENING_STONE = 23528,
|
||||
CONSUM_ID_ADAMANTITE_SHARPENING_STONE = 23529,
|
||||
CONSUM_ID_LINEN_BANDAGE = 1251,
|
||||
CONSUM_ID_HEAVY_LINEN_BANDAGE = 2581,
|
||||
CONSUM_ID_WOOL_BANDAGE = 3530,
|
||||
CONSUM_ID_HEAVY_WOOL_BANDAGE = 3531,
|
||||
CONSUM_ID_SILK_BANDAGE = 6450,
|
||||
CONSUM_ID_HEAVY_SILK_BANDAGE = 6451,
|
||||
CONSUM_ID_MAGEWEAVE_BANDAGE = 8544,
|
||||
CONSUM_ID_HEAVY_MAGEWEAVE_BANDAGE = 8545,
|
||||
CONSUM_ID_RUNECLOTH_BANDAGE = 14529,
|
||||
CONSUM_ID_HEAVY_RUNECLOTH_BANDAGE = 14530,
|
||||
CONSUM_ID_NETHERWEAVE_BANDAGE = 21990,
|
||||
CONSUM_ID_HEAVY_NETHERWEAVE_BANDAGE = 21991,
|
||||
CONSUM_ID_BRILLIANT_MANA_OIL = 20748,
|
||||
CONSUM_ID_MINOR_MANA_OIL = 20745,
|
||||
CONSUM_ID_SUPERIOR_MANA_OIL = 22521,
|
||||
CONSUM_ID_LESSER_MANA_OIL = 20747,
|
||||
CONSUM_ID_BRILLIANT_WIZARD_OIL = 20749,
|
||||
CONSUM_ID_MINOR_WIZARD_OIL = 20744,
|
||||
CONSUM_ID_SUPERIOR_WIZARD_OIL = 22522,
|
||||
CONSUM_ID_WIZARD_OIL = 20750,
|
||||
CONSUM_ID_LESSER_WIZARD_OIL = 20746,
|
||||
CONSUM_ID_INSTANT_POISON = 6947,
|
||||
CONSUM_ID_INSTANT_POISON_II = 6949,
|
||||
CONSUM_ID_INSTANT_POISON_III = 6950,
|
||||
CONSUM_ID_INSTANT_POISON_IV = 8926,
|
||||
CONSUM_ID_INSTANT_POISON_V = 8927,
|
||||
CONSUM_ID_INSTANT_POISON_VI = 8928,
|
||||
CONSUM_ID_INSTANT_POISON_VII = 21927,
|
||||
CONSUM_ID_DEADLY_POISON = 2892,
|
||||
CONSUM_ID_DEADLY_POISON_II = 2893,
|
||||
CONSUM_ID_DEADLY_POISON_III = 8984,
|
||||
CONSUM_ID_DEADLY_POISON_IV = 8985,
|
||||
CONSUM_ID_DEADLY_POISON_V = 20844,
|
||||
CONSUM_ID_DEADLY_POISON_VI = 22053,
|
||||
CONSUM_ID_DEADLY_POISON_VII = 22054
|
||||
};
|
||||
|
||||
#define MAX_CONSUM_ID 28
|
||||
|
||||
class PlayerbotFactory
|
||||
{
|
||||
public:
|
||||
@@ -128,8 +71,10 @@ public:
|
||||
void InitAmmo();
|
||||
static uint32 CalcMixedGearScore(uint32 gs, uint32 quality);
|
||||
void InitPetTalents();
|
||||
|
||||
void CleanupConsumables();
|
||||
void InitReagents();
|
||||
void InitConsumables();
|
||||
void InitPotions();
|
||||
void InitGlyphs(bool increment = false);
|
||||
void InitFood();
|
||||
void InitMounts();
|
||||
@@ -140,7 +85,6 @@ public:
|
||||
void InitKeyring();
|
||||
void InitReputation();
|
||||
void InitAttunementQuests();
|
||||
void InitPotions();
|
||||
|
||||
private:
|
||||
void Prepare();
|
||||
@@ -177,7 +121,6 @@ private:
|
||||
void InitGuild();
|
||||
void InitArenaTeam();
|
||||
void InitImmersive();
|
||||
void AddConsumables();
|
||||
static void AddPrevQuests(uint32 questId, std::list<uint32>& questIds);
|
||||
void LoadEnchantContainer();
|
||||
void ApplyEnchantTemplate();
|
||||
|
||||
@@ -29,12 +29,12 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto)
|
||||
{
|
||||
if (proto->IsRangedWeapon())
|
||||
{
|
||||
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
||||
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
||||
stats[STATS_TYPE_RANGED_DPS] += val;
|
||||
}
|
||||
else if (proto->IsWeapon())
|
||||
{
|
||||
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
||||
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
||||
stats[STATS_TYPE_MELEE_DPS] += val;
|
||||
}
|
||||
stats[STATS_TYPE_ARMOR] += proto->Armor;
|
||||
@@ -436,10 +436,10 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
|
||||
switch (itemStatType)
|
||||
{
|
||||
case ITEM_MOD_MANA:
|
||||
stats[STATS_TYPE_MANA_REGENERATION] += val / 10;
|
||||
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / 10;
|
||||
break;
|
||||
case ITEM_MOD_HEALTH:
|
||||
stats[STATS_TYPE_STAMINA] += val / 15;
|
||||
stats[STATS_TYPE_STAMINA] += (float)val / 15;
|
||||
break;
|
||||
case ITEM_MOD_AGILITY:
|
||||
stats[STATS_TYPE_AGILITY] += val;
|
||||
@@ -747,11 +747,11 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
|
||||
}
|
||||
}
|
||||
|
||||
int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
|
||||
float StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
|
||||
{
|
||||
// float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
|
||||
int32 basePoints = effectInfo.BasePoints;
|
||||
int32 randomPoints = int32(effectInfo.DieSides);
|
||||
float basePoints = effectInfo.BasePoints;
|
||||
int32 randomPoints = effectInfo.DieSides;
|
||||
|
||||
switch (randomPoints)
|
||||
{
|
||||
@@ -761,7 +761,7 @@ int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
|
||||
basePoints += 1;
|
||||
break;
|
||||
default:
|
||||
int32 randvalue = (1 + randomPoints) / 2;
|
||||
float randvalue = (1 + randomPoints) / 2.0f;
|
||||
basePoints += randvalue;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
bool CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict = true);
|
||||
|
||||
public:
|
||||
int32 stats[STATS_TYPE_MAX];
|
||||
float stats[STATS_TYPE_MAX];
|
||||
|
||||
private:
|
||||
void CollectByItemStatType(uint32 itemStatType, int32 val);
|
||||
@@ -80,7 +80,7 @@ private:
|
||||
|
||||
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
|
||||
uint32 triggerCooldown);
|
||||
int32 AverageValue(const SpellEffectInfo& effectInfo);
|
||||
float AverageValue(const SpellEffectInfo& effectInfo);
|
||||
|
||||
private:
|
||||
CollectorType type_;
|
||||
|
||||
@@ -33,6 +33,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
|
||||
else
|
||||
type_ = CollectorType::RANGED;
|
||||
cls = player->getClass();
|
||||
lvl = player->GetLevel();
|
||||
tab = AiFactory::GetPlayerSpecTab(player);
|
||||
collector_ = std::make_unique<StatsCollector>(type_, cls);
|
||||
|
||||
@@ -70,7 +71,7 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId, int32 randomPropertyId
|
||||
Reset();
|
||||
|
||||
collector_->CollectItemStats(proto);
|
||||
|
||||
|
||||
if (randomPropertyIds != 0)
|
||||
CalculateRandomProperty(randomPropertyIds, itemId);
|
||||
|
||||
@@ -181,6 +182,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_ARMOR] += 0.001f;
|
||||
stats_weights_[STATS_TYPE_BONUS] += 1.0f;
|
||||
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
|
||||
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
|
||||
|
||||
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL))
|
||||
{
|
||||
@@ -529,13 +531,13 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
|
||||
if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
|
||||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
|
||||
player_->CanDualWield()) ||
|
||||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
|
||||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)))
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
}
|
||||
|
||||
}
|
||||
// spec with double hand
|
||||
// fury without duel wield, arms, bear, retribution, blood dk
|
||||
@@ -551,15 +553,11 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
weight_ *= 0.1;
|
||||
}
|
||||
// caster's main hand (cannot duel weapon but can equip two-hands stuff)
|
||||
if (cls == CLASS_MAGE ||
|
||||
cls == CLASS_PRIEST ||
|
||||
cls == CLASS_WARLOCK ||
|
||||
cls == CLASS_DRUID ||
|
||||
if (cls == CLASS_MAGE || cls == CLASS_PRIEST || cls == CLASS_WARLOCK || cls == CLASS_DRUID ||
|
||||
(cls == CLASS_SHAMAN && !player_->CanDualWield()))
|
||||
{
|
||||
weight_ *= 0.65;
|
||||
}
|
||||
|
||||
}
|
||||
// fury with titan's grip
|
||||
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM ||
|
||||
@@ -568,16 +566,16 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
}
|
||||
|
||||
|
||||
if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN)
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
}
|
||||
|
||||
if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
|
||||
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
|
||||
if (lvl >= 10 && cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
|
||||
proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
|
||||
{
|
||||
weight_ *= 0.5;
|
||||
weight_ *= 1.5;
|
||||
}
|
||||
|
||||
if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
|
||||
@@ -660,7 +658,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
else
|
||||
validPoints = 0;
|
||||
}
|
||||
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], (int)validPoints);
|
||||
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], validPoints);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -677,8 +675,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
else
|
||||
validPoints = 0;
|
||||
|
||||
collector_->stats[STATS_TYPE_EXPERTISE] =
|
||||
std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
|
||||
collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], validPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +692,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
else
|
||||
validPoints = 0;
|
||||
|
||||
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], (int)validPoints);
|
||||
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], validPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,7 +711,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
validPoints = 0;
|
||||
|
||||
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] =
|
||||
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
|
||||
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], validPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ private:
|
||||
CollectorType hitOverflowType_;
|
||||
std::unique_ptr<StatsCollector> collector_;
|
||||
uint8 cls;
|
||||
uint8 lvl;
|
||||
int tab;
|
||||
bool enable_overflow_penalty_;
|
||||
bool enable_item_set_bonus_;
|
||||
|
||||
@@ -8,39 +8,89 @@
|
||||
#include "ActionContext.h"
|
||||
#include "ChatActionContext.h"
|
||||
#include "ChatTriggerContext.h"
|
||||
#include "DKAiObjectContext.h"
|
||||
#include "DruidAiObjectContext.h"
|
||||
#include "HunterAiObjectContext.h"
|
||||
#include "MageAiObjectContext.h"
|
||||
#include "PaladinAiObjectContext.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RaidUlduarTriggerContext.h"
|
||||
#include "PriestAiObjectContext.h"
|
||||
#include "RaidUlduarActionContext.h"
|
||||
#include "RaidUlduarTriggerContext.h"
|
||||
#include "RogueAiObjectContext.h"
|
||||
#include "ShamanAiObjectContext.h"
|
||||
#include "SharedValueContext.h"
|
||||
#include "StrategyContext.h"
|
||||
#include "TriggerContext.h"
|
||||
#include "ValueContext.h"
|
||||
#include "WarlockAiObjectContext.h"
|
||||
#include "WarriorAiObjectContext.h"
|
||||
#include "WorldPacketActionContext.h"
|
||||
#include "WorldPacketTriggerContext.h"
|
||||
#include "raids/RaidStrategyContext.h"
|
||||
#include "raids/blackwinglair/RaidBwlActionContext.h"
|
||||
#include "raids/blackwinglair/RaidBwlTriggerContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxActionContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
|
||||
#include "raids/icecrown/RaidIccActionContext.h"
|
||||
#include "raids/icecrown/RaidIccTriggerContext.h"
|
||||
#include "raids/obsidiansanctum/RaidOsActionContext.h"
|
||||
#include "raids/obsidiansanctum/RaidOsTriggerContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoEActionContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoATriggerContext.h"
|
||||
#include "raids/onyxia/RaidOnyxiaActionContext.h"
|
||||
#include "raids/onyxia/RaidOnyxiaTriggerContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
|
||||
#include "raids/moltencore/RaidMcActionContext.h"
|
||||
#include "raids/moltencore/RaidMcTriggerContext.h"
|
||||
#include "raids/aq20/RaidAq20ActionContext.h"
|
||||
#include "raids/aq20/RaidAq20TriggerContext.h"
|
||||
#include "dungeons/DungeonStrategyContext.h"
|
||||
#include "dungeons/wotlk/WotlkDungeonActionContext.h"
|
||||
#include "dungeons/wotlk/WotlkDungeonTriggerContext.h"
|
||||
#include "raids/RaidStrategyContext.h"
|
||||
#include "raids/aq20/RaidAq20ActionContext.h"
|
||||
#include "raids/aq20/RaidAq20TriggerContext.h"
|
||||
#include "raids/blackwinglair/RaidBwlActionContext.h"
|
||||
#include "raids/blackwinglair/RaidBwlTriggerContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoEActionContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
|
||||
#include "raids/icecrown/RaidIccActionContext.h"
|
||||
#include "raids/icecrown/RaidIccTriggerContext.h"
|
||||
#include "raids/moltencore/RaidMcActionContext.h"
|
||||
#include "raids/moltencore/RaidMcTriggerContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxActionContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
|
||||
#include "raids/obsidiansanctum/RaidOsActionContext.h"
|
||||
#include "raids/obsidiansanctum/RaidOsTriggerContext.h"
|
||||
#include "raids/onyxia/RaidOnyxiaActionContext.h"
|
||||
#include "raids/onyxia/RaidOnyxiaTriggerContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoATriggerContext.h"
|
||||
|
||||
AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
SharedNamedObjectContextList<Strategy> AiObjectContext::sharedStrategyContexts;
|
||||
SharedNamedObjectContextList<Action> AiObjectContext::sharedActionContexts;
|
||||
SharedNamedObjectContextList<Trigger> AiObjectContext::sharedTriggerContexts;
|
||||
SharedNamedObjectContextList<UntypedValue> AiObjectContext::sharedValueContexts;
|
||||
|
||||
AiObjectContext::AiObjectContext(PlayerbotAI* botAI, SharedNamedObjectContextList<Strategy>& sharedStrategyContext,
|
||||
SharedNamedObjectContextList<Action>& sharedActionContext,
|
||||
SharedNamedObjectContextList<Trigger>& sharedTriggerContext,
|
||||
SharedNamedObjectContextList<UntypedValue>& sharedValueContext)
|
||||
: PlayerbotAIAware(botAI),
|
||||
strategyContexts(sharedStrategyContext),
|
||||
actionContexts(sharedActionContext),
|
||||
triggerContexts(sharedTriggerContext),
|
||||
valueContexts(sharedValueContext)
|
||||
{
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildAllSharedContexts()
|
||||
{
|
||||
AiObjectContext::BuildSharedContexts();
|
||||
PriestAiObjectContext::BuildSharedContexts();
|
||||
MageAiObjectContext::BuildSharedContexts();
|
||||
WarlockAiObjectContext::BuildSharedContexts();
|
||||
WarriorAiObjectContext::BuildSharedContexts();
|
||||
ShamanAiObjectContext::BuildSharedContexts();
|
||||
PaladinAiObjectContext::BuildSharedContexts();
|
||||
DruidAiObjectContext::BuildSharedContexts();
|
||||
HunterAiObjectContext::BuildSharedContexts();
|
||||
RogueAiObjectContext::BuildSharedContexts();
|
||||
DKAiObjectContext::BuildSharedContexts();
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedContexts()
|
||||
{
|
||||
BuildSharedStrategyContexts(sharedStrategyContexts);
|
||||
BuildSharedActionContexts(sharedActionContexts);
|
||||
BuildSharedTriggerContexts(sharedTriggerContexts);
|
||||
BuildSharedValueContexts(sharedValueContexts);
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts)
|
||||
{
|
||||
strategyContexts.Add(new StrategyContext());
|
||||
strategyContexts.Add(new MovementStrategyContext());
|
||||
@@ -48,7 +98,10 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
strategyContexts.Add(new QuestStrategyContext());
|
||||
strategyContexts.Add(new RaidStrategyContext());
|
||||
strategyContexts.Add(new DungeonStrategyContext());
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)
|
||||
{
|
||||
actionContexts.Add(new ActionContext());
|
||||
actionContexts.Add(new ChatActionContext());
|
||||
actionContexts.Add(new WorldPacketActionContext());
|
||||
@@ -77,7 +130,10 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
actionContexts.Add(new WotlkDungeonFoSActionContext());
|
||||
actionContexts.Add(new WotlkDungeonPoSActionContext());
|
||||
actionContexts.Add(new WotlkDungeonToCActionContext());
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts)
|
||||
{
|
||||
triggerContexts.Add(new TriggerContext());
|
||||
triggerContexts.Add(new ChatTriggerContext());
|
||||
triggerContexts.Add(new WorldPacketTriggerContext());
|
||||
@@ -106,26 +162,11 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
triggerContexts.Add(new WotlkDungeonFoSTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonPoSTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
|
||||
{
|
||||
valueContexts.Add(new ValueContext());
|
||||
|
||||
valueContexts.Add(sSharedValueContext);
|
||||
}
|
||||
|
||||
void AiObjectContext::Update()
|
||||
{
|
||||
strategyContexts.Update();
|
||||
triggerContexts.Update();
|
||||
actionContexts.Update();
|
||||
valueContexts.Update();
|
||||
}
|
||||
|
||||
void AiObjectContext::Reset()
|
||||
{
|
||||
strategyContexts.Reset();
|
||||
triggerContexts.Reset();
|
||||
actionContexts.Reset();
|
||||
valueContexts.Reset();
|
||||
}
|
||||
|
||||
std::vector<std::string> AiObjectContext::Save()
|
||||
@@ -218,5 +259,3 @@ std::string const AiObjectContext::FormatValues()
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void AiObjectContext::AddShared(NamedObjectContext<UntypedValue>* sharedValues) { valueContexts.Add(sharedValues); }
|
||||
|
||||
@@ -19,10 +19,20 @@
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
typedef Strategy* (*StrategyCreator)(PlayerbotAI* botAI);
|
||||
typedef Action* (*ActionCreator)(PlayerbotAI* botAI);
|
||||
typedef Trigger* (*TriggerCreator)(PlayerbotAI* botAI);
|
||||
typedef UntypedValue* (*ValueCreator)(PlayerbotAI* botAI);
|
||||
|
||||
class AiObjectContext : public PlayerbotAIAware
|
||||
{
|
||||
public:
|
||||
AiObjectContext(PlayerbotAI* botAI);
|
||||
static BoolCalculatedValue* custom_glyphs(PlayerbotAI* ai); // Added for cutom glyphs
|
||||
AiObjectContext(PlayerbotAI* botAI,
|
||||
SharedNamedObjectContextList<Strategy>& sharedStrategyContext = sharedStrategyContexts,
|
||||
SharedNamedObjectContextList<Action>& sharedActionContext = sharedActionContexts,
|
||||
SharedNamedObjectContextList<Trigger>& sharedTriggerContext = sharedTriggerContexts,
|
||||
SharedNamedObjectContextList<UntypedValue>& sharedValueContext = sharedValueContexts);
|
||||
virtual ~AiObjectContext() {}
|
||||
|
||||
virtual Strategy* GetStrategy(std::string const name);
|
||||
@@ -56,20 +66,30 @@ public:
|
||||
std::set<std::string> GetSupportedActions();
|
||||
std::string const FormatValues();
|
||||
|
||||
virtual void Update();
|
||||
virtual void Reset();
|
||||
virtual void AddShared(NamedObjectContext<UntypedValue>* sharedValues);
|
||||
|
||||
std::vector<std::string> Save();
|
||||
void Load(std::vector<std::string> data);
|
||||
|
||||
std::vector<std::string> performanceStack;
|
||||
|
||||
static void BuildAllSharedContexts();
|
||||
|
||||
static void BuildSharedContexts();
|
||||
static void BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts);
|
||||
static void BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts);
|
||||
static void BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts);
|
||||
static void BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts);
|
||||
|
||||
protected:
|
||||
NamedObjectContextList<Strategy> strategyContexts;
|
||||
NamedObjectContextList<Action> actionContexts;
|
||||
NamedObjectContextList<Trigger> triggerContexts;
|
||||
NamedObjectContextList<UntypedValue> valueContexts;
|
||||
|
||||
private:
|
||||
static SharedNamedObjectContextList<Strategy> sharedStrategyContexts;
|
||||
static SharedNamedObjectContextList<Action> sharedActionContexts;
|
||||
static SharedNamedObjectContextList<Trigger> sharedTriggerContexts;
|
||||
static SharedNamedObjectContextList<UntypedValue> sharedValueContexts;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Playerbots.h"
|
||||
#include "Queue.h"
|
||||
#include "Strategy.h"
|
||||
#include "Timer.h"
|
||||
|
||||
Engine::Engine(PlayerbotAI* botAI, AiObjectContext* factory) : PlayerbotAIAware(botAI), aiObjectContext(factory)
|
||||
{
|
||||
@@ -108,6 +109,8 @@ void Engine::Reset()
|
||||
}
|
||||
|
||||
multipliers.clear();
|
||||
|
||||
actionNodeFactories.creators.clear();
|
||||
}
|
||||
|
||||
void Engine::Init()
|
||||
@@ -120,9 +123,10 @@ void Engine::Init()
|
||||
strategyTypeMask |= strategy->GetType();
|
||||
strategy->InitMultipliers(multipliers);
|
||||
strategy->InitTriggers(triggers);
|
||||
|
||||
Event emptyEvent;
|
||||
MultiplyAndPush(strategy->getDefaultActions(), 0.0f, false, emptyEvent, "default");
|
||||
for (auto &iter : strategy->actionNodeFactories.creators)
|
||||
{
|
||||
actionNodeFactories.creators[iter.first] = iter.second;
|
||||
}
|
||||
}
|
||||
|
||||
if (testMode)
|
||||
@@ -248,11 +252,9 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
|
||||
|
||||
ActionNode* Engine::CreateActionNode(std::string const name)
|
||||
{
|
||||
for (std::map<std::string, Strategy*>::iterator i = strategies.begin(); i != strategies.end(); i++)
|
||||
{
|
||||
if (ActionNode* node = i->second->GetAction(name))
|
||||
return node;
|
||||
}
|
||||
ActionNode* node = actionNodeFactories.GetContextObject(name, botAI);
|
||||
if (node)
|
||||
return node;
|
||||
|
||||
return new ActionNode(name,
|
||||
/*P*/ nullptr,
|
||||
@@ -432,6 +434,7 @@ bool Engine::HasStrategy(std::string const name) { return strategies.find(name)
|
||||
void Engine::ProcessTriggers(bool minimal)
|
||||
{
|
||||
std::unordered_map<Trigger*, Event> fires;
|
||||
uint32 now = getMSTime();
|
||||
for (std::vector<TriggerNode*>::iterator i = triggers.begin(); i != triggers.end(); i++)
|
||||
{
|
||||
TriggerNode* node = *i;
|
||||
@@ -451,7 +454,7 @@ void Engine::ProcessTriggers(bool minimal)
|
||||
if (fires.find(trigger) != fires.end())
|
||||
continue;
|
||||
|
||||
if (testMode || trigger->needCheck())
|
||||
if (testMode || trigger->needCheck(now))
|
||||
{
|
||||
if (minimal && node->getFirstRelevance() < 100)
|
||||
continue;
|
||||
|
||||
@@ -114,6 +114,7 @@ protected:
|
||||
float lastRelevance;
|
||||
std::string lastAction;
|
||||
uint32 strategyTypeMask;
|
||||
NamedObjectFactoryList<ActionNode> actionNodeFactories;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,10 +14,10 @@ void Qualified::Qualify(int qual)
|
||||
qualifier = out.str();
|
||||
}
|
||||
|
||||
std::string const Qualified::MultiQualify(std::vector<std::string> qualifiers, const std::string& separator, const std::string_view brackets)
|
||||
std::string const Qualified::MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator, const std::string_view brackets)
|
||||
{
|
||||
std::stringstream out;
|
||||
for (uint8 i = 0; i < qualifiers.size(); i++)
|
||||
for (uint8 i = 0; i < qualifiers.size(); ++i)
|
||||
{
|
||||
const std::string& qualifier = qualifiers[i];
|
||||
if (i == qualifiers.size() - 1)
|
||||
@@ -40,13 +40,13 @@ std::string const Qualified::MultiQualify(std::vector<std::string> qualifiers, c
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Qualified::getMultiQualifiers(std::string const qualifier1)
|
||||
std::vector<std::string> Qualified::getMultiQualifiers(const std::string& qualifier1)
|
||||
{
|
||||
std::istringstream iss(qualifier1);
|
||||
return {std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}};
|
||||
}
|
||||
|
||||
int32 Qualified::getMultiQualifier(std::string const qualifier1, uint32 pos)
|
||||
int32 Qualified::getMultiQualifier(const std::string& qualifier1, uint32 pos)
|
||||
{
|
||||
return std::stoi(getMultiQualifiers(qualifier1)[pos]);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
@@ -29,9 +30,10 @@ public:
|
||||
|
||||
std::string const getQualifier() { return qualifier; }
|
||||
|
||||
static std::string const MultiQualify(std::vector<std::string> qualifiers, const std::string& separator, const std::string_view brackets = "{}");
|
||||
static std::vector<std::string> getMultiQualifiers(std::string const qualifier1);
|
||||
static int32 getMultiQualifier(std::string const qualifier1, uint32 pos);
|
||||
static std::string const MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator,
|
||||
const std::string_view brackets = "{}");
|
||||
static std::vector<std::string> getMultiQualifiers(const std::string& qualifier1);
|
||||
static int32 getMultiQualifier(const std::string& qualifier1, uint32 pos);
|
||||
|
||||
protected:
|
||||
std::string qualifier;
|
||||
@@ -40,12 +42,12 @@ protected:
|
||||
template <class T>
|
||||
class NamedObjectFactory
|
||||
{
|
||||
protected:
|
||||
typedef T* (*ActionCreator)(PlayerbotAI* botAI);
|
||||
std::unordered_map<std::string, ActionCreator> creators;
|
||||
public:
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
|
||||
public:
|
||||
T* create(std::string name, PlayerbotAI* botAI)
|
||||
virtual T* create(std::string name, PlayerbotAI* botAI)
|
||||
{
|
||||
size_t found = name.find("::");
|
||||
std::string qualifier;
|
||||
@@ -58,11 +60,9 @@ public:
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
ActionCreator creator = creators[name];
|
||||
if (!creator)
|
||||
return nullptr;
|
||||
ObjectCreator& creator = creators[name];
|
||||
|
||||
T* object = (*creator)(botAI);
|
||||
T* object = creator(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
std::set<std::string> supports()
|
||||
{
|
||||
std::set<std::string> keys;
|
||||
for (typename std::unordered_map<std::string, ActionCreator>::iterator it = creators.begin();
|
||||
for (typename std::unordered_map<std::string, ObjectCreator>::const_iterator it = creators.begin();
|
||||
it != creators.end(); it++)
|
||||
keys.insert(it->first);
|
||||
|
||||
@@ -92,7 +92,7 @@ public:
|
||||
|
||||
virtual ~NamedObjectContext() { Clear(); }
|
||||
|
||||
T* create(std::string const name, PlayerbotAI* botAI)
|
||||
virtual T* create(std::string name, PlayerbotAI* botAI) override
|
||||
{
|
||||
if (created.find(name) == created.end())
|
||||
return created[name] = NamedObjectFactory<T>::create(name, botAI);
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
|
||||
void Clear()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
delete i->second;
|
||||
@@ -111,24 +111,6 @@ public:
|
||||
created.clear();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
i->second->Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
i->second->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsShared() { return shared; }
|
||||
bool IsSupportsSiblings() { return supportsSiblings; }
|
||||
|
||||
@@ -147,53 +129,91 @@ protected:
|
||||
bool supportsSiblings;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class SharedNamedObjectContextList
|
||||
{
|
||||
public:
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
std::vector<NamedObjectContext<T>*> contexts;
|
||||
|
||||
~SharedNamedObjectContextList()
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::const_iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void Add(NamedObjectContext<T>* context)
|
||||
{
|
||||
contexts.push_back(context);
|
||||
for (const auto& iter : context->creators)
|
||||
{
|
||||
creators[iter.first] = iter.second;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class NamedObjectContextList
|
||||
{
|
||||
public:
|
||||
virtual ~NamedObjectContextList()
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
const std::unordered_map<std::string, ObjectCreator>& creators;
|
||||
const std::vector<NamedObjectContext<T>*>& contexts;
|
||||
std::unordered_map<std::string, T*> created;
|
||||
|
||||
NamedObjectContextList(const SharedNamedObjectContextList<T>& shared)
|
||||
: creators(shared.creators), contexts(shared.contexts)
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
NamedObjectContext<T>* context = *i;
|
||||
if (!context->IsShared())
|
||||
delete context;
|
||||
}
|
||||
}
|
||||
|
||||
void Add(NamedObjectContext<T>* context) { contexts.push_back(context); }
|
||||
|
||||
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
|
||||
~NamedObjectContextList()
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (T* object = (*i)->create(name, botAI))
|
||||
return object;
|
||||
if (i->second)
|
||||
delete i->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
created.clear();
|
||||
}
|
||||
|
||||
void Update()
|
||||
T* create(std::string name, PlayerbotAI* botAI)
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
size_t found = name.find("::");
|
||||
std::string qualifier;
|
||||
if (found != std::string::npos)
|
||||
{
|
||||
if (!(*i)->IsShared())
|
||||
(*i)->Update();
|
||||
qualifier = name.substr(found + 2);
|
||||
name = name.substr(0, found);
|
||||
}
|
||||
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
const ObjectCreator& creator = creators.at(name);
|
||||
|
||||
T* object = creator(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
T* GetContextObject(const std::string& name, PlayerbotAI* botAI)
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
if (created.find(name) == created.end())
|
||||
{
|
||||
(*i)->Reset();
|
||||
if (T* object = create(name, botAI))
|
||||
return created[name] = object;
|
||||
}
|
||||
return created[name];
|
||||
}
|
||||
|
||||
std::set<std::string> GetSiblings(std::string const name)
|
||||
std::set<std::string> GetSiblings(const std::string& name)
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (auto i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
if (!(*i)->IsSupportsSiblings())
|
||||
continue;
|
||||
@@ -213,11 +233,11 @@ public:
|
||||
std::set<std::string> supports()
|
||||
{
|
||||
std::set<std::string> result;
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (auto i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
std::set<std::string> supported = (*i)->supports();
|
||||
|
||||
for (std::set<std::string>::iterator j = supported.begin(); j != supported.end(); j++)
|
||||
for (std::set<std::string>::const_iterator j = supported.begin(); j != supported.end(); ++j)
|
||||
result.insert(*j);
|
||||
}
|
||||
|
||||
@@ -227,46 +247,67 @@ public:
|
||||
std::set<std::string> GetCreated()
|
||||
{
|
||||
std::set<std::string> result;
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
std::set<std::string> createdKeys = (*i)->GetCreated();
|
||||
|
||||
for (std::set<std::string>::iterator j = createdKeys.begin(); j != createdKeys.end(); j++)
|
||||
result.insert(*j);
|
||||
result.insert(i->first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<NamedObjectContext<T>*> contexts;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class NamedObjectFactoryList
|
||||
{
|
||||
public:
|
||||
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||
std::vector<NamedObjectFactory<T>*> factories;
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
|
||||
virtual ~NamedObjectFactoryList()
|
||||
{
|
||||
for (typename std::list<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
|
||||
for (typename std::vector<NamedObjectFactory<T>*>::const_iterator i = factories.begin(); i != factories.end(); i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void Add(NamedObjectFactory<T>* context) { factories.push_front(context); }
|
||||
|
||||
T* GetContextObject(std::string const& name, PlayerbotAI* botAI)
|
||||
T* create(std::string name, PlayerbotAI* botAI)
|
||||
{
|
||||
for (typename std::list<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
|
||||
size_t found = name.find("::");
|
||||
std::string qualifier;
|
||||
if (found != std::string::npos)
|
||||
{
|
||||
if (T* object = (*i)->create(name, botAI))
|
||||
return object;
|
||||
qualifier = name.substr(found + 2);
|
||||
name = name.substr(0, found);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
const ObjectCreator& creator = creators[name];
|
||||
|
||||
T* object = creator(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<NamedObjectFactory<T>*> factories;
|
||||
void Add(NamedObjectFactory<T>* context)
|
||||
{
|
||||
factories.push_back(context);
|
||||
for (const auto& iter : context->creators)
|
||||
{
|
||||
creators[iter.first] = iter.second;
|
||||
}
|
||||
}
|
||||
|
||||
T* GetContextObject(const std::string& name, PlayerbotAI* botAI)
|
||||
{
|
||||
if (T* object = create(name, botAI))
|
||||
return object;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -41,6 +41,7 @@ enum StrategyType : uint32
|
||||
// };
|
||||
|
||||
static float ACTION_IDLE = 0.0f;
|
||||
static float ACTION_BG = 1.0f;
|
||||
static float ACTION_DEFAULT = 5.0f;
|
||||
static float ACTION_NORMAL = 10.0f;
|
||||
static float ACTION_HIGH = 20.0f;
|
||||
@@ -68,7 +69,7 @@ public:
|
||||
void Update() {}
|
||||
void Reset() {}
|
||||
|
||||
protected:
|
||||
public:
|
||||
NamedObjectFactoryList<ActionNode> actionNodeFactories;
|
||||
};
|
||||
|
||||
|
||||
@@ -32,12 +32,11 @@ Value<Unit*>* Trigger::GetTargetValue() { return context->GetValue<Unit*>(GetTar
|
||||
|
||||
Unit* Trigger::GetTarget() { return GetTargetValue()->Get(); }
|
||||
|
||||
bool Trigger::needCheck()
|
||||
bool Trigger::needCheck(uint32 now)
|
||||
{
|
||||
if (checkInterval < 2)
|
||||
return true;
|
||||
|
||||
uint32 now = getMSTime();
|
||||
if (!lastCheckTime || now - lastCheckTime >= checkInterval)
|
||||
{
|
||||
lastCheckTime = now;
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
virtual Value<Unit*>* GetTargetValue();
|
||||
virtual std::string const GetTargetName() { return "self target"; }
|
||||
|
||||
bool needCheck();
|
||||
bool needCheck(uint32 now);
|
||||
|
||||
protected:
|
||||
int32 checkInterval;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "InviteToGroupAction.h"
|
||||
#include "LeaveGroupAction.h"
|
||||
#include "LootAction.h"
|
||||
#include "LootRollAction.h"
|
||||
#include "MoveToRpgTargetAction.h"
|
||||
#include "MoveToTravelTargetAction.h"
|
||||
#include "MovementActions.h"
|
||||
@@ -63,6 +64,7 @@
|
||||
#include "WorldBuffAction.h"
|
||||
#include "XpGainAction.h"
|
||||
#include "NewRpgAction.h"
|
||||
#include "CancelChannelAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
@@ -189,10 +191,13 @@ public:
|
||||
creators["buy tabard"] = &ActionContext::buy_tabard;
|
||||
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
|
||||
creators["clean quest log"] = &ActionContext::clean_quest_log;
|
||||
creators["roll"] = &ActionContext::roll_action;
|
||||
creators["cancel channel"] = &ActionContext::cancel_channel;
|
||||
|
||||
// BG Tactics
|
||||
creators["bg tactics"] = &ActionContext::bg_tactics;
|
||||
creators["bg move to start"] = &ActionContext::bg_move_to_start;
|
||||
creators["bg reset objective force"] = &ActionContext::bg_reset_objective_force;
|
||||
creators["bg move to objective"] = &ActionContext::bg_move_to_objective;
|
||||
creators["bg select objective"] = &ActionContext::bg_select_objective;
|
||||
creators["bg check objective"] = &ActionContext::bg_check_objective;
|
||||
@@ -241,14 +246,16 @@ public:
|
||||
creators["rpg mount anim"] = &ActionContext::rpg_mount_anim;
|
||||
|
||||
creators["toggle pet spell"] = &ActionContext::toggle_pet_spell;
|
||||
creators["pet attack"] = &ActionContext::pet_attack;
|
||||
creators["pet attack"] = &ActionContext::pet_attack;
|
||||
creators["set pet stance"] = &ActionContext::set_pet_stance;
|
||||
|
||||
creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
|
||||
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
|
||||
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 go camp"] = &ActionContext::new_rpg_go_camp;
|
||||
creators["new rpg wander random"] = &ActionContext::new_rpg_wander_random;
|
||||
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
|
||||
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
|
||||
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -296,6 +303,7 @@ private:
|
||||
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
|
||||
static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); }
|
||||
static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); }
|
||||
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
|
||||
|
||||
static Action* emote(PlayerbotAI* botAI) { return new EmoteAction(botAI); }
|
||||
static Action* talk(PlayerbotAI* botAI) { return new TalkAction(botAI); }
|
||||
@@ -372,10 +380,12 @@ private:
|
||||
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
|
||||
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
|
||||
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
|
||||
// BG Tactics
|
||||
static Action* bg_tactics(PlayerbotAI* botAI) { return new BGTactics(botAI); }
|
||||
static Action* bg_move_to_start(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to start"); }
|
||||
static Action* bg_reset_objective_force(PlayerbotAI* botAI) { return new BGTactics(botAI, "reset objective force"); }
|
||||
static Action* bg_move_to_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to objective"); }
|
||||
static Action* bg_select_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "select objective"); }
|
||||
static Action* bg_check_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "check objective"); }
|
||||
@@ -425,13 +435,15 @@ private:
|
||||
|
||||
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); }
|
||||
static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); }
|
||||
static Action* set_pet_stance(PlayerbotAI* ai) { return new SetPetStanceAction(ai); }
|
||||
|
||||
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
|
||||
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
|
||||
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_go_camp(PlayerbotAI* ai) { return new NewRpgGoCampAction(ai); }
|
||||
static Action* new_rpg_wander_random(PlayerbotAI* ai) { return new NewRpgWanderRandomAction(ai); }
|
||||
static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); }
|
||||
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
|
||||
static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -59,10 +59,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
|
||||
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
|
||||
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
|
||||
// there's no reason to do attack again
|
||||
if (sameTarget && inCombat && sameAttackMode)
|
||||
return false;
|
||||
|
||||
|
||||
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
|
||||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||
{
|
||||
@@ -82,52 +79,53 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
||||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
|
||||
&& (target->IsPlayer() || target->IsPet()))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot->IsFriendlyTo(target))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target->isDead())
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->IsWithinLOSInMap(target))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sameTarget && inCombat && sameAttackMode)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->IsValidAttackTarget(target))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack an invalid target");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream msg;
|
||||
msg << target->GetName();
|
||||
|
||||
if (bot->IsFriendlyTo(target))
|
||||
{
|
||||
msg << " is friendly to me";
|
||||
if (verbose)
|
||||
botAI->TellError(msg.str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->IsWithinLOSInMap(target))
|
||||
{
|
||||
msg << " is not in my sight";
|
||||
if (verbose)
|
||||
botAI->TellError(msg.str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target->isDead())
|
||||
{
|
||||
msg << " is dead";
|
||||
if (verbose)
|
||||
botAI->TellError(msg.str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId())
|
||||
&& (target->IsPlayer() || target->IsPet()))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack others in PvP prohibited zones");
|
||||
botAI->TellError("I cannot attack an invalid target.");
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -141,9 +139,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
ObjectGuid guid = target->GetGUID();
|
||||
bot->SetSelection(target->GetGUID());
|
||||
|
||||
|
||||
|
||||
context->GetValue<Unit*>("old target")->Set(oldTarget);
|
||||
context->GetValue<Unit*>("old target")->Set(oldTarget);
|
||||
|
||||
context->GetValue<Unit*>("current target")->Set(target);
|
||||
context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid);
|
||||
@@ -157,7 +153,6 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
|
||||
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
{
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
|
||||
@@ -157,15 +157,23 @@ void AutoMaintenanceOnLevelupAction::LearnSpell(uint32 spellId, std::ostringstre
|
||||
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
||||
{
|
||||
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
|
||||
// Clean up old consumables before adding new ones
|
||||
factory.CleanupConsumables();
|
||||
|
||||
factory.InitAmmo();
|
||||
factory.InitReagents();
|
||||
factory.InitFood();
|
||||
factory.InitConsumables();
|
||||
factory.InitPotions();
|
||||
|
||||
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
|
||||
{
|
||||
factory.InitEquipment(true);
|
||||
if (sPlayerbotAIConfig->incrementalGearInit)
|
||||
factory.InitEquipment(true);
|
||||
}
|
||||
factory.InitAmmo();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
#ifndef _PLAYERBOT_BATTLEGROUNDTACTICSACTION_H
|
||||
#define _PLAYERBOT_BATTLEGROUNDTACTICSACTION_H
|
||||
|
||||
#include "BattlegroundAV.h"
|
||||
#include "MovementActions.h"
|
||||
|
||||
class ChatHandler;
|
||||
@@ -15,8 +16,49 @@ struct Position;
|
||||
|
||||
#define SPELL_CAPTURE_BANNER 21651
|
||||
|
||||
enum WSBotStrategy : uint8
|
||||
{
|
||||
WS_STRATEGY_BALANCED = 0,
|
||||
WS_STRATEGY_OFFENSIVE = 1,
|
||||
WS_STRATEGY_DEFENSIVE = 2,
|
||||
WS_STRATEGY_MAX = 3,
|
||||
};
|
||||
|
||||
enum ABBotStrategy : uint8
|
||||
{
|
||||
AB_STRATEGY_BALANCED = 0,
|
||||
AB_STRATEGY_OFFENSIVE = 1,
|
||||
AB_STRATEGY_DEFENSIVE = 2,
|
||||
AB_STRATEGY_MAX = 3,
|
||||
};
|
||||
|
||||
enum AVBotStrategy : uint8
|
||||
{
|
||||
AV_STRATEGY_BALANCED = 0,
|
||||
AV_STRATEGY_OFFENSIVE = 1,
|
||||
AV_STRATEGY_DEFENSIVE = 2,
|
||||
AV_STRATEGY_MAX = 3,
|
||||
};
|
||||
|
||||
enum EYBotStrategy : uint8
|
||||
{
|
||||
EY_STRATEGY_BALANCED = 0,
|
||||
EY_STRATEGY_FRONT_FOCUS = 1,
|
||||
EY_STRATEGY_BACK_FOCUS = 2,
|
||||
EY_STRATEGY_FLAG_FOCUS = 3,
|
||||
EY_STRATEGY_MAX = 4
|
||||
};
|
||||
|
||||
typedef void (*BattleBotWaypointFunc)();
|
||||
|
||||
struct BGStrategyData
|
||||
{
|
||||
uint8 allianceStrategy = 0;
|
||||
uint8 hordeStrategy = 0;
|
||||
};
|
||||
|
||||
extern std::unordered_map<uint32, BGStrategyData> bgStrategies;
|
||||
|
||||
struct BattleBotWaypoint
|
||||
{
|
||||
BattleBotWaypoint(float x_, float y_, float z_, BattleBotWaypointFunc func) : x(x_), y(y_), z(z_), pFunc(func){};
|
||||
@@ -27,6 +69,31 @@ struct BattleBotWaypoint
|
||||
BattleBotWaypointFunc pFunc = nullptr;
|
||||
};
|
||||
|
||||
struct AVNodePositionData
|
||||
{
|
||||
Position pos;
|
||||
float maxRadius;
|
||||
};
|
||||
|
||||
// Added to fix bot stuck at objectives
|
||||
static std::unordered_map<uint8, AVNodePositionData> AVNodeMovementTargets = {
|
||||
{BG_AV_NODES_FIRSTAID_STATION, {Position(640.364f, -36.535f, 45.625f), 15.0f}},
|
||||
{BG_AV_NODES_STORMPIKE_GRAVE, {Position(665.598f, -292.976f, 30.291f), 15.0f}},
|
||||
{BG_AV_NODES_STONEHEART_GRAVE, {Position(76.108f, -399.602f, 45.730f), 15.0f}},
|
||||
{BG_AV_NODES_SNOWFALL_GRAVE, {Position(-201.298f, -119.661f, 78.291f), 15.0f}},
|
||||
{BG_AV_NODES_ICEBLOOD_GRAVE, {Position(-617.858f, -400.654f, 59.692f), 15.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_GRAVE, {Position(-1083.803f, -341.520f, 55.304f), 15.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_HUT, {Position(-1405.678f, -309.108f, 89.377f, 0.392f), 10.0f}},
|
||||
{BG_AV_NODES_DUNBALDAR_SOUTH, {Position(556.551f, -77.240f, 51.931f), 0.0f}},
|
||||
{BG_AV_NODES_DUNBALDAR_NORTH, {Position(670.664f, -142.031f, 63.666f), 0.0f}},
|
||||
{BG_AV_NODES_ICEWING_BUNKER, {Position(200.310f, -361.232f, 56.387f), 0.0f}},
|
||||
{BG_AV_NODES_STONEHEART_BUNKER, {Position(-156.302f, -440.032f, 40.403f), 0.0f}},
|
||||
{BG_AV_NODES_ICEBLOOD_TOWER, {Position(-569.702f, -265.362f, 75.009f), 0.0f}},
|
||||
{BG_AV_NODES_TOWER_POINT, {Position(-767.439f, -360.200f, 90.895f), 0.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_ETOWER, {Position(-1303.737f, -314.070f, 113.868f), 0.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_WTOWER, {Position(-1300.648f, -267.356f, 114.151f), 0.0f}},
|
||||
};
|
||||
|
||||
typedef std::vector<BattleBotWaypoint> BattleBotPath;
|
||||
|
||||
extern std::vector<BattleBotPath*> const vPaths_WS;
|
||||
@@ -39,6 +106,7 @@ class BGTactics : public MovementAction
|
||||
{
|
||||
public:
|
||||
static bool HandleConsoleCommand(ChatHandler* handler, char const* args);
|
||||
uint8 static GetBotStrategyForTeam(Battleground* bg, TeamId teamId);
|
||||
|
||||
BGTactics(PlayerbotAI* botAI, std::string const name = "bg tactics") : MovementAction(botAI, name) {}
|
||||
|
||||
@@ -48,13 +116,13 @@ private:
|
||||
static std::string const HandleConsoleCommandPrivate(WorldSession* session, char const* args);
|
||||
bool moveToStart(bool force = false);
|
||||
bool selectObjective(bool reset = false);
|
||||
bool moveToObjective();
|
||||
bool moveToObjective(bool ignoreDist);
|
||||
bool selectObjectiveWp(std::vector<BattleBotPath*> const& vPaths);
|
||||
bool moveToObjectiveWp(BattleBotPath* const& currentPath, uint32 currentPoint, bool reverse = false);
|
||||
bool startNewPathBegin(std::vector<BattleBotPath*> const& vPaths);
|
||||
bool startNewPathFree(std::vector<BattleBotPath*> const& vPaths);
|
||||
bool resetObjective();
|
||||
bool wsgPaths();
|
||||
bool wsJumpDown();
|
||||
bool eyJumpDown();
|
||||
bool atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<uint32> const& vFlagIds);
|
||||
bool flagTaken();
|
||||
|
||||
@@ -22,6 +22,7 @@ bool BossFireResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("fire resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ bool BossFrostResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("frost resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -48,5 +50,20 @@ bool BossNatureResistanceAction::Execute(Event event)
|
||||
{
|
||||
HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("aspect of the wild", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BossShadowResistanceAction::isUseful()
|
||||
{
|
||||
BossShadowResistanceTrigger bossShadowResistanceTrigger(botAI, bossName);
|
||||
return bossShadowResistanceTrigger.IsActive();
|
||||
}
|
||||
|
||||
bool BossShadowResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("shadow resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,4 +54,18 @@ private:
|
||||
std::string bossName;
|
||||
};
|
||||
|
||||
class BossShadowResistanceAction : public Action
|
||||
{
|
||||
public:
|
||||
BossShadowResistanceAction(PlayerbotAI* botAI, std::string const bossName)
|
||||
: Action(botAI, bossName + " shadow resistance action"), bossName(bossName)
|
||||
{
|
||||
}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
std::string bossName;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
18
src/strategy/actions/CancelChannelAction.cpp
Normal file
18
src/strategy/actions/CancelChannelAction.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CancelChannelAction.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
bool CancelChannelAction::Execute(Event event)
|
||||
{
|
||||
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_CHANNELED_SPELL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
21
src/strategy/actions/CancelChannelAction.h
Normal file
21
src/strategy/actions/CancelChannelAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CANCELCHANNELACTION_H
|
||||
#define _PLAYERBOT_CANCELCHANNELACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class CancelChannelAction : public Action
|
||||
{
|
||||
public:
|
||||
CancelChannelAction(PlayerbotAI* botAI) : Action(botAI, "cancel channel") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -11,9 +11,18 @@
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Log.h"
|
||||
|
||||
bool ChangeTalentsAction::Execute(Event event)
|
||||
{
|
||||
auto* flag = botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs"); // Added for custom Glyphs
|
||||
|
||||
if (flag->Get()) // Added for custom Glyphs
|
||||
{
|
||||
flag->Set(false);
|
||||
LOG_INFO("playerbots", "Custom Glyph Flag set to OFF");
|
||||
}
|
||||
std::string param = event.getParam();
|
||||
|
||||
std::ostringstream out;
|
||||
@@ -135,6 +144,10 @@ std::string ChangeTalentsAction::SpecPick(std::string param)
|
||||
if (sPlayerbotAIConfig->premadeSpecName[cls][specNo] == param)
|
||||
{
|
||||
PlayerbotFactory::InitTalentsBySpecNo(bot, specNo, true);
|
||||
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
factory.InitGlyphs(false);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Picking " << sPlayerbotAIConfig->premadeSpecName[cls][specNo];
|
||||
return out.str();
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "ListSpellsAction.h"
|
||||
#include "LogLevelAction.h"
|
||||
#include "LootStrategyAction.h"
|
||||
#include "LootRollAction.h"
|
||||
#include "MailAction.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "NewRpgAction.h"
|
||||
@@ -73,10 +74,15 @@
|
||||
#include "UseItemAction.h"
|
||||
#include "UseMeetingStoneAction.h"
|
||||
#include "WhoAction.h"
|
||||
#include "WipeAction.h"
|
||||
#include "WtsAction.h"
|
||||
#include "OpenItemAction.h"
|
||||
#include "UnlockItemAction.h"
|
||||
#include "UnlockTradedItemAction.h"
|
||||
#include "TameAction.h"
|
||||
#include "TellGlyphsAction.h"
|
||||
#include "EquipGlyphsAction.h"
|
||||
#include "PetAction.h"
|
||||
|
||||
class ChatActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
@@ -185,6 +191,13 @@ public:
|
||||
creators["join"] = &ChatActionContext::join;
|
||||
creators["lfg"] = &ChatActionContext::lfg;
|
||||
creators["calc"] = &ChatActionContext::calc;
|
||||
creators["wipe"] = &ChatActionContext::wipe;
|
||||
creators["tame"] = &ChatActionContext::tame;
|
||||
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
|
||||
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
|
||||
creators["pet"] = &ChatActionContext::pet;
|
||||
creators["pet attack"] = &ChatActionContext::pet_attack;
|
||||
creators["roll"] = &ChatActionContext::roll_action;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -290,6 +303,13 @@ private:
|
||||
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
|
||||
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
|
||||
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
|
||||
static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); }
|
||||
static Action* tame(PlayerbotAI* botAI) { return new TameAction(botAI); }
|
||||
static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs
|
||||
static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs
|
||||
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
|
||||
static Action* pet_attack(PlayerbotAI* botAI) { return new PetAction(botAI, "attack"); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -53,6 +53,9 @@ BotCheatMask CheatAction::GetCheatMask(std::string const cheat)
|
||||
if (cheat == "power")
|
||||
return BotCheatMask::power;
|
||||
|
||||
if (cheat == "raid")
|
||||
return BotCheatMask::raid;
|
||||
|
||||
return BotCheatMask::none;
|
||||
}
|
||||
|
||||
@@ -70,6 +73,8 @@ std::string const CheatAction::GetCheatName(BotCheatMask cheatMask)
|
||||
return "mana";
|
||||
case BotCheatMask::power:
|
||||
return "power";
|
||||
case BotCheatMask::raid:
|
||||
return "raid";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
#include "CheckMountStateAction.h"
|
||||
#include "BattleGroundTactics.h"
|
||||
#include "BattlegroundEY.h"
|
||||
#include "BattlegroundWS.h"
|
||||
#include "Event.h"
|
||||
#include "PlayerbotAI.h"
|
||||
@@ -98,7 +100,7 @@ bool CheckMountStateAction::isUseful()
|
||||
if (bot->InBattleground())
|
||||
{
|
||||
// Do not use when carrying BG Flags
|
||||
if (bot->HasAura(23333) || bot->HasAura(23335) || bot->HasAura(34976))
|
||||
if (bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG) || bot->HasAura(BG_EY_NETHERSTORM_FLAG_SPELL))
|
||||
return false;
|
||||
|
||||
// Only mount if BG starts in less than 30 sec
|
||||
|
||||
@@ -25,7 +25,7 @@ bool AttackEnemyPlayerAction::isUseful()
|
||||
bool AttackEnemyFlagCarrierAction::isUseful()
|
||||
{
|
||||
Unit* target = context->GetValue<Unit*>("enemy flag carrier")->Get();
|
||||
return target && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), 75.0f) &&
|
||||
return target && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), 100.0f) &&
|
||||
PlayerHasFlag::IsCapturingFlag(bot);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,14 @@ bool DropTargetAction::Execute(Event event)
|
||||
bot->SetTarget(ObjectGuid::Empty);
|
||||
bot->SetSelection(ObjectGuid());
|
||||
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
// botAI->InterruptSpell();
|
||||
if (bot->getClass() == CLASS_HUNTER) // Check for Hunter Class
|
||||
{
|
||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||
}
|
||||
}
|
||||
bot->AttackStop();
|
||||
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
#include "EquipAction.h"
|
||||
#include <utility>
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
@@ -11,6 +12,7 @@
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "StatsWeightCalculator.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
bool EquipAction::Execute(Event event)
|
||||
{
|
||||
@@ -104,7 +106,10 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid itemguid = item->GetGUID();
|
||||
packet << itemguid << uint8(EQUIPMENT_SLOT_RANGED);
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet);
|
||||
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in ranged slot";
|
||||
@@ -187,7 +192,8 @@ void EquipAction::EquipItem(Item* item)
|
||||
// Priority 1: Replace main hand if the new weapon is strictly better
|
||||
// and if conditions allow (e.g. no conflicting 2H logic)
|
||||
bool betterThanMH = (newItemScore > mainHandScore);
|
||||
bool mhConditionOK = ((invType != INVTYPE_2HWEAPON && !have2HWeaponEquipped) ||
|
||||
// If a one-handed weapon is better, we can still use it instead of a two-handed weapon
|
||||
bool mhConditionOK = (invType != INVTYPE_2HWEAPON ||
|
||||
(isTwoHander && !canTitanGrip) ||
|
||||
(canTitanGrip && isValidTGWeapon));
|
||||
|
||||
@@ -198,7 +204,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid newItemGuid = item->GetGUID();
|
||||
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND);
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
}
|
||||
|
||||
// Try moving old main hand weapon to offhand if beneficial
|
||||
@@ -209,7 +217,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid oldMHGuid = mainHandItem->GetGUID();
|
||||
offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(offhandPacket);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(offhandPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream moveMsg;
|
||||
moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand";
|
||||
@@ -229,7 +239,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid newItemGuid = item->GetGUID();
|
||||
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(eqPacket);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in offhand";
|
||||
@@ -286,7 +298,9 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid itemguid = item->GetGUID();
|
||||
packet << itemguid << dstSlot;
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(packet);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
159
src/strategy/actions/EquipGlyphsAction.cpp
Normal file
159
src/strategy/actions/EquipGlyphsAction.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "EquipGlyphsAction.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "DBCStores.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace
|
||||
{
|
||||
// itemId -> GlyphInfo
|
||||
std::unordered_map<uint32, EquipGlyphsAction::GlyphInfo> s_GlyphCache;
|
||||
}
|
||||
|
||||
void EquipGlyphsAction::BuildGlyphCache()
|
||||
{
|
||||
if (!s_GlyphCache.empty())
|
||||
return;
|
||||
|
||||
ItemTemplateContainer const* store = sObjectMgr->GetItemTemplateStore();
|
||||
|
||||
for (auto const& kv : *store)
|
||||
{
|
||||
uint32 itemId = kv.first;
|
||||
ItemTemplate const* proto = &kv.second;
|
||||
if (!proto || proto->Class != ITEM_CLASS_GLYPH)
|
||||
continue;
|
||||
|
||||
// inspect item spell
|
||||
for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
||||
{
|
||||
uint32 spellId = proto->Spells[i].SpellId;
|
||||
if (!spellId) continue;
|
||||
|
||||
SpellInfo const* si = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!si) continue;
|
||||
|
||||
for (uint8 eff = 0; eff <= EFFECT_2; ++eff)
|
||||
{
|
||||
if (si->Effects[eff].Effect != SPELL_EFFECT_APPLY_GLYPH)
|
||||
continue;
|
||||
|
||||
uint32 glyphId = si->Effects[eff].MiscValue;
|
||||
if (!glyphId) continue;
|
||||
|
||||
if (auto const* gp = sGlyphPropertiesStore.LookupEntry(glyphId))
|
||||
s_GlyphCache[itemId] = {gp, proto};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EquipGlyphsAction::GlyphInfo const* EquipGlyphsAction::GetGlyphInfo(uint32 itemId)
|
||||
{
|
||||
BuildGlyphCache();
|
||||
auto it = s_GlyphCache.find(itemId);
|
||||
return (it == s_GlyphCache.end()) ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
/// -----------------------------------------------------------------
|
||||
/// Validation and collect
|
||||
/// -----------------------------------------------------------------
|
||||
bool EquipGlyphsAction::CollectGlyphs(std::vector<uint32> const& itemIds,
|
||||
std::vector<GlyphInfo const*>& out) const
|
||||
{
|
||||
std::unordered_set<uint32> seen;
|
||||
|
||||
for (uint32 itemId : itemIds)
|
||||
{
|
||||
if (!seen.insert(itemId).second)
|
||||
return false; // double
|
||||
|
||||
auto const* info = GetGlyphInfo(itemId);
|
||||
if (!info) // no good glyph
|
||||
return false;
|
||||
|
||||
// check class by AllowableClass
|
||||
if ((info->proto->AllowableClass & bot->getClassMask()) == 0)
|
||||
return false;
|
||||
|
||||
out.push_back(info);
|
||||
}
|
||||
return out.size() <= 6 && !out.empty();
|
||||
}
|
||||
|
||||
/// -----------------------------------------------------------------
|
||||
/// Action
|
||||
/// -----------------------------------------------------------------
|
||||
bool EquipGlyphsAction::Execute(Event event)
|
||||
{
|
||||
// 1) parse IDs
|
||||
std::vector<uint32> itemIds;
|
||||
std::istringstream iss(event.getParam());
|
||||
for (uint32 id; iss >> id; ) itemIds.push_back(id);
|
||||
|
||||
std::vector<GlyphInfo const*> glyphs;
|
||||
if (!CollectGlyphs(itemIds, glyphs))
|
||||
{
|
||||
botAI->TellMaster("Usage: glyph equip <6 glyph item IDs> (3 major, 3 minor).");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2) prepare a empty slots table ?
|
||||
bool used[6] = {false,false,false,false,false,false};
|
||||
|
||||
// 3) for each glyph, find the first available and compatible socket
|
||||
for (auto const* g : glyphs)
|
||||
{
|
||||
bool placed = false;
|
||||
|
||||
for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
|
||||
{
|
||||
if (used[i]) continue;
|
||||
|
||||
uint32 slotId = bot->GetGlyphSlot(i);
|
||||
auto const* gs = sGlyphSlotStore.LookupEntry(slotId);
|
||||
if (!gs || gs->TypeFlags != g->prop->TypeFlags)
|
||||
continue; // major/minor don't match
|
||||
|
||||
// Remove aura if exist
|
||||
uint32 cur = bot->GetGlyph(i);
|
||||
if (cur)
|
||||
if (auto* old = sGlyphPropertiesStore.LookupEntry(cur))
|
||||
bot->RemoveAurasDueToSpell(old->SpellId);
|
||||
|
||||
// Apply new one
|
||||
bot->CastSpell(bot, g->prop->SpellId, true);
|
||||
bot->SetGlyph(i, g->prop->Id, true);
|
||||
|
||||
used[i] = true;
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!placed)
|
||||
{
|
||||
botAI->TellMaster("Not enought empty sockets for all glyphs.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
botAI->TellMaster("Glyphs updated.");
|
||||
|
||||
// Flag for custom glyphs
|
||||
botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs")->Set(true);
|
||||
LOG_INFO("playerbots", "Custom Glyph Flag set to ON");
|
||||
|
||||
return true;
|
||||
}
|
||||
37
src/strategy/actions/EquipGlyphsAction.h
Normal file
37
src/strategy/actions/EquipGlyphsAction.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_EQUIPGLYPHSACTION_H
|
||||
#define _PLAYERBOT_EQUIPGLYPHSACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
// 1 = major, 2 = minor dans GlyphProperties.dbc
|
||||
enum class GlyphKind : uint32 { MAJOR = 1, MINOR = 2 };
|
||||
|
||||
class EquipGlyphsAction : public Action
|
||||
{
|
||||
public:
|
||||
EquipGlyphsAction(PlayerbotAI* ai) : Action(ai, "glyph equip") {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
/// ---- Rendu public pour être utilisable par le cache global ----
|
||||
struct GlyphInfo
|
||||
{
|
||||
GlyphPropertiesEntry const* prop; ///< entrée GlyphProperties.dbc
|
||||
ItemTemplate const* proto; ///< template de l’objet glyphe
|
||||
};
|
||||
|
||||
private:
|
||||
/// Construit la cache {itemId -> GlyphInfo}
|
||||
static void BuildGlyphCache();
|
||||
static GlyphInfo const* GetGlyphInfo(uint32 itemId);
|
||||
|
||||
/// Parse & valide la liste d’items glyphes
|
||||
bool CollectGlyphs(std::vector<uint32> const& itemIds,
|
||||
std::vector<GlyphInfo const*>& out) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -36,10 +36,13 @@ bool FollowAction::Execute(Event event)
|
||||
true, priority, true);
|
||||
}
|
||||
|
||||
if (Pet* pet = bot->GetPet())
|
||||
{
|
||||
botAI->PetFollow();
|
||||
}
|
||||
// This section has been commented out because it was forcing the pet to
|
||||
// follow the bot on every "follow" action tick, overriding any attack or
|
||||
// stay commands that might have been issued by the player.
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
// {
|
||||
// botAI->PetFollow();
|
||||
// }
|
||||
// if (moved)
|
||||
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
|
||||
|
||||
|
||||
@@ -4,9 +4,19 @@
|
||||
*/
|
||||
|
||||
#include "GenericActions.h"
|
||||
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Player.h"
|
||||
#include "Pet.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "CharmInfo.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
enum PetSpells
|
||||
{
|
||||
@@ -14,10 +24,26 @@ enum PetSpells
|
||||
PET_PROWL_2 = 24452,
|
||||
PET_PROWL_3 = 24453,
|
||||
PET_COWER = 1742,
|
||||
PET_LEAP = 47482
|
||||
PET_LEAP = 47482,
|
||||
PET_SPELL_LOCK_1 = 19244,
|
||||
PET_SPELL_LOCK_2 = 19647,
|
||||
PET_DEVOUR_MAGIC_1 = 19505,
|
||||
PET_DEVOUR_MAGIC_2 = 19731,
|
||||
PET_DEVOUR_MAGIC_3 = 19734,
|
||||
PET_DEVOUR_MAGIC_4 = 19736,
|
||||
PET_DEVOUR_MAGIC_5 = 27276,
|
||||
PET_DEVOUR_MAGIC_6 = 27277,
|
||||
PET_DEVOUR_MAGIC_7 = 48011,
|
||||
PET_SPIRIT_WOLF_LEAP = 58867
|
||||
};
|
||||
|
||||
static std::vector<uint32> disabledPetSpells = {PET_PROWL_1, PET_PROWL_2, PET_PROWL_3, PET_COWER, PET_LEAP};
|
||||
static std::vector<uint32> disabledPetSpells = {
|
||||
PET_PROWL_1, PET_PROWL_2, PET_PROWL_3,
|
||||
PET_COWER, PET_LEAP,
|
||||
PET_SPELL_LOCK_1, PET_SPELL_LOCK_2,
|
||||
PET_DEVOUR_MAGIC_1, PET_DEVOUR_MAGIC_2, PET_DEVOUR_MAGIC_3,
|
||||
PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7, PET_SPIRIT_WOLF_LEAP
|
||||
};
|
||||
|
||||
bool MeleeAction::isUseful()
|
||||
{
|
||||
@@ -85,6 +111,11 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
|
||||
toggled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Debug message if pet spells have been toggled and debug is enabled
|
||||
if (toggled && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet autocast spells have been toggled.");
|
||||
|
||||
return toggled;
|
||||
}
|
||||
|
||||
@@ -92,22 +123,23 @@ bool PetAttackAction::Execute(Event event)
|
||||
{
|
||||
Guardian* pet = bot->GetGuardianPet();
|
||||
if (!pet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not attack if the pet's stance is set to "passive".
|
||||
if (pet->GetReactState() == REACT_PASSIVE)
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->IsValidAttackTarget(target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pet->SetReactState(REACT_PASSIVE);
|
||||
// This section has been commented because it was overriding the
|
||||
// pet's stance to "passive" every time the attack action was executed.
|
||||
// pet->SetReactState(REACT_PASSIVE);
|
||||
|
||||
pet->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||
pet->AttackStop();
|
||||
pet->SetTarget(target->GetGUID());
|
||||
@@ -121,3 +153,76 @@ bool PetAttackAction::Execute(Event event)
|
||||
pet->ToCreature()->AI()->AttackStart(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetPetStanceAction::Execute(Event /*event*/)
|
||||
{
|
||||
// Prepare a list to hold all controlled pet and guardian creatures
|
||||
std::vector<Creature*> targets;
|
||||
|
||||
// Add the bot's main pet (if it exists) to the target list
|
||||
Pet* pet = bot->GetPet();
|
||||
if (pet)
|
||||
targets.push_back(pet);
|
||||
|
||||
// Loop through all units controlled by the bot (could be pets, guardians, etc.)
|
||||
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
|
||||
{
|
||||
// Only add creatures (skip players, vehicles, etc.)
|
||||
Creature* creature = dynamic_cast<Creature*>(*itr);
|
||||
if (!creature)
|
||||
continue;
|
||||
// Avoid adding the main pet twice
|
||||
if (pet && creature == pet)
|
||||
continue;
|
||||
targets.push_back(creature);
|
||||
}
|
||||
|
||||
// If there are no controlled pets or guardians, notify the player and exit
|
||||
if (targets.empty())
|
||||
{
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the default pet stance from the configuration
|
||||
int32 stance = sPlayerbotAIConfig->defaultPetStance;
|
||||
ReactStates react = REACT_DEFENSIVE;
|
||||
std::string stanceText = "defensive (from config, fallback)";
|
||||
|
||||
// Map the config stance integer to a ReactStates value and a message
|
||||
switch (stance)
|
||||
{
|
||||
case 0:
|
||||
react = REACT_PASSIVE;
|
||||
stanceText = "passive (from config)";
|
||||
break;
|
||||
case 1:
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive (from config)";
|
||||
break;
|
||||
case 2:
|
||||
react = REACT_AGGRESSIVE;
|
||||
stanceText = "aggressive (from config)";
|
||||
break;
|
||||
default:
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive (from config, fallback)";
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply the stance to all target creatures (pets/guardians)
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
target->SetReactState(react);
|
||||
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||
// If the creature has a CharmInfo, set the player-visible stance as well
|
||||
if (charmInfo)
|
||||
charmInfo->SetPlayerReactState(react);
|
||||
}
|
||||
|
||||
// If debug is enabled in config, inform the master of the new stance
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + " (applied to all pets/guardians).");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#define _PLAYERBOT_GENERICACTIONS_H
|
||||
|
||||
#include "AttackAction.h"
|
||||
#include "Action.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
@@ -33,4 +35,12 @@ public:
|
||||
virtual bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class SetPetStanceAction : public Action
|
||||
{
|
||||
public:
|
||||
SetPetStanceAction(PlayerbotAI* botAI) : Action(botAI, "set pet stance") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -304,7 +304,6 @@ bool UseTrinketAction::Execute(Event event)
|
||||
return true;
|
||||
|
||||
Item* trinket2 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET2);
|
||||
|
||||
if (trinket2 && UseTrinket(trinket2))
|
||||
return true;
|
||||
|
||||
@@ -333,6 +332,33 @@ bool UseTrinketAction::UseTrinket(Item* item)
|
||||
if (item->GetTemplate()->Spells[i].SpellId > 0 && item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
|
||||
{
|
||||
spellId = item->GetTemplate()->Spells[i].SpellId;
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
if (!spellInfo || !spellInfo->IsPositive())
|
||||
return false;
|
||||
|
||||
bool applyAura = false;
|
||||
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
|
||||
{
|
||||
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
|
||||
if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA) {
|
||||
applyAura = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!applyAura)
|
||||
return false;
|
||||
|
||||
uint32 spellProcFlag = spellInfo->ProcFlags;
|
||||
|
||||
// Handle items with procflag "if you kill a target that grants honor or experience"
|
||||
// Bots will "learn" the trinket proc, so CanCastSpell() will be true
|
||||
// e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to
|
||||
// constant casting of the proc spell onto themselfes https://www.wowhead.com/wotlk/spell=59787/oracle-ablutions
|
||||
// This will lead to multiple hundreds of entries in m_appliedAuras -> Once killing an enemy -> Big diff time spikes
|
||||
if (spellProcFlag != 0) return false;
|
||||
|
||||
if (!botAI->CanCastSpell(spellId, bot, false))
|
||||
{
|
||||
return false;
|
||||
@@ -345,17 +371,8 @@ bool UseTrinketAction::UseTrinket(Item* item)
|
||||
WorldPacket packet(CMSG_USE_ITEM);
|
||||
packet << bagIndex << slot << cast_count << spellId << item_guid << glyphIndex << castFlags;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (target)
|
||||
{
|
||||
targetFlag = TARGET_FLAG_UNIT;
|
||||
packet << targetFlag << target->GetGUID().WriteAsPacked();
|
||||
}
|
||||
else
|
||||
{
|
||||
targetFlag = TARGET_FLAG_NONE;
|
||||
packet << targetFlag << bot->GetPackGUID();
|
||||
}
|
||||
targetFlag = TARGET_FLAG_NONE;
|
||||
packet << targetFlag << bot->GetPackGUID();
|
||||
bot->GetSession()->HandleUseItemOpcode(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ bool GiveFoodAction::isUseful()
|
||||
|
||||
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget());
|
||||
|
||||
return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood);
|
||||
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
|
||||
}
|
||||
|
||||
Unit* GiveWaterAction::GetTarget() { return AI_VALUE(Unit*, "party member without water"); }
|
||||
@@ -88,5 +88,5 @@ bool GiveWaterAction::isUseful()
|
||||
|
||||
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget());
|
||||
|
||||
return !isRandomBot || (isRandomBot && !sPlayerbotAIConfig->freeFood);
|
||||
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
|
||||
}
|
||||
|
||||
@@ -19,22 +19,51 @@ bool ImbueWithPoisonAction::Execute(Event event)
|
||||
if (bot->HasAura(SPELL_AURA_MOD_STEALTH))
|
||||
bot->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
|
||||
|
||||
// hp check
|
||||
if (bot->getStandState() != UNIT_STAND_STATE_STAND)
|
||||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||||
|
||||
// Search and apply poison to weapons
|
||||
// Mainhand ...
|
||||
static const std::vector<uint32_t> prioritizedInstantPoisons = {
|
||||
INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V, INSTANT_POISON_IV,
|
||||
INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON
|
||||
};
|
||||
|
||||
static const std::vector<uint32_t> prioritizedDeadlyPoisons = {
|
||||
DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V, DEADLY_POISON_IV,
|
||||
DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON
|
||||
};
|
||||
|
||||
// Check if we have any deadly or instant poisons
|
||||
Item* deadlyPoison = nullptr;
|
||||
for (auto id : prioritizedDeadlyPoisons)
|
||||
{
|
||||
deadlyPoison = botAI->FindConsumable(id);
|
||||
if (deadlyPoison) break;
|
||||
}
|
||||
|
||||
Item* instantPoison = nullptr;
|
||||
for (auto id : prioritizedInstantPoisons)
|
||||
{
|
||||
instantPoison = botAI->FindConsumable(id);
|
||||
if (instantPoison) break;
|
||||
}
|
||||
|
||||
// Mainhand
|
||||
Item* poison = nullptr;
|
||||
Item* weapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0)
|
||||
{
|
||||
poison = botAI->FindConsumable(INSTANT_POISON_DISPLAYID);
|
||||
if (!poison)
|
||||
poison = botAI->FindConsumable(DEADLY_POISON_DISPLAYID);
|
||||
|
||||
if (!poison)
|
||||
poison = botAI->FindConsumable(WOUND_POISON_DISPLAYID);
|
||||
if (instantPoison && deadlyPoison)
|
||||
{
|
||||
poison = instantPoison;
|
||||
}
|
||||
else if (deadlyPoison)
|
||||
{
|
||||
poison = deadlyPoison;
|
||||
}
|
||||
else if (instantPoison)
|
||||
{
|
||||
poison = instantPoison;
|
||||
}
|
||||
|
||||
if (poison)
|
||||
{
|
||||
@@ -43,16 +72,23 @@ bool ImbueWithPoisonAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
|
||||
//... and offhand
|
||||
// Offhand
|
||||
poison = nullptr;
|
||||
weapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
||||
if (weapon && weapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) == 0)
|
||||
{
|
||||
poison = botAI->FindConsumable(DEADLY_POISON_DISPLAYID);
|
||||
if (!poison)
|
||||
poison = botAI->FindConsumable(WOUND_POISON_DISPLAYID);
|
||||
|
||||
if (!poison)
|
||||
poison = botAI->FindConsumable(INSTANT_POISON_DISPLAYID);
|
||||
if (deadlyPoison && instantPoison)
|
||||
{
|
||||
poison = deadlyPoison;
|
||||
}
|
||||
else if (instantPoison)
|
||||
{
|
||||
poison = instantPoison;
|
||||
}
|
||||
else if (deadlyPoison)
|
||||
{
|
||||
poison = deadlyPoison;
|
||||
}
|
||||
|
||||
if (poison)
|
||||
{
|
||||
@@ -141,8 +177,8 @@ bool ImbueWithOilAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint32 uPriorizedHealingItemIds[19] = {
|
||||
HEALTHSTONE_DISPLAYID,
|
||||
static const uint32 uPrioritizedHealingItemIds[19] = {
|
||||
HEALTHSTONE,
|
||||
FEL_REGENERATION_POTION,
|
||||
SUPER_HEALING_POTION,
|
||||
CRYSTAL_HEALING_POTION,
|
||||
@@ -182,9 +218,9 @@ bool TryEmergencyAction::Execute(Event event)
|
||||
}
|
||||
|
||||
// Else loop over the list of health consumable to pick one
|
||||
for (uint8 i = 0; i < std::size(uPriorizedHealingItemIds); ++i)
|
||||
for (uint8 i = 0; i < std::size(uPrioritizedHealingItemIds); ++i)
|
||||
{
|
||||
if (Item* healthItem = botAI->FindConsumable(uPriorizedHealingItemIds[i]))
|
||||
if (Item* healthItem = botAI->FindConsumable(uPrioritizedHealingItemIds[i]))
|
||||
{
|
||||
botAI->ImbueItem(healthItem);
|
||||
}
|
||||
|
||||
@@ -98,10 +98,10 @@ bool InviteNearbyToGroupAction::Execute(Event event)
|
||||
|
||||
if (group && group->isRaidGroup())
|
||||
bot->Say(BOT_TEXT2("join_raid", placeholders),
|
||||
(bot->GetTeamId() == ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||
else
|
||||
bot->Say(BOT_TEXT2("join_group", placeholders),
|
||||
(bot->GetTeamId() == ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||
}
|
||||
|
||||
return Invite(bot, player);
|
||||
|
||||
@@ -63,7 +63,7 @@ public:
|
||||
}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() { return bot->GetGuildId() && InviteNearbyToGroupAction::isUseful(); };
|
||||
bool isUseful() override { return bot->GetGuildId() && InviteNearbyToGroupAction::isUseful(); };
|
||||
|
||||
private:
|
||||
std::vector<Player*> getGuildMembers();
|
||||
|
||||
@@ -224,3 +224,37 @@ bool RollUniqueCheck(ItemTemplate const* proto, Player* bot)
|
||||
}
|
||||
return false; // Item is not equipped or in bags, roll for it
|
||||
}
|
||||
|
||||
bool RollAction::Execute(Event event)
|
||||
{
|
||||
std::string link = event.getParam();
|
||||
|
||||
if (link.empty())
|
||||
{
|
||||
bot->DoRandomRoll(0,100);
|
||||
return false;
|
||||
}
|
||||
ItemIds itemIds = chat->parseItems(link);
|
||||
if (itemIds.empty())
|
||||
return false;
|
||||
uint32 itemId = *itemIds.begin();
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!proto)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::string itemUsageParam;
|
||||
itemUsageParam = std::to_string(itemId);
|
||||
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
||||
switch (proto->Class)
|
||||
{
|
||||
case ITEM_CLASS_WEAPON:
|
||||
case ITEM_CLASS_ARMOR:
|
||||
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
|
||||
{
|
||||
bot->DoRandomRoll(0,100);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,4 +37,12 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class RollAction : public Action
|
||||
{
|
||||
public:
|
||||
RollAction(PlayerbotAI* botAI) : Action(botAI, "roll") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
|
||||
WorldLocation location = *target->getPosition();
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster())
|
||||
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
|
||||
@@ -1801,7 +1801,6 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
|
||||
|
||||
bool FleeAction::Execute(Event event)
|
||||
{
|
||||
// return Flee(AI_VALUE(Unit*, "current target"));
|
||||
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
|
||||
}
|
||||
|
||||
@@ -1811,6 +1810,10 @@ bool FleeAction::isUseful()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (target && target->IsInWorld() && !bot->IsWithinMeleeRange(target))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2764,3 +2767,177 @@ bool MoveFromGroupAction::Execute(Event event)
|
||||
distance = 20.0f; // flee distance from config is too small for this
|
||||
return MoveFromGroup(distance);
|
||||
}
|
||||
|
||||
bool MoveAwayFromCreatureAction::Execute(Event event)
|
||||
{
|
||||
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
|
||||
Creature* nearestCreature = bot->FindNearestCreature(creatureId, range, alive);
|
||||
|
||||
// Find all creatures with the specified Id
|
||||
std::vector<Unit*> creatures;
|
||||
for (const auto& guid : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(guid);
|
||||
if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId)
|
||||
{
|
||||
creatures.push_back(unit);
|
||||
}
|
||||
}
|
||||
|
||||
if (creatures.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Search for a safe position
|
||||
const int directions = 8;
|
||||
const float increment = 3.0f;
|
||||
float bestX = bot->GetPositionX();
|
||||
float bestY = bot->GetPositionY();
|
||||
float bestZ = bot->GetPositionZ();
|
||||
float maxSafetyScore = -1.0f;
|
||||
|
||||
for (int i = 0; i < directions; ++i)
|
||||
{
|
||||
float angle = (i * 2 * M_PI) / directions;
|
||||
for (float distance = increment; distance <= 30.0f; distance += increment)
|
||||
{
|
||||
float moveX = bot->GetPositionX() + distance * cos(angle);
|
||||
float moveY = bot->GetPositionY() + distance * sin(angle);
|
||||
float moveZ = bot->GetPositionZ();
|
||||
|
||||
// Check creature distance constraints
|
||||
bool isSafeFromCreatures = true;
|
||||
float minCreatureDist = std::numeric_limits<float>::max();
|
||||
for (Unit* creature : creatures)
|
||||
{
|
||||
float dist = creature->GetExactDist2d(moveX, moveY);
|
||||
if (dist < range)
|
||||
{
|
||||
isSafeFromCreatures = false;
|
||||
break;
|
||||
}
|
||||
if (dist < minCreatureDist)
|
||||
{
|
||||
minCreatureDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSafeFromCreatures && bot->IsWithinLOS(moveX, moveY, moveZ))
|
||||
{
|
||||
// A simple safety score: the minimum distance to any creature. Higher is better.
|
||||
if (minCreatureDist > maxSafetyScore)
|
||||
{
|
||||
maxSafetyScore = minCreatureDist;
|
||||
bestX = moveX;
|
||||
bestY = moveY;
|
||||
bestZ = moveZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the best position found
|
||||
if (maxSafetyScore > 0.0f)
|
||||
{
|
||||
return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false,
|
||||
MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveAwayFromCreatureAction::isPossible()
|
||||
{
|
||||
return bot->CanFreeMove();
|
||||
}
|
||||
|
||||
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
||||
{
|
||||
Player* closestPlayer = nullptr;
|
||||
float minDistance = 0.0f;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Player*> debuffedPlayers;
|
||||
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
Player* player = gref->GetSource();
|
||||
if (player && player->IsAlive() && player->HasAura(spellId))
|
||||
{
|
||||
debuffedPlayers.push_back(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (debuffedPlayers.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Search for a safe position
|
||||
const int directions = 8;
|
||||
const float increment = 3.0f;
|
||||
float bestX = bot->GetPositionX();
|
||||
float bestY = bot->GetPositionY();
|
||||
float bestZ = bot->GetPositionZ();
|
||||
float maxSafetyScore = -1.0f;
|
||||
|
||||
for (int i = 0; i < directions; ++i)
|
||||
{
|
||||
float angle = (i * 2 * M_PI) / directions;
|
||||
for (float distance = increment; distance <= (range + 5.0f); distance += increment)
|
||||
{
|
||||
float moveX = bot->GetPositionX() + distance * cos(angle);
|
||||
float moveY = bot->GetPositionY() + distance * sin(angle);
|
||||
float moveZ = bot->GetPositionZ();
|
||||
|
||||
// Check creature distance constraints
|
||||
bool isSafeFromDebuffedPlayer = true;
|
||||
float minDebuffedPlayerDistance = std::numeric_limits<float>::max();
|
||||
for (Unit* debuffedPlayer : debuffedPlayers)
|
||||
{
|
||||
float dist = debuffedPlayer->GetExactDist2d(moveX, moveY);
|
||||
if (dist < range)
|
||||
{
|
||||
isSafeFromDebuffedPlayer = false;
|
||||
break;
|
||||
}
|
||||
if (dist < minDebuffedPlayerDistance)
|
||||
{
|
||||
minDebuffedPlayerDistance = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSafeFromDebuffedPlayer && bot->IsWithinLOS(moveX, moveY, moveZ))
|
||||
{
|
||||
// A simple safety score: the minimum distance to any debuffed player. Higher is better.
|
||||
if (minDebuffedPlayerDistance > maxSafetyScore)
|
||||
{
|
||||
maxSafetyScore = minDebuffedPlayerDistance;
|
||||
bestX = moveX;
|
||||
bestY = moveY;
|
||||
bestZ = moveZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the best position found
|
||||
if (maxSafetyScore > 0.0f)
|
||||
{
|
||||
return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false,
|
||||
MovementPriority::MOVEMENT_COMBAT, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveAwayFromPlayerWithDebuffAction::isPossible()
|
||||
{
|
||||
return bot->CanFreeMove();
|
||||
}
|
||||
|
||||
@@ -294,4 +294,34 @@ public:
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MoveAwayFromCreatureAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveAwayFromCreatureAction(PlayerbotAI* botAI, std::string name, uint32 creatureId, float range, bool alive = true)
|
||||
: MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isPossible() override;
|
||||
|
||||
private:
|
||||
uint32 creatureId;
|
||||
float range;
|
||||
bool alive;
|
||||
};
|
||||
|
||||
class MoveAwayFromPlayerWithDebuffAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range)
|
||||
: MovementAction(botAI, name), spellId(spellId), range(range) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isPossible() override;
|
||||
|
||||
private:
|
||||
uint32 spellId;
|
||||
float range;
|
||||
bool alive;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,7 @@ bool DrinkAction::Execute(Event event)
|
||||
if (!hasMana)
|
||||
return false;
|
||||
|
||||
if (sPlayerbotAIConfig->freeFood)
|
||||
if (botAI->HasCheat(BotCheatMask::food))
|
||||
{
|
||||
// if (bot->IsNonMeleeSpellCast(true))
|
||||
// return false;
|
||||
@@ -40,13 +40,13 @@ bool DrinkAction::Execute(Event event)
|
||||
float delay;
|
||||
|
||||
if (!bot->InBattleground())
|
||||
delay = 27000.0f * (100 - p) / 100.0f;
|
||||
delay = 18000.0f * (100 - p) / 100.0f;
|
||||
else
|
||||
delay = 20000.0f * (100 - p) / 100.0f;
|
||||
delay = 12000.0f * (100 - p) / 100.0f;
|
||||
|
||||
botAI->SetNextCheckDelay(delay);
|
||||
|
||||
bot->AddAura(24707, bot);
|
||||
bot->AddAura(25990, bot);
|
||||
return true;
|
||||
// return botAI->CastSpell(24707, bot);
|
||||
}
|
||||
@@ -54,11 +54,11 @@ bool DrinkAction::Execute(Event event)
|
||||
return UseItemAction::Execute(event);
|
||||
}
|
||||
|
||||
bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 85; }
|
||||
bool DrinkAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < 100; }
|
||||
|
||||
bool DrinkAction::isPossible()
|
||||
{
|
||||
return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible());
|
||||
return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
|
||||
}
|
||||
|
||||
bool EatAction::Execute(Event event)
|
||||
@@ -66,7 +66,7 @@ bool EatAction::Execute(Event event)
|
||||
if (bot->IsInCombat())
|
||||
return false;
|
||||
|
||||
if (sPlayerbotAIConfig->freeFood)
|
||||
if (botAI->HasCheat(BotCheatMask::food))
|
||||
{
|
||||
// if (bot->IsNonMeleeSpellCast(true))
|
||||
// return false;
|
||||
@@ -90,13 +90,13 @@ bool EatAction::Execute(Event event)
|
||||
float delay;
|
||||
|
||||
if (!bot->InBattleground())
|
||||
delay = 27000.0f * (100 - p) / 100.0f;
|
||||
delay = 18000.0f * (100 - p) / 100.0f;
|
||||
else
|
||||
delay = 20000.0f * (100 - p) / 100.0f;
|
||||
delay = 12000.0f * (100 - p) / 100.0f;
|
||||
|
||||
botAI->SetNextCheckDelay(delay);
|
||||
|
||||
bot->AddAura(24707, bot);
|
||||
bot->AddAura(25990, bot);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -107,5 +107,5 @@ bool EatAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8
|
||||
|
||||
bool EatAction::isPossible()
|
||||
{
|
||||
return !bot->IsInCombat() && (sPlayerbotAIConfig->freeFood || UseItemAction::isPossible());
|
||||
return !bot->IsInCombat() && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Event.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
bool OutfitAction::Execute(Event event)
|
||||
{
|
||||
@@ -70,7 +71,9 @@ bool OutfitAction::Execute(Event event)
|
||||
|
||||
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
|
||||
packet << bagIndex << slot << dstBag;
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
|
||||
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
|
||||
}
|
||||
|
||||
EquipItems(outfit);
|
||||
|
||||
257
src/strategy/actions/PetAction.cpp
Normal file
257
src/strategy/actions/PetAction.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "PetAction.h"
|
||||
|
||||
#include "CharmInfo.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "Pet.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "SharedDefines.h"
|
||||
|
||||
bool PetAction::Execute(Event event)
|
||||
{
|
||||
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
|
||||
std::string param = event.getParam();
|
||||
if (param.empty() && !defaultCmd.empty())
|
||||
{
|
||||
param = defaultCmd;
|
||||
}
|
||||
|
||||
if (param.empty())
|
||||
{
|
||||
// If no parameter is provided, show usage instructions and return.
|
||||
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
return false;
|
||||
}
|
||||
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
// Collect all controlled pets and guardians, except totems, into the targets vector.
|
||||
std::vector<Creature*> targets;
|
||||
Pet* pet = bot->GetPet();
|
||||
if (pet)
|
||||
targets.push_back(pet);
|
||||
|
||||
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
|
||||
{
|
||||
Creature* creature = dynamic_cast<Creature*>(*itr);
|
||||
if (!creature)
|
||||
continue;
|
||||
if (pet && creature == pet)
|
||||
continue;
|
||||
if (creature->IsTotem())
|
||||
continue;
|
||||
targets.push_back(creature);
|
||||
}
|
||||
|
||||
// If no pets or guardians are found, notify and return.
|
||||
if (targets.empty())
|
||||
{
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ReactStates react;
|
||||
std::string stanceText;
|
||||
|
||||
// Handle stance commands: aggressive, defensive, or passive.
|
||||
if (param == "aggressive")
|
||||
{
|
||||
react = REACT_AGGRESSIVE;
|
||||
stanceText = "aggressive";
|
||||
}
|
||||
else if (param == "defensive")
|
||||
{
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive";
|
||||
}
|
||||
else if (param == "passive")
|
||||
{
|
||||
react = REACT_PASSIVE;
|
||||
stanceText = "passive";
|
||||
}
|
||||
// The "stance" command simply reports the current stance of each pet/guardian.
|
||||
else if (param == "stance")
|
||||
{
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
std::string type = target->IsPet() ? "pet" : "guardian";
|
||||
std::string name = target->GetName();
|
||||
std::string stance;
|
||||
switch (target->GetReactState())
|
||||
{
|
||||
case REACT_AGGRESSIVE:
|
||||
stance = "aggressive";
|
||||
break;
|
||||
case REACT_DEFENSIVE:
|
||||
stance = "defensive";
|
||||
break;
|
||||
case REACT_PASSIVE:
|
||||
stance = "passive";
|
||||
break;
|
||||
default:
|
||||
stance = "unknown";
|
||||
break;
|
||||
}
|
||||
botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// The "attack" command forces pets/guardians to attack the master's selected target.
|
||||
else if (param == "attack")
|
||||
{
|
||||
// Try to get the master's selected target.
|
||||
Player* master = botAI->GetMaster();
|
||||
Unit* targetUnit = nullptr;
|
||||
|
||||
if (master)
|
||||
{
|
||||
ObjectGuid masterTargetGuid = master->GetTarget();
|
||||
if (!masterTargetGuid.IsEmpty())
|
||||
{
|
||||
targetUnit = botAI->GetUnit(masterTargetGuid);
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid target is selected, show an error and return.
|
||||
if (!targetUnit)
|
||||
{
|
||||
botAI->TellError("No valid target selected by master.");
|
||||
return false;
|
||||
}
|
||||
if (!targetUnit->IsAlive())
|
||||
{
|
||||
botAI->TellError("Target is not alive.");
|
||||
return false;
|
||||
}
|
||||
if (!bot->IsValidAttackTarget(targetUnit))
|
||||
{
|
||||
botAI->TellError("Target is not a valid attack target for the bot.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool didAttack = false;
|
||||
// For each controlled pet/guardian, command them to attack the selected target.
|
||||
for (Creature* petCreature : targets)
|
||||
{
|
||||
CharmInfo* charmInfo = petCreature->GetCharmInfo();
|
||||
if (!charmInfo)
|
||||
continue;
|
||||
|
||||
petCreature->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||
// Only command attack if not already attacking the target, or if not currently under command attack.
|
||||
if (petCreature->GetVictim() != targetUnit ||
|
||||
(petCreature->GetVictim() == targetUnit && !charmInfo->IsCommandAttack()))
|
||||
{
|
||||
if (petCreature->GetVictim())
|
||||
petCreature->AttackStop();
|
||||
|
||||
if (!petCreature->IsPlayer() && petCreature->ToCreature()->IsAIEnabled)
|
||||
{
|
||||
// For AI-enabled creatures (NPC pets/guardians): issue attack command and set flags.
|
||||
charmInfo->SetIsCommandAttack(true);
|
||||
charmInfo->SetIsAtStay(false);
|
||||
charmInfo->SetIsFollowing(false);
|
||||
charmInfo->SetIsCommandFollow(false);
|
||||
charmInfo->SetIsReturning(false);
|
||||
|
||||
petCreature->ToCreature()->AI()->AttackStart(targetUnit);
|
||||
|
||||
didAttack = true;
|
||||
}
|
||||
else // For charmed player pets/guardians
|
||||
{
|
||||
if (petCreature->GetVictim() && petCreature->GetVictim() != targetUnit)
|
||||
petCreature->AttackStop();
|
||||
|
||||
charmInfo->SetIsCommandAttack(true);
|
||||
charmInfo->SetIsAtStay(false);
|
||||
charmInfo->SetIsFollowing(false);
|
||||
charmInfo->SetIsCommandFollow(false);
|
||||
charmInfo->SetIsReturning(false);
|
||||
|
||||
petCreature->Attack(targetUnit, true);
|
||||
didAttack = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Inform the master if the command succeeded or failed.
|
||||
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to attack your target.");
|
||||
else if (!didAttack)
|
||||
botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
|
||||
return didAttack;
|
||||
}
|
||||
// The "follow" command makes all pets/guardians follow the bot.
|
||||
else if (param == "follow")
|
||||
{
|
||||
botAI->PetFollow();
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to follow.");
|
||||
return true;
|
||||
}
|
||||
// The "stay" command causes all pets/guardians to stop and stay in place.
|
||||
else if (param == "stay")
|
||||
{
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
// If not already in controlled motion, stop movement and set to idle.
|
||||
bool controlledMotion =
|
||||
target->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE;
|
||||
if (!controlledMotion)
|
||||
{
|
||||
target->StopMovingOnCurrentPos();
|
||||
target->GetMotionMaster()->Clear(false);
|
||||
target->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
|
||||
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||
if (charmInfo)
|
||||
{
|
||||
// Set charm/pet state flags for "stay".
|
||||
charmInfo->SetCommandState(COMMAND_STAY);
|
||||
charmInfo->SetIsCommandAttack(false);
|
||||
charmInfo->SetIsCommandFollow(false);
|
||||
charmInfo->SetIsFollowing(false);
|
||||
charmInfo->SetIsReturning(false);
|
||||
charmInfo->SetIsAtStay(!controlledMotion);
|
||||
charmInfo->SaveStayPosition(controlledMotion);
|
||||
if (target->ToPet())
|
||||
target->ToPet()->ClearCastWhenWillAvailable();
|
||||
|
||||
charmInfo->SetForcedSpell(0);
|
||||
charmInfo->SetForcedTargetGUID();
|
||||
}
|
||||
}
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet commanded to stay.");
|
||||
return true;
|
||||
}
|
||||
// Unknown command: show usage instructions and return.
|
||||
else
|
||||
{
|
||||
botAI->TellError("Unknown pet command: " + param +
|
||||
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||
return false;
|
||||
}
|
||||
|
||||
// For stance commands, apply the chosen stance to all targets.
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
target->SetReactState(react);
|
||||
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||
if (charmInfo)
|
||||
charmInfo->SetPlayerReactState(react);
|
||||
}
|
||||
|
||||
// Inform the master of the new stance if debug is enabled.
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + ".");
|
||||
|
||||
return true;
|
||||
}
|
||||
29
src/strategy/actions/PetAction.h
Normal file
29
src/strategy/actions/PetAction.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_PETACTION_H
|
||||
#define _PLAYERBOT_PETACTION_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Action.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Unit.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class PetAction : public Action
|
||||
{
|
||||
public:
|
||||
PetAction(PlayerbotAI* botAI, const std::string& defaultCmd = "") : Action(botAI, "pet"), defaultCmd(defaultCmd) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
bool warningEnabled = true;
|
||||
std::string defaultCmd;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
#include "ReleaseSpiritAction.h"
|
||||
|
||||
#include "ServerFacade.h"
|
||||
#include "Event.h"
|
||||
#include "GameGraveyard.h"
|
||||
#include "NearestNpcsValue.h"
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "Corpse.h"
|
||||
#include "Log.h"
|
||||
|
||||
// ReleaseSpiritAction implementation
|
||||
bool ReleaseSpiritAction::Execute(Event event)
|
||||
@@ -247,3 +248,19 @@ void RepopAction::PerformGraveyardTeleport(const GraveyardStruct* graveyard) con
|
||||
RESET_AI_VALUE(bool, "combat::self target");
|
||||
RESET_AI_VALUE(WorldPosition, "current position");
|
||||
}
|
||||
|
||||
// SelfResurrectAction implementation for Warlock's Soulstone Resurrection/Shaman's Reincarnation
|
||||
bool SelfResurrectAction::Execute(Event event)
|
||||
{
|
||||
if (!bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL))
|
||||
{
|
||||
WorldPacket packet(CMSG_SELF_RES);
|
||||
bot->GetSession()->HandleSelfResOpcode(packet);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool SelfResurrectAction::isUseful()
|
||||
{
|
||||
return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL);
|
||||
}
|
||||
|
||||
@@ -56,4 +56,13 @@ private:
|
||||
void PerformGraveyardTeleport(const GraveyardStruct* graveyard) const;
|
||||
};
|
||||
|
||||
// SelfResurrectAction action registration
|
||||
class SelfResurrectAction : public Action
|
||||
{
|
||||
public:
|
||||
SelfResurrectAction(PlayerbotAI* ai) : Action(ai, "self resurrect") {}
|
||||
virtual bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -39,7 +39,7 @@ bool RevealGatheringItemAction::Execute(Event event)
|
||||
std::list<GameObject*> targets;
|
||||
AnyGameObjectInObjectRangeCheck u_check(bot, sPlayerbotAIConfig->grindDistance);
|
||||
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
|
||||
Cell::VisitAllObjects(bot, searcher, sPlayerbotAIConfig->reactDistance);
|
||||
Cell::VisitObjects(bot, searcher, sPlayerbotAIConfig->reactDistance);
|
||||
|
||||
std::vector<GameObject*> result;
|
||||
for (GameObject* go : targets)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "ItemUsageValue.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
class SellItemsVisitor : public IterateItemsVisitor
|
||||
{
|
||||
@@ -114,9 +115,12 @@ void SellAction::Sell(Item* item)
|
||||
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
|
||||
WorldPacket p;
|
||||
WorldPacket p(CMSG_SELL_ITEM);
|
||||
p << vendorguid << itemguid << count;
|
||||
bot->GetSession()->HandleSellItemOpcode(p);
|
||||
|
||||
WorldPackets::Item::SellItem nicePacket(std::move(p));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleSellItemOpcode(nicePacket);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
{
|
||||
|
||||
500
src/strategy/actions/TameAction.cpp
Normal file
500
src/strategy/actions/TameAction.cpp
Normal file
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "TameAction.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include "DBCStructure.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Pet.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
bool IsExoticPet(const CreatureTemplate* creature)
|
||||
{
|
||||
// Use the IsExotic() method from CreatureTemplate
|
||||
return creature && creature->IsExotic();
|
||||
}
|
||||
|
||||
bool HasBeastMastery(Player* bot)
|
||||
{
|
||||
// Beast Mastery talent aura ID for WotLK is 53270
|
||||
return bot->HasAura(53270);
|
||||
}
|
||||
|
||||
bool TameAction::Execute(Event event)
|
||||
{
|
||||
// Parse the user's input command into mode and value (e.g. "name wolf", "id 1234", etc.)
|
||||
std::string param = event.getParam();
|
||||
std::istringstream iss(param);
|
||||
std::string mode, value;
|
||||
iss >> mode;
|
||||
std::getline(iss, value);
|
||||
value.erase(0, value.find_first_not_of(" ")); // Remove leading spaces from value
|
||||
|
||||
bool found = false;
|
||||
|
||||
// Reset any previous pet name/id state
|
||||
lastPetName = "";
|
||||
lastPetId = 0;
|
||||
|
||||
// If the command is "family" with no value, list all available pet families
|
||||
if (mode == "family" && value.empty())
|
||||
{
|
||||
std::set<std::string> normalFamilies;
|
||||
std::set<std::string> exoticFamilies;
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
// Loop over all creature templates and collect tameable families
|
||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||
{
|
||||
const CreatureTemplate& creature = itr->second;
|
||||
if (!creature.IsTameable(true))
|
||||
continue;
|
||||
|
||||
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
|
||||
if (!familyEntry)
|
||||
continue;
|
||||
|
||||
std::string familyName = familyEntry->Name[0];
|
||||
if (familyName.empty())
|
||||
continue;
|
||||
|
||||
if (creature.IsExotic())
|
||||
exoticFamilies.insert(familyName);
|
||||
else
|
||||
normalFamilies.insert(familyName);
|
||||
}
|
||||
|
||||
// Build the output message for the user
|
||||
std::ostringstream oss;
|
||||
oss << "Available pet families: ";
|
||||
size_t count = 0;
|
||||
for (const auto& name : normalFamilies)
|
||||
{
|
||||
if (count++ != 0)
|
||||
oss << ", ";
|
||||
oss << name;
|
||||
}
|
||||
if (!exoticFamilies.empty())
|
||||
{
|
||||
if (!normalFamilies.empty())
|
||||
oss << " | ";
|
||||
oss << "Exotic: ";
|
||||
count = 0;
|
||||
for (const auto& name : exoticFamilies)
|
||||
{
|
||||
if (count++ != 0)
|
||||
oss << ", ";
|
||||
oss << name;
|
||||
}
|
||||
}
|
||||
|
||||
botAI->TellError(oss.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle "tame abandon" command to give up your current pet
|
||||
if (mode == "abandon")
|
||||
{
|
||||
return AbandonPet();
|
||||
}
|
||||
|
||||
// Try to process the command based on mode and value
|
||||
if (mode == "name" && !value.empty())
|
||||
{
|
||||
found = SetPetByName(value);
|
||||
}
|
||||
else if (mode == "id" && !value.empty())
|
||||
{
|
||||
// Try to convert value to an integer and set pet by ID
|
||||
try
|
||||
{
|
||||
uint32 id = std::stoul(value);
|
||||
found = SetPetById(id);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
botAI->TellError("Invalid tame id.");
|
||||
}
|
||||
}
|
||||
else if (mode == "family" && !value.empty())
|
||||
{
|
||||
found = SetPetByFamily(value);
|
||||
}
|
||||
else if (mode == "rename" && !value.empty())
|
||||
{
|
||||
found = RenamePet(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unrecognized command or missing argument; show usage
|
||||
botAI->TellError(
|
||||
"Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the requested tame/rename failed, return failure
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
// For all non-rename commands, initialize the new pet and talents, then notify the master
|
||||
if (mode != "rename")
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
factory.InitPet();
|
||||
factory.InitPetTalents();
|
||||
|
||||
if (!lastPetName.empty() && lastPetId != 0)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << ".";
|
||||
botAI->TellMaster(oss.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
botAI->TellMaster("Pet changed and initialized!");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TameAction::SetPetByName(const std::string& name)
|
||||
{
|
||||
// Make a lowercase copy of the input name for case-insensitive comparison
|
||||
std::string lowerName = name;
|
||||
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
|
||||
|
||||
// Get the full list of creature templates from the object manager
|
||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
// Iterate through all creature templates
|
||||
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||
{
|
||||
const CreatureTemplate& creature = itr->second;
|
||||
std::string creatureName = creature.Name;
|
||||
// Convert creature's name to lowercase for comparison
|
||||
std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower);
|
||||
|
||||
// If the input name matches this creature's name
|
||||
if (creatureName == lowerName)
|
||||
{
|
||||
// Skip if the creature isn't tameable at all
|
||||
if (!creature.IsTameable(true))
|
||||
continue;
|
||||
|
||||
// If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail
|
||||
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
||||
{
|
||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if the creature isn't tameable by this bot (respecting exotic pet rules)
|
||||
if (!creature.IsTameable(bot->CanTameExoticPets()))
|
||||
continue;
|
||||
|
||||
// Store the found pet's name and entry ID for later use/feedback
|
||||
lastPetName = creature.Name;
|
||||
lastPetId = creature.Entry;
|
||||
// Create and set this pet for the bot
|
||||
return CreateAndSetPet(creature.Entry);
|
||||
}
|
||||
}
|
||||
|
||||
// If no suitable pet found, show an error and return failure
|
||||
botAI->TellError("No tameable pet found with name: " + name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TameAction::SetPetById(uint32 id)
|
||||
{
|
||||
// Look up the creature template by its numeric entry/id
|
||||
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id);
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
// Proceed only if a valid creature was found
|
||||
if (creature)
|
||||
{
|
||||
// Check if this creature is ever tameable (ignore bot's own restrictions for now)
|
||||
if (!creature->IsTameable(true))
|
||||
{
|
||||
// If not tameable at all, show an error and fail
|
||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it's an exotic pet, make sure the bot has the Beast Mastery talent
|
||||
if (IsExoticPet(creature) && !HasBeastMastery(bot))
|
||||
{
|
||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the bot is actually allowed to tame this pet (honoring exotic pet rules)
|
||||
if (!creature->IsTameable(bot->CanTameExoticPets()))
|
||||
{
|
||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remember this pet's name and id for later feedback
|
||||
lastPetName = creature->Name;
|
||||
lastPetId = creature->Entry;
|
||||
// Set and create the pet for the bot
|
||||
return CreateAndSetPet(creature->Entry);
|
||||
}
|
||||
|
||||
// If no valid creature was found by id, show an error
|
||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TameAction::SetPetByFamily(const std::string& family)
|
||||
{
|
||||
// Convert the input family name to lowercase for case-insensitive comparison
|
||||
std::string lowerFamily = family;
|
||||
std::transform(lowerFamily.begin(), lowerFamily.end(), lowerFamily.begin(), ::tolower);
|
||||
|
||||
// Get all creature templates from the object manager
|
||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
// Prepare a list of candidate creatures and track if any exotic pet is found
|
||||
std::vector<const CreatureTemplate*> candidates;
|
||||
bool foundExotic = false;
|
||||
|
||||
// Iterate through all creature templates
|
||||
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||
{
|
||||
const CreatureTemplate& creature = itr->second;
|
||||
|
||||
// Skip if this creature is never tameable
|
||||
if (!creature.IsTameable(true))
|
||||
continue;
|
||||
|
||||
// Look up the family entry for this creature
|
||||
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
|
||||
if (!familyEntry)
|
||||
continue;
|
||||
|
||||
// Compare the family name in a case-insensitive way
|
||||
std::string familyName = familyEntry->Name[0];
|
||||
std::transform(familyName.begin(), familyName.end(), familyName.begin(), ::tolower);
|
||||
|
||||
if (familyName != lowerFamily)
|
||||
continue;
|
||||
|
||||
// If the creature is exotic, check Beast Mastery talent requirements
|
||||
if (IsExoticPet(&creature))
|
||||
{
|
||||
foundExotic = true;
|
||||
if (!HasBeastMastery(bot))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only add as candidate if this bot is allowed to tame it (including exotic rules)
|
||||
if (!creature.IsTameable(bot->CanTameExoticPets()))
|
||||
continue;
|
||||
|
||||
candidates.push_back(&creature);
|
||||
}
|
||||
|
||||
// If no candidates found, inform the user of the reason and return false
|
||||
if (candidates.empty())
|
||||
{
|
||||
if (foundExotic && !HasBeastMastery(bot))
|
||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
||||
else
|
||||
botAI->TellError("No tameable pet found with family: " + family);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Randomly select one candidate from the list to tame
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> dis(0, candidates.size() - 1);
|
||||
|
||||
const CreatureTemplate* selected = candidates[dis(gen)];
|
||||
|
||||
// Save the selected pet's name and id for feedback
|
||||
lastPetName = selected->Name;
|
||||
lastPetId = selected->Entry;
|
||||
// Attempt to create and set the new pet for the bot
|
||||
return CreateAndSetPet(selected->Entry);
|
||||
}
|
||||
|
||||
bool TameAction::RenamePet(const std::string& newName)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
Pet* pet = bot->GetPet();
|
||||
// Check if the bot currently has a pet
|
||||
if (!pet)
|
||||
{
|
||||
botAI->TellError("You have no pet to rename.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the new name: must not be empty and max 12 characters
|
||||
if (newName.empty() || newName.length() > 12)
|
||||
{
|
||||
botAI->TellError("Pet name must be between 1 and 12 alphabetic characters.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure all characters in the new name are alphabetic
|
||||
for (char c : newName)
|
||||
{
|
||||
if (!std::isalpha(static_cast<unsigned char>(c)))
|
||||
{
|
||||
botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z).");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the name: capitalize the first letter, lowercase the rest
|
||||
std::string normalized = newName;
|
||||
normalized[0] = std::toupper(normalized[0]);
|
||||
for (size_t i = 1; i < normalized.size(); ++i)
|
||||
normalized[i] = std::tolower(normalized[i]);
|
||||
|
||||
// Check if the new name is reserved or forbidden
|
||||
if (sObjectMgr->IsReservedName(normalized))
|
||||
{
|
||||
botAI->TellError("That pet name is forbidden. Please choose another name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the pet's name and save it to the database
|
||||
pet->SetName(normalized);
|
||||
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
|
||||
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
|
||||
|
||||
// Notify the master about the rename and give a tip to update the client name display
|
||||
botAI->TellMaster("Your pet has been renamed to " + normalized + "!");
|
||||
botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet.");
|
||||
|
||||
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
|
||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
|
||||
if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883))
|
||||
{
|
||||
bot->CastSpell(bot, 883, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
// Ensure the player is a hunter and at least level 10 (required for pets)
|
||||
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
||||
{
|
||||
botAI->TellError("Only level 10+ hunters can have pets.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve the creature template for the given entry (pet species info)
|
||||
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
|
||||
if (!creature)
|
||||
{
|
||||
botAI->TellError("Creature template not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the bot already has a current pet or an unslotted pet, remove them to avoid conflicts
|
||||
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
|
||||
{
|
||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
|
||||
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
||||
}
|
||||
if (bot->GetPetStable() && bot->GetPetStable()->GetUnslottedHunterPet())
|
||||
{
|
||||
bot->GetPetStable()->UnslottedPets.clear();
|
||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
|
||||
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
||||
}
|
||||
|
||||
// Create the new tamed pet from the specified creature entry
|
||||
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
|
||||
if (!pet)
|
||||
{
|
||||
botAI->TellError("Failed to create pet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the pet's level to one below the bot's current level, then add to the map and set to full level
|
||||
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel() - 1);
|
||||
pet->GetMap()->AddToMap(pet->ToCreature());
|
||||
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel());
|
||||
// Set the pet as the bot's active minion
|
||||
bot->SetMinion(pet, true);
|
||||
// Initialize talents appropriate for the pet's level
|
||||
pet->InitTalentForLevel();
|
||||
// Save pet to the database as the current pet
|
||||
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
|
||||
// Initialize available pet spells
|
||||
bot->PetSpellInitialize();
|
||||
|
||||
// Further initialize pet stats to match the bot's level
|
||||
pet->InitStatsForLevel(bot->GetLevel());
|
||||
pet->SetLevel(bot->GetLevel());
|
||||
// Set happiness and health of the pet to maximum values
|
||||
pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS)));
|
||||
pet->SetHealth(pet->GetMaxHealth());
|
||||
|
||||
// Enable autocast for all active (not removed) non-passive spells the pet knows
|
||||
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
|
||||
{
|
||||
if (itr->second.state == PETSPELL_REMOVED)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (spellInfo->IsPassive())
|
||||
continue;
|
||||
|
||||
pet->ToggleAutocast(spellInfo, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TameAction::AbandonPet()
|
||||
{
|
||||
// Get the bot player and its current pet (if any)
|
||||
Player* bot = botAI->GetBot();
|
||||
Pet* pet = bot->GetPet();
|
||||
|
||||
// Check if the bot has a pet and that it is a hunter pet
|
||||
if (pet && pet->getPetType() == HUNTER_PET)
|
||||
{
|
||||
// Remove the pet from the bot and mark it as deleted in the database
|
||||
bot->RemovePet(pet, PET_SAVE_AS_DELETED);
|
||||
// Inform the bot's master/player that the pet was abandoned
|
||||
botAI->TellMaster("Your pet has been abandoned.");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is no hunter pet, show an error message
|
||||
botAI->TellError("You have no hunter pet to abandon.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
34
src/strategy/actions/TameAction.h
Normal file
34
src/strategy/actions/TameAction.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_TAMEACTION_H
|
||||
#define _PLAYERBOT_TAMEACTION_H
|
||||
|
||||
#include <string>
|
||||
#include "Action.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class TameAction : public Action
|
||||
{
|
||||
public:
|
||||
TameAction(PlayerbotAI* botAI) : Action(botAI, "tame") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
bool SetPetByName(const std::string& name);
|
||||
bool SetPetById(uint32 id);
|
||||
bool SetPetByFamily(const std::string& family);
|
||||
bool RenamePet(const std::string& newName);
|
||||
bool CreateAndSetPet(uint32 creatureEntry);
|
||||
bool AbandonPet();
|
||||
|
||||
std::string lastPetName;
|
||||
uint32 lastPetId = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -73,7 +73,7 @@ bool TaxiAction::Execute(Event event)
|
||||
{
|
||||
if (Creature* npcPtr = ObjectAccessor::GetCreature(*bot, npcGuid))
|
||||
if (!movement.taxiNodes.empty())
|
||||
bot->ActivateTaxiPathTo(movement.taxiNodes, npcPtr);
|
||||
bot->ActivateTaxiPathTo(movement.taxiNodes, npcPtr, 0);
|
||||
},
|
||||
delay);
|
||||
botAI->SetNextCheckDelay(delay + 50);
|
||||
@@ -114,7 +114,7 @@ bool TaxiAction::Execute(Event event)
|
||||
return bot->ActivateTaxiPathTo({entry->from, entry->to}, npc, 0);
|
||||
}
|
||||
|
||||
if (!movement.taxiNodes.empty() && !bot->ActivateTaxiPathTo(movement.taxiNodes, npc))
|
||||
if (!movement.taxiNodes.empty() && !bot->ActivateTaxiPathTo(movement.taxiNodes, npc, 0))
|
||||
{
|
||||
movement.taxiNodes.clear();
|
||||
movement.Set(nullptr);
|
||||
|
||||
113
src/strategy/actions/TellGlyphsAction.cpp
Normal file
113
src/strategy/actions/TellGlyphsAction.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "TellGlyphsAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
#include "ObjectMgr.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "World.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
// -----------------------------------------------------------------
|
||||
// Cache : GlyphID (MiscValue) -> ItemTemplate*
|
||||
// -----------------------------------------------------------------
|
||||
std::unordered_map<uint32, ItemTemplate const*> s_GlyphItemCache;
|
||||
|
||||
void BuildGlyphItemCache()
|
||||
{
|
||||
if (!s_GlyphItemCache.empty())
|
||||
return;
|
||||
|
||||
ItemTemplateContainer const* store = sObjectMgr->GetItemTemplateStore();
|
||||
|
||||
for (auto const& kv : *store) // C++17 : range-for sur map
|
||||
{
|
||||
ItemTemplate const* proto = &kv.second;
|
||||
|
||||
if (!proto || proto->Class != ITEM_CLASS_GLYPH)
|
||||
continue;
|
||||
|
||||
for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
||||
{
|
||||
uint32 spellId = proto->Spells[i].SpellId;
|
||||
if (!spellId)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spell = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spell)
|
||||
continue;
|
||||
|
||||
for (uint32 eff = 0; eff <= EFFECT_2; ++eff)
|
||||
{
|
||||
if (spell->Effects[eff].Effect != SPELL_EFFECT_APPLY_GLYPH)
|
||||
continue;
|
||||
|
||||
uint32 glyphId = spell->Effects[eff].MiscValue;
|
||||
if (glyphId)
|
||||
s_GlyphItemCache[glyphId] = proto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Action
|
||||
// -----------------------------------------------------------------
|
||||
bool TellGlyphsAction::Execute(Event event)
|
||||
{
|
||||
//-----------------------------------------------------------------
|
||||
// 1. who sended the wisp ? (source of event)
|
||||
//-----------------------------------------------------------------
|
||||
Player* sender = event.getOwner(); // API Event
|
||||
if (!sender)
|
||||
return false;
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// 2. Generate glyphId cache -> item
|
||||
//-----------------------------------------------------------------
|
||||
BuildGlyphItemCache();
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// 3. Look at the 6 glyphs sockets
|
||||
//-----------------------------------------------------------------
|
||||
std::ostringstream list;
|
||||
bool first = true;
|
||||
|
||||
for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
|
||||
{
|
||||
uint32 glyphId = bot->GetGlyph(slot);
|
||||
if (!glyphId)
|
||||
continue;
|
||||
|
||||
auto it = s_GlyphItemCache.find(glyphId);
|
||||
if (it == s_GlyphItemCache.end())
|
||||
continue; // No glyph found (rare)
|
||||
|
||||
if (!first)
|
||||
list << ", ";
|
||||
|
||||
// chat->FormatItem
|
||||
list << chat->FormatItem(it->second);
|
||||
first = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// 4. Send chat messages
|
||||
//-----------------------------------------------------------------
|
||||
if (first) // no glyphs
|
||||
botAI->TellMaster("No glyphs equipped");
|
||||
else
|
||||
botAI->TellMaster(std::string("Glyphs: ") + list.str());
|
||||
|
||||
return true;
|
||||
}
|
||||
21
src/strategy/actions/TellGlyphsAction.h
Normal file
21
src/strategy/actions/TellGlyphsAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_TELLGLYPHSACTION_H
|
||||
#define _PLAYERBOT_TELLGLYPHSACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class TellGlyphsAction : public Action
|
||||
{
|
||||
public:
|
||||
TellGlyphsAction(PlayerbotAI* ai, std::string const name = "glyphs")
|
||||
: Action(ai, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
#include "TradeAction.h"
|
||||
|
||||
#include "ChatHelper.h"
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
@@ -15,11 +14,8 @@ bool TradeAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
|
||||
// Table with prefixes to be excluded from analysis
|
||||
static const std::vector<std::string> excludedPrefixes = {"RPLL_H_"};
|
||||
|
||||
// If text starts with any excluded prefix, don't process it further.
|
||||
for (const auto& prefix : excludedPrefixes)
|
||||
for (const auto& prefix : sPlayerbotAIConfig->tradeActionExcludedPrefixes)
|
||||
{
|
||||
if (text.find(prefix) == 0)
|
||||
return false;
|
||||
|
||||
@@ -175,6 +175,8 @@ bool MaintenanceAction::Execute(Event event)
|
||||
factory.InitAmmo();
|
||||
factory.InitFood();
|
||||
factory.InitReagents();
|
||||
factory.InitConsumables();
|
||||
factory.InitPotions();
|
||||
factory.InitTalentsTree(true);
|
||||
factory.InitPet();
|
||||
factory.InitPetTalents();
|
||||
@@ -184,9 +186,8 @@ bool MaintenanceAction::Execute(Event event)
|
||||
factory.InitReputation();
|
||||
factory.InitSpecialSpells();
|
||||
factory.InitMounts();
|
||||
factory.InitGlyphs(true);
|
||||
factory.InitGlyphs(false);
|
||||
factory.InitKeyring();
|
||||
factory.InitPotions();
|
||||
if (bot->GetLevel() >= sPlayerbotAIConfig->minEnchantingBotLevel)
|
||||
factory.ApplyEnchantAndGemsNew();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ bool TravelAction::Execute(Event event)
|
||||
std::list<Unit*> targets;
|
||||
Acore::AnyUnitInObjectRangeCheck u_check(bot, sPlayerbotAIConfig->sightDistance * 2);
|
||||
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
|
||||
Cell::VisitAllObjects(bot, searcher, sPlayerbotAIConfig->sightDistance);
|
||||
Cell::VisitObjects(bot, searcher, sPlayerbotAIConfig->sightDistance);
|
||||
|
||||
for (Unit* unit : targets)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "Playerbots.h"
|
||||
#include "WorldSession.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
std::vector<std::string> split(std::string const s, char delim);
|
||||
|
||||
@@ -70,7 +72,9 @@ void UnequipAction::UnequipItem(Item* item)
|
||||
|
||||
WorldPacket packet(CMSG_AUTOSTORE_BAG_ITEM, 3);
|
||||
packet << bagIndex << slot << dstBag;
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(packet);
|
||||
WorldPackets::Item::AutoStoreBagItem nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoStoreBagItemOpcode(nicePacket);
|
||||
|
||||
std::ostringstream out;
|
||||
out << chat->FormatItem(item->GetTemplate()) << " unequipped";
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Event.h"
|
||||
#include "ItemUsageValue.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
bool UseItemAction::Execute(Event event)
|
||||
{
|
||||
@@ -238,9 +239,24 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
||||
{
|
||||
targetFlag = TARGET_FLAG_NONE;
|
||||
packet << targetFlag;
|
||||
packet << bot->GetPackGUID();
|
||||
targetSelected = true;
|
||||
out << " on self";
|
||||
|
||||
// Use the actual target if provided
|
||||
if (unitTarget)
|
||||
{
|
||||
packet << unitTarget->GetGUID();
|
||||
targetSelected = true;
|
||||
// If the target is bot or is an enemy, say "on self"
|
||||
if (unitTarget == bot || (unitTarget->IsHostileTo(bot)))
|
||||
out << " on self";
|
||||
else
|
||||
out << " on " << unitTarget->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
packet << bot->GetPackGUID();
|
||||
targetSelected = true;
|
||||
out << " on self";
|
||||
}
|
||||
}
|
||||
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
@@ -309,8 +325,8 @@ void UseItemAction::TellConsumableUse(Item* item, std::string const action, floa
|
||||
|
||||
bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
{
|
||||
WorldPacket* const packet = new WorldPacket(CMSG_SOCKET_GEMS);
|
||||
*packet << item->GetGUID();
|
||||
WorldPacket packet(CMSG_SOCKET_GEMS);
|
||||
packet << item->GetGUID();
|
||||
|
||||
bool fits = false;
|
||||
for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS;
|
||||
@@ -322,14 +338,14 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
{
|
||||
if (fits)
|
||||
{
|
||||
*packet << ObjectGuid::Empty;
|
||||
packet << ObjectGuid::Empty;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(enchant_slot));
|
||||
if (!enchant_id)
|
||||
{
|
||||
*packet << gem->GetGUID();
|
||||
packet << gem->GetGUID();
|
||||
fits = true;
|
||||
continue;
|
||||
}
|
||||
@@ -337,20 +353,20 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
|
||||
if (!enchantEntry || !enchantEntry->GemID)
|
||||
{
|
||||
*packet << gem->GetGUID();
|
||||
packet << gem->GetGUID();
|
||||
fits = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (replace && enchantEntry->GemID != gem->GetTemplate()->ItemId)
|
||||
{
|
||||
*packet << gem->GetGUID();
|
||||
packet << gem->GetGUID();
|
||||
fits = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
*packet << ObjectGuid::Empty;
|
||||
packet << ObjectGuid::Empty;
|
||||
}
|
||||
|
||||
if (fits)
|
||||
@@ -360,7 +376,9 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
||||
out << " with " << chat->FormatItem(gem->GetTemplate());
|
||||
botAI->TellMaster(out);
|
||||
|
||||
bot->GetSession()->HandleSocketOpcode(*packet);
|
||||
WorldPackets::Item::SocketGems nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleSocketOpcode(nicePacket);
|
||||
}
|
||||
|
||||
return fits;
|
||||
|
||||
@@ -110,7 +110,7 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
|
||||
std::list<GameObject*> targets;
|
||||
AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
|
||||
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(summoner, targets, u_check);
|
||||
Cell::VisitAllObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
|
||||
Cell::VisitObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
|
||||
|
||||
for (GameObject* go : targets)
|
||||
{
|
||||
@@ -130,7 +130,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
||||
std::list<Unit*> targets;
|
||||
Acore::AnyUnitInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
|
||||
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(summoner, targets, u_check);
|
||||
Cell::VisitAllObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
|
||||
Cell::VisitObjects(summoner, searcher, sPlayerbotAIConfig->sightDistance);
|
||||
|
||||
for (Unit* unit : targets)
|
||||
{
|
||||
|
||||
18
src/strategy/actions/WipeAction.cpp
Normal file
18
src/strategy/actions/WipeAction.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "PlayerbotAI.h"
|
||||
#include "WipeAction.h"
|
||||
|
||||
bool WipeAction::Execute(Event event)
|
||||
{
|
||||
Player* master = event.getOwner();
|
||||
|
||||
if (botAI->GetMaster()->GetGUID() != event.getOwner()->GetGUID())
|
||||
return false;
|
||||
|
||||
bot->Kill(bot, bot);
|
||||
return true;
|
||||
}
|
||||
24
src/strategy/actions/WipeAction.h
Normal file
24
src/strategy/actions/WipeAction.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_WIPEACTION_H
|
||||
#define _PLAYERBOT_WIPEACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class WipeAction : public Action
|
||||
{
|
||||
public:
|
||||
WipeAction(PlayerbotAI* botAI) : Action(botAI, "wipe") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
std::string bossName;
|
||||
};
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user