Compare commits

..

No commits in common. "da3237fa78549685c2531a5723503883da1b45b1" and "c8dce882d621f6250a96370bfd3c1bf79e9ebc4e" have entirely different histories.

253 changed files with 2157 additions and 9780 deletions

View File

@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: 'mod-playerbots/azerothcore-wotlk' repository: 'mod-playerbots/azerothcore-wotlk'
ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }} ref: 'Playerbot'
- name: Set reusable strings - name: Set reusable strings
id: strings id: strings

View File

@ -1,39 +0,0 @@
name: Label translation PRs
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
label-translation:
runs-on: ubuntu-latest
steps:
- name: Fetch PR diff
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api \
repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} \
--header "Accept: application/vnd.github.v3.diff" > pr.diff
- name: Detect ai_playerbot_texts inserts
id: detect
run: |
if grep -E '^\+.*INSERT[[:space:]]+INTO[[:space:]]+`?ai_playerbot_texts`?' pr.diff; then
echo "has_translation=true" >> "$GITHUB_OUTPUT"
else
echo "has_translation=false" >> "$GITHUB_OUTPUT"
fi
- name: Add label
if: steps.detect.outputs.has_translation == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} \
--add-label "Added translation"

View File

@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
repository: 'mod-playerbots/azerothcore-wotlk' repository: 'mod-playerbots/azerothcore-wotlk'
ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }} ref: 'Playerbot'
- name: Checkout Playerbot Module - name: Checkout Playerbot Module
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: 'mod-playerbots/azerothcore-wotlk' repository: 'mod-playerbots/azerothcore-wotlk'
ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }} ref: 'Playerbot'
path: 'ac' path: 'ac'
- name: Checkout Playerbot Module - name: Checkout Playerbot Module
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@ -61,14 +61,29 @@ any impact on performance, you may skip these question. If necessary, a maintain
## Messages to Translate
<!--
Bot messages have to be translatable, but you don't need to do the translations here. You only need to make sure
the message is in a translatable format, and list in the table the message_key and the default English message.
Search for GetBotTextOrDefault in the codebase for examples.
-->
- Does this change add bot messages to translate?
- - [ ] No
- - [ ] Yes (**list messages in the table**)
| Message key | Default message |
| --------------- | ------------------ |
| | |
| | |
## AI Assistance ## AI Assistance
<!-- <!--
AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor. AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not understand. We expect contributors to be honest about what they do and do not understand.
--> -->
Was AI assistance used while working on this change? - Was AI assistance used while working on this change?
- - [ ] No - - [ ] No
- - [ ] Yes (**explain below**) - - [ ] Yes (**explain below**)
<!-- <!--
If yes, please specify: If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation). - Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation).
@ -77,25 +92,12 @@ If yes, please specify:
<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format. This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German, Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as an example of a translation SQL
update, whose content are called within the codebase at src/strategy/actions/FishingAction.cpp
-->
## Final Checklist ## Final Checklist
- - [ ] Stability is not compromised. - - [ ] Stability is not compromised.
- - [ ] Performance impact is understood, tested, and acceptable. - - [ ] Performance impact is understood, tested, and acceptable.
- - [ ] Added logic complexity is justified and explained. - - [ ] Added logic complexity is justified and explained.
- - [ ] Any new bot dialogue lines are translated.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands). - - [ ] Documentation updated if needed (Conf comments, WiKi commands).
## Notes for Reviewers ## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request. --> <!-- Anything else that's helpful to review or test your pull request. -->

View File

@ -582,7 +582,7 @@ AiPlayerbot.AutoGearScoreLimit = 0
AiPlayerbot.BotCheats = "food,taxi,raid" AiPlayerbot.BotCheats = "food,taxi,raid"
# List of attunement quests (comma-separated list of quest IDs) that are automatically completed for all bots. # List of attunement quests (comma-separated list of quest IDs) that are automatically completed for all bots.
# While mod-playerbots does not restore removed attunement requirements, other mods, such as mod-individual-progression, may do so. # While mod-playerbots does not restore removed attunement requirements, although other mods, such as mod-individual-progression, may do so.
# This is meant to exclude bots from such requirements. # This is meant to exclude bots from such requirements.
# #
# Default: # Default:
@ -651,14 +651,9 @@ AiPlayerbot.BotTaxiGapJitterMs = 100
#################################################################################################### ####################################################################################################
# PROFESSIONS # PROFESSIONS
# Note: Random bots currently do not get professions
# #
# Percentage of randombots in each class bucket that receive a class-matching
# weighted profession combination. The remaining randombots use the weighted
# random sane-pair profession pool.
# Default: 30
AiPlayerbot.ClassMatchingProfessionChance = 30
# Automatically adds the 'master fishing' strategy to bots that have the fishing skill when the bots master fishes. # Automatically adds the 'master fishing' strategy to bots that have the fishing skill when the bots master fishes.
# Default: 1 (Enabled) # Default: 1 (Enabled)
AiPlayerbot.EnableFishingWithMaster = 1 AiPlayerbot.EnableFishingWithMaster = 1
@ -806,27 +801,6 @@ AiPlayerbot.RandomGearQualityLimit = 3
# Default: 0 (no limit) # Default: 0 (no limit)
AiPlayerbot.RandomGearScoreLimit = 0 AiPlayerbot.RandomGearScoreLimit = 0
# Prefer armor of the class's ideal type: apply 3x score multiplier to class-appropriate armor.
# When enabled, Warriors strongly prefer plate, Shamans prefer mail, etc.
# A truly superior item can still win (no hard filtering), but same-quality
# armor of the preferred type will score 3x higher and be equipped instead.
#
# ARMOR TYPE PREFERENCES:
# Plate: Warriors, Paladins, Death Knights
# Mail: Hunters, Shamans
# Leather: Rogues, Druids
# Cloth: Priests, Mages, Warlocks
#
# Default: 0 (disabled)
AiPlayerbot.PreferClassArmorType = 0
# When enabled, bots prefer spec-appropriate weapons based on speed and weapon type during autogear.
# Examples: Arms Warriors favor slow 2H axes/polearms (Axe Specialization), Combat Rogues
# favor a slow MH with a fast OH, and Enhancement Shamans favor synchronized slow 1H weapons.
# Default: 0 (disabled)
AiPlayerbot.PreferredSpecWeapons = 0
# If disabled, random bots can only upgrade equipment through looting and quests # If disabled, random bots can only upgrade equipment through looting and quests
# Default: 1 (enabled) # Default: 1 (enabled)
AiPlayerbot.IncrementalGearInit = 1 AiPlayerbot.IncrementalGearInit = 1
@ -891,65 +865,34 @@ AiPlayerbot.ExcludedHunterPetFamilies = ""
# #
# #
#################################################################################################### ####################################################################################################
####################################################################################################
# ACTIVITY
#
# BotActiveAlone
# - Controls how many bots are active when no real players are nearby.
# - Think of it as a rough percentage: 10 means approximately 10% of bots will be active.
# Not exact — the actual number may vary slightly per rotation cycle.
# - The active bots rotate: every <DurationSeconds> a different set of bots takes a turn.
# - The real number of active bots will always be higher than this value, because bots in
# combat, dungeons, battlegrounds, LFG queue, groups with real players, etc. are always
# forced active on top of this (see force rules below).
# - Set to 100 (with SmartScale off) = all bots always active. Maximum server load.
# - Set to 0 = only bots that match a force rule below will be active.
#
# BotActiveAloneDurationSeconds
# - How often the active roster rotates (in seconds). A different group of bots wakes up
# and the previous group may go idle.
# - This is a minimum, not exact. If a bot is in combat or meets any force rule when the
# rotation happens, it stays active until those conditions end — it won't be cut off
# mid-fight just because its turn expired.
#
AiPlayerbot.BotActiveAlone = 10
AiPlayerbot.BotActiveAloneDurationSeconds = 30
####################################################################################################
# ACTIVITIES
# #
# Force-active rules (1 = on, 0 = off)
# These override the percentage above. If any of these conditions is true, the bot stays active.
#
# InRadius - A real player is within this many yards (set to 0 to disable).
# InZone - A real player is in the same zone (e.g. Elwynn Forest).
# InMap - A real player is on the same continent (e.g. Eastern Kingdoms).
# IsFriend - A real player has this bot on their friends list.
# InGuild - This bot is in a guild that has a real player in it.
#
# Bots are also always forced active (not configurable) when:
# in combat, inside a dungeon/raid/BG, in a BG or LFG queue,
# grouped with a real player, or controlled by a real player.
# #
# Specify percent of active bots
# The default is 100% but will be automatically adjusted if botActiveAloneSmartScale
# is enabled. Regardless, this value is only applied to inactive areas where no real players
# are detected. When real players are nearby, the value is always enforced to 100%
AiPlayerbot.BotActiveAlone = 100
# Force botActiveAlone when bot is within the specified distance of a real player
AiPlayerbot.BotActiveAloneForceWhenInRadius = 150 AiPlayerbot.BotActiveAloneForceWhenInRadius = 150
AiPlayerbot.BotActiveAloneForceWhenInZone = 1 AiPlayerbot.BotActiveAloneForceWhenInZone = 1
AiPlayerbot.BotActiveAloneForceWhenInMap = 0 AiPlayerbot.BotActiveAloneForceWhenInMap = 0
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 0 AiPlayerbot.BotActiveAloneForceWhenIsFriend = 1
AiPlayerbot.BotActiveAloneForceWhenInGuild = 1 AiPlayerbot.BotActiveAloneForceWhenInGuild = 1
# SmartScale — automatically reduces active bots when the server is struggling. # SmartScale (automatic scaling of percentage of active bots based on latency)
# Monitors the server's update time (how long each server tick takes in milliseconds). # The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
# When the server slows down, fewer bots are kept active to reduce load. # (The scaling will be overruled by the BotActiveAloneForceWhen...rules)
# #
# Floor (default 50ms) - Below this, no reduction. Server is running fine. # Limitfloor - when DIFF (latency) is above floor, activity scaling begins
# Ceiling (default 200ms) - At or above this, all non-forced bots are paused. # LimitCeiling - when DIFF (latency) is above ceiling, activity is 0%
# Between floor and ceiling, activity scales down gradually.
# Example: BotActiveAlone=10, floor=50, ceiling=200
# Server at 50ms → ~10% active (no reduction)
# Server at 125ms → ~5% active (half reduction)
# Server at 200ms → 0% active (only forced bots remain)
# #
# MinLevel/MaxLevel — only bots within this level range are affected by SmartScale. # MinLevel - only apply scaling when level is above or equal to min(bot)Level
# Bots outside the range always use the full BotActiveAlone value. # MaxLevel - only apply scaling when level is lower or equal of max(bot)Level
# Force rules always win over SmartScale.
# #
AiPlayerbot.botActiveAloneSmartScale = 1 AiPlayerbot.botActiveAloneSmartScale = 1
AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor = 50 AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor = 50
@ -1064,7 +1007,6 @@ AiPlayerbot.EnableNewRpgStrategy = 1
# DoQuest (Default: 60 Select quest from the quest log and head to the location to attempt completion) # 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) # 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) # Rest (Default: 5 Take a break for a while and do nothing)
# OutdoorPvp (Default: 10 Participate in outdoor PvP capture points if already in an outdoor PvP zone)
AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15 AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15
AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20 AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20
AiPlayerbot.RpgStatusProbWeight.GoGrind = 15 AiPlayerbot.RpgStatusProbWeight.GoGrind = 15
@ -1072,7 +1014,6 @@ AiPlayerbot.RpgStatusProbWeight.GoCamp = 10
AiPlayerbot.RpgStatusProbWeight.DoQuest = 60 AiPlayerbot.RpgStatusProbWeight.DoQuest = 60
AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15 AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15
AiPlayerbot.RpgStatusProbWeight.Rest = 5 AiPlayerbot.RpgStatusProbWeight.Rest = 5
AiPlayerbot.RpgStatusProbWeight.OutdoorPvp = 10
# Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy # Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy
# Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel # Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel
@ -1345,7 +1286,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
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" 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) # PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973,4085,4086,4087,4088" AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"
# Improve reaction speeds in battlegrounds and arenas (may cause lag) # Improve reaction speeds in battlegrounds and arenas (may cause lag)
AiPlayerbot.FastReactInBG = 1 AiPlayerbot.FastReactInBG = 1
@ -1746,7 +1687,7 @@ AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530135201051
AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55 AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55
AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005 AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005
AiPlayerbot.PremadeSpecName.9.2 = destro pve AiPlayerbot.PremadeSpecName.9.2 = destro pve
AiPlayerbot.PremadeSpecGlyph.9.2 = 45785,43390,42454,43394,43393,42453 AiPlayerbot.PremadeSpecGlyph.9.2 = 45785,43390,42454,43394,43393,45785
AiPlayerbot.PremadeSpecLink.9.2.60 = --05203215200231051305031151 AiPlayerbot.PremadeSpecLink.9.2.60 = --05203215200231051305031151
AiPlayerbot.PremadeSpecLink.9.2.80 = 23-0302-05203215220331051335231351 AiPlayerbot.PremadeSpecLink.9.2.80 = 23-0302-05203215220331051335231351
AiPlayerbot.PremadeSpecName.9.3 = affli pvp AiPlayerbot.PremadeSpecName.9.3 = affli pvp
@ -1823,10 +1764,10 @@ AiPlayerbot.PremadeSpecLink.11.6.80 = 05320021--230033312031500531353013251
# Requires sending the command "nc +worldbuff" in chat to a bot (or a group of bots) to enable # Requires sending the command "nc +worldbuff" in chat to a bot (or a group of bots) to enable
# Each entry in the matrix should be formatted as follows: Entry:FactionID,ClassID,SpecID,MinimumLevel,MaximumLevel:SpellID1,SpellID2,etc.; # Each entry in the matrix should be formatted as follows: Entry:FactionID,ClassID,SpecID,MinimumLevel,MaximumLevel:SpellID1,SpellID2,etc.;
# FactionID may be set to 0 for the entry to apply buffs to bots of either faction # FactionID may be set to 0 for the entry to apply buffs to bots of either faction
# The default entries create a cross-faction level 60-69 Vanilla buffs, level 70-79 TBC buffs, and level 80 buffs for each implemented pve spec from the "Premade Specs" section # 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 # The default entries may be deleted or modified, and new custom entries may be added
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; # WARRIOR ARMS TBC 33:0,1,0,70,79:28520,33256; # WARRIOR FURY TBC 34:0,1,1,70,79:28520,33256; # WARRIOR PROTECTION TBC 35:0,1,2,70,79:28518,33257; # PALADIN HOLY TBC 36:0,2,0,70,79:28491,39627,33263; # PALADIN PROTECTION TBC 37:0,2,1,70,79:28518,33257; # PALADIN RETRIBUTION TBC 38:0,2,2,70,79:28520,33256; # HUNTER BEAST TBC 39:0,3,0,70,79:28520,33261; # HUNTER MARKSMANSHIP TBC 40:0,3,1,70,79:28520,33261; # HUNTER SURVIVAL TBC 41:0,3,2,70,79:28520,33261; # ROGUE ASSASSINATION TBC 42:0,4,0,70,79:28520,33261; # ROGUE COMBAT TBC 43:0,4,1,70,79:28520,33261; # ROGUE SUBTLETY TBC 44:0,4,2,70,79:28520,33261; # PRIEST DISCIPLINE TBC 45:0,5,0,70,79:28491,39627,33263; # PRIEST HOLY TBC 46:0,5,1,70,79:28491,39627,33263; # PRIEST SHADOW TBC 47:0,5,2,70,79:28540,33263; # SHAMAN ELEMENTAL TBC 48:0,7,0,70,79:28521,33263; # SHAMAN ENHANCEMENT TBC 49:0,7,1,70,79:28520,33261; # SHAMAN RESTORATION TBC 50:0,7,2,70,79:28491,39627,33263; # MAGE ARCANE TBC 51:0,8,0,70,79:28521,33263; # MAGE FIRE TBC 52:0,8,1,70,79:28540,33263; # MAGE FROST TBC 53:0,8,2,70,79:28540,33263; # WARLOCK AFFLICTION TBC 54:0,9,0,70,79:28540,33263; # WARLOCK DEMONOLOGY TBC 55:0,9,1,70,79:28540,33263; # WARLOCK DESTRUCTION TBC 56:0,9,2,70,79:28540,33263; # DRUID BALANCE TBC 57:0,11,0,70,79:28521,33263; # DRUID FERAL BEAR TBC 58:0,11,1,70,79:28518,33257; # DRUID RESTORATION TBC 59:0,11,2,70,79:28491,39627,33263; # DRUID FERAL CAT TBC 60:0,11,3,70,79:28520,33261; # WARRIOR ARMS VANILLA 61:0,1,0,60,69:17538,24799; # WARRIOR FURY VANILLA 62:0,1,1,60,69:17538,24799; # WARRIOR PROTECTION VANILLA 63:0,1,2,60,69:17626,25661; # PALADIN HOLY VANILLA 64:0,2,0,60,69:17627,18194; # PALADIN PROTECTION VANILLA 65:0,2,1,60,69:17626,25661; # PALADIN RETRIBUTION VANILLA 66:0,2,2,60,69:17628,24799; # HUNTER BEAST VANILLA 67:0,3,0,60,69:17538,18192; # HUNTER MARKSMANSHIP VANILLA 68:0,3,1,60,69:17538,18192; # HUNTER SURVIVAL VANILLA 69:0,3,2,60,69:17538,18192; # ROGUE ASSASSINATION VANILLA 70:0,4,0,60,69:17538,18192; # ROGUE COMBAT VANILLA 71:0,4,1,60,69:17538,18192; # ROGUE SUBTLETY VANILLA 72:0,4,2,60,69:17538,18192; # PRIEST DISCIPLINE VANILLA 73:0,5,0,60,69:17628,18194; # PRIEST HOLY VANILLA 74:0,5,1,60,69:17627,18194; # PRIEST SHADOW VANILLA 75:0,5,2,60,69:17628,18194; # SHAMAN ELEMENTAL VANILLA 76:0,7,0,60,69:17628,18194; # SHAMAN ENHANCEMENT VANILLA 77:0,7,1,60,69:17538,24799; # SHAMAN RESTORATION VANILLA 78:0,7,2,60,69:17627,18194; # MAGE ARCANE VANILLA 79:0,8,0,60,69:17628,18194; # MAGE FIRE VANILLA 80:0,8,1,60,69:17628,18194; # MAGE FROST VANILLA 81:0,8,2,60,69:17628,18194; # WARLOCK AFFLICTION VANILLA 82:0,9,0,60,69:17628,25661; # WARLOCK DEMONOLOGY VANILLA 83:0,9,1,60,69:17628,25661; # WARLOCK DESTRUCTION VANILLA 84:0,9,2,60,69:17628,25661; # DRUID BALANCE VANILLA 85:0,11,0,60,69:17628,18194; # DRUID FERAL BEAR VANILLA 86:0,11,1,60,69:17626,25661; # DRUID RESTORATION VANILLA 87:0,11,2,60,69:17627,18194; # DRUID FERAL CAT VANILLA 88:0,11,3,60,69:17538,24799 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
# #
# #
@ -1857,24 +1798,12 @@ AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIO
# #
# #
# arms pve
AiPlayerbot.RandomClassSpecProb.1.0 = 20 AiPlayerbot.RandomClassSpecProb.1.0 = 20
AiPlayerbot.RandomClassSpecIndex.1.0 = 0 AiPlayerbot.RandomClassSpecIndex.1.0 = 0
# fury pve
AiPlayerbot.RandomClassSpecProb.1.1 = 40 AiPlayerbot.RandomClassSpecProb.1.1 = 40
AiPlayerbot.RandomClassSpecIndex.1.1 = 1 AiPlayerbot.RandomClassSpecIndex.1.1 = 1
# prot pve
AiPlayerbot.RandomClassSpecProb.1.2 = 40 AiPlayerbot.RandomClassSpecProb.1.2 = 40
AiPlayerbot.RandomClassSpecIndex.1.2 = 2 AiPlayerbot.RandomClassSpecIndex.1.2 = 2
# arms pvp
AiPlayerbot.RandomClassSpecProb.1.3 = 0
AiPlayerbot.RandomClassSpecIndex.1.3 = 3
# fury pvp
AiPlayerbot.RandomClassSpecProb.1.4 = 0
AiPlayerbot.RandomClassSpecIndex.1.4 = 4
# prot pvp
AiPlayerbot.RandomClassSpecProb.1.5 = 0
AiPlayerbot.RandomClassSpecIndex.1.5 = 5
# #
# #
@ -1886,24 +1815,12 @@ AiPlayerbot.RandomClassSpecIndex.1.5 = 5
# #
# #
# holy pve
AiPlayerbot.RandomClassSpecProb.2.0 = 30 AiPlayerbot.RandomClassSpecProb.2.0 = 30
AiPlayerbot.RandomClassSpecIndex.2.0 = 0 AiPlayerbot.RandomClassSpecIndex.2.0 = 0
# prot pve
AiPlayerbot.RandomClassSpecProb.2.1 = 40 AiPlayerbot.RandomClassSpecProb.2.1 = 40
AiPlayerbot.RandomClassSpecIndex.2.1 = 1 AiPlayerbot.RandomClassSpecIndex.2.1 = 1
# ret pve
AiPlayerbot.RandomClassSpecProb.2.2 = 30 AiPlayerbot.RandomClassSpecProb.2.2 = 30
AiPlayerbot.RandomClassSpecIndex.2.2 = 2 AiPlayerbot.RandomClassSpecIndex.2.2 = 2
# holy pvp
AiPlayerbot.RandomClassSpecProb.2.3 = 0
AiPlayerbot.RandomClassSpecIndex.2.3 = 3
# prot pvp
AiPlayerbot.RandomClassSpecProb.2.4 = 0
AiPlayerbot.RandomClassSpecIndex.2.4 = 4
# ret pvp
AiPlayerbot.RandomClassSpecProb.2.5 = 0
AiPlayerbot.RandomClassSpecIndex.2.5 = 5
# #
# #
@ -1915,24 +1832,12 @@ AiPlayerbot.RandomClassSpecIndex.2.5 = 5
# #
# #
# bm pve
AiPlayerbot.RandomClassSpecProb.3.0 = 33 AiPlayerbot.RandomClassSpecProb.3.0 = 33
AiPlayerbot.RandomClassSpecIndex.3.0 = 0 AiPlayerbot.RandomClassSpecIndex.3.0 = 0
# mm pve
AiPlayerbot.RandomClassSpecProb.3.1 = 33 AiPlayerbot.RandomClassSpecProb.3.1 = 33
AiPlayerbot.RandomClassSpecIndex.3.1 = 1 AiPlayerbot.RandomClassSpecIndex.3.1 = 1
# surv pve
AiPlayerbot.RandomClassSpecProb.3.2 = 33 AiPlayerbot.RandomClassSpecProb.3.2 = 33
AiPlayerbot.RandomClassSpecIndex.3.2 = 2 AiPlayerbot.RandomClassSpecIndex.3.2 = 2
# bm pvp
AiPlayerbot.RandomClassSpecProb.3.3 = 0
AiPlayerbot.RandomClassSpecIndex.3.3 = 3
# mm pvp
AiPlayerbot.RandomClassSpecProb.3.4 = 0
AiPlayerbot.RandomClassSpecIndex.3.4 = 4
# surv pvp
AiPlayerbot.RandomClassSpecProb.3.5 = 0
AiPlayerbot.RandomClassSpecIndex.3.5 = 5
# #
# #
@ -1944,24 +1849,12 @@ AiPlayerbot.RandomClassSpecIndex.3.5 = 5
# #
# #
# as pve
AiPlayerbot.RandomClassSpecProb.4.0 = 45 AiPlayerbot.RandomClassSpecProb.4.0 = 45
AiPlayerbot.RandomClassSpecIndex.4.0 = 0 AiPlayerbot.RandomClassSpecIndex.4.0 = 0
# combat pve
AiPlayerbot.RandomClassSpecProb.4.1 = 45 AiPlayerbot.RandomClassSpecProb.4.1 = 45
AiPlayerbot.RandomClassSpecIndex.4.1 = 1 AiPlayerbot.RandomClassSpecIndex.4.1 = 1
# subtlety pve
AiPlayerbot.RandomClassSpecProb.4.2 = 10 AiPlayerbot.RandomClassSpecProb.4.2 = 10
AiPlayerbot.RandomClassSpecIndex.4.2 = 2 AiPlayerbot.RandomClassSpecIndex.4.2 = 2
# as pvp
AiPlayerbot.RandomClassSpecProb.4.3 = 0
AiPlayerbot.RandomClassSpecIndex.4.3 = 3
# combat pvp
AiPlayerbot.RandomClassSpecProb.4.4 = 0
AiPlayerbot.RandomClassSpecIndex.4.4 = 4
# subtlety pvp
AiPlayerbot.RandomClassSpecProb.4.5 = 0
AiPlayerbot.RandomClassSpecIndex.4.5 = 5
# #
# #
@ -1973,24 +1866,12 @@ AiPlayerbot.RandomClassSpecIndex.4.5 = 5
# #
# #
# disc pve
AiPlayerbot.RandomClassSpecProb.5.0 = 40 AiPlayerbot.RandomClassSpecProb.5.0 = 40
AiPlayerbot.RandomClassSpecIndex.5.0 = 0 AiPlayerbot.RandomClassSpecIndex.5.0 = 0
# holy pve
AiPlayerbot.RandomClassSpecProb.5.1 = 35 AiPlayerbot.RandomClassSpecProb.5.1 = 35
AiPlayerbot.RandomClassSpecIndex.5.1 = 1 AiPlayerbot.RandomClassSpecIndex.5.1 = 1
# shadow pve
AiPlayerbot.RandomClassSpecProb.5.2 = 25 AiPlayerbot.RandomClassSpecProb.5.2 = 25
AiPlayerbot.RandomClassSpecIndex.5.2 = 2 AiPlayerbot.RandomClassSpecIndex.5.2 = 2
# disc pvp
AiPlayerbot.RandomClassSpecProb.5.3 = 0
AiPlayerbot.RandomClassSpecIndex.5.3 = 3
# holy pvp
AiPlayerbot.RandomClassSpecProb.5.4 = 0
AiPlayerbot.RandomClassSpecIndex.5.4 = 4
# shadow pvp
AiPlayerbot.RandomClassSpecProb.5.5 = 0
AiPlayerbot.RandomClassSpecIndex.5.5 = 5
# #
# #
@ -2002,27 +1883,12 @@ AiPlayerbot.RandomClassSpecIndex.5.5 = 5
# #
# #
# blood pve
AiPlayerbot.RandomClassSpecProb.6.0 = 30 AiPlayerbot.RandomClassSpecProb.6.0 = 30
AiPlayerbot.RandomClassSpecIndex.6.0 = 0 AiPlayerbot.RandomClassSpecIndex.6.0 = 0
# frost pve
AiPlayerbot.RandomClassSpecProb.6.1 = 40 AiPlayerbot.RandomClassSpecProb.6.1 = 40
AiPlayerbot.RandomClassSpecIndex.6.1 = 1 AiPlayerbot.RandomClassSpecIndex.6.1 = 1
# unholy pve
AiPlayerbot.RandomClassSpecProb.6.2 = 30 AiPlayerbot.RandomClassSpecProb.6.2 = 30
AiPlayerbot.RandomClassSpecIndex.6.2 = 2 AiPlayerbot.RandomClassSpecIndex.6.2 = 2
# double aura blood pve
AiPlayerbot.RandomClassSpecProb.6.3 = 0
AiPlayerbot.RandomClassSpecIndex.6.3 = 3
# blood pvp
AiPlayerbot.RandomClassSpecProb.6.4 = 0
AiPlayerbot.RandomClassSpecIndex.6.4 = 4
# frost pvp
AiPlayerbot.RandomClassSpecProb.6.5 = 0
AiPlayerbot.RandomClassSpecIndex.6.5 = 5
# unholy pvp
AiPlayerbot.RandomClassSpecProb.6.6 = 0
AiPlayerbot.RandomClassSpecIndex.6.6 = 6
# #
# #
@ -2034,24 +1900,12 @@ AiPlayerbot.RandomClassSpecIndex.6.6 = 6
# #
# #
# ele pve
AiPlayerbot.RandomClassSpecProb.7.0 = 33 AiPlayerbot.RandomClassSpecProb.7.0 = 33
AiPlayerbot.RandomClassSpecIndex.7.0 = 0 AiPlayerbot.RandomClassSpecIndex.7.0 = 0
# enh pve
AiPlayerbot.RandomClassSpecProb.7.1 = 33 AiPlayerbot.RandomClassSpecProb.7.1 = 33
AiPlayerbot.RandomClassSpecIndex.7.1 = 1 AiPlayerbot.RandomClassSpecIndex.7.1 = 1
# resto pve
AiPlayerbot.RandomClassSpecProb.7.2 = 33 AiPlayerbot.RandomClassSpecProb.7.2 = 33
AiPlayerbot.RandomClassSpecIndex.7.2 = 2 AiPlayerbot.RandomClassSpecIndex.7.2 = 2
# ele pvp
AiPlayerbot.RandomClassSpecProb.7.3 = 0
AiPlayerbot.RandomClassSpecIndex.7.3 = 3
# enh pvp
AiPlayerbot.RandomClassSpecProb.7.4 = 0
AiPlayerbot.RandomClassSpecIndex.7.4 = 4
# resto pvp
AiPlayerbot.RandomClassSpecProb.7.5 = 0
AiPlayerbot.RandomClassSpecIndex.7.5 = 5
# #
# #
@ -2063,27 +1917,12 @@ AiPlayerbot.RandomClassSpecIndex.7.5 = 5
# #
# #
# arcane pve
AiPlayerbot.RandomClassSpecProb.8.0 = 30 AiPlayerbot.RandomClassSpecProb.8.0 = 30
AiPlayerbot.RandomClassSpecIndex.8.0 = 0 AiPlayerbot.RandomClassSpecIndex.8.0 = 0
# fire pve
AiPlayerbot.RandomClassSpecProb.8.1 = 30 AiPlayerbot.RandomClassSpecProb.8.1 = 30
AiPlayerbot.RandomClassSpecIndex.8.1 = 1 AiPlayerbot.RandomClassSpecIndex.8.1 = 1
# frost pve
AiPlayerbot.RandomClassSpecProb.8.2 = 40 AiPlayerbot.RandomClassSpecProb.8.2 = 40
AiPlayerbot.RandomClassSpecIndex.8.2 = 2 AiPlayerbot.RandomClassSpecIndex.8.2 = 2
# frostfire pve
AiPlayerbot.RandomClassSpecProb.8.3 = 0
AiPlayerbot.RandomClassSpecIndex.8.3 = 3
# arcane pvp
AiPlayerbot.RandomClassSpecProb.8.4 = 0
AiPlayerbot.RandomClassSpecIndex.8.4 = 4
# fire pvp
AiPlayerbot.RandomClassSpecProb.8.5 = 0
AiPlayerbot.RandomClassSpecIndex.8.5 = 5
# frost pvp
AiPlayerbot.RandomClassSpecProb.8.6 = 0
AiPlayerbot.RandomClassSpecIndex.8.6 = 6
# #
# #
@ -2095,24 +1934,12 @@ AiPlayerbot.RandomClassSpecIndex.8.6 = 6
# #
# #
# affli pve
AiPlayerbot.RandomClassSpecProb.9.0 = 33 AiPlayerbot.RandomClassSpecProb.9.0 = 33
AiPlayerbot.RandomClassSpecIndex.9.0 = 0 AiPlayerbot.RandomClassSpecIndex.9.0 = 0
# demo pve
AiPlayerbot.RandomClassSpecProb.9.1 = 34 AiPlayerbot.RandomClassSpecProb.9.1 = 34
AiPlayerbot.RandomClassSpecIndex.9.1 = 1 AiPlayerbot.RandomClassSpecIndex.9.1 = 1
# destro pve
AiPlayerbot.RandomClassSpecProb.9.2 = 33 AiPlayerbot.RandomClassSpecProb.9.2 = 33
AiPlayerbot.RandomClassSpecIndex.9.2 = 2 AiPlayerbot.RandomClassSpecIndex.9.2 = 2
# affli pvp
AiPlayerbot.RandomClassSpecProb.9.3 = 0
AiPlayerbot.RandomClassSpecIndex.9.3 = 3
# demo pvp
AiPlayerbot.RandomClassSpecProb.9.4 = 0
AiPlayerbot.RandomClassSpecIndex.9.4 = 4
# destro pvp
AiPlayerbot.RandomClassSpecProb.9.5 = 0
AiPlayerbot.RandomClassSpecIndex.9.5 = 5
# #
# #
@ -2124,27 +1951,14 @@ AiPlayerbot.RandomClassSpecIndex.9.5 = 5
# #
# #
# balance pve
AiPlayerbot.RandomClassSpecProb.11.0 = 20 AiPlayerbot.RandomClassSpecProb.11.0 = 20
AiPlayerbot.RandomClassSpecIndex.11.0 = 0 AiPlayerbot.RandomClassSpecIndex.11.0 = 0
# bear pve
AiPlayerbot.RandomClassSpecProb.11.1 = 25 AiPlayerbot.RandomClassSpecProb.11.1 = 25
AiPlayerbot.RandomClassSpecIndex.11.1 = 1 AiPlayerbot.RandomClassSpecIndex.11.1 = 1
# resto pve
AiPlayerbot.RandomClassSpecProb.11.2 = 35 AiPlayerbot.RandomClassSpecProb.11.2 = 35
AiPlayerbot.RandomClassSpecIndex.11.2 = 2 AiPlayerbot.RandomClassSpecIndex.11.2 = 2
# cat pve
AiPlayerbot.RandomClassSpecProb.11.3 = 20 AiPlayerbot.RandomClassSpecProb.11.3 = 20
AiPlayerbot.RandomClassSpecIndex.11.3 = 3 AiPlayerbot.RandomClassSpecIndex.11.3 = 3
# balance pvp
AiPlayerbot.RandomClassSpecProb.11.4 = 0
AiPlayerbot.RandomClassSpecIndex.11.4 = 4
# cat pvp
AiPlayerbot.RandomClassSpecProb.11.5 = 0
AiPlayerbot.RandomClassSpecIndex.11.5 = 5
# resto pvp
AiPlayerbot.RandomClassSpecProb.11.6 = 0
AiPlayerbot.RandomClassSpecIndex.11.6 = 6
# #
# #

View File

@ -1,104 +0,0 @@
-- #########################################################
-- Playerbots - Add texts for SetWaitForAttackTimeAction
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
-- zhTW, esES, esMX, ruRU)
-- #########################################################
DELETE FROM ai_playerbot_texts WHERE name IN ('wait_for_attack_provide_time', 'wait_for_attack_invalid_time', 'wait_for_attack_time_set');
DELETE FROM ai_playerbot_texts_chance WHERE name IN ('wait_for_attack_provide_time', 'wait_for_attack_invalid_time', 'wait_for_attack_time_set');
-- ---------------------------------------------------------
-- wait_for_attack_provide_time
-- Please provide a time to set (in seconds)
-- ---------------------------------------------------------
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1740,
'wait_for_attack_provide_time',
'Please provide a time to set (in seconds)',
0, 0,
-- koKR
'설정할 시간을 입력해 주세요 (초 단위)',
-- frFR
'Veuillez indiquer un temps à définir (en secondes)',
-- deDE
'Bitte gib eine Zeit an (in Sekunden)',
-- zhCN
'请提供要设置的时间(以秒为单位)',
-- zhTW
'請提供要設定的時間(以秒為單位)',
-- esES
'Por favor, indica un tiempo a establecer (en segundos)',
-- esMX
'Por favor, indica un tiempo a establecer (en segundos)',
-- ruRU
'Пожалуйста, укажите время (в секундах)');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('wait_for_attack_provide_time', 100);
-- ---------------------------------------------------------
-- wait_for_attack_invalid_time
-- Please provide valid time to set (in seconds) between 0 and 99
-- ---------------------------------------------------------
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1741,
'wait_for_attack_invalid_time',
'Please provide valid time to set (in seconds) between 0 and 99',
0, 0,
-- koKR
'0에서 99 사이의 유효한 시간을 입력해 주세요 (초 단위)',
-- frFR
'Veuillez indiquer un temps valide (en secondes) entre 0 et 99',
-- deDE
'Bitte gib eine gültige Zeit an (in Sekunden) zwischen 0 und 99',
-- zhCN
'请提供有效的时间(以秒为单位),范围为 0 到 99',
-- zhTW
'請提供有效的時間(以秒為單位),範圍為 0 到 99',
-- esES
'Por favor, indica un tiempo válido (en segundos) entre 0 y 99',
-- esMX
'Por favor, indica un tiempo válido (en segundos) entre 0 y 99',
-- ruRU
'Пожалуйста, укажите допустимое время (в секундах) от 0 до 99');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('wait_for_attack_invalid_time', 100);
-- ---------------------------------------------------------
-- wait_for_attack_time_set
-- Wait for attack time set to %new_time seconds
-- ---------------------------------------------------------
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1742,
'wait_for_attack_time_set',
'Wait for attack time set to %new_time seconds',
0, 0,
-- koKR
'공격 대기 시간이 %new_time초로 설정되었습니다',
-- frFR
'Temps d''attente avant l''attaque défini à %new_time secondes',
-- deDE
'Wartezeit vor dem Angriff auf %new_time Sekunden gesetzt',
-- zhCN
'等待攻击时间已设置为 %new_time 秒',
-- zhTW
'等待攻擊時間已設定為 %new_time 秒',
-- esES
'Tiempo de espera para atacar establecido en %new_time segundos',
-- esMX
'Tiempo de espera para atacar establecido en %new_time segundos',
-- ruRU
'Время ожидания атаки установлено на %new_time секунд');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('wait_for_attack_time_set', 100);

View File

@ -1,59 +0,0 @@
-- Translations for additional logout related messages
DELETE FROM ai_playerbot_texts WHERE name IN ('bot_not_your_master', 'bot_rndbot_no_logout');
DELETE FROM ai_playerbot_texts_chance WHERE name IN ('bot_not_your_master', 'bot_rndbot_no_logout');
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1743,
'bot_not_your_master',
"You are not my master!",
0, 0,
-- koKR
"당신은 내 주인이 아닙니다!",
-- frFR
"Tu n'es pas mon maître !",
-- deDE
"Du bist nicht mein Meister!",
-- zhCN
"你不是我的主人!",
-- zhTW
"你不是我的主人!",
-- esES
"¡No eres mi amo!",
-- esMX
"¡No eres mi amo!",
-- ruRU
"Ты не мой хозяин!");
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('bot_not_your_master', 100);
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1744,
'bot_rndbot_no_logout',
"You can't command me to logout!",
0, 0,
-- koKR
"당신은 나에게 로그아웃을 명령할 수 없습니다!",
-- frFR
"Tu ne peux pas m'ordonner de me déconnecter !",
-- deDE
"Du kannst mir nicht befehlen, mich auszuloggen!",
-- zhCN
"你不能命令我下线!",
-- zhTW
"你不能命令我登出!",
-- esES
"¡No puedes ordenarme que cierre sesión!",
-- esMX
"¡No puedes ordenarme que cierre sesión!",
-- ruRU
"Ты не можешь приказать мне выйти из игры!");
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('bot_rndbot_no_logout', 100);

View File

@ -1,240 +0,0 @@
-- #########################################################
-- Playerbots - Add focus heal command texts
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
-- zhTW, esES, esMX, ruRU)
-- #########################################################
DELETE FROM ai_playerbot_texts WHERE name IN (
'focus_heal_not_healer',
'focus_heal_provide_names',
'focus_heal_no_targets',
'focus_heal_current_targets',
'focus_heal_cleared',
'focus_heal_add_remove_syntax',
'focus_heal_not_in_group',
'focus_heal_not_in_group_with',
'focus_heal_added',
'focus_heal_removed'
);
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
'focus_heal_not_healer',
'focus_heal_provide_names',
'focus_heal_no_targets',
'focus_heal_current_targets',
'focus_heal_cleared',
'focus_heal_add_remove_syntax',
'focus_heal_not_in_group',
'focus_heal_not_in_group_with',
'focus_heal_added',
'focus_heal_removed'
);
-- focus_heal_not_healer
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1745,
'focus_heal_not_healer',
'I''m not a healer or offhealer (please change my strats to heal or offheal)',
0, 0,
'저는 힐러나 오프힐러가 아닙니다 (전략을 heal 또는 offheal로 변경해주세요)',
'Je ne suis pas un soigneur ou un soigneur secondaire (veuillez changer mes strats en heal ou offheal)',
'Ich bin kein Heiler oder Nebenheiler (bitte ändere meine Strategien auf heal oder offheal)',
'我不是治疗者或副治疗者(请将我的策略更改为 heal 或 offheal',
'我不是治療者或副治療者(請將我的策略更改為 heal 或 offheal',
'No soy un sanador ni un sanador secundario (por favor cambia mis estrategias a heal o offheal)',
'No soy un sanador ni un sanador secundario (por favor cambia mis estrategias a heal o offheal)',
'Я не лекарь и не побочный лекарь (пожалуйста, измените мои стратегии на heal или offheal)');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_healer', 100);
-- focus_heal_provide_names
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1746,
'focus_heal_provide_names',
'Please provide one or more player names',
0, 0,
'하나 이상의 플레이어 이름을 제공해주세요',
'Veuillez fournir un ou plusieurs noms de joueurs',
'Bitte geben Sie einen oder mehrere Spielernamen an',
'请提供一个或多个玩家名称',
'請提供一個或多個玩家名稱',
'Por favor proporciona uno o más nombres de jugadores',
'Por favor proporciona uno o más nombres de jugadores',
'Пожалуйста, укажите одно или несколько имён игроков');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_provide_names', 100);
-- focus_heal_no_targets
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1747,
'focus_heal_no_targets',
'I don''t have any focus heal targets',
0, 0,
'지정된 집중 치유 대상이 없습니다',
'Je n''ai aucune cible de soin prioritaire',
'Ich habe keine fokussierten Heilziele',
'我没有任何集中治疗目标',
'我沒有任何集中治療目標',
'No tengo ningún objetivo de sanación prioritario',
'No tengo ningún objetivo de sanación prioritario',
'У меня нет целей приоритетного лечения');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_no_targets', 100);
-- focus_heal_current_targets: %targets is replaced with comma-separated player names
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1748,
'focus_heal_current_targets',
'My focus heal targets are %targets',
0, 0,
'나의 집중 치유 대상: %targets',
'Mes cibles de soin prioritaire sont %targets',
'Meine fokussierten Heilziele sind %targets',
'我的集中治疗目标是 %targets',
'我的集中治療目標是 %targets',
'Mis objetivos de sanación prioritarios son %targets',
'Mis objetivos de sanación prioritarios son %targets',
'Мои цели приоритетного лечения: %targets');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_current_targets', 100);
-- focus_heal_cleared
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1749,
'focus_heal_cleared',
'Removed focus heal targets',
0, 0,
'집중 치유 대상을 제거했습니다',
'Cibles de soin prioritaire supprimées',
'Fokussierte Heilziele entfernt',
'已移除集中治疗目标',
'已移除集中治療目標',
'Objetivos de sanación prioritarios eliminados',
'Objetivos de sanación prioritarios eliminados',
'Цели приоритетного лечения удалены');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_cleared', 100);
-- focus_heal_add_remove_syntax
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1750,
'focus_heal_add_remove_syntax',
'Please specify a + for add or - to remove a target',
0, 0,
'대상을 추가하려면 +, 제거하려면 -를 지정해주세요',
'Veuillez spécifier + pour ajouter ou - pour retirer une cible',
'Bitte geben Sie + zum Hinzufügen oder - zum Entfernen eines Ziels an',
'请指定 + 添加或 - 移除目标',
'請指定 + 添加或 - 移除目標',
'Por favor especifica + para agregar o - para eliminar un objetivo',
'Por favor especifica + para agregar o - para eliminar un objetivo',
'Пожалуйста, укажите + для добавления или - для удаления цели');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_add_remove_syntax', 100);
-- focus_heal_not_in_group
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1751,
'focus_heal_not_in_group',
'I''m not in a group',
0, 0,
'저는 파티에 속해있지 않습니다',
'Je ne suis pas dans un groupe',
'Ich bin in keiner Gruppe',
'我不在队伍中',
'我不在隊伍中',
'No estoy en un grupo',
'No estoy en un grupo',
'Я не в группе');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_in_group', 100);
-- focus_heal_not_in_group_with: %player_name is replaced with the target player's name
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1752,
'focus_heal_not_in_group_with',
'I''m not in a group with %player_name',
0, 0,
'%player_name 와(과) 같은 파티에 없습니다',
'Je ne suis pas dans un groupe avec %player_name',
'Ich bin nicht in einer Gruppe mit %player_name',
'我与 %player_name 不在同一队伍中',
'我與 %player_name 不在同一隊伍中',
'No estoy en un grupo con %player_name',
'No estoy en un grupo con %player_name',
'Я не в группе с %player_name');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_in_group_with', 100);
-- focus_heal_added: %player_name is replaced with the added player's name
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1753,
'focus_heal_added',
'Added %player_name to focus heal targets',
0, 0,
'%player_name 을(를) 집중 치유 대상에 추가했습니다',
'%player_name ajouté aux cibles de soin prioritaire',
'%player_name zu den fokussierten Heilzielen hinzugefügt',
'已将 %player_name 添加到集中治疗目标',
'已將 %player_name 添加到集中治療目標',
'%player_name agregado a los objetivos de sanación prioritarios',
'%player_name agregado a los objetivos de sanación prioritarios',
'%player_name добавлен в цели приоритетного лечения');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_added', 100);
-- focus_heal_removed: %player_name is replaced with the removed player's name
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1754,
'focus_heal_removed',
'Removed %player_name from focus heal targets',
0, 0,
'%player_name 을(를) 집중 치유 대상에서 제거했습니다',
'%player_name retiré des cibles de soin prioritaire',
'%player_name aus den fokussierten Heilzielen entfernt',
'已将 %player_name 从集中治疗目标中移除',
'已將 %player_name 從集中治療目標中移除',
'%player_name eliminado de los objetivos de sanación prioritarios',
'%player_name eliminado de los objetivos de sanación prioritarios',
'%player_name удалён из целей приоритетного лечения');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_removed', 100);

View File

@ -1,102 +0,0 @@
-- #########################################################
-- Playerbots - Add pull command texts
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
-- zhTW, esES, esMX, ruRU)
-- #########################################################
DELETE FROM ai_playerbot_texts WHERE name IN (
'pull_no_target_error',
'pull_target_too_far_error',
'pull_invalid_target_error',
'pull_action_unavailable_error'
);
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
'pull_no_target_error',
'pull_target_too_far_error',
'pull_invalid_target_error',
'pull_action_unavailable_error'
);
-- pull_no_target_error
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1755,
'pull_no_target_error',
'You have no target',
0, 0,
'대상이 없습니다',
'Vous n''avez pas de cible',
'Du hast kein Ziel',
'你没有目标',
'你沒有目標',
'No tienes objetivo',
'No tienes objetivo',
'У вас нет цели');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_no_target_error', 100);
-- pull_target_too_far_error
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1756,
'pull_target_too_far_error',
'The target is too far away',
0, 0,
'대상이 너무 멀리 있습니다',
'La cible est trop loin',
'Das Ziel ist zu weit entfernt',
'目标太远了',
'目標太遠了',
'El objetivo está demasiado lejos',
'El objetivo está demasiado lejos',
'Цель слишком далеко');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_target_too_far_error', 100);
-- pull_invalid_target_error
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1757,
'pull_invalid_target_error',
'The target can''t be pulled',
0, 0,
'해당 대상은 풀링할 수 없습니다',
'La cible ne peut pas être attirée',
'Das Ziel kann nicht gepullt werden',
'该目标无法被拉怪',
'該目標無法被拉怪',
'No se puede hacer pull al objetivo',
'No se puede hacer pull al objetivo',
'Эту цель нельзя пуллить');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_invalid_target_error', 100);
-- pull_action_unavailable_error: %action_name is replaced with the configured pull action
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1758,
'pull_action_unavailable_error',
'Can''t perform pull action ''%action_name''',
0, 0,
'''%action_name'' 풀 액션을 수행할 수 없습니다',
'Impossible d''effectuer l''action d''engagement ''%action_name''',
'Die Pull-Aktion ''%action_name'' kann nicht ausgeführt werden',
'无法执行拉怪动作“%action_name”',
'無法執行拉怪動作「%action_name」',
'No se puede realizar la acción de pull ''%action_name''',
'No se puede realizar la acción de pull ''%action_name''',
'Невозможно выполнить действие пула ''%action_name''');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_action_unavailable_error', 100);

View File

@ -45,7 +45,6 @@
#include "NonCombatActions.h" #include "NonCombatActions.h"
#include "OutfitAction.h" #include "OutfitAction.h"
#include "PositionAction.h" #include "PositionAction.h"
#include "PullActions.h"
#include "DropQuestAction.h" #include "DropQuestAction.h"
#include "RandomBotUpdateAction.h" #include "RandomBotUpdateAction.h"
#include "ReachTargetActions.h" #include "ReachTargetActions.h"
@ -64,10 +63,8 @@
#include "WorldBuffAction.h" #include "WorldBuffAction.h"
#include "XpGainAction.h" #include "XpGainAction.h"
#include "NewRpgAction.h" #include "NewRpgAction.h"
#include "NewRpgOutdoorPvP.h"
#include "FishingAction.h" #include "FishingAction.h"
#include "CancelChannelAction.h" #include "CancelChannelAction.h"
#include "WaitForAttackAction.h"
class PlayerbotAI; class PlayerbotAI;
@ -106,13 +103,6 @@ public:
creators["shoot"] = &ActionContext::shoot; creators["shoot"] = &ActionContext::shoot;
creators["lifeblood"] = &ActionContext::lifeblood; creators["lifeblood"] = &ActionContext::lifeblood;
creators["arcane torrent"] = &ActionContext::arcane_torrent; creators["arcane torrent"] = &ActionContext::arcane_torrent;
creators["pull my target"] = &ActionContext::pull_my_target;
creators["pull rti target"] = &ActionContext::pull_rti_target;
creators["pull start"] = &ActionContext::pull_start;
creators["pull action"] = &ActionContext::pull_action;
creators["pull end"] = &ActionContext::pull_end;
creators["return to pull position"] = &ActionContext::return_to_pull_position;
creators["reach pull"] = &ActionContext::reach_pull;
creators["end pull"] = &ActionContext::end_pull; creators["end pull"] = &ActionContext::end_pull;
creators["healthstone"] = &ActionContext::healthstone; creators["healthstone"] = &ActionContext::healthstone;
creators["healing potion"] = &ActionContext::healing_potion; creators["healing potion"] = &ActionContext::healing_potion;
@ -174,7 +164,6 @@ public:
creators["blood fury"] = &ActionContext::blood_fury; creators["blood fury"] = &ActionContext::blood_fury;
creators["berserking"] = &ActionContext::berserking; creators["berserking"] = &ActionContext::berserking;
creators["every man for himself"] = &ActionContext::every_man_for_himself; creators["every man for himself"] = &ActionContext::every_man_for_himself;
creators["will of the forsaken"] = &ActionContext::will_of_the_forsaken;
creators["use trinket"] = &ActionContext::use_trinket; creators["use trinket"] = &ActionContext::use_trinket;
creators["auto talents"] = &ActionContext::auto_talents; creators["auto talents"] = &ActionContext::auto_talents;
creators["auto share quest"] = &ActionContext::auto_share_quest; creators["auto share quest"] = &ActionContext::auto_share_quest;
@ -274,8 +263,6 @@ public:
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc; creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest; creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight; creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
creators["new rpg outdoor pvp"] = &ActionContext::new_rpg_outdoor_pvp;
creators["wait for attack keep safe distance"] = &ActionContext::wait_for_attack_keep_safe_distance;
} }
private: private:
@ -321,13 +308,6 @@ private:
static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); } static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); }
static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); } static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); }
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); } static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
static Action* pull_my_target(PlayerbotAI* botAI) { return new PullMyTargetAction(botAI); }
static Action* pull_rti_target(PlayerbotAI* botAI) { return new PullRtiTargetAction(botAI); }
static Action* pull_start(PlayerbotAI* botAI) { return new PullStartAction(botAI); }
static Action* pull_action(PlayerbotAI* botAI) { return new PullAction(botAI); }
static Action* pull_end(PlayerbotAI* botAI) { return new PullEndAction(botAI); }
static Action* return_to_pull_position(PlayerbotAI* botAI) { return new ReturnToPullPositionAction(botAI); }
static Action* reach_pull(PlayerbotAI* botAI) { return new ReachPullAction(botAI); }
static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); } static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); }
static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); } static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); }
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); } static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
@ -379,7 +359,6 @@ private:
static Action* blood_fury(PlayerbotAI* botAI) { return new CastBloodFuryAction(botAI); } static Action* blood_fury(PlayerbotAI* botAI) { return new CastBloodFuryAction(botAI); }
static Action* berserking(PlayerbotAI* botAI) { return new CastBerserkingAction(botAI); } static Action* berserking(PlayerbotAI* botAI) { return new CastBerserkingAction(botAI); }
static Action* every_man_for_himself(PlayerbotAI* botAI) { return new CastEveryManForHimselfAction(botAI); } static Action* every_man_for_himself(PlayerbotAI* botAI) { return new CastEveryManForHimselfAction(botAI); }
static Action* will_of_the_forsaken(PlayerbotAI* botAI) { return new CastWillOfTheForsakenAction(botAI); }
static Action* use_trinket(PlayerbotAI* botAI) { return new UseTrinketAction(botAI); } static Action* use_trinket(PlayerbotAI* botAI) { return new UseTrinketAction(botAI); }
static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); } static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); }
static Action* auto_share_quest(PlayerbotAI* ai) { return new AutoShareQuestAction(ai); } static Action* auto_share_quest(PlayerbotAI* ai) { return new AutoShareQuestAction(ai); }
@ -479,8 +458,6 @@ private:
static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(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_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); } static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); }
static Action* new_rpg_outdoor_pvp(PlayerbotAI* ai) { return new NewRpgOutdoorPvpAction(ai); }
static Action* wait_for_attack_keep_safe_distance(PlayerbotAI* ai) { return new WaitForAttackKeepSafeDistanceAction(ai); }
}; };
#endif #endif

View File

@ -14,7 +14,6 @@
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "Unit.h" #include "Unit.h"
#include "WaitForAttackStrategy.h"
bool AttackAction::Execute(Event /*event*/) bool AttackAction::Execute(Event /*event*/)
{ {
@ -53,6 +52,22 @@ bool AttackMyTargetAction::Execute(Event /*event*/)
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{ {
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
if (verbose)
botAI->TellError("I cannot attack in flight");
return false;
}
if (!target) if (!target)
{ {
if (verbose) if (verbose)
@ -69,15 +84,6 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
return false; return false;
} }
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
if (verbose)
botAI->TellError("I cannot attack in flight");
return false;
}
// Check if bot OR target is in prohibited zone/area (skip for duels) // Check if bot OR target is in prohibited zone/area (skip for duels)
if ((target->IsPlayer() || target->IsPet()) && if ((target->IsPlayer() || target->IsPet()) &&
(!bot->duel || bot->duel->Opponent != target) && (!bot->duel || bot->duel->Opponent != target) &&
@ -114,18 +120,6 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
return false; return false;
} }
// Infantry attacks are not allowed from vehicles drivers.
// Check is needed to stop some auto-attack situations.
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
return false;
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
if (sameTarget && inCombat && sameAttackMode) if (sameTarget && inCombat && sameAttackMode)
{ {
if (verbose) if (verbose)
@ -151,7 +145,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
ObjectGuid guid = target->GetGUID(); ObjectGuid guid = target->GetGUID();
bot->SetSelection(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<Unit*>("current target")->Set(target);
context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid); context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid);
@ -169,8 +164,7 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
botAI->ChangeEngine(BOT_STATE_COMBAT); botAI->ChangeEngine(BOT_STATE_COMBAT);
if (!WaitForAttackStrategy::ShouldWait(botAI)) bot->Attack(target, shouldMelee);
bot->Attack(target, shouldMelee);
/* prevent pet dead immediately in group */ /* prevent pet dead immediately in group */
// if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat()) // if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat())
// { // {

View File

@ -73,7 +73,7 @@ void AutoMaintenanceOnLevelupAction::LearnSpells(std::ostringstream* out)
LearnQuestSpells(out); LearnQuestSpells(out);
} }
void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* /*out*/) void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* out)
{ {
PlayerbotFactory factory(bot, bot->GetLevel()); PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitSkills(); factory.InitSkills();

View File

@ -27,7 +27,7 @@ bool BankAction::Execute(Event event)
return false; return false;
} }
bool BankAction::ExecuteBank(std::string const text, Unit* /*bank*/) bool BankAction::ExecuteBank(std::string const text, Unit* bank)
{ {
if (text.empty() || text == "?") if (text.empty() || text == "?")
{ {

View File

@ -534,18 +534,21 @@ bool BGJoinAction::JoinQueue(uint32 type)
botAI->GetAiObjectContext()->GetValue<uint32>("bg type")->Set(0); botAI->GetAiObjectContext()->GetValue<uint32>("bg type")->Set(0);
WorldPacket* packet = nullptr;
if (!isArena) if (!isArena)
{ {
packet = new WorldPacket(CMSG_BATTLEMASTER_JOIN, 20); WorldPacket* packet = new WorldPacket(CMSG_BATTLEMASTER_JOIN, 20);
*packet << bot->GetGUID() << bgTypeId_ << instanceId << joinAsGroup; *packet << bot->GetGUID() << bgTypeId_ << instanceId << joinAsGroup;
/// FIX race condition
// bot->GetSession()->HandleBattlemasterJoinOpcode(packet);
bot->GetSession()->QueuePacket(packet);
} }
else else
{ {
packet = new WorldPacket(CMSG_BATTLEMASTER_JOIN_ARENA, 20); WorldPacket arena_packet(CMSG_BATTLEMASTER_JOIN_ARENA, 20);
*packet << unit->GetGUID() << arenaslot << asGroup << uint8(isRated); arena_packet << unit->GetGUID() << arenaslot << asGroup << uint8(isRated);
bot->GetSession()->HandleBattlemasterJoinArena(arena_packet);
} }
bot->GetSession()->QueuePacket(packet);
return true; return true;
} }

View File

@ -21,7 +21,7 @@ public:
} }
bool Execute(Event event) override; bool Execute(Event event) override;
virtual std::string const castString(WorldObject* /*target*/) { return "cast"; } virtual std::string const castString(WorldObject* target) { return "cast"; }
protected: protected:
bool ncCast = false; bool ncCast = false;
@ -49,7 +49,7 @@ public:
bool isUseful() override { return false; } bool isUseful() override { return false; }
virtual bool AcceptSpell(SpellInfo const* spellInfo); virtual bool AcceptSpell(SpellInfo const* spellInfo);
virtual uint32 GetSpellPriority(SpellInfo const* /*spellInfo*/) { return 1; } virtual uint32 GetSpellPriority(SpellInfo const* spellInfo) { return 1; }
virtual bool castSpell(uint32 spellId, WorldObject* wo); virtual bool castSpell(uint32 spellId, WorldObject* wo);
bool Execute(Event event) override; bool Execute(Event event) override;

View File

@ -80,7 +80,7 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
true, priority); true, priority);
} }
if (bot->GetPet()) if (Pet* pet = bot->GetPet())
botAI->PetFollow(); botAI->PetFollow();
if (moved) if (moved)

View File

@ -116,7 +116,6 @@ bool ChooseRpgTargetAction::Execute(Event /*event*/)
GuidPosition masterRpgTarget; GuidPosition masterRpgTarget;
if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported()) if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported())
{ {
//TODO Implement
Player* player = botAI->GetMaster(); Player* player = botAI->GetMaster();
//GuidPosition masterRpgTarget = PAI_VALUE(GuidPosition, "rpg target"); //not used, line marked for removal. //GuidPosition masterRpgTarget = PAI_VALUE(GuidPosition, "rpg target"); //not used, line marked for removal.
} }

View File

@ -62,16 +62,31 @@ bool CleanQuestLogAction::Execute(Event event)
{ {
Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
if (!requester) if (!requester)
{
botAI->TellMaster("No event owner detected");
return false; return false;
}
if (!sPlayerbotAIConfig.dropObsoleteQuests) if (!sPlayerbotAIConfig.dropObsoleteQuests)
{
return false; return false;
}
// Only output this message if "debug rpg" strategy is enabled // Only output this message if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests..."); botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests...");
}
uint8 botLevel = bot->GetLevel(); // Get bot's level uint8 botLevel = bot->GetLevel(); // Get bot's level
uint8 numQuest = 0;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
if (bot->GetQuestSlotQuestId(slot))
{
numQuest++;
}
}
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{ {
@ -86,24 +101,34 @@ bool CleanQuestLogAction::Execute(Event event)
// Determine if quest is trivial by comparing levels // Determine if quest is trivial by comparing levels
int32 questLevel = quest->GetQuestLevel(); int32 questLevel = quest->GetQuestLevel();
if (questLevel == -1) // For scaling quests, default to bot level if (questLevel == -1) // For scaling quests, default to bot level
{
questLevel = botLevel; questLevel = botLevel;
}
// Set the level difference for when a quest becomes trivial // Set the level difference for when a quest becomes trivial
// This was determined by using the Lua code the client uses // This was determined by using the Lua code the client uses
int32 trivialLevel = 5; int32 trivialLevel = 5;
if (botLevel >= 40) if (botLevel >= 40)
{
trivialLevel = 8; trivialLevel = 8;
}
else if (botLevel >= 30) else if (botLevel >= 30)
{
trivialLevel = 7; trivialLevel = 7;
}
else if (botLevel >= 20) else if (botLevel >= 20)
{
trivialLevel = 6; trivialLevel = 6;
}
// Check if the quest is trivial (grey) for the bot // Check if the quest is trivial (grey) for the bot
if ((botLevel - questLevel) > trivialLevel) if ((botLevel - questLevel) > trivialLevel)
{ {
// Output only if "debug rpg" strategy is enabled // Output only if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey)."); botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey).");
}
// Remove quest // Remove quest
botAI->rpgStatistic.questDropped++; botAI->rpgStatistic.questDropped++;
@ -112,6 +137,8 @@ bool CleanQuestLogAction::Execute(Event event)
bot->SetQuestStatus(questId, QUEST_STATUS_NONE); bot->SetQuestStatus(questId, QUEST_STATUS_NONE);
bot->RemoveRewardedQuest(questId); bot->RemoveRewardedQuest(questId);
numQuest--;
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{ {
const std::string text_quest = ChatHelper::FormatQuest(quest); const std::string text_quest = ChatHelper::FormatQuest(quest);
@ -120,13 +147,17 @@ bool CleanQuestLogAction::Execute(Event event)
} }
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed."); botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed.");
}
} }
else else
{ {
// Only output if "debug rpg" strategy is enabled // Only output if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept."); botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept.");
}
} }
} }
@ -143,6 +174,7 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG
{ {
std::random_device rd; std::random_device rd;
std::mt19937 g(rd()); std::mt19937 g(rd());
std::shuffle(slots.begin(), slots.end(), g); std::shuffle(slots.begin(), slots.end(), g);
} }
@ -168,10 +200,8 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG
bot->GetLevel() <= bot->GetQuestLevel(quest) + uint32(lowLevelDiff)) // Quest is not gray bot->GetLevel() <= bot->GetQuestLevel(quest) + uint32(lowLevelDiff)) // Quest is not gray
{ {
if (bot->GetLevel() + 5 > bot->GetQuestLevel(quest)) // Quest is not red if (bot->GetLevel() + 5 > bot->GetQuestLevel(quest)) // Quest is not red
{
if (!isGreen) if (!isGreen)
continue; continue;
}
} }
else // Quest is gray else // Quest is gray
{ {

View File

@ -168,8 +168,8 @@ bool FollowAction::Execute(Event /*event*/)
? MovementPriority::MOVEMENT_COMBAT ? MovementPriority::MOVEMENT_COMBAT
: MovementPriority::MOVEMENT_NORMAL; : MovementPriority::MOVEMENT_NORMAL;
bool const movingAllowed = IsMovingAllowed(); bool const movingAllowed = IsMovingAllowed(mapId, destX, destY, destZ);
bool const dupMove = IsDuplicateMove(destX, destY, destZ); bool const dupMove = IsDuplicateMove(mapId, destX, destY, destZ);
bool const waiting = IsWaitingForLastMove(priority); bool const waiting = IsWaitingForLastMove(priority);
if (movingAllowed && !dupMove && !waiting) if (movingAllowed && !dupMove && !waiting)

View File

@ -67,7 +67,7 @@ namespace ai::buff
if (info->Reagent[i] > 0) if (info->Reagent[i] > 0)
{ {
uint32 const itemId = info->Reagent[i]; uint32 const itemId = info->Reagent[i];
int32 const need = info->ReagentCount[i]; int32 const need = info->ReagentCount[i];
if ((int32)bot->GetItemCount(itemId, false) < need) if ((int32)bot->GetItemCount(itemId, false) < need)
return false; return false;
} }
@ -143,28 +143,3 @@ namespace ai::buff
return castName; return castName;
} }
} }
namespace ai::spell
{
bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId)
{
if (bot->HasSpellCooldown(spellId))
return true;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
uint32 category = spellInfo->GetCategory();
if (!category)
return false;
for (auto const& [cooldownSpellId, cooldown] : bot->GetSpellCooldownMap())
{
if (cooldown.category == category && bot->GetSpellCooldownDelay(cooldownSpellId) > 0)
return true;
}
return false;
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#pragma once
#include <string>
#include <functional>
#include "Common.h"
#include "Group.h"
#include "Chat.h"
#include "Language.h"
class Player;
class PlayerbotAI;
namespace ai::buff
{
// Build an aura qualifier "single + greater" to avoid double-buffing
std::string MakeAuraQualifierForBuff(std::string const& name);
// Returns the group spell name for a given single-target buff.
// If no group equivalent exists, returns "".
std::string GroupVariantFor(std::string const& name);
// Checks if the bot has the required reagents to cast a spell (by its spellId).
// Returns false if the spellId is invalid.
bool HasRequiredReagents(Player* bot, uint32 spellId);
// Applies the "switch to group buff" policy if: the bot is in a group of size x+,
// the group variant is known/useful, and reagents are available. Otherwise, returns baseName.
// If announceOnMissing == true and reagents are missing, calls the 'announce' callback
// (if provided) to notify the party/raid.
std::string UpgradeToGroupIfAppropriate(
Player* bot,
PlayerbotAI* botAI,
std::string const& baseName,
bool announceOnMissing = false,
std::function<void(std::string const&)> announce = {}
);
}
namespace ai::chat {
inline std::function<void(std::string const&)> MakeGroupAnnouncer(Player* me)
{
return [me](std::string const& msg)
{
if (Group* g = me->GetGroup())
{
WorldPacket data;
ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY;
ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str());
g->BroadcastPacket(&data, true, -1, me->GetGUID());
}
else
{
me->Say(msg, LANG_UNIVERSAL);
}
};
}
}

View File

@ -17,11 +17,10 @@
#include "WorldPacket.h" #include "WorldPacket.h"
#include "Group.h" #include "Group.h"
#include "Chat.h" #include "Chat.h"
#include "Ai/Base/Util/GenericBuffUtils.h" #include "GenericBuffUtils.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
using ai::buff::MakeAuraQualifierForBuff; using ai::buff::MakeAuraQualifierForBuff;
using ai::spell::HasSpellOrCategoryCooldown;
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell) CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) : Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell)
@ -151,9 +150,7 @@ bool CastMeleeSpellAction::isUseful()
return CastSpellAction::isUseful(); return CastSpellAction::isUseful();
} }
CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction( CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) :
CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
{ {
range = ATTACK_DISTANCE; range = ATTACK_DISTANCE;
} }
@ -205,35 +202,6 @@ bool CastEnchantItemAction::isPossible()
return spellId && AI_VALUE2(Item*, "item for spell", spellId); return spellId && AI_VALUE2(Item*, "item for spell", spellId);
} }
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
bool CastEnchantItemMainHandAction::isPossible()
{
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
return item && !item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) &&
item->GetTemplate()->Class == ITEM_CLASS_WEAPON;
}
CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
bool CastEnchantItemOffHandAction::isPossible()
{
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (!item || item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
return false;
uint32 invType = item->GetTemplate()->InventoryType;
return invType == INVTYPE_WEAPON || invType == INVTYPE_WEAPONOFFHAND;
}
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount, CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
HealingManaEfficiency manaEfficiency, bool isOwner) HealingManaEfficiency manaEfficiency, bool isOwner)
: CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency) : CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency)
@ -273,7 +241,7 @@ bool BuffOnPartyAction::Execute(Event /*event*/)
} }
// End greater buff fix // End greater buff fix
CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0) CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot")
{ {
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
{ {
@ -283,40 +251,17 @@ CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "s
{ {
case ITEM_SUBCLASS_WEAPON_GUN: case ITEM_SUBCLASS_WEAPON_GUN:
spell += " gun"; spell += " gun";
shootSpellId = 3018;
break; break;
case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_BOW:
spell += " bow"; spell += " bow";
shootSpellId = 3018;
break; break;
case ITEM_SUBCLASS_WEAPON_CROSSBOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW:
spell += " crossbow"; spell += " crossbow";
shootSpellId = 3018;
break;
case ITEM_SUBCLASS_WEAPON_THROWN:
spell = "throw";
shootSpellId = 2764;
break; break;
} }
} }
} }
bool CastShootAction::isPossible()
{
if (shootSpellId)
return botAI->CanCastSpell(shootSpellId, GetTarget(), false);
return CastSpellAction::isPossible();
}
bool CastShootAction::Execute(Event /*event*/)
{
if (shootSpellId)
return botAI->CastSpell(shootSpellId, GetTarget());
return botAI->CastSpell(spell, GetTarget());
}
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue() Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
{ {
return context->GetValue<Unit*>("attacker without aura", spell); return context->GetValue<Unit*>("attacker without aura", spell);
@ -375,7 +320,7 @@ bool CastEveryManForHimselfAction::isPossible()
if (!bot->HasSpell(spellId)) if (!bot->HasSpell(spellId))
return false; return false;
if (HasSpellOrCategoryCooldown(bot, spellId)) if (bot->HasSpellCooldown(spellId))
return false; return false;
return true; return true;
@ -383,36 +328,11 @@ bool CastEveryManForHimselfAction::isPossible()
bool CastEveryManForHimselfAction::isUseful() bool CastEveryManForHimselfAction::isUseful()
{ {
return (bot->HasAuraType(SPELL_AURA_MOD_STUN) || return bot->HasAuraType(SPELL_AURA_MOD_STUN) ||
bot->HasAuraType(SPELL_AURA_MOD_FEAR) || bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraType(SPELL_AURA_MOD_ROOT) || bot->HasAuraType(SPELL_AURA_MOD_ROOT) ||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) ||
bot->HasAuraType(SPELL_AURA_MOD_CHARM)) bot->HasAuraType(SPELL_AURA_MOD_CHARM);
&& CastSpellAction::isUseful();
}
bool CastWillOfTheForsakenAction::isPossible()
{
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
if (!spellId)
return false;
if (!bot->HasSpell(spellId))
return false;
if (HasSpellOrCategoryCooldown(bot, spellId))
return false;
return true;
}
bool CastWillOfTheForsakenAction::isUseful()
{
return (bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraType(SPELL_AURA_MOD_CHARM) ||
bot->HasAuraType(SPELL_AURA_AOE_CHARM) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP))
&& CastSpellAction::isUseful();
} }
bool UseTrinketAction::Execute(Event /*event*/) bool UseTrinketAction::Execute(Event /*event*/)

View File

@ -130,20 +130,6 @@ public:
std::string const GetTargetName() override { return "self target"; } std::string const GetTargetName() override { return "self target"; }
}; };
class CastEnchantItemMainHandAction : public CastEnchantItemAction
{
public:
CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
};
class CastEnchantItemOffHandAction : public CastEnchantItemAction
{
public:
CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
};
class CastHealingSpellAction : public CastAuraSpellAction class CastHealingSpellAction : public CastAuraSpellAction
{ {
public: public:
@ -253,12 +239,7 @@ class CastShootAction : public CastSpellAction
public: public:
CastShootAction(PlayerbotAI* botAI); CastShootAction(PlayerbotAI* botAI);
bool isPossible() override;
bool Execute(Event event) override;
ActionThreatType getThreatType() override { return ActionThreatType::None; } ActionThreatType getThreatType() override { return ActionThreatType::None; }
private:
uint32 shootSpellId;
}; };
class CastLifeBloodAction : public CastHealingSpellAction class CastLifeBloodAction : public CastHealingSpellAction
@ -313,16 +294,6 @@ public:
bool isUseful() override; bool isUseful() override;
}; };
class CastWillOfTheForsakenAction : public CastSpellAction
{
public:
CastWillOfTheForsakenAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "will of the forsaken") {}
std::string const GetTargetName() override { return "self target"; }
bool isPossible() override;
bool isUseful() override;
};
class UseTrinketAction : public Action class UseTrinketAction : public Action
{ {
public: public:

View File

@ -53,7 +53,7 @@ bool GuildBankAction::Execute(std::string const text, GameObject* bank)
return result; return result;
} }
bool GuildBankAction::MoveFromCharToBank(Item* item, GameObject* /*bank*/) bool GuildBankAction::MoveFromCharToBank(Item* item, GameObject* bank)
{ {
uint32 playerSlot = item->GetSlot(); uint32 playerSlot = item->GetSlot();
uint32 playerBag = item->GetBagSlot(); uint32 playerBag = item->GetBagSlot();

View File

@ -78,7 +78,7 @@ private:
class TakeMailProcessor : public MailProcessor class TakeMailProcessor : public MailProcessor
{ {
public: public:
bool Process(uint32 /*index*/, Mail* mail, PlayerbotAI* botAI) override bool Process(uint32 index, Mail* mail, PlayerbotAI* botAI) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (!CheckBagSpace(bot)) if (!CheckBagSpace(bot))
@ -104,7 +104,7 @@ public:
{ {
std::vector<uint32> guids; std::vector<uint32> guids;
for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i) for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i)
if (sObjectMgr->GetItemTemplate(i->item_template)) if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(i->item_template))
guids.push_back(i->item_guid); guids.push_back(i->item_guid);
for (std::vector<uint32>::iterator i = guids.begin(); i != guids.end(); ++i) for (std::vector<uint32>::iterator i = guids.begin(); i != guids.end(); ++i)
@ -157,7 +157,7 @@ private:
class DeleteMailProcessor : public MailProcessor class DeleteMailProcessor : public MailProcessor
{ {
public: public:
bool Process(uint32 /*index*/, Mail* mail, PlayerbotAI* botAI) override bool Process(uint32 index, Mail* mail, PlayerbotAI* botAI) override
{ {
std::ostringstream out; std::ostringstream out;
out << "|cffffffff" << mail->subject << "|cffff0000 deleted"; out << "|cffffffff" << mail->subject << "|cffff0000 deleted";
@ -172,7 +172,7 @@ public:
class ReadMailProcessor : public MailProcessor class ReadMailProcessor : public MailProcessor
{ {
public: public:
bool Process(uint32 /*index*/, Mail* mail, PlayerbotAI* botAI) override bool Process(uint32 index, Mail* mail, PlayerbotAI* botAI) override
{ {
std::ostringstream out, body; std::ostringstream out, body;
out << "|cffffffff" << mail->subject; out << "|cffffffff" << mail->subject;

View File

@ -74,18 +74,8 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
float maxDistance = target->getDestination()->getRadiusMin(); float maxDistance = target->getDestination()->getRadiusMin();
// Spread bots around the target but keep the offset stable per // Evenly distribute around the target.
// (bot, destination) pair. Previously the angle and radius were float angle = 2 * M_PI * urand(0, 100) / 100.0;
// re-rolled every time the action re-entered (i.e. every tick the
// bot wasn't already moving), which made bots oscillate between
// two random points around the same quest POI instead of
// committing to one approach.
uint32 botLow = bot->GetGUID().GetCounter();
int32 destSeed = static_cast<int32>(location.GetPositionX()) * 73856093 ^
static_cast<int32>(location.GetPositionY()) * 19349663;
uint32 seed = botLow ^ static_cast<uint32>(destSeed);
float angle = 2.0f * static_cast<float>(M_PI) * static_cast<float>(seed % 1000) / 1000.0f;
float mod = 0.5f + static_cast<float>((seed / 1000) % 1000) / 2000.0f; // [0.5, 1.0]
if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up. if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up.
{ {
@ -99,6 +89,9 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
float z = location.GetPositionZ(); float z = location.GetPositionZ();
float mapId = location.GetMapId(); float mapId = location.GetMapId();
// Move between 0.5 and 1.0 times the maxDistance.
float mod = frand(50.f, 100.f) / 100.0f;
x += cos(angle) * maxDistance * mod; x += cos(angle) * maxDistance * mod;
y += sin(angle) * maxDistance * mod; y += sin(angle) * maxDistance * mod;

View File

@ -63,10 +63,10 @@ void MovementAction::CreateWp(Player* wpOwner, float x, float y, float z, float
bool MovementAction::JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority) bool MovementAction::JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority)
{ {
UpdateMovementState(); UpdateMovementState();
if (!IsMovingAllowed()) if (!IsMovingAllowed(mapId, x, y, z))
return false; return false;
if (IsDuplicateMove(x, y, z)) if (IsDuplicateMove(mapId, x, y, z))
return false; return false;
if (IsWaitingForLastMove(priority)) if (IsWaitingForLastMove(priority))
@ -101,11 +101,6 @@ bool MovementAction::MoveNear(WorldObject* target, float distance, MovementPrior
float x = target->GetPositionX() + cos(angle) * distance; float x = target->GetPositionX() + cos(angle) * distance;
float y = target->GetPositionY() + sin(angle) * distance; float y = target->GetPositionY() + sin(angle) * distance;
float z = target->GetPositionZ(); float z = target->GetPositionZ();
// Clamp Z to the terrain under the offset point so we don't
// hand PointMovementGenerator a Z that matches the target's
// floor but not the sampled (x,y) — avoids straight-line
// fallbacks through geometry.
bot->UpdateAllowedPositionZ(x, y, z);
if (!bot->IsWithinLOS(x, y, z)) if (!bot->IsWithinLOS(x, y, z))
continue; continue;
@ -171,11 +166,11 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
bool exact_waypoint, MovementPriority priority, bool lessDelay, bool backwards) bool exact_waypoint, MovementPriority priority, bool lessDelay, bool backwards)
{ {
UpdateMovementState(); UpdateMovementState();
if (!IsMovingAllowed()) if (!IsMovingAllowed(mapId, x, y, z))
{ {
return false; return false;
} }
if (IsDuplicateMove(x, y, z)) if (IsDuplicateMove(mapId, x, y, z))
{ {
return false; return false;
} }
@ -255,7 +250,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
DoMovePoint(bot, x, y, modifiedZ, generatePath, backwards); DoMovePoint(bot, x, y, z, generatePath, backwards);
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@ -263,8 +258,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
} }
delay = std::max(.0f, delay); delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay); delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement") AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
.Set(mapId, x, y, modifiedZ, bot->GetOrientation(), delay, priority);
return true; return true;
} }
} }
@ -784,17 +778,15 @@ bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriorit
float dx = cos(angle) * needToGo + bx; float dx = cos(angle) * needToGo + bx;
float dy = sin(angle) * needToGo + by; float dy = sin(angle) * needToGo + by;
// Start from a seed Z between bot and target, then clamp to the float dz; // = std::max(bz, tz); // calc accurate z position to avoid stuck
// terrain under (dx,dy). Linear interpolation alone ignores hills
// between the two units and fed PointMovementGenerator a Z that
// could be well above/below ground, triggering straight-line
// fallbacks through walls.
float dz;
if (distanceToTarget > CONTACT_DISTANCE) if (distanceToTarget > CONTACT_DISTANCE)
{
dz = bz + (tz - bz) * (needToGo / distanceToTarget); dz = bz + (tz - bz) * (needToGo / distanceToTarget);
}
else else
{
dz = tz; dz = tz;
bot->UpdateAllowedPositionZ(dx, dy, dz); }
return MoveTo(target->GetMapId(), dx, dy, dz, false, false, false, false, priority); return MoveTo(target->GetMapId(), dx, dy, dz, false, false, false, false, priority);
} }
@ -897,7 +889,20 @@ bool MovementAction::IsMovingAllowed(WorldObject* target)
return IsMovingAllowed(); return IsMovingAllowed();
} }
bool MovementAction::IsDuplicateMove(float x, float y, float z) bool MovementAction::IsMovingAllowed(uint32 mapId, float x, float y, float z)
{
// removed sqrt as means distance limit was effectively 22500 (ReactDistance<63>)
// leaving it commented incase we find ReactDistance limit causes problems
// float distance = sqrt(bot->GetDistance(x, y, z));
// Remove react distance limit
// if (!bot->InBattleground())
// return false;
return IsMovingAllowed();
}
bool MovementAction::IsDuplicateMove(uint32 mapId, float x, float y, float z)
{ {
LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement"); LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement");
@ -943,15 +948,14 @@ void MovementAction::UpdateMovementState()
const auto liquidState = bot->GetLiquidData().Status; const auto liquidState = bot->GetLiquidData().Status;
const float gZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); const float gZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
const bool onGroundZ = bot->GetPositionZ() < gZ + 1.f; const bool onGroundZ = bot->GetPositionZ() < gZ + 1.f;
const bool wantsSwim = liquidState == LIQUID_MAP_IN_WATER || liquidState == LIQUID_MAP_UNDER_WATER; const bool canSwim = liquidState == LIQUID_MAP_IN_WATER || liquidState == LIQUID_MAP_UNDER_WATER;
const bool wantsFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura(); const bool canFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
const bool canWaterWalk = bot->HasWaterWalkAura(); const bool canWaterWalk = bot->HasWaterWalkAura();
const bool isMasterFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true; const bool isMasterFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true;
const bool isMasterSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true; const bool isMasterSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true;
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING); const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING); const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
const bool hasGravityDisabled = bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
bool movementFlagsUpdated = false; bool movementFlagsUpdated = false;
// handle water (fragile logic do not alter without testing every detail, animation and transition) // handle water (fragile logic do not alter without testing every detail, animation and transition)
@ -966,11 +970,11 @@ void MovementAction::UpdateMovementState()
else if ((!canWaterWalk || isMasterSwimming) && isWaterWalking) else if ((!canWaterWalk || isMasterSwimming) && isWaterWalking)
{ {
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
if (wantsSwim) if (canSwim)
bot->SetSwim(true); bot->SetSwim(true);
movementFlagsUpdated = true; movementFlagsUpdated = true;
} }
else if (!wantsSwim && isSwimming) else if (!canSwim && isSwimming)
{ {
bot->SetSwim(false); bot->SetSwim(false);
movementFlagsUpdated = true; movementFlagsUpdated = true;
@ -986,21 +990,17 @@ void MovementAction::UpdateMovementState()
} }
// handle flying // handle flying
if (wantsFly && !isFlying && isMasterFlying) if ((canFly && !isFlying) && isMasterFlying)
{ {
bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY); bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING); bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
movementFlagsUpdated = true;
// required for transition and state monitoring.
if (MotionMaster* mm = bot->GetMotionMaster())
mm->MoveTakeoff(0, {bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() + 1.F}, 0.F, true);
} }
else if (!wantsFly && !isWaterWalking && (isFlying || hasGravityDisabled)) else if ((!canFly && !isWaterWalking && isFlying) || (!isMasterFlying && isFlying && onGroundZ))
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
movementFlagsUpdated = true;
}
else if (!isMasterFlying && isFlying && onGroundZ)
{ {
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY); bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
@ -1273,7 +1273,7 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
return true; return true;
} }
bool MovementAction::ChaseTo(WorldObject* obj, float distance) bool MovementAction::ChaseTo(WorldObject* obj, float distance, float angle)
{ {
if (!IsMovingAllowed()) if (!IsMovingAllowed())
{ {
@ -1846,7 +1846,7 @@ bool FleeAction::isUseful()
bool FleeWithPetAction::Execute(Event /*event*/) bool FleeWithPetAction::Execute(Event /*event*/)
{ {
if (bot->GetPet()) if (Pet* pet = bot->GetPet())
botAI->PetFollow(); botAI->PetFollow();
return Flee(AI_VALUE(Unit*, "current target")); return Flee(AI_VALUE(Unit*, "current target"));

View File

@ -43,13 +43,14 @@ protected:
float GetFollowAngle(); float GetFollowAngle();
bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance); bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance);
bool Follow(Unit* target, float distance, float angle); bool Follow(Unit* target, float distance, float angle);
bool ChaseTo(WorldObject* obj, float distance = 0.0f); bool ChaseTo(WorldObject* obj, float distance = 0.0f, float angle = 0.0f);
bool ReachCombatTo(Unit* target, float distance = 0.0f); bool ReachCombatTo(Unit* target, float distance = 0.0f);
float MoveDelay(float distance, bool backwards = false); float MoveDelay(float distance, bool backwards = false);
void WaitForReach(float distance); void WaitForReach(float distance);
void SetNextMovementDelay(float delayMillis); void SetNextMovementDelay(float delayMillis);
bool IsMovingAllowed(WorldObject* target); bool IsMovingAllowed(WorldObject* target);
bool IsDuplicateMove(float x, float y, float z); bool IsMovingAllowed(uint32 mapId, float x, float y, float z);
bool IsDuplicateMove(uint32 mapId, float x, float y, float z);
bool IsWaitingForLastMove(MovementPriority priority); bool IsWaitingForLastMove(MovementPriority priority);
bool IsMovingAllowed(); bool IsMovingAllowed();
bool Flee(Unit* target); bool Flee(Unit* target);

View File

@ -1,321 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AttackersValue.h"
#include "CreatureAI.h"
#include "Playerbots.h"
#include "PlayerbotTextMgr.h"
#include "PositionValue.h"
#include "PullActions.h"
#include "PullStrategy.h"
#include "RtiTargetValue.h"
#include <algorithm>
namespace
{
float GetPullReachDistance(Player* bot, Unit* target, PullStrategy const* strategy)
{
if (!bot || !target || !strategy)
return 0.0f;
float const combatDistance = bot->GetCombatReach() + target->GetCombatReach();
return std::max(0.0f, strategy->GetRange() - combatDistance);
}
bool IsWithinPullRange(Player* bot, Unit* target, PullStrategy const* strategy)
{
return bot && target && strategy && bot->GetExactDist(target) <= strategy->GetRange();
}
}
bool PullRequestAction::Execute(Event event)
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
if (!botAI->IsTank(bot))
return false;
Unit* target = GetPullTarget(event);
if (!target || !target->IsInWorld())
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_no_target_error", "You have no target", {});
botAI->TellError(text);
return false;
}
float const maxPullDistance = sPlayerbotAIConfig.reactDistance * 3.0f;
if (target->GetMapId() != bot->GetMapId() || bot->GetDistance(target) > maxPullDistance)
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_target_too_far_error", "The target is too far away", {});
botAI->TellError(text);
return false;
}
if (!AttackersValue::IsPossibleTarget(target, bot))
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_invalid_target_error", "The target can't be pulled", {});
botAI->TellError(text);
return false;
}
if (!strategy->CanDoPullAction(target))
{
std::string const actionName = strategy->GetPullActionName();
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_action_unavailable_error",
"Can't perform pull action '%action_name'",
{{"%action_name", actionName}});
botAI->TellError(text);
return false;
}
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo pullPosition = posMap["pull"];
pullPosition.Set(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId());
posMap["pull"] = pullPosition;
strategy->RequestPull(target);
context->GetValue<Unit*>("current target")->Set(target);
botAI->ChangeEngine(BOT_STATE_COMBAT);
botAI->SetNextCheckDelay(sPlayerbotAIConfig.reactDelay);
return true;
}
Unit* PullMyTargetAction::GetPullTarget(Event event)
{
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
if (event.GetSource() == "attack anything")
return botAI->GetCreature(event.getObject());
return requester ? requester->GetSelectedUnit() : nullptr;
}
Unit* PullRtiTargetAction::GetPullTarget(Event /*event*/)
{
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
if (rtiTarget)
return rtiTarget;
Group* group = bot->GetGroup();
if (!group)
return nullptr;
std::string const rti = AI_VALUE(std::string, "rti");
int32 const index = RtiTargetValue::GetRtiIndex(rti);
if (index < 0)
return nullptr;
ObjectGuid const guid = group->GetTargetIcon(index);
return guid.IsEmpty() ? nullptr : botAI->GetUnit(guid);
}
bool PullStartAction::Execute(Event event)
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* target = strategy->GetTarget();
if (!target)
return false;
std::string const preActionName = strategy->GetPreActionName();
if (!preActionName.empty() && !botAI->DoSpecificAction(preActionName, event, true))
return false;
if (Pet* pet = bot->GetPet())
{
Creature* creature = pet->ToCreature();
if (creature)
{
strategy->SetPetReactState(creature->GetReactState());
creature->SetReactState(REACT_PASSIVE);
}
}
strategy->OnPullStarted();
return true;
}
PullAction::PullAction(PlayerbotAI* botAI, std::string const name) : CastSpellAction(botAI, name) { InitPullAction(); }
Unit* PullAction::GetTarget()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return nullptr;
return strategy->GetTarget();
}
std::vector<NextAction> PullAction::getPrerequisites()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
if (!strategy || !target)
return {};
return IsWithinPullRange(bot, target, strategy) ? std::vector<NextAction>{}
: std::vector<NextAction>{ NextAction("reach pull", ACTION_MOVE) };
}
bool PullAction::Execute(Event event)
{
InitPullAction();
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* target = strategy->GetTarget();
if (!target || !target->IsInWorld())
return false;
if (target->IsInCombat())
return false;
if (!IsWithinPullRange(bot, target, strategy))
{
strategy->RequestPull(target, false);
return false;
}
if (bot->isMoving())
{
bot->StopMoving();
strategy->RequestPull(target, false);
return false;
}
context->GetValue<Unit*>("current target")->Set(target);
if (!botAI->DoSpecificAction(strategy->GetPullActionName(), event, true))
return false;
return true;
}
bool PullAction::isPossible()
{
InitPullAction();
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* target = strategy->GetTarget();
std::string const spellName = strategy->GetSpellName();
if (!target || !target->IsInWorld() || target->GetMapId() != bot->GetMapId() || spellName.empty())
return false;
return true;
}
void PullAction::InitPullAction()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return;
std::string const spellName = strategy->GetSpellName();
if (spellName.empty())
return;
spell = spellName;
bool isShoot = (spellName == "shoot" || spellName == "shoot bow" ||
spellName == "shoot gun" || spellName == "shoot crossbow" ||
spellName == "throw");
range = botAI->GetRange(isShoot ? "shoot" : "spell");
}
bool PullEndAction::Execute(Event /*event*/)
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* pullTarget = strategy->GetTarget();
if (!strategy->HasPullStarted() && !strategy->IsPullPendingToStart() && !strategy->HasTarget())
return false;
if (Pet* pet = bot->GetPet())
{
Creature* creature = pet->ToCreature();
if (creature)
creature->SetReactState(strategy->GetPetReactState());
}
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo pullPosition = posMap["pull"];
if (pullPosition.isSet())
posMap.erase("pull");
if (pullTarget && context->GetValue<Unit*>("current target")->Get() == pullTarget)
context->GetValue<Unit*>("current target")->Set(nullptr);
strategy->OnPullEnded();
return true;
}
bool ReturnToPullPositionAction::Execute(Event /*event*/)
{
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
if (!pullPosition.isSet() || pullPosition.mapId != bot->GetMapId())
return false;
return MoveTo(pullPosition.mapId, pullPosition.x, pullPosition.y, pullPosition.z,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true);
}
bool ReturnToPullPositionAction::isUseful()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
if (!strategy || !target || !target->IsInCombat())
return false;
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
return pullPosition.isSet() && pullPosition.mapId == bot->GetMapId() &&
bot->GetDistance(pullPosition.x, pullPosition.y, pullPosition.z) > sPlayerbotAIConfig.followDistance;
}
bool ReachPullAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!target || !strategy)
return false;
float const reachDistance = GetPullReachDistance(bot, target, strategy);
return ReachCombatTo(target, reachDistance);
}
bool ReachPullAction::isUseful()
{
if (botAI->HasStrategy("stay", botAI->GetState()))
return false;
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
return false;
PullStrategy* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
return target && !IsWithinPullRange(bot, target, strategy);
}
Unit* ReachPullAction::GetTarget()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return nullptr;
return strategy->GetTarget();
}

View File

@ -1,90 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PULLACTIONS_H
#define _PLAYERBOT_PULLACTIONS_H
#include "GenericSpellActions.h"
#include "ReachTargetActions.h"
class PullRequestAction : public Action
{
public:
PullRequestAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) {}
bool Execute(Event event) override;
protected:
virtual Unit* GetPullTarget(Event event) = 0;
};
class PullMyTargetAction : public PullRequestAction
{
public:
PullMyTargetAction(PlayerbotAI* botAI) : PullRequestAction(botAI, "pull my target") {}
private:
Unit* GetPullTarget(Event event) override;
};
class PullRtiTargetAction : public PullRequestAction
{
public:
PullRtiTargetAction(PlayerbotAI* botAI) : PullRequestAction(botAI, "pull rti target") {}
private:
Unit* GetPullTarget(Event event) override;
};
class PullStartAction : public Action
{
public:
PullStartAction(PlayerbotAI* botAI, std::string const name = "pull start") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class PullAction : public CastSpellAction
{
public:
PullAction(PlayerbotAI* botAI, std::string const name = "pull action");
bool Execute(Event event) override;
bool isPossible() override;
std::vector<NextAction> getPrerequisites() override;
Unit* GetTarget() override;
private:
void InitPullAction();
};
class PullEndAction : public Action
{
public:
PullEndAction(PlayerbotAI* botAI, std::string const name = "pull end") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ReachPullAction : public ReachTargetAction
{
public:
ReachPullAction(PlayerbotAI* botAI) : ReachTargetAction(botAI, "reach pull", botAI->GetRange("spell")) {}
bool Execute(Event event) override;
bool isUseful() override;
Unit* GetTarget() override;
};
class ReturnToPullPositionAction : public MovementAction
{
public:
ReturnToPullPositionAction(PlayerbotAI* botAI) : MovementAction(botAI, "return to pull position") {}
bool Execute(Event event) override;
bool isUseful() override;
};
#endif

View File

@ -45,7 +45,7 @@ std::once_flag ReadyChecker::initFlag;
class HealthChecker : public ReadyChecker class HealthChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* /*botAI*/, AiObjectContext* context) override bool Check(PlayerbotAI* botAI, AiObjectContext* context) override
{ {
return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.almostFullHealth; return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.almostFullHealth;
} }
@ -56,7 +56,7 @@ public:
class ManaChecker : public ReadyChecker class ManaChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* /*botAI*/, AiObjectContext* context) override bool Check(PlayerbotAI* botAI, AiObjectContext* context) override
{ {
return !AI_VALUE2(bool, "has mana", "self target") || return !AI_VALUE2(bool, "has mana", "self target") ||
AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumHealth; AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumHealth;
@ -68,7 +68,7 @@ public:
class DistanceChecker : public ReadyChecker class DistanceChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* botAI, AiObjectContext* /*context*/) override bool Check(PlayerbotAI* botAI, AiObjectContext* context) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (Player* master = botAI->GetMaster()) if (Player* master = botAI->GetMaster())
@ -90,7 +90,7 @@ public:
class HunterChecker : public ReadyChecker class HunterChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* botAI, AiObjectContext* /*context*/) override bool Check(PlayerbotAI* botAI, AiObjectContext* context) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (bot->getClass() == CLASS_HUNTER) if (bot->getClass() == CLASS_HUNTER)
@ -126,7 +126,7 @@ class ItemCountChecker : public ReadyChecker
public: public:
ItemCountChecker(std::string const item, std::string const name) : item(item), name(name) {} ItemCountChecker(std::string const item, std::string const name) : item(item), name(name) {}
bool Check(PlayerbotAI* /*botAI*/, AiObjectContext* context) override bool Check(PlayerbotAI* botAI, AiObjectContext* context) override
{ {
return AI_VALUE2(uint32, "item count", item) > 0; return AI_VALUE2(uint32, "item count", item) > 0;
} }
@ -225,4 +225,4 @@ bool ReadyCheckAction::ReadyCheck()
return true; return true;
} }
bool FinishReadyCheckAction::Execute(Event /*event*/) { return ReadyCheck(); } bool FinishReadyCheckAction::Execute(Event event) { return ReadyCheck(); }

View File

@ -65,7 +65,7 @@ void ReleaseSpiritAction::IncrementDeathCount() const
} }
} }
void ReleaseSpiritAction::LogRelease(const std::string& releaseMsg) const void ReleaseSpiritAction::LogRelease(const std::string& releaseMsg, bool isAutoRelease) const
{ {
const std::string teamPrefix = bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H"; const std::string teamPrefix = bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H";
@ -82,13 +82,13 @@ bool AutoReleaseSpiritAction::Execute(Event /*event*/)
{ {
IncrementDeathCount(); IncrementDeathCount();
bot->DurabilityRepairAll(false, 1.0f, false); bot->DurabilityRepairAll(false, 1.0f, false);
LogRelease("auto released"); LogRelease("auto released", true);
WorldPacket packet(CMSG_REPOP_REQUEST); WorldPacket packet(CMSG_REPOP_REQUEST);
packet << uint8(0); packet << uint8(0);
bot->GetSession()->HandleRepopRequestOpcode(packet); bot->GetSession()->HandleRepopRequestOpcode(packet);
LogRelease("releases spirit"); LogRelease("releases spirit", true);
if (bot->InBattleground()) if (bot->InBattleground())
{ {

View File

@ -18,7 +18,7 @@ public:
: Action(botAI, name) {} : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
void LogRelease(const std::string& releaseType) const; void LogRelease(const std::string& releaseType, bool isAutoRelease = false) const;
protected: protected:
void IncrementDeathCount() const; void IncrementDeathCount() const;

View File

@ -251,9 +251,9 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
std::vector<uint32> races; std::vector<uint32> races;
if (bot->GetTeamId() == TEAM_ALLIANCE) if (bot->GetTeamId() == TEAM_ALLIANCE)
races = {RACE_HUMAN, RACE_DWARF, RACE_GNOME, RACE_NIGHTELF, RACE_DRAENEI}; races = {RACE_HUMAN, RACE_DWARF, RACE_GNOME, RACE_NIGHTELF};
else else
races = {RACE_ORC, RACE_TROLL, RACE_TAUREN, RACE_UNDEAD_PLAYER, RACE_BLOODELF}; races = {RACE_ORC, RACE_TROLL, RACE_TAUREN, RACE_UNDEAD_PLAYER};
float graveDistance = -1; float graveDistance = -1;

View File

@ -154,7 +154,7 @@ bool SayAction::isUseful()
return (time(nullptr) - lastSaid) > 30; return (time(nullptr) - lastSaid) > 30;
} }
void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, std::string& msg, std::string& chanName, std::string& name) void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint32& guid2, std::string& msg, std::string& chanName, std::string& name)
{ {
std::string respondsText = ""; std::string respondsText = "";
@ -205,14 +205,14 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, std:
if (msg.starts_with(sPlayerbotAIConfig.toxicLinksPrefix) if (msg.starts_with(sPlayerbotAIConfig.toxicLinksPrefix)
&& (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0)) && (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0))
{ {
HandleToxicLinksReply(bot, chatChannelSource); HandleToxicLinksReply(bot, chatChannelSource, msg, name);
return; return;
} }
//thunderfury //thunderfury
if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019)) if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019))
{ {
HandleThunderfuryReply(bot, chatChannelSource); HandleThunderfuryReply(bot, chatChannelSource, msg, name);
return; return;
} }
@ -220,7 +220,7 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, std:
SendGeneralResponse(bot, chatChannelSource, messageRepy, name); SendGeneralResponse(bot, chatChannelSource, messageRepy, name);
} }
bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource) bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name)
{ {
std::map<std::string, std::string> placeholders; std::map<std::string, std::string> placeholders;
const auto thunderfury = sObjectMgr->GetItemTemplate(19019); const auto thunderfury = sObjectMgr->GetItemTemplate(19019);
@ -248,7 +248,7 @@ bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chat
return true; return true;
} }
bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource) bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name)
{ {
//quests //quests
std::vector<uint32> incompleteQuests; std::vector<uint32> incompleteQuests;

View File

@ -29,12 +29,12 @@ class ChatReplyAction : public Action
{ {
public: public:
ChatReplyAction(PlayerbotAI* ai) : Action(ai, "chat message") {} ChatReplyAction(PlayerbotAI* ai) : Action(ai, "chat message") {}
virtual bool Execute(Event /*event*/) { return true; } virtual bool Execute(Event event) { return true; }
bool isUseful() { return true; } bool isUseful() { return true; }
static void ChatReplyDo(Player* bot, uint32& type, uint32& guid1, std::string& msg, std::string& chanName, std::string& name); static void ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint32& guid2, std::string& msg, std::string& chanName, std::string& name);
static bool HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource); static bool HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name);
static bool HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource); static bool HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name);
static bool HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); static bool HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name);
static bool HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); static bool HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name);
static bool SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string& responseMessage, std::string& name); static bool SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string& responseMessage, std::string& name);

View File

@ -15,7 +15,7 @@
std::set<uint32> const FISHING_SPELLS = {7620, 7731, 7732, 18248, 33095, 51294}; std::set<uint32> const FISHING_SPELLS = {7620, 7731, 7732, 18248, 33095, 51294};
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* /*lastWp*/, Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
bool important) bool important)
{ {
float dist = wpOwner->GetDistance(x, y, z); float dist = wpOwner->GetDistance(x, y, z);

View File

@ -1,219 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "SetFocusHealTargetsAction.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
#include "PlayerbotTextMgr.h"
#include <algorithm>
#include <cctype>
static std::string LowercaseString(std::string const& str)
{
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
static Player* FindGroupPlayerByName(Player* player, std::string const& playerName)
{
if (!player)
return nullptr;
Group* group = player->GetGroup();
if (!group)
return nullptr;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member)
{
std::string memberName = member->GetName();
if (LowercaseString(memberName) == playerName)
return member;
}
}
return nullptr;
}
bool SetFocusHealTargetsAction::Execute(Event event)
{
if (!botAI->IsHeal(bot) && !botAI->HasStrategy("offheal", BOT_STATE_COMBAT))
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_not_healer",
"I'm not a healer or offhealer (please change my strats to heal or offheal)",
{});
botAI->TellMasterNoFacing(text);
return false;
}
std::string const param = LowercaseString(event.getParam());
if (param.empty())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_provide_names",
"Please provide one or more player names",
{});
botAI->TellMasterNoFacing(text);
return false;
}
std::list<ObjectGuid> focusHealTargets =
AI_VALUE(std::list<ObjectGuid>, "focus heal targets");
// Query current focus targets
if (param.find('?') != std::string::npos)
{
if (focusHealTargets.empty())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_no_targets",
"I don't have any focus heal targets",
{});
botAI->TellMasterNoFacing(text);
}
else
{
std::stringstream targetNames;
for (auto it = focusHealTargets.begin(); it != focusHealTargets.end(); ++it)
{
Unit* target = botAI->GetUnit(*it);
if (target)
{
if (it != focusHealTargets.begin())
targetNames << ", ";
targetNames << target->GetName();
}
}
std::map<std::string, std::string> placeholders;
placeholders["%targets"] = targetNames.str();
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_current_targets",
"My focus heal targets are %targets",
placeholders);
botAI->TellMasterNoFacing(text);
}
return true;
}
// Clear all targets
if (param == "none" || param == "unset" || param == "clear")
{
focusHealTargets.clear();
SET_AI_VALUE(std::list<ObjectGuid>, "focus heal targets", focusHealTargets);
botAI->ChangeStrategy("-focus heal targets", BOT_STATE_COMBAT);
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_cleared",
"Removed focus heal targets",
{});
botAI->TellMasterNoFacing(text);
return true;
}
// Parse multiple targets separated by commas
std::vector<std::string> targetNames;
if (param.find(',') != std::string::npos)
{
std::string targetName;
std::stringstream ss(param);
while (std::getline(ss, targetName, ','))
targetNames.push_back(targetName);
}
else
targetNames.push_back(param);
if (targetNames.empty())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_provide_names",
"Please provide one or more player names",
{});
botAI->TellMasterNoFacing(text);
return false;
}
if (!bot->GetGroup())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_not_in_group",
"I'm not in a group",
{});
botAI->TellMasterNoFacing(text);
return false;
}
for (std::string const& targetName : targetNames)
{
bool const add = targetName.find("+") != std::string::npos;
bool const remove = targetName.find("-") != std::string::npos;
if (!add && !remove)
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_add_remove_syntax",
"Please specify a + for add or - to remove a target",
{});
botAI->TellMasterNoFacing(text);
continue;
}
std::string const playerName = targetName.substr(1);
Player* target = FindGroupPlayerByName(bot, playerName);
if (!target)
{
std::map<std::string, std::string> placeholders;
placeholders["%player_name"] = playerName;
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_not_in_group_with",
"I'm not in a group with %player_name",
placeholders);
botAI->TellMasterNoFacing(text);
continue;
}
ObjectGuid const& targetGuid = target->GetGUID();
if (add)
{
if (std::find(focusHealTargets.begin(), focusHealTargets.end(), targetGuid) ==
focusHealTargets.end())
focusHealTargets.push_back(targetGuid);
std::map<std::string, std::string> placeholders;
placeholders["%player_name"] = playerName;
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_added",
"Added %player_name to focus heal targets",
placeholders);
botAI->TellMasterNoFacing(text);
}
else
{
focusHealTargets.remove(targetGuid);
std::map<std::string, std::string> placeholders;
placeholders["%player_name"] = playerName;
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_removed",
"Removed %player_name from focus heal targets",
placeholders);
botAI->TellMasterNoFacing(text);
}
}
SET_AI_VALUE(std::list<ObjectGuid>, "focus heal targets", focusHealTargets);
if (focusHealTargets.empty())
botAI->ChangeStrategy("-focus heal targets", BOT_STATE_COMBAT);
else
botAI->ChangeStrategy("+focus heal targets", BOT_STATE_COMBAT);
return true;
}

View File

@ -1,21 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_SETFOCUSHEALTARGETSACTION_H
#define _PLAYERBOT_SETFOCUSHEALTARGETSACTION_H
#include "Action.h"
class PlayerbotAI;
class SetFocusHealTargetsAction : public Action
{
public:
SetFocusHealTargetsAction(PlayerbotAI* botAI) : Action(botAI, "focus heal targets") {}
bool Execute(Event event) override;
};
#endif

View File

@ -61,7 +61,7 @@ bool SummonAction::Execute(Event /*event*/)
if (!master) if (!master)
return false; return false;
if (bot->GetPet()) if (Pet* pet = bot->GetPet())
botAI->PetFollow(); botAI->PetFollow();
if (master->GetSession()->GetSecurity() >= SEC_PLAYER) if (master->GetSession()->GetSecurity() >= SEC_PLAYER)

View File

@ -1,174 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "WaitForAttackAction.h"
#include <algorithm>
#include <cctype>
#include "ObjectAccessor.h"
#include "PlayerbotAI.h"
#include "PlayerbotTextMgr.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "TravelMgr.h"
#include "WaitForAttackStrategy.h"
namespace
{
WorldPosition GetBestPoint(AiObjectContext* context, Player* bot, Unit* target,
float minDistance, float maxDistance)
{
WorldPosition botPosition(bot);
WorldPosition targetPosition(target);
int8 startDir = urand(0, 1) * 2 - 1;
float const radiansIncrement = (5.0f / 180.0f) * static_cast<float>(M_PI);
float startAngle = targetPosition.getAngleTo(botPosition) +
frand(0.0f, radiansIncrement) * startDir;
float distance = frand(minDistance, maxDistance);
GuidVector enemies = AI_VALUE(GuidVector, "possible targets no los");
for (float tryAngle = 0.0f; tryAngle < static_cast<float>(M_PI); tryAngle += radiansIncrement)
{
for (int8 tryDir = -1; tryAngle && tryDir < 1; tryDir += 2)
{
float pointAngle = startAngle + tryAngle * startDir * tryDir;
float x = targetPosition.GetPositionX() + distance * cos(pointAngle);
float y = targetPosition.GetPositionY() + distance * sin(pointAngle);
float z = targetPosition.GetPositionZ() + 1.0f;
WorldPosition point(targetPosition.GetMapId(), x, y, z);
float groundZ = bot->GetMapHeight(x, y, z);
if (groundZ == INVALID_HEIGHT || groundZ == VMAP_INVALID_HEIGHT_VALUE)
continue;
point.setZ(groundZ);
// Check line of sight to target
if (!target->IsWithinLOS(point.GetPositionX(), point.GetPositionY(),
point.GetPositionZ() + bot->GetCollisionHeight()))
continue;
// Check if enemies are close to this point
bool enemyClose = false;
for (ObjectGuid const& enemyGUID : enemies)
{
Unit* enemy = ObjectAccessor::GetUnit(*bot, enemyGUID);
if (enemy && enemy->IsWithinLOSInMap(bot) && enemy->IsHostileTo(bot))
{
float enemyAttackRange = enemy->GetCombatReach() + ATTACK_DISTANCE;
WorldPosition enemyPos(enemy);
if (enemyPos.sqDistance(point) <= (enemyAttackRange * enemyAttackRange))
{
enemyClose = true;
break;
}
}
}
if (enemyClose)
continue;
// Check if bot can path to this point
if (!botPosition.canPathTo(point, bot))
continue;
return point;
}
}
return botPosition;
}
} // namespace
bool WaitForAttackKeepSafeDistanceAction::Execute(Event /*event*/)
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
return false;
// If our target is moving towards a stationary unit, use that unit as anchor
if (!target->IsStopped())
{
ObjectGuid targetGuid = target->GetTarget();
if (targetGuid)
{
Unit* targetsTarget = ObjectAccessor::GetUnit(*target, targetGuid);
if (targetsTarget && targetsTarget->IsStopped())
target = targetsTarget;
}
}
if (target->IsAlive())
{
float safeDistance = std::max(
target->GetCombatReach() + ATTACK_DISTANCE,
WaitForAttackStrategy::GetSafeDistance());
float safeDistanceThreshold = WaitForAttackStrategy::GetSafeDistanceThreshold();
WorldPosition bestPoint = GetBestPoint(context, bot, target,
safeDistance - safeDistanceThreshold, safeDistance);
if (bestPoint)
return MoveTo(bestPoint.GetMapId(), bestPoint.GetPositionX(),
bestPoint.GetPositionY(), bestPoint.GetPositionZ());
}
return false;
}
bool SetWaitForAttackTimeAction::Execute(Event event)
{
std::string newTimeStr = event.getParam();
if (newTimeStr.empty())
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"wait_for_attack_provide_time",
"Please provide a time to set (in seconds)",
std::map<std::string, std::string>());
botAI->TellMaster(text);
return false;
}
if (!std::all_of(newTimeStr.begin(), newTimeStr.end(), ::isdigit))
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"wait_for_attack_invalid_time",
"Please provide valid time to set (in seconds) between 0 and 99",
std::map<std::string, std::string>());
botAI->TellMaster(text);
return false;
}
int newTime = std::stoi(newTimeStr);
if (newTime < 0 || newTime > 99)
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"wait_for_attack_invalid_time",
"Please provide valid time to set (in seconds) between 0 and 99",
std::map<std::string, std::string>());
botAI->TellMaster(text);
return false;
}
context->GetValue<uint8>("wait for attack time")->Set(static_cast<uint8>(newTime));
std::map<std::string, std::string> placeholders;
placeholders["%new_time"] = std::to_string(newTime);
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"wait_for_attack_time_set",
"Wait for attack time set to %new_time seconds",
placeholders);
botAI->TellMaster(text);
return true;
}

View File

@ -1,31 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_WAITFORATTACKACTION_H
#define _PLAYERBOT_WAITFORATTACKACTION_H
#include "MovementActions.h"
class PlayerbotAI;
class WaitForAttackKeepSafeDistanceAction : public MovementAction
{
public:
WaitForAttackKeepSafeDistanceAction(PlayerbotAI* botAI)
: MovementAction(botAI, "wait for attack keep safe distance") {}
bool Execute(Event event) override;
};
class SetWaitForAttackTimeAction : public Action
{
public:
SetWaitForAttackTimeAction(PlayerbotAI* botAI)
: Action(botAI, "wait for attack time") {}
bool Execute(Event event) override;
};
#endif

View File

@ -37,13 +37,11 @@
#include "LogLevelAction.h" #include "LogLevelAction.h"
#include "LootStrategyAction.h" #include "LootStrategyAction.h"
#include "LootRollAction.h" #include "LootRollAction.h"
#include "SetFocusHealTargetsAction.h"
#include "MailAction.h" #include "MailAction.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "NewRpgAction.h" #include "NewRpgAction.h"
#include "PassLeadershipToMasterAction.h" #include "PassLeadershipToMasterAction.h"
#include "PositionAction.h" #include "PositionAction.h"
#include "PullActions.h"
#include "QueryItemUsageAction.h" #include "QueryItemUsageAction.h"
#include "QueryQuestAction.h" #include "QueryQuestAction.h"
#include "RangeAction.h" #include "RangeAction.h"
@ -86,7 +84,6 @@
#include "TellGlyphsAction.h" #include "TellGlyphsAction.h"
#include "EquipGlyphsAction.h" #include "EquipGlyphsAction.h"
#include "PetsAction.h" #include "PetsAction.h"
#include "WaitForAttackAction.h"
class ChatActionContext : public NamedObjectContext<Action> class ChatActionContext : public NamedObjectContext<Action>
{ {
@ -139,8 +136,6 @@ public:
creators["autogear"] = &ChatActionContext::autogear; creators["autogear"] = &ChatActionContext::autogear;
creators["equip upgrade"] = &ChatActionContext::equip_upgrade; creators["equip upgrade"] = &ChatActionContext::equip_upgrade;
creators["attack my target"] = &ChatActionContext::attack_my_target; creators["attack my target"] = &ChatActionContext::attack_my_target;
creators["pull my target"] = &ChatActionContext::pull_my_target;
creators["pull rti target"] = &ChatActionContext::pull_rti_target;
creators["chat"] = &ChatActionContext::chat; creators["chat"] = &ChatActionContext::chat;
creators["home"] = &ChatActionContext::home; creators["home"] = &ChatActionContext::home;
creators["destroy"] = &ChatActionContext::destroy; creators["destroy"] = &ChatActionContext::destroy;
@ -204,8 +199,6 @@ public:
creators["pet"] = &ChatActionContext::pet; creators["pet"] = &ChatActionContext::pet;
creators["pet attack"] = &ChatActionContext::pet_attack; creators["pet attack"] = &ChatActionContext::pet_attack;
creators["roll"] = &ChatActionContext::roll_action; creators["roll"] = &ChatActionContext::roll_action;
creators["wait for attack time"] = &ChatActionContext::wait_for_attack_time;
creators["focus heal targets"] = &ChatActionContext::focus_heal_targets;
} }
private: private:
@ -253,8 +246,6 @@ private:
static Action* home(PlayerbotAI* botAI) { return new SetHomeAction(botAI); } static Action* home(PlayerbotAI* botAI) { return new SetHomeAction(botAI); }
static Action* chat(PlayerbotAI* botAI) { return new ChangeChatAction(botAI); } static Action* chat(PlayerbotAI* botAI) { return new ChangeChatAction(botAI); }
static Action* attack_my_target(PlayerbotAI* botAI) { return new AttackMyTargetAction(botAI); } static Action* attack_my_target(PlayerbotAI* botAI) { return new AttackMyTargetAction(botAI); }
static Action* pull_my_target(PlayerbotAI* botAI) { return new PullMyTargetAction(botAI); }
static Action* pull_rti_target(PlayerbotAI* botAI) { return new PullRtiTargetAction(botAI); }
static Action* trainer(PlayerbotAI* botAI) { return new TrainerAction(botAI); } static Action* trainer(PlayerbotAI* botAI) { return new TrainerAction(botAI); }
static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); } static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); }
static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); } static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); }
@ -320,8 +311,6 @@ private:
static Action* pet(PlayerbotAI* botAI) { return new PetsAction(botAI); } static Action* pet(PlayerbotAI* botAI) { return new PetsAction(botAI); }
static Action* pet_attack(PlayerbotAI* botAI) { return new PetsAction(botAI, "attack"); } static Action* pet_attack(PlayerbotAI* botAI) { return new PetsAction(botAI, "attack"); }
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); } static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
static Action* wait_for_attack_time(PlayerbotAI* botAI) { return new SetWaitForAttackTimeAction(botAI); }
static Action* focus_heal_targets(PlayerbotAI* botAI) { return new SetFocusHealTargetsAction(botAI); }
}; };
#endif #endif

View File

@ -66,9 +66,6 @@ public:
creators["autogear"] = &ChatTriggerContext::autogear; creators["autogear"] = &ChatTriggerContext::autogear;
creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade; creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade;
creators["attack"] = &ChatTriggerContext::attack; creators["attack"] = &ChatTriggerContext::attack;
creators["pull"] = &ChatTriggerContext::pull;
creators["pull back"] = &ChatTriggerContext::pull_back;
creators["pull rti"] = &ChatTriggerContext::pull_rti;
creators["chat"] = &ChatTriggerContext::chat; creators["chat"] = &ChatTriggerContext::chat;
creators["accept"] = &ChatTriggerContext::accept; creators["accept"] = &ChatTriggerContext::accept;
creators["home"] = &ChatTriggerContext::home; creators["home"] = &ChatTriggerContext::home;
@ -148,8 +145,6 @@ public:
creators["pet"] = &ChatTriggerContext::pet; creators["pet"] = &ChatTriggerContext::pet;
creators["pet attack"] = &ChatTriggerContext::pet_attack; creators["pet attack"] = &ChatTriggerContext::pet_attack;
creators["roll"] = &ChatTriggerContext::roll_action; creators["roll"] = &ChatTriggerContext::roll_action;
creators["wait for attack time"] = &ChatTriggerContext::wait_for_attack_time;
creators["focus heal"] = &ChatTriggerContext::focus_heal;
} }
private: private:
@ -212,9 +207,6 @@ private:
static Trigger* accept(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "accept"); } static Trigger* accept(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "accept"); }
static Trigger* chat(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "chat"); } static Trigger* chat(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "chat"); }
static Trigger* attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "attack"); } static Trigger* attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "attack"); }
static Trigger* pull(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pull"); }
static Trigger* pull_back(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pull back"); }
static Trigger* pull_rti(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pull rti"); }
static Trigger* trainer(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "trainer"); } static Trigger* trainer(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "trainer"); }
static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); } static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); }
static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); } static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); }
@ -277,8 +269,6 @@ private:
static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); } static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); }
static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); } static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); }
static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); } static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); }
static Trigger* wait_for_attack_time(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wait for attack time"); }
static Trigger* focus_heal(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "focus heal"); }
}; };
#endif #endif

View File

@ -11,7 +11,7 @@ public:
ChatCommandActionNodeFactoryInternal() { creators["tank attack chat shortcut"] = &tank_attack_chat_shortcut; } ChatCommandActionNodeFactoryInternal() { creators["tank attack chat shortcut"] = &tank_attack_chat_shortcut; }
private: private:
static ActionNode* tank_attack_chat_shortcut(PlayerbotAI* /*botAI*/) static ActionNode* tank_attack_chat_shortcut(PlayerbotAI* botAI)
{ {
return new ActionNode("tank attack chat shortcut", return new ActionNode("tank attack chat shortcut",
/*P*/ {}, /*P*/ {},
@ -81,12 +81,6 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("attackers", { NextAction("tell attackers", relevance) })); new TriggerNode("attackers", { NextAction("tell attackers", relevance) }));
triggers.push_back( triggers.push_back(
new TriggerNode("target", { NextAction("tell target", relevance) })); new TriggerNode("target", { NextAction("tell target", relevance) }));
triggers.push_back(
new TriggerNode("pull", { NextAction("pull my target", relevance) }));
triggers.push_back(
new TriggerNode("pull back", { NextAction("pull my target", relevance) }));
triggers.push_back(
new TriggerNode("pull rti", { NextAction("pull rti target", relevance) }));
triggers.push_back( triggers.push_back(
new TriggerNode("ready", { NextAction("ready check", relevance) })); new TriggerNode("ready", { NextAction("ready check", relevance) }));
triggers.push_back( triggers.push_back(
@ -113,7 +107,6 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) })); triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) }));
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) })); triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) }));
triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) })); triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) }));
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
} }
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@ -206,6 +199,4 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("glyph equip"); // Added for custom Glyphs supported.push_back("glyph equip"); // Added for custom Glyphs
supported.push_back("pet"); supported.push_back("pet");
supported.push_back("pet attack"); supported.push_back("pet attack");
supported.push_back("wait for attack time");
supported.push_back("focus heal");
} }

View File

@ -64,11 +64,11 @@ std::vector<NextAction> AvoidAoeStrategy::getDefaultActions()
}; };
} }
void AvoidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) void AvoidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
} }
void AvoidAoeStrategy::InitMultipliers(std::vector<Multiplier*>& /*multipliers*/) void AvoidAoeStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{ {
} }
@ -81,7 +81,7 @@ std::vector<NextAction> TankFaceStrategy::getDefaultActions()
}; };
} }
void TankFaceStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) void TankFaceStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
} }

View File

@ -17,6 +17,6 @@ void DuelStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
DuelStrategy::DuelStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) {} DuelStrategy::DuelStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) {}
void StartDuelStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) {} void StartDuelStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {}
StartDuelStrategy::StartDuelStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} StartDuelStrategy::StartDuelStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}

View File

@ -1,20 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_FOCUSTARGETSTRATEGY_H
#define _PLAYERBOT_FOCUSTARGETSTRATEGY_H
#include "Strategy.h"
class PlayerbotAI;
class FocusHealTargetsStrategy : public Strategy
{
public:
FocusHealTargetsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "focus heal targets"; }
};
#endif

View File

@ -12,6 +12,6 @@ std::vector<NextAction> FollowMasterStrategy::getDefaultActions()
}; };
} }
void FollowMasterStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) void FollowMasterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
} }

View File

@ -12,4 +12,4 @@ std::vector<NextAction> GuardStrategy::getDefaultActions()
}; };
} }
void GuardStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) {} void GuardStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {}

View File

@ -13,7 +13,7 @@ void MaintenanceStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode(
"seldom", "random",
{ {
NextAction("clean quest log", 6.0f) NextAction("clean quest log", 6.0f)
} }

View File

@ -17,7 +17,7 @@ void CollisionStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new TriggerNode("collision", { NextAction("move out of collision", 2.0f) })); new TriggerNode("collision", { NextAction("move out of collision", 2.0f) }));
} }
void MountStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) void MountStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
} }

View File

@ -5,188 +5,8 @@
#include "PullStrategy.h" #include "PullStrategy.h"
#include "AiObjectContext.h"
#include "PassiveMultiplier.h" #include "PassiveMultiplier.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "SpellMgr.h"
class PullStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
PullStrategyActionNodeFactory()
{
creators["pull start"] = &pull_start;
}
private:
static ActionNode* pull_start(PlayerbotAI* /*botAI*/)
{
return new ActionNode("pull start", {}, {}, { NextAction("pull action", ACTION_NORMAL) });
}
};
PullStrategy::PullStrategy(PlayerbotAI* botAI, std::string const action, std::string const preAction)
: Strategy(botAI), action(action), preAction(preAction)
{
actionNodeFactories.Add(new PullStrategyActionNodeFactory());
}
PullStrategy* PullStrategy::Get(PlayerbotAI* botAI)
{
if (!botAI)
return nullptr;
if (PullStrategy* strategy = dynamic_cast<PullStrategy*>(botAI->GetStrategy("pull", BOT_STATE_NON_COMBAT)))
{
if (strategy->IsPullPendingToStart() || strategy->HasPullStarted() || strategy->HasTarget())
return strategy;
}
return dynamic_cast<PullStrategy*>(botAI->GetStrategy("pull", BOT_STATE_COMBAT));
}
Unit* PullStrategy::GetTarget() const
{
ObjectGuid const guid = botAI->GetAiObjectContext()->GetValue<ObjectGuid>("pull strategy target")->Get();
if (guid.IsEmpty())
return nullptr;
Unit* target = botAI->GetUnit(guid);
Player* bot = botAI->GetBot();
if (!bot || !target || !target->IsAlive() || !target->IsInWorld() ||
target->GetMapId() != bot->GetMapId())
return nullptr;
return target;
}
bool PullStrategy::HasTarget() const { return GetTarget() != nullptr; }
void PullStrategy::SetTarget(Unit* target)
{
botAI->GetAiObjectContext()->GetValue<ObjectGuid>("pull strategy target")->Set(target ? target->GetGUID() : ObjectGuid::Empty);
}
std::string PullStrategy::GetPullActionName() const
{
return action;
}
std::string PullStrategy::GetSpellName() const
{
Player* bot = botAI->GetBot();
std::string spellName = GetPullActionName();
if (!bot || spellName != "shoot")
return spellName;
Item* equippedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
if (!equippedWeapon)
return spellName;
ItemTemplate const* itemTemplate = equippedWeapon->GetTemplate();
if (!itemTemplate)
return spellName;
switch (itemTemplate->SubClass)
{
case ITEM_SUBCLASS_WEAPON_THROWN:
return "throw";
case ITEM_SUBCLASS_WEAPON_GUN:
return "shoot gun";
case ITEM_SUBCLASS_WEAPON_BOW:
return "shoot bow";
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
return "shoot crossbow";
default:
return spellName;
}
}
float PullStrategy::GetRange() const
{
Player* bot = botAI->GetBot();
std::string const spellName = GetSpellName();
if (bot && !spellName.empty())
{
uint32 const spellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", spellName)->Get();
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId))
return bot->GetSpellMaxRangeForTarget(GetTarget(), spellInfo) - CONTACT_DISTANCE;
}
return (action == "shoot" ? botAI->GetRange("shoot") : botAI->GetRange("spell")) - CONTACT_DISTANCE;
}
std::string PullStrategy::GetPreActionName() const
{
return preAction;
}
bool PullStrategy::CanDoPullAction(Unit* target)
{
Player* bot = botAI->GetBot();
if (!bot || !target)
return false;
if (!target->IsInWorld() || target->GetMapId() != bot->GetMapId())
return false;
if (bot->getClass() != CLASS_DRUID && bot->getClass() != CLASS_PALADIN &&
GetPullActionName() == "shoot" && !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
{
return false;
}
std::string const spellName = GetSpellName();
if (spellName.empty())
return false;
return true;
}
void PullStrategy::RequestPull(Unit* target, bool resetTime)
{
SetTarget(target);
pendingToStart = true;
if (resetTime)
pullStartTime = time(nullptr);
}
void PullStrategy::OnPullStarted() { pendingToStart = false; }
void PullStrategy::OnPullEnded()
{
pullStartTime = 0;
pendingToStart = false;
SetTarget(nullptr);
}
PullMultiplier::PullMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "pull") {}
float PullMultiplier::GetValue(Action* action)
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
if (!strategy || !strategy->HasTarget() || !action)
return 1.0f;
if (!strategy->IsPullPendingToStart() && !strategy->HasPullStarted())
return 1.0f;
std::string const actionName = action->getName();
if (actionName == "pull my target" ||
actionName == "pull rti target" ||
actionName == "reach pull" ||
actionName == "pull start" ||
actionName == "pull action" ||
actionName == "return to pull position" ||
actionName == "pull end" ||
actionName == "follow" ||
actionName == "set facing")
return 1.0f;
return 0.0f;
}
class MagePullMultiplier : public PassiveMultiplier class MagePullMultiplier : public PassiveMultiplier
{ {
@ -204,16 +24,8 @@ float MagePullMultiplier::GetValue(Action* action)
if (!action) if (!action)
return 1.0f; return 1.0f;
PullStrategy const* strategy = PullStrategy::Get(botAI);
if (!strategy || !strategy->HasTarget())
return 1.0f;
std::string const name = action->getName(); std::string const name = action->getName();
if (actionName == name || name == "pull action" || name == "pull start" || name == "pull end" || if (actionName == name || name == "reach spell" || name == "change strategy")
name == "pull my target" || name == "pull rti target" ||
name == "reach spell" || name == "reach pull" ||
name == "return to pull position" || name == "follow" ||
name == "set facing" || name == "change strategy")
return 1.0f; return 1.0f;
return PassiveMultiplier::GetValue(action); return PassiveMultiplier::GetValue(action);
@ -222,32 +34,18 @@ float MagePullMultiplier::GetValue(Action* action)
std::vector<NextAction> PullStrategy::getDefaultActions() std::vector<NextAction> PullStrategy::getDefaultActions()
{ {
return { return {
NextAction("pull action", 105.0f), NextAction(action, 105.0f),
NextAction("follow", 104.0f),
NextAction("end pull", 103.0f),
}; };
} }
void PullStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void PullStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) { CombatStrategy::InitTriggers(triggers); }
{
triggers.push_back(new TriggerNode(
"pull start",
{
NextAction("pull start", 106.0f),
NextAction("pull action", ACTION_MOVE)
}
));
triggers.push_back(new TriggerNode(
"pull end",
{
NextAction("pull end", 107.0f)
}
));
}
void PullStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void PullStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{ {
multipliers.push_back(new PullMultiplier(botAI));
multipliers.push_back(new MagePullMultiplier(botAI, action)); multipliers.push_back(new MagePullMultiplier(botAI, action));
CombatStrategy::InitMultipliers(multipliers);
} }
void PossibleAddsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void PossibleAddsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@ -263,15 +61,3 @@ void PossibleAddsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
) )
); );
} }
void PullBackStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
Strategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"return to pull position",
{
NextAction("return to pull position", ACTION_MOVE + 5.0f)
}
));
}

View File

@ -6,65 +6,22 @@
#ifndef _PLAYERBOT_PULLSTRATEGY_H #ifndef _PLAYERBOT_PULLSTRATEGY_H
#define _PLAYERBOT_PULLSTRATEGY_H #define _PLAYERBOT_PULLSTRATEGY_H
#include "Strategy.h" #include "CombatStrategy.h"
class Action;
class Multiplier;
class Unit;
class PlayerbotAI; class PlayerbotAI;
class PullStrategy : public Strategy class PullStrategy : public CombatStrategy
{ {
public: public:
PullStrategy(PlayerbotAI* botAI, std::string const action, std::string const preAction = ""); PullStrategy(PlayerbotAI* botAI, std::string const action) : CombatStrategy(botAI), action(action) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override; void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
std::string const getName() override { return "pull"; } std::string const getName() override { return "pull"; }
std::vector<NextAction> getDefaultActions() override; std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_NONCOMBAT; }
static PullStrategy* Get(PlayerbotAI* botAI);
static uint8 GetMaxPullTime() { return 15; }
time_t GetPullStartTime() const { return pullStartTime; }
bool IsPullPendingToStart() const { return pendingToStart; }
bool HasPullStarted() const { return pullStartTime > 0; }
bool CanDoPullAction(Unit* target);
Unit* GetTarget() const;
bool HasTarget() const;
virtual std::string GetPullActionName() const;
std::string GetSpellName() const;
float GetRange() const;
virtual std::string GetPreActionName() const;
void RequestPull(Unit* target, bool resetTime = true);
void OnPullStarted();
void OnPullEnded();
ReactStates GetPetReactState() const { return petReactState; }
void SetPetReactState(ReactStates reactState) { petReactState = reactState; }
private:
void SetTarget(Unit* target);
private: private:
std::string const action; std::string const action;
std::string const preAction;
bool pendingToStart = false;
time_t pullStartTime = 0;
ReactStates petReactState = REACT_DEFENSIVE;
};
class PullMultiplier : public Multiplier
{
public:
PullMultiplier(PlayerbotAI* botAI);
float GetValue(Action* action) override;
}; };
class PossibleAddsStrategy : public Strategy class PossibleAddsStrategy : public Strategy
@ -76,13 +33,4 @@ public:
std::string const getName() override { return "adds"; } std::string const getName() override { return "adds"; }
}; };
class PullBackStrategy : public Strategy
{
public:
PullBackStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "pull back"; }
};
#endif #endif

View File

@ -7,4 +7,4 @@
RTSCStrategy::RTSCStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} RTSCStrategy::RTSCStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void RTSCStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) {} void RTSCStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {}

View File

@ -11,7 +11,7 @@ public:
RacialsStrategyActionNodeFactory() { creators["lifeblood"] = &lifeblood; } RacialsStrategyActionNodeFactory() { creators["lifeblood"] = &lifeblood; }
private: private:
static ActionNode* lifeblood(PlayerbotAI* /*botAI*/) static ActionNode* lifeblood(PlayerbotAI* botAI)
{ {
return new ActionNode("lifeblood", return new ActionNode("lifeblood",
/*P*/ {}, /*P*/ {},
@ -37,9 +37,6 @@ void RacialsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"loss of control", { NextAction("every man for himself", ACTION_EMERGENCY + 1) })); "loss of control", { NextAction("every man for himself", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode(
"fear charm sleep", { NextAction("will of the forsaken", ACTION_EMERGENCY + 1) }));
} }
RacialsStrategy::RacialsStrategy(PlayerbotAI* botAI) : Strategy(botAI) RacialsStrategy::RacialsStrategy(PlayerbotAI* botAI) : Strategy(botAI)

View File

@ -11,7 +11,7 @@ public:
UsePotionsStrategyActionNodeFactory() { creators["healthstone"] = &healthstone; } UsePotionsStrategyActionNodeFactory() { creators["healthstone"] = &healthstone; }
private: private:
static ActionNode* healthstone(PlayerbotAI* /*botAI*/) static ActionNode* healthstone(PlayerbotAI* botAI)
{ {
return new ActionNode("healthstone", return new ActionNode("healthstone",
/*P*/ {}, /*P*/ {},

View File

@ -1,94 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "WaitForAttackStrategy.h"
#include "Action.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "Strategy.h"
void WaitForAttackStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"wait for attack safe distance",
{
NextAction("wait for attack keep safe distance", ACTION_RAID)
}
));
}
void WaitForAttackStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new WaitForAttackMultiplier(botAI));
}
bool WaitForAttackStrategy::ShouldWait(PlayerbotAI* botAI)
{
if (botAI->HasStrategy("wait for attack", BOT_STATE_COMBAT))
{
Player* bot = botAI->GetBot();
if (bot->GetGroup() && botAI->HasRealPlayerMaster())
{
// Don't wait if the current target is an enemy player
Unit* target = botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
if (target && target->IsPlayer())
return false;
AiObjectContext* context = botAI->GetAiObjectContext();
time_t combatStartTime = context->GetValue<time_t>("combat start time")->Get();
if (bot->IsInCombat())
{
if (combatStartTime == 0)
{
combatStartTime = time(nullptr);
context->GetValue<time_t>("combat start time")->Set(combatStartTime);
}
time_t elapsedTime = time(nullptr) - combatStartTime;
return elapsedTime < GetWaitTime(botAI);
}
else
{
if (combatStartTime != 0)
context->GetValue<time_t>("combat start time")->Set(0);
}
}
}
return false;
}
uint8 WaitForAttackStrategy::GetWaitTime(PlayerbotAI* botAI)
{
return botAI->GetAiObjectContext()->GetValue<uint8>("wait for attack time")->Get();
}
float WaitForAttackStrategy::GetSafeDistance()
{
return sPlayerbotAIConfig.spellDistance;
}
float WaitForAttackMultiplier::GetValue(Action* action)
{
std::string const& actionName = action->getName();
if (actionName != "wait for attack keep safe distance" &&
actionName != "dps assist" &&
actionName != "set facing" &&
actionName != "pull my target" &&
actionName != "pull rti target" &&
actionName != "reach pull" &&
actionName != "pull start" &&
actionName != "pull action" &&
actionName != "pull end")
{
return WaitForAttackStrategy::ShouldWait(botAI) ? 0.0f : 1.0f;
}
return 1.0f;
}

View File

@ -1,39 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_WAITFORATTACKSTRATEGY_H
#define _PLAYERBOT_WAITFORATTACKSTRATEGY_H
#include "Multiplier.h"
#include "Strategy.h"
class PlayerbotAI;
class WaitForAttackStrategy : public Strategy
{
public:
WaitForAttackStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "wait for attack"; }
static bool ShouldWait(PlayerbotAI* botAI);
static uint8 GetWaitTime(PlayerbotAI* botAI);
static float GetSafeDistance();
static float GetSafeDistanceThreshold() { return 2.5f; }
private:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
class WaitForAttackMultiplier : public Multiplier
{
public:
WaitForAttackMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "wait for attack") {}
float GetValue(Action* action) override;
};
#endif

View File

@ -19,7 +19,6 @@
#include "DuelStrategy.h" #include "DuelStrategy.h"
#include "EmoteStrategy.h" #include "EmoteStrategy.h"
#include "FleeStrategy.h" #include "FleeStrategy.h"
#include "FocusTargetStrategy.h"
#include "FollowMasterStrategy.h" #include "FollowMasterStrategy.h"
#include "GrindingStrategy.h" #include "GrindingStrategy.h"
#include "GroupStrategy.h" #include "GroupStrategy.h"
@ -51,7 +50,6 @@
#include "TravelStrategy.h" #include "TravelStrategy.h"
#include "UseFoodStrategy.h" #include "UseFoodStrategy.h"
#include "UsePotionsStrategy.h" #include "UsePotionsStrategy.h"
#include "WaitForAttackStrategy.h"
#include "WorldPacketHandlerStrategy.h" #include "WorldPacketHandlerStrategy.h"
class StrategyContext : public NamedObjectContext<Strategy> class StrategyContext : public NamedObjectContext<Strategy>
@ -95,7 +93,6 @@ public:
creators["sit"] = &StrategyContext::sit; creators["sit"] = &StrategyContext::sit;
creators["mark rti"] = &StrategyContext::mark_rti; creators["mark rti"] = &StrategyContext::mark_rti;
creators["adds"] = &StrategyContext::possible_adds; creators["adds"] = &StrategyContext::possible_adds;
creators["pull back"] = &StrategyContext::pull_back;
creators["close"] = &StrategyContext::close; creators["close"] = &StrategyContext::close;
creators["ranged"] = &StrategyContext::ranged; creators["ranged"] = &StrategyContext::ranged;
creators["behind"] = &StrategyContext::behind; creators["behind"] = &StrategyContext::behind;
@ -127,8 +124,6 @@ public:
creators["worldbuff"] = &StrategyContext::world_buff; creators["worldbuff"] = &StrategyContext::world_buff;
creators["use bobber"] = &StrategyContext::bobber_strategy; creators["use bobber"] = &StrategyContext::bobber_strategy;
creators["master fishing"] = &StrategyContext::master_fishing; creators["master fishing"] = &StrategyContext::master_fishing;
creators["wait for attack"] = &StrategyContext::wait_for_attack;
creators["focus heal targets"] = &StrategyContext::focus_heal_targets;
} }
private: private:
@ -172,7 +167,6 @@ private:
static Strategy* map_full(PlayerbotAI* botAI) { return new MapFullStrategy(botAI); } static Strategy* map_full(PlayerbotAI* botAI) { return new MapFullStrategy(botAI); }
static Strategy* sit(PlayerbotAI* botAI) { return new SitStrategy(botAI); } static Strategy* sit(PlayerbotAI* botAI) { return new SitStrategy(botAI); }
static Strategy* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsStrategy(botAI); } static Strategy* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsStrategy(botAI); }
static Strategy* pull_back(PlayerbotAI* botAI) { return new PullBackStrategy(botAI); }
static Strategy* mount(PlayerbotAI* botAI) { return new MountStrategy(botAI); } static Strategy* mount(PlayerbotAI* botAI) { return new MountStrategy(botAI); }
static Strategy* bg(PlayerbotAI* botAI) { return new BGStrategy(botAI); } static Strategy* bg(PlayerbotAI* botAI) { return new BGStrategy(botAI); }
static Strategy* battleground(PlayerbotAI* botAI) { return new BattlegroundStrategy(botAI); } static Strategy* battleground(PlayerbotAI* botAI) { return new BattlegroundStrategy(botAI); }
@ -201,8 +195,6 @@ private:
static Strategy* world_buff(PlayerbotAI* botAI) { return new WorldBuffStrategy(botAI); } static Strategy* world_buff(PlayerbotAI* botAI) { return new WorldBuffStrategy(botAI); }
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); } static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); } static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
static Strategy* wait_for_attack(PlayerbotAI* botAI) { return new WaitForAttackStrategy(botAI); }
static Strategy* focus_heal_targets(PlayerbotAI* botAI) { return new FocusHealTargetsStrategy(botAI); }
}; };
class MovementStrategyContext : public NamedObjectContext<Strategy> class MovementStrategyContext : public NamedObjectContext<Strategy>

View File

@ -473,21 +473,6 @@ bool LossOfControlTrigger::IsActive()
bot->HasAuraType(SPELL_AURA_MOD_CHARM); bot->HasAuraType(SPELL_AURA_MOD_CHARM);
} }
bool FearCharmSleepTrigger::IsActive()
{
return bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraType(SPELL_AURA_MOD_CHARM) ||
bot->HasAuraType(SPELL_AURA_AOE_CHARM) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP);
}
bool FearSleepSapTrigger::IsActive()
{
return bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SAPPED);
}
bool HasAuraStackTrigger::IsActive() bool HasAuraStackTrigger::IsActive()
{ {
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack); Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack);

View File

@ -754,22 +754,6 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class FearCharmSleepTrigger : public Trigger
{
public:
FearCharmSleepTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fear charm sleep", 1) {}
bool IsActive() override;
};
class FearSleepSapTrigger : public Trigger
{
public:
FearSleepSapTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fear sleep sap", 1) {}
bool IsActive() override;
};
class IsSwimmingTrigger : public Trigger class IsSwimmingTrigger : public Trigger
{ {
public: public:

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "PullTriggers.h"
#include "PositionValue.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "PullStrategy.h"
bool PullStartTrigger::IsActive()
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
return strategy && strategy->IsPullPendingToStart();
}
bool PullEndTrigger::IsActive()
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
if (!strategy || !strategy->HasPullStarted())
return false;
Unit* target = strategy->GetTarget();
if (!target || !target->IsInWorld() || !target->IsAlive())
return true;
time_t const secondsSincePullStarted = time(nullptr) - strategy->GetPullStartTime();
if (secondsSincePullStarted >= PullStrategy::GetMaxPullTime())
return true;
float distanceToPullTarget = bot->GetDistance(target);
if (distanceToPullTarget > ATTACK_DISTANCE && !target->IsNonMeleeSpellCast(false, false, true) &&
(!botAI->IsRanged(bot) || distanceToPullTarget > botAI->GetRange("spell")))
return false;
if (!botAI->HasStrategy("pull back", BOT_STATE_COMBAT))
return true;
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
if (!pullPosition.isSet() || pullPosition.mapId != bot->GetMapId())
return true;
return bot->GetDistance(pullPosition.x, pullPosition.y, pullPosition.z) <= botAI->GetRange("follow");
}
bool ReturnToPullPositionTrigger::IsActive()
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
if (!strategy || !strategy->HasPullStarted() || !target || !target->IsInCombat() ||
!botAI->HasStrategy("pull back", BOT_STATE_COMBAT))
return false;
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
return pullPosition.isSet() && pullPosition.mapId == bot->GetMapId() &&
bot->GetDistance(pullPosition.x, pullPosition.y, pullPosition.z) > sPlayerbotAIConfig.followDistance;
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PULLTRIGGERS_H
#define _PLAYERBOT_PULLTRIGGERS_H
#include "Trigger.h"
class PullStartTrigger : public Trigger
{
public:
PullStartTrigger(PlayerbotAI* botAI, std::string const name = "pull start") : Trigger(botAI, name) {}
bool IsActive() override;
};
class PullEndTrigger : public Trigger
{
public:
PullEndTrigger(PlayerbotAI* botAI, std::string const name = "pull end") : Trigger(botAI, name) {}
bool IsActive() override;
};
class ReturnToPullPositionTrigger : public Trigger
{
public:
ReturnToPullPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "return to pull position") {}
bool IsActive() override;
};
#endif

View File

@ -16,5 +16,5 @@ bool NoRtiTrigger::IsActive()
return false; return false;
Unit* target = AI_VALUE(Unit*, "rti target"); Unit* target = AI_VALUE(Unit*, "rti target");
return target == nullptr; return target != nullptr;
} }

View File

@ -8,7 +8,7 @@
#include "CellImpl.h" #include "CellImpl.h"
#include "PathGenerator.h" #include "PathGenerator.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "MapCollisionData.h" #include "MMapFactory.h"
bool MoveStuckTrigger::IsActive() bool MoveStuckTrigger::IsActive()
{ {
@ -89,7 +89,8 @@ bool MoveLongStuckTrigger::IsActive()
return true; return true;
} }
if (bot->GetMap()->IsGridCreated(GridCoord(cell.GridX(), cell.GridY()))) if (cell.GridX() > 0 && cell.GridY() > 0 &&
!MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(botPos.GetMapId(), cell.GridX(), cell.GridY()))
{ {
// LOG_INFO("playerbots", "Bot {} {}:{} <{}> was in unloaded grid {},{} on map {}", // LOG_INFO("playerbots", "Bot {} {}:{} <{}> was in unloaded grid {},{} on map {}",
// bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), // bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(),

View File

@ -1,49 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_WAITFORATTACKTRIGGERS_H
#define _PLAYERBOT_WAITFORATTACKTRIGGERS_H
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "Trigger.h"
#include "WaitForAttackStrategy.h"
class PlayerbotAI;
class WaitForAttackSafeDistanceTrigger : public Trigger
{
public:
WaitForAttackSafeDistanceTrigger(PlayerbotAI* botAI)
: Trigger(botAI, "wait for attack safe distance") {}
bool IsActive() override
{
if (!WaitForAttackStrategy::ShouldWait(botAI))
return false;
// Do not move if stay strategy is set
if (botAI->HasStrategy("stay", botAI->GetState()))
return false;
// Do not move if currently being targeted
if (!bot->getAttackers().empty())
return false;
Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
return false;
float safeDistance = WaitForAttackStrategy::GetSafeDistance();
float safeDistanceThreshold = WaitForAttackStrategy::GetSafeDistanceThreshold();
float distanceToTarget = ServerFacade::instance().GetDistance2d(bot, target);
return (distanceToTarget > (safeDistance + safeDistanceThreshold)) ||
(distanceToTarget < (safeDistance - safeDistanceThreshold));
}
};
#endif

View File

@ -16,12 +16,10 @@
#include "NewRpgStrategy.h" #include "NewRpgStrategy.h"
#include "NewRpgTriggers.h" #include "NewRpgTriggers.h"
#include "PvpTriggers.h" #include "PvpTriggers.h"
#include "PullTriggers.h"
#include "RpgTriggers.h" #include "RpgTriggers.h"
#include "RtiTriggers.h" #include "RtiTriggers.h"
#include "StuckTriggers.h" #include "StuckTriggers.h"
#include "TravelTriggers.h" #include "TravelTriggers.h"
#include "WaitForAttackTriggers.h"
class PlayerbotAI; class PlayerbotAI;
@ -62,8 +60,6 @@ public:
creators["generic boost"] = &TriggerContext::generic_boost; creators["generic boost"] = &TriggerContext::generic_boost;
creators["loss of control"] = &TriggerContext::loss_of_control; creators["loss of control"] = &TriggerContext::loss_of_control;
creators["fear charm sleep"] = &TriggerContext::fear_charm_sleep;
creators["fear sleep sap"] = &TriggerContext::fear_sleep_sap;
creators["protect party member"] = &TriggerContext::protect_party_member; creators["protect party member"] = &TriggerContext::protect_party_member;
@ -130,9 +126,6 @@ public:
creators["has attackers"] = &TriggerContext::has_attackers; creators["has attackers"] = &TriggerContext::has_attackers;
creators["no possible targets"] = &TriggerContext::no_possible_targets; creators["no possible targets"] = &TriggerContext::no_possible_targets;
creators["possible adds"] = &TriggerContext::possible_adds; creators["possible adds"] = &TriggerContext::possible_adds;
creators["pull start"] = &TriggerContext::pull_start;
creators["pull end"] = &TriggerContext::pull_end;
creators["return to pull position"] = &TriggerContext::return_to_pull_position;
creators["no drink"] = &TriggerContext::no_drink; creators["no drink"] = &TriggerContext::no_drink;
creators["no food"] = &TriggerContext::no_food; creators["no food"] = &TriggerContext::no_food;
@ -234,12 +227,10 @@ public:
creators["wander npc status"] = &TriggerContext::wander_npc_status; creators["wander npc status"] = &TriggerContext::wander_npc_status;
creators["do quest status"] = &TriggerContext::do_quest_status; creators["do quest status"] = &TriggerContext::do_quest_status;
creators["travel flight status"] = &TriggerContext::travel_flight_status; creators["travel flight status"] = &TriggerContext::travel_flight_status;
creators["outdoor pvp status"] = &TriggerContext::outdoor_pvp_status;
creators["can self resurrect"] = &TriggerContext::can_self_resurrect; creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
creators["can fish"] = &TriggerContext::can_fish; creators["can fish"] = &TriggerContext::can_fish;
creators["can use fishing bobber"] = &TriggerContext::can_use_fishing_bobber; creators["can use fishing bobber"] = &TriggerContext::can_use_fishing_bobber;
creators["new pet"] = &TriggerContext::new_pet; creators["new pet"] = &TriggerContext::new_pet;
creators["wait for attack safe distance"] = &TriggerContext::wait_for_attack_safe_distance;
} }
private: private:
@ -284,9 +275,6 @@ private:
static Trigger* swimming(PlayerbotAI* botAI) { return new IsSwimmingTrigger(botAI); } static Trigger* swimming(PlayerbotAI* botAI) { return new IsSwimmingTrigger(botAI); }
static Trigger* no_possible_targets(PlayerbotAI* botAI) { return new NoPossibleTargetsTrigger(botAI); } static Trigger* no_possible_targets(PlayerbotAI* botAI) { return new NoPossibleTargetsTrigger(botAI); }
static Trigger* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsTrigger(botAI); } static Trigger* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsTrigger(botAI); }
static Trigger* pull_start(PlayerbotAI* botAI) { return new PullStartTrigger(botAI); }
static Trigger* pull_end(PlayerbotAI* botAI) { return new PullEndTrigger(botAI); }
static Trigger* return_to_pull_position(PlayerbotAI* botAI) { return new ReturnToPullPositionTrigger(botAI); }
static Trigger* can_loot(PlayerbotAI* botAI) { return new CanLootTrigger(botAI); } static Trigger* can_loot(PlayerbotAI* botAI) { return new CanLootTrigger(botAI); }
static Trigger* far_from_loot_target(PlayerbotAI* botAI) { return new FarFromCurrentLootTrigger(botAI); } static Trigger* far_from_loot_target(PlayerbotAI* botAI) { return new FarFromCurrentLootTrigger(botAI); }
static Trigger* far_from_master(PlayerbotAI* botAI) { return new FarFromMasterTrigger(botAI); } static Trigger* far_from_master(PlayerbotAI* botAI) { return new FarFromMasterTrigger(botAI); }
@ -377,8 +365,6 @@ private:
} }
static Trigger* generic_boost(PlayerbotAI* botAI) { return new GenericBoostTrigger(botAI); } static Trigger* generic_boost(PlayerbotAI* botAI) { return new GenericBoostTrigger(botAI); }
static Trigger* loss_of_control(PlayerbotAI* botAI) { return new LossOfControlTrigger(botAI); } static Trigger* loss_of_control(PlayerbotAI* botAI) { return new LossOfControlTrigger(botAI); }
static Trigger* fear_charm_sleep(PlayerbotAI* botAI) { return new FearCharmSleepTrigger(botAI); }
static Trigger* fear_sleep_sap(PlayerbotAI* botAI) { return new FearSleepSapTrigger(botAI); }
static Trigger* PartyMemberCriticalHealth(PlayerbotAI* botAI) static Trigger* PartyMemberCriticalHealth(PlayerbotAI* botAI)
{ {
return new PartyMemberCriticalHealthTrigger(botAI); return new PartyMemberCriticalHealthTrigger(botAI);
@ -444,12 +430,10 @@ private:
static Trigger* wander_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_WANDER_NPC); } static Trigger* wander_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_WANDER_NPC); }
static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); } static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); } static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); }
static Trigger* outdoor_pvp_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_OUTDOOR_PVP); }
static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); } static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); } static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); }
static Trigger* can_use_fishing_bobber(PlayerbotAI* ai) { return new CanUseFishingBobberTrigger(ai); } static Trigger* can_use_fishing_bobber(PlayerbotAI* ai) { return new CanUseFishingBobberTrigger(ai); }
static Trigger* new_pet(PlayerbotAI* ai) { return new NewPetTrigger(ai); } static Trigger* new_pet(PlayerbotAI* ai) { return new NewPetTrigger(ai); }
static Trigger* wait_for_attack_safe_distance(PlayerbotAI* ai) { return new WaitForAttackSafeDistanceTrigger(ai); }
}; };
#endif #endif

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#pragma once
#include <string>
#include <functional>
#include "Common.h"
#include "Group.h"
#include "Chat.h"
#include "Language.h"
class Player;
class PlayerbotAI;
namespace ai::buff
{
// Build an aura qualifier "single + greater" to avoid double-buffing
std::string MakeAuraQualifierForBuff(std::string const& name);
// Returns the group spell name for a given single-target buff.
// If no group equivalent exists, returns "".
std::string GroupVariantFor(std::string const& name);
// Checks if the bot has the required reagents to cast a spell (by its spellId).
// Returns false if the spellId is invalid.
bool HasRequiredReagents(Player* bot, uint32 spellId);
// Applies the "switch to group buff" policy if: the bot is in a group of size x+,
// the group variant is known/useful, and reagents are available. Otherwise, returns baseName.
// If announceOnMissing == true and reagents are missing, calls the 'announce' callback
// (if provided) to notify the party/raid.
std::string UpgradeToGroupIfAppropriate(
Player* bot,
PlayerbotAI* botAI,
std::string const& baseName,
bool announceOnMissing = false,
std::function<void(std::string const&)> announce = {}
);
}
namespace ai::spell
{
bool HasSpellOrCategoryCooldown(Player* bot, uint32 spellId);
}
namespace ai::chat {
inline std::function<void(std::string const&)> MakeGroupAnnouncer(Player* me)
{
return [me](std::string const& msg)
{
if (Group* g = me->GetGroup())
{
WorldPacket data;
ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY;
ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str());
g->BroadcastPacket(&data, true, -1, me->GetGUID());
}
else
{
me->Say(msg, LANG_UNIVERSAL);
}
};
}
}

View File

@ -19,7 +19,6 @@ WorldLocation ArrowFormation::GetLocationInternal()
uint32 tankLines = 1 + tanks.Size() / 6; uint32 tankLines = 1 + tanks.Size() / 6;
uint32 meleeLines = 1 + melee.Size() / 6; uint32 meleeLines = 1 + melee.Size() / 6;
uint32 rangedLines = 1 + ranged.Size() / 6; uint32 rangedLines = 1 + ranged.Size() / 6;
//TODO Implement Healer Lines
uint32 healerLines = 1 + healers.Size() / 6; uint32 healerLines = 1 + healers.Size() / 6;
float offset = 0.f; float offset = 0.f;
@ -148,7 +147,7 @@ UnitPosition MultiLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint3
return placer.Place(unit, indexInLine, lineSize); return placer.Place(unit, indexInLine, lineSize);
} }
UnitPosition SingleLineUnitPlacer::Place(FormationUnit* /*unit*/, uint32 index, uint32 count) UnitPosition SingleLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint32 count)
{ {
float angle = orientation - M_PI / 2.0f; float angle = orientation - M_PI / 2.0f;
float x = cos(angle) * sPlayerbotAIConfig.followDistance * ((float)index - (float)count / 2); float x = cos(angle) * sPlayerbotAIConfig.followDistance * ((float)index - (float)count / 2);

View File

@ -20,7 +20,7 @@ public:
} }
public: public:
void CheckAttacker(Unit* creature, ThreatManager* /*threatMgr*/) override void CheckAttacker(Unit* creature, ThreatManager* threatMgr) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (!botAI->CanCastSpell(spell, creature)) if (!botAI->CanCastSpell(spell, creature))

View File

@ -13,7 +13,7 @@ public:
{ {
} }
void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
{ {
if (botAI->HasAura(spell, attacker)) if (botAI->HasAura(spell, attacker))
result = attacker; result = attacker;

View File

@ -50,7 +50,7 @@ public:
result = nullptr; result = nullptr;
} }
void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
{ {
if (Group* group = botAI->GetBot()->GetGroup()) if (Group* group = botAI->GetBot()->GetGroup())
{ {

View File

@ -11,6 +11,8 @@ std::vector<Item*> InventoryItemValueBase::Find(std::string const qualifier)
{ {
std::vector<Item*> result; std::vector<Item*> result;
Player* bot = InventoryAction::botAI->GetBot();
std::vector<Item*> items = InventoryAction::parseItems(qualifier); std::vector<Item*> items = InventoryAction::parseItems(qualifier);
for (Item* item : items) for (Item* item : items)
result.push_back(item); result.push_back(item);

View File

@ -17,7 +17,7 @@ class InventoryItemValueBase : public InventoryAction
public: public:
InventoryItemValueBase(PlayerbotAI* botAI) : InventoryAction(botAI, "empty") {} InventoryItemValueBase(PlayerbotAI* botAI) : InventoryAction(botAI, "empty") {}
bool Execute(Event /*event*/) override { return false; } bool Execute(Event event) override { return false; }
protected: protected:
std::vector<Item*> Find(std::string const qualifier); std::vector<Item*> Find(std::string const qualifier);

View File

@ -48,6 +48,23 @@ Item* ItemForSpellValue::Calculate()
} }
} }
// Workaround as some spells have no item mask (e.g. shaman weapon enhancements)
if (!strcmpi(spellInfo->SpellName[0], "rockbiter weapon") ||
!strcmpi(spellInfo->SpellName[0], "flametongue weapon") ||
!strcmpi(spellInfo->SpellName[0], "earthliving weapon") ||
!strcmpi(spellInfo->SpellName[0], "frostbrand weapon") || !strcmpi(spellInfo->SpellName[0], "windfury weapon"))
{
itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_MAINHAND, spellInfo);
if (itemForSpell && itemForSpell->GetTemplate()->Class == ITEM_CLASS_WEAPON)
return itemForSpell;
itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_OFFHAND, spellInfo);
if (itemForSpell && itemForSpell->GetTemplate()->Class == ITEM_CLASS_WEAPON)
return itemForSpell;
return nullptr;
}
if (!(spellInfo->Targets & TARGET_FLAG_ITEM)) if (!(spellInfo->Targets & TARGET_FLAG_ITEM))
return nullptr; return nullptr;

View File

@ -234,11 +234,6 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
calculator.SetItemSetBonus(false); calculator.SetItemSetBonus(false);
calculator.SetOverflowPenalty(false); calculator.SetOverflowPenalty(false);
// Apply PvP weights if the bot is specced for PvP
bool isPvp = sRandomPlayerbotMgr.IsSpecPvp(bot->GetGUID().GetCounter(), bot->getClass());
if (isPvp)
calculator.SetPvpSpec(true);
float itemScore = calculator.CalculateItem(itemProto->ItemId, randomPropertyId); float itemScore = calculator.CalculateItem(itemProto->ItemId, randomPropertyId);
if (itemScore) if (itemScore)
@ -869,6 +864,8 @@ bool ItemUsageValue::SpellGivesSkillUp(uint32 spellId, Player* bot)
{ {
uint32 SkillValue = bot->GetPureSkillValue(skill->SkillLine); uint32 SkillValue = bot->GetPureSkillValue(skill->SkillLine);
uint32 craft_skill_gain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_CRAFTING);
if (SkillGainChance(SkillValue, skill->TrivialSkillLineRankHigh, if (SkillGainChance(SkillValue, skill->TrivialSkillLineRankHigh,
(skill->TrivialSkillLineRankHigh + skill->TrivialSkillLineRankLow) / 2, (skill->TrivialSkillLineRankHigh + skill->TrivialSkillLineRankLow) / 2,
skill->TrivialSkillLineRankLow) > 0) skill->TrivialSkillLineRankLow) > 0)

View File

@ -13,7 +13,7 @@ class FindLeastHpTargetStrategy : public FindNonCcTargetStrategy
public: public:
FindLeastHpTargetStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minHealth(0) {} FindLeastHpTargetStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minHealth(0) {}
void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
{ {
if (IsCcTarget(attacker)) if (IsCcTarget(attacker))
return; return;

View File

@ -60,7 +60,7 @@ public:
class AllLootStrategy : public LootStrategy class AllLootStrategy : public LootStrategy
{ {
public: public:
bool CanLoot(ItemTemplate const* /*proto*/, AiObjectContext* /*context*/) override { return true; } bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override { return true; }
std::string const GetName() override { return "all"; } std::string const GetName() override { return "all"; }
}; };

View File

@ -28,4 +28,4 @@ void NearestCorpsesValue::FindUnits(std::list<Unit*>& targets)
Cell::VisitObjects(bot, searcher, range); Cell::VisitObjects(bot, searcher, range);
} }
bool NearestCorpsesValue::AcceptUnit(Unit* /*unit*/) { return true; } bool NearestCorpsesValue::AcceptUnit(Unit* unit) { return true; }

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "PartyMemberSnaredTargetValue.h"
#include <limits>
#include "PlayerbotAIAware.h"
#include "Playerbots.h"
class PartyMemberSnaredTargetPredicate : public FindPlayerPredicate, public PlayerbotAIAware
{
public:
PartyMemberSnaredTargetPredicate(PlayerbotAI* botAI)
: PlayerbotAIAware(botAI)
{
}
bool Check(Unit* unit) override
{
if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit == botAI->GetBot())
return false;
if (unit->GetMapId() != botAI->GetBot()->GetMapId())
return false;
if (!botAI->GetBot()->IsWithinLOSInMap(unit))
return false;
return botAI->IsMovementImpaired(unit);
}
};
Unit* PartyMemberSnaredTargetValue::Calculate()
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
PartyMemberSnaredTargetPredicate predicate(botAI);
Player* bestTarget = nullptr;
float closestDistanceSq = std::numeric_limits<float>::max();
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member)
continue;
if (!predicate.Check(member))
continue;
float const distanceSq = bot->GetExactDist2dSq(member->GetPositionX(), member->GetPositionY());
if (distanceSq < closestDistanceSq)
{
closestDistanceSq = distanceSq;
bestTarget = member;
}
}
return bestTarget;
}

View File

@ -1,24 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PARTYMEMBERSNAREDTARGETVALUE_H
#define _PLAYERBOT_PARTYMEMBERSNAREDTARGETVALUE_H
#include "NamedObjectContext.h"
#include "PartyMemberValue.h"
class PartyMemberSnaredTargetValue : public PartyMemberValue
{
public:
PartyMemberSnaredTargetValue(PlayerbotAI* botAI, std::string const name = "party member snared target")
: PartyMemberValue(botAI, name)
{
}
protected:
Unit* Calculate() override;
};
#endif

View File

@ -38,36 +38,6 @@ Unit* PartyMemberToHeal::Calculate()
bool isRaid = bot->GetGroup()->isRaidGroup(); bool isRaid = bot->GetGroup()->isRaidGroup();
MinValueCalculator calc(100); MinValueCalculator calc(100);
// If focus heal targets strategy is active, only heal those targets
if (botAI->HasStrategy("focus heal targets", BOT_STATE_COMBAT))
{
std::list<ObjectGuid> const focusHealTargets =
AI_VALUE(std::list<ObjectGuid>, "focus heal targets");
for (ObjectGuid const& focusHealTarget : focusHealTargets)
{
Player* player = ObjectAccessor::FindPlayer(focusHealTarget);
if (!player || !player->IsInWorld() || !player->IsAlive() || !player->IsInSameGroupWith(bot))
continue;
float health = player->GetHealthPct();
if (isRaid || health < sPlayerbotAIConfig.mediumHealth ||
!IsTargetOfSpellCast(player, predicate))
{
float probeValue = 100.0f;
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
probeValue = health + 30.0f;
else
probeValue = health + player->GetDistance2d(bot) / 10.0f;
if (probeValue < calc.minValue && Check(player))
calc.probe(probeValue, player);
}
}
return (Unit*)calc.param;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{ {
Player* player = gref->GetSource(); Player* player = gref->GetSource();
@ -75,17 +45,17 @@ Unit* PartyMemberToHeal::Calculate()
continue; continue;
if (player && player->IsAlive()) if (player && player->IsAlive())
{ {
float health = player->GetHealthPct(); uint8 health = player->GetHealthPct();
if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate)) if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate))
{ {
float probeValue = 100.0f; uint32 probeValue = 100;
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance) if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
{ {
probeValue = health + 30.0f; probeValue = health + 30;
} }
else else
{ {
probeValue = health + player->GetDistance2d(bot) / 10.0f; probeValue = health + player->GetDistance2d(bot) / 10;
} }
// delay Check player to here for better performance // delay Check player to here for better performance
if (probeValue < calc.minValue && Check(player)) if (probeValue < calc.minValue && Check(player))
@ -98,10 +68,10 @@ Unit* PartyMemberToHeal::Calculate()
Pet* pet = player->GetPet(); Pet* pet = player->GetPet();
if (pet && pet->IsAlive()) if (pet && pet->IsAlive())
{ {
float health = ((Unit*)pet)->GetHealthPct(); uint8 health = ((Unit*)pet)->GetHealthPct();
float probeValue = 100.0f; uint32 probeValue = 100;
if (isRaid || health < sPlayerbotAIConfig.mediumHealth) if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
probeValue = health + 30.0f; probeValue = health + 30;
// delay Check pet to here for better performance // delay Check pet to here for better performance
if (probeValue < calc.minValue && Check(pet)) if (probeValue < calc.minValue && Check(pet))
{ {
@ -112,10 +82,10 @@ Unit* PartyMemberToHeal::Calculate()
Unit* charm = player->GetCharm(); Unit* charm = player->GetCharm();
if (charm && charm->IsAlive()) if (charm && charm->IsAlive())
{ {
float health = charm->GetHealthPct(); uint8 health = charm->GetHealthPct();
float probeValue = 100.0f; uint32 probeValue = 100;
if (isRaid || health < sPlayerbotAIConfig.mediumHealth) if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
probeValue = health + 30.0f; probeValue = health + 30;
// delay Check charm to here for better performance // delay Check charm to here for better performance
if (probeValue < calc.minValue && Check(charm)) if (probeValue < calc.minValue && Check(charm))
{ {

View File

@ -27,7 +27,7 @@ Unit* PartyMemberValue::FindPartyMember(std::vector<Player*>* party, FindPlayerP
return nullptr; return nullptr;
} }
Unit* PartyMemberValue::FindPartyMember(FindPlayerPredicate& predicate, bool /*ignoreOutOfGroup*/) Unit* PartyMemberValue::FindPartyMember(FindPlayerPredicate& predicate, bool ignoreOutOfGroup)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
// GuidVector nearestPlayers; // GuidVector nearestPlayers;

View File

@ -13,7 +13,6 @@
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "NearestGameObjects.h" #include "NearestGameObjects.h"
#include <unordered_set>
std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags; std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
@ -89,11 +88,8 @@ bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags; std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags;
// Sparse starting zones where the default scan range is insufficient for WANDER_NPC (requires >= 3 NPCs)
static const std::unordered_set<uint32> rpgRangeOverrideAreaIds = { 3526 /* Ammen Vale */, 2117 /* Deathknell */ };
PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range) PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range)
: NearestUnitsValue(botAI, "possible new rpg targets", range, true), defaultRange(range) : NearestUnitsValue(botAI, "possible new rpg targets", range, true)
{ {
if (allowedNpcFlags.empty()) if (allowedNpcFlags.empty())
{ {
@ -123,11 +119,6 @@ PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float
GuidVector PossibleNewRpgTargetsValue::Calculate() GuidVector PossibleNewRpgTargetsValue::Calculate()
{ {
if (rpgRangeOverrideAreaIds.count(bot->GetAreaId()) && defaultRange < 200.0f)
range = 200.0f;
else
range = defaultRange;
std::list<Unit*> targets; std::list<Unit*> targets;
FindUnits(targets); FindUnits(targets);

View File

@ -35,8 +35,6 @@ public:
protected: protected:
void FindUnits(std::list<Unit*>& targets) override; void FindUnits(std::list<Unit*>& targets) override;
bool AcceptUnit(Unit* unit) override; bool AcceptUnit(Unit* unit) override;
private:
float defaultRange;
}; };
class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue

View File

@ -40,13 +40,13 @@ bool SpellCastUsefulValue::Calculate()
return false; return false;
} }
if (qualifier == "windfury weapon" || qualifier == "flametongue weapon" || // TODO: workaround
qualifier == "frostbrand weapon" || qualifier == "rockbiter weapon" || if (qualifier == "windfury weapon" || qualifier == "flametongue weapon" || qualifier == "frostbrand weapon" ||
qualifier == "earthliving weapon" || qualifier == "spellstone") qualifier == "rockbiter weapon" || qualifier == "earthliving weapon" || qualifier == "spellstone")
{ {
if (Item* item = AI_VALUE2(Item*, "item for spell", spellid); if (Item* item = AI_VALUE2(Item*, "item for spell", spellid))
item && item->IsInWorld() && item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) if (item->IsInWorld() && item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
return false; return false;
} }
std::set<uint32>& skipSpells = AI_VALUE(std::set<uint32>&, "skip spells list"); std::set<uint32>& skipSpells = AI_VALUE(std::set<uint32>&, "skip spells list");

View File

@ -49,7 +49,7 @@ class FindTankTargetSmartStrategy : public FindTargetStrategy
public: public:
FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {} FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {}
void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override
{ {
if (Group* group = botAI->GetBot()->GetGroup()) if (Group* group = botAI->GetBot()->GetGroup())
{ {

View File

@ -161,7 +161,7 @@ Unit* FindTargetValue::Calculate()
return nullptr; return nullptr;
} }
void FindBossTargetStrategy::CheckAttacker(Unit* attacker, ThreatManager* /*threatManager*/) void FindBossTargetStrategy::CheckAttacker(Unit* attacker, ThreatManager* threatManager)
{ {
UnitAI* unitAI = attacker->GetAI(); UnitAI* unitAI = attacker->GetAI();
BossAI* bossAI = dynamic_cast<BossAI*>(unitAI); BossAI* bossAI = dynamic_cast<BossAI*>(unitAI);

View File

@ -116,15 +116,6 @@ public:
} }
}; };
class PullStrategyTargetValue : public ManualSetValue<ObjectGuid>
{
public:
PullStrategyTargetValue(PlayerbotAI* botAI, std::string const name = "pull strategy target")
: ManualSetValue<ObjectGuid>(botAI, ObjectGuid::Empty, name)
{
}
};
class FindTargetValue : public UnitCalculatedValue, public Qualified class FindTargetValue : public UnitCalculatedValue, public Qualified
{ {
public: public:
@ -149,11 +140,4 @@ public:
public: public:
Unit* Calculate(); Unit* Calculate();
}; };
class FocusHealTargetValue : public ManualSetValue<std::list<ObjectGuid>>
{
public:
FocusHealTargetValue(PlayerbotAI* botAI) : ManualSetValue<std::list<ObjectGuid>>(botAI, {}, "focus heal targets") {}
};
#endif #endif

View File

@ -1,25 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_WAITFORATTACKTIMEVALUE_H
#define _PLAYERBOT_WAITFORATTACKTIMEVALUE_H
#include "Value.h"
class PlayerbotAI;
class WaitForAttackTimeValue : public ManualSetValue<uint8>
{
public:
WaitForAttackTimeValue(PlayerbotAI* botAI) : ManualSetValue<uint8>(botAI, 10, "wait for attack time") {}
};
class CombatStartTimeValue : public ManualSetValue<time_t>
{
public:
CombatStartTimeValue(PlayerbotAI* botAI) : ManualSetValue<time_t>(botAI, 0, "combat start time") {}
};
#endif

View File

@ -66,7 +66,6 @@
#include "PartyMemberToDispel.h" #include "PartyMemberToDispel.h"
#include "PartyMemberToHeal.h" #include "PartyMemberToHeal.h"
#include "PartyMemberToResurrect.h" #include "PartyMemberToResurrect.h"
#include "PartyMemberSnaredTargetValue.h"
#include "PartyMemberWithoutAuraValue.h" #include "PartyMemberWithoutAuraValue.h"
#include "PartyMemberWithoutItemValue.h" #include "PartyMemberWithoutItemValue.h"
#include "PetTargetValue.h" #include "PetTargetValue.h"
@ -92,7 +91,6 @@
#include "ThreatValues.h" #include "ThreatValues.h"
#include "TradeValues.h" #include "TradeValues.h"
#include "Value.h" #include "Value.h"
#include "WaitForAttackTimeValue.h"
class PlayerbotAI; class PlayerbotAI;
@ -153,7 +151,6 @@ public:
creators["duel target"] = &ValueContext::duel_target; creators["duel target"] = &ValueContext::duel_target;
creators["party member to dispel"] = &ValueContext::party_member_to_dispel; creators["party member to dispel"] = &ValueContext::party_member_to_dispel;
creators["party member to protect"] = &ValueContext::party_member_to_protect; creators["party member to protect"] = &ValueContext::party_member_to_protect;
creators["party member snared target"] = &ValueContext::party_member_snared_target;
creators["health"] = &ValueContext::health; creators["health"] = &ValueContext::health;
creators["rage"] = &ValueContext::rage; creators["rage"] = &ValueContext::rage;
creators["energy"] = &ValueContext::energy; creators["energy"] = &ValueContext::energy;
@ -241,8 +238,6 @@ public:
creators["travel target"] = &ValueContext::travel_target; creators["travel target"] = &ValueContext::travel_target;
creators["talk target"] = &ValueContext::talk_target; creators["talk target"] = &ValueContext::talk_target;
creators["pull target"] = &ValueContext::pull_target; creators["pull target"] = &ValueContext::pull_target;
creators["pull strategy target"] = &ValueContext::pull_strategy_target;
creators["focus heal targets"] = &ValueContext::focus_heal_targets;
creators["group"] = &ValueContext::group; creators["group"] = &ValueContext::group;
creators["range"] = &ValueContext::range; creators["range"] = &ValueContext::range;
creators["inside target"] = &ValueContext::inside_target; creators["inside target"] = &ValueContext::inside_target;
@ -327,8 +322,6 @@ public:
creators["can fish"] = &ValueContext::can_fish; creators["can fish"] = &ValueContext::can_fish;
creators["can use fishing bobber"] = &ValueContext::can_use_fishing_bobber; creators["can use fishing bobber"] = &ValueContext::can_use_fishing_bobber;
creators["fishing spot"] = &ValueContext::fishing_spot; creators["fishing spot"] = &ValueContext::fishing_spot;
creators["wait for attack time"] = &ValueContext::wait_for_attack_time;
creators["combat start time"] = &ValueContext::combat_start_time;
} }
private: private:
@ -454,7 +447,6 @@ private:
static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); } static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); }
static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); } static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); }
static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); } static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); }
static UntypedValue* party_member_snared_target(PlayerbotAI* botAI) { return new PartyMemberSnaredTargetValue(botAI); }
static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); } static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); }
@ -499,8 +491,6 @@ private:
static UntypedValue* next_rpg_action(PlayerbotAI* botAI) { return new NextRpgActionValue(botAI); } static UntypedValue* next_rpg_action(PlayerbotAI* botAI) { return new NextRpgActionValue(botAI); }
static UntypedValue* travel_target(PlayerbotAI* botAI) { return new TravelTargetValue(botAI); } static UntypedValue* travel_target(PlayerbotAI* botAI) { return new TravelTargetValue(botAI); }
static UntypedValue* pull_target(PlayerbotAI* botAI) { return new PullTargetValue(botAI); } static UntypedValue* pull_target(PlayerbotAI* botAI) { return new PullTargetValue(botAI); }
static UntypedValue* pull_strategy_target(PlayerbotAI* botAI) { return new PullStrategyTargetValue(botAI); }
static UntypedValue* focus_heal_targets(PlayerbotAI* botAI) { return new FocusHealTargetValue(botAI); }
static UntypedValue* bg_master(PlayerbotAI* botAI) { return new BgMasterValue(botAI); } static UntypedValue* bg_master(PlayerbotAI* botAI) { return new BgMasterValue(botAI); }
static UntypedValue* bg_role(PlayerbotAI* botAI) { return new BgRoleValue(botAI); } static UntypedValue* bg_role(PlayerbotAI* botAI) { return new BgRoleValue(botAI); }
@ -588,8 +578,6 @@ private:
{ {
return new ManualSetValue<bool>(ai, false, "custom_glyphs"); return new ManualSetValue<bool>(ai, false, "custom_glyphs");
} }
static UntypedValue* wait_for_attack_time(PlayerbotAI* ai) { return new WaitForAttackTimeValue(ai); }
static UntypedValue* combat_start_time(PlayerbotAI* ai) { return new CombatStartTimeValue(ai); }
}; };
#endif #endif

View File

@ -48,55 +48,3 @@ bool CastRaiseDeadAction::Execute(Event event)
return true; return true;
} }
Unit* CastHysteriaAction::GetTarget()
{
Group* group = bot->GetGroup();
if (!group)
{
if (!bot->HasAura(49016))
return bot;
return nullptr;
}
Unit* rangedDps = nullptr;
Unit* tank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (member->GetMap() != bot->GetMap() || bot->GetDistance(member) > sPlayerbotAIConfig.spellDistance)
continue;
// Skip if already has hysteria
if (member->HasAura(49016))
continue;
// Priority 1: Melee DPS
if (botAI->IsMelee(member) && botAI->IsDps(member))
return member;
// Priority 2: Ranged DPS (physical, not casters)
if (!rangedDps && botAI->IsRanged(member) && botAI->IsDps(member) && !botAI->IsCaster(member))
rangedDps = member;
// Priority 3: Tank
if (!tank && botAI->IsTank(member))
tank = member;
}
if (rangedDps)
return rangedDps;
if (tank)
return tank;
// Fallback to self if no hysteria
if (!bot->HasAura(49016))
return bot;
return nullptr;
}

View File

@ -340,11 +340,4 @@ public:
CastBloodTapAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "blood tap") {} CastBloodTapAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "blood tap") {}
}; };
class CastHysteriaAction : public CastSpellAction
{
public:
CastHysteriaAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hysteria") {}
Unit* GetTarget() override;
};
#endif #endif

View File

@ -8,11 +8,11 @@
#include "BloodDKStrategy.h" #include "BloodDKStrategy.h"
#include "DKActions.h" #include "DKActions.h"
#include "DKTriggers.h" #include "DKTriggers.h"
#include "DeathKnightPullStrategy.h"
#include "FrostDKStrategy.h" #include "FrostDKStrategy.h"
#include "GenericDKNonCombatStrategy.h" #include "GenericDKNonCombatStrategy.h"
#include "GenericTriggers.h" #include "GenericTriggers.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PullStrategy.h"
#include "UnholyDKStrategy.h" #include "UnholyDKStrategy.h"
class DeathKnightStrategyFactoryInternal : public NamedObjectContext<Strategy> class DeathKnightStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -28,7 +28,7 @@ public:
private: private:
static Strategy* nc(PlayerbotAI* botAI) { return new GenericDKNonCombatStrategy(botAI); } static Strategy* nc(PlayerbotAI* botAI) { return new GenericDKNonCombatStrategy(botAI); }
static Strategy* pull(PlayerbotAI* botAI) { return new DeathKnightPullStrategy(botAI); } static Strategy* pull(PlayerbotAI* botAI) { return new PullStrategy(botAI, "icy touch"); }
static Strategy* frost_aoe(PlayerbotAI* botAI) { return new FrostDKAoeStrategy(botAI); } static Strategy* frost_aoe(PlayerbotAI* botAI) { return new FrostDKAoeStrategy(botAI); }
static Strategy* unholy_aoe(PlayerbotAI* botAI) { return new UnholyDKAoeStrategy(botAI); } static Strategy* unholy_aoe(PlayerbotAI* botAI) { return new UnholyDKAoeStrategy(botAI); }
}; };
@ -100,7 +100,6 @@ public:
creators["dd cd and no desolation"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_no_desolation; creators["dd cd and no desolation"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_no_desolation;
creators["death and decay cooldown"] = &DeathKnightTriggerFactoryInternal::death_and_decay_cooldown; creators["death and decay cooldown"] = &DeathKnightTriggerFactoryInternal::death_and_decay_cooldown;
creators["army of the dead"] = &DeathKnightTriggerFactoryInternal::army_of_the_dead; creators["army of the dead"] = &DeathKnightTriggerFactoryInternal::army_of_the_dead;
creators["hysteria no cd"] = &DeathKnightTriggerFactoryInternal::hysteria_no_cd;
} }
private: private:
@ -153,7 +152,6 @@ private:
} }
static Trigger* death_and_decay_cooldown(PlayerbotAI* botAI) { return new DeathAndDecayCooldownTrigger(botAI); } static Trigger* death_and_decay_cooldown(PlayerbotAI* botAI) { return new DeathAndDecayCooldownTrigger(botAI); }
static Trigger* army_of_the_dead(PlayerbotAI* botAI) { return new ArmyOfTheDeadTrigger(botAI); } static Trigger* army_of_the_dead(PlayerbotAI* botAI) { return new ArmyOfTheDeadTrigger(botAI); }
static Trigger* hysteria_no_cd(PlayerbotAI* botAI) { return new HysteriaNoCooldownTrigger(botAI); }
}; };
class DeathKnightAiObjectContextInternal : public NamedObjectContext<Action> class DeathKnightAiObjectContextInternal : public NamedObjectContext<Action>
@ -211,7 +209,7 @@ public:
creators["vampiric blood"] = &DeathKnightAiObjectContextInternal::vampiric_blood; creators["vampiric blood"] = &DeathKnightAiObjectContextInternal::vampiric_blood;
creators["death pact"] = &DeathKnightAiObjectContextInternal::death_pact; creators["death pact"] = &DeathKnightAiObjectContextInternal::death_pact;
creators["death rune_mastery"] = &DeathKnightAiObjectContextInternal::death_rune_mastery; creators["death rune_mastery"] = &DeathKnightAiObjectContextInternal::death_rune_mastery;
creators["hysteria"] = &DeathKnightAiObjectContextInternal::hysteria; // creators["hysteria"] = &DeathKnightAiObjectContextInternal::hysteria;
creators["dancing rune weapon"] = &DeathKnightAiObjectContextInternal::dancing_rune_weapon; creators["dancing rune weapon"] = &DeathKnightAiObjectContextInternal::dancing_rune_weapon;
creators["dark command"] = &DeathKnightAiObjectContextInternal::dark_command; creators["dark command"] = &DeathKnightAiObjectContextInternal::dark_command;
} }
@ -267,7 +265,7 @@ private:
static Action* vampiric_blood(PlayerbotAI* botAI) { return new CastVampiricBloodAction(botAI); } static Action* vampiric_blood(PlayerbotAI* botAI) { return new CastVampiricBloodAction(botAI); }
static Action* death_pact(PlayerbotAI* botAI) { return new CastDeathPactAction(botAI); } static Action* death_pact(PlayerbotAI* botAI) { return new CastDeathPactAction(botAI); }
static Action* death_rune_mastery(PlayerbotAI* botAI) { return new CastDeathRuneMasteryAction(botAI); } static Action* death_rune_mastery(PlayerbotAI* botAI) { return new CastDeathRuneMasteryAction(botAI); }
static Action* hysteria(PlayerbotAI* botAI) { return new CastHysteriaAction(botAI); } // static Action* hysteria(PlayerbotAI* botAI) { return new CastHysteriaAction(botAI); }
static Action* dancing_rune_weapon(PlayerbotAI* botAI) { return new CastDancingRuneWeaponAction(botAI); } static Action* dancing_rune_weapon(PlayerbotAI* botAI) { return new CastDancingRuneWeaponAction(botAI); }
static Action* dark_command(PlayerbotAI* botAI) { return new CastDarkCommandAction(botAI); } static Action* dark_command(PlayerbotAI* botAI) { return new CastDarkCommandAction(botAI); }
static Action* mind_freeze_on_enemy_healer(PlayerbotAI* botAI) static Action* mind_freeze_on_enemy_healer(PlayerbotAI* botAI)

View File

@ -50,9 +50,7 @@ private:
{ {
NextAction("frost presence") NextAction("frost presence")
}, },
/*A*/ { /*A*/ {},
NextAction("blood strike")
},
/*C*/ {} /*C*/ {}
); );
} }
@ -91,11 +89,13 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
std::vector<NextAction> BloodDKStrategy::getDefaultActions() std::vector<NextAction> BloodDKStrategy::getDefaultActions()
{ {
return { return {
NextAction("rune strike", ACTION_DEFAULT + 0.6f), NextAction("rune strike", ACTION_DEFAULT + 0.8f),
NextAction("icy touch", ACTION_DEFAULT + 0.5f), NextAction("icy touch", ACTION_DEFAULT + 0.7f),
NextAction("heart strike", ACTION_DEFAULT + 0.4f), NextAction("heart strike", ACTION_DEFAULT + 0.6f),
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.3f), NextAction("blood strike", ACTION_DEFAULT + 0.5f),
NextAction("death coil", ACTION_DEFAULT + 0.2f), NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
NextAction("death coil", ACTION_DEFAULT + 0.3f),
NextAction("plague strike", ACTION_DEFAULT + 0.2f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f), NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT) NextAction("melee", ACTION_DEFAULT)
}; };
@ -105,14 +105,6 @@ void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDKStrategy::InitTriggers(triggers); GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode(
"hysteria no cd",
{
NextAction("hysteria", ACTION_NORMAL + 4)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode(
"rune strike", "rune strike",
@ -170,12 +162,4 @@ void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
} }
) )
); );
triggers.push_back(
new TriggerNode(
"high unholy rune",
{
NextAction("death strike", ACTION_HIGH + 1)
}
)
);
} }

View File

@ -1,43 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "DeathKnightPullStrategy.h"
#include "AiObjectContext.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
std::string DeathKnightPullStrategy::GetPullActionName() const
{
Player* bot = botAI->GetBot();
Unit* target = GetTarget();
if (!bot || !target ||
(!botAI->HasStrategy("blood", BOT_STATE_COMBAT) && !botAI->HasStrategy("blood", BOT_STATE_NON_COMBAT)))
{
return PullStrategy::GetPullActionName();
}
uint32 const deathGripSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "death grip")->Get();
if (deathGripSpellId && bot->HasSpell(deathGripSpellId) &&
botAI->CanCastSpell(deathGripSpellId, target))
{
return "death grip";
}
uint32 const icyTouchSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "icy touch")->Get();
if (!icyTouchSpellId || !bot->HasSpell(icyTouchSpellId) ||
!botAI->CanCastSpell(icyTouchSpellId, target))
{
uint32 const darkCommandSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "dark command")->Get();
if (darkCommandSpellId && bot->HasSpell(darkCommandSpellId) &&
botAI->CanCastSpell(darkCommandSpellId, target))
{
return "dark command";
}
}
return PullStrategy::GetPullActionName();
}

View File

@ -1,19 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_DEATH_KNIGHT_PULL_STRATEGY_H
#define _PLAYERBOT_DEATH_KNIGHT_PULL_STRATEGY_H
#include "PullStrategy.h"
class DeathKnightPullStrategy : public PullStrategy
{
public:
DeathKnightPullStrategy(PlayerbotAI* botAI) : PullStrategy(botAI, "icy touch") {}
std::string GetPullActionName() const override;
};
#endif

View File

@ -91,6 +91,7 @@ std::vector<NextAction> FrostDKStrategy::getDefaultActions()
return { return {
NextAction("obliterate", ACTION_DEFAULT + 0.7f), NextAction("obliterate", ACTION_DEFAULT + 0.7f),
NextAction("frost strike", ACTION_DEFAULT + 0.4f), NextAction("frost strike", ACTION_DEFAULT + 0.4f),
NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f), NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT) NextAction("melee", ACTION_DEFAULT)
}; };

Some files were not shown because too many files have changed in this diff Show More