Compare commits

..

61 Commits

Author SHA1 Message Date
Keleborn
652c7a1996 Merge pull request #2175 from mod-playerbots/test-staging
Test staging
2026-03-06 07:55:29 -08:00
Benjamin Jackson
d32b10de15 Add dedicated icon image and separate readme banner image. (#2177)
# Pull Request

This PR separates the image currently in the readme (currently named
`icon.png`) into two, one that is an exact copy (`banner.png`) and one
that only includes the image component (`icon.png`). This is to make the
module more approachable when scrolling through the [AzerothCore
catalogue](https://www.azerothcore.org/catalogue.html) as [it uses the
`icon.png`
image](https://github.com/user-attachments/assets/4c820f22-2a52-42b9-b360-f0e4d1496060).

---

## Feature Evaluation

Please answer the following:

- This has no changes to code, and has no impact on it.

---

## How to Test the Changes

- This PR requires no testing.

## Complexity & Impact

Does this change add new decision branches?
- - [X] No

Does this change increase per-bot or per-tick processing?
- - [X] No

Could this logic scale poorly under load?
- - [X] No
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [X] No

If this introduces more advanced or AI-heavy logic:
- - [X] Lightweight mode remains the default
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [X] No

---

## Final Checklist

- - [X] Stability is not compromised
- - [X] Performance impact is understood, tested, and acceptable
- - [X] Added logic complexity is justified and explained
- - [X] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-03-01 07:24:37 -08:00
kadeshar
3b6cf5060e Translation cleanup (#2154)
# Pull Request

Translation cleanup for better track changes in translations.

---

## How to Test the Changes

- Run server and check that script apply to database

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---
2026-02-28 09:07:15 +01:00
NoxMax
b7b67e0fd9 Feat/Fix: Expand follower bot flightmaster distance search and use correct config patterns (#2140)
# Pull Request

Feat:
A common problem I have with follower bots is that if I quickly run up
to a flightmaster and select a destination, as I go on my way, the bots
can't get on a damn bird and say "Cannot find any flightmaster to talk".
Guy was 8 yards away and they're completely blind to him.

This is because when you select a destination, at that moment the bot
would check `GetNPCIfCanInteractWith` from core, which uses
`INTERACTION_DISTANCE`, which is defined as 5.5 yards. So the bot has to
have caught up with you to be within 5.5 yards of the flightmaster.

This PR expands that distance to use our own
`sPlayerbotAIConfig.farDistance`, which is by default set to 20 yards.
So just as long as bots have caught up to be within 20 yards from the
flightmaster, they will follow you.

Fix:
While I was doing this, I noticed that the timings for bot flight
staggering (introduced in #1281) are defined in TaxiAction and
PlayerbotAIConfig. So I removed their definitions from TaxiAction, made
proper calls to the configs, and renamed them to similar format that
other configs use.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

Changes here use a minimal amount of code to accomplish the objective,
including using pre-defined distance values rather than creating new
ones. Changes have no effect on processing.

---

## How to Test the Changes

For expanding flightmaster search distance: You will be using the `stay`
command. A bot commanded to `stay` will still take a flight with you, if
it is near a flightmaster. So you can use the command to position the
bot exactly where you want it to be
1. Place your follower bot immediately next to the flightmaster
2. Take a flight and the bot should follow. Nothing new here
3. Place the bot about 12 yards away from flightmaster.
4. Take a flight and the bot should follow. Same as before.
5. Repeat again, but this time place the bot 22 yards away. It should
not follow you and instead say "Cannot find any flightmaster to talk"
6. The change should work correctly with `InstantFlightPaths = 0` in
worldserver.conf, or if it's set to 1/2 and bots can instantly fly.

For the config of staggering:
1. Make sure `InstantFlightPaths = 0` in worldserver.conf.
2. Change the timings in playerbots.conf under the `# FLIGHTPATH`
section.
3. Changes should be correctly reflected in world.


## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

Follower bots search a slightly bigger distance for nearby
flightmasters.

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [x] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-27 16:07:42 -08:00
Keleborn
8519b10d39 Fix movenearwateraction isUseful (#2168)
# Pull Request

Minor sign change to make check work properly. 

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-27 16:05:31 -08:00
Keleborn
439293e100 Warnings PR 2 clean unused variables (#2107)
# Pull Request

Removed unused variables and fixed styling issues. 


## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)


---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

This was filtered from the code provided by SmashingQuasar. Eliminated
variables were confirmed to be not used, but unclear at times if that is
due to mistakes in writing.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-27 16:04:33 -08:00
Crow
be2cf436ea Implement Tempest Keep: The Eye Strategies (#1943)
Edit: Descriptions of methods are out of date right now. To be updated.

This one comes with the same caveats as SSC about requiring ownership
from somebody with C++ knowledge, except I think the matter is even more
acute here because these strategies incorporate a novel approach
proposed by Timberpoes. By redeclaring the entire bossai class for
Kael’thas, it was possible to add new member functions to the class in
order to access its private member variables. This allows bots to have
visibility into boss mechanics beyond what they could do with ordinary
techniques and is similar in approach to what was done by the Naxx
strategies, except that this approach does not require any modifications
to the core. I used it for only one mechanic, which was to detect
Kael’thas’s phase. That was very helpful because the fight is divided
into 5 phases, and distinguishing between them with traditional
techniques requires lookups of a dozen NPCs and comparisons of their
various unit states, react states, and auras; by accessing his bossai,
this can all be avoided. However, there is far more potential beyond
this if the approach is an acceptable one.

On with the (shit)show.

### Trash
In a perfect world, there would be many strategies for TK trash, which
is easily more difficult than two of the bosses. It’s a real pain to do
though because to solve the biggest issues properly, each pack would
have to be handled a little differently. So the only thing I’ve included
is for Mages to cast polymorph on the Crimson Hand Centurions when they
are channeling Arcane Flurry. The purpose is not to actually keep them
CC’d but to interrupt their channel.

### Al’ar
This fight sucked so much to write a strategy for. The only silver
lining is that being the post-nerf version, the boss moves between only
4 platform locations (instead of 6), and movement between them is on a
fixed rotation (interrupted by Flame Quills) instead of being random.
Thus, a strategy can be consistently replicated, and the fight can be
done with only 3 tanks (2 on the platforms for the boss and 1 below for
adds).

**Phase 1:**
I’m going to call the platform that Al’ar lands at after the pull
“platform 0” because that reflects the indices in the code. In a
clockwise direction, the remaining platforms will be referred to as
platforms 1, 2, and 3, respectively.
The best way to pull is to first put all ranged, as well as tanks other
than your main tank and first assistant tank, on nc +stay below platform
0. Then, go up the ramp to platform 0 with your main tank, first
assistant tank, and melee dps following you, then hit Al’ar with any
ranged attack or spell to start the fight.

- Your main tank will start at platform 0, and your first assistant tank
will immediately move to platform 1. When Al’ar moves to platform 1,
your main tank will move to platform 2. When Al’ar moves to platform 2,
your first assistant tank will move to platform 3. When Al’ar moves to
platform 3, your main tank will move back to platform 0. This assures a
tank is available to receive Al’ar after every platform movement (every
30 seconds).
- Melee DPS will follow Al’ar as it moves between platforms.
- Each platform is mapped to a corresponding ground location below it.
Ranged DPS and healers will follow Al’ar by moving to the corresponding
ground location as it flies between platforms.
- After each platform move, an Ember of Al’ar will spawn. Your second
assistant tank will pick up the Ember and move it to the point that is
25 yards away from the ground position corresponding to Al’ar’s platform
(on an invisible line between such ground position and the middle of the
room). Ranged DPS will then focus down the Ember before switching back
to Al’ar (this positioning is so that ranged are not hit by the Ember
Blast explosion that happens whenever an Ember dies).
- Each time Al’ar leaves a platform, it has a chance to instead fly up
high in the middle of the room to perform Flame Quills, which will
one-shot anybody on the upper level or ramps. When Al’ar begins the
Flame Quills sequence, all bots on the top level will jump off. FYI,
Al’ar’s usage of Flame Quills is not entirely random: there is a 20%
chance for it to do so after the first platform move, and the chance
increases by another 20% after each subsequent platform move that does
not trigger Flame Quills (reset after each Flame Quills sequence).
- After Flame Quills, Al’ar will randomly land at either platform 0 or
3. To prepare for this, bots will move to assigned positions during the
Flame Quills sequence:
- Ranged and the second assistant tank will wait in the middle of the
room.
- Melee DPS will wait at a point that is between the base of each ramp.
  - The main tank will wait at the base of the ramp to platform 0.
- The first assistant tank will wait at the base of the ramp to platform
3.
- Once Al’ar lands, the regular Phase 1 strategies resume.
- When Al’ar “dies,” it disappears and moves to the center of the room,
where it casts Rebirth and returns to full HP. Bots will wait outside of
the radius of the Rebirth explosion for Phase 2 to start.

Phase 2:

- Your main tank will tank Al’ar initially. When Al’ar casts Melt Armor,
your first assistant tank will taunt Al’ar and take over. The tank swaps
will continue back and forth every time Melt Armor is cast.
- Bots will avoid Flame Patches. FWIW, the standard co +avoid aoe
strategy does work for Flame Patches, but avoid aoe provides no buffer
distance so as you’ve probably noticed, it doesn’t provide for
preemptive avoidance. Also, avoid aoe does not consider multiple hazards
together so it can be an issue when movement needs to take into account
more than one hazard, plus when a strategy requires particular bot
movement, it’s better to account for the hazards within that movement
strategy instead of relying on separate methods that can create
conflicts.
- When Al’ar takes to the sky to perform Dive Bomb, bots will spread out
(and continue to avoid Flame Patches). After the Dive Bomb, Al’ar does
another Rebirth explosion. I have tried a million different things to
properly detect this full sequence (even accessing the bossAI like I did
with Kael’thas) and cannot get it to work properly. Ultimately, all I’ve
been able to get to work at all with respect to the final explosion is
for bots to detect the 2-second cast of the Rebirth and run out. It is
not enough time for bots that are too close when the cast happens so
some bots may get hit, but if you have adequate gear, they should
survive.
- After each Dive Bomb, 2 Embers will spawn. Your second assistant tank
will tank one Ember, and either the main tank or first assistant tank,
whichever one is not tanking Al’ar at the time, will tank the other
Ember. They will both move the Embers away from bots, and ranged DPS
will focus both Embers down before switching back to Al’ar.
- Because the room is so large, it is possible for bots to get too far
away from active combat (particularly if they are thrown across the room
by Ember Blast) so there is also a method for them to run back toward
the center if they get too far away.

### Void Reaver
Ironically, what was often considered the easiest boss in 25-player
content in TBC is the only boss with an ability (Arcane Orb) that I do
not believe can be avoided by bots, even with access to Void Reaver’s
boss script. Therefore, every single Arcane Orb is going to hit its
target, so the strategy can only try to limit the damage by spreading
ranged bots in two rings around Void Reaver (one for healers and one for
ranged DPS, to try to ensure sufficient distribution of healers). The
tanks will all fight for aggro (necessary due to Knock Away) and try to
keep Void Reaver in the middle of the room. Bots that can wipe aggro or
otherwise gain invulnerability are directed to use the applicable
abilities as soon as they pick up aggro (e.g., Soulshatter). He’s still
easy, but if you have IP nerfs, it’s a little bit of a gear check.

### High Astromancer Solarian
No boss was hit harder by nerfs in TBC than Solarian, whose encounter
went from a totally unique fight that required arcane resistance to a
fight that is kind of just an easier Baron Geddon. IMO, she is the
easiest boss in TBC 25-player raids.

- Ranged bots stack up at a distance from Solarian; this leaves all bots
with plenty of space to run away from other bots when they get Wrath of
the Astromancer.
- When Solarian vanishes, all bots will stack to AoE down the Solarium
Agents that spawn.
- When Solarian returns with two Solarium Priests, melee will divide
into two groups, with one focused on each Solarium Priest. I think this
method is not working correctly right now because when one Priest dies,
the bots still on the second Priest are leaving it. I’ll need to decide
whether I want to figure it out or just get rid of it because this fight
is so easy regardless.
- Priest bots will cast Fear Ward on the main tank to block the Psychic
Scream during the final tank-and-spank Voidwalker phase, and the main
tank will pick up Voidwalker Solarian as soon as she transforms.

Note that the bots will not be knocked into the air by Wrath of the
Astromancer. The issue is due to the presence of a check for knockbacks
in Playerbots that causes bots to ignore knockbacks that would launch
them at a velocity beyond a hardcoded value. I’ve increased that
velocity limit on my own fork, and it does allow Wrath of the
Astromancer (and other knockbacks that otherwise don’t work) to work on
bots. But that’s obviously a broader issue and not addressed in this PR,
and bots don’t take fall damage in any case.

### Kael’thas Sunstrider

So this strategy has 23(!) action methods. But like in retail, this is
actually an easy fight once it is learned because it is highly scripted.

Unlike in other strategies I’ve done, the bots probably cannot do this
fight by themselves unless they are way overgeared. This is because
there are a few windows during which bots need to position themselves
properly based on dynamic factors. But no RTSC is needed—you just need
to have bots follow you to the right locations. Also note that the gear
check for this strategy is higher than in retail because you have to get
all of the legendary weapons down and looted before the advisors aggro
in Phase 3, or it’s going to be an absolute shitshow (with human
players, you can deal with there still being a couple of weapons up).
For a point of reference, when I was first working on this strategy with
damage reduced to 50% and bots pretty close to T4 BiS, I had almost no
margin of error (I would usually get the weapons down with barely a
second to spare).

You will need at least 2 tanks, but 3 is better. Your main tank will
need to be able to equip the legendary shield so you must use a Warrior
or Paladin. However, it is ideal for the first assistant tank to be a
Druid because they can equip the legendary staff.

**Phase 1:**
Fun fact—when you “kill” the advisors in this phase, they don’t actually
die but get an aura applied called “Permanent Feign Death” (nice
oxymoron).

- _Thaladred_: You’re supposed to kite him, and bots can’t really kite,
so the method is a poor man’s method of having the bot move away from
him in a straight line when fixated. You want him to die in the far
Southern part of the room. If he dies in a bad location, you may as well
call a wipe and restart. What will work best for you will depend on your
DPS since you don’t want to kill him before he gets to the location you
want but also don’t want bots to be trapped up against a wall since they
can’t properly kite him. The way that works best for me is to have bots
stay back while I aggro the boss, and wait until right before Thaladred
switches to his second fixate target before attacking. Note that if you
do put bots on stay, when you put them back on follow, the bot that is
then being fixated will remain on stay (because they need to disregard
movement orders other than running away from Thaladred). So after
Thaladred dies, make sure to manually type /follow or the bot that was
fixated when you took the bots off of stay will not rejoin the fight.
- _Sanguinar_: He will be tanked by your main tank, who will be targeted
by your Priests for Fear Ward. Bots will wait to engage him; I made it a
very generous time (12 seconds) because there is absolutely no rush in
Phase 1. There’s no sense in being aggressive. During that time, the
main tank will drag Sanguinar to the West wall.
- _Capernian_: This is the first make-or-break part of the fight. Phase
1 Capernian was the most frequent cause for wipes for me.
- She should be tanked by a Warlock. If you want to pick your Warlock
tank, you can do so by the assistant flag, but if you don’t, the
strategy will just pick your highest HP Warlock. If you raid without a
Warlock, then you’re insane, but at least there’s a guard so your server
won’t crash?
- You do not need to add the tank strategy to your Warlock. There is a
method that will automatically switch your selected tank Warlock between
DPS and tank strategies at appropriate times because you need to squeeze
out every drop of DPS you can get, particularly for Phase 2, where
you’ll need your Warlock to be blowing up weapons with Seed of
Corruption instead of spamming Searing Pain. You’ll want your Warlock to
start with a DPS strategy as usual (since they should be DPSing
Thaladred).
- To engage Capernian, start running East right before Sanguinar dies.
She will activate quickly, and you want to try to get in front of her
(but not too close) before she aggros.
- When Capernian aggros, your Warlock tank will immediately switch to
the tank strategy and attack. Your main tank will run toward Capernian
but not actually attack; their purpose will be to bait her Conflagration
to reduce the chance that it hits your Warlock tank. Other melee will
not engage Capernian. Ranged DPS will be idle for 12 seconds; during
this time, you should run South to make sure they are not in range of
Capernian. After 12 seconds, your ranged DPS will activate, move into
range and spread out, and attack (it doesn’t seem possible to outrange
Conflagrate, so if bots don’t spread, she will annihilate the entire
ranged group with a single cast). Ideally, you kill her not too far from
her starting position. If she ends up in the middle of the room, you
should probably wipe and start over.
- _Telonicus_: He is very easy in retail but actually is a big risk for
wipes with respect to bots because his bombs will one-shot any non-tank,
and bots will stupidly stand in front of him without a proper strategy.
You should keep some distance from him before he aggros. Your first
assistant tank will pick him up and move him to the West wall near
Sanguinar. Again, there is a 12-second delay before DPS starts. Your
melee DPS are coded to stay directly behind him and not get too close so
they don’t get hit by bombs.

**Phase 2:**
Kael’thas will summon all weapons immediately after Telonicus is down.
Just before Telonicus is down, you should move to the platform where the
advisors originally were—you’ll be in better position for the raid to
AoE down the weapons.

- Your main tank will pick up the axe and move it away from the group.
The axe is the biggest threat during this phase and can easily one-shot
casters if not pulled away.
- One of your Hunters will attempt to get aggro on the bow and move away
from the group (as a hacky way of trying to turn the bow away from the
group because you can’t really get a bot to do that directly). This
method is hit or miss, but it shouldn’t be that big of a deal if your
Hunter doesn’t pull it off properly.
- Everybody else will prioritize weapons in the following order (but
most damage will come from AoE, which is what you want or you will not
beat the timer): staff, mace, sword, dagger, axe (ranged only), bow, and
shield.
- As weapons are defeated, bots will loot and equip them. If you have
not disabled bot announcements in your config, you get to see your
entire raid go nuts because they looted legendary items.
- Here is what weapons bots will loot and equip. I don't know anything
about DKs, having never played WotLK, so tell me if anything is wrong
for them.
- _Healers:_ Mace (if a healer normally uses a staff, it's best if they
keep an OH in their bags for this fight)
- _Tanks:_ Shield and sword for Paladins and DK, shield and dagger for
warriors, staff for Druid
  - _Offensive_ casters: Staff
- _Rogues:_ Sword and dagger if Combat or Subtlety, dagger only if
Assassination
  - _DPS Death Knights, Retribution Paladins, Arms Warriors_: Axe
- _Fury Warriors_: Dagger. I understand that due to Titan Grip, they
should also have the Axe for best DPS; however, Fury Warriors have awful
DPS (we’re talking barely above Prot-level) at this stage. Thus, my view
is it is better to give them only the dagger so they will MH it and help
break MC in Phase 4, since they will contribute hardly any DPS
regardless.
  - _Cat Druids_: Staff
  - _Enhancement Shamans_: Dagger
- _Hunters:_ Bow and dagger. Note that I do NOT have them loot the sword
because they need the dagger in their mainhand to use to break MC in
Phase 4; whatever marginal benefit they get from the sword as a stat
stick is not worth losing this capability. If your Hunter uses a 2H, it
is best to have them carry a 1H in their inventory so they can put
something in the OH after they equip the dagger.
- After looting weapons, bots with the staff will use it (once) to
activate the Mental Protection Field. Hunters will use the bow to
generate the legendary arrows and equip those (and will continue to do
so during the fight if they use up the arrows).
- If you wipe from this point forward, everybody will lose their
legendary weapons, and by default, most bots will not automatically
reequip their own weapons until a loot event occurs. This was extremely
annoying, and therefore there is a noncombat method implemented that
causes everybody to equip upgrades when they get within 150 yards of
Kael’thas. I considered applying this to the whole instance, but I’m not
sure if some people would not like that so I decided to limit things to
the Kael’thas encounter.

**Phase 3:**
I highly recommend you have your Shamans drop Tremor Totems (co +tremor)
during this phase. Doing so is not coded because I wanted to leave
flexibility, but I think it is very helpful for Sanguinar. After the
weapons die, you want to move your bots to a central location between
the advisors. If Thaladred died closer to the middle of the room,
ideally you position to the side of Thaladred so when he fixates he will
not chase bots North into the other advisors.

- Shamans will immediately use Heroism/Bloodlust.
- Your melee tanks will bring Sanguinar and Telonicus to their tanking
positions (same as Phase 1). If your first assistant tank is a Druid,
they will be immune to Telonicus’s Remote Toy due to having the
legendary staff’s aura activated and will also make your main tank
immune.
- One healer will stay by the Sanguinar and Telonicus tanking positions
to heal the tanks. Once IsHealAssistantOfIndex() is fixed, you will be
able to select this healer with the assistant flag. Right now, this will
just be the last healer that joined your raid (per standard AC logic).
- DPS priority will be Thaladred, Capernian (ranged only), Sanguinar,
Telonicus. As with retail, the most chaotic period will be before
Thaladred is killed, particularly if he chases bots into other advisors.
I don’t have a great solution for this, but Capernian is significantly
less dangerous during this phase thanks to the legendary staff. This is
the last true breakpoint—if you get Thaladred down with your raid mostly
intact, you are very likely to get the kill.

**Phase 4:**
Kael’thas will aggro immediately after all advisors are dead.

- Your main tank will position Kael’thas at his original position.
- Bots will move out of Flame Strikes.
- Assist tanks will pick up Phoenixes. Since they die over time anyway,
bots will not waste time attacking them. When Phoenixes die, they turn
into an Egg—at that point, bots will switch to the Egg to destroy it
before the Phoenix is reborn.
- When Kael’thas puts up Shock Barrier and starts casting Pyroblast on
your main tank (a one-shot), all bots will focus DPS on him (even if
there is an egg up). You have 4 seconds to break the barrier (80K HP)
and interrupt his Pyroblast. It is likely that you will not be able to
if you are playing with IP nerfs and are in T4 gear. However, the main
tank will use the legendary shield’s ability, which will allow them to
absorb one cast, giving you 8 seconds to break the barrier and interrupt
Pyroblast. Bots will put top priority on interrupting Pyroblast as soon
as the barrier is down.
- If a bot (or player) is mind controlled, bots with the legendary
dagger (other than tanks) will move to MC’d players and use the
following attacks to break MC: Shiv (Rogues), Hamstring (Warriors), Wing
Clip (Hunters), and Stormstrike (Shamans).

**Phase 5:**
At 50% HP, Kael’thas enters a long RP sequence. This is a good time to
kill any remaining Phoenixes and/or Eggs.
- Kael’thas stops casting Pyroblast and Mind Control.
- His main new ability is Gravity Lapse, and it doesn’t work properly on
bots... He sucks in the entire raid then knocks everybody back in a
different direction. What is supposed to happen is that players will end
up floating in midair in different directions and at different heights.
However, bots will immediately fall to the ground after getting knocked
back. They will not actually hit the ground though and instead remain in
a flying state right above the floor.
- If you could move in 3D space, Netherbeam would be very easy to deal
with. However, because that is not available to bots, they can spread
only in 2D space and thus need to move farther to get properly spread,
and they waste the first moments falling straight down. As a result, the
damage from Netherbeam can be quite high, and the beginning of Gravity
Lapse requires a lot of healing. I don’t really have a better way of
dealing with this.
- FWIW, I don’t think there is any existing method to make bots disperse
in 3D anyway.
- Kael’thas is supposed to use Nether Void when players are in midair,
which creates clouds that reduce your max HP and thus make it more
challenging to maneuver, but AC is bugged and he doesn’t use the ability
at all (there’s been an open issue about this forever).

For fuck's sake, that's all.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
2026-02-27 16:04:10 -08:00
kadeshar
d8c668cf96 Merge pull request #2172 from mod-playerbots/test-staging
Update master from Test staging and Core Update
2026-02-27 22:55:19 +01:00
kadeshar
1401657a6d Modify action to workaround github settings (#2167)
Github action modification
2026-02-24 22:10:09 +01:00
killerzwelch
e7d5eaabac Make playerbots compatible with latest refactoring done on azerothcore (#2158)
# Pull Request

When integrating latest changes from
https://github.com/azerothcore/azerothcore-wotlk into
https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot you
will face some compiling issues due to refactoring. That PR does not
change any of the logic, but implements needed changes to be compatible
again

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [ X] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [ X] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [ X] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [ X] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [ X] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [ X] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [ X] Stability is not compromised
- - [ X] Performance impact is understood, tested, and acceptable
- - [ X] Added logic complexity is justified and explained
- - [ X] Documentation updated if needed

---

## Notes for Reviewers

Please doublecheck if none of the timing-logic (migration from uint32 to
microseconds) has been changed

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
2026-02-24 00:49:45 +01:00
Alex Dcnh
1f3d11d1c4 Stage2 refactor switch custom calculations by core helpers clean (#2127)
# Pull Request

This change replaces a few manual distance calculations in
`WorldPosition` with AzerothCore distance helpers. The goal is to reduce
duplicated math, keep behavior consistent with core utilities, and avoid
reimplementing logic that already exists in the core.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
Use existing core distance helpers instead of manual math, keeping the
logic localized to `WorldPosition`.
- Describe the **cheapest implementation** that produces an acceptable
result?
Directly call `GetExactDist`, `GetExactDist2d`, and `GetExactDist2dSq`
where appropriate.
- Describe the **runtime cost** when this logic executes across many
bots?
No additional cost; the helper calls replace equivalent math and avoid
extra intermediate objects.

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Build the module and run existing bot scenarios that rely on
`WorldPosition` distance checks.
  - Verify no behavioral regressions in travel-related logic.
- Any required setup (e.g. multiple players, bots, specific
configuration)
  - Standard server + mod-playerbots setup.
- Expected behavior and how to verify it
- Distances computed in travel logic remain identical; no gameplay
change expected.

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [x] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [ ] Documentation updated if needed

---

## Notes for Reviewers

This is a localized refactor that replaces manual distance math with
core helpers for consistency and maintainability.
No behavioral change is expected.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-02-23 13:19:56 -08:00
Rikus Louw
ea60b38eb9 Add Serpentshrine Cavern attunement quest to bot factory (#2136)
# Pull Request

I've being getting ready to test Serpentshrine Cavern strategy on
`test-staging`, but noticed the bots don't currently have attunement
setup.

Added attunement quest.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Add bots and convert to raid
- Make sure you have attunement by completing
[this](https://www.wowhead.com/tbc/quest=13431/the-cudgel-of-kardesh)
quest
- Teleport to SSC and summon bots. The bots should appear in the raid.

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [ ] No
- - [x] Yes (**explain why**)

This adds the attunement quest for SSC by default

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-23 11:02:11 -08:00
dillyns
629aa19dbd Add aggressive non combat targeting strategy (#2117)
# Pull Request

Tired of failing that escort quest because your bots stood and watched
while the escort npc got swarmed and killed?
Tired of your bots standing around doing nothing while the npc you are
supposed to be guarding for 5 minutes is getting attacked?
Don't want to use the grind strategy because it is too heavy-handed and
has too many restrictions?

Look no further! Just do "nc +aggressive" and your bots will pick a
fight with anything they can in a 30 yard radius.

The aggressive targetting is a stripped down version of the grind
target.

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
Add a strategy, action, and targetting that will cause bots to attack
nearby enemies when out of combat.

- Describe the **cheapest implementation** that produces an acceptable
result?
Hopefully this is the cheapest.

- Describe the **runtime cost** when this logic executes across many
bots?
Minimal runtime cost as this strategy needs to be added specifically to
bots.

---

## How to Test the Changes

- Add a bot to party, or use selfbot
- Give them the aggressive strategy via "nc +aggressive"
- They should attack anything within 30 yards.
- If it is a bot with a master, the 30 yards should be centered around
the master not the bot (prevent chaining from enemy to enemy)

## Complexity & Impact

Does this change add new decision branches?
```
[] No
[x] Yes (**explain below**)
Only for bots that have the added strategy, adds decision to attack nearby targets when out of combat.
```

Does this change increase per-bot or per-tick processing?
```
[] No
[x] Yes (**describe and justify impact**)
Minimal increase to only bots that have this strategy added.
```

Could this logic scale poorly under load?
```
[x] No
[ ] Yes (**explain why**)
```
---

## Defaults & Configuration

Does this change modify default bot behavior?
```
[x] No
[ ] Yes (**explain why**)
```

If this introduces more advanced or AI-heavy logic:
```
[x] Lightweight mode remains the default
[ ] More complex behavior is optional and thereby configurable
```
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
```
[ ] No
[x] Yes (**explain below**)
```
Claude is used to explore the codebase to find similar implementations
to be used for examples.

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-23 11:00:55 -08:00
privatecore
2f7dfdbbfc Fix rest of trainers' related stuff + codestyle changes and corrections (#2104)
# Pull Request

* Fix the rest of the trainer-related functionality: list spells and
learn (cast vs. direct learn) spells.
* Rewrite `TrainerAction`: split the logic between appropriate methods
(`GetTarget`, `isUseful`, `isPossible`) instead of pushing everything
inside a single `Execute` method.
* Change method definitions to remove unnecessary declarations and
parameters overhead.
* Move the `Trainer` header into the implementation. Rewrite
`RpgTrainTrigger` to fit the original logic and move all validation to
`RpgTrainAction` (`isUseful` + `isPossible`).
* Implement "can train" context value calculation to use with
`RpgTrainTrigger`.
* Update and optimize "train cost" context value calculation -- it
should be much faster.
* Replace `AiPlayerbot.AutoTrainSpells` with
`AiPlayerbot.AllowLearnTrainerSpells` and remove the "free" value
behavior — please use `AiPlayerbot.BotCheats` if you want bots to learn
trainer's spells for "free".
* Add `nullptr` checks wherever necessary (only inside targeted
methods/functions).
* Make some codestyle changes and corrections based on the AC codestyle
guide.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## How to Test the Changes

Force bots to learn spells from trainers using the chat command `trainer
learn` or `trainer learn <spellId>`. Bots should properly list available
spells (`trainer` command) or learn them (based on configuration and
command).

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [x] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-23 11:00:24 -08:00
Revision
2f0a557655 Merge pull request #2150 from mod-playerbots/test-staging
Master update from Test staging
2026-02-23 00:05:48 +01:00
Keleborn
d1cac8d027 Bug fix. Equip Action triggered action (#2142)
# Pull Request

Brighton caught a mistake I made changeing the action registry, so the
correct action was no longer triggering. I cleaned that up, and renamed
the action.


## How to Test the Changes

- This was tested by adding logging to both equip actions. But to test
this without that, the best way to verify the fix is to stop alts from
auto upgrading via config. Then they should correctly follow the
configured behavior.

## Complexity & Impact

Does this change add new decision branches?
- - [x ] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-15 16:29:20 -08:00
bash
3c62a45fad Revert "Convert PlayerBots tables to InnoDB (#2083)"
This reverts commit c86032f43b.
2026-02-15 20:47:26 +01:00
bash
295f2d2784 Revert "Convert PlayerBots tables to InnoDB (#2083)"
This reverts commit c86032f43b.
2026-02-15 20:46:42 +01:00
Keleborn
441f9f7552 Warnings PR 1: Event warnings and headers (#2106)
# Pull Request

This is the first in a series of PRs intended to eliminate warnings in
the module. The design intent is to eliminate the calling event when not
needed in the body of the function. Based off of SmashingQuasars work.

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-14 20:55:10 +01:00
bashermens
80aeeda0e8 Flying, waterwalking, swimming movement optimizations and transition fixes. (#2134)
# Pull Request

**Fixes and optimizations for flying, water walking, swimming**:
* optimized triggers
* ensuring movement flag updates only happen between actual transitions
states
* fly bug fix; fly with bots following with stay command midair, fly
down and dismount yourself, follow command and now the bots fall instead
of lingering around in the air)
* updated z-axes correction for water walking and bots (for real players
this is handled client-side)
* added lift off movement for more stabile transition from ground(level)
to flying

**Tested**:
* Test all transitions; water walk, swimming, swimming, walking,
mounting while water walking etc.
* Flying with bots and fly master routes
* Movement flag updates only occur during transitions

**Known issues**: transition between water walking, swimming and back
again, in most cases the bots will stay under the waterline instead of
jumping on the z axes on water level. (will fix that another time)

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

Apply water walking effect on your bots, shaman or dk, and test all
possible transitions and follow actions
of the bots. water walking, swim, walk on land, swimming and walk
without water walking effect/aura, fly mount from water, from ground,
etc.

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)


Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [x] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-14 11:31:45 -08:00
NoxMax
25800f54e8 Fix/Feat: PVP with master and PVP probablity system (thread-safe remake) (#2008)
This is a remake of #1914 that had to be reverted. Original PR had a
thread-safe issue where a crash happens if multiple threads access the
cache at the same time. Unfortunately this problem was not caught in
earlier testing. I don't know if because I was testing on a month old
branch, if my settings had only ~2000, or if I needed test runs longer
than an hour to find out.

Regardless, this has all been addressed. Test have been run on the
latest commits from today (2026/1/11), with all 7500 of my bots active,
with a test run that lasted 15 hours. All stable and bots are following
the probability system without issue.

~~The new edit uses mutex locking, preventing simultaneous access of the
cache by multiple threads.~~
The new edit uses deterministic hashing, thereby not having issues with
cache thread safety to begin with. Thank you @hermensbas for catching
and reverting the original problem PR. Apologies for not catching the
issue myself.

---
Original PR description:

There are two related PVP components in this PR. First is the simple yet
fundamental change to bot behaviour when they are in party. Right now
bots with a master will go into PVP when there's a nearby PVP target,
even if master is not in PVP. This absolutely should not happen. Bots
should not consider PVP at all if master is not in PVP. The fix is only
3 lines in EnemyPlayerValue

The second component is introducing PVP probabilities, to make decisions
more realistic. Right now even a level 1 bot will 100% go into PVP if it
sees a level 80 PVP target. They can't help themselves. So the change
here addresses that insanity. Several thresholds (subject to community
review) are introduced:

1. Bots will not fight a target 5 or more levels higher than them
2. Bots have a 25% chance starting a fight with a target +/- 4 levels
from them.
3. Bots have a 50% chance starting a fight with a target +/- 3 levels
from them.
4. Bots have a 75% chance starting a fight with a target +/- 2 levels
from them.
5. Bots have a 100% chance starting a fight with a target +/- 1 level
from them.
6. Bots have a 25% chance starting a fight with a target 5 or more
levels below them (ganking. thought it would be funny, and technically
realistic of player behaviour)

Exception of course exist for BG/Arena/Duel, and in capitals where bots
will always PVP. Also bots will always defend themselves if attacked.

Few notes: 
1. The if/ else if logic can be further simplified, but only if we use
thresholds that are different by one. So current logic allows for
flexibility of using values like 10/7/5/3 instead of 5/4/3/2.
2. The caching system is per-bot basis. So for some target X, if some
bot decides to attack it, another bot will make its own decision. At
first I used a simplified global system (thinking there might be
performance concerns) where if one bot decides to attack a target then
they all do, but when I switched to the more realistic per-bot basis, I
didn't see an effect on performance.
3. Variables are obviously not configurable right now. I'm starting to
see Bash's POV that maybe we have too many configs 😬 Still,
they can be easily exposed in the future, and if someone is reading this
then, remember to change constexpr to const.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-13 09:31:55 -08:00
Crow
9748e36ad6 Fix potential failure of Magtheridon cube clickers to engage in combat (#2129)
# Pull Request

I noticed a problem that has always existed with the Magtheridon
strategy but just never came up for me due to chance. Cube clicker logic
is based on a timer that resets after every Blast Nova. If the timer is
not reset, the cubes will still be clicked, but the clickers will do
nothing but wait to click on the cubes instead of resuming combat
between Blast Novas. Because tracking of the Blast Nova state happens
during the cube clicking sequence, if a cube clicker is assigned the
singular role to track Blast Nova state (which is done simply by
returning the first DPS bot found), then the timer will not be reset.

This whole strategy needs a refactor, but the simple fix for this
problem for now is just to remove the role check for tracking the Blast
Nova state. I tested the fix, and it works.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [X] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [X] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [X] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [X] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [X] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [ ] Stability is not compromised
- - [ ] Performance impact is understood, tested, and acceptable
- - [ ] Added logic complexity is justified and explained
- - [ ] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-13 09:25:12 -08:00
Alex Dcnh
17b8d7f68b Stage1 refactor world position method names (#2126)
# Pull Request

This change replaces the non‑standard
WorldPosition::getX/getY/getZ/getO/getMapId wrappers with the core
getters (GetPositionX/Y/Z, GetOrientation, GetMapId) and removes the
redundant wrappers.
Goal: align the module with AzerothCore conventions, reduce local
adapters, and improve long‑term maintainability.

---

## Design Philosophy

This is a structural cleanup only (coordinate access) and does not alter
any AI behavior or decision logic.
It follows the stability/performance-first philosophy and does not add
branches or extra runtime work.

Before submitting: yes, this change aligns with the principles of
stability, performance, and predictability.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Minimum logic required: use core getters (GetPositionX/Y/Z, GetMapId,
GetOrientation) wherever coordinates are needed.
- Cheapest implementation: direct call replacement and removal of
redundant wrappers.
- Runtime cost: negligible (same data access, no additional logic).

---

## How to Test the Changes

- No functional testing required (behavior‑neutral refactor).
- Recommended: compile the module and run a normal server startup as
validation.

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [x] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [x] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [ ] No
- - [x] Yes (**explain below**)

If yes, please specify:

- AI tool or model used: Copilot
- Purpose of usage: Translate this PR text from french to English

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

This is a core-friendly cleanup only, with no behavioral change.
No additional logic or CPU cost is introduced.
2026-02-13 09:24:42 -08:00
privatecore
a0a50204ec Fix action validation checks: isUseful -> isPossible + codestyle fixes and corrections (#2125)
# Pull Request

Fix the incorrect logic flaw when processing actions from different
sources. It should be: `isUseful` -> `isPossible`. The original logic is
based on the Mangosbot code and the impl presented inside
`Engine::DoNextAction`. This should fix all wrong validation orders for
triggers and direct/specific actions.

Code style is based on the AzerothCore style guide + clang-format.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-13 09:24:11 -08:00
Keleborn
80b3823f12 Warnings PR 3, remove std::move when not necessary. (#2108)
# Pull Request

std::move was being used in a few places to return a vector. Its not
necessary. A direct return allows for some optimizations that moving
wouldnt.

## How to Test the Changes

-Bots should initialize correctly 

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [x] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

---

## Final Checklist

- [ ] Stability is not compromised
- [ ] Performance impact is understood, tested, and acceptable
- [ ] Added logic complexity is justified and explained
- [ ] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-13 09:22:27 -08:00
Keleborn
ee2a399ac8 Refactor newrpginfo data union to std::variant (#2079)
# Pull Request

As I began modifying the newrpginfo to change the types of data it
stored, or add new data I found myself with the issue of ending up
either with garbage memory if the information wasnt properly stored on
status change, or needing complicated destructor patterns for non
trivial data sets.

---

## Design Philosophy

Make rpginfo able to handle more complicated information in a strongly 

---

## Feature Evaluation

No Feature changes

---

## How to Test the Changes

-  Server should be stable for an extended period of time. 
- Bots should be able to complete quests, fly, etc as they did before.

## Complexity & Impact

- Does this change add new decision branches?
    - [X ] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [ ] No
    - [ X] Yes (**describe and justify impact**)
Potentially as there can be more memory involved in the object.

- Could this logic scale poorly under load?
    - [X ] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [ X] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [ ] No
    - [ X] Yes (**explain below**)

If yes, please specify:

- Gemini suggested the use of std::variant as an alternative data
structure. I found additinal external references that correlated with
the same suggestion of moving away from a union.
- Implementation was performed manually with Co-pilot auto-complete

---

## Final Checklist 
In progress.
- [ ] Stability is not compromised
- [ ] Performance impact is understood, tested, and acceptable
- [ ] Added logic complexity is justified and explained
- [ ] Documentation updated if needed

---

## Notes for Reviewers

Im not 100% sure if this is a good design choice. There are some things
I didnt quite like by the end of this, specifically having to double
check whenever accessing data whether exists or not even though an
action has already been triggered. But I have a PR in the works where I
want to store a full flight path vector, and the union was giving me
issues. (It appears that state changes may be occuring in the same tick
between RPG status update and the stated action, leading to incorrect
data gathering.

I ended up solving it by first checking a pointer to the object, and
then getting the reference.
```c++
    auto* dataPtr = std::get_if<NewRpgInfo::DoQuest>(&info.data);
    if (!dataPtr)
        return false;
    auto& data = *dataPtr;
```

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-13 09:19:54 -08:00
Keleborn
a6a6af1b4d Merge pull request #2128 from mod-playerbots/test-staging
Master update from Test-staging: Fix ObjectAccessor retrieval, optimize EquipActions, and implement RaidBossHelpers
2026-02-13 09:16:49 -08:00
privatecore
610fdc16d7 Fix bug with GetCreature + GetGameObject = use ObjectAccessor's methods instead (#2105)
# Pull Request

https://en.cppreference.com/w/cpp/algorithm/equal_range.html
> second is an iterator to the first element of the range [first, last)
ordered after value (or last if no such element is found).

The original code uses `return bounds.second->second`, which causes the
wrong creature/gameobject to be returned. Instead, both methods
(`GetCreature` and `GetGameObject`) now utilize ObjectAccessor's methods
to retrieve the correct entities. These built-in methods offer a safer
way to access objects. Additionally, `GetUnit` no longer includes
redundant creature processing before checks and now has the same logic
as the `ObjectAccessor::GetUnit` method.

Furthermore, `GuidPosition::isDead` method has been renamed to
`GuidPosition::IsCreatureOrGOAccessible` and updated, as it is used only
for creatures (NOT units) and gameobjects.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## How to Test the Changes

The behavior has not changed after all.

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-08 09:36:56 -08:00
dillyns
c9c936d5c1 Add Unending Breath to Warlock NonCombat Strat (#2074)
# Pull Request

Adds actions and triggers for Warlock class to cast Unending Breath when
swimming, following the existing implementation for Shaman Water
Breathing.

---

## Feature Evaluation

Add triggers for Warlock noncombat strategy for Unending Breath on self
and party.
Triggers should only be active while swimming.
Minimal runtime cost on Warlock bots trigger processing.

---

## How to Test the Changes

- Bring a Warlock bot into water
- It should cast Unending Breath on itself and anyone in the party

## Complexity & Impact

- Does this change add new decision branches?
    - [ ] No
    - [x] Yes (**explain below**)
It adds triggers to Warlock to decide when to cast Unending Breath on
self or party members.

- Does this change increase per-bot or per-tick processing?
    - [ ] No
    - [x] Yes (**describe and justify impact**)
Minimal additional processing for Warlock triggers, same as already
existing triggers for Shaman.

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [ ] No
    - [x] Yes (**explain below**)

Claude was used to explore the codebase to find similar implementations
that already existed.

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-08 09:36:27 -08:00
kadeshar
cfb2ed4bf3 Merge pull request #2124 from kadeshar/oculus-drake-fix
Oculus drake mounting fix
2026-02-08 17:10:18 +01:00
Hokken
e9e79ad696 Fix LootRollLevel=1 to match documented 'greed' behavior (#2068)
## Summary

Fixes `AiPlayerbot.LootRollLevel = 1` to actually behave as "greed" mode
per the config documentation.

## Problem

The config documentation states:
```conf
# Bots' loot roll level (0 = pass, 1 = greed, 2 = need)
# Default: 1 (greed)
AiPlayerbot.LootRollLevel = 1
```

However, level 1 was converting **all GREED votes to PASS**, causing
bots to pass on almost everything:

| Item Type | AI Decision | Level 1 Behavior (Before) | Expected |
|-----------|-------------|---------------------------|----------|
| Gear upgrade | NEED | GREED ✓ | GREED |
| Usable gear (not upgrade) | GREED | **PASS** ✗ | GREED |
| Crafting materials | GREED | **PASS** ✗ | GREED |
| Recipes, consumables | GREED | **PASS** ✗ | GREED |

The only items bots would greed on were direct gear upgrades (originally
NEED, downgraded to GREED).

## Root Cause

In `LootRollAction.cpp`, lines 104-107 were converting GREED to PASS:

```cpp
else if (vote == GREED)
{
    vote = PASS;  // This breaks "greed" mode
}
```

## Fix

Remove the GREED→PASS conversion. Level 1 now only downgrades NEED to
GREED (as intended), preserving GREED votes for useful items.

## Behavior After Fix

| Level | Description | Behavior |
|-------|-------------|----------|
| 0 | Pass | Always pass on all items |
| 1 | Greed | Greed on useful items, never need |
| 2 | Need | Full AI logic (need/greed/pass) |

## Test Plan

- [ ] Set `AiPlayerbot.LootRollLevel = 1`
- [ ] Kill mobs that drop crafting materials, recipes, or non-upgrade
gear
- [ ] Verify bots greed on useful items instead of passing
- [ ] Verify bots still pass on junk items
- [ ] Verify bots never roll need (only greed)

Co-authored-by: Hokken <Hokken@users.noreply.github.com>
2026-02-08 12:45:03 +01:00
Keleborn
3db2a5a193 Refactor of EquipActions (#1994)
#PR Description 

The root cause of issue #1987 was the AI Value item usage becoming a
very expensive call when bots gained professions accidentally.

My original approach was to eliminate it entirely, but after inputs and
testing I decided to introduce a more focused Ai value "Item upgrade"
that only checks equipment and ammo inheriting directly from item usage,
so the logic is unified between them.

Upgrades are now only assessed when receiving an item that can be
equipped.

Additionally, I noticed that winning loot rolls did not trigger the
upgrade action, so I added a new package handler for that.


Performance needs to be re-evaluated, but I expect a reduction in calls
and in the cost of each call.

I tested with bots and selfbot in deadmines and ahadowfang keep.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-08 12:41:33 +01:00
Crow
8585f10f48 Implement Serpentshrine Cavern Strategies (#1888)
Edited: Below description of methods were brought up to date as of the
PR coming off of draft.

### General
I've starting leveraging, to the extent possible, an out-of-combat
method to erase map keys. This is mostly useful for timers that need to
start upon the pull because I dislike having to rely on a check for a
boss to be at 100% HP (or 99.9% or whatever) because it can be
unreliable sometimes.

### Trash
Underbog Colossi: Some Colossi leave behind a lake of toxin when they
die that quickly kills any player that is standing in it. The pool is a
dynamic-object-generated AoE, and bots will not avoid it on their own (I
think because the AoE is out of combat, plus the radius is much larger
than the default avoidance radius in the config). The method does not
require bots to be in combat, and simply gets bots to run out of the
toxin. You will probably still get a couple of idiots who drink in the
middle of it, but in my experience, the vast majority of the raid gets
out, and healers that escape can easily keep up a couple of fools until
they've drank to full.

Greyheart Tidecallers: Bots will mark and destroy Water Elemental Totems
immediately.

### Hydross the Unstable
The strategy uses 2 tanks, with the main tank assigned to the frost
phase and the 1st assistant tank assigned to the nature phase.

- The main tank will tank the frost phase, and the first assistant tank
will tank the nature phase. They each have designated spots and will
wait at their spots twiddling their thumbs while Hydross is in the other
phase.
- Hunters will misdirect to the applicable tank upon the pull and after
each phase change.
- The phase change process begins 1 second after Hydross reaches 100%
Marks. The current tank will begin moving to the next phase tank's spot
for the next tank to take over as soon as Hydross transitions.
- DPS is ordered to stop after Hydross reaches 100% Marks until 5
seconds after he transitions.
- Bots will prioritize the elementals adds after every phase change,
unless Hydross is under 10% HP, in which case they should ignore the
adds and burn the boss.
- Ranged bots should spread during the frost phase to mitigate the
impact of Water Tombs.

### The Lurker Below

- There is a designated spot for the main tank.
- Ranged DPS will fan out over a 120-degree arc that is centered
directly across from the tank spot (to try to spread to reduce Geyser
damage while also keeping them behind Lurker).
- When Spout begins, all bots will run around behind Lurker. The intent
is to keep a distance with a radius of 20 or 21 yards and within 45
degrees (either side) of directly behind him. Movement is specifically
tangential along an arc so bots don't run in front of Lurker.
- Spout's duration is tracked by a timer. The mechanics of the spell
itself are rather unique and don't involve a continuous cast or aura to
track easily so I settled for the timer.
- If you have 3 (or more) tanks, each of the first 3 tanks will be
assigned to one of the 3 Coilfang Guardians during the submerge phase.

### Leotheras the Blind
The fight is designed for a Warlock tank. You can choose the Warlock
tank by giving a Warlock the Assistant flag. If you don't do that, your
highest HP Warlock will be picked. Do NOT switch the Warlock tank to a
co +tank strategy--the designated Warlock is hardcoded to spam Searing
Pain on Demon Leo and otherwise will engage in normal DPS strategies. If
you don't have a Warlock at all, the strategy has some methods built in
to try to make things work as best as possible with a melee tank.

- The Spellbinders get marked with skulls and killed in order.
- There is no designated spot or designated tank for the human phase.
Your tanks will fight for aggro. Ranged bots will attempt to keep some
distance, and when Whirlwind starts, everybody will run away from
Leotheras.
- During the demon phase, your melee tanks should take a backseat to
your Warlock tank, who will receive help in the form of Misdirection.
Bots will get the hell away from the Warlock tank so the Warlock tank
should be taking every Chaos Blast alone.
- During the final phase, your regular tanks will tank Leotheras, and
the Warlock tank will tank his Shadow. The melee tanks will attempt to
separate Leotheras from his Shadow so bots can focus down Leotheras
without getting hit with Chaos Blasts.
- Bots will wait 5 seconds to DPS after every transition into human
phase, 12 seconds to DPS after every transition into demon phase, and 8
seconds to DPS after the transition into the final phase. There is no
waiting on DPS after Whirlwinds, even though it would be ideal. It's not
a big deal to live without, and for various reasons, it would have been
a pain in the ass to deal with.
- Bots will save Bloodlust/Heroism until after Spellbinders are down.
- To deal with the Inner Demons, I disabled DPS assist for bots who are
targeted and force them to focus only on their Inner Demons. This is
sufficient in my experience for all DPS bots and Protection Warriors and
Paladins to kill their Inner Demons, even at 50% damage. Feral Tank
Druids and Healers still need help, so the strategy hardcodes their
actions while fighting Inner Demons. For example, Resto Druids are coded
to shift out of Tree Form, cast Barkskin on themselves, and just spam
Wrath until the Inner Demon is dead. There are no bot strategy changes
used for this method.

### Fathom-Lord Karathress
You will need 4 tanks. Your main tank will tank Karathress, and an
assistant tank will tank each Fathom Guard. If you have fewer than 4
tanks, then the priority order for tank assignment will be Karathress,
Caribdis, Sharkkis, and then Tidalvess.

- Roughly, the tank spots are (1) for Karathress, near where he starts
but closer to the ledge for LoS reasons, (2) for Sharkkis, North from
his starting location on the other side of the ramp, (3) for Tidalvess,
Northwest from his starting location near the pillar, and (4) for
Caribdis, far to the West of her starting position, near the corner.
- Note that the tanks will probably clip through the terrain a bit when
going to their positions. This is due to me implementing a forced MoveTo
to the tank position coordinates. There is something weird about the
maps in Karathress's room, and the tanks will take some really screwed
up paths without making them go directly to the exact coordinates. So
this looks stupid but is necessary.
- One healer will be assigned to heal the Caribdis tank. Because AC
Playerbots does not yet have a focus heal strategy, this just means that
such healer has a designated location near the Caribdis tank's location.
This healer can be selected with the Assistant flag.
- Hunters will misdirect the Fathom Guards onto their applicable tanks.
If you don't have three Hunters, the priority is Caribdis, Tidalvess,
then Sharkkis.
- DPS will wait 12 seconds to begin attacking. After that, they will
prioritize targets as follows:
- (1): Melee will always prioritize Spitfire Totems as soon as they
spawn. This will continue through the duration of the fight.
- (2): All bots will kill Tidalvess first.
- (3): Melee bots will move to Sharkkis, and ranged bots will move to
Caribdis. I understand this is not the standard kill order for players,
which would have the entire raid kill Sharkkis next. The reasons I have
done this differently are because melee DPS is much stronger with 3.3.5
talents vs. in retail TBC, and because bots get really thrown off by
Cyclones and therefore they struggle to kill Caribdis quickly. You do
not want Karathress below 75% HP before all Fathom-Guards are dead or he
gets a huge damage buff.
- (4) If Caribdis dies first, ranged bots will help with Sharkkis.
- (5) Everybody kills Sharkkis's pet.
- (6) Everybody kills Karathress.

### Morogrim Tidewalker

- The main tank will pull the boss to the Northeast pillar, with the
tank's back against the pillar.
- A hunter will misdirect the boss onto the main tank upon the pull.
- When the boss gets to 26% HP, the main tank will begin moving the boss
to the Northeast corner of the room in preparation for Phase 2 (which
begins at 25%). The tank will move in two steps to get around the
pillar.
- When the boss gets to 25% HP, ranged will follow the main tank to the
corner and stack up right behind the boss. They will also move in two
steps.
- There is no method for melee since they will just naturally follow the
boss anyway.

### Lady Vashj

**Phase 1**:
- The main tank will tank Vashj in the center of the arena.
- If a Shaman is in the main tank's group, that Shaman will attempt to
keep a Grounding Totem down in range of the main tank to absorb Shock
Blast. This should continue in Phase 3.
- Ranged bots will spread out in a semicircle around the center of the
arena.
- If any bot other than the main tank gets Static Charge, it will run
away from other bots. If the main tank gets Static Charge, other bots
will run away from the main tank. This method should continue in Phase
3.
- If any bot is Entangled and has Static Charge, the bot will attempt to
use Cloak of Shadows if it is a Rogue, and Paladins will attempt to use
Hand of Freedom. This method should continue in Phase 3 (with some
modifications).
- Bots will not use Bloodlust or Heroism (saved for Phase 3). Bots will
not use any other major cooldowns, either, such as Metamorphosis (saved
for Phase 2 and 3).

**Phase 2**:
There are two central mechanics to this phase, both of which were
challenging to get bots to execute properly. First is the system of
prioritizing adds. The large playing field and multiple types of adds
coming from random directions make this phase not doable with realistic
DPS under the standard Playerbots target selection system. Therefore, I
took inspiration from liyunfan's Naxx strategy for Phase 1 of Kel'Thuzad
to disable dps assist and create a custom target selection system.

First, a cheat with respect to the Coilfang Striders: 
- Tanks will permanently have the Fear Ward aura applied to them if you
have raid cheats enabled. This allows them to tank the Coilfang
Striders. The standard strategy was to have an Elemental Shaman kite the
Strider around the perimeter of the arena, with ranged players
(including healers) spamming DoTs on the Strider. If you can make bots
do this, then great, but it's far beyond my capabilities. Therefore,
with the cheat, the first assistant tank is responsible for tanking
Striders and keeping them away from Core passers (described below) and
Vashj. Evidently it was (and is, in TBC Classic) possible to tank (and
melee DPS) Striders by wearing a Dire Maul Ogre Suit, which would give
you enough reach to stay out of the Strider's fear. I actually tried
that, and it does not work, either because AC's radiuses are not the
same or just because bots do not maintain the same level of precise
positioning. But anyway, the point is that technically the Striders are
tankable by real players, so maybe that will make you feel better about
using this cheat (it's fine enough rationalizing for me). I found this
fight to be unmanageable without this cheat (i.e., using a method that
would only have bots try to run away from Striders) because each Strider
was guaranteed to wipe out a couple of bots, and you really cannot
afford to lose anyone. YMMV though.
- If cheats are enabled for Striders, Hunters will attempt to Misdirect
the Striders to the first assist tank.
- If cheats are not enabled, bots will attempt to use slows/roots to
stop the Striders. I have some logic for them to use Netherweave Nets,
but I suspect it does not actually work so I may remove it instead of
trying to get it to function properly.

Target priority is as follows:
- Hunters and Mages: Enchanted Elementals, Coilfang Striders, Coilfang
Elites.
- Other Ranged Bots: Elites, Striders, Elementals. 
- Melee DPS: Elementals, Elites.
- Tanks: Elites, Elementals (except if cheats are enabled, the first
assistant tank will instead prioritize Striders and then Elementals)
- Everybody else (basically means healers): Elementals, Elites, Striders
- If there is more than one of the same target, bots will prioritize the
one that is closer to Vashj.
- In all cases, the valid attack ranged is limited so that bots should
not leave the central platform.
- If somehow a bot ends up too far from the center of the room and is
not actively attacking anything, there is logic to make them run back.

Handling Tainted Elementals and the Tainted Core: I will make another
post about this later. It is easily the most complicated strategy I've
ever worked on (far beyond anything on Kael'thas even) so will
necessitate a long explanation. The tl;dr is that there is a chain of
two-to-four bots that receive/pass the Tainted Core before using it on a
Shield Generator, and if you are playing by yourself, you probably need
to turn raid cheats on, in which case there will also be a bot that
teleports to, kills, and loots the Tainted Elementals (i.e., the bots
will then handle the entire sequence of shutting down Shield
Generators).

**Phase 3**:
- The main tank will pick up Vashj immediately and try to keep her away
from Enchanted Elementals.
- DPS will burn down residual adds from Phase 2 in the order of (1)
elementals, (2) strider for ranged only (if you have more than one up,
you're dead), and (3) elites (hopefully you have only one up, but two
with one almost dead is possible).
- Hunters will kill Toxic Sporebats. This works quite well, but they
(and anybody else if ordered to target Sporebats) have a tendency to
levitate up into the pipes at the top of the room when killing the
Sporebats. To counteract this, a method forcibly teleports bots to the
ground if they get more than 2 yards above the ground.
- The Phase 1 Cloak of Shadows/Hand of Freedom method is now expanded to
include bots Entangled in the Sporebat poison pools (with Hand of
Freedom usage prioritized on the main tank).
- There is a specific method to avoid the Sporebat poison pools. The
Vashj tank will move backwards when avoiding poison.

---------

Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-02-08 12:31:23 +01:00
kadeshar
79fb3a5bbc - Fixed Oculus drake mounting 2026-02-07 17:53:55 +01:00
kadeshar
6ed3f24ecb Enforce test fix (#2122)
CI/CD PR

---------

Co-authored-by: Crow <pengchengw@me.com>
2026-02-07 07:34:15 -08:00
Alex Dcnh
76b6df9ea3 Extend SummonWhenGroup to auto-added bots (#2034)
### Summary
Extend AiPlayerbot.SummonWhenGroup to apply when bots are auto-added to
a group (e.g., addclass bots or raidus style auto invites).

### Motivation
Bots added automatically to a group never accept a normal invite, so
they do not trigger the summon-on-accept path. When SummonWhenGroup is
enabled, these bots should also be teleported next to the master to
match expected behavior.

### Implementation details
Hook the summon behavior right after automatic group addition.
2026-02-06 14:20:31 -08:00
kadeshar
026df0dabe Chilton wand fix (#2115)
# Pull Request

Added Chilton wand to excluded to equipment items for bots and unified 2
exclusion lists to single one.
Resolves: https://github.com/mod-playerbots/mod-playerbots/issues/2093

---

## How to Test the Changes

Couldnt reproduce Chilton wand bug then testing sound impossible.
Someone can try getting this items on shaman.

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes

If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes
---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---
2026-02-06 11:57:48 -08:00
Crow
b31bda85ee Refactor raid strategy framework (#2069)
# Pull Request

The purposes of this PR are to (1) establish a general raid helper
framework for the benefit of future raid strategies and (2) make some
improvements to problematic areas of the raid strategy code.

List of changes:
1. Added new RaidBossHelpers.cpp and RaidBossHelpers.h files in the Raid
folder.

3. Moved reused helpers from Karazhan, Gruul, and Magtheridon strategies
to the new helper files.
4. Modified the prior function that assigned a DPS bot to store and
erase timers and trackers in associative containers--the function now
includes parameters for mapId (so a bot that is not in the instance will
not be assigned) and for the ability to exclude a bot (useful for
excluding particular important roles, such as a Warlock tank, so they
are not bogged down by these extra tasks at critical moments). I also
renamed it from IsInstanceTimerManager to IsMechanicTrackerBot.
5. Moved all helper files in raid strategies to Util folders (was needed
for ICC, MC, and Ulduar).
6. Renamed and reordered includes of Ulduar files in AiObjectContext.cpp
to match other raid strategies.
a. This initially caused compile errors which made me realize that the
existing code had several problems with missing includes and was
compiling only due to the prior ordering in AiObjectContext.cpp.
Therefore, I added the missing includes to Molten Core, Ulduar, and
Vault of Archavon strategies.
b. Ulduar and Old Kingdom were also using the same constant name for a
spell--the reordering caused a compile error here as well, which just
highlighted an existing problem that was being hidden. I renamed the
constant for Ulduar to fix this, but I think the better approach going
forward would be to use a namespace or enum class. But that is for
another time and probably another person.
7. Several changes with respect to Ulduar files:
a. The position constants and enums for spells and NPCs and such were in
the trigger header file. I did not think that made sense so moved them
to existing helper files.
b. Since the strategy does not use multipliers, I removed all files and
references to multipliers in it.
c. I removed some unneeded includes. I did not do a detailed review to
determine what else could be removed--I just took some out that I could
tell right away were not needed.
d. I renamed the ingame strategy name from "uld" to "ulduar," which I
think is clearer and is still plenty short.
8. Partial refactor of Gruul and Magtheridon strategies:
a. I did not due a full refactoring but made some quick changes to
things I did previously that were rather stupid like repeating
calculations, having useless logic like pointless IsAlive() checks for
creatures already on the hostile references list, and not using the
existing Position class for coordinates.
b. There were a few substantive changes, such as allowing players to
pick Maulgar mage and moonkin tanks with the assistant flag, but a
greater refactoring of the strategies themselves is beyond this PR.
c. I was clearing some containers used for Gruul and Magtheridon
strategies; the methods are now fixed to erase only the applicable keys
so that in the unlikely event that one server has multiple groups
running Gruul or Magtheridon at the same time, there won't be timer or
position tracker conflicts.

## How to Test the Changes

1. Enter any raid instance that has any code impacted by this PR
2. Engage bosses and observe if any strategies are now broken

I personally tested Maulgar, Gruul, and Magtheridon and confirmed that
they still work as intended.

## Complexity & Impact

I do not expect this PR to have any relevant changes to in-game
performance, but I will defer to those more knowledgeable than I if
there are concerns in this area. As I've mentioned before, you can
consider me to be like a person who has taken half an intro C++ course
at best.

## AI Assistance

None beyond autocomplete of repetitive changes.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-06 11:55:43 -08:00
kadeshar
bebac60c51 test-staging alignment (#2121)
# Pull Request

Describe what this change does and why it is needed...

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [ ] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [ ] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [ ] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [ ] No
- - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [ ] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [ ] Stability is not compromised
- - [ ] Performance impact is understood, tested, and acceptable
- - [ ] Added logic complexity is justified and explained
- - [ ] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: Crow <pengchengw@me.com>
2026-02-06 11:55:28 -08:00
Crow
52d4191b43 Correct Zone ID 10 in config (Deadwind Pass -> Duskwood) (#2109)
Simple fix to config--zone ID 10 is Duskwood, not Deadwind Pass, as pointed out by @privatecore
2026-02-04 16:01:18 -08:00
kadeshar
254055ff32 Workaround for checkboxes without task (#2116)
# Pull Request

Describe what this change does and why it is needed...

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
```
[ ] No
[ ] Yes (**explain below**)
```

Does this change increase per-bot or per-tick processing?
```
[ ] No
[ ] Yes (**describe and justify impact**)
```

Could this logic scale poorly under load?
```
[ ] No
[ ] Yes (**explain why**)
```
---

## Defaults & Configuration

Does this change modify default bot behavior?
```
[ ] No
[ ] Yes (**explain why**)
```

If this introduces more advanced or AI-heavy logic:
```
[ ] Lightweight mode remains the default
[ ] More complex behavior is optional and thereby configurable
```
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
```
[ ] No
[ ] Yes (**explain below**)
```

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- [ ] Stability is not compromised
- [ ] Performance impact is understood, tested, and acceptable
- [ ] Added logic complexity is justified and explained
- [ ] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-03 23:08:53 +01:00
bashermens
31765c77fa Update PULL_REQUEST_TEMPLATE.md (#2114) 2026-02-03 21:48:58 +01:00
Thomas Frey
c86032f43b Convert PlayerBots tables to InnoDB (#2083)
Convert PlayerBots tables to InnoDB (disable strict mode during
conversion)

# Pull Request

### This change converts the PlayerBots-related tables from MyISAM to
InnoDB.

**Why this is beneficial (even without fixing a specific bug):**

- Crash safety & data integrity: InnoDB is transactional and uses redo
logs; it provides automatic crash recovery, unlike MyISAM which can
require manual repairs after unclean shutdowns.
- Row-level locking: InnoDB reduces write contention and improves
concurrency under bot-heavy workloads compared to MyISAM’s table-level
locks.
- Consistent reads: InnoDB supports MVCC, enabling stable reads while
writes are happening—useful for mixed read/write access patterns.
- Operational robustness: Better behavior under backup/restore and
replication scenarios; fewer “table marked as crashed” style issues.

Strict mode handling:
The migration toggles innodb_strict_mode off only for the session to
prevent the conversion from failing on edge-case legacy definitions,
then re-enables it immediately after.

---

## How to Test the Changes

- Step-by-step instructions to test the change
Run the SQL script in the Playerbot database.
- Any required setup (e.g. multiple players, bots, specific
configuration)
No
- Expected behavior and how to verify it
All tables should now have been converted from InnoDB to MyISAM.
This script should return nothing:

```
SELECT
    t.TABLE_SCHEMA AS db_name,
    t.TABLE_NAME   AS table_name,
    t.ENGINE       AS storage_engine
FROM information_schema.TABLES t
WHERE t.TABLE_SCHEMA = DATABASE()
-- With phpMyAdmin, use the following and insert your database name, e.g., “acore_playerbots.”
-- WHERE t.TABLE_SCHEMA = 'YOUR_PLAYERBOT_DB'
  AND t.TABLE_TYPE = 'BASE TABLE'
  AND t.ENGINE = 'MyISAM'
ORDER BY t.TABLE_NAME;
```

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [ ] Documentation updated if needed
- [x] I tested this script on a server with 2000 bots for 6 days
(running 24/h) and had no issues with it.

---


## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-02 22:42:02 +01:00
Alex Dcnh
ba835250c8 New whisper command "pvp stats" that allows players to ask a bot to report its current Arena Points, Honor Points, and Arena Teams (#2071)
# Pull Request

This PR adds a new whisper command "pvp stats" that allows players to
ask a bot to report its current Arena Points, Honor Points, and Arena
Teams (name and team rating).

Reason:
Due to a client limitation in WoW 3.3.5a, the inspection window does not
display another player's Arena or Honor points , only team data.
This command provides an easy in-game way to check a bot’s PvP
currencies without modifying the client or core packets.

---

## Design Philosophy

Uses existing core getters (GetArenaPoints, GetHonorPoints,
GetArenaTeamId, etc.).
Fully integrated into the chat command system (ChatTriggerContext,
ChatActionContext).
Safe, no gameplay changes, purely informational.
No harcoded texts, use database local instead

---

## How to Test the Changes

/w BotName pvp stats

Bot reply:

[PVP] Arena Points: 302 | Honor Points: 11855
[PVP] 2v2: <The Fighters> (rating 2000)
[PVP] 3v3: <The Trio> (rating 573)

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [x] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

Multibot already ready

Here is a sample of multibot when merged:
<img width="706" height="737" alt="image"
src="https://github.com/user-attachments/assets/5bcdd9f8-e2fc-4c29-a497-9fffba5dfd4e"
/>

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-02 22:40:12 +01:00
bashermens
8c2a27b9fe Update check_pr_source.yml (#2101) 2026-02-01 22:41:49 +01:00
bashermens
8e316cd321 fix (#2099) 2026-02-01 22:26:07 +01:00
bashermens
cafb95e7bd Create check_pr_source.yml (#2098) 2026-02-01 22:21:57 +01:00
bashermens
8529654f8f Correction after singleton PR (#2095)
# Pull Request

Some logic was changed but differs from the original code, certain edge
cases so not result in same behavior. This returns the original code
with only the singleton chances.

@Wishmaster117 Reviewed the hotfix and noticed the different code paths.

ps: reverted an removed placeholder since its ongoing issue/research.
2026-02-01 10:18:59 +01:00
Crow
6ee1684e9b Fix WSG graveyard camping by flag carrier (#2086)
Quick fix for a very annoying error identified by SmashingQuasar. In
WSG, bots will camp the opposing graveyard if up 2-0. This is supposed
to exclude the flag carrier, but a logical error has resulted in the
flag carrier being excluded for Alliance camping only, meaning the Horde
flag carrier will camp the GY with the rest of the team if up 2-0 and
thus refuse to end the game.
2026-01-31 23:11:40 +01:00
Crow
9546363d41 Hotfix for OnBotLoginOperation() Crash (#2089)
Hotfix for an issue arising from
https://github.com/mod-playerbots/mod-playerbots/pull/2082

OnBotLoginOperation() is calling OnBotLogin() twice for altbots. I don't
know the full implication, but RandomPlayerbotMgr::OnBotLoginInternal()
is being called on altbots, and the server will crash if you attempt to
then log out the altbot.

This fix works for me right now. Discussed with @Celandriel , going to
push this hotfix for now until the rest of the maintainers can take a
look.
2026-01-31 10:54:19 +01:00
Crow
00d19dbf9c Fix Destro Warlock Glyphs (#2084)
Updates are only to the config. This PR should be simple. Tl;dr is
destro pve spec is using the wrong glyphs.

Longer explanation--right now, PreMadeSpecGlyph in the config provides
for destro pve spec to use the following Major Glyphs at levels 15, 30,
and 80, respectively: Life Tap, Quick Decay, Conflagrate. Quick Decay is
useless for destro because destro does not cast Corruption except as a
filler instant cast when on the move. Meanwhile, the spec is almost
unplayable without Glyph of Conflagrate, so that should not be withheld
until level 80. After Conflagrate, there are several viable glyphs,
including Life Tap, Incinerate, Immolate, and Imp. I understand Glyph of
Life Tap gets worse over time to the point that you don't want to use
that glyph in ICC, but that's quite late, and it is useful for the vast
majority of the game as a glyph that would actually be available at
level 15. I also understand that Glyph of Immolate does not excel until
high gear levels. Therefore, I decided to use Incinerate as the default
level 80 glyph.

The new order for default glyphs for destro pve for levels 15, 30, and
80 is Life Tap, Conflagrate, and Incinerate, respectively. I also made a
couple of other very minor fixes in the config. No impact on performance
or AI, obviously.

Sidenote: Glyph of Conflagrate is not available at level 30--it requires
level 40, so from 30 to 40, InitGlyphs() will plug in a random glyph for
the second Major slot. This issue applies to many specs, and it's not
avoidable unless InitGlyphs() is broken up into level brackets, which I
think is not worthwhile. I think the better approach for glyphs is to
ensure the right ones are applied at high levels, but with an attempt to
make them usable at lower levels too where possible.
2026-01-30 21:52:35 +01:00
Keleborn
caae524a0a Minor flightMasterCache fix. (#2085)
# Pull Request

Incorrect comparison fix. 

---

## How to Test the Changes

- Alliance Bots should now be able to find the correct flightmaster and
use it

## Complexity & Impact

- Does this change add new decision branches?
    - [X] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [X] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [X] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [X] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [X] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [X] No
    - [ ] Yes (**explain below**)

---

## Final Checklist

- [X] Stability is not compromised
- [X] Performance impact is understood, tested, and acceptable
- [X] Added logic complexity is justified and explained
- [X] Documentation updated if needed
2026-01-30 21:50:09 +01:00
bashermens
13fff46fa0 Improper singletons migration to clean Meyer's singletons (cherry-pick) (#2082)
# Pull Request

- Applies the clean and corrected singletons, Meyer pattern. (cherry
picked from @SmashingQuasar )

Testing by just playing the game in various ways. Been tested by myself
@Celandriel and @SmashingQuasar
---

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)
---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: Nicolas Lebacq <nicolas.cordier@outlook.com>
Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-01-30 21:49:37 +01:00
dillyns
a92886032c Summon Logic Tweaks (#2049)
Issues:
- When you have selfbot enabled and use summon command, you will summon
yourself. This causes odd movement if you summon while moving, and can
sometimes lead to falling through the floor.
- When using the summon command on bots with pets/guardians from a
medium distance (like jumping down a ledge then commanding summon), the
pets will pathfind run to catch up. This causes them to aggro everything
on the way.

Solution: 
Fix summon logic to prevent selfbot summon and ensure pets are
teleported with bots.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-01-29 12:25:50 -08:00
gtkk
f5711dc6f7 FIX Onyxia Crash (#2062)
Solve these two problems #2043 #1981
@Regrad is the main contributor of the code, while I was just helping to
submit the pull request. Express my gratitude to him.
After testing, the code is proven to be effective.
2026-01-25 14:03:53 +01:00
bashermens
43e8e31980 Update PULL_REQUEST_TEMPLATE.md (#2066) 2026-01-25 13:46:23 +01:00
bashermens
c59a02ed89 Update PULL_REQUEST_TEMPLATE.md (#2065) 2026-01-25 12:26:15 +01:00
bashermens
7abd836971 Update PULL_REQUEST_TEMPLATE.md (#2064) 2026-01-25 12:22:53 +01:00
bashermens
5365ba86b5 Added PR template (#2063) 2026-01-25 12:13:36 +01:00
Crow
378254af3f Fix Assistant Assignment Functions (#1930)
IsHealAssistantOfIndex() and IsRangedDpsAssistantOfIndex() are supposed
to iterate through the group and first return members with the
applicable role that have the assistant flag, and then iterate through
non-assistants only if there are not enough assistants for the
designated index. They are not written properly and actually completely
ignore the assistant flag.

I rely on these functions for significant roles in SSC and TK (which I
have decided I'll PR in the same way as SSC, as a long-term draft). I
have them fixed on my own fork, but it is problematic for testers if
these functions do not work.

So I've done three things here:
1. Fixed the functions to prefer members with the assistant flag.
2. Added a third parameter for ignoreDeadPlayers, like
IsAssistTankOfIndex() has. Note that the parameter is by default false
for IsAssistTankOfIndex(), meaning dead players are _not_ ignored. This
is not my preferred design choice--I think the default should be to
ignore dead players, but I have not changed the default and have made
the default the same for IsAssistHealOfIndex() and
IsAssistRangedDpsOfIndex(), since I don't know the intent of the
pre-existing boss strats that use the functions.
3. Changed the names to IsAssistHealOfIndex() and
IsAssistRangedDpsOfIndex() so they parallel IsAssistTankOfIndex(), and
made corresponding changes in the few boss strats that use the
functions.

Also, note that the functions _do _not_ exclude real players. I think
there are arguments for and against excluding real players. A fourth
parameter for this could be useful, but I've not made any change in that
regard.
2026-01-24 21:26:49 +01:00
bashermens
3d467ce3bb Added some additional defense checks around isHostile and unit/target (#2056)
Needs second pair of eyes, they appear in crash logs here and there. Its
merely a patch on a open wound.

----
As in aslong there multithreads in mapupdate, which we need for decent
performance and core calls are not done correctly due various reasons.
These type of issues remain.

Although i am planning to experiment a little with threadsafe execution
of our strategies vs performance.

The most effective thing we could do is check every single action and
check its stateless and where it does effect the state or read the state
of a core object its done in the safest way. flags, worldthread where
possible and/ot simply taking into account the state might be invalid.
2026-01-24 20:41:12 +01:00
451 changed files with 20910 additions and 8549 deletions

21
.github/workflows/check_pr_source.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Enforce test-staging → master
on:
pull_request:
branches:
- master
- test-staging
jobs:
require-test-staging:
runs-on: ubuntu-22.04
if: github.event.pull_request.base.ref == 'master'
steps:
- name: Ensure PR source is test-staging
run: |
echo "Base: ${{ github.event.pull_request.base.ref }}"
echo "Head: ${{ github.event.pull_request.head.ref }}"
if [ "${{ github.event.pull_request.head.ref }}" != "test-staging" ]; then
echo "✖ Pull request must come from test-staging"
exit 1
fi

View File

@@ -2,9 +2,9 @@ name: Codestyle
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "codestyle-${{ github.event.pull_request.number }}" group: "codestyle-${{ github.event.pull_request.number }}"

View File

@@ -6,10 +6,6 @@ on:
- reopened - reopened
- synchronize - synchronize
- ready_for_review - ready_for_review
paths:
- src/**
- "!README.md"
- "!docs/**"
concurrency: concurrency:
group: "codestyle-cppcheck-${{ github.event.pull_request.number }}" group: "codestyle-cppcheck-${{ github.event.pull_request.number }}"
@@ -22,13 +18,27 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
cpp:
- 'src/**'
- '!README.md'
- '!docs/**'
- name: Setup python - name: Setup python
if: steps.filter.outputs.cpp == 'true'
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.10' python-version: '3.10'
- name: AzerothCore codestyle - name: AzerothCore codestyle
if: steps.filter.outputs.cpp == 'true'
run: python ./apps/codestyle/codestyle-cpp.py run: python ./apps/codestyle/codestyle-cpp.py
- name: C++ Advanced - name: C++ Advanced
if: steps.filter.outputs.cpp == 'true'
run: | run: |
sudo apt update -y sudo apt update -y
sudo apt install -y cppcheck sudo apt install -y cppcheck

View File

@@ -2,9 +2,9 @@ name: ubuntu-build
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "core-build-${{ github.event.pull_request.number }}" group: "core-build-${{ github.event.pull_request.number }}"

View File

@@ -1,9 +1,9 @@
name: macos-build name: macos-build
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "macos-build-${{ github.event.pull_request.number }}" group: "macos-build-${{ github.event.pull_request.number }}"

View File

@@ -1,9 +1,9 @@
name: windows-build name: windows-build
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "windows-build-${{ github.event.pull_request.number }}" group: "windows-build-${{ github.event.pull_request.number }}"

124
PULL_REQUEST_TEMPLATE.md Normal file
View File

@@ -0,0 +1,124 @@
# Pull Request
Describe what this change does and why it is needed...
---
## Design Philosophy
We prioritize **stability, performance, and predictability** over behavioral realism.
Complex player-mimicking logic is intentionally limited due to its negative impact on scalability, maintainability, and
long-term robustness.
Excessive processing overhead can lead to server hiccups, increased CPU usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability, and significantly higher maintenance overhead.
Every additional branch of logic increases long-term responsibility. All decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having a measurable performance cost.
Principles:
- **Stability before intelligence**
A stable system is always preferred over a smarter one.
- **Performance is a shared resource**
Any increase in bot cost affects all players and all bots.
- **Simple logic scales better than smart logic**
Predictable behavior under load is more valuable than perfect decisions.
- **Complexity must justify itself**
If a feature cannot clearly explain its cost, it should not exist.
- **Defaults must be cheap**
Expensive behavior must always be optional and clearly communicated.
- **Bots should look reasonable, not perfect**
The goal is believable behavior, not human simulation.
Before submitting, confirm that this change aligns with those principles.
---
## Feature Evaluation
Please answer the following:
- Describe the **minimum logic** required to achieve the intended behavior?
- Describe the **cheapest implementation** that produces an acceptable result?
- Describe the **runtime cost** when this logic executes across many bots?
---
## How to Test the Changes
- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific configuration)
- Expected behavior and how to verify it
## Complexity & Impact
Does this change add new decision branches?
- - [ ] No
- - [ ] Yes (**explain below**)
Does this change increase per-bot or per-tick processing?
- - [ ] No
- - [ ] Yes (**describe and justify impact**)
Could this logic scale poorly under load?
- - [ ] No
- - [ ] Yes (**explain why**)
---
## Defaults & Configuration
Does this change modify default bot behavior?
- - [ ] No
- - [ ] Yes (**explain why**)
If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---
## AI Assistance
Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change?
- - [ ] No
- - [ ] Yes (**explain below**)
If yes, please specify:
- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted
AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB logic. We expect contributors to be honest
about what they do and do not understand.
---
## Final Checklist
- - [ ] Stability is not compromised
- - [ ] Performance impact is understood, tested, and acceptable
- - [ ] Added logic complexity is justified and explained
- - [ ] Documentation updated if needed
---
## Notes for Reviewers
Anything that significantly improves realism at the cost of stability or performance should be carefully discussed
before merging.

View File

@@ -8,7 +8,7 @@
<div align="center"> <div align="center">
<img src="icon.png" alt="Playerbots Icon" width="700px"> <img src="banner.png" alt="Playerbots Banner" width="700px">
</div> </div>
<div align="center"> <div align="center">

BIN
banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

View File

@@ -192,9 +192,12 @@ AiPlayerbot.AutoInitOnly = 0
# Default: 1.0 (same with the player) # Default: 1.0 (same with the player)
AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0 AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0
# Bot automatically trains spells when talking to trainer #
# yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells # AllowLearnTrainerSpells
AiPlayerbot.AutoTrainSpells = yes # Description: Allow the bot to learn trainers' spells as long as it has the money.
# Default: 1 - (Enabled)
# 0 - (Disabled)
AiPlayerbot.AllowLearnTrainerSpells = 1
# #
# #
@@ -544,8 +547,8 @@ AiPlayerbot.AutoGearQualityLimit = 3
# Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92 # Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92
# TBC # TBC
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164 # Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164 # Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 2(SSC, TK, ZA) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
# Wotlk # WotLK
# Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290 # Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290
# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290 # Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290
# Default: 0 (no limit) # Default: 0 (no limit)
@@ -558,11 +561,39 @@ AiPlayerbot.AutoGearScoreLimit = 0
# "mana" (bots have infinite mana) # "mana" (bots have infinite mana)
# "power" (bots have infinite energy, rage, and runic power) # "power" (bots have infinite energy, rage, and runic power)
# "taxi" (bots may use all flight paths, though they will not actually learn them) # "taxi" (bots may use all flight paths, though they will not actually learn them)
# "raid" (bots use cheats implemented into raid strategies (currently only for Ulduar)) # "raid" (bots use cheats implemented into raid strategies (currently only for SSC and Ulduar))
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi") # To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
# Default: food, taxi, and raid are enabled # Default: food, taxi, and raid are enabled
AiPlayerbot.BotCheats = "food,taxi,raid" AiPlayerbot.BotCheats = "food,taxi,raid"
# Attunement quests (comma-separated list of quest IDs)
# Default:
# Caverns of Time - Part 1
# - 10279, To The Master's Lair
# - 10277, The Caverns of Time
#
# Caverns of Time - Part 2 (Escape from Durnholde Keep)
# - 10282, Old Hillsbrad
# - 10283, Taretha's Diversion
# - 10284, Escape from Durnholde
# - 10285, Return to Andormu
#
# Caverns of Time - Part 2 (The Black Morass)
# - 10296, The Black Morass
# - 10297, The Opening of the Dark Portal
# - 10298, Hero of the Brood
#
# Magister's Terrace Attunement
# - 11481, Crisis at the Sunwell
# - 11482, Duty Calls
# - 11488, Magisters' Terrace
# - 11490, The Scryer's Scryer
# - 11492, Hard to Kill
#
# Serpentshrine Cavern
# - 10901, The Cudgel of Kar'desh
AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901
# #
# #
# #
@@ -736,7 +767,7 @@ AiPlayerbot.RandomGearQualityLimit = 3
# TBC # TBC
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164 # Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 2(SSC, TK, ZA) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164 # Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 2(SSC, TK, ZA) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
# Wotlk # WotLK
# Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290 # Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290
# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290 # Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290
# Default: 0 (no limit) # Default: 0 (no limit)
@@ -990,7 +1021,7 @@ AiPlayerbot.ZoneBracket.3433 = 10,22
AiPlayerbot.ZoneBracket.3525 = 10,21 AiPlayerbot.ZoneBracket.3525 = 10,21
# Classic WoW - High-level zones: # Classic WoW - High-level zones:
# Deadwind Pass (Zone ID: 10 Default Min,Max: 19,33) # Duskwood (Zone ID: 10 Default Min,Max: 19,33)
# Wetlands (Zone ID: 11 Default Min,Max: 21,30) # Wetlands (Zone ID: 11 Default Min,Max: 21,30)
# Redridge Mountains (Zone ID: 44 Default Min,Max: 16,28) # Redridge Mountains (Zone ID: 44 Default Min,Max: 16,28)
# Hillsbrad Foothills (Zone ID: 267 Default Min,Max: 20,34) # Hillsbrad Foothills (Zone ID: 267 Default Min,Max: 20,34)
@@ -1624,7 +1655,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,50077,43394,43393,42454 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
@@ -2182,4 +2213,4 @@ AiPlayerbot.SummonAtInnkeepersEnabled = 1
# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for non tank bots, increased threat for tank bots. # 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for non tank bots, increased threat for tank bots.
# Buffs will be applied on PP, Sindragosa and Lich King # Buffs will be applied on PP, Sindragosa and Lich King
AiPlayerbot.EnableICCBuffs = 1 AiPlayerbot.EnableICCBuffs = 1

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
-- #########################################################
-- Playerbots - Add PVP / Arena texts for TellPvpAction
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
-- zhTW, esES, esMX, ruRU)
-- #########################################################
DELETE FROM ai_playerbot_texts WHERE name IN ('pvp_currency', 'pvp_arena_team', 'pvp_no_arena_team');
DELETE FROM ai_playerbot_texts_chance WHERE name IN ('pvp_currency', 'pvp_arena_team', 'pvp_no_arena_team');
-- ---------------------------------------------------------
-- pvp_currency
-- [PVP] Arena points: %arena_points | Honor Points: %honor_points
-- ---------------------------------------------------------
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 (
1737,
'pvp_currency',
'[PVP] Arena points: %arena_points | Honor Points: %honor_points',
0, 0,
-- koKR
'[PVP] 투기장 점수: %arena_points | 명예 점수: %honor_points',
-- frFR
'[PVP] Points d''arène : %arena_points | Points d''honneur : %honor_points',
-- deDE
'[PVP] Arenapunkte: %arena_points | Ehrenpunkte: %honor_points',
-- zhCN
'[PVP] 竞技场点数:%arena_points | 荣誉点数:%honor_points',
-- zhTW
'[PVP] 競技場點數:%arena_points | 榮譽點數:%honor_points',
-- esES
'[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points',
-- esMX
'[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points',
-- ruRU
'[PVP] Очки арены: %arena_points | Очки чести: %honor_points');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pvp_currency', 100);
-- ---------------------------------------------------------
-- pvp_arena_team
-- [PVP] %bracket: <%team_name> (rating %team_rating)
-- ---------------------------------------------------------
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 (
1738,
'pvp_arena_team',
'[PVP] %bracket: <%team_name> (rating %team_rating)',
0, 0,
-- koKR
'[PVP] %bracket: <%team_name> (평점 %team_rating)',
-- frFR
'[PVP] %bracket : <%team_name> (cote %team_rating)',
-- deDE
'[PVP] %bracket: <%team_name> (Wertung %team_rating)',
-- zhCN
'[PVP] %bracket: <%team_name> (评分 %team_rating)',
-- zhTW
'[PVP] %bracket: <%team_name> (評分 %team_rating)',
-- esES
'[PVP] %bracket: <%team_name> (índice %team_rating)',
-- esMX
'[PVP] %bracket: <%team_name> (índice %team_rating)',
-- ruRU
'[PVP] %bracket: <%team_name> (рейтинг %team_rating)');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pvp_arena_team', 100);
-- ---------------------------------------------------------
-- pvp_no_arena_team
-- [PVP] I have no Arena Team.
-- ---------------------------------------------------------
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 (
1739,
'pvp_no_arena_team',
'[PVP] I have no Arena Team.',
0, 0,
-- koKR
'[PVP] 투기장 팀이 없습니다.',
-- frFR
'[PVP] Je n''ai aucune équipe d''arène.',
-- deDE
'[PVP] Ich habe kein Arenateam.',
-- zhCN
'[PVP] 我没有竞技场战队。',
-- zhTW
'[PVP] 我沒有競技場隊伍。',
-- esES
'[PVP] No tengo equipo de arena.',
-- esMX
'[PVP] No tengo equipo de arena.',
-- ruRU
'[PVP] У меня нет команды арены.');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pvp_no_arena_team', 100);

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

After

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -125,6 +125,7 @@ public:
creators["runaway"] = &ActionContext::runaway; creators["runaway"] = &ActionContext::runaway;
creators["stay"] = &ActionContext::stay; creators["stay"] = &ActionContext::stay;
creators["sit"] = &ActionContext::sit; creators["sit"] = &ActionContext::sit;
creators["aggressive target"] = &ActionContext::aggressive_target;
creators["attack anything"] = &ActionContext::attack_anything; creators["attack anything"] = &ActionContext::attack_anything;
creators["attack least hp target"] = &ActionContext::attack_least_hp_target; creators["attack least hp target"] = &ActionContext::attack_least_hp_target;
creators["attack enemy player"] = &ActionContext::attack_enemy_player; creators["attack enemy player"] = &ActionContext::attack_enemy_player;
@@ -315,6 +316,7 @@ private:
static Action* suggest_what_to_do(PlayerbotAI* botAI) { return new SuggestWhatToDoAction(botAI); } static Action* suggest_what_to_do(PlayerbotAI* botAI) { return new SuggestWhatToDoAction(botAI); }
static Action* suggest_trade(PlayerbotAI* botAI) { return new SuggestTradeAction(botAI); } static Action* suggest_trade(PlayerbotAI* botAI) { return new SuggestTradeAction(botAI); }
static Action* suggest_dungeon(PlayerbotAI* botAI) { return new SuggestDungeonAction(botAI); } static Action* suggest_dungeon(PlayerbotAI* botAI) { return new SuggestDungeonAction(botAI); }
static Action* aggressive_target(PlayerbotAI* botAI) { return new AggressiveTargetAction(botAI); }
static Action* attack_anything(PlayerbotAI* botAI) { return new AttackAnythingAction(botAI); } static Action* attack_anything(PlayerbotAI* botAI) { return new AttackAnythingAction(botAI); }
static Action* attack_least_hp_target(PlayerbotAI* botAI) { return new AttackLeastHpTargetAction(botAI); } static Action* attack_least_hp_target(PlayerbotAI* botAI) { return new AttackLeastHpTargetAction(botAI); }
static Action* attack_enemy_player(PlayerbotAI* botAI) { return new AttackEnemyPlayerAction(botAI); } static Action* attack_enemy_player(PlayerbotAI* botAI) { return new AttackEnemyPlayerAction(botAI); }

View File

@@ -6,9 +6,9 @@
#include "AcceptBattlegroundInvitationAction.h" #include "AcceptBattlegroundInvitationAction.h"
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "PlayerbotAI.h"
bool AcceptBgInvitationAction::Execute(Event event) bool AcceptBgInvitationAction::Execute(Event /*event*/)
{ {
uint8 type = 0; // arenatype if arena uint8 type = 0; // arenatype if arena
uint8 unk2 = 0; // unk, can be 0x0 (may be if was invited?) and 0x1 uint8 unk2 = 0; // unk, can be 0x0 (may be if was invited?) and 0x1
@@ -18,9 +18,9 @@ bool AcceptBgInvitationAction::Execute(Event event)
WorldPacket packet(CMSG_BATTLEFIELD_PORT, 20); WorldPacket packet(CMSG_BATTLEFIELD_PORT, 20);
packet << type << unk2 << (uint32)bgTypeId_ << unk << action; packet << type << unk2 << (uint32)bgTypeId_ << unk << action;
// packet << bgTypeId_ << action;
bot->GetSession()->HandleBattleFieldPortOpcode(packet); bot->GetSession()->HandleBattleFieldPortOpcode(packet);
botAI->ResetStrategies(); botAI->ResetStrategies();
return true; return true;
} }

View File

@@ -46,10 +46,10 @@ bool AcceptInvitationAction::Execute(Event event)
if (!bot->GetGroup() || !bot->GetGroup()->IsMember(inviter->GetGUID())) if (!bot->GetGroup() || !bot->GetGroup()->IsMember(inviter->GetGUID()))
return false; return false;
if (sRandomPlayerbotMgr->IsRandomBot(bot)) if (sRandomPlayerbotMgr.IsRandomBot(bot))
botAI->SetMaster(inviter); botAI->SetMaster(inviter);
// else // else
// sPlayerbotRepository->Save(botAI); // PlayerbotRepository::instance().Save(botAI);
botAI->ResetStrategies(); botAI->ResetStrategies();
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
@@ -57,7 +57,7 @@ bool AcceptInvitationAction::Execute(Event event)
botAI->TellMaster("Hello"); botAI->TellMaster("Hello");
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance) if (sPlayerbotAIConfig.summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig.sightDistance)
{ {
Teleport(inviter, bot, true); Teleport(inviter, bot, true);
} }

View File

@@ -22,7 +22,7 @@ bool AddLootAction::Execute(Event event)
return AI_VALUE(LootObjectStack*, "available loot")->Add(guid); return AI_VALUE(LootObjectStack*, "available loot")->Add(guid);
} }
bool AddAllLootAction::Execute(Event event) bool AddAllLootAction::Execute(Event /*event*/)
{ {
bool added = false; bool added = false;

View File

@@ -50,7 +50,7 @@ bool ReachAreaTriggerAction::Execute(Event event)
/*forceDestination*/ false); /*forceDestination*/ false);
float distance = bot->GetDistance(at->x, at->y, at->z); float distance = bot->GetDistance(at->x, at->y, at->z);
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay; float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay;
botAI->TellError("Wait for me"); botAI->TellError("Wait for me");
botAI->SetNextCheckDelay(delay); botAI->SetNextCheckDelay(delay);
context->GetValue<LastMovement&>("last area trigger")->Get().lastAreaTrigger = triggerId; context->GetValue<LastMovement&>("last area trigger")->Get().lastAreaTrigger = triggerId;
@@ -58,7 +58,7 @@ bool ReachAreaTriggerAction::Execute(Event event)
return true; return true;
} }
bool AreaTriggerAction::Execute(Event event) bool AreaTriggerAction::Execute(Event /*event*/)
{ {
LastMovement& movement = context->GetValue<LastMovement&>("last area trigger")->Get(); LastMovement& movement = context->GetValue<LastMovement&>("last area trigger")->Get();

View File

@@ -87,8 +87,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
// 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) &&
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) || (sPlayerbotAIConfig.IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId()))) sPlayerbotAIConfig.IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
{ {
if (verbose) if (verbose)
botAI->TellError("I cannot attack other players in PvP prohibited areas."); botAI->TellError("I cannot attack other players in PvP prohibited areas.");
@@ -160,7 +160,7 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
} }
if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target)) if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
sServerFacade->SetFacingTo(bot, target); ServerFacade::instance().SetFacingTo(bot, target);
botAI->ChangeEngine(BOT_STATE_COMBAT); botAI->ChangeEngine(BOT_STATE_COMBAT);

View File

@@ -1,25 +1,26 @@
#include "AutoMaintenanceOnLevelupAction.h" #include "AutoMaintenanceOnLevelupAction.h"
#include "GuildMgr.h" #include "SpellMgr.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "BroadcastHelper.h" #include "BroadcastHelper.h"
bool AutoMaintenanceOnLevelupAction::Execute(Event event) bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
{ {
AutoPickTalents(); AutoPickTalents();
AutoLearnSpell(); AutoLearnSpell();
AutoUpgradeEquip(); AutoUpgradeEquip();
AutoTeleportForLevel(); AutoTeleportForLevel();
return true; return true;
} }
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel() void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
{ {
if (!sPlayerbotAIConfig->autoTeleportForLevel || !sRandomPlayerbotMgr->IsRandomBot(bot)) if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot))
{ {
return; return;
} }
@@ -27,13 +28,13 @@ void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
{ {
return; return;
} }
sRandomPlayerbotMgr->RandomTeleportForLevel(bot); sRandomPlayerbotMgr.RandomTeleportForLevel(bot);
return; return;
} }
void AutoMaintenanceOnLevelupAction::AutoPickTalents() void AutoMaintenanceOnLevelupAction::AutoPickTalents()
{ {
if (!sPlayerbotAIConfig->autoPickTalents || !sRandomPlayerbotMgr->IsRandomBot(bot)) if (!sPlayerbotAIConfig.autoPickTalents || !sRandomPlayerbotMgr.IsRandomBot(bot))
return; return;
if (bot->GetFreeTalentPoints() <= 0) if (bot->GetFreeTalentPoints() <= 0)
@@ -65,10 +66,10 @@ void AutoMaintenanceOnLevelupAction::AutoLearnSpell()
void AutoMaintenanceOnLevelupAction::LearnSpells(std::ostringstream* out) void AutoMaintenanceOnLevelupAction::LearnSpells(std::ostringstream* out)
{ {
BroadcastHelper::BroadcastLevelup(botAI, bot); BroadcastHelper::BroadcastLevelup(botAI, bot);
if (sPlayerbotAIConfig->autoLearnTrainerSpells && sRandomPlayerbotMgr->IsRandomBot(bot)) if (sPlayerbotAIConfig.autoLearnTrainerSpells && sRandomPlayerbotMgr.IsRandomBot(bot))
LearnTrainerSpells(out); LearnTrainerSpells(out);
if (sPlayerbotAIConfig->autoLearnQuestSpells && sRandomPlayerbotMgr->IsRandomBot(bot)) if (sPlayerbotAIConfig.autoLearnQuestSpells && sRandomPlayerbotMgr.IsRandomBot(bot))
LearnQuestSpells(out); LearnQuestSpells(out);
} }
@@ -166,7 +167,7 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip() void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
{ {
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot)) if (!sPlayerbotAIConfig.autoUpgradeEquip || !sRandomPlayerbotMgr.IsRandomBot(bot))
return; return;
PlayerbotFactory factory(bot, bot->GetLevel()); PlayerbotFactory factory(bot, bot->GetLevel());
@@ -180,9 +181,9 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
factory.InitConsumables(); factory.InitConsumables();
factory.InitPotions(); factory.InitPotions();
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel)
{ {
if (sPlayerbotAIConfig->incrementalGearInit) if (sPlayerbotAIConfig.incrementalGearInit)
factory.InitEquipment(true); factory.InitEquipment(true);
} }
} }

View File

@@ -13,9 +13,8 @@
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PositionValue.h" #include "PositionValue.h"
#include "UpdateTime.h"
bool BGJoinAction::Execute(Event event) bool BGJoinAction::Execute(Event /*event*/)
{ {
uint32 queueType = AI_VALUE(uint32, "bg type"); uint32 queueType = AI_VALUE(uint32, "bg type");
if (!queueType) // force join to fill bg if (!queueType) // force join to fill bg
@@ -25,8 +24,6 @@ bool BGJoinAction::Execute(Event event)
BattlegroundQueueTypeId queueTypeId = (BattlegroundQueueTypeId)bgList[urand(0, bgList.size() - 1)]; BattlegroundQueueTypeId queueTypeId = (BattlegroundQueueTypeId)bgList[urand(0, bgList.size() - 1)];
BattlegroundTypeId bgTypeId = BattlegroundMgr::BGTemplateId(queueTypeId); BattlegroundTypeId bgTypeId = BattlegroundMgr::BGTemplateId(queueTypeId);
BattlegroundBracketId bracketId;
bool isArena = false;
bool isRated = false; bool isRated = false;
Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId);
@@ -38,12 +35,8 @@ bool BGJoinAction::Execute(Event event)
if (!pvpDiff) if (!pvpDiff)
return false; return false;
bracketId = pvpDiff->GetBracketId();
if (ArenaType type = ArenaType(BattlegroundMgr::BGArenaType(queueTypeId))) if (ArenaType type = ArenaType(BattlegroundMgr::BGArenaType(queueTypeId)))
{ {
isArena = true;
std::vector<uint32>::iterator i = find(ratedList.begin(), ratedList.end(), queueTypeId); std::vector<uint32>::iterator i = find(ratedList.begin(), ratedList.end(), queueTypeId);
if (i != ratedList.end()) if (i != ratedList.end())
isRated = true; isRated = true;
@@ -89,7 +82,7 @@ bool BGJoinAction::gatherArenaTeam(ArenaType type)
// continue; // continue;
if (offline) if (offline)
sRandomPlayerbotMgr->AddPlayerBot(itr->Guid, 0); sRandomPlayerbotMgr.AddPlayerBot(itr->Guid, 0);
if (member) if (member)
{ {
@@ -100,7 +93,7 @@ bool BGJoinAction::gatherArenaTeam(ArenaType type)
if (member->GetGroup() && memberBotAI->HasRealPlayerMaster()) if (member->GetGroup() && memberBotAI->HasRealPlayerMaster())
continue; continue;
if (!sPlayerbotAIConfig->IsInRandomAccountList(member->GetSession()->GetAccountId())) if (!sPlayerbotAIConfig.IsInRandomAccountList(member->GetSession()->GetAccountId()))
continue; continue;
if (member->IsInCombat()) if (member->IsInCombat())
@@ -250,13 +243,13 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun
TeamSize = (uint32)type; TeamSize = (uint32)type;
// Check if bots should join Rated Arena (Only captains can queue) // Check if bots should join Rated Arena (Only captains can queue)
uint32 ratedArenaBotCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount; uint32 ratedArenaBotCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount;
uint32 ratedArenaPlayerCount = uint32 ratedArenaPlayerCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount;
uint32 ratedArenaInstanceCount = uint32 ratedArenaInstanceCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaInstanceCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaInstanceCount;
uint32 activeRatedArenaQueue = uint32 activeRatedArenaQueue =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].activeRatedArenaQueue; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].activeRatedArenaQueue;
bool isRated = (ratedArenaBotCount + ratedArenaPlayerCount) < bool isRated = (ratedArenaBotCount + ratedArenaPlayerCount) <
(BracketSize * (activeRatedArenaQueue + ratedArenaInstanceCount)); (BracketSize * (activeRatedArenaQueue + ratedArenaInstanceCount));
@@ -265,7 +258,7 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun
{ {
if (sArenaTeamMgr->GetArenaTeamByCaptain(bot->GetGUID(), type)) if (sArenaTeamMgr->GetArenaTeamByCaptain(bot->GetGUID(), type))
{ {
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount += TeamSize; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount += TeamSize;
ratedList.push_back(queueTypeId); ratedList.push_back(queueTypeId);
return true; return true;
} }
@@ -274,13 +267,13 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun
// Check if bots should join Skirmish Arena // Check if bots should join Skirmish Arena
// We have extra bots queue because same faction can vs each other but can't be in the same group. // We have extra bots queue because same faction can vs each other but can't be in the same group.
uint32 skirmishArenaBotCount = uint32 skirmishArenaBotCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount;
uint32 skirmishArenaPlayerCount = uint32 skirmishArenaPlayerCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaPlayerCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaPlayerCount;
uint32 skirmishArenaInstanceCount = uint32 skirmishArenaInstanceCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaInstanceCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaInstanceCount;
uint32 activeSkirmishArenaQueue = uint32 activeSkirmishArenaQueue =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].activeSkirmishArenaQueue; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].activeSkirmishArenaQueue;
uint32 maxRequiredSkirmishBots = BracketSize * (activeSkirmishArenaQueue + skirmishArenaInstanceCount); uint32 maxRequiredSkirmishBots = BracketSize * (activeSkirmishArenaQueue + skirmishArenaInstanceCount);
if (maxRequiredSkirmishBots != 0) if (maxRequiredSkirmishBots != 0)
maxRequiredSkirmishBots = maxRequiredSkirmishBots + TeamSize; maxRequiredSkirmishBots = maxRequiredSkirmishBots + TeamSize;
@@ -294,12 +287,12 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun
} }
// Check if bots should join Battleground // Check if bots should join Battleground
uint32 bgAllianceBotCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount; uint32 bgAllianceBotCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount;
uint32 bgAlliancePlayerCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgAlliancePlayerCount; uint32 bgAlliancePlayerCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgAlliancePlayerCount;
uint32 bgHordeBotCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgHordeBotCount; uint32 bgHordeBotCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgHordeBotCount;
uint32 bgHordePlayerCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount; uint32 bgHordePlayerCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount;
uint32 activeBgQueue = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].activeBgQueue; uint32 activeBgQueue = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].activeBgQueue;
uint32 bgInstanceCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgInstanceCount; uint32 bgInstanceCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgInstanceCount;
if (teamId == TEAM_ALLIANCE) if (teamId == TEAM_ALLIANCE)
{ {
@@ -318,7 +311,7 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun
bool BGJoinAction::isUseful() bool BGJoinAction::isUseful()
{ {
// do not try if BG bots disabled // do not try if BG bots disabled
if (!sPlayerbotAIConfig->randomBotJoinBG) if (!sPlayerbotAIConfig.randomBotJoinBG)
return false; return false;
// can't queue while in BG/Arena // can't queue while in BG/Arena
@@ -409,8 +402,6 @@ bool BGJoinAction::JoinQueue(uint32 type)
bracketId = pvpDiff->GetBracketId(); bracketId = pvpDiff->GetBracketId();
uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2;
uint32 TeamSize = bg->GetMaxPlayersPerTeam();
TeamId teamId = bot->GetTeamId(); TeamId teamId = bot->GetTeamId();
// check if already in queue // check if already in queue
@@ -440,7 +431,7 @@ bool BGJoinAction::JoinQueue(uint32 type)
// get battlemaster // get battlemaster
// Unit* unit = botAI->GetUnit(AI_VALUE2(CreatureData const*, "bg master", bgTypeId)); // Unit* unit = botAI->GetUnit(AI_VALUE2(CreatureData const*, "bg master", bgTypeId));
Unit* unit = botAI->GetUnit(sRandomPlayerbotMgr->GetBattleMasterGUID(bot, bgTypeId)); Unit* unit = botAI->GetUnit(sRandomPlayerbotMgr.GetBattleMasterGUID(bot, bgTypeId));
if (!unit && isArena) if (!unit && isArena)
{ {
botAI->GetAiObjectContext()->GetValue<uint32>("bg type")->Set(0); botAI->GetAiObjectContext()->GetValue<uint32>("bg type")->Set(0);
@@ -450,7 +441,7 @@ bool BGJoinAction::JoinQueue(uint32 type)
// This breaks groups as refresh includes a remove from group function call. // This breaks groups as refresh includes a remove from group function call.
// refresh food/regs // refresh food/regs
// sRandomPlayerbotMgr->Refresh(bot); // sRandomPlayerbotMgr.Refresh(bot);
bool joinAsGroup = bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() == bot->GetGUID(); bool joinAsGroup = bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() == bot->GetGUID();
@@ -487,8 +478,6 @@ bool BGJoinAction::JoinQueue(uint32 type)
if (isArena) if (isArena)
{ {
isArena = true; isArena = true;
BracketSize = type * 2;
TeamSize = type;
isRated = botAI->GetAiObjectContext()->GetValue<uint32>("arena type")->Get(); isRated = botAI->GetAiObjectContext()->GetValue<uint32>("arena type")->Get();
if (joinAsGroup) if (joinAsGroup)
@@ -523,23 +512,23 @@ bool BGJoinAction::JoinQueue(uint32 type)
{ {
if (!isRated) if (!isRated)
{ {
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++;
} }
} }
else if (!joinAsGroup) else if (!joinAsGroup)
{ {
if (teamId == TEAM_ALLIANCE) if (teamId == TEAM_ALLIANCE)
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount++; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount++;
else else
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++;
} }
else else
{ {
if (teamId == TEAM_ALLIANCE) if (teamId == TEAM_ALLIANCE)
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount += sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount +=
bot->GetGroup()->GetMembersCount(); bot->GetGroup()->GetMembersCount();
else else
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgHordeBotCount += sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgHordeBotCount +=
bot->GetGroup()->GetMembersCount(); bot->GetGroup()->GetMembersCount();
} }
@@ -588,13 +577,13 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg
TeamSize = (uint32)type; TeamSize = (uint32)type;
// Check if bots should join Rated Arena (Only captains can queue) // Check if bots should join Rated Arena (Only captains can queue)
uint32 ratedArenaBotCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount; uint32 ratedArenaBotCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount;
uint32 ratedArenaPlayerCount = uint32 ratedArenaPlayerCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount;
uint32 ratedArenaInstanceCount = uint32 ratedArenaInstanceCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaInstanceCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaInstanceCount;
uint32 activeRatedArenaQueue = uint32 activeRatedArenaQueue =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].activeRatedArenaQueue; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].activeRatedArenaQueue;
bool isRated = (ratedArenaBotCount + ratedArenaPlayerCount) < bool isRated = (ratedArenaBotCount + ratedArenaPlayerCount) <
(BracketSize * (activeRatedArenaQueue + ratedArenaInstanceCount)); (BracketSize * (activeRatedArenaQueue + ratedArenaInstanceCount));
@@ -603,7 +592,7 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg
{ {
if (sArenaTeamMgr->GetArenaTeamByCaptain(bot->GetGUID(), type)) if (sArenaTeamMgr->GetArenaTeamByCaptain(bot->GetGUID(), type))
{ {
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount += TeamSize; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount += TeamSize;
ratedList.push_back(queueTypeId); ratedList.push_back(queueTypeId);
return true; return true;
} }
@@ -612,13 +601,13 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg
// Check if bots should join Skirmish Arena // Check if bots should join Skirmish Arena
// We have extra bots queue because same faction can vs each other but can't be in the same group. // We have extra bots queue because same faction can vs each other but can't be in the same group.
uint32 skirmishArenaBotCount = uint32 skirmishArenaBotCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount;
uint32 skirmishArenaPlayerCount = uint32 skirmishArenaPlayerCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaPlayerCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaPlayerCount;
uint32 skirmishArenaInstanceCount = uint32 skirmishArenaInstanceCount =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaInstanceCount; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaInstanceCount;
uint32 activeSkirmishArenaQueue = uint32 activeSkirmishArenaQueue =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].activeSkirmishArenaQueue; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].activeSkirmishArenaQueue;
uint32 maxRequiredSkirmishBots = BracketSize * (activeSkirmishArenaQueue + skirmishArenaInstanceCount); uint32 maxRequiredSkirmishBots = BracketSize * (activeSkirmishArenaQueue + skirmishArenaInstanceCount);
if (maxRequiredSkirmishBots != 0) if (maxRequiredSkirmishBots != 0)
maxRequiredSkirmishBots = maxRequiredSkirmishBots + TeamSize; maxRequiredSkirmishBots = maxRequiredSkirmishBots + TeamSize;
@@ -632,12 +621,12 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg
} }
// Check if bots should join Battleground // Check if bots should join Battleground
uint32 bgAllianceBotCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount; uint32 bgAllianceBotCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgAllianceBotCount;
uint32 bgAlliancePlayerCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgAlliancePlayerCount; uint32 bgAlliancePlayerCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgAlliancePlayerCount;
uint32 bgHordeBotCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgHordeBotCount; uint32 bgHordeBotCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgHordeBotCount;
uint32 bgHordePlayerCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount; uint32 bgHordePlayerCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount;
uint32 activeBgQueue = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].activeBgQueue; uint32 activeBgQueue = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].activeBgQueue;
uint32 bgInstanceCount = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].bgInstanceCount; uint32 bgInstanceCount = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].bgInstanceCount;
if (teamId == TEAM_ALLIANCE) if (teamId == TEAM_ALLIANCE)
{ {
@@ -653,7 +642,7 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg
return false; return false;
} }
bool BGLeaveAction::Execute(Event event) bool BGLeaveAction::Execute(Event /*event*/)
{ {
if (!(bot->InBattlegroundQueue() || bot->InBattleground())) if (!(bot->InBattlegroundQueue() || bot->InBattleground()))
return false; return false;
@@ -670,7 +659,7 @@ bool BGLeaveAction::Execute(Event event)
uint16 unk = 0x1F90; uint16 unk = 0x1F90;
uint8 unk2 = 0x0; uint8 unk2 = 0x0;
bool isArena = false; bool isArena = false;
bool IsRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot); bool IsRandomBot = sRandomPlayerbotMgr.IsRandomBot(bot);
ArenaType arenaType = ArenaType(BattlegroundMgr::BGArenaType(queueTypeId)); ArenaType arenaType = ArenaType(BattlegroundMgr::BGArenaType(queueTypeId));
if (arenaType) if (arenaType)
@@ -709,7 +698,7 @@ bool BGStatusAction::LeaveBG(PlayerbotAI* botAI)
if (!bg) if (!bg)
return false; return false;
bool isArena = bg->isArena(); bool isArena = bg->isArena();
bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot); bool isRandomBot = sRandomPlayerbotMgr.IsRandomBot(bot);
if (isRandomBot) if (isRandomBot)
botAI->SetMaster(nullptr); botAI->SetMaster(nullptr);
@@ -805,7 +794,7 @@ bool BGStatusAction::Execute(Event event)
break; break;
} }
bool IsRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot); bool IsRandomBot = sRandomPlayerbotMgr.IsRandomBot(bot);
BattlegroundQueueTypeId queueTypeId = bot->GetBattlegroundQueueTypeId(QueueSlot); BattlegroundQueueTypeId queueTypeId = bot->GetBattlegroundQueueTypeId(QueueSlot);
BattlegroundTypeId _bgTypeId = BattlegroundMgr::BGTemplateId(queueTypeId); BattlegroundTypeId _bgTypeId = BattlegroundMgr::BGTemplateId(queueTypeId);
if (!queueTypeId) if (!queueTypeId)
@@ -958,10 +947,10 @@ bool BGStatusAction::Execute(Event event)
//TeamId teamId = bot->GetTeamId(); //not used, line marked for removal. //TeamId teamId = bot->GetTeamId(); //not used, line marked for removal.
bool realPlayers = false; bool realPlayers = false;
if (isRated) if (isRated)
realPlayers = sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount > 0; realPlayers = sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount > 0;
else else
realPlayers = realPlayers =
sRandomPlayerbotMgr->BattlegroundData[queueTypeId][bracketId].skirmishArenaPlayerCount > 0; sRandomPlayerbotMgr.BattlegroundData[queueTypeId][bracketId].skirmishArenaPlayerCount > 0;
if (realPlayers) if (realPlayers)
return false; return false;
@@ -1064,7 +1053,7 @@ bool BGStatusAction::Execute(Event event)
return true; return true;
} }
bool BGStatusCheckAction::Execute(Event event) bool BGStatusCheckAction::Execute(Event /*event*/)
{ {
if (bot->IsBeingTeleported()) if (bot->IsBeingTeleported())
return false; return false;
@@ -1080,7 +1069,7 @@ bool BGStatusCheckAction::Execute(Event event)
bool BGStatusCheckAction::isUseful() { return bot->InBattlegroundQueue(); } bool BGStatusCheckAction::isUseful() { return bot->InBattlegroundQueue(); }
bool BGStrategyCheckAction::Execute(Event event) bool BGStrategyCheckAction::Execute(Event /*event*/)
{ {
bool inside_bg = bot->InBattleground() && bot->GetBattleground(); bool inside_bg = bot->InBattleground() && bot->GetBattleground();
; ;

View File

@@ -1276,7 +1276,7 @@ static std::pair<uint32, uint32> IC_AttackObjectives[] = {
// useful commands for fixing BG bugs and checking waypoints/paths // useful commands for fixing BG bugs and checking waypoints/paths
bool BGTactics::HandleConsoleCommand(ChatHandler* handler, char const* args) bool BGTactics::HandleConsoleCommand(ChatHandler* handler, char const* args)
{ {
if (!sPlayerbotAIConfig->enabled) if (!sPlayerbotAIConfig.enabled)
{ {
handler->PSendSysMessage("|cffff0000Playerbot system is currently disabled!"); handler->PSendSysMessage("|cffff0000Playerbot system is currently disabled!");
return true; return true;
@@ -1557,7 +1557,7 @@ bool BGTactics::eyJumpDown()
// //
// actual bg tactics below // actual bg tactics below
// //
bool BGTactics::Execute(Event event) bool BGTactics::Execute(Event /*event*/)
{ {
Battleground* bg = bot->GetBattleground(); Battleground* bg = bot->GetBattleground();
if (!bg) if (!bg)
@@ -2219,7 +2219,7 @@ bool BGTactics::selectObjective(bool reset)
if (urand(0, 99) < 20 && teamFC) if (urand(0, 99) < 20 && teamFC)
{ {
target.Relocate(teamFC->GetPositionX(), teamFC->GetPositionY(), teamFC->GetPositionZ()); target.Relocate(teamFC->GetPositionX(), teamFC->GetPositionY(), teamFC->GetPositionZ());
if (sServerFacade->GetDistance2d(bot, teamFC) < 33.0f) if (ServerFacade::instance().GetDistance2d(bot, teamFC) < 33.0f)
Follow(teamFC); Follow(teamFC);
} }
else else
@@ -2227,8 +2227,8 @@ bool BGTactics::selectObjective(bool reset)
} }
// Graveyard Camping if in lead // Graveyard Camping if in lead
else if (!hasFlag && role < 8 && else if (!hasFlag && role < 8 &&
(team == TEAM_ALLIANCE && allianceScore == 2 && hordeScore == 0) || ((team == TEAM_ALLIANCE && allianceScore == 2 && hordeScore == 0) ||
(team == TEAM_HORDE && hordeScore == 2 && allianceScore == 0)) (team == TEAM_HORDE && hordeScore == 2 && allianceScore == 0)))
{ {
if (team == TEAM_ALLIANCE) if (team == TEAM_ALLIANCE)
SetSafePos(WS_GY_CAMPING_HORDE, 10.0f); SetSafePos(WS_GY_CAMPING_HORDE, 10.0f);
@@ -2263,7 +2263,7 @@ bool BGTactics::selectObjective(bool reset)
if (urand(0, 99) < 70) if (urand(0, 99) < 70)
{ {
target.Relocate(teamFC->GetPositionX(), teamFC->GetPositionY(), teamFC->GetPositionZ()); target.Relocate(teamFC->GetPositionX(), teamFC->GetPositionY(), teamFC->GetPositionZ());
if (sServerFacade->GetDistance2d(bot, teamFC) < 33.0f) if (ServerFacade::instance().GetDistance2d(bot, teamFC) < 33.0f)
Follow(teamFC); Follow(teamFC);
} }
} }
@@ -2284,7 +2284,7 @@ bool BGTactics::selectObjective(bool reset)
{ {
// Assist own FC if not pursuing enemy FC // Assist own FC if not pursuing enemy FC
target.Relocate(teamFC->GetPositionX(), teamFC->GetPositionY(), teamFC->GetPositionZ()); target.Relocate(teamFC->GetPositionX(), teamFC->GetPositionY(), teamFC->GetPositionZ());
if (sServerFacade->GetDistance2d(bot, teamFC) < 33.0f) if (ServerFacade::instance().GetDistance2d(bot, teamFC) < 33.0f)
Follow(teamFC); Follow(teamFC);
} }
else if (urand(0, 99) < 5) else if (urand(0, 99) < 5)
@@ -2497,7 +2497,6 @@ bool BGTactics::selectObjective(bool reset)
EYBotStrategy strategyHorde = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_HORDE)); EYBotStrategy strategyHorde = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_HORDE));
EYBotStrategy strategyAlliance = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_ALLIANCE)); EYBotStrategy strategyAlliance = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_ALLIANCE));
EYBotStrategy strategy = (team == TEAM_ALLIANCE) ? strategyAlliance : strategyHorde; EYBotStrategy strategy = (team == TEAM_ALLIANCE) ? strategyAlliance : strategyHorde;
EYBotStrategy enemyStrategy = (team == TEAM_ALLIANCE) ? strategyHorde : strategyAlliance;
auto IsOwned = [&](uint32 nodeId) -> bool auto IsOwned = [&](uint32 nodeId) -> bool
{ return eyeOfTheStormBG->GetCapturePointInfo(nodeId)._ownerTeamId == team; }; { return eyeOfTheStormBG->GetCapturePointInfo(nodeId)._ownerTeamId == team; };
@@ -3197,11 +3196,11 @@ bool BGTactics::moveToObjective(bool ignoreDist)
return true; return true;
} }
if (!ignoreDist && sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, pos.x, pos.y), 100.0f)) if (!ignoreDist && ServerFacade::instance().IsDistanceGreaterThan(ServerFacade::instance().GetDistance2d(bot, pos.x, pos.y), 100.0f))
{ {
// std::ostringstream out; // std::ostringstream out;
// out << "It is too far away! " << pos.x << ", " << pos.y << ", Distance: " << // out << "It is too far away! " << pos.x << ", " << pos.y << ", Distance: " <<
// sServerFacade->GetDistance2d(bot, pos.x, pos.y); bot->Say(out.str(), LANG_UNIVERSAL); // ServerFacade::instance().GetDistance2d(bot, pos.x, pos.y); bot->Say(out.str(), LANG_UNIVERSAL);
return false; return false;
} }
@@ -3213,7 +3212,7 @@ bool BGTactics::moveToObjective(bool ignoreDist)
} }
// std::ostringstream out; out << "Moving to objective " << pos.x << ", " << pos.y << ", Distance: " << // std::ostringstream out; out << "Moving to objective " << pos.x << ", " << pos.y << ", Distance: " <<
// sServerFacade->GetDistance2d(bot, pos.x, pos.y); bot->Say(out.str(), LANG_UNIVERSAL); // ServerFacade::instance().GetDistance2d(bot, pos.x, pos.y); bot->Say(out.str(), LANG_UNIVERSAL);
// dont increase from 1.5 will cause bugs with horde capping AV towers // dont increase from 1.5 will cause bugs with horde capping AV towers
return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z, 1.5f); return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z, 1.5f);
@@ -3231,7 +3230,6 @@ bool BGTactics::selectObjectiveWp(std::vector<BattleBotPath*> const& vPaths)
if (bgType == BATTLEGROUND_RB) if (bgType == BATTLEGROUND_RB)
bgType = bg->GetBgTypeID(true); bgType = bg->GetBgTypeID(true);
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()["bg objective"]; PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()["bg objective"];
if (!pos.isSet()) if (!pos.isSet())
return false; return false;
@@ -3433,7 +3431,7 @@ bool BGTactics::moveToObjectiveWp(BattleBotPath* const& currentPath, uint32 curr
// out << "WP: "; // out << "WP: ";
// reverse ? out << currPoint << " <<< -> " << nPoint : out << currPoint << ">>> ->" << nPoint; // reverse ? out << currPoint << " <<< -> " << nPoint : out << currPoint << ">>> ->" << nPoint;
// out << ", " << nextPoint.x << ", " << nextPoint.y << " Path Size: " << currentPath->size() << ", Dist: " << // out << ", " << nextPoint.x << ", " << nextPoint.y << " Path Size: " << currentPath->size() << ", Dist: " <<
// sServerFacade->GetDistance2d(bot, nextPoint.x, nextPoint.y); bot->Say(out.str(), LANG_UNIVERSAL); // ServerFacade::instance().GetDistance2d(bot, nextPoint.x, nextPoint.y); bot->Say(out.str(), LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), nextPoint.x + frand(-2, 2), nextPoint.y + frand(-2, 2), nextPoint.z); return MoveTo(bot->GetMapId(), nextPoint.x + frand(-2, 2), nextPoint.y + frand(-2, 2), nextPoint.z);
} }
@@ -4039,9 +4037,9 @@ bool BGTactics::useBuff()
if (closeObjects.empty()) if (closeObjects.empty())
return false; return false;
bool needRegen = bot->GetHealthPct() < sPlayerbotAIConfig->mediumHealth || bool needRegen = bot->GetHealthPct() < sPlayerbotAIConfig.mediumHealth ||
(AI_VALUE2(bool, "has mana", "self target") && (AI_VALUE2(bool, "has mana", "self target") &&
AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig->mediumMana); AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.mediumMana);
bool needSpeed = (bgType != BATTLEGROUND_WS || bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bool needSpeed = (bgType != BATTLEGROUND_WS || bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) ||
bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG) || bot->HasAura(BG_EY_NETHERSTORM_FLAG_SPELL)) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG) || bot->HasAura(BG_EY_NETHERSTORM_FLAG_SPELL)) ||
!(teamFlagTaken() || flagTaken()); !(teamFlagTaken() || flagTaken());
@@ -4057,7 +4055,7 @@ bool BGTactics::useBuff()
continue; continue;
// use speed buff only if close // use speed buff only if close
if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, go), if (ServerFacade::instance().IsDistanceGreaterThan(ServerFacade::instance().GetDistance2d(bot, go),
go->GetEntry() == Buff_Entries[0] ? 20.0f : 50.0f)) go->GetEntry() == Buff_Entries[0] ? 20.0f : 50.0f))
continue; continue;
@@ -4107,7 +4105,7 @@ uint32 BGTactics::getPlayersInArea(TeamId teamId, Position point, float range, b
if (!combat && player->IsInCombat()) if (!combat && player->IsInCombat())
continue; continue;
if (sServerFacade->GetDistance2d(player, point.GetPositionX(), point.GetPositionY()) < range) if (ServerFacade::instance().GetDistance2d(player, point.GetPositionX(), point.GetPositionY()) < range)
++defCount; ++defCount;
} }
} }
@@ -4191,9 +4189,9 @@ bool BGTactics::IsLockedInsideKeep()
// get closest portal // get closest portal
if (bot->GetTeamId() == TEAM_ALLIANCE && go->GetEntry() == GO_TELEPORTER_4) if (bot->GetTeamId() == TEAM_ALLIANCE && go->GetEntry() == GO_TELEPORTER_4)
{ {
float tempDist = sServerFacade->GetDistance2d(bot, go->GetPositionX(), go->GetPositionY()); float tempDist = ServerFacade::instance().GetDistance2d(bot, go->GetPositionX(), go->GetPositionY());
if (sServerFacade->IsDistanceLessThan(tempDist, closestDistance)) if (ServerFacade::instance().IsDistanceLessThan(tempDist, closestDistance))
{ {
closestDistance = tempDist; closestDistance = tempDist;
closestPortal = go; closestPortal = go;
@@ -4204,9 +4202,9 @@ bool BGTactics::IsLockedInsideKeep()
// get closest portal // get closest portal
if (bot->GetTeamId() == TEAM_HORDE && go->GetEntry() == GO_TELEPORTER_2) if (bot->GetTeamId() == TEAM_HORDE && go->GetEntry() == GO_TELEPORTER_2)
{ {
float tempDist = sServerFacade->GetDistance2d(bot, go->GetPositionX(), go->GetPositionY()); float tempDist = ServerFacade::instance().GetDistance2d(bot, go->GetPositionX(), go->GetPositionY());
if (sServerFacade->IsDistanceLessThan(tempDist, closestDistance)) if (ServerFacade::instance().IsDistanceLessThan(tempDist, closestDistance))
{ {
closestDistance = tempDist; closestDistance = tempDist;
closestPortal = go; closestPortal = go;
@@ -4249,11 +4247,11 @@ bool BGTactics::IsLockedInsideKeep()
return false; return false;
} }
bool ArenaTactics::Execute(Event event) bool ArenaTactics::Execute(Event /*event*/)
{ {
if (!bot->InBattleground()) if (!bot->InBattleground())
{ {
bool IsRandomBot = sRandomPlayerbotMgr->IsRandomBot(bot->GetGUID().GetCounter()); bool IsRandomBot = sRandomPlayerbotMgr.IsRandomBot(bot->GetGUID().GetCounter());
botAI->ChangeStrategy("-arena", BOT_STATE_COMBAT); botAI->ChangeStrategy("-arena", BOT_STATE_COMBAT);
botAI->ChangeStrategy("-arena", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("-arena", BOT_STATE_NON_COMBAT);
botAI->ResetStrategies(!IsRandomBot); botAI->ResetStrategies(!IsRandomBot);

View File

@@ -18,7 +18,7 @@ bool BossFireResistanceAction::isUseful()
return bossFireResistanceTrigger.IsActive(); return bossFireResistanceTrigger.IsActive();
} }
bool BossFireResistanceAction::Execute(Event event) bool BossFireResistanceAction::Execute(Event /*event*/)
{ {
PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI); PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
@@ -32,7 +32,7 @@ bool BossFrostResistanceAction::isUseful()
return bossFrostResistanceTrigger.IsActive(); return bossFrostResistanceTrigger.IsActive();
} }
bool BossFrostResistanceAction::Execute(Event event) bool BossFrostResistanceAction::Execute(Event /*event*/)
{ {
PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI); PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
@@ -46,7 +46,7 @@ bool BossNatureResistanceAction::isUseful()
return bossNatureResistanceTrigger.IsActive(); return bossNatureResistanceTrigger.IsActive();
} }
bool BossNatureResistanceAction::Execute(Event event) bool BossNatureResistanceAction::Execute(Event /*event*/)
{ {
HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI); HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
@@ -60,7 +60,7 @@ bool BossShadowResistanceAction::isUseful()
return bossShadowResistanceTrigger.IsActive(); return bossShadowResistanceTrigger.IsActive();
} }
bool BossShadowResistanceAction::Execute(Event event) bool BossShadowResistanceAction::Execute(Event /*event*/)
{ {
PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI); PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);

View File

@@ -175,7 +175,7 @@ bool BuyAction::Execute(Event event)
if (needMoneyFor == NeedMoneyFor::gear) if (needMoneyFor == NeedMoneyFor::gear)
{ {
botAI->DoSpecificAction("equip upgrades"); botAI->DoSpecificAction("equip upgrades packet action");
} }
} }
} }
@@ -206,7 +206,7 @@ bool BuyAction::Execute(Event event)
if (usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_EQUIP || if (usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_EQUIP ||
usage == ITEM_USAGE_BAD_EQUIP || usage == ITEM_USAGE_BROKEN_EQUIP) usage == ITEM_USAGE_BAD_EQUIP || usage == ITEM_USAGE_BROKEN_EQUIP)
{ {
botAI->DoSpecificAction("equip upgrades"); botAI->DoSpecificAction("equip upgrades packet action");
break; break;
} }
} }

View File

@@ -7,12 +7,14 @@
#include "Player.h" #include "Player.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
bool CancelChannelAction::Execute(Event event) bool CancelChannelAction::Execute(Event /*event*/)
{ {
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
{ {
bot->InterruptSpell(CURRENT_CHANNELED_SPELL); bot->InterruptSpell(CURRENT_CHANNELED_SPELL);
return true; return true;
} }
return false; return false;
} }

View File

@@ -130,10 +130,10 @@ bool CastCustomSpellAction::Execute(Event event)
return false; return false;
} }
if (target != bot && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target, sPlayerbotAIConfig->sightDistance)) if (target != bot && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target, sPlayerbotAIConfig.sightDistance))
{ {
sServerFacade->SetFacingTo(bot, target); ServerFacade::instance().SetFacingTo(bot, target);
botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); botAI->SetNextCheckDelay(sPlayerbotAIConfig.reactDelay);
msg << "cast " << text; msg << "cast " << text;
botAI->HandleCommand(CHAT_MSG_WHISPER, msg.str(), master); botAI->HandleCommand(CHAT_MSG_WHISPER, msg.str(), master);
@@ -286,7 +286,7 @@ bool CastRandomSpellAction::Execute(Event event)
if (isCast) if (isCast)
{ {
if (MultiCast && ((wo && bot->HasInArc(CAST_ANGLE_IN_FRONT, wo, sPlayerbotAIConfig->sightDistance)))) if (MultiCast && ((wo && bot->HasInArc(CAST_ANGLE_IN_FRONT, wo, sPlayerbotAIConfig.sightDistance))))
{ {
std::ostringstream cmd; std::ostringstream cmd;
cmd << "castnc " << chat->FormatWorldobject(wo) + " " << spellId << " " << 19; cmd << "castnc " << chat->FormatWorldobject(wo) + " " << spellId << " " << 19;
@@ -334,7 +334,7 @@ bool CastRandomSpellAction::castSpell(uint32 spellId, WorldObject* wo)
return botAI->CastSpell(spellId, wo->GetPositionX(), wo->GetPositionY(), wo->GetPositionZ()); return botAI->CastSpell(spellId, wo->GetPositionX(), wo->GetPositionY(), wo->GetPositionZ());
} }
bool DisEnchantRandomItemAction::Execute(Event event) bool DisEnchantRandomItemAction::Execute(Event /*event*/)
{ {
std::vector<Item*> items = std::vector<Item*> items =
AI_VALUE2(std::vector<Item*>, "inventory items", "usage " + std::to_string(ITEM_USAGE_DISENCHANT)); AI_VALUE2(std::vector<Item*>, "inventory items", "usage " + std::to_string(ITEM_USAGE_DISENCHANT));

View File

@@ -24,7 +24,7 @@ bool ChangeCombatStrategyAction::Execute(Event event)
case '+': case '+':
case '-': case '-':
case '~': case '~':
sPlayerbotRepository->Save(botAI); PlayerbotRepository::instance().Save(botAI);
break; break;
case '?': case '?':
break; break;
@@ -40,7 +40,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
std::string const text = event.getParam(); std::string const text = event.getParam();
uint32 account = bot->GetSession()->GetAccountId(); uint32 account = bot->GetSession()->GetAccountId();
if (sPlayerbotAIConfig->IsInRandomAccountList(account) && botAI->GetMaster() && if (sPlayerbotAIConfig.IsInRandomAccountList(account) && botAI->GetMaster() &&
botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER) botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER)
{ {
if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos) if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos)
@@ -62,7 +62,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
case '+': case '+':
case '-': case '-':
case '~': case '~':
sPlayerbotRepository->Save(botAI); PlayerbotRepository::instance().Save(botAI);
break; break;
case '?': case '?':
break; break;

View File

@@ -10,9 +10,9 @@
#include "Event.h" #include "Event.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "Log.h" #include "Log.h"
#include "RandomPlayerbotMgr.h"
bool ChangeTalentsAction::Execute(Event event) bool ChangeTalentsAction::Execute(Event event)
{ {
@@ -110,20 +110,20 @@ std::string ChangeTalentsAction::SpecList()
std::ostringstream out; std::ostringstream out;
for (int specNo = 0; specNo < MAX_SPECNO; ++specNo) for (int specNo = 0; specNo < MAX_SPECNO; ++specNo)
{ {
if (sPlayerbotAIConfig->premadeSpecName[cls][specNo].size() == 0) if (sPlayerbotAIConfig.premadeSpecName[cls][specNo].size() == 0)
{ {
break; break;
} }
specFound++; specFound++;
std::ostringstream out; std::ostringstream out;
std::vector<std::vector<uint32>> parsed = sPlayerbotAIConfig->parsedSpecLinkOrder[cls][specNo][80]; std::vector<std::vector<uint32>> parsed = sPlayerbotAIConfig.parsedSpecLinkOrder[cls][specNo][80];
std::unordered_map<int, int> tabCount; std::unordered_map<int, int> tabCount;
tabCount[0] = tabCount[1] = tabCount[2] = 0; tabCount[0] = tabCount[1] = tabCount[2] = 0;
for (auto& item : parsed) for (auto& item : parsed)
{ {
tabCount[item[0]] += item[3]; tabCount[item[0]] += item[3];
} }
out << specFound << ". " << sPlayerbotAIConfig->premadeSpecName[cls][specNo] << " ("; out << specFound << ". " << sPlayerbotAIConfig.premadeSpecName[cls][specNo] << " (";
out << tabCount[0] << "-" << tabCount[1] << "-" << tabCount[2] << ")"; out << tabCount[0] << "-" << tabCount[1] << "-" << tabCount[2] << ")";
botAI->TellMasterNoFacing(out.str()); botAI->TellMasterNoFacing(out.str());
} }
@@ -137,11 +137,11 @@ std::string ChangeTalentsAction::SpecPick(std::string param)
// int specFound = 0; //not used, line marked for removal. // int specFound = 0; //not used, line marked for removal.
for (int specNo = 0; specNo < MAX_SPECNO; ++specNo) for (int specNo = 0; specNo < MAX_SPECNO; ++specNo)
{ {
if (sPlayerbotAIConfig->premadeSpecName[cls][specNo].size() == 0) if (sPlayerbotAIConfig.premadeSpecName[cls][specNo].size() == 0)
{ {
break; break;
} }
if (sPlayerbotAIConfig->premadeSpecName[cls][specNo] == param) if (sPlayerbotAIConfig.premadeSpecName[cls][specNo] == param)
{ {
PlayerbotFactory::InitTalentsBySpecNo(bot, specNo, true); PlayerbotFactory::InitTalentsBySpecNo(bot, specNo, true);
@@ -149,7 +149,7 @@ std::string ChangeTalentsAction::SpecPick(std::string param)
factory.InitGlyphs(false); factory.InitGlyphs(false);
std::ostringstream out; std::ostringstream out;
out << "Picking " << sPlayerbotAIConfig->premadeSpecName[cls][specNo]; out << "Picking " << sPlayerbotAIConfig.premadeSpecName[cls][specNo];
return out.str(); return out.str();
} }
} }
@@ -176,7 +176,7 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(std::string const findName) // std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(std::string const findName)
// { // {
// std::vector<TalentPath*> ret; // std::vector<TalentPath*> ret;
// // for (auto& path : sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath) // // for (auto& path : sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath)
// // { // // {
// // if (findName.empty() || path.name.find(findName) != std::string::npos) // // if (findName.empty() || path.name.find(findName) != std::string::npos)
// // { // // {
@@ -184,14 +184,14 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// // } // // }
// // } // // }
// return std::move(ret); // return ret;
// } // }
// std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(TalentSpec* oldSpec) // std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(TalentSpec* oldSpec)
// { // {
// std::vector<TalentPath*> ret; // std::vector<TalentPath*> ret;
// // for (auto& path : sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath) // // for (auto& path : sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath)
// // { // // {
// // TalentSpec newSpec = *GetBestPremadeSpec(path.id); // // TalentSpec newSpec = *GetBestPremadeSpec(path.id);
// // newSpec.CropTalents(bot->GetLevel()); // // newSpec.CropTalents(bot->GetLevel());
@@ -201,12 +201,12 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// // } // // }
// // } // // }
// return std::move(ret); // return ret;
// } // }
// TalentPath* ChangeTalentsAction::getPremadePath(uint32 id) // TalentPath* ChangeTalentsAction::getPremadePath(uint32 id)
// { // {
// // for (auto& path : sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath) // // for (auto& path : sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath)
// // { // // {
// // if (id == path.id) // // if (id == path.id)
// // { // // {
@@ -214,7 +214,7 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// // } // // }
// // } // // }
// // return &sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath[0]; // // return &sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath[0];
// return nullptr; // return nullptr;
// } // }
@@ -270,9 +270,9 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// return false; // return false;
// } // }
// uint32 specNo = sRandomPlayerbotMgr->GetValue(bot->GetGUID().GetCounter(), "specNo"); // uint32 specNo = sRandomPlayerbotMgr.GetValue(bot->GetGUID().GetCounter(), "specNo");
// uint32 specId = specNo - 1; // uint32 specId = specNo - 1;
// std::string specLink = sRandomPlayerbotMgr->GetData(bot->GetGUID().GetCounter(), "specLink"); // std::string specLink = sRandomPlayerbotMgr.GetData(bot->GetGUID().GetCounter(), "specLink");
// //Continue the current spec // //Continue the current spec
// if (specNo > 0) // if (specNo > 0)
@@ -319,15 +319,15 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// specId = -1; // specId = -1;
// // specLink = ""; // // specLink = "";
// } // }
// else if (paths.size() > 1 && false/*!sPlayerbotAIConfig->autoPickTalents*/ && // else if (paths.size() > 1 && false/*!sPlayerbotAIConfig.autoPickTalents*/ &&
// !sRandomPlayerbotMgr->IsRandomBot(bot)) // !sRandomPlayerbotMgr.IsRandomBot(bot))
// { // {
// *out << "Found multiple specs: "; // *out << "Found multiple specs: ";
// listPremadePaths(paths, out); // listPremadePaths(paths, out);
// } // }
// else // else
// { // {
// specId = PickPremadePath(paths, sRandomPlayerbotMgr->IsRandomBot(bot))->id; // specId = PickPremadePath(paths, sRandomPlayerbotMgr.IsRandomBot(bot))->id;
// TalentSpec newSpec = *GetBestPremadeSpec(specId); // TalentSpec newSpec = *GetBestPremadeSpec(specId);
// specLink = newSpec.GetTalentLink(); // specLink = newSpec.GetTalentLink();
// newSpec.CropTalents(bot->GetLevel()); // newSpec.CropTalents(bot->GetLevel());
@@ -341,12 +341,12 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// } // }
// } // }
// sRandomPlayerbotMgr->SetValue(bot->GetGUID().GetCounter(), "specNo", specId + 1); // sRandomPlayerbotMgr.SetValue(bot->GetGUID().GetCounter(), "specNo", specId + 1);
// if (!specLink.empty() && specId == -1) // if (!specLink.empty() && specId == -1)
// sRandomPlayerbotMgr->SetValue(bot->GetGUID().GetCounter(), "specLink", 1, specLink); // sRandomPlayerbotMgr.SetValue(bot->GetGUID().GetCounter(), "specLink", 1, specLink);
// else // else
// sRandomPlayerbotMgr->SetValue(bot->GetGUID().GetCounter(), "specLink", 0); // sRandomPlayerbotMgr.SetValue(bot->GetGUID().GetCounter(), "specLink", 0);
// return (specNo == 0) ? false : true; // return (specNo == 0) ? false : true;
// } // }
@@ -364,15 +364,15 @@ std::string ChangeTalentsAction::SpecApply(std::string param)
// if (path->talentSpec.size()) // if (path->talentSpec.size())
// return &path->talentSpec.back(); // return &path->talentSpec.back();
// // return &sPlayerbotAIConfig->classSpecs[bot->getClassMask()].baseSpec; // // return &sPlayerbotAIConfig.classSpecs[bot->getClassMask()].baseSpec;
// return nullptr; // return nullptr;
// } // }
bool AutoSetTalentsAction::Execute(Event event) bool AutoSetTalentsAction::Execute(Event /*event*/)
{ {
std::ostringstream out; std::ostringstream out;
if (!sPlayerbotAIConfig->autoPickTalents || !sRandomPlayerbotMgr->IsRandomBot(bot)) if (!PlayerbotAIConfig::instance().autoPickTalents || !RandomPlayerbotMgr::instance().IsRandomBot(bot))
return false; return false;
if (bot->GetFreeTalentPoints() <= 0) if (bot->GetFreeTalentPoints() <= 0)

View File

@@ -42,7 +42,7 @@ void PositionsResetAction::SetStayPosition(float x, float y, float z)
posMap["stay"] = pos; posMap["stay"] = pos;
} }
bool FollowChatShortcutAction::Execute(Event event) bool FollowChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -68,9 +68,7 @@ bool FollowChatShortcutAction::Execute(Event event)
std::string const target = formation->GetTargetName(); std::string const target = formation->GetTargetName();
bool moved = false; bool moved = false;
if (!target.empty()) if (!target.empty())
{
moved = Follow(AI_VALUE(Unit*, target)); moved = Follow(AI_VALUE(Unit*, target));
}
else else
{ {
WorldLocation loc = formation->GetLocation(); WorldLocation loc = formation->GetLocation();
@@ -83,9 +81,7 @@ bool FollowChatShortcutAction::Execute(Event event)
} }
if (Pet* pet = bot->GetPet()) if (Pet* pet = bot->GetPet())
{
botAI->PetFollow(); botAI->PetFollow();
}
if (moved) if (moved)
{ {
@@ -96,7 +92,7 @@ bool FollowChatShortcutAction::Execute(Event event)
/* Default mechanics takes care of this now. /* Default mechanics takes care of this now.
if (bot->GetMapId() != master->GetMapId() || (master && bot->GetDistance(master) > if (bot->GetMapId() != master->GetMapId() || (master && bot->GetDistance(master) >
sPlayerbotAIConfig->sightDistance)) sPlayerbotAIConfig.sightDistance))
{ {
if (bot->isDead()) if (bot->isDead())
{ {
@@ -116,7 +112,7 @@ bool FollowChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool StayChatShortcutAction::Execute(Event event) bool StayChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -133,7 +129,7 @@ bool StayChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool MoveFromGroupChatShortcutAction::Execute(Event event) bool MoveFromGroupChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -148,7 +144,7 @@ bool MoveFromGroupChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool FleeChatShortcutAction::Execute(Event event) bool FleeChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -161,7 +157,7 @@ bool FleeChatShortcutAction::Execute(Event event)
ResetReturnPosition(); ResetReturnPosition();
ResetStayPosition(); ResetStayPosition();
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig->sightDistance) if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance)
{ {
botAI->TellError("I will not flee with you - too far away"); botAI->TellError("I will not flee with you - too far away");
return true; return true;
@@ -171,7 +167,7 @@ bool FleeChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool GoawayChatShortcutAction::Execute(Event event) bool GoawayChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -188,7 +184,7 @@ bool GoawayChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool GrindChatShortcutAction::Execute(Event event) bool GrindChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -204,7 +200,7 @@ bool GrindChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool TankAttackChatShortcutAction::Execute(Event event) bool TankAttackChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -224,7 +220,7 @@ bool TankAttackChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool MaxDpsChatShortcutAction::Execute(Event event) bool MaxDpsChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
@@ -241,7 +237,7 @@ bool MaxDpsChatShortcutAction::Execute(Event event)
return true; return true;
} }
bool BwlChatShortcutAction::Execute(Event event) bool BwlChatShortcutAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)

View File

@@ -86,7 +86,7 @@ void CheatAction::ListCheats()
for (int i = 0; i < log2((uint32)BotCheatMask::maxMask); i++) for (int i = 0; i < log2((uint32)BotCheatMask::maxMask); i++)
{ {
BotCheatMask cheatMask = BotCheatMask(1 << i); BotCheatMask cheatMask = BotCheatMask(1 << i);
if ((uint32)cheatMask & (uint32)sPlayerbotAIConfig->botCheatMask) if ((uint32)cheatMask & (uint32)sPlayerbotAIConfig.botCheatMask)
out << "[conf:" << GetCheatName(BotCheatMask(cheatMask)) << "]"; out << "[conf:" << GetCheatName(BotCheatMask(cheatMask)) << "]";
else if (botAI->HasCheat(cheatMask)) else if (botAI->HasCheat(cheatMask))
out << "[" << GetCheatName(BotCheatMask(cheatMask)) << "]"; out << "[" << GetCheatName(BotCheatMask(cheatMask)) << "]";

View File

@@ -7,9 +7,10 @@
#include "Event.h" #include "Event.h"
#include "GuildTaskMgr.h" #include "GuildTaskMgr.h"
#include "Playerbots.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotAI.h"
bool CheckMailAction::Execute(Event event) bool CheckMailAction::Execute(Event /*event*/)
{ {
WorldPacket p; WorldPacket p;
bot->GetSession()->HandleQueryNextMailTime(p); bot->GetSession()->HandleQueryNextMailTime(p);
@@ -28,7 +29,7 @@ bool CheckMailAction::Execute(Event event)
continue; continue;
uint32 account = owner->GetSession()->GetAccountId(); uint32 account = owner->GetSession()->GetAccountId();
if (sPlayerbotAIConfig->IsInRandomAccountList(account)) if (PlayerbotAIConfig::instance().IsInRandomAccountList(account))
continue; continue;
ProcessMail(mail, owner, trans); ProcessMail(mail, owner, trans);
@@ -80,7 +81,7 @@ void CheckMailAction::ProcessMail(Mail* mail, Player* owner, CharacterDatabaseTr
if (!item) if (!item)
continue; continue;
if (!sGuildTaskMgr->CheckItemTask(i->item_template, item->GetCount(), owner, bot, true)) if (!GuildTaskMgr::instance().CheckItemTask(i->item_template, item->GetCount(), owner, bot, true))
{ {
std::ostringstream body; std::ostringstream body;
body << "Hello, " << owner->GetName() << ",\n"; body << "Hello, " << owner->GetName() << ",\n";

View File

@@ -55,63 +55,6 @@ MountData CollectMountData(const Player* bot)
return data; return data;
} }
bool CheckMountStateAction::isUseful()
{
// Not useful when:
if (botAI->IsInVehicle() || bot->isDead() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
!bot->IsOutdoors() || bot->InArena())
return false;
master = GetMaster();
// Get shapeshift states, only applicable when there's a master
if (master)
{
botInShapeshiftForm = bot->GetShapeshiftForm();
masterInShapeshiftForm = master->GetShapeshiftForm();
}
// Not useful when in combat and not currently mounted / travel formed
if ((bot->IsInCombat() || botAI->GetState() == BOT_STATE_COMBAT) &&
!bot->IsMounted() && botInShapeshiftForm != FORM_TRAVEL && botInShapeshiftForm != FORM_FLIGHT && botInShapeshiftForm != FORM_FLIGHT_EPIC)
return false;
// In addition to checking IsOutdoors, also check whether bot is clipping below floor slightly because that will
// cause bot to falsly indicate they are outdoors. This fixes bug where bot tries to mount indoors (which seems
// to mostly be an issue in tunnels of WSG and AV)
float posZ = bot->GetPositionZ();
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel)
return false;
// Not useful when bot does not have mount strat and is not currently mounted
if (!GET_PLAYERBOT_AI(bot)->HasStrategy("mount", BOT_STATE_NON_COMBAT) && !bot->IsMounted())
return false;
// Not useful when level lower than minimum required
if (bot->GetLevel() < sPlayerbotAIConfig->useGroundMountAtMinLevel)
return false;
// Allow mounting while transformed only if the form allows it
if (bot->HasAuraType(SPELL_AURA_TRANSFORM) && bot->IsInDisallowedMountForm())
return false;
// BG Logic
if (bot->InBattleground())
{
// Do not use when carrying BG Flags
if (bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG) || bot->HasAura(BG_EY_NETHERSTORM_FLAG_SPELL))
return false;
// Only mount if BG starts in less than 30 sec
if (Battleground* bg = bot->GetBattleground())
if (bg->GetStatus() == STATUS_WAIT_JOIN && bg->GetStartDelayTime() > BG_START_DELAY_30S)
return false;
}
return true;
}
bool CheckMountStateAction::Execute(Event /*event*/) bool CheckMountStateAction::Execute(Event /*event*/)
{ {
// Determine if there are no attackers // Determine if there are no attackers
@@ -182,6 +125,63 @@ bool CheckMountStateAction::Execute(Event /*event*/)
return false; return false;
} }
bool CheckMountStateAction::isUseful()
{
// Not useful when:
if (botAI->IsInVehicle() || bot->isDead() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
!bot->IsOutdoors() || bot->InArena())
return false;
master = GetMaster();
// Get shapeshift states, only applicable when there's a master
if (master)
{
botInShapeshiftForm = bot->GetShapeshiftForm();
masterInShapeshiftForm = master->GetShapeshiftForm();
}
// Not useful when in combat and not currently mounted / travel formed
if ((bot->IsInCombat() || botAI->GetState() == BOT_STATE_COMBAT) &&
!bot->IsMounted() && botInShapeshiftForm != FORM_TRAVEL && botInShapeshiftForm != FORM_FLIGHT && botInShapeshiftForm != FORM_FLIGHT_EPIC)
return false;
// In addition to checking IsOutdoors, also check whether bot is clipping below floor slightly because that will
// cause bot to falsly indicate they are outdoors. This fixes bug where bot tries to mount indoors (which seems
// to mostly be an issue in tunnels of WSG and AV)
float posZ = bot->GetPositionZ();
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel)
return false;
// Not useful when bot does not have mount strat and is not currently mounted
if (!GET_PLAYERBOT_AI(bot)->HasStrategy("mount", BOT_STATE_NON_COMBAT) && !bot->IsMounted())
return false;
// Not useful when level lower than minimum required
if (bot->GetLevel() < sPlayerbotAIConfig.useGroundMountAtMinLevel)
return false;
// Allow mounting while transformed only if the form allows it
if (bot->HasAuraType(SPELL_AURA_TRANSFORM) && bot->IsInDisallowedMountForm())
return false;
// BG Logic
if (bot->InBattleground())
{
// Do not use when carrying BG Flags
if (bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG) || bot->HasAura(BG_EY_NETHERSTORM_FLAG_SPELL))
return false;
// Only mount if BG starts in less than 30 sec
if (Battleground* bg = bot->GetBattleground())
if (bg->GetStatus() == STATUS_WAIT_JOIN && bg->GetStartDelayTime() > BG_START_DELAY_30S)
return false;
}
return true;
}
bool CheckMountStateAction::Mount() bool CheckMountStateAction::Mount()
{ {
// Remove current Shapeshift if need be // Remove current Shapeshift if need be
@@ -402,7 +402,7 @@ float CheckMountStateAction::CalculateDismountDistance() const
// Warrior bots should dismount far enough to charge (because it's important for generating some initial rage), // Warrior bots should dismount far enough to charge (because it's important for generating some initial rage),
// a real player would be riding toward enemy mashing the charge key but the bots won't cast charge while mounted. // a real player would be riding toward enemy mashing the charge key but the bots won't cast charge while mounted.
bool isMelee = PlayerbotAI::IsMelee(bot); bool isMelee = PlayerbotAI::IsMelee(bot);
float dismountDistance = isMelee ? sPlayerbotAIConfig->meleeDistance + 2.0f : sPlayerbotAIConfig->spellDistance + 2.0f; float dismountDistance = isMelee ? sPlayerbotAIConfig.meleeDistance + 2.0f : sPlayerbotAIConfig.spellDistance + 2.0f;
return bot->getClass() == CLASS_WARRIOR ? std::max(18.0f, dismountDistance) : dismountDistance; return bot->getClass() == CLASS_WARRIOR ? std::max(18.0f, dismountDistance) : dismountDistance;
} }
@@ -413,7 +413,7 @@ float CheckMountStateAction::CalculateMountDistance() const
// seconds: // seconds:
// 21 / 7 = 21 / 14 + 1.5 = 3 (7 = dismounted speed 14 = epic-mount speed 1.5 = mount-spell cast time) // 21 / 7 = 21 / 14 + 1.5 = 3 (7 = dismounted speed 14 = epic-mount speed 1.5 = mount-spell cast time)
bool isMelee = PlayerbotAI::IsMelee(bot); bool isMelee = PlayerbotAI::IsMelee(bot);
float baseDistance = isMelee ? sPlayerbotAIConfig->meleeDistance + 10.0f : sPlayerbotAIConfig->spellDistance + 10.0f; float baseDistance = isMelee ? sPlayerbotAIConfig.meleeDistance + 10.0f : sPlayerbotAIConfig.spellDistance + 10.0f;
return std::max(21.0f, baseDistance); return std::max(21.0f, baseDistance);
} }
@@ -440,7 +440,7 @@ int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const Mou
int32 ridingSkill = bot->GetPureSkillValue(SKILL_RIDING); int32 ridingSkill = bot->GetPureSkillValue(SKILL_RIDING);
int32 botLevel = bot->GetLevel(); int32 botLevel = bot->GetLevel();
if (ridingSkill <= 75 && botLevel < static_cast<int32>(sPlayerbotAIConfig->useFastGroundMountAtMinLevel)) if (ridingSkill <= 75 && botLevel < static_cast<int32>(sPlayerbotAIConfig.useFastGroundMountAtMinLevel))
return 59; return 59;
// If there is a master and bot not in BG, use master's aura effects. // If there is a master and bot not in BG, use master's aura effects.

View File

@@ -6,12 +6,15 @@
#include "CheckValuesAction.h" #include "CheckValuesAction.h"
#include "Event.h" #include "Event.h"
#include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "PlayerbotAI.h"
#include "TravelNode.h"
#include "AiObjectContext.h"
CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {} CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {}
bool CheckValuesAction::Execute(Event event) bool CheckValuesAction::Execute(Event /*event*/)
{ {
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
{ {
@@ -20,7 +23,7 @@ bool CheckValuesAction::Execute(Event event)
if (botAI->HasStrategy("map", BOT_STATE_NON_COMBAT) || botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy("map", BOT_STATE_NON_COMBAT) || botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT))
{ {
sTravelNodeMap->manageNodes(bot, botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT)); TravelNodeMap::instance().manageNodes(bot, botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT));
} }
GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets"); GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets");

View File

@@ -6,7 +6,6 @@
#include <random> #include <random>
#include "ChooseRpgTargetAction.h" #include "ChooseRpgTargetAction.h"
#include "BattlegroundMgr.h"
#include "BudgetValues.h" #include "BudgetValues.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "Event.h" #include "Event.h"
@@ -14,7 +13,6 @@
#include "GuildCreateActions.h" #include "GuildCreateActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RpgSubActions.h" #include "RpgSubActions.h"
#include "Util.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "PossibleRpgTargetsValue.h" #include "PossibleRpgTargetsValue.h"
@@ -112,9 +110,8 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
return floor((maxRelevance - 1.0) * 1000.0f); return floor((maxRelevance - 1.0) * 1000.0f);
} }
bool ChooseRpgTargetAction::Execute(Event event) bool ChooseRpgTargetAction::Execute(Event /*event*/)
{ {
//TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target"); //not used, line marked for removal.
Player* master = botAI->GetMaster(); Player* master = botAI->GetMaster();
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())
@@ -126,7 +123,6 @@ bool ChooseRpgTargetAction::Execute(Event event)
master = nullptr; master = nullptr;
std::unordered_map<ObjectGuid, uint32> targets; std::unordered_map<ObjectGuid, uint32> targets;
// uint32 num = 0; //not used, line marked for removal.
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets"); GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
GuidVector possibleObjects = AI_VALUE(GuidVector, "nearest game objects no los"); GuidVector possibleObjects = AI_VALUE(GuidVector, "nearest game objects no los");
GuidVector possiblePlayers = AI_VALUE(GuidVector, "nearest friendly players"); GuidVector possiblePlayers = AI_VALUE(GuidVector, "nearest friendly players");
@@ -248,7 +244,7 @@ bool ChooseRpgTargetAction::Execute(Event event)
} }
std::mt19937 gen(time(0)); std::mt19937 gen(time(0));
sTravelMgr->weighted_shuffle(guidps.begin(), guidps.end(), relevances.begin(), relevances.end(), gen); TravelMgr::instance().weighted_shuffle(guidps.begin(), guidps.end(), relevances.begin(), relevances.end(), gen);
GuidPosition guidP(guidps.front()); GuidPosition guidP(guidps.front());
if (!guidP) if (!guidP)
@@ -279,7 +275,7 @@ bool ChooseRpgTargetAction::isUseful()
GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target"); GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target");
if (guidP && guidP.distance(bot) < sPlayerbotAIConfig->reactDistance * 2) if (guidP && guidP.distance(bot) < sPlayerbotAIConfig.reactDistance * 2)
return false; return false;
// TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target"); //not used, line marked for removal. // TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target"); //not used, line marked for removal.
@@ -320,7 +316,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
inDungeon = true; inDungeon = true;
if (realMaster && realMaster->IsInWorld() && realMaster->GetMap()->IsDungeon() && if (realMaster && realMaster->IsInWorld() && realMaster->GetMap()->IsDungeon() &&
(realMaster->GetMapId() != pos.getMapId())) (realMaster->GetMapId() != pos.GetMapId()))
return false; return false;
} }
@@ -330,17 +326,17 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return true; return true;
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2) if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig.rpgDistance * 2)
return false; return false;
Formation* formation = AI_VALUE(Formation*, "formation"); Formation* formation = AI_VALUE(Formation*, "formation");
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY()); float distance = groupLeader->GetDistance2d(pos.GetPositionX(), pos.GetPositionY());
if (!botAI->HasActivePlayerMaster() && distance < 50.0f) if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
{ {
Player* player = groupLeader; Player* player = groupLeader;
if (groupLeader && !groupLeader->isMoving() || if (groupLeader && !groupLeader->isMoving() ||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance) PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig.reactDistance)
return true; return true;
} }

View File

@@ -20,48 +20,25 @@ bool AttackEnemyPlayerAction::isUseful()
if (PlayerHasFlag::IsCapturingFlag(bot)) if (PlayerHasFlag::IsCapturingFlag(bot))
return false; return false;
return !sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()); return !sPlayerbotAIConfig.IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId());
} }
bool AttackEnemyFlagCarrierAction::isUseful() bool AttackEnemyFlagCarrierAction::isUseful()
{ {
Unit* target = context->GetValue<Unit*>("enemy flag carrier")->Get(); Unit* target = context->GetValue<Unit*>("enemy flag carrier")->Get();
return target && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), 100.0f) && return target && ServerFacade::instance().IsDistanceLessOrEqualThan(ServerFacade::instance().GetDistance2d(bot, target), 100.0f) &&
PlayerHasFlag::IsCapturingFlag(bot); PlayerHasFlag::IsCapturingFlag(bot);
} }
bool AttackAnythingAction::isUseful() bool AggressiveTargetAction::isUseful()
{ {
if (!bot || !botAI) // Prevents invalid accesses
return false;
if (!botAI->AllowActivity(GRIND_ACTIVITY)) // Bot cannot be active
return false;
if (botAI->HasStrategy("stay", BOT_STATE_NON_COMBAT))
return false;
if (bot->IsInCombat()) if (bot->IsInCombat())
return false; return false;
Unit* target = GetTarget();
if (!target || !target->IsInWorld()) // Checks if the target is valid and in the world
return false;
std::string const name = std::string(target->GetName());
if (!name.empty() &&
(name.find("Dummy") != std::string::npos ||
name.find("Charge Target") != std::string::npos ||
name.find("Melee Target") != std::string::npos ||
name.find("Ranged Target") != std::string::npos))
{
return false;
}
return true; return true;
} }
bool DropTargetAction::Execute(Event event) bool DropTargetAction::Execute(Event /*event*/)
{ {
Unit* target = context->GetValue<Unit*>("current target")->Get(); Unit* target = context->GetValue<Unit*>("current target")->Get();
if (target && target->isDead()) if (target && target->isDead())
@@ -127,7 +104,38 @@ bool AttackAnythingAction::Execute(Event event)
return result; return result;
} }
bool AttackAnythingAction::isPossible() { return AttackAction::isPossible() && GetTarget(); } bool AttackAnythingAction::isUseful()
{
if (!bot || !botAI) // Prevents invalid accesses
return false;
if (!botAI->AllowActivity(GRIND_ACTIVITY)) // Bot cannot be active
return false;
if (botAI->HasStrategy("stay", BOT_STATE_NON_COMBAT))
return false;
if (bot->IsInCombat())
return false;
Unit* target = GetTarget();
if (!target || !target->IsInWorld()) // Checks if the target is valid and in the world
return false;
std::string const name = std::string(target->GetName());
if (!name.empty() &&
(name.find("Dummy") != std::string::npos ||
name.find("Charge Target") != std::string::npos ||
name.find("Melee Target") != std::string::npos ||
name.find("Ranged Target") != std::string::npos))
{
return false;
}
return true;
}
bool AttackAnythingAction::isPossible() { return GetTarget() && AttackAction::isPossible(); }
bool DpsAssistAction::isUseful() bool DpsAssistAction::isUseful()
{ {
@@ -137,7 +145,7 @@ bool DpsAssistAction::isUseful()
return true; return true;
} }
bool AttackRtiTargetAction::Execute(Event event) bool AttackRtiTargetAction::Execute(Event /*event*/)
{ {
Unit* rtiTarget = AI_VALUE(Unit*, "rti target"); Unit* rtiTarget = AI_VALUE(Unit*, "rti target");

View File

@@ -35,6 +35,15 @@ public:
std::string const GetTargetName() override { return "tank target"; } std::string const GetTargetName() override { return "tank target"; }
}; };
class AggressiveTargetAction : public AttackAction
{
public:
AggressiveTargetAction(PlayerbotAI* botAI) : AttackAction(botAI, "aggressive target") {}
std::string const GetTargetName() override { return "aggressive target"; }
bool isUseful() override;
};
class AttackAnythingAction : public AttackAction class AttackAnythingAction : public AttackAction
{ {
public: public:

View File

@@ -9,7 +9,7 @@
#include "LootObjectStack.h" #include "LootObjectStack.h"
#include "Playerbots.h" #include "Playerbots.h"
bool ChooseTravelTargetAction::Execute(Event event) bool ChooseTravelTargetAction::Execute(Event /*event*/)
{ {
// Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); //not used, line marked for removal. // Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); //not used, line marked for removal.
@@ -232,15 +232,6 @@ void ChooseTravelTargetAction::ReportTravelTarget(TravelTarget* newTarget, Trave
QuestTravelDestination* QuestDestination = (QuestTravelDestination*)destination; QuestTravelDestination* QuestDestination = (QuestTravelDestination*)destination;
Quest const* quest = QuestDestination->GetQuestTemplate(); Quest const* quest = QuestDestination->GetQuestTemplate();
WorldPosition botLocation(bot); WorldPosition botLocation(bot);
CreatureTemplate const* cInfo = nullptr;
GameObjectTemplate const* gInfo = nullptr;
if (destination->getEntry() > 0)
cInfo = sObjectMgr->GetCreatureTemplate(destination->getEntry());
else
gInfo = sObjectMgr->GetGameObjectTemplate(destination->getEntry() * -1);
std::string Sub; std::string Sub;
if (newTarget->isGroupCopy()) if (newTarget->isGroupCopy())
@@ -366,7 +357,7 @@ bool ChooseTravelTargetAction::getBestDestination(std::vector<TravelDestination*
WorldPosition botLocation(bot); WorldPosition botLocation(bot);
std::vector<WorldPosition*> availablePoints = std::vector<WorldPosition*> availablePoints =
sTravelMgr->getNextPoint(&botLocation, *activePoints); // Pick a good point. TravelMgr::instance().getNextPoint(&botLocation, *activePoints); // Pick a good point.
if (availablePoints.empty()) // No points available. if (availablePoints.empty()) // No points available.
return false; return false;
@@ -488,7 +479,7 @@ bool ChooseTravelTargetAction::SetQuestTarget(TravelTarget* target, bool onlyCom
if (newQuests) if (newQuests)
{ {
// Prefer new quests near the player at lower levels. // Prefer new quests near the player at lower levels.
activeDestinations = sTravelMgr->getQuestTravelDestinations(bot, -1, true, false, 400 + bot->GetLevel() * 10); activeDestinations = TravelMgr::instance().getQuestTravelDestinations(bot, -1, true, false, 400 + bot->GetLevel() * 10);
} }
if (activeQuests || completedQuests) if (activeQuests || completedQuests)
{ {
@@ -510,7 +501,7 @@ bool ChooseTravelTargetAction::SetQuestTarget(TravelTarget* target, bool onlyCom
continue; continue;
//Find quest takers or objectives //Find quest takers or objectives
std::vector<TravelDestination*> questDestinations = sTravelMgr->getQuestTravelDestinations(bot, questId, true, false, 0); std::vector<TravelDestination*> questDestinations = TravelMgr::instance().getQuestTravelDestinations(bot, questId, true, false, 0);
if (onlyClassQuest && activeDestinations.size() && questDestinations.size()) //Only do class quests if we have any. if (onlyClassQuest && activeDestinations.size() && questDestinations.size()) //Only do class quests if we have any.
{ {
@@ -525,7 +516,7 @@ bool ChooseTravelTargetAction::SetQuestTarget(TravelTarget* target, bool onlyCom
} }
} }
if (newQuests && activeDestinations.empty()) if (newQuests && activeDestinations.empty())
activeDestinations = sTravelMgr->getQuestTravelDestinations(bot, -1, true, false); //If we really don't find any new quests look futher away. activeDestinations = TravelMgr::instance().getQuestTravelDestinations(bot, -1, true, false); //If we really don't find any new quests look futher away.
if (botAI->HasStrategy("debug travel", BotState::BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy("debug travel", BotState::BOT_STATE_NON_COMBAT))
botAI->TellMasterNoFacing(std::to_string(activeDestinations.size()) + " quest destinations found."); botAI->TellMasterNoFacing(std::to_string(activeDestinations.size()) + " quest destinations found.");
@@ -547,7 +538,7 @@ bool ChooseTravelTargetAction::SetNewQuestTarget(TravelTarget* target)
// Find quest givers. // Find quest givers.
std::vector<TravelDestination*> TravelDestinations = std::vector<TravelDestination*> TravelDestinations =
sTravelMgr->getQuestTravelDestinations(bot, -1, botAI->HasRealPlayerMaster()); TravelMgr::instance().getQuestTravelDestinations(bot, -1, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end()); activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
@@ -576,7 +567,7 @@ bool ChooseTravelTargetAction::SetRpgTarget(TravelTarget* target)
// Find rpg npcs // Find rpg npcs
std::vector<TravelDestination*> TravelDestinations = std::vector<TravelDestination*> TravelDestinations =
sTravelMgr->getRpgTravelDestinations(bot, botAI->HasRealPlayerMaster()); TravelMgr::instance().getRpgTravelDestinations(bot, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end()); activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
@@ -605,7 +596,7 @@ bool ChooseTravelTargetAction::SetGrindTarget(TravelTarget* target)
// Find grind mobs. // Find grind mobs.
std::vector<TravelDestination*> TravelDestinations = std::vector<TravelDestination*> TravelDestinations =
sTravelMgr->getGrindTravelDestinations(bot, botAI->HasRealPlayerMaster()); TravelMgr::instance().getGrindTravelDestinations(bot, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end()); activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
@@ -634,7 +625,7 @@ bool ChooseTravelTargetAction::SetBossTarget(TravelTarget* target)
// Find boss mobs. // Find boss mobs.
std::vector<TravelDestination*> TravelDestinations = std::vector<TravelDestination*> TravelDestinations =
sTravelMgr->getBossTravelDestinations(bot, botAI->HasRealPlayerMaster()); TravelMgr::instance().getBossTravelDestinations(bot, botAI->HasRealPlayerMaster());
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end()); activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
@@ -662,7 +653,7 @@ bool ChooseTravelTargetAction::SetExploreTarget(TravelTarget* target)
WorldPosition botLocation(bot); WorldPosition botLocation(bot);
// Find quest givers. // Find quest givers.
std::vector<TravelDestination*> TravelDestinations = sTravelMgr->getExploreTravelDestinations(bot, true, true); std::vector<TravelDestination*> TravelDestinations = TravelMgr::instance().getExploreTravelDestinations(bot, true, true);
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end()); activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
/* /*
@@ -681,7 +672,7 @@ bool ChooseTravelTargetAction::SetExploreTarget(TravelTarget* target)
if (activePoints.empty()) if (activePoints.empty())
{ {
TravelDestinations = sTravelMgr->getExploreTravelDestinations(bot, botAI->HasRealPlayerMaster()); TravelDestinations = TravelMgr::instance().getExploreTravelDestinations(bot, botAI->HasRealPlayerMaster());
for (auto& activeTarget : activeDestinations) for (auto& activeTarget : activeDestinations)
{ {
@@ -710,7 +701,7 @@ bool ChooseTravelTargetAction::SetNpcFlagTarget(TravelTarget* target, std::vecto
std::vector<TravelDestination*> dests; std::vector<TravelDestination*> dests;
for (auto& d : sTravelMgr->getRpgTravelDestinations(bot, true, true)) for (auto& d : TravelMgr::instance().getRpgTravelDestinations(bot, true, true))
{ {
if (!d->getEntry()) if (!d->getEntry())
continue; continue;
@@ -813,7 +804,7 @@ std::vector<TravelDestination*> TravelMgr::getBossTravelDestinations(Player* bot
bool ChooseTravelTargetAction::SetNullTarget(TravelTarget* target) bool ChooseTravelTargetAction::SetNullTarget(TravelTarget* target)
{ {
target->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true); target->setTarget(TravelMgr::instance().nullTravelDestination, TravelMgr::instance().nullWorldPosition, true);
return true; return true;
} }
@@ -823,16 +814,12 @@ char* strstri(char const* haystack, char const* needle);
TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::string const name, bool zones, bool npcs, bool quests, bool mobs, bool bosses) TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::string const name, bool zones, bool npcs, bool quests, bool mobs, bool bosses)
{ {
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
// AiObjectContext* context = botAI->GetAiObjectContext(); //not used, line marked for removal.
std::vector<TravelDestination*> dests; std::vector<TravelDestination*> dests;
//Quests //Quests
if (quests) if (quests)
{ {
for (auto& d : sTravelMgr->getQuestTravelDestinations(bot, 0, true, true)) for (auto& d : TravelMgr::instance().getQuestTravelDestinations(bot, 0, true, true))
{ {
if (strstri(d->getTitle().c_str(), name.c_str())) if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d); dests.push_back(d);
@@ -842,7 +829,7 @@ TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::s
//Zones //Zones
if (zones) if (zones)
{ {
for (auto& d : sTravelMgr->getExploreTravelDestinations(bot, true, true)) for (auto& d : TravelMgr::instance().getExploreTravelDestinations(bot, true, true))
{ {
if (strstri(d->getTitle().c_str(), name.c_str())) if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d); dests.push_back(d);
@@ -852,7 +839,7 @@ TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::s
//Npcs //Npcs
if (npcs) if (npcs)
{ {
for (auto& d : sTravelMgr->getRpgTravelDestinations(bot, true, true)) for (auto& d : TravelMgr::instance().getRpgTravelDestinations(bot, true, true))
{ {
if (strstri(d->getTitle().c_str(), name.c_str())) if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d); dests.push_back(d);
@@ -862,7 +849,7 @@ TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::s
//Mobs //Mobs
if (mobs) if (mobs)
{ {
for (auto& d : sTravelMgr->getGrindTravelDestinations(bot, true, true, 5000.0f)) for (auto& d : TravelMgr::instance().getGrindTravelDestinations(bot, true, true, 5000.0f))
{ {
if (strstri(d->getTitle().c_str(), name.c_str())) if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d); dests.push_back(d);
@@ -872,7 +859,7 @@ TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::s
//Bosses //Bosses
if (bosses) if (bosses)
{ {
for (auto& d : sTravelMgr->getBossTravelDestinations(bot, true, true)) for (auto& d : TravelMgr::instance().getBossTravelDestinations(bot, true, true))
{ {
if (strstri(d->getTitle().c_str(), name.c_str())) if (strstri(d->getTitle().c_str(), name.c_str()))
dests.push_back(d); dests.push_back(d);

View File

@@ -25,7 +25,7 @@ bool SwitchToMeleeAction::isUseful()
return botAI->HasStrategy("ranged", BOT_STATE_COMBAT) && return botAI->HasStrategy("ranged", BOT_STATE_COMBAT) &&
((bot->IsInCombat() && target && ((bot->IsInCombat() && target &&
(target->GetVictim() == bot && (!bot->GetGroup() || lastFlee) && (target->GetVictim() == bot && (!bot->GetGroup() || lastFlee) &&
sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "current target"), 8.0f))) || ServerFacade::instance().IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "current target"), 8.0f))) ||
(!bot->IsInCombat())); (!bot->IsInCombat()));
} }
@@ -47,7 +47,7 @@ bool SwitchToRangedAction::isUseful()
return botAI->HasStrategy("close", BOT_STATE_COMBAT) && hasAmmo && return botAI->HasStrategy("close", BOT_STATE_COMBAT) && hasAmmo &&
((bot->IsInCombat() && target && ((bot->IsInCombat() && target &&
((target->GetVictim() != bot || target->GetTarget() != bot->GetGUID()) || ((target->GetVictim() != bot || target->GetTarget() != bot->GetGUID()) ||
sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "current target"), 8.0f))) || ServerFacade::instance().IsDistanceGreaterThan(AI_VALUE2(float, "distance", "current target"), 8.0f))) ||
(!bot->IsInCombat())); (!bot->IsInCombat()));
} }

View File

@@ -7,7 +7,11 @@
#include "ChooseTravelTargetAction.h" #include "ChooseTravelTargetAction.h"
#include "MapMgr.h" #include "MapMgr.h"
#include "Playerbots.h" #include "TravelMgr.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "SpellMgr.h"
#include "Spell.h"
bool DebugAction::Execute(Event event) bool DebugAction::Execute(Event event)
{ {
@@ -19,7 +23,7 @@ bool DebugAction::Execute(Event event)
if (text == "scan") if (text == "scan")
{ {
sPlayerbotAIConfig->openLog("scan.csv", "w"); sPlayerbotAIConfig.openLog("scan.csv", "w");
uint32 i = 0; uint32 i = 0;
for (auto p : WorldPosition().getCreaturesNear()) for (auto p : WorldPosition().getCreaturesNear())
@@ -28,23 +32,23 @@ bool DebugAction::Execute(Event event)
uint32 areaId = 0; uint32 areaId = 0;
uint32 zoneId = 0; uint32 zoneId = 0;
sMapMgr->GetZoneAndAreaId(PHASEMASK_NORMAL, zoneId, areaId, pos.getMapId(), pos.getX(), pos.getY(), sMapMgr->GetZoneAndAreaId(PHASEMASK_NORMAL, zoneId, areaId, pos.GetMapId(), pos.GetPositionX(), pos.GetPositionY(),
pos.getZ()); pos.GetPositionZ());
std::ostringstream out; std::ostringstream out;
out << zoneId << "," << areaId << "," << (pos.getAreaName().empty() ? "none" : pos.getAreaName()) << ","; out << zoneId << "," << areaId << "," << (pos.getAreaName().empty() ? "none" : pos.getAreaName()) << ",";
pos.printWKT(out); pos.printWKT(out);
sPlayerbotAIConfig->log("scan.csv", out.str().c_str()); sPlayerbotAIConfig.log("scan.csv", out.str().c_str());
if (zoneId == 0 && areaId == 0) if (zoneId == 0 && areaId == 0)
{ {
sPlayerbotAIConfig->log("x", out.str().c_str()); sPlayerbotAIConfig.log("x", out.str().c_str());
} }
else else
{ {
sPlayerbotAIConfig->log("y", out.str().c_str()); sPlayerbotAIConfig.log("y", out.str().c_str());
} }
i = zoneId; i = zoneId;
@@ -53,8 +57,8 @@ bool DebugAction::Execute(Event event)
} }
else if (text.find("printmap") != std::string::npos) else if (text.find("printmap") != std::string::npos)
{ {
sTravelNodeMap->printMap(); TravelNodeMap::instance().printMap();
sTravelNodeMap->printNodeStore(); TravelNodeMap::instance().printNodeStore();
return true; return true;
} }
else if (text.find("travel ") != std::string::npos) else if (text.find("travel ") != std::string::npos)
@@ -72,7 +76,7 @@ bool DebugAction::Execute(Event event)
return false; return false;
std::vector<WorldPosition> beginPath, endPath; std::vector<WorldPosition> beginPath, endPath;
TravelNodeRoute route = sTravelNodeMap->getRoute(botPos, *points.front(), beginPath, bot); TravelNodeRoute route = TravelNodeMap::instance().getRoute(botPos, *points.front(), beginPath, bot);
std::ostringstream out; std::ostringstream out;
out << "Traveling to " << dest->getTitle() << ": "; out << "Traveling to " << dest->getTitle() << ": ";
@@ -108,7 +112,7 @@ bool DebugAction::Execute(Event event)
out << quest->GetTitle() << ": "; out << quest->GetTitle() << ": ";
QuestContainer* cont = sTravelMgr->quests[questId]; QuestContainer* cont = TravelMgr::instance().quests[questId];
for (auto g : cont->questGivers) for (auto g : cont->questGivers)
{ {
@@ -135,11 +139,11 @@ bool DebugAction::Execute(Event event)
else if (text.find("quest") != std::string::npos) else if (text.find("quest") != std::string::npos)
{ {
std::ostringstream out; std::ostringstream out;
out << sTravelMgr->quests.size() << " quests "; out << TravelMgr::instance().quests.size() << " quests ";
uint32 noT = 0, noG = 0, noO = 0; uint32 noT = 0, noG = 0, noO = 0;
for (auto q : sTravelMgr->quests) for (auto q : TravelMgr::instance().quests)
{ {
if (q.second->questGivers.empty()) if (q.second->questGivers.empty())
noG++; noG++;
@@ -164,7 +168,7 @@ bool DebugAction::Execute(Event event)
// uint32 noT = 0, noG = 0, noO = 0; //not used, line marked for removal. // uint32 noT = 0, noG = 0, noO = 0; //not used, line marked for removal.
for (auto q : sTravelMgr->quests) for (auto q : TravelMgr::instance().quests)
{ {
Quest const* quest = sObjectMgr->GetQuestTemplate(q.first); Quest const* quest = sObjectMgr->GetQuestTemplate(q.first);
@@ -194,16 +198,16 @@ bool DebugAction::Execute(Event event)
std::string const name = "USER:" + text.substr(9); std::string const name = "USER:" + text.substr(9);
/* TravelNode* startNode = */ sTravelNodeMap->addNode(pos, name, false, false); // startNode not used, but addNode as side effect, fragment marked for removal. /* TravelNode* startNode = */ TravelNodeMap::instance().addNode(pos, name, false, false); // startNode not used, but addNode as side effect, fragment marked for removal.
for (auto& endNode : sTravelNodeMap->getNodes(pos, 2000)) for (auto& endNode : TravelNodeMap::instance().getNodes(pos, 2000))
{ {
endNode->setLinked(false); endNode->setLinked(false);
} }
botAI->TellMasterNoFacing("Node " + name + " created."); botAI->TellMasterNoFacing("Node " + name + " created.");
sTravelNodeMap->setHasToGen(); TravelNodeMap::instance().setHasToGen();
return true; return true;
} }
@@ -211,7 +215,7 @@ bool DebugAction::Execute(Event event)
{ {
WorldPosition pos(bot); WorldPosition pos(bot);
TravelNode* startNode = sTravelNodeMap->getNode(pos, nullptr, 50); TravelNode* startNode = TravelNodeMap::instance().getNode(pos, nullptr, 50);
if (!startNode) if (!startNode)
return false; return false;
@@ -221,24 +225,24 @@ bool DebugAction::Execute(Event event)
botAI->TellMasterNoFacing("Node can not be removed."); botAI->TellMasterNoFacing("Node can not be removed.");
} }
sTravelNodeMap->m_nMapMtx.lock(); TravelNodeMap::instance().m_nMapMtx.lock();
sTravelNodeMap->removeNode(startNode); TravelNodeMap::instance().removeNode(startNode);
botAI->TellMasterNoFacing("Node removed."); botAI->TellMasterNoFacing("Node removed.");
sTravelNodeMap->m_nMapMtx.unlock(); TravelNodeMap::instance().m_nMapMtx.unlock();
sTravelNodeMap->setHasToGen(); TravelNodeMap::instance().setHasToGen();
return true; return true;
} }
else if (text.find("reset node") != std::string::npos) else if (text.find("reset node") != std::string::npos)
{ {
for (auto& node : sTravelNodeMap->getNodes()) for (auto& node : TravelNodeMap::instance().getNodes())
node->setLinked(false); node->setLinked(false);
return true; return true;
} }
else if (text.find("reset path") != std::string::npos) else if (text.find("reset path") != std::string::npos)
{ {
for (auto& node : sTravelNodeMap->getNodes()) for (auto& node : TravelNodeMap::instance().getNodes())
for (auto& path : *node->getLinks()) for (auto& path : *node->getLinks())
node->removeLinkTo(path.first, true); node->removeLinkTo(path.first, true);
return true; return true;
@@ -246,23 +250,23 @@ bool DebugAction::Execute(Event event)
else if (text.find("gen node") != std::string::npos) else if (text.find("gen node") != std::string::npos)
{ {
// Pathfinder // Pathfinder
sTravelNodeMap->generateNodes(); TravelNodeMap::instance().generateNodes();
return true; return true;
} }
else if (text.find("gen path") != std::string::npos) else if (text.find("gen path") != std::string::npos)
{ {
sTravelNodeMap->generatePaths(); TravelNodeMap::instance().generatePaths();
return true; return true;
} }
else if (text.find("crop path") != std::string::npos) else if (text.find("crop path") != std::string::npos)
{ {
sTravelNodeMap->removeUselessPaths(); TravelNodeMap::instance().removeUselessPaths();
return true; return true;
} }
else if (text.find("save node") != std::string::npos) else if (text.find("save node") != std::string::npos)
{ {
sTravelNodeMap->printNodeStore(); TravelNodeMap::instance().printNodeStore();
sTravelNodeMap->saveNodeStore(); TravelNodeMap::instance().saveNodeStore();
return true; return true;
} }
else if (text.find("load node") != std::string::npos) else if (text.find("load node") != std::string::npos)
@@ -270,8 +274,8 @@ bool DebugAction::Execute(Event event)
std::thread t( std::thread t(
[] []
{ {
sTravelNodeMap->removeNodes(); TravelNodeMap::instance().removeNodes();
sTravelNodeMap->loadNodeStore(); TravelNodeMap::instance().loadNodeStore();
}); });
t.detach(); t.detach();
@@ -282,7 +286,7 @@ bool DebugAction::Execute(Event event)
{ {
WorldPosition pos(bot); WorldPosition pos(bot);
std::vector<TravelNode*> nodes = sTravelNodeMap->getNodes(pos, 500); std::vector<TravelNode*> nodes = TravelNodeMap::instance().getNodes(pos, 500);
for (auto& node : nodes) for (auto& node : nodes)
{ {
@@ -298,7 +302,7 @@ bool DebugAction::Execute(Event event)
for (auto p : ppath) for (auto p : ppath)
{ {
Creature* wpCreature = Creature* wpCreature =
bot->SummonCreature(1, p.getX(), p.getY(), p.getZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 20000.0f); bot->SummonCreature(1, p.GetPositionX(), p.GetPositionY(), p.GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 20000.0f);
// addAura(246, wpCreature); // addAura(246, wpCreature);
units.push_back(wpCreature->GetGUID()); units.push_back(wpCreature->GetGUID());
@@ -325,11 +329,11 @@ bool DebugAction::Execute(Event event)
WorldPosition botPos(bot); WorldPosition botPos(bot);
WorldPosition botPos1 = botPos; WorldPosition botPos1 = botPos;
botPos.setX(botPos.getX() + cos(ang) * dist); botPos.setX(botPos.GetPositionX() + cos(ang) * dist);
botPos.setY(botPos.getY() + sin(ang) * dist); botPos.setY(botPos.GetPositionY() + sin(ang) * dist);
botPos.setZ(botPos.getHeight() + 2); botPos.setZ(botPos.getHeight() + 2);
Creature* wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, Creature* wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
FakeSpell(spellEffect, wpCreature, wpCreature, prev->GetGUID(), {}, {}, botPos, botPos); FakeSpell(spellEffect, wpCreature, wpCreature, prev->GetGUID(), {}, {}, botPos, botPos);
@@ -352,11 +356,11 @@ bool DebugAction::Execute(Event event)
WorldPosition botPos(bot); WorldPosition botPos(bot);
WorldPosition botPos1 = botPos; WorldPosition botPos1 = botPos;
botPos.setX(botPos.getX() + cos(ang) * dist); botPos.setX(botPos.GetPositionX() + cos(ang) * dist);
botPos.setY(botPos.getY() + sin(ang) * dist); botPos.setY(botPos.GetPositionY() + sin(ang) * dist);
botPos.setZ(botPos.getHeight() + 2); botPos.setZ(botPos.getHeight() + 2);
Creature* wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, Creature* wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
if (wpCreature) if (wpCreature)
@@ -383,11 +387,11 @@ bool DebugAction::Execute(Event event)
WorldPosition botPos(bot); WorldPosition botPos(bot);
WorldPosition botPos1 = botPos; WorldPosition botPos1 = botPos;
botPos.setX(botPos.getX() + cos(ang) * dist); botPos.setX(botPos.GetPositionX() + cos(ang) * dist);
botPos.setY(botPos.getY() + sin(ang) * dist); botPos.setY(botPos.GetPositionY() + sin(ang) * dist);
botPos.setZ(botPos.getHeight() + 2); botPos.setZ(botPos.getHeight() + 2);
Creature* wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, Creature* wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 5000.0f + i * 100.0f); TEMPSUMMON_TIMED_DESPAWN, 5000.0f + i * 100.0f);
wpCreature->SetObjectScale(0.5f); wpCreature->SetObjectScale(0.5f);
@@ -411,11 +415,11 @@ bool DebugAction::Execute(Event event)
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + cos(ang) * dist); botPos.setX(botPos.GetPositionX() + cos(ang) * dist);
botPos.setY(botPos.getY() + sin(ang) * dist); botPos.setY(botPos.GetPositionY() + sin(ang) * dist);
botPos.setZ(botPos.getHeight() + 2); botPos.setZ(botPos.getHeight() + 2);
Creature* wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, Creature* wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
units.push_back(wpCreature->GetGUID()); units.push_back(wpCreature->GetGUID());
@@ -480,13 +484,13 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + spellEffect * 100; uint32 effect = dx + dy * 10 + spellEffect * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = Creature* wpCreature =
bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.getX(), botPos.getY(), bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.GetPositionX(), botPos.GetPositionY(),
botPos.getZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f); botPos.GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
if (wpCreature) if (wpCreature)
{ {
@@ -512,12 +516,12 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + spellEffect * 100; uint32 effect = dx + dy * 10 + spellEffect * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = bot->SummonCreature(effect, botPos.getX(), botPos.getY(), botPos.getZ(), 0, bot->SummonCreature(effect, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
} }
} }
return true; return true;
@@ -532,8 +536,8 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + spellEffect * 100; uint32 effect = dx + dy * 10 + spellEffect * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
FakeSpell(effect, bot, nullptr, ObjectGuid::Empty, {}, {}, botPos, botPos, true); FakeSpell(effect, bot, nullptr, ObjectGuid::Empty, {}, {}, botPos, botPos, true);
@@ -552,11 +556,11 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + spellEffect * 100; uint32 effect = dx + dy * 10 + spellEffect * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, Creature* wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
if (wpCreature) if (wpCreature)
@@ -568,7 +572,7 @@ bool DebugAction::Execute(Event event)
// wpCreature->SendMessageToSet(&data, true); // wpCreature->SendMessageToSet(&data, true);
datMap.push_back(data); datMap.push_back(data);
// wpCreature->MonsterMoveWithSpeed(botPos.getX(), botPos.getY() + 80, botPos.getZ(), 8.0f, true, // wpCreature->MonsterMoveWithSpeed(botPos.GetPositionX(), botPos.GetPositionY() + 80, botPos.GetPositionZ(), 8.0f, true,
// true); // true);
} }
} }
@@ -600,13 +604,13 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + spellEffect * 100; uint32 effect = dx + dy * 10 + spellEffect * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = Creature* wpCreature =
bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.getX(), botPos.getY(), bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.GetPositionX(), botPos.GetPositionY(),
botPos.getZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f); botPos.GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
if (wpCreature) if (wpCreature)
{ {
@@ -646,12 +650,12 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + spellEffect * 100; uint32 effect = dx + dy * 10 + spellEffect * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
wpCreature = bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.getX(), wpCreature = bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.GetPositionX(),
botPos.getY(), botPos.getZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f); botPos.GetPositionY(), botPos.GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
if (wpCreature && lCreature) if (wpCreature && lCreature)
{ {
@@ -675,11 +679,11 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + spellEffect * 100; uint32 effect = dx + dy * 10 + spellEffect * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
if (wpCreature) if (wpCreature)
@@ -708,11 +712,11 @@ bool DebugAction::Execute(Event event)
{ {
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, Creature* wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
all_targets.push_back(wpCreature->GetGUID()); all_targets.push_back(wpCreature->GetGUID());
@@ -788,11 +792,11 @@ bool DebugAction::Execute(Event event)
{ {
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = bot->SummonCreature(2334, botPos.getX(), botPos.getY(), botPos.getZ(), 0, Creature* wpCreature = bot->SummonCreature(2334, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
all_targets.push_back(wpCreature->GetGUID()); all_targets.push_back(wpCreature->GetGUID());
@@ -868,13 +872,13 @@ bool DebugAction::Execute(Event event)
uint32 effect = dx + dy * 10 + soundEffects * 100; uint32 effect = dx + dy * 10 + soundEffects * 100;
WorldPosition botPos(bot); WorldPosition botPos(bot);
botPos.setX(botPos.getX() + (dx - 5) * 5); botPos.setX(botPos.GetPositionX() + (dx - 5) * 5);
botPos.setY(botPos.getY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = Creature* wpCreature =
bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.getX(), botPos.getY(), bot->SummonCreature((dy == 0 && (dx == 0 || dx == 2)) ? 6 : 2, botPos.GetPositionX(), botPos.GetPositionY(),
botPos.getZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f); botPos.GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
wpCreature->PlayDistanceSound(effect); wpCreature->PlayDistanceSound(effect);
} }
@@ -964,7 +968,7 @@ void DebugAction::FakeSpell(uint32 spellId, Unit* truecaster, Unit* caster, Obje
m_targets.SetDst(dest); m_targets.SetDst(dest);
if ((spellInfo && spellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION) || forceDest) if ((spellInfo && spellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION) || forceDest)
m_targets.SetSrc(source.getX(), source.getY(), source.getZ()); m_targets.SetSrc(source.GetPositionX(), source.GetPositionY(), source.GetPositionZ());
if (!forceDest && target) if (!forceDest && target)
if (!spellInfo || if (!spellInfo ||

View File

@@ -6,15 +6,19 @@
#include "DelayAction.h" #include "DelayAction.h"
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
bool DelayAction::Execute(Event event) bool DelayAction::Execute(Event /*event*/)
{ {
uint32 delay = sPlayerbotAIConfig->passiveDelay + sPlayerbotAIConfig->globalCoolDown; const uint32 delay = PlayerbotAIConfig::instance().passiveDelay + PlayerbotAIConfig::instance().globalCoolDown;
botAI->SetNextCheckDelay(delay); botAI->SetNextCheckDelay(delay);
return true; return true;
} }
bool DelayAction::isUseful() { return !botAI->AllowActivity(ALL_ACTIVITY); } bool DelayAction::isUseful()
{
return !botAI->AllowActivity(ALL_ACTIVITY);
}

View File

@@ -39,7 +39,7 @@ void DestroyItemAction::DestroyItem(FindItemVisitor* visitor)
bool SmartDestroyItemAction::isUseful() { return !botAI->HasActivePlayerMaster(); } bool SmartDestroyItemAction::isUseful() { return !botAI->HasActivePlayerMaster(); }
bool SmartDestroyItemAction::Execute(Event event) bool SmartDestroyItemAction::Execute(Event /*event*/)
{ {
uint8 bagSpace = AI_VALUE(uint8, "bag space"); uint8 bagSpace = AI_VALUE(uint8, "bag space");

View File

@@ -67,7 +67,7 @@ bool CleanQuestLogAction::Execute(Event event)
return false; return false;
} }
if (!sPlayerbotAIConfig->dropObsoleteQuests) if (!sPlayerbotAIConfig.dropObsoleteQuests)
{ {
return false; return false;
} }

View File

@@ -6,7 +6,6 @@
#include "EmoteAction.h" #include "EmoteAction.h"
#include "Event.h" #include "Event.h"
#include "PlayerbotTextMgr.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
@@ -88,7 +87,7 @@ void EmoteActionBase::InitEmotes()
bool EmoteActionBase::Emote(Unit* target, uint32 type, bool textEmote) bool EmoteActionBase::Emote(Unit* target, uint32 type, bool textEmote)
{ {
if (target && !bot->HasInArc(static_cast<float>(M_PI), target, sPlayerbotAIConfig->sightDistance)) if (target && !bot->HasInArc(static_cast<float>(M_PI), target, sPlayerbotAIConfig.sightDistance))
bot->SetFacingToObject(target); bot->SetFacingToObject(target);
ObjectGuid oldSelection = bot->GetTarget(); ObjectGuid oldSelection = bot->GetTarget();
@@ -100,7 +99,7 @@ bool EmoteActionBase::Emote(Unit* target, uint32 type, bool textEmote)
if (player) if (player)
{ {
PlayerbotAI* playerBotAI = GET_PLAYERBOT_AI(player); PlayerbotAI* playerBotAI = GET_PLAYERBOT_AI(player);
if (playerBotAI && !player->HasInArc(static_cast<float>(M_PI), bot, sPlayerbotAIConfig->sightDistance)) if (playerBotAI && !player->HasInArc(static_cast<float>(M_PI), bot, sPlayerbotAIConfig.sightDistance))
{ {
player->SetFacingToObject(bot); player->SetFacingToObject(bot);
} }
@@ -133,7 +132,7 @@ Unit* EmoteActionBase::GetTarget()
for (GuidVector::iterator i = nfp.begin(); i != nfp.end(); ++i) for (GuidVector::iterator i = nfp.begin(); i != nfp.end(); ++i)
{ {
Unit* unit = botAI->GetUnit(*i); Unit* unit = botAI->GetUnit(*i);
if (unit && sServerFacade->GetDistance2d(bot, unit) < sPlayerbotAIConfig->tooCloseDistance) if (unit && ServerFacade::instance().GetDistance2d(bot, unit) < sPlayerbotAIConfig.tooCloseDistance)
targets.push_back(unit); targets.push_back(unit);
} }
@@ -618,8 +617,8 @@ bool EmoteActionBase::ReceiveEmote(Player* source, uint32 emote, bool verbal)
break; break;
} }
if (source && !bot->isMoving() && !bot->HasInArc(static_cast<float>(M_PI), source, sPlayerbotAIConfig->farDistance)) if (source && !bot->isMoving() && !bot->HasInArc(static_cast<float>(M_PI), source, sPlayerbotAIConfig.farDistance))
sServerFacade->SetFacingTo(bot, source); ServerFacade::instance().SetFacingTo(bot, source);
if (verbal) if (verbal)
{ {
@@ -689,7 +688,7 @@ bool EmoteAction::Execute(Event event)
p >> emoteId >> source; p >> emoteId >> source;
pSource = ObjectAccessor::FindPlayer(source); pSource = ObjectAccessor::FindPlayer(source);
if (pSource && pSource != bot && sServerFacade->GetDistance2d(bot, pSource) < sPlayerbotAIConfig->farDistance && if (pSource && pSource != bot && ServerFacade::instance().GetDistance2d(bot, pSource) < sPlayerbotAIConfig.farDistance &&
emoteId != EMOTE_ONESHOT_NONE) emoteId != EMOTE_ONESHOT_NONE)
{ {
if ((pSource->GetGUID() != bot->GetGUID()) && if ((pSource->GetGUID() != bot->GetGUID()) &&
@@ -737,7 +736,7 @@ bool EmoteAction::Execute(Event event)
// time_t lastEmote = AI_VALUE2(time_t, "last emote", qualifier); //not used, line marked for removal. // time_t lastEmote = AI_VALUE2(time_t, "last emote", qualifier); //not used, line marked for removal.
botAI->GetAiObjectContext() botAI->GetAiObjectContext()
->GetValue<time_t>("last emote", qualifier) ->GetValue<time_t>("last emote", qualifier)
->Set(time(nullptr) + urand(1000, sPlayerbotAIConfig->repeatDelay) / 1000); ->Set(time(nullptr) + urand(1000, sPlayerbotAIConfig.repeatDelay) / 1000);
param = qualifier; param = qualifier;
} }
@@ -787,7 +786,7 @@ bool EmoteAction::isUseful()
return time(nullptr) >= lastEmote; return time(nullptr) >= lastEmote;
} }
bool TalkAction::Execute(Event event) bool TalkAction::Execute(Event /*event*/)
{ {
Unit* target = botAI->GetUnit(AI_VALUE(ObjectGuid, "talk target")); Unit* target = botAI->GetUnit(AI_VALUE(ObjectGuid, "talk target"));
if (!target) if (!target)

View File

@@ -85,8 +85,8 @@ void EquipAction::EquipItem(Item* item)
if (itemProto->Class == ITEM_CLASS_CONTAINER) if (itemProto->Class == ITEM_CLASS_CONTAINER)
{ {
// Attempt to equip as a bag // Attempt to equip as a bag
Bag* pBag = reinterpret_cast<Bag*>(item);
uint8 newBagSlot = GetSmallestBagSlot(); uint8 newBagSlot = GetSmallestBagSlot();
if (newBagSlot > 0) if (newBagSlot > 0)
{ {
uint16 src = ((bagIndex << 8) | slot); uint16 src = ((bagIndex << 8) | slot);
@@ -328,12 +328,48 @@ void EquipAction::EquipItem(Item* item)
botAI->TellMaster(out); botAI->TellMaster(out);
} }
bool EquipUpgradesAction::Execute(Event event) ItemIds EquipAction::SelectInventoryItemsToEquip()
{ {
if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot)) CollectItemsVisitor visitor;
return false; IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
if (event.GetSource() == "trade status") ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
continue;
ItemTemplate const* itemTemplate = item->GetTemplate();
if (!itemTemplate)
continue;
//TODO Expand to Glyphs and Gems, that can be placed in equipment
//Pre-filter non-equipable items
if (itemTemplate->InventoryType == INVTYPE_NON_EQUIP)
continue;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
else
itemUsageParam = std::to_string(itemId);
ItemUsage usage = AI_VALUE2(ItemUsage, "item upgrade", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
items.insert(itemId);
}
return items;
}
bool EquipUpgradesPacketAction::Execute(Event event)
{
if (!sPlayerbotAIConfig.autoEquipUpgradeLoot && !sRandomPlayerbotMgr.IsRandomBot(bot))
return false;
std::string const source = event.GetSource();
if (source == "trade status")
{ {
WorldPacket p(event.getPacket()); WorldPacket p(event.getPacket());
p.rpos(0); p.rpos(0);
@@ -344,7 +380,7 @@ bool EquipUpgradesAction::Execute(Event event)
return false; return false;
} }
if (event.GetSource() == "item push result") else if (source == "item push result")
{ {
WorldPacket p(event.getPacket()); WorldPacket p(event.getPacket());
p.rpos(0); p.rpos(0);
@@ -361,72 +397,18 @@ bool EquipUpgradesAction::Execute(Event event)
p >> itemId; p >> itemId;
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId); ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT) if (item->InventoryType == INVTYPE_NON_EQUIP)
return false; return false;
} }
CollectItemsVisitor visitor; ItemIds items = SelectInventoryItemsToEquip();
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
break;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
{
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
}
else
{
itemUsageParam = std::to_string(itemId);
}
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
{
items.insert(itemId);
}
}
EquipItems(items); EquipItems(items);
return true; return true;
} }
bool EquipUpgradeAction::Execute(Event event) bool EquipUpgradeAction::Execute(Event /*event*/)
{ {
CollectItemsVisitor visitor; ItemIds items = SelectInventoryItemsToEquip();
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
break;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
{
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
}
else
{
itemUsageParam = std::to_string(itemId);
}
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
{
items.insert(itemId);
}
}
EquipItems(items); EquipItems(items);
return true; return true;
} }

View File

@@ -8,6 +8,7 @@
#include "ChatHelper.h" #include "ChatHelper.h"
#include "InventoryAction.h" #include "InventoryAction.h"
#include "Item.h"
class FindItemVisitor; class FindItemVisitor;
class Item; class Item;
@@ -20,6 +21,7 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
void EquipItems(ItemIds ids); void EquipItems(ItemIds ids);
ItemIds SelectInventoryItemsToEquip();
private: private:
void EquipItem(FindItemVisitor* visitor); void EquipItem(FindItemVisitor* visitor);
@@ -27,10 +29,10 @@ private:
void EquipItem(Item* item); void EquipItem(Item* item);
}; };
class EquipUpgradesAction : public EquipAction class EquipUpgradesPacketAction : public EquipAction
{ {
public: public:
EquipUpgradesAction(PlayerbotAI* botAI, std::string const name = "equip upgrades") : EquipAction(botAI, name) {} explicit EquipUpgradesPacketAction(PlayerbotAI* botAI, std::string const name = "equip upgrades packet action") : EquipAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
@@ -38,7 +40,7 @@ public:
class EquipUpgradeAction : public EquipAction class EquipUpgradeAction : public EquipAction
{ {
public: public:
EquipUpgradeAction(PlayerbotAI* botAI, std::string const name = "equip upgrade") : EquipAction(botAI, name) {} explicit EquipUpgradeAction(PlayerbotAI* botAI, std::string const name = "equip upgrade") : EquipAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };

View File

@@ -246,7 +246,7 @@ WorldPosition FindFishingHole(PlayerbotAI* botAI)
return WorldPosition(); return WorldPosition();
} }
bool MoveNearWaterAction::Execute(Event event) bool MoveNearWaterAction::Execute(Event /*event*/)
{ {
WorldPosition landSpot = AI_VALUE(WorldPosition, "fishing spot"); WorldPosition landSpot = AI_VALUE(WorldPosition, "fishing spot");
if (landSpot.IsValid()) if (landSpot.IsValid())
@@ -262,7 +262,7 @@ bool MoveNearWaterAction::isUseful()
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot"); FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
WorldPosition pos = fishingSpotValueObject->Get(); WorldPosition pos = fishingSpotValueObject->Get();
return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) || return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) ||
bot->GetExactDist(&pos) < 0.1f; bot->GetExactDist(&pos) > 0.1f;
} }
@@ -272,9 +272,9 @@ bool MoveNearWaterAction::isPossible()
float fishingSearchWindow; float fishingSearchWindow;
if (master) if (master)
fishingSearchWindow = sPlayerbotAIConfig->fishingDistanceFromMaster; fishingSearchWindow = sPlayerbotAIConfig.fishingDistanceFromMaster;
else else
fishingSearchWindow = sPlayerbotAIConfig->fishingDistance; fishingSearchWindow = sPlayerbotAIConfig.fishingDistance;
WorldPosition fishingHole = FindFishingHole(botAI); WorldPosition fishingHole = FindFishingHole(botAI);
@@ -292,7 +292,6 @@ bool MoveNearWaterAction::isPossible()
// Water spot is out of range, lets look for a spot to move to for the fishing hole. // Water spot is out of range, lets look for a spot to move to for the fishing hole.
if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER) if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER)
{ {
float angle = bot->GetAngle(fishingHole.GetPositionX(), fishingHole.GetPositionY());
WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32); WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32);
if (landSpot.IsValid()) if (landSpot.IsValid())
{ {
@@ -323,7 +322,6 @@ bool MoveNearWaterAction::isPossible()
if (!water.IsValid()) if (!water.IsValid())
return false; return false;
bool hasLOS = bot->IsWithinLOS(water.GetPositionX(), water.GetPositionY(), water.GetPositionZ());
float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY()); float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY());
WorldPosition landSpot = WorldPosition landSpot =
FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false); FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false);
@@ -336,7 +334,7 @@ bool MoveNearWaterAction::isPossible()
return false; return false;
} }
bool EquipFishingPoleAction::Execute(Event event) bool EquipFishingPoleAction::Execute(Event /*event*/)
{ {
if (!_pole) if (!_pole)
return false; return false;
@@ -385,7 +383,7 @@ bool EquipFishingPoleAction::isUseful()
} }
} }
if (sRandomPlayerbotMgr->IsRandomBot(bot)) if (sRandomPlayerbotMgr.IsRandomBot(bot))
{ {
bot->StoreNewItemInBestSlots(FISHING_POLE, 1); // Try to get a fishing pole bot->StoreNewItemInBestSlots(FISHING_POLE, 1); // Try to get a fishing pole
return true; return true;
@@ -396,7 +394,7 @@ bool EquipFishingPoleAction::isUseful()
return false; return false;
std::string masterName = master->GetName(); std::string masterName = master->GetName();
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"no_fishing_pole_error", "I don't have a Fishing Pole",{}); "no_fishing_pole_error", "I don't have a Fishing Pole",{});
botAI->Whisper(text, masterName); botAI->Whisper(text, masterName);
@@ -463,7 +461,7 @@ bool UseBobberAction::isUseful()
return AI_VALUE(bool, "can use fishing bobber"); return AI_VALUE(bool, "can use fishing bobber");
} }
bool UseBobberAction::Execute(Event event) bool UseBobberAction::Execute(Event /*event*/)
{ {
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los"); GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
for (auto const& guid : gos) for (auto const& guid : gos)
@@ -485,7 +483,7 @@ bool UseBobberAction::Execute(Event event)
return false; return false;
} }
bool EndMasterFishingAction::Execute(Event event) bool EndMasterFishingAction::Execute(Event /*event*/)
{ {
botAI->ChangeStrategy("-master fishing", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("-master fishing", BOT_STATE_NON_COMBAT);
return true; return true;
@@ -499,11 +497,11 @@ bool EndMasterFishingAction::isUseful()
return false; return false;
WorldPosition nearWater = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), WorldPosition nearWater = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
bot->GetMap(), bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, sPlayerbotAIConfig->endFishingWithMaster, 10.0f); bot->GetMap(), bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, sPlayerbotAIConfig.endFishingWithMaster, 10.0f);
return !nearWater.IsValid(); return !nearWater.IsValid();
} }
bool RemoveBobberStrategyAction::Execute(Event event) bool RemoveBobberStrategyAction::Execute(Event /*event*/)
{ {
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT); botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
return true; return true;

View File

@@ -7,22 +7,24 @@
#define _PLAYERBOT_FISHINGACTION_H #define _PLAYERBOT_FISHINGACTION_H
#include "Action.h" #include "Action.h"
#include "MovementActions.h"
#include "Event.h" #include "Event.h"
#include "MovementActions.h"
#include "Playerbots.h" #include "Playerbots.h"
extern const uint32 FISHING_SPELL; extern const uint32 FISHING_SPELL;
extern const uint32 FISHING_POLE; extern const uint32 FISHING_POLE;
extern const uint32 FISHING_BOBBER; extern const uint32 FISHING_BOBBER;
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS=false, int numDirections = 16); WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance,
float maxDistance, float increment, bool checkLOS = false, int numDirections = 16);
class PlayerbotAI; class PlayerbotAI;
class FishingAction : public Action class FishingAction : public Action
{ {
public: public:
FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing"){} FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override; bool isUseful() override;
}; };
@@ -31,8 +33,10 @@ class EquipFishingPoleAction : public Action
{ {
public: public:
EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {} EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override; bool isUseful() override;
private: private:
Item* _pole = nullptr; Item* _pole = nullptr;
}; };
@@ -40,7 +44,8 @@ private:
class MoveNearWaterAction : public MovementAction class MoveNearWaterAction : public MovementAction
{ {
public: public:
MoveNearWaterAction(PlayerbotAI* botAI): MovementAction(botAI, "move near water") {} MoveNearWaterAction(PlayerbotAI* botAI) : MovementAction(botAI, "move near water") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override; bool isUseful() override;
bool isPossible() override; bool isPossible() override;
@@ -50,6 +55,7 @@ class UseBobberAction : public Action
{ {
public: public:
UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {} UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override; bool isUseful() override;
}; };
@@ -58,6 +64,7 @@ class EndMasterFishingAction : public Action
{ {
public: public:
EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {} EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override; bool isUseful() override;
}; };
@@ -66,6 +73,8 @@ class RemoveBobberStrategyAction : public Action
{ {
public: public:
RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {} RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
#endif #endif

View File

@@ -5,17 +5,14 @@
#include "FollowActions.h" #include "FollowActions.h"
#include <cstddef>
#include "Event.h" #include "Event.h"
#include "Formations.h" #include "Formations.h"
#include "LastMovementValue.h" #include "LastMovementValue.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h"
bool FollowAction::Execute(Event event) bool FollowAction::Execute(Event /*event*/)
{ {
Formation* formation = AI_VALUE(Formation*, "formation"); Formation* formation = AI_VALUE(Formation*, "formation");
std::string const target = formation->GetTargetName(); std::string const target = formation->GetTargetName();
@@ -44,7 +41,7 @@ bool FollowAction::Execute(Event event)
// botAI->PetFollow(); // botAI->PetFollow();
// } // }
// if (moved) // if (moved)
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); // botAI->SetNextCheckDelay(sPlayerbotAIConfig.reactDelay);
return moved; return moved;
} }
@@ -98,9 +95,9 @@ bool FollowAction::isUseful()
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ()); distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
} }
if (botAI->HasStrategy("master fishing", BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy("master fishing", BOT_STATE_NON_COMBAT))
return sServerFacade->IsDistanceGreaterThan(distance, sPlayerbotAIConfig->fishingDistanceFromMaster); return ServerFacade::instance().IsDistanceGreaterThan(distance, sPlayerbotAIConfig.fishingDistanceFromMaster);
return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance()); return ServerFacade::instance().IsDistanceGreaterThan(distance, formation->GetMaxDistance());
} }
bool FollowAction::CanDeadFollow(Unit* target) bool FollowAction::CanDeadFollow(Unit* target)
@@ -116,7 +113,7 @@ bool FollowAction::CanDeadFollow(Unit* target)
return true; return true;
} }
bool FleeToGroupLeaderAction::Execute(Event event) bool FleeToGroupLeaderAction::Execute(Event /*event*/)
{ {
Unit* fTarget = AI_VALUE(Unit*, "group leader"); Unit* fTarget = AI_VALUE(Unit*, "group leader");
bool canFollow = Follow(fTarget); bool canFollow = Follow(fTarget);
@@ -130,7 +127,7 @@ bool FleeToGroupLeaderAction::Execute(Event event)
WorldPosition bosPos(bot); WorldPosition bosPos(bot);
float distance = bosPos.fDist(targetPos); float distance = bosPos.fDist(targetPos);
if (distance < sPlayerbotAIConfig->reactDistance * 3) if (distance < sPlayerbotAIConfig.reactDistance * 3)
{ {
if (!urand(0, 3)) if (!urand(0, 3))
botAI->TellMaster("I am close, wait for me!"); botAI->TellMaster("I am close, wait for me!");

View File

@@ -11,8 +11,6 @@
#include "CreatureAI.h" #include "CreatureAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "CharmInfo.h" #include "CharmInfo.h"
#include "SharedDefines.h"
#include "ObjectGuid.h"
#include "SpellMgr.h" #include "SpellMgr.h"
#include "SpellInfo.h" #include "SpellInfo.h"
#include <vector> #include <vector>
@@ -54,7 +52,7 @@ bool MeleeAction::isUseful()
return true; return true;
} }
bool TogglePetSpellAutoCastAction::Execute(Event event) bool TogglePetSpellAutoCastAction::Execute(Event /*event*/)
{ {
Pet* pet = bot->GetPet(); Pet* pet = bot->GetPet();
if (!pet) if (!pet)
@@ -113,13 +111,13 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
} }
// Debug message if pet spells have been toggled and debug is enabled // Debug message if pet spells have been toggled and debug is enabled
if (toggled && sPlayerbotAIConfig->petChatCommandDebug == 1) if (toggled && sPlayerbotAIConfig.petChatCommandDebug == 1)
botAI->TellMaster("Pet autocast spells have been toggled."); botAI->TellMaster("Pet autocast spells have been toggled.");
return toggled; return toggled;
} }
bool PetAttackAction::Execute(Event event) bool PetAttackAction::Execute(Event /*event*/)
{ {
Guardian* pet = bot->GetGuardianPet(); Guardian* pet = bot->GetGuardianPet();
if (!pet) if (!pet)
@@ -185,7 +183,7 @@ bool SetPetStanceAction::Execute(Event /*event*/)
} }
// Get the default pet stance from the configuration // Get the default pet stance from the configuration
int32 stance = sPlayerbotAIConfig->defaultPetStance; int32 stance = sPlayerbotAIConfig.defaultPetStance;
ReactStates react = REACT_DEFENSIVE; ReactStates react = REACT_DEFENSIVE;
std::string stanceText = "defensive (from config, fallback)"; std::string stanceText = "defensive (from config, fallback)";
@@ -221,7 +219,7 @@ bool SetPetStanceAction::Execute(Event /*event*/)
} }
// If debug is enabled in config, inform the master of the new stance // If debug is enabled in config, inform the master of the new stance
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (sPlayerbotAIConfig.petChatCommandDebug == 1)
botAI->TellMaster("Pet stance set to " + stanceText + " (applied to all pets/guardians)."); botAI->TellMaster("Pet stance set to " + stanceText + " (applied to all pets/guardians).");
return true; return true;

View File

@@ -87,7 +87,7 @@ namespace ai::buff
{ {
std::string castName = baseName; std::string castName = baseName;
Group* g = bot->GetGroup(); Group* g = bot->GetGroup();
if (!g || g->GetMembersCount() < static_cast<uint32>(sPlayerbotAIConfig->minBotsForGreaterBuff)) if (!g || g->GetMembersCount() < static_cast<uint32>(sPlayerbotAIConfig.minBotsForGreaterBuff))
return castName; // Group too small: stay in solo mode return castName; // Group too small: stay in solo mode
if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty()) if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty())
@@ -114,7 +114,7 @@ namespace ai::buff
time_t now = std::time(nullptr); time_t now = std::time(nullptr);
uint32 botLow = static_cast<uint32>(bot->GetGUID().GetCounter()); uint32 botLow = static_cast<uint32>(bot->GetGUID().GetCounter());
time_t& last = s_lastWarn[ std::make_pair(botLow, groupName) ]; time_t& last = s_lastWarn[ std::make_pair(botLow, groupName) ];
if (!last || now - last >= sPlayerbotAIConfig->rpWarningCooldown) // Configurable anti-spam if (!last || now - last >= sPlayerbotAIConfig.rpWarningCooldown) // Configurable anti-spam
{ {
// DB Key choice in regard of the buff // DB Key choice in regard of the buff
std::string key; std::string key;
@@ -132,7 +132,7 @@ namespace ai::buff
placeholders["%group_spell"] = groupName; placeholders["%group_spell"] = groupName;
placeholders["%base_spell"] = baseName; placeholders["%base_spell"] = baseName;
std::string announceText = sPlayerbotTextMgr->GetBotTextOrDefault(key, std::string announceText = PlayerbotTextMgr::instance().GetBotTextOrDefault(key,
"Out of components for %group_spell. Using %base_spell!", placeholders); "Out of components for %group_spell. Using %base_spell!", placeholders);
announce(announceText); announce(announceText);

View File

@@ -17,19 +17,17 @@
#include "WorldPacket.h" #include "WorldPacket.h"
#include "Group.h" #include "Group.h"
#include "Chat.h" #include "Chat.h"
#include "Language.h"
#include "GenericBuffUtils.h" #include "GenericBuffUtils.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
using ai::buff::MakeAuraQualifierForBuff; using ai::buff::MakeAuraQualifierForBuff;
using ai::buff::UpgradeToGroupIfAppropriate;
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)
{ {
} }
bool CastSpellAction::Execute(Event event) bool CastSpellAction::Execute(Event /*event*/)
{ {
if (spell == "conjure food" || spell == "conjure water") if (spell == "conjure food" || spell == "conjure water")
{ {
@@ -78,34 +76,6 @@ bool CastSpellAction::Execute(Event event)
return botAI->CastSpell(spell, GetTarget()); return botAI->CastSpell(spell, GetTarget());
} }
bool CastSpellAction::isPossible()
{
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && botAI->HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Can cast spell failed. Vehicle. - bot name: {}", bot->GetName());
}
return false;
}
if (spell == "mount" && !bot->IsMounted() && !bot->IsInCombat())
return true;
if (spell == "mount" && bot->IsInCombat())
{
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && botAI->HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Can cast spell failed. Mount. - bot name: {}", bot->GetName());
}
bot->Dismount();
return false;
}
// Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL); //not used, line marked for removal.
return botAI->CanCastSpell(spell, GetTarget());
}
bool CastSpellAction::isUseful() bool CastSpellAction::isUseful()
{ {
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true)) if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
@@ -127,13 +97,40 @@ bool CastSpellAction::isUseful()
if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId()) if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
return false; return false;
// float combatReach = bot->GetCombatReach() + spellTarget->GetCombatReach(); // float combatReach = bot->GetCombatReach() + target->GetCombatReach();
// if (!botAI->IsRanged(bot)) // if (!botAI->IsRanged(bot))
// combatReach += 4.0f / 3.0f; // combatReach += 4.0f / 3.0f;
return spellTarget && return AI_VALUE2(bool, "spell cast useful", spell);
AI_VALUE2(bool, "spell cast useful", // && ServerFacade::instance().GetDistance2d(bot, target) <= (range + combatReach);
spell); // && sServerFacade->GetDistance2d(bot, spellTarget) <= (range + combatReach); }
bool CastSpellAction::isPossible()
{
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
{
if (!sPlayerbotAIConfig.logInGroupOnly || (bot->GetGroup() && botAI->HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Can cast spell failed. Vehicle. - bot name: {}", bot->GetName());
}
return false;
}
if (spell == "mount" && !bot->IsMounted() && !bot->IsInCombat())
return true;
if (spell == "mount" && bot->IsInCombat())
{
if (!sPlayerbotAIConfig.logInGroupOnly || (bot->GetGroup() && botAI->HasRealPlayerMaster()))
{
LOG_DEBUG("playerbots", "Can cast spell failed. Mount. - bot name: {}", bot->GetName());
}
bot->Dismount();
return false;
}
// Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL); //not used, line marked for removal.
return botAI->CanCastSpell(spell, GetTarget());
} }
CastMeleeSpellAction::CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) CastMeleeSpellAction::CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
@@ -233,7 +230,7 @@ Value<Unit*>* BuffOnPartyAction::GetTargetValue()
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell)); return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
} }
bool BuffOnPartyAction::Execute(Event event) bool BuffOnPartyAction::Execute(Event /*event*/)
{ {
std::string castName = spell; // default = mono std::string castName = spell; // default = mono
@@ -290,7 +287,7 @@ Value<Unit*>* CastSnareSpellAction::GetTargetValue() { return context->GetValue<
Value<Unit*>* CastCrowdControlSpellAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); } Value<Unit*>* CastCrowdControlSpellAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); }
bool CastCrowdControlSpellAction::Execute(Event event) { return botAI->CastSpell(getName(), GetTarget()); } bool CastCrowdControlSpellAction::Execute(Event /*event*/) { return botAI->CastSpell(getName(), GetTarget()); }
bool CastCrowdControlSpellAction::isPossible() { return botAI->CanCastSpell(getName(), GetTarget()); } bool CastCrowdControlSpellAction::isPossible() { return botAI->CanCastSpell(getName(), GetTarget()); }
@@ -308,13 +305,13 @@ bool CastVehicleSpellAction::isPossible()
bool CastVehicleSpellAction::isUseful() { return botAI->IsInVehicle(false, true); } bool CastVehicleSpellAction::isUseful() { return botAI->IsInVehicle(false, true); }
bool CastVehicleSpellAction::Execute(Event event) bool CastVehicleSpellAction::Execute(Event /*event*/)
{ {
uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", spell); uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", spell);
return botAI->CastVehicleSpell(spellId, GetTarget()); return botAI->CastVehicleSpell(spellId, GetTarget());
} }
bool UseTrinketAction::Execute(Event event) bool UseTrinketAction::Execute(Event /*event*/)
{ {
Item* trinket1 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET1); Item* trinket1 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET1);

View File

@@ -23,8 +23,8 @@ public:
std::string const GetTargetName() override { return "current target"; }; std::string const GetTargetName() override { return "current target"; };
bool Execute(Event event) override; bool Execute(Event event) override;
bool isPossible() override;
bool isUseful() override; bool isUseful() override;
bool isPossible() override;
ActionThreatType getThreatType() override { return ActionThreatType::Single; } ActionThreatType getThreatType() override { return ActionThreatType::Single; }
std::vector<NextAction> getPrerequisites() override std::vector<NextAction> getPrerequisites() override

View File

@@ -11,7 +11,7 @@
std::vector<std::string> split(std::string const s, char delim); std::vector<std::string> split(std::string const s, char delim);
bool GiveItemAction::Execute(Event event) bool GiveItemAction::Execute(Event /*event*/)
{ {
Unit* target = GetTarget(); Unit* target = GetTarget();
if (!target) if (!target)
@@ -28,7 +28,6 @@ bool GiveItemAction::Execute(Event event)
if (receiverAi->GetAiObjectContext()->GetValue<uint32>("item count", item)->Get()) if (receiverAi->GetAiObjectContext()->GetValue<uint32>("item count", item)->Get())
return true; return true;
bool moved = false;
std::vector<Item*> items = InventoryAction::parseItems(item, ITERATE_ITEMS_IN_BAGS); std::vector<Item*> items = InventoryAction::parseItems(item, ITERATE_ITEMS_IN_BAGS);
for (Item* item : items) for (Item* item : items)
{ {
@@ -42,7 +41,6 @@ bool GiveItemAction::Execute(Event event)
bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
item->SetOwnerGUID(target->GetGUID()); item->SetOwnerGUID(target->GetGUID());
receiver->MoveItemToInventory(dest, item, true); receiver->MoveItemToInventory(dest, item, true);
moved = true;
std::ostringstream out; std::ostringstream out;
out << "Got " << chat->FormatItem(item->GetTemplate(), item->GetCount()) << " from " << bot->GetName(); out << "Got " << chat->FormatItem(item->GetTemplate(), item->GetCount()) << " from " << bot->GetName();
@@ -64,7 +62,7 @@ Unit* GiveItemAction::GetTarget() { return AI_VALUE2(Unit*, "party member withou
bool GiveItemAction::isUseful() bool GiveItemAction::isUseful()
{ {
return GetTarget() && AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig->lowMana; return GetTarget() && AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.lowMana;
} }
Unit* GiveFoodAction::GetTarget() { return AI_VALUE(Unit*, "party member without food"); } Unit* GiveFoodAction::GetTarget() { return AI_VALUE(Unit*, "party member without food"); }
@@ -74,7 +72,7 @@ bool GiveFoodAction::isUseful()
if (!GetTarget()) if (!GetTarget())
return false; return false;
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget()); bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr.IsRandomBot((Player*)GetTarget());
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food)); return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
} }
@@ -86,7 +84,7 @@ bool GiveWaterAction::isUseful()
if (!GetTarget()) if (!GetTarget())
return false; return false;
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget()); bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr.IsRandomBot((Player*)GetTarget());
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food)); return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
} }

View File

@@ -61,7 +61,7 @@ bool GoAction::Execute(Event event)
else else
{ {
botAI->TellMasterNoFacing("Clearing travel target"); botAI->TellMasterNoFacing("Clearing travel target");
target->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition); target->setTarget(TravelMgr::instance().nullTravelDestination, TravelMgr::instance().nullWorldPosition);
target->setForced(false); target->setForced(false);
return true; return true;
} }
@@ -75,8 +75,8 @@ bool GoAction::Execute(Event event)
if (GameObject* go = botAI->GetGameObject(guid)) if (GameObject* go = botAI->GetGameObject(guid))
if (go->isSpawned()) if (go->isSpawned())
{ {
if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, go), if (ServerFacade::instance().IsDistanceGreaterThan(ServerFacade::instance().GetDistance2d(bot, go),
sPlayerbotAIConfig->reactDistance)) sPlayerbotAIConfig.reactDistance))
{ {
botAI->TellError("It is too far away"); botAI->TellError("It is too far away");
return false; return false;
@@ -86,7 +86,7 @@ bool GoAction::Execute(Event event)
out << "Moving to " << ChatHelper::FormatGameobject(go); out << "Moving to " << ChatHelper::FormatGameobject(go);
botAI->TellMasterNoFacing(out.str()); botAI->TellMasterNoFacing(out.str());
return MoveNear(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ() + 0.5f, return MoveNear(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ() + 0.5f,
sPlayerbotAIConfig->followDistance); sPlayerbotAIConfig.followDistance);
} }
} }
return false; return false;
@@ -106,7 +106,7 @@ bool GoAction::Execute(Event event)
out << "Moving to " << unit->GetName(); out << "Moving to " << unit->GetName();
botAI->TellMasterNoFacing(out.str()); botAI->TellMasterNoFacing(out.str());
return MoveNear(bot->GetMapId(), unit->GetPositionX(), unit->GetPositionY(), return MoveNear(bot->GetMapId(), unit->GetPositionX(), unit->GetPositionY(),
unit->GetPositionZ() + 0.5f, sPlayerbotAIConfig->followDistance); unit->GetPositionZ() + 0.5f, sPlayerbotAIConfig.followDistance);
} }
} }
@@ -176,8 +176,8 @@ bool GoAction::Execute(Event event)
float z = bot->GetPositionZ(); float z = bot->GetPositionZ();
bot->UpdateAllowedPositionZ(x, y, z); bot->UpdateAllowedPositionZ(x, y, z);
if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, x, y), if (ServerFacade::instance().IsDistanceGreaterThan(ServerFacade::instance().GetDistance2d(bot, x, y),
sPlayerbotAIConfig->reactDistance)) sPlayerbotAIConfig.reactDistance))
{ {
botAI->TellMaster("It is too far away"); botAI->TellMaster("It is too far away");
return false; return false;
@@ -203,14 +203,14 @@ bool GoAction::Execute(Event event)
out << "Moving to " << x1 << "," << y1; out << "Moving to " << x1 << "," << y1;
botAI->TellMasterNoFacing(out.str()); botAI->TellMasterNoFacing(out.str());
return MoveNear(bot->GetMapId(), x, y, z + 0.5f, sPlayerbotAIConfig->followDistance); return MoveNear(bot->GetMapId(), x, y, z + 0.5f, sPlayerbotAIConfig.followDistance);
} }
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[param]; PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[param];
if (pos.isSet()) if (pos.isSet())
{ {
if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, pos.x, pos.y), if (ServerFacade::instance().IsDistanceGreaterThan(ServerFacade::instance().GetDistance2d(bot, pos.x, pos.y),
sPlayerbotAIConfig->reactDistance)) sPlayerbotAIConfig.reactDistance))
{ {
botAI->TellError("It is too far away"); botAI->TellError("It is too far away");
return false; return false;
@@ -219,7 +219,7 @@ bool GoAction::Execute(Event event)
std::ostringstream out; std::ostringstream out;
out << "Moving to position " << param; out << "Moving to position " << param;
botAI->TellMasterNoFacing(out.str()); botAI->TellMasterNoFacing(out.str());
return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z + 0.5f, sPlayerbotAIConfig->followDistance); return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z + 0.5f, sPlayerbotAIConfig.followDistance);
} }
botAI->TellMaster("Whisper 'go x,y', 'go [game object]', 'go unit' or 'go position' and I will go there"); botAI->TellMaster("Whisper 'go x,y', 'go [game object]', 'go unit' or 'go position' and I will go there");

View File

@@ -10,7 +10,7 @@
GreetAction::GreetAction(PlayerbotAI* botAI) : Action(botAI, "greet") {} GreetAction::GreetAction(PlayerbotAI* botAI) : Action(botAI, "greet") {}
bool GreetAction::Execute(Event event) bool GreetAction::Execute(Event /*event*/)
{ {
ObjectGuid guid = AI_VALUE(ObjectGuid, "new player nearby"); ObjectGuid guid = AI_VALUE(ObjectGuid, "new player nearby");
if (!guid || !guid.IsPlayer()) if (!guid || !guid.IsPlayer())
@@ -20,7 +20,7 @@ bool GreetAction::Execute(Event event)
if (!player) if (!player)
return false; return false;
if (!bot->HasInArc(CAST_ANGLE_IN_FRONT, player, sPlayerbotAIConfig->sightDistance)) if (!bot->HasInArc(CAST_ANGLE_IN_FRONT, player, sPlayerbotAIConfig.sightDistance))
bot->SetFacingToObject(player); bot->SetFacingToObject(player);
ObjectGuid oldSel = bot->GetTarget(); ObjectGuid oldSel = bot->GetTarget();

View File

@@ -6,7 +6,8 @@
#include "GuildBankAction.h" #include "GuildBankAction.h"
#include "GuildMgr.h" #include "GuildMgr.h"
#include "Playerbots.h" #include "PlayerbotAI.h"
#include "AiObjectContext.h"
bool GuildBankAction::Execute(Event event) bool GuildBankAction::Execute(Event event)
{ {

View File

@@ -12,12 +12,11 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "RandomPlayerbotFactory.h" #include "RandomPlayerbotFactory.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h" // GOLD #include "SharedDefines.h"
bool BuyPetitionAction::Execute(Event event) bool BuyPetitionAction::Execute(Event /*event*/)
{ {
GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get(); GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
bool vendored = false, result = false;
for (GuidVector::iterator i = vendors.begin(); i != vendors.end(); ++i) for (GuidVector::iterator i = vendors.begin(); i != vendors.end(); ++i)
{ {
ObjectGuid vendorguid = *i; ObjectGuid vendorguid = *i;
@@ -97,7 +96,6 @@ bool BuyPetitionAction::canBuyPetition(Player* bot)
bool PetitionOfferAction::Execute(Event event) bool PetitionOfferAction::Execute(Event event)
{ {
uint32 petitionEntry = 5863; // GUILD_CHARTER
std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863)); std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863));
if (petitions.empty()) if (petitions.empty())
@@ -152,7 +150,7 @@ bool PetitionOfferAction::Execute(Event event)
bool PetitionOfferAction::isUseful() { return !bot->GetGuildId(); } bool PetitionOfferAction::isUseful() { return !bot->GetGuildId(); }
bool PetitionOfferNearbyAction::Execute(Event event) bool PetitionOfferNearbyAction::Execute(Event /*event*/)
{ {
uint32 found = 0; uint32 found = 0;
@@ -184,11 +182,11 @@ bool PetitionOfferNearbyAction::Execute(Event event)
} }
else else
{ {
if (!sPlayerbotAIConfig->randomBotGroupNearby) if (!sPlayerbotAIConfig.randomBotGroupNearby)
return false; return false;
} }
if (sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->sightDistance) if (ServerFacade::instance().GetDistance2d(bot, player) > sPlayerbotAIConfig.sightDistance)
continue; continue;
// Parse rpg target to quest action. // Parse rpg target to quest action.
@@ -209,10 +207,9 @@ bool PetitionOfferNearbyAction::isUseful()
AI_VALUE(uint8, "petition signs") < sWorld->getIntConfig(CONFIG_MIN_PETITION_SIGNS); AI_VALUE(uint8, "petition signs") < sWorld->getIntConfig(CONFIG_MIN_PETITION_SIGNS);
} }
bool PetitionTurnInAction::Execute(Event event) bool PetitionTurnInAction::Execute(Event /*event*/)
{ {
GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get(); GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
bool vendored = false, result = false;
std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863)); std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863));
if (petitions.empty()) if (petitions.empty())
@@ -297,7 +294,7 @@ bool PetitionTurnInAction::isUseful()
!context->GetValue<TravelTarget*>("travel target")->Get()->isTraveling(); !context->GetValue<TravelTarget*>("travel target")->Get()->isTraveling();
} }
bool BuyTabardAction::Execute(Event event) bool BuyTabardAction::Execute(Event /*event*/)
{ {
bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:")); bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:"));
if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976))) if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976)))

View File

@@ -128,7 +128,7 @@ bool GuildRemoveAction::PlayerIsValid(Player* member)
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member); return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
}; };
bool GuildManageNearbyAction::Execute(Event event) bool GuildManageNearbyAction::Execute(Event /*event*/)
{ {
uint32 found = 0; uint32 found = 0;
@@ -149,7 +149,6 @@ bool GuildManageNearbyAction::Execute(Event event)
// Promote or demote nearby members based on chance. // Promote or demote nearby members based on chance.
if (player->GetGuildId() && player->GetGuildId() == bot->GetGuildId()) if (player->GetGuildId() && player->GetGuildId() == bot->GetGuildId())
{ {
Guild::Member* member = guild->GetMember(player->GetGUID());
uint32 dCount = AI_VALUE(uint32, "death count"); uint32 dCount = AI_VALUE(uint32, "death count");
if (!urand(0, 30) && dCount < 2 && guild->GetRankRights(botMember->GetRankId()) & GR_RIGHT_PROMOTE) if (!urand(0, 30) && dCount < 2 && guild->GetRankRights(botMember->GetRankId()) & GR_RIGHT_PROMOTE)
@@ -171,7 +170,7 @@ bool GuildManageNearbyAction::Execute(Event event)
continue; continue;
} }
if (!sPlayerbotAIConfig->randomBotGuildNearby) if (!sPlayerbotAIConfig.randomBotGuildNearby)
return false; return false;
if (guild->GetMemberSize() > 1000) if (guild->GetMemberSize() > 1000)
@@ -185,7 +184,7 @@ bool GuildManageNearbyAction::Execute(Event event)
PlayerbotAI* botAi = GET_PLAYERBOT_AI(player); PlayerbotAI* botAi = GET_PLAYERBOT_AI(player);
if (!sPlayerbotAIConfig->randomBotInvitePlayer && botAi && botAi->IsRealPlayer()) if (!sPlayerbotAIConfig.randomBotInvitePlayer && botAi && botAi->IsRealPlayer())
continue; continue;
if (botAi) if (botAi)
@@ -193,16 +192,16 @@ bool GuildManageNearbyAction::Execute(Event event)
if (botAi->GetGuilderType() == GuilderType::SOLO && !botAi->HasRealPlayerMaster()) //Do not invite solo players. if (botAi->GetGuilderType() == GuilderType::SOLO && !botAi->HasRealPlayerMaster()) //Do not invite solo players.
continue; continue;
if (botAi->HasActivePlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(player)) //Do not invite alts of active players. if (botAi->HasActivePlayerMaster() && !sRandomPlayerbotMgr.IsRandomBot(player)) //Do not invite alts of active players.
continue; continue;
} }
bool sameGroup = bot->GetGroup() && bot->GetGroup()->IsMember(player->GetGUID()); bool sameGroup = bot->GetGroup() && bot->GetGroup()->IsMember(player->GetGUID());
if (!sameGroup && sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->spellDistance) if (!sameGroup && ServerFacade::instance().GetDistance2d(bot, player) > sPlayerbotAIConfig.spellDistance)
continue; continue;
if (sPlayerbotAIConfig->inviteChat && (sRandomPlayerbotMgr->IsRandomBot(bot) || !botAI->HasActivePlayerMaster())) if (sPlayerbotAIConfig.inviteChat && (sRandomPlayerbotMgr.IsRandomBot(bot) || !botAI->HasActivePlayerMaster()))
{ {
/* std::map<std::string, std::string> placeholders; /* std::map<std::string, std::string> placeholders;
placeholders["%name"] = player->GetName(); placeholders["%name"] = player->GetName();
@@ -210,8 +209,8 @@ bool GuildManageNearbyAction::Execute(Event event)
placeholders["%guildname"] = guild->GetName(); placeholders["%guildname"] = guild->GetName();
AreaTableEntry const* current_area = botAI->GetCurrentArea(); AreaTableEntry const* current_area = botAI->GetCurrentArea();
AreaTableEntry const* current_zone = botAI->GetCurrentZone(); AreaTableEntry const* current_zone = botAI->GetCurrentZone();
placeholders["%area_name"] = current_area ? current_area->area_name[BroadcastHelper::GetLocale()] : BOT_TEXT1("string_unknown_area"); placeholders["%area_name"] = current_area ? current_area->area_name[BroadcastHelper::GetLocale()] : PlayerbotTextMgr::instance().GetBotText("string_unknown_area");
placeholders["%zone_name"] = current_zone ? current_zone->area_name[BroadcastHelper::GetLocale()] : BOT_TEXT1("string_unknown_area"); placeholders["%zone_name"] = current_zone ? current_zone->area_name[BroadcastHelper::GetLocale()] : PlayerbotTextMgr::instance().GetBotText("string_unknown_area");
std::vector<std::string> lines; std::vector<std::string> lines;
@@ -219,45 +218,45 @@ bool GuildManageNearbyAction::Execute(Event event)
switch ((urand(0, 10) * urand(0, 10)) / 10) switch ((urand(0, 10) * urand(0, 10)) / 10)
{ {
case 0: case 0:
lines.push_back(BOT_TEXT2("Hey %name do you want to join my guild?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("Hey %name do you want to join my guild?", placeholders));
break; break;
case 1: case 1:
lines.push_back(BOT_TEXT2("Hey man you wanna join my guild %name?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("Hey man you wanna join my guild %name?", placeholders));
break; break;
case 2: case 2:
lines.push_back(BOT_TEXT2("I think you would be a good contribution to %guildname. Would you like to join %name?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("I think you would be a good contribution to %guildname. Would you like to join %name?", placeholders));
break; break;
case 3: case 3:
lines.push_back(BOT_TEXT2("My guild %guildname has %members quality members. Would you like to make it 1 more %name?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("My guild %guildname has %members quality members. Would you like to make it 1 more %name?", placeholders));
break; break;
case 4: case 4:
lines.push_back(BOT_TEXT2("Hey %name do you want to join %guildname? We have %members members and looking to become number 1 of the server.", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("Hey %name do you want to join %guildname? We have %members members and looking to become number 1 of the server.", placeholders));
break; break;
case 5: case 5:
lines.push_back(BOT_TEXT2("I'm not really good at smalltalk. Do you wanna join my guild %name/r?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("I'm not really good at smalltalk. Do you wanna join my guild %name/r?", placeholders));
break; break;
case 6: case 6:
lines.push_back(BOT_TEXT2("Welcome to %zone_name.... do you want to join my guild %name?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("Welcome to %zone_name.... do you want to join my guild %name?", placeholders));
break; break;
case 7: case 7:
lines.push_back(BOT_TEXT2("%name, you should join my guild!", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("%name, you should join my guild!", placeholders));
break; break;
case 8: case 8:
lines.push_back(BOT_TEXT2("%name, I got this guild....", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("%name, I got this guild....", placeholders));
break; break;
case 9: case 9:
lines.push_back(BOT_TEXT2("You are actually going to join my guild %name?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("You are actually going to join my guild %name?", placeholders));
lines.push_back(BOT_TEXT2("Haha.. you are the man! We are going to raid Molten...", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("Haha.. you are the man! We are going to raid Molten...", placeholders));
break; break;
case 10: case 10:
lines.push_back(BOT_TEXT2("Hey Hey! do you guys wanna join my gild????", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("Hey Hey! do you guys wanna join my gild????", placeholders));
lines.push_back(BOT_TEXT2("We've got a bunch of high levels and we are really super friendly..", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("We've got a bunch of high levels and we are really super friendly..", placeholders));
lines.push_back(BOT_TEXT2("..and watch your dog and do your homework...", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("..and watch your dog and do your homework...", placeholders));
lines.push_back(BOT_TEXT2("..and we raid once a week and are working on MC raids...", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("..and we raid once a week and are working on MC raids...", placeholders));
lines.push_back(BOT_TEXT2("..and we have more members than just me...", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("..and we have more members than just me...", placeholders));
lines.push_back(BOT_TEXT2("..and please stop I'm lonenly and we can get a ride the whole time...", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("..and please stop I'm lonenly and we can get a ride the whole time...", placeholders));
lines.push_back(BOT_TEXT2("..and it's really beautifull and I feel like crying...", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("..and it's really beautifull and I feel like crying...", placeholders));
lines.push_back(BOT_TEXT2("So what do you guys say are you going to join are you going to join?", placeholders)); lines.push_back(PlayerbotTextMgr::instance().GetBotText("So what do you guys say are you going to join are you going to join?", placeholders));
break; break;
} }
@@ -274,7 +273,7 @@ bool GuildManageNearbyAction::Execute(Event event)
if (botAI->DoSpecificAction("guild invite", Event("guild management", guid), true)) if (botAI->DoSpecificAction("guild invite", Event("guild management", guid), true))
{ {
if (sPlayerbotAIConfig->inviteChat) if (sPlayerbotAIConfig.inviteChat)
return true; return true;
found++; found++;
} }

View File

@@ -7,13 +7,13 @@
#include "ChatActionContext.h" #include "ChatActionContext.h"
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "AiObjectContext.h"
HelpAction::HelpAction(PlayerbotAI* botAI) : Action(botAI, "help") { chatContext = new ChatActionContext(); } HelpAction::HelpAction(PlayerbotAI* botAI) : Action(botAI, "help") { chatContext = new ChatActionContext(); }
HelpAction::~HelpAction() { delete chatContext; } HelpAction::~HelpAction() { delete chatContext; }
bool HelpAction::Execute(Event event) bool HelpAction::Execute(Event /*event*/)
{ {
TellChatCommands(); TellChatCommands();
TellStrategies(); TellStrategies();

View File

@@ -6,15 +6,16 @@
#include "HireAction.h" #include "HireAction.h"
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "RandomPlayerbotMgr.h"
#include "PlayerbotAI.h"
bool HireAction::Execute(Event event) bool HireAction::Execute(Event /*event*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
if (!master) if (!master)
return false; return false;
if (!sRandomPlayerbotMgr->IsRandomBot(bot)) if (!RandomPlayerbotMgr::instance().IsRandomBot(bot))
return false; return false;
uint32 account = master->GetSession()->GetAccountId(); uint32 account = master->GetSession()->GetAccountId();
@@ -39,7 +40,7 @@ bool HireAction::Execute(Event event)
return false; return false;
} }
uint32 discount = sRandomPlayerbotMgr->GetTradeDiscount(bot, master); uint32 discount = RandomPlayerbotMgr::instance().GetTradeDiscount(bot, master);
uint32 m = 1 + (bot->GetLevel() / 10); uint32 m = 1 + (bot->GetLevel() / 10);
uint32 moneyReq = m * 5000 * bot->GetLevel(); uint32 moneyReq = m * 5000 * bot->GetLevel();
if (discount < moneyReq) if (discount < moneyReq)
@@ -54,7 +55,7 @@ bool HireAction::Execute(Event event)
botAI->TellMaster("I will join you at your next relogin"); botAI->TellMaster("I will join you at your next relogin");
bot->SetMoney(moneyReq); bot->SetMoney(moneyReq);
sRandomPlayerbotMgr->Remove(bot); RandomPlayerbotMgr::instance().Remove(bot);
CharacterDatabase.Execute("UPDATE characters SET account = {} WHERE guid = {}", account, CharacterDatabase.Execute("UPDATE characters SET account = {} WHERE guid = {}", account,
bot->GetGUID().GetCounter()); bot->GetGUID().GetCounter());

View File

@@ -10,7 +10,7 @@
ImbueWithPoisonAction::ImbueWithPoisonAction(PlayerbotAI* botAI) : Action(botAI, "apply poison") {} ImbueWithPoisonAction::ImbueWithPoisonAction(PlayerbotAI* botAI) : Action(botAI, "apply poison") {}
bool ImbueWithPoisonAction::Execute(Event event) bool ImbueWithPoisonAction::Execute(Event /*event*/)
{ {
if (bot->IsInCombat()) if (bot->IsInCombat())
return false; return false;
@@ -103,7 +103,7 @@ bool ImbueWithPoisonAction::Execute(Event event)
// Search and apply stone to weapons // Search and apply stone to weapons
ImbueWithStoneAction::ImbueWithStoneAction(PlayerbotAI* botAI) : Action(botAI, "apply stone") {} ImbueWithStoneAction::ImbueWithStoneAction(PlayerbotAI* botAI) : Action(botAI, "apply stone") {}
bool ImbueWithStoneAction::Execute(Event event) bool ImbueWithStoneAction::Execute(Event /*event*/)
{ {
if (bot->IsInCombat()) if (bot->IsInCombat())
return false; return false;
@@ -148,7 +148,7 @@ bool ImbueWithStoneAction::Execute(Event event)
// Search and apply oil to weapons // Search and apply oil to weapons
ImbueWithOilAction::ImbueWithOilAction(PlayerbotAI* botAI) : Action(botAI, "apply oil") {} ImbueWithOilAction::ImbueWithOilAction(PlayerbotAI* botAI) : Action(botAI, "apply oil") {}
bool ImbueWithOilAction::Execute(Event event) bool ImbueWithOilAction::Execute(Event /*event*/)
{ {
if (bot->IsInCombat()) if (bot->IsInCombat())
return false; return false;
@@ -201,7 +201,7 @@ static const uint32 uPrioritizedHealingItemIds[19] = {
TryEmergencyAction::TryEmergencyAction(PlayerbotAI* botAI) : Action(botAI, "try emergency") {} TryEmergencyAction::TryEmergencyAction(PlayerbotAI* botAI) : Action(botAI, "try emergency") {}
bool TryEmergencyAction::Execute(Event event) bool TryEmergencyAction::Execute(Event /*event*/)
{ {
// Do not use consumable if bot can heal self // Do not use consumable if bot can heal self
if ((botAI->IsHeal(bot)) && (bot->GetPowerPct(POWER_MANA) > 20)) if ((botAI->IsHeal(bot)) && (bot->GetPowerPct(POWER_MANA) > 20))

View File

@@ -351,9 +351,7 @@ uint32 InventoryAction::GetItemCount(FindItemVisitor* visitor, IterateItemsMask
std::vector<Item*>& items = visitor->GetResult(); std::vector<Item*>& items = visitor->GetResult();
for (Item* item : items) for (Item* item : items)
{
count += item->GetCount(); count += item->GetCount();
}
return count; return count;
} }

View File

@@ -8,7 +8,6 @@
#include "BroadcastHelper.h" #include "BroadcastHelper.h"
#include "Event.h" #include "Event.h"
#include "GuildMgr.h" #include "GuildMgr.h"
#include "Log.h"
#include "PlayerbotOperations.h" #include "PlayerbotOperations.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PlayerbotWorldThreadProcessor.h" #include "PlayerbotWorldThreadProcessor.h"
@@ -31,7 +30,7 @@ bool InviteToGroupAction::Invite(Player* inviter, Player* player)
if (!group->isRaidGroup() && group->GetMembersCount() > 4) if (!group->isRaidGroup() && group->GetMembersCount() > 4)
{ {
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(inviter->GetGUID()); auto convertOp = std::make_unique<GroupConvertToRaidOperation>(inviter->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp)); PlayerbotWorldThreadProcessor::instance().QueueOperation(std::move(convertOp));
} }
} }
@@ -44,7 +43,7 @@ bool InviteToGroupAction::Invite(Player* inviter, Player* player)
return true; return true;
} }
bool InviteNearbyToGroupAction::Execute(Event event) bool InviteNearbyToGroupAction::Execute(Event /*event*/)
{ {
GuidVector nearGuids = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest friendly players")->Get(); GuidVector nearGuids = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest friendly players")->Get();
for (auto& i : nearGuids) for (auto& i : nearGuids)
@@ -62,7 +61,7 @@ bool InviteNearbyToGroupAction::Execute(Event event)
if (player->GetGroup()) if (player->GetGroup())
continue; continue;
if (!sPlayerbotAIConfig->randomBotInvitePlayer && GET_PLAYERBOT_AI(player)->IsRealPlayer()) if (!PlayerbotAIConfig::instance().randomBotInvitePlayer && GET_PLAYERBOT_AI(player)->IsRealPlayer())
continue; continue;
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
@@ -88,7 +87,7 @@ bool InviteNearbyToGroupAction::Execute(Event event)
if (abs(int32(player->GetLevel() - bot->GetLevel())) > 2) if (abs(int32(player->GetLevel() - bot->GetLevel())) > 2)
continue; continue;
if (sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->sightDistance) if (ServerFacade::instance().GetDistance2d(bot, player) > PlayerbotAIConfig::instance().sightDistance)
continue; continue;
// When inviting the 5th member of the group convert to raid for future invites. // When inviting the 5th member of the group convert to raid for future invites.
@@ -96,19 +95,19 @@ bool InviteNearbyToGroupAction::Execute(Event event)
bot->GetGroup()->GetMembersCount() > 3) bot->GetGroup()->GetMembersCount() > 3)
{ {
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID()); auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp)); PlayerbotWorldThreadProcessor::instance().QueueOperation(std::move(convertOp));
} }
if (sPlayerbotAIConfig->inviteChat && sRandomPlayerbotMgr->IsRandomBot(bot)) if (PlayerbotAIConfig::instance().inviteChat && RandomPlayerbotMgr::instance().IsRandomBot(bot))
{ {
std::map<std::string, std::string> placeholders; std::map<std::string, std::string> placeholders;
placeholders["%player"] = player->GetName(); placeholders["%player"] = player->GetName();
if (group && group->isRaidGroup()) if (group && group->isRaidGroup())
bot->Say(BOT_TEXT2("join_raid", placeholders), bot->Say(PlayerbotTextMgr::instance().GetBotText("join_raid", placeholders),
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
else else
bot->Say(BOT_TEXT2("join_group", placeholders), bot->Say(PlayerbotTextMgr::instance().GetBotText("join_group", placeholders),
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
} }
@@ -120,7 +119,7 @@ bool InviteNearbyToGroupAction::Execute(Event event)
bool InviteNearbyToGroupAction::isUseful() bool InviteNearbyToGroupAction::isUseful()
{ {
if (!sPlayerbotAIConfig->randomBotGroupNearby) if (!PlayerbotAIConfig::instance().randomBotGroupNearby)
return false; return false;
if (bot->InBattleground()) if (bot->InBattleground())
@@ -166,10 +165,8 @@ std::vector<Player*> InviteGuildToGroupAction::getGuildMembers()
return worker.GetResult(); return worker.GetResult();
} }
bool InviteGuildToGroupAction::Execute(Event event) bool InviteGuildToGroupAction::Execute(Event /*event*/)
{ {
Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId());
for (auto& member : getGuildMembers()) for (auto& member : getGuildMembers())
{ {
Player* player = member; Player* player = member;
@@ -186,7 +183,7 @@ bool InviteGuildToGroupAction::Execute(Event event)
if (player->isDND()) if (player->isDND())
continue; continue;
if (!sPlayerbotAIConfig->randomBotInvitePlayer && GET_PLAYERBOT_AI(player)->IsRealPlayer()) if (!PlayerbotAIConfig::instance().randomBotInvitePlayer && GET_PLAYERBOT_AI(player)->IsRealPlayer())
continue; continue;
if (player->IsBeingTeleported()) if (player->IsBeingTeleported())
@@ -221,7 +218,7 @@ bool InviteGuildToGroupAction::Execute(Event event)
player->GetLevel() + 5) // Do not invite members that too low level or risk dragging them to deadly places. player->GetLevel() + 5) // Do not invite members that too low level or risk dragging them to deadly places.
continue; continue;
if (!playerAi && sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->sightDistance) if (!playerAi && ServerFacade::instance().GetDistance2d(bot, player) > PlayerbotAIConfig::instance().sightDistance)
continue; continue;
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
@@ -230,11 +227,11 @@ bool InviteGuildToGroupAction::Execute(Event event)
bot->GetGroup()->GetMembersCount() > 3) bot->GetGroup()->GetMembersCount() > 3)
{ {
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID()); auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp)); PlayerbotWorldThreadProcessor::instance().QueueOperation(std::move(convertOp));
} }
if (sPlayerbotAIConfig->inviteChat && if (PlayerbotAIConfig::instance().inviteChat &&
(sRandomPlayerbotMgr->IsRandomBot(bot) || !botAI->HasActivePlayerMaster())) (RandomPlayerbotMgr::instance().IsRandomBot(bot) || !botAI->HasActivePlayerMaster()))
{ {
BroadcastHelper::BroadcastGuildGroupOrRaidInvite(botAI, bot, player, group); BroadcastHelper::BroadcastGuildGroupOrRaidInvite(botAI, bot, player, group);
} }
@@ -373,7 +370,7 @@ bool LfgAction::Execute(Event event)
else else
{ {
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(requester->GetGUID()); auto convertOp = std::make_unique<GroupConvertToRaidOperation>(requester->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp)); PlayerbotWorldThreadProcessor::instance().QueueOperation(std::move(convertOp));
} }
} }

View File

@@ -33,7 +33,7 @@ bool PartyCommandAction::Execute(Event event)
Player* master = GetMaster(); Player* master = GetMaster();
if (master && member == master->GetName()) if (master && member == master->GetName())
{ {
if (sRandomPlayerbotMgr->IsRandomBot(bot)) if (sRandomPlayerbotMgr.IsRandomBot(bot))
{ {
Player* newMaster = botAI->FindNewMaster(); Player* newMaster = botAI->FindNewMaster();
if (newMaster || bot->InBattleground()) if (newMaster || bot->InBattleground())
@@ -92,7 +92,7 @@ bool LeaveGroupAction::Leave()
return true; return true;
} }
bool LeaveFarAwayAction::Execute(Event event) bool LeaveFarAwayAction::Execute(Event /*event*/)
{ {
// allow bot to leave party when they want // allow bot to leave party when they want
return Leave(); return Leave();
@@ -150,7 +150,7 @@ bool LeaveFarAwayAction::isUseful()
if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4) if (abs(int32(groupLeader->GetLevel() - bot->GetLevel())) > 4)
return true; return true;
if (bot->GetMapId() != groupLeader->GetMapId() || bot->GetDistance2d(groupLeader) >= 2 * sPlayerbotAIConfig->rpgDistance) if (bot->GetMapId() != groupLeader->GetMapId() || bot->GetDistance2d(groupLeader) >= 2 * sPlayerbotAIConfig.rpgDistance)
{ {
return true; return true;
} }

View File

@@ -8,11 +8,11 @@
#include "AiFactory.h" #include "AiFactory.h"
#include "ItemVisitors.h" #include "ItemVisitors.h"
#include "LFGMgr.h" #include "LFGMgr.h"
#include "LFGPackets.h"
#include "Opcodes.h" #include "Opcodes.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "World.h" #include "World.h"
#include "WorldPacket.h" #include "WorldPacket.h"
#include "RandomPlayerbotMgr.h"
using namespace lfg; using namespace lfg;
@@ -20,7 +20,7 @@ bool LfgJoinAction::Execute(Event event) { return JoinLFG(); }
uint32 LfgJoinAction::GetRoles() uint32 LfgJoinAction::GetRoles()
{ {
if (!sRandomPlayerbotMgr->IsRandomBot(bot)) if (!RandomPlayerbotMgr::instance().IsRandomBot(bot))
{ {
if (botAI->IsTank(bot)) if (botAI->IsTank(bot))
return PLAYER_ROLE_TANK; return PLAYER_ROLE_TANK;
@@ -101,7 +101,7 @@ bool LfgJoinAction::JoinLFG()
LfgDungeonSet list; LfgDungeonSet list;
std::vector<uint32> selected; std::vector<uint32> selected;
std::vector<uint32> dungeons = sRandomPlayerbotMgr->LfgDungeons[bot->GetTeamId()]; std::vector<uint32> dungeons = RandomPlayerbotMgr::instance().LfgDungeons[bot->GetTeamId()];
if (!dungeons.size()) if (!dungeons.size())
return false; return false;
@@ -170,7 +170,7 @@ bool LfgJoinAction::JoinLFG()
return true; return true;
} }
bool LfgRoleCheckAction::Execute(Event event) bool LfgRoleCheckAction::Execute(Event /*event*/)
{ {
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())
{ {
@@ -216,9 +216,9 @@ bool LfgAcceptAction::Execute(Event event)
*packet << id << true; *packet << id << true;
bot->GetSession()->QueuePacket(packet); bot->GetSession()->QueuePacket(packet);
if (sRandomPlayerbotMgr->IsRandomBot(bot) && !bot->GetGroup()) if (RandomPlayerbotMgr::instance().IsRandomBot(bot) && !bot->GetGroup())
{ {
sRandomPlayerbotMgr->Refresh(bot); RandomPlayerbotMgr::instance().Refresh(bot);
botAI->ResetStrategies(); botAI->ResetStrategies();
} }
@@ -251,9 +251,9 @@ bool LfgAcceptAction::Execute(Event event)
*packet << id << true; *packet << id << true;
bot->GetSession()->QueuePacket(packet); bot->GetSession()->QueuePacket(packet);
if (sRandomPlayerbotMgr->IsRandomBot(bot) && !bot->GetGroup()) if (RandomPlayerbotMgr::instance().IsRandomBot(bot) && !bot->GetGroup())
{ {
sRandomPlayerbotMgr->Refresh(bot); RandomPlayerbotMgr::instance().Refresh(bot);
botAI->ResetStrategies(); botAI->ResetStrategies();
} }
@@ -265,7 +265,7 @@ bool LfgAcceptAction::Execute(Event event)
return false; return false;
} }
bool LfgLeaveAction::Execute(Event event) bool LfgLeaveAction::Execute(Event /*event*/)
{ {
// Don't leave if lfg strategy enabled // Don't leave if lfg strategy enabled
// if (botAI->HasStrategy("lfg", BOT_STATE_NON_COMBAT)) // if (botAI->HasStrategy("lfg", BOT_STATE_NON_COMBAT))
@@ -306,7 +306,7 @@ bool LfgTeleportAction::Execute(Event event)
bool LfgJoinAction::isUseful() bool LfgJoinAction::isUseful()
{ {
if (!sPlayerbotAIConfig->randomBotJoinLfg) if (!sPlayerbotAIConfig.randomBotJoinLfg)
{ {
// botAI->ChangeStrategy("-lfg", BOT_STATE_NON_COMBAT); // botAI->ChangeStrategy("-lfg", BOT_STATE_NON_COMBAT);
return false; return false;
@@ -337,7 +337,7 @@ bool LfgJoinAction::isUseful()
if (bot->isDead()) if (bot->isDead())
return false; return false;
if (!sRandomPlayerbotMgr->IsRandomBot(bot)) if (!RandomPlayerbotMgr::instance().IsRandomBot(bot))
return false; return false;
Map* map = bot->GetMap(); Map* map = bot->GetMap();

View File

@@ -107,14 +107,14 @@ uint32 ListQuestsAction::ListQuests(bool completed, bool silent, QuestTravelDeta
if (travelDetail == QUEST_TRAVEL_DETAIL_SUMMARY) if (travelDetail == QUEST_TRAVEL_DETAIL_SUMMARY)
{ {
std::vector<TravelDestination*> allDestinations = std::vector<TravelDestination*> allDestinations =
sTravelMgr->getQuestTravelDestinations(bot, questId, true, true, -1); TravelMgr::instance().getQuestTravelDestinations(bot, questId, true, true, -1);
std::vector<TravelDestination*> availDestinations = std::vector<TravelDestination*> availDestinations =
sTravelMgr->getQuestTravelDestinations(bot, questId, botAI->GetMaster(), false, -1); TravelMgr::instance().getQuestTravelDestinations(bot, questId, botAI->GetMaster(), false, -1);
uint32 desTot = allDestinations.size(); uint32 desTot = allDestinations.size();
uint32 desAvail = availDestinations.size(); uint32 desAvail = availDestinations.size();
uint32 desFull = desAvail - sTravelMgr->getQuestTravelDestinations(bot, questId, false, false, -1).size(); uint32 desFull = desAvail - TravelMgr::instance().getQuestTravelDestinations(bot, questId, false, false, -1).size();
uint32 desRange = desAvail - sTravelMgr->getQuestTravelDestinations(bot, questId, false, false).size(); uint32 desRange = desAvail - TravelMgr::instance().getQuestTravelDestinations(bot, questId, false, false).size();
uint32 tpoints = 0; uint32 tpoints = 0;
uint32 apoints = 0; uint32 apoints = 0;
@@ -140,7 +140,7 @@ uint32 ListQuestsAction::ListQuests(bool completed, bool silent, QuestTravelDeta
{ {
uint32 limit = 0; uint32 limit = 0;
std::vector<TravelDestination*> allDestinations = std::vector<TravelDestination*> allDestinations =
sTravelMgr->getQuestTravelDestinations(bot, questId, true, true, -1); TravelMgr::instance().getQuestTravelDestinations(bot, questId, true, true, -1);
std::sort(allDestinations.begin(), allDestinations.end(), std::sort(allDestinations.begin(), allDestinations.end(),
[botPos](TravelDestination* i, TravelDestination* j) { [botPos](TravelDestination* i, TravelDestination* j) {

View File

@@ -136,7 +136,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
if (spellInfo->IsPassive()) if (spellInfo->IsPassive())
continue; continue;
SkillLineAbilityEntry const* skillLine = sPlayerbotSpellRepository->GetSkillLine(itr->first); SkillLineAbilityEntry const* skillLine = PlayerbotSpellRepository::Instance().GetSkillLine(itr->first);
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill)) if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
continue; continue;
@@ -175,7 +175,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
FindItemByIdVisitor visitor(itemid); FindItemByIdVisitor visitor(itemid);
uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor); uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor);
bool buyable = sPlayerbotSpellRepository->IsItemBuyable(itemid); bool buyable = PlayerbotSpellRepository::Instance().IsItemBuyable(itemid);
if (!buyable) if (!buyable)
{ {
uint32 craftable = reagentsInInventory / reagentsRequired; uint32 craftable = reagentsInInventory / reagentsRequired;
@@ -303,4 +303,4 @@ bool ListSpellsAction::Execute(Event event)
botAI->TellMasterNoFacing(i->second); botAI->TellMasterNoFacing(i->second);
return true; return true;
} }

View File

@@ -25,7 +25,7 @@ bool LootAction::Execute(Event /*event*/)
LootObject prevLoot = AI_VALUE(LootObject, "loot target"); LootObject prevLoot = AI_VALUE(LootObject, "loot target");
LootObject const& lootObject = LootObject const& lootObject =
AI_VALUE(LootObjectStack*, "available loot")->GetLoot(sPlayerbotAIConfig->lootDistance); AI_VALUE(LootObjectStack*, "available loot")->GetLoot(sPlayerbotAIConfig.lootDistance);
if (!prevLoot.IsEmpty() && prevLoot.guid != lootObject.guid) if (!prevLoot.IsEmpty() && prevLoot.guid != lootObject.guid)
{ {
@@ -37,7 +37,7 @@ bool LootAction::Execute(Event /*event*/)
// Provide a system to check if the game object id is disallowed in the user configurable list or not. // Provide a system to check if the game object id is disallowed in the user configurable list or not.
// Check if the game object id is disallowed in the user configurable list or not. // Check if the game object id is disallowed in the user configurable list or not.
if (sPlayerbotAIConfig->disallowedGameObjects.find(lootObject.guid.GetEntry()) != sPlayerbotAIConfig->disallowedGameObjects.end()) if (sPlayerbotAIConfig.disallowedGameObjects.find(lootObject.guid.GetEntry()) != sPlayerbotAIConfig.disallowedGameObjects.end())
{ {
return false; // Game object ID is disallowed, so do not proceed return false; // Game object ID is disallowed, so do not proceed
} }
@@ -50,7 +50,7 @@ bool LootAction::Execute(Event /*event*/)
bool LootAction::isUseful() bool LootAction::isUseful()
{ {
return sPlayerbotAIConfig->freeMethodLoot || !bot->GetGroup() || bot->GetGroup()->GetLootMethod() != FREE_FOR_ALL; return sPlayerbotAIConfig.freeMethodLoot || !bot->GetGroup() || bot->GetGroup()->GetLootMethod() != FREE_FOR_ALL;
} }
enum ProfessionSpells enum ProfessionSpells
@@ -95,7 +95,7 @@ bool OpenLootAction::DoLoot(LootObject& lootObject)
if (bot->IsMounted()) if (bot->IsMounted())
{ {
bot->Dismount(); bot->Dismount();
botAI->SetNextCheckDelay(sPlayerbotAIConfig->lootDelay); // Small delay to avoid animation issues botAI->SetNextCheckDelay(sPlayerbotAIConfig.lootDelay); // Small delay to avoid animation issues
} }
if (creature && creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE)) if (creature && creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE))
@@ -104,7 +104,7 @@ bool OpenLootAction::DoLoot(LootObject& lootObject)
*packet << lootObject.guid; *packet << lootObject.guid;
bot->GetSession()->QueuePacket(packet); bot->GetSession()->QueuePacket(packet);
// bot->GetSession()->HandleLootOpcode(packet); // bot->GetSession()->HandleLootOpcode(packet);
botAI->SetNextCheckDelay(sPlayerbotAIConfig->lootDelay); botAI->SetNextCheckDelay(sPlayerbotAIConfig.lootDelay);
return true; return true;
} }
@@ -202,7 +202,7 @@ uint32 OpenLootAction::GetOpeningSpell(LootObject& lootObject, GameObject* go)
return spellId; return spellId;
} }
return sPlayerbotAIConfig->openGoSpell; return sPlayerbotAIConfig.openGoSpell;
} }
bool OpenLootAction::CanOpenLock(LootObject& /*lootObject*/, SpellInfo const* spellInfo, GameObject* go) bool OpenLootAction::CanOpenLock(LootObject& /*lootObject*/, SpellInfo const* spellInfo, GameObject* go)
@@ -294,7 +294,7 @@ bool StoreLootAction::AuctionItem(uint32 itemId)
AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(ahEntry); AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(ahEntry);
uint32 price = oldItem->GetCount() * proto->BuyPrice * sRandomPlayerbotMgr->GetBuyMultiplier(bot); uint32 price = oldItem->GetCount() * proto->BuyPrice * sRandomPlayerbotMgr.GetBuyMultiplier(bot);
uint32 stackCount = urand(1, proto->GetMaxStackSize()); uint32 stackCount = urand(1, proto->GetMaxStackSize());
if (!price || !stackCount) if (!price || !stackCount)
@@ -426,28 +426,28 @@ bool StoreLootAction::Execute(Event event)
} }
Player* master = botAI->GetMaster(); Player* master = botAI->GetMaster();
if (sRandomPlayerbotMgr->IsRandomBot(bot) && master) if (sRandomPlayerbotMgr.IsRandomBot(bot) && master)
{ {
uint32 price = itemcount * proto->BuyPrice * sRandomPlayerbotMgr->GetBuyMultiplier(bot) + gold; uint32 price = itemcount * proto->BuyPrice * sRandomPlayerbotMgr.GetBuyMultiplier(bot) + gold;
if (price) if (price)
sRandomPlayerbotMgr->AddTradeDiscount(bot, master, price); sRandomPlayerbotMgr.AddTradeDiscount(bot, master, price);
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
if (ref->GetSource() != bot) if (ref->GetSource() != bot)
sGuildTaskMgr->CheckItemTask(itemid, itemcount, ref->GetSource(), bot); GuildTaskMgr::instance().CheckItemTask(itemid, itemcount, ref->GetSource(), bot);
} }
WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1);
*packet << itemindex; *packet << itemindex;
bot->GetSession()->QueuePacket(packet); bot->GetSession()->QueuePacket(packet);
// bot->GetSession()->HandleAutostoreLootItemOpcode(packet); // bot->GetSession()->HandleAutostoreLootItemOpcode(packet);
botAI->SetNextCheckDelay(sPlayerbotAIConfig->lootDelay); botAI->SetNextCheckDelay(sPlayerbotAIConfig.lootDelay);
if (proto->Quality > ITEM_QUALITY_NORMAL && !urand(0, 50) && botAI->HasStrategy("emote", BOT_STATE_NON_COMBAT) && sPlayerbotAIConfig->randomBotEmote) if (proto->Quality > ITEM_QUALITY_NORMAL && !urand(0, 50) && botAI->HasStrategy("emote", BOT_STATE_NON_COMBAT) && sPlayerbotAIConfig.randomBotEmote)
botAI->PlayEmote(TEXT_EMOTE_CHEER); botAI->PlayEmote(TEXT_EMOTE_CHEER);
if (proto->Quality >= ITEM_QUALITY_RARE && !urand(0, 1) && botAI->HasStrategy("emote", BOT_STATE_NON_COMBAT) && sPlayerbotAIConfig->randomBotEmote) if (proto->Quality >= ITEM_QUALITY_RARE && !urand(0, 1) && botAI->HasStrategy("emote", BOT_STATE_NON_COMBAT) && sPlayerbotAIConfig.randomBotEmote)
botAI->PlayEmote(TEXT_EMOTE_CHEER); botAI->PlayEmote(TEXT_EMOTE_CHEER);
BroadcastHelper::BroadcastLootingItem(botAI, bot, proto); BroadcastHelper::BroadcastLootingItem(botAI, bot, proto);
@@ -498,7 +498,7 @@ bool StoreLootAction::IsLootAllowed(uint32 itemid, PlayerbotAI* botAI)
{ {
// if (AI_VALUE2(uint32, "item count", proto->Name1) < quest->RequiredItemCount[i]) // if (AI_VALUE2(uint32, "item count", proto->Name1) < quest->RequiredItemCount[i])
// { // {
// if (botAI->GetMaster() && sPlayerbotAIConfig->syncQuestWithPlayer) // if (botAI->GetMaster() && sPlayerbotAIConfig.syncQuestWithPlayer)
// return false; //Quest is autocomplete for the bot so no item needed. // return false; //Quest is autocomplete for the bot so no item needed.
// } // }
@@ -514,7 +514,7 @@ bool StoreLootAction::IsLootAllowed(uint32 itemid, PlayerbotAI* botAI)
bool canLoot = lootStrategy->CanLoot(proto, context); bool canLoot = lootStrategy->CanLoot(proto, context);
// if (canLoot && proto->Bonding == BIND_WHEN_PICKED_UP && botAI->HasActivePlayerMaster()) // if (canLoot && proto->Bonding == BIND_WHEN_PICKED_UP && botAI->HasActivePlayerMaster())
// canLoot = sPlayerbotAIConfig->IsInRandomAccountList(botAI->GetBot()->GetSession()->GetAccountId()); // canLoot = sPlayerbotAIConfig.IsInRandomAccountList(botAI->GetBot()->GetSession()->GetAccountId());
return canLoot; return canLoot;
} }

View File

@@ -13,7 +13,7 @@
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "Playerbots.h" #include "Playerbots.h"
bool LootRollAction::Execute(Event event) bool LootRollAction::Execute(Event /*event*/)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
@@ -27,7 +27,6 @@ bool LootRollAction::Execute(Event event)
continue; continue;
} }
ObjectGuid guid = roll->itemGUID; ObjectGuid guid = roll->itemGUID;
uint32 slot = roll->itemSlot;
uint32 itemId = roll->itemid; uint32 itemId = roll->itemid;
int32 randomProperty = 0; int32 randomProperty = 0;
if (roll->itemRandomPropId) if (roll->itemRandomPropId)
@@ -84,12 +83,14 @@ bool LootRollAction::Execute(Event event)
break; break;
} }
} }
if (sPlayerbotAIConfig->lootRollLevel == 0) if (sPlayerbotAIConfig.lootRollLevel == 0)
{ {
vote = PASS; vote = PASS;
} }
else if (sPlayerbotAIConfig->lootRollLevel == 1) else if (sPlayerbotAIConfig.lootRollLevel == 1)
{ {
// Level 1 = "greed" mode: bots greed on useful items but never need
// Only downgrade NEED to GREED, preserve GREED votes as-is
if (vote == NEED) if (vote == NEED)
{ {
if (RollUniqueCheck(proto, bot)) if (RollUniqueCheck(proto, bot))
@@ -101,10 +102,6 @@ bool LootRollAction::Execute(Event event)
vote = GREED; vote = GREED;
} }
} }
else if (vote == GREED)
{
vote = PASS;
}
} }
switch (group->GetLootMethod()) switch (group->GetLootMethod())
{ {
@@ -186,7 +183,6 @@ bool MasterLootRollAction::Execute(Event event)
if (!group) if (!group)
return false; return false;
RollVote vote = CalculateRollVote(proto);
group->CountRollVote(bot->GetGUID(), creatureGuid, CalculateRollVote(proto)); group->CountRollVote(bot->GetGUID(), creatureGuid, CalculateRollVote(proto));
return true; return true;

View File

@@ -16,7 +16,6 @@ bool LootStrategyAction::Execute(Event event)
{ {
std::string const strategy = event.getParam(); std::string const strategy = event.getParam();
LootObjectStack* lootItems = AI_VALUE(LootObjectStack*, "available loot");
std::set<uint32>& alwaysLootItems = AI_VALUE(std::set<uint32>&, "always loot list"); std::set<uint32>& alwaysLootItems = AI_VALUE(std::set<uint32>&, "always loot list");
Value<LootStrategy*>* lootStrategy = context->GetValue<LootStrategy*>("loot strategy"); Value<LootStrategy*>* lootStrategy = context->GetValue<LootStrategy*>("loot strategy");

View File

@@ -134,7 +134,7 @@ public:
private: private:
bool CheckBagSpace(Player* bot) bool CheckBagSpace(Player* bot)
{ {
uint32 totalused = 0, total = 16; uint32 totalused = 0;
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++)
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
++totalused; ++totalused;

View File

@@ -11,20 +11,16 @@
#include "LastMovementValue.h" #include "LastMovementValue.h"
#include "Playerbots.h" #include "Playerbots.h"
bool MoveToRpgTargetAction::Execute(Event event) bool MoveToRpgTargetAction::Execute(Event /*event*/)
{ {
GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target"); GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target");
Unit* unit = botAI->GetUnit(guidP); Unit* unit = botAI->GetUnit(guidP);
if (unit && !unit->IsInWorld()) if (unit && !unit->IsInWorld())
{
return false; return false;
}
GameObject* go = botAI->GetGameObject(guidP); GameObject* go = botAI->GetGameObject(guidP);
if (go && !go->IsInWorld()) if (go && !go->IsInWorld())
{
return false; return false;
}
Player* player = guidP.GetPlayer();
WorldObject* wo = nullptr; WorldObject* wo = nullptr;
if (unit) if (unit)
@@ -61,7 +57,7 @@ bool MoveToRpgTargetAction::Execute(Event event)
} }
if ((unit && unit->isMoving() && !urand(0, 20)) || !ChooseRpgTargetAction::isFollowValid(bot, wo) || if ((unit && unit->isMoving() && !urand(0, 20)) || !ChooseRpgTargetAction::isFollowValid(bot, wo) ||
guidP.distance(bot) > sPlayerbotAIConfig->reactDistance * 2 || !urand(0, 50)) guidP.distance(bot) > sPlayerbotAIConfig.reactDistance * 2 || !urand(0, 50))
{ {
AI_VALUE(GuidSet&, "ignore rpg target").insert(AI_VALUE(GuidPosition, "rpg target")); AI_VALUE(GuidSet&, "ignore rpg target").insert(AI_VALUE(GuidPosition, "rpg target"));
RESET_AI_VALUE(GuidPosition, "rpg target"); RESET_AI_VALUE(GuidPosition, "rpg target");
@@ -73,7 +69,7 @@ bool MoveToRpgTargetAction::Execute(Event event)
float z = wo->GetPositionZ(); float z = wo->GetPositionZ();
float mapId = wo->GetMapId(); float mapId = wo->GetMapId();
if (sPlayerbotAIConfig->randombotsWalkingRPG) if (sPlayerbotAIConfig.randombotsWalkingRPG)
if (!bot->IsOutdoors()) if (!bot->IsOutdoors())
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_WALKING); bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_WALKING);

View File

@@ -7,10 +7,9 @@
#include "ChooseRpgTargetAction.h" #include "ChooseRpgTargetAction.h"
#include "LootObjectStack.h" #include "LootObjectStack.h"
#include "PathGenerator.h"
#include "Playerbots.h" #include "Playerbots.h"
bool MoveToTravelTargetAction::Execute(Event event) bool MoveToTravelTargetAction::Execute(Event /*event*/)
{ {
TravelTarget* target = AI_VALUE(TravelTarget*, "travel target"); TravelTarget* target = AI_VALUE(TravelTarget*, "travel target");
@@ -43,7 +42,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
if (memberDistance < 50.0f) if (memberDistance < 50.0f)
continue; continue;
if (memberDistance > sPlayerbotAIConfig->reactDistance * 20) if (memberDistance > sPlayerbotAIConfig.reactDistance * 20)
continue; continue;
// float memberAngle = botLocation.getAngleBetween(targetPos, memberPos); // float memberAngle = botLocation.getAngleBetween(targetPos, memberPos);
@@ -65,9 +64,9 @@ bool MoveToTravelTargetAction::Execute(Event event)
botAI->TellMasterNoFacing(out); botAI->TellMasterNoFacing(out);
} }
target->setExpireIn(target->getTimeLeft() + sPlayerbotAIConfig->maxWaitForMove); target->setExpireIn(target->getTimeLeft() + sPlayerbotAIConfig.maxWaitForMove);
botAI->SetNextCheckDelay(sPlayerbotAIConfig->maxWaitForMove); botAI->SetNextCheckDelay(sPlayerbotAIConfig.maxWaitForMove);
return true; return true;
} }
@@ -80,7 +79,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up. if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up.
{ {
// distance = sPlayerbotAIConfig->fleeDistance; // distance = sPlayerbotAIConfig.fleeDistance;
// angle = bot->GetAngle(location.GetPositionX(), location.GetPositionY()); // angle = bot->GetAngle(location.GetPositionX(), location.GetPositionY());
// location = botLocation.getLocation(); // location = botLocation.getLocation();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ public:
protected: protected:
bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance, bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig.contactDistance,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveToLOS(WorldObject* target, bool ranged = false); bool MoveToLOS(WorldObject* target, bool ranged = false);
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false, bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
@@ -38,10 +38,10 @@ protected:
bool backwards = false); bool backwards = false);
bool MoveTo(WorldObject* target, float distance = 0.0f, bool MoveTo(WorldObject* target, float distance = 0.0f,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig.contactDistance,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
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, float angle = 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);
@@ -56,10 +56,10 @@ protected:
bool Flee(Unit* target); bool Flee(Unit* target);
void ClearIdleState(); void ClearIdleState();
void UpdateMovementState(); void UpdateMovementState();
bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig->fleeDistance, bool backwards = false); bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig.fleeDistance, bool backwards = false);
bool MoveFromGroup(float distance); bool MoveFromGroup(float distance);
bool Move(float angle, float distance); bool Move(float angle, float distance);
bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance, bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig.followDistance,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false); void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false);
Position BestPositionForMeleeToFlee(Position pos, float radius); Position BestPositionForMeleeToFlee(Position pos, float radius);
@@ -86,7 +86,7 @@ private:
class FleeAction : public MovementAction class FleeAction : public MovementAction
{ {
public: public:
FleeAction(PlayerbotAI* botAI, float distance = sPlayerbotAIConfig->spellDistance) FleeAction(PlayerbotAI* botAI, float distance = sPlayerbotAIConfig.spellDistance)
: MovementAction(botAI, "flee"), distance(distance) : MovementAction(botAI, "flee"), distance(distance)
{ {
} }
@@ -138,8 +138,8 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
protected: protected:
Position AverageGroupPos(float dis = sPlayerbotAIConfig->sightDistance, bool ranged = false, bool self = false); Position AverageGroupPos(float dis = sPlayerbotAIConfig.sightDistance, bool ranged = false, bool self = false);
Player* NearestGroupMember(float dis = sPlayerbotAIConfig->sightDistance); Player* NearestGroupMember(float dis = sPlayerbotAIConfig.sightDistance);
float AverageGroupAngle(Unit* from, bool ranged = false, bool self = false); float AverageGroupAngle(Unit* from, bool ranged = false, bool self = false);
Position GetNearestPosition(const std::vector<Position>& positions); Position GetNearestPosition(const std::vector<Position>& positions);
int lastMoveTimer = 0; int lastMoveTimer = 0;
@@ -332,7 +332,6 @@ public:
private: private:
uint32 spellId; uint32 spellId;
float range; float range;
bool alive;
}; };
#endif #endif

View File

@@ -49,18 +49,16 @@ bool DrinkAction::Execute(Event event)
bool DrinkAction::isUseful() bool DrinkAction::isUseful()
{ {
return UseItemAction::isUseful() && return UseItemAction::isUseful() && AI_VALUE2(bool, "has mana", "self target") &&
AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < 100;
AI_VALUE2(uint8, "mana", "self target") < 100;
} }
bool DrinkAction::isPossible() bool DrinkAction::isPossible()
{ {
return !bot->IsInCombat() && return !bot->IsInCombat() && !bot->IsMounted() &&
!bot->IsMounted() && !botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "aquatic form",
!botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "flight form", "swift flight form", nullptr) &&
"aquatic form","flight form", "swift flight form", nullptr) && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
(botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
} }
bool EatAction::Execute(Event event) bool EatAction::Execute(Event event)
@@ -76,7 +74,7 @@ bool EatAction::Execute(Event event)
if (bot->isMoving()) if (bot->isMoving())
{ {
bot->StopMoving(); bot->StopMoving();
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown); // botAI->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown);
// return false; // return false;
} }
@@ -102,17 +100,12 @@ bool EatAction::Execute(Event event)
return UseItemAction::Execute(event); return UseItemAction::Execute(event);
} }
bool EatAction::isUseful() bool EatAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "health", "self target") < 100; }
{
return UseItemAction::isUseful() &&
AI_VALUE2(uint8, "health", "self target") < 100;
}
bool EatAction::isPossible() bool EatAction::isPossible()
{ {
return !bot->IsInCombat() && return !bot->IsInCombat() && !bot->IsMounted() &&
!bot->IsMounted() && !botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "aquatic form",
!botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "flight form", "swift flight form", nullptr) &&
"aquatic form","flight form", "swift flight form", nullptr) && (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
(botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible());
} }

View File

@@ -7,7 +7,7 @@
#include "LootObjectStack.h" #include "LootObjectStack.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"
bool OpenItemAction::Execute(Event event) bool OpenItemAction::Execute(Event /*event*/)
{ {
bool foundOpenable = false; bool foundOpenable = false;

View File

@@ -7,21 +7,20 @@
#include "Event.h" #include "Event.h"
#include "PlayerbotOperations.h" #include "PlayerbotOperations.h"
#include "Playerbots.h"
#include "PlayerbotWorldThreadProcessor.h" #include "PlayerbotWorldThreadProcessor.h"
bool PassLeadershipToMasterAction::Execute(Event event) bool PassLeadershipToMasterAction::Execute(Event /*event*/)
{ {
if (Player* master = GetMaster()) if (Player* master = GetMaster())
if (master && master != bot && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetGUID())) if (master && master != bot && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetGUID()))
{ {
auto setLeaderOp = std::make_unique<GroupSetLeaderOperation>(bot->GetGUID(), master->GetGUID()); auto setLeaderOp = std::make_unique<GroupSetLeaderOperation>(bot->GetGUID(), master->GetGUID());
sPlayerbotWorldProcessor->QueueOperation(std::move(setLeaderOp)); PlayerbotWorldThreadProcessor::instance().QueueOperation(std::move(setLeaderOp));
if (!message.empty()) if (!message.empty())
botAI->TellMasterNoFacing(message); botAI->TellMasterNoFacing(message);
if (sRandomPlayerbotMgr->IsRandomBot(bot)) if (sRandomPlayerbotMgr.IsRandomBot(bot))
{ {
botAI->ResetStrategies(); botAI->ResetStrategies();
botAI->Reset(); botAI->Reset();

View File

@@ -23,7 +23,7 @@ bool PetsAction::Execute(Event event)
if (param.empty()) if (param.empty())
{ {
// If no parameter is provided, show usage instructions and return. // If no parameter is provided, show usage instructions and return.
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {}); "pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {});
botAI->TellError(text); botAI->TellError(text);
return false; return false;
@@ -52,7 +52,7 @@ bool PetsAction::Execute(Event event)
// If no pets or guardians are found, notify and return. // If no pets or guardians are found, notify and return.
if (targets.empty()) if (targets.empty())
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_no_pet_error", "You have no pet or guardian pet.", {}); "pet_no_pet_error", "You have no pet or guardian pet.", {});
botAI->TellError(text); botAI->TellError(text);
return false; return false;
@@ -65,19 +65,19 @@ bool PetsAction::Execute(Event event)
if (param == "aggressive") if (param == "aggressive")
{ {
react = REACT_AGGRESSIVE; react = REACT_AGGRESSIVE;
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( stanceText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_aggressive", "aggressive", {}); "pet_stance_aggressive", "aggressive", {});
} }
else if (param == "defensive") else if (param == "defensive")
{ {
react = REACT_DEFENSIVE; react = REACT_DEFENSIVE;
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( stanceText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_defensive", "defensive", {}); "pet_stance_defensive", "defensive", {});
} }
else if (param == "passive") else if (param == "passive")
{ {
react = REACT_PASSIVE; react = REACT_PASSIVE;
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( stanceText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_passive", "passive", {}); "pet_stance_passive", "passive", {});
} }
// The "stance" command simply reports the current stance of each pet/guardian. // The "stance" command simply reports the current stance of each pet/guardian.
@@ -86,30 +86,30 @@ bool PetsAction::Execute(Event event)
for (Creature* target : targets) for (Creature* target : targets)
{ {
std::string type = target->IsPet() ? std::string type = target->IsPet() ?
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) : PlayerbotTextMgr::instance().GetBotTextOrDefault("pet_type_pet", "pet", {}) :
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {}); PlayerbotTextMgr::instance().GetBotTextOrDefault("pet_type_guardian", "guardian", {});
std::string name = target->GetName(); std::string name = target->GetName();
std::string stance; std::string stance;
switch (target->GetReactState()) switch (target->GetReactState())
{ {
case REACT_AGGRESSIVE: case REACT_AGGRESSIVE:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_aggressive", "aggressive", {}); "pet_stance_aggressive", "aggressive", {});
break; break;
case REACT_DEFENSIVE: case REACT_DEFENSIVE:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_defensive", "defensive", {}); "pet_stance_defensive", "defensive", {});
break; break;
case REACT_PASSIVE: case REACT_PASSIVE:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_passive", "passive", {}); "pet_stance_passive", "passive", {});
break; break;
default: default:
stance = sPlayerbotTextMgr->GetBotTextOrDefault( stance = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_unknown", "unknown", {}); "pet_stance_unknown", "unknown", {});
break; break;
} }
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_report", "Current stance of %type \"%name\": %stance.", "pet_stance_report", "Current stance of %type \"%name\": %stance.",
{{"type", type}, {"name", name}, {"stance", stance}}); {{"type", type}, {"name", name}, {"stance", stance}});
botAI->TellMaster(text); botAI->TellMaster(text);
@@ -133,30 +133,30 @@ bool PetsAction::Execute(Event event)
// If no valid target is selected, show an error and return. // If no valid target is selected, show an error and return.
if (!targetUnit) if (!targetUnit)
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_no_target_error", "No valid target selected by master.", {}); "pet_no_target_error", "No valid target selected by master.", {});
botAI->TellError(text); botAI->TellError(text);
return false; return false;
} }
if (!targetUnit->IsAlive()) if (!targetUnit->IsAlive())
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_target_dead_error", "Target is not alive.", {}); "pet_target_dead_error", "Target is not alive.", {});
botAI->TellError(text); botAI->TellError(text);
return false; return false;
} }
if (!bot->IsValidAttackTarget(targetUnit)) if (!bot->IsValidAttackTarget(targetUnit))
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_invalid_target_error", "Target is not a valid attack target for the bot.", {}); "pet_invalid_target_error", "Target is not a valid attack target for the bot.", {});
botAI->TellError(text); botAI->TellError(text);
return false; return false;
} }
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) && if (sPlayerbotAIConfig.IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) &&
(targetUnit->IsPlayer() || targetUnit->IsPet()) && (targetUnit->IsPlayer() || targetUnit->IsPet()) &&
(!bot->duel || bot->duel->Opponent != targetUnit)) (!bot->duel || bot->duel->Opponent != targetUnit))
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {}); "pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
botAI->TellError(text); botAI->TellError(text);
return false; return false;
@@ -208,15 +208,15 @@ bool PetsAction::Execute(Event event)
} }
} }
// Inform the master if the command succeeded or failed. // Inform the master if the command succeeded or failed.
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1) if (didAttack && sPlayerbotAIConfig.petChatCommandDebug == 1)
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_attack_success", "Pet commanded to attack your target.", {}); "pet_attack_success", "Pet commanded to attack your target.", {});
botAI->TellMaster(text); botAI->TellMaster(text);
} }
else if (!didAttack) else if (!didAttack)
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {}); "pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {});
botAI->TellError(text); botAI->TellError(text);
} }
@@ -226,9 +226,9 @@ bool PetsAction::Execute(Event event)
else if (param == "follow") else if (param == "follow")
{ {
botAI->PetFollow(); botAI->PetFollow();
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (sPlayerbotAIConfig.petChatCommandDebug == 1)
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_follow_success", "Pet commanded to follow.", {}); "pet_follow_success", "Pet commanded to follow.", {});
botAI->TellMaster(text); botAI->TellMaster(text);
} }
@@ -267,9 +267,9 @@ bool PetsAction::Execute(Event event)
charmInfo->SetForcedTargetGUID(); charmInfo->SetForcedTargetGUID();
} }
} }
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (sPlayerbotAIConfig.petChatCommandDebug == 1)
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stay_success", "Pet commanded to stay.", {}); "pet_stay_success", "Pet commanded to stay.", {});
botAI->TellMaster(text); botAI->TellMaster(text);
} }
@@ -278,7 +278,7 @@ bool PetsAction::Execute(Event event)
// Unknown command: show usage instructions and return. // Unknown command: show usage instructions and return.
else else
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>", "pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
{{"param", param}}); {{"param", param}});
botAI->TellError(text); botAI->TellError(text);
@@ -295,13 +295,13 @@ bool PetsAction::Execute(Event event)
} }
// Inform the master of the new stance if debug is enabled. // Inform the master of the new stance if debug is enabled.
if (sPlayerbotAIConfig->petChatCommandDebug == 1) if (sPlayerbotAIConfig.petChatCommandDebug == 1)
{ {
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pet_stance_set_success", "Pet stance set to %stance.", "pet_stance_set_success", "Pet stance set to %stance.",
{{"stance", stanceText}}); {{"stance", stanceText}});
botAI->TellMaster(text); botAI->TellMaster(text);
} }
return true; return true;
} }

View File

@@ -9,8 +9,6 @@
#include <string> #include <string>
#include "Action.h" #include "Action.h"
#include "PlayerbotFactory.h"
#include "Unit.h"
class PlayerbotAI; class PlayerbotAI;
@@ -22,7 +20,6 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
bool warningEnabled = true;
std::string defaultCmd; std::string defaultCmd;
}; };

View File

@@ -102,7 +102,7 @@ bool PositionAction::Execute(Event event)
return false; return false;
} }
bool MoveToPositionAction::Execute(Event event) bool MoveToPositionAction::Execute(Event /*event*/)
{ {
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier]; PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier];
if (!pos.isSet()) if (!pos.isSet())
@@ -120,10 +120,10 @@ bool MoveToPositionAction::isUseful()
{ {
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier]; PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier];
float distance = AI_VALUE2(float, "distance", std::string("position_") + qualifier); float distance = AI_VALUE2(float, "distance", std::string("position_") + qualifier);
return pos.isSet() && distance > sPlayerbotAIConfig->followDistance && distance < sPlayerbotAIConfig->reactDistance; return pos.isSet() && distance > sPlayerbotAIConfig.followDistance && distance < sPlayerbotAIConfig.reactDistance;
} }
bool SetReturnPositionAction::Execute(Event event) bool SetReturnPositionAction::Execute(Event /*event*/)
{ {
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get(); PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
PositionInfo returnPos = posMap["return"]; PositionInfo returnPos = posMap["return"];
@@ -131,7 +131,7 @@ bool SetReturnPositionAction::Execute(Event event)
if (returnPos.isSet() && !randomPos.isSet()) if (returnPos.isSet() && !randomPos.isSet())
{ {
float angle = 2 * M_PI * urand(0, 1000) / 100.0f; float angle = 2 * M_PI * urand(0, 1000) / 100.0f;
float dist = sPlayerbotAIConfig->followDistance * urand(0, 1000) / 1000.0f; float dist = sPlayerbotAIConfig.followDistance * urand(0, 1000) / 1000.0f;
float x = returnPos.x + cos(angle) * dist; float x = returnPos.x + cos(angle) * dist;
float y = returnPos.y + sin(angle) * dist; float y = returnPos.y + sin(angle) * dist;
float z = bot->GetPositionZ(); float z = bot->GetPositionZ();
@@ -157,7 +157,7 @@ bool SetReturnPositionAction::isUseful()
bool ReturnAction::isUseful() bool ReturnAction::isUseful()
{ {
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier]; PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[qualifier];
return pos.isSet() && AI_VALUE2(float, "distance", "position_random") > sPlayerbotAIConfig->followDistance; return pos.isSet() && AI_VALUE2(float, "distance", "position_random") > sPlayerbotAIConfig.followDistance;
} }
bool ReturnToStayPositionAction::isPossible() bool ReturnToStayPositionAction::isPossible()
@@ -167,7 +167,7 @@ bool ReturnToStayPositionAction::isPossible()
if (stayPosition.isSet()) if (stayPosition.isSet())
{ {
const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z); const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
if (distance > sPlayerbotAIConfig->reactDistance) if (distance > sPlayerbotAIConfig.reactDistance)
{ {
botAI->TellMaster("The stay position is too far to return. I am going to stay where I am now"); botAI->TellMaster("The stay position is too far to return. I am going to stay where I am now");

View File

@@ -118,7 +118,7 @@ std::string const QueryItemUsageAction::QueryItemUsage(ItemTemplate const* item)
std::string const QueryItemUsageAction::QueryItemPrice(ItemTemplate const* item) std::string const QueryItemUsageAction::QueryItemPrice(ItemTemplate const* item)
{ {
if (!sRandomPlayerbotMgr->IsRandomBot(bot)) if (!sRandomPlayerbotMgr.IsRandomBot(bot))
return ""; return "";
if (item->Bonding == BIND_WHEN_PICKED_UP) if (item->Bonding == BIND_WHEN_PICKED_UP)
@@ -133,7 +133,7 @@ std::string const QueryItemUsageAction::QueryItemPrice(ItemTemplate const* item)
{ {
Item* sell = *i; Item* sell = *i;
int32 price = int32 price =
sell->GetCount() * sell->GetTemplate()->SellPrice * sRandomPlayerbotMgr->GetSellMultiplier(bot); sell->GetCount() * sell->GetTemplate()->SellPrice * sRandomPlayerbotMgr.GetSellMultiplier(bot);
if (!sellPrice || sellPrice > price) if (!sellPrice || sellPrice > price)
sellPrice = price; sellPrice = price;
} }
@@ -147,7 +147,7 @@ std::string const QueryItemUsageAction::QueryItemPrice(ItemTemplate const* item)
if (usage == ITEM_USAGE_NONE) if (usage == ITEM_USAGE_NONE)
return msg.str(); return msg.str();
int32 buyPrice = item->BuyPrice * sRandomPlayerbotMgr->GetBuyMultiplier(bot); int32 buyPrice = item->BuyPrice * sRandomPlayerbotMgr.GetBuyMultiplier(bot);
if (buyPrice) if (buyPrice)
{ {
if (sellPrice) if (sellPrice)

View File

@@ -7,7 +7,7 @@
#include "ChatHelper.h" #include "ChatHelper.h"
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "PlayerbotAI.h"
void QueryQuestAction::TellObjective(std::string const name, uint32 available, uint32 required) void QueryQuestAction::TellObjective(std::string const name, uint32 available, uint32 required)
{ {
@@ -16,7 +16,6 @@ void QueryQuestAction::TellObjective(std::string const name, uint32 available, u
bool QueryQuestAction::Execute(Event event) bool QueryQuestAction::Execute(Event event)
{ {
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
WorldPosition botPos(bot); WorldPosition botPos(bot);
WorldPosition* ptr_botpos = &botPos; WorldPosition* ptr_botpos = &botPos;
@@ -73,7 +72,7 @@ bool QueryQuestAction::Execute(Event event)
{ {
uint32 limit = 0; uint32 limit = 0;
std::vector<TravelDestination*> allDestinations = std::vector<TravelDestination*> allDestinations =
sTravelMgr->getQuestTravelDestinations(bot, questId, true, true, -1); TravelMgr::instance().getQuestTravelDestinations(bot, questId, true, true, -1);
std::sort(allDestinations.begin(), allDestinations.end(), [ptr_botpos](TravelDestination* i, TravelDestination* j) {return i->distanceTo(ptr_botpos) < j->distanceTo(ptr_botpos); }); std::sort(allDestinations.begin(), allDestinations.end(), [ptr_botpos](TravelDestination* i, TravelDestination* j) {return i->distanceTo(ptr_botpos) < j->distanceTo(ptr_botpos); });
for (auto dest : allDestinations) for (auto dest : allDestinations)

View File

@@ -5,6 +5,7 @@
#include "QuestAction.h" #include "QuestAction.h"
#include <sstream> #include <sstream>
#include <algorithm>
#include "Chat.h" #include "Chat.h"
#include "ChatHelper.h" #include "ChatHelper.h"
@@ -116,7 +117,8 @@ bool QuestAction::CompleteQuest(Player* player, uint32 entry)
player->CastedCreatureOrGO(creature, ObjectGuid(), spell_id); player->CastedCreatureOrGO(creature, ObjectGuid(), spell_id);
} }
}*/ }*/
/*else*/ if (creature > 0) /*else*/
if (creature > 0)
{ {
if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(creature)) if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(creature))
for (uint16 z = 0; z < creaturecount; ++z) for (uint16 z = 0; z < creaturecount; ++z)
@@ -182,7 +184,7 @@ bool QuestAction::ProcessQuests(WorldObject* questGiver)
{ {
ObjectGuid guid = questGiver->GetGUID(); ObjectGuid guid = questGiver->GetGUID();
if (bot->GetDistance(questGiver) > INTERACTION_DISTANCE && !sPlayerbotAIConfig->syncQuestWithPlayer) if (bot->GetDistance(questGiver) > INTERACTION_DISTANCE && !sPlayerbotAIConfig.syncQuestWithPlayer)
{ {
//if (botAI->HasStrategy("debug", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug", BotState::BOT_STATE_NON_COMBAT)) //if (botAI->HasStrategy("debug", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug", BotState::BOT_STATE_NON_COMBAT))
@@ -190,7 +192,7 @@ bool QuestAction::ProcessQuests(WorldObject* questGiver)
return false; return false;
} }
if (!bot->HasInArc(CAST_ANGLE_IN_FRONT, questGiver, sPlayerbotAIConfig->sightDistance)) if (!bot->HasInArc(CAST_ANGLE_IN_FRONT, questGiver, sPlayerbotAIConfig.sightDistance))
bot->SetFacingToObject(questGiver); bot->SetFacingToObject(questGiver);
bot->SetTarget(guid); bot->SetTarget(guid);
@@ -238,7 +240,7 @@ bool QuestAction::AcceptQuest(Quest const* quest, ObjectGuid questGiver)
p.rpos(0); p.rpos(0);
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p); bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p);
if (bot->GetQuestStatus(questId) == QUEST_STATUS_NONE && sPlayerbotAIConfig->syncQuestWithPlayer) if (bot->GetQuestStatus(questId) == QUEST_STATUS_NONE && sPlayerbotAIConfig.syncQuestWithPlayer)
{ {
Object* pObject = ObjectAccessor::GetObjectByTypeMask(*bot, questGiver, Object* pObject = ObjectAccessor::GetObjectByTypeMask(*bot, questGiver,
TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM); TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM);
@@ -350,7 +352,6 @@ bool QuestUpdateAddItemAction::Execute(Event event)
uint32 itemId, count; uint32 itemId, count;
p >> itemId >> count; p >> itemId >> count;
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
auto const* itemPrototype = sObjectMgr->GetItemTemplate(itemId); auto const* itemPrototype = sObjectMgr->GetItemTemplate(itemId);
if (itemPrototype) if (itemPrototype)
{ {
@@ -366,7 +367,7 @@ bool QuestUpdateAddItemAction::Execute(Event event)
placeholders["%quest_obj_required"] = std::to_string(requiredItemsCount); placeholders["%quest_obj_required"] = std::to_string(requiredItemsCount);
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT))
{ {
const auto text = BOT_TEXT2("%quest_link - %item_link %quest_obj_available/%quest_obj_required", placeholders); const auto text = PlayerbotTextMgr::instance().GetBotText("%quest_link - %item_link %quest_obj_available/%quest_obj_required", placeholders);
botAI->Say(text); botAI->Say(text);
LOG_INFO("playerbots", "{} => {}", bot->GetName(), text); LOG_INFO("playerbots", "{} => {}", bot->GetName(), text);
} }
@@ -405,8 +406,6 @@ bool QuestItemPushResultAction::Execute(Event event)
if (!quest) if (!quest)
return false; return false;
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++) for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
{ {
uint32 itemId = quest->RequiredItemId[i]; uint32 itemId = quest->RequiredItemId[i];
@@ -432,7 +431,7 @@ bool QuestItemPushResultAction::Execute(Event event)
return false; return false;
} }
bool QuestUpdateFailedAction::Execute(Event event) bool QuestUpdateFailedAction::Execute(Event /*event*/)
{ {
//opcode SMSG_QUESTUPDATE_FAILED is never sent...(yet?) //opcode SMSG_QUESTUPDATE_FAILED is never sent...(yet?)
return false; return false;
@@ -446,15 +445,13 @@ bool QuestUpdateFailedTimerAction::Execute(Event event)
uint32 questId; uint32 questId;
p >> questId; p >> questId;
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId); Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId);
if (qInfo) if (qInfo)
{ {
std::map<std::string, std::string> placeholders; std::map<std::string, std::string> placeholders;
placeholders["%quest_link"] = botAI->GetChatHelper()->FormatQuest(qInfo); placeholders["%quest_link"] = botAI->GetChatHelper()->FormatQuest(qInfo);
botAI->TellMaster(BOT_TEXT2("Failed timer for %quest_link, abandoning", placeholders)); botAI->TellMaster(PlayerbotTextMgr::instance().GetBotText("Failed timer for %quest_link, abandoning", placeholders));
BroadcastHelper::BroadcastQuestUpdateFailedTimer(botAI, bot, qInfo); BroadcastHelper::BroadcastQuestUpdateFailedTimer(botAI, bot, qInfo);
} }
else else

View File

@@ -20,4 +20,4 @@ bool QuestConfirmAcceptAction::Execute(Event event)
botAI->TellMaster(out); botAI->TellMaster(out);
bot->GetSession()->HandleQuestConfirmAccept(sendPacket); bot->GetSession()->HandleQuestConfirmAccept(sendPacket);
return true; return true;
} }

View File

@@ -24,4 +24,4 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
#endif #endif

View File

@@ -8,9 +8,9 @@
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "Playerbots.h"
bool RandomBotUpdateAction::Execute(Event event) bool RandomBotUpdateAction::Execute(Event /*event*/)
{ {
if (!sRandomPlayerbotMgr->IsRandomBot(bot)) if (!sRandomPlayerbotMgr.IsRandomBot(bot))
return false; return false;
if (bot->GetGroup() && botAI->GetGroupLeader()) if (bot->GetGroup() && botAI->GetGroupLeader())
@@ -20,10 +20,10 @@ bool RandomBotUpdateAction::Execute(Event event)
return true; return true;
} }
if (botAI->HasPlayerNearby(sPlayerbotAIConfig->grindDistance)) if (botAI->HasPlayerNearby(sPlayerbotAIConfig.grindDistance))
return true; return true;
return sRandomPlayerbotMgr->ProcessBot(bot); return sRandomPlayerbotMgr.ProcessBot(bot);
} }
bool RandomBotUpdateAction::isUseful() { return AI_VALUE(bool, "random bot update"); } bool RandomBotUpdateAction::isUseful() { return AI_VALUE(bool, "random bot update"); }

View File

@@ -10,7 +10,7 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
bool ReachTargetAction::Execute(Event event) { return ReachCombatTo(AI_VALUE(Unit*, GetTargetName()), distance); } bool ReachTargetAction::Execute(Event /*event*/) { return ReachCombatTo(AI_VALUE(Unit*, GetTargetName()), distance); }
bool ReachTargetAction::isUseful() bool ReachTargetAction::isUseful()
{ {
@@ -28,7 +28,7 @@ bool ReachTargetAction::isUseful()
Unit* target = GetTarget(); Unit* target = GetTarget();
// float dis = distance + CONTACT_DISTANCE; // float dis = distance + CONTACT_DISTANCE;
return target && return target &&
!bot->IsWithinCombatRange(target, distance); // sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, !bot->IsWithinCombatRange(target, distance); // ServerFacade::instance().IsDistanceGreaterThan(AI_VALUE2(float,
// "distance", GetTargetName()), distance); // "distance", GetTargetName()), distance);
} }
@@ -42,8 +42,8 @@ bool CastReachTargetSpellAction::isUseful()
return false; return false;
} }
return sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "current target"), return ServerFacade::instance().IsDistanceGreaterThan(AI_VALUE2(float, "distance", "current target"),
(distance + sPlayerbotAIConfig->contactDistance)); (distance + sPlayerbotAIConfig.contactDistance));
} }
ReachSpellAction::ReachSpellAction(PlayerbotAI* botAI) ReachSpellAction::ReachSpellAction(PlayerbotAI* botAI)

View File

@@ -44,7 +44,7 @@ protected:
class ReachMeleeAction : public ReachTargetAction class ReachMeleeAction : public ReachTargetAction
{ {
public: public:
ReachMeleeAction(PlayerbotAI* botAI) : ReachTargetAction(botAI, "reach melee", sPlayerbotAIConfig->meleeDistance) {} ReachMeleeAction(PlayerbotAI* botAI) : ReachTargetAction(botAI, "reach melee", sPlayerbotAIConfig.meleeDistance) {}
}; };
class ReachSpellAction : public ReachTargetAction class ReachSpellAction : public ReachTargetAction

View File

@@ -47,7 +47,7 @@ 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;
} }
std::string const getName() override { return "HP"; } std::string const getName() override { return "HP"; }
@@ -59,7 +59,7 @@ 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;
} }
std::string const getName() override { return "MP"; } std::string const getName() override { return "MP"; }
@@ -73,7 +73,7 @@ public:
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (Player* master = botAI->GetMaster()) if (Player* master = botAI->GetMaster())
{ {
bool distance = bot->GetDistance(master) <= sPlayerbotAIConfig->sightDistance; bool distance = bot->GetDistance(master) <= sPlayerbotAIConfig.sightDistance;
if (!distance) if (!distance)
{ {
return false; return false;

View File

@@ -78,7 +78,7 @@ void ReleaseSpiritAction::LogRelease(const std::string& releaseMsg, bool isAutoR
} }
// AutoReleaseSpiritAction implementation // AutoReleaseSpiritAction implementation
bool AutoReleaseSpiritAction::Execute(Event event) bool AutoReleaseSpiritAction::Execute(Event /*event*/)
{ {
IncrementDeathCount(); IncrementDeathCount();
bot->DurabilityRepairAll(false, 1.0f, false); bot->DurabilityRepairAll(false, 1.0f, false);
@@ -183,9 +183,9 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
return false; return false;
} }
return sServerFacade->IsDistanceGreaterThan( return ServerFacade::instance().IsDistanceGreaterThan(
AI_VALUE2(float, "distance", "group leader"), AI_VALUE2(float, "distance", "group leader"),
sPlayerbotAIConfig->sightDistance); sPlayerbotAIConfig.sightDistance);
} }
bool AutoReleaseSpiritAction::ShouldDelayBattlegroundRelease() const bool AutoReleaseSpiritAction::ShouldDelayBattlegroundRelease() const
@@ -214,7 +214,7 @@ bool AutoReleaseSpiritAction::ShouldDelayBattlegroundRelease() const
return true; return true;
} }
bool RepopAction::Execute(Event event) bool RepopAction::Execute(Event /*event*/)
{ {
const GraveyardStruct* graveyard = GetGrave( const GraveyardStruct* graveyard = GetGrave(
AI_VALUE(uint32, "death count") > 10 || AI_VALUE(uint32, "death count") > 10 ||
@@ -250,7 +250,7 @@ void RepopAction::PerformGraveyardTeleport(const GraveyardStruct* graveyard) con
} }
// SelfResurrectAction implementation for Warlock's Soulstone Resurrection/Shaman's Reincarnation // SelfResurrectAction implementation for Warlock's Soulstone Resurrection/Shaman's Reincarnation
bool SelfResurrectAction::Execute(Event event) bool SelfResurrectAction::Execute(Event /*event*/)
{ {
if (!bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL)) if (!bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL))
{ {

View File

@@ -7,7 +7,7 @@
#include "Event.h" #include "Event.h"
#include "LastMovementValue.h" #include "LastMovementValue.h"
#include "Playerbots.h" #include "AiObjectContext.h"
bool RememberTaxiAction::Execute(Event event) bool RememberTaxiAction::Execute(Event event)
{ {
@@ -28,7 +28,7 @@ bool RememberTaxiAction::Execute(Event event)
case CMSG_ACTIVATETAXIEXPRESS: case CMSG_ACTIVATETAXIEXPRESS:
{ {
ObjectGuid guid; ObjectGuid guid;
uint32 node_count, totalcost; uint32 node_count;
p >> guid >> node_count; p >> guid >> node_count;
LastMovement& movement = context->GetValue<LastMovement&>("last taxi")->Get(); LastMovement& movement = context->GetValue<LastMovement&>("last taxi")->Get();

View File

@@ -9,7 +9,7 @@
#include "Event.h" #include "Event.h"
#include "Playerbots.h" #include "Playerbots.h"
bool RepairAllAction::Execute(Event event) bool RepairAllAction::Execute(Event /*event*/)
{ {
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
for (ObjectGuid const guid : npcs) for (ObjectGuid const guid : npcs)

View File

@@ -44,7 +44,7 @@ bool ResetAiAction::Execute(Event event)
} }
} }
} }
sPlayerbotRepository->Reset(botAI); PlayerbotRepository::instance().Reset(botAI);
botAI->ResetStrategies(false); botAI->ResetStrategies(false);
botAI->TellMaster("AI was reset to defaults"); botAI->TellMaster("AI was reset to defaults");
return true; return true;

View File

@@ -5,14 +5,16 @@
#include "ResetInstancesAction.h" #include "ResetInstancesAction.h"
#include "Playerbots.h" #include "PlayerbotAI.h"
bool ResetInstancesAction::Execute(Event event) #include "InstancePackets.h"
bool ResetInstancesAction::Execute(Event /*event*/)
{ {
WorldPacket packet(CMSG_RESET_INSTANCES, 0); WorldPacket packet(CMSG_RESET_INSTANCES, 0);
bot->GetSession()->HandleResetInstancesOpcode(packet); WorldPackets::Instance::ResetInstances resetInstance(std::move(packet));
bot->GetSession()->HandleResetInstancesOpcode(resetInstance);
botAI->TellMaster("Resetting all instances");
return true; return true;
} }

View File

@@ -10,26 +10,26 @@
#include "Event.h" #include "Event.h"
#include "GridNotifiers.h" #include "GridNotifiers.h"
#include "GridNotifiersImpl.h" #include "GridNotifiersImpl.h"
#include "Playerbots.h" #include "PlayerbotAI.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "NearestGameObjects.h" #include "NearestGameObjects.h"
bool RevealGatheringItemAction::Execute(Event event) bool RevealGatheringItemAction::Execute(Event /*event*/)
{ {
if (!bot->GetGroup()) if (!bot->GetGroup())
return false; return false;
std::list<GameObject*> targets; std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, sPlayerbotAIConfig->grindDistance); AnyGameObjectInObjectRangeCheck u_check(bot, sPlayerbotAIConfig.grindDistance);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check); Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, sPlayerbotAIConfig->reactDistance); Cell::VisitObjects(bot, searcher, sPlayerbotAIConfig.reactDistance);
std::vector<GameObject*> result; std::vector<GameObject*> result;
for (GameObject* go : targets) for (GameObject* go : targets)
{ {
if (!go || !go->isSpawned() || if (!go || !go->isSpawned() ||
sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, go), ServerFacade::instance().IsDistanceLessOrEqualThan(ServerFacade::instance().GetDistance2d(bot, go),
sPlayerbotAIConfig->lootDistance)) sPlayerbotAIConfig.lootDistance))
continue; continue;
if (LockEntry const* lockInfo = sLockStore.LookupEntry(go->GetGOInfo()->GetLockId())) if (LockEntry const* lockInfo = sLockStore.LookupEntry(go->GetGOInfo()->GetLockId()))

View File

@@ -9,7 +9,6 @@
#include "FleeManager.h" #include "FleeManager.h"
#include "GameGraveyard.h" #include "GameGraveyard.h"
#include "MapMgr.h" #include "MapMgr.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "ServerFacade.h" #include "ServerFacade.h"
@@ -24,8 +23,8 @@ bool ReviveFromCorpseAction::Execute(Event event)
WorldPacket& p = event.getPacket(); WorldPacket& p = event.getPacket();
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && groupLeader && !corpse && bot->IsAlive()) if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && groupLeader && !corpse && bot->IsAlive())
{ {
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), if (ServerFacade::instance().IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
sPlayerbotAIConfig->farDistance)) sPlayerbotAIConfig.farDistance))
{ {
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT)) if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
{ {
@@ -46,8 +45,8 @@ bool ReviveFromCorpseAction::Execute(Event event)
if (groupLeader) if (groupLeader)
{ {
if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() && if (!GET_PLAYERBOT_AI(groupLeader) && groupLeader->isDead() && groupLeader->GetCorpse() &&
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), ServerFacade::instance().IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
sPlayerbotAIConfig->farDistance)) sPlayerbotAIConfig.farDistance))
return false; return false;
} }
@@ -74,7 +73,7 @@ bool ReviveFromCorpseAction::Execute(Event event)
return true; return true;
} }
bool FindCorpseAction::Execute(Event event) bool FindCorpseAction::Execute(Event /*event*/)
{ {
if (bot->InBattleground()) if (bot->InBattleground())
return false; return false;
@@ -87,8 +86,8 @@ bool FindCorpseAction::Execute(Event event)
// if (groupLeader) // if (groupLeader)
// { // {
// if (!GET_PLAYERBOT_AI(groupLeader) && // if (!GET_PLAYERBOT_AI(groupLeader) &&
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"), // ServerFacade::instance().IsDistanceLessThan(AI_VALUE2(float, "distance", "group leader"),
// sPlayerbotAIConfig->farDistance)) return false; // sPlayerbotAIConfig.farDistance)) return false;
// } // }
uint32 dCount = AI_VALUE(uint32, "death count"); uint32 dCount = AI_VALUE(uint32, "death count");
@@ -101,8 +100,8 @@ bool FindCorpseAction::Execute(Event event)
// 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(),
// bot->GetName().c_str()); // bot->GetName().c_str());
context->GetValue<uint32>("death count")->Set(0); context->GetValue<uint32>("death count")->Set(0);
// sRandomPlayerbotMgr->RandomTeleportForLevel(bot); // sRandomPlayerbotMgr.RandomTeleportForLevel(bot);
sRandomPlayerbotMgr->Revive(bot); sRandomPlayerbotMgr.Revive(bot);
return true; return true;
} }
} }
@@ -123,7 +122,7 @@ bool FindCorpseAction::Execute(Event event)
{ {
if (moveToLeader) // We are near group leader. if (moveToLeader) // We are near group leader.
{ {
if (botPos.fDist(leaderPos) < sPlayerbotAIConfig->spellDistance) if (botPos.fDist(leaderPos) < sPlayerbotAIConfig.spellDistance)
return false; return false;
} }
else if (deadTime > 8 * MINUTE) // We have walked too long already. else if (deadTime > 8 * MINUTE) // We have walked too long already.
@@ -138,7 +137,7 @@ bool FindCorpseAction::Execute(Event event)
} }
// If we are getting close move to a save ressurrection spot instead of just the corpse. // If we are getting close move to a save ressurrection spot instead of just the corpse.
if (corpseDist < sPlayerbotAIConfig->reactDistance) if (corpseDist < sPlayerbotAIConfig.reactDistance)
{ {
if (moveToLeader) if (moveToLeader)
moveToPos = leaderPos; moveToPos = leaderPos;
@@ -150,7 +149,7 @@ bool FindCorpseAction::Execute(Event event)
{ {
float rx, ry, rz; float rx, ry, rz;
if (manager.CalculateDestination(&rx, &ry, &rz)) if (manager.CalculateDestination(&rx, &ry, &rz))
moveToPos = WorldPosition(moveToPos.getMapId(), rx, ry, rz, 0.0); moveToPos = WorldPosition(moveToPos.GetMapId(), rx, ry, rz, 0.0);
else if (!moveToPos.GetReachableRandomPointOnGround(bot, reclaimDist, urand(0, 1))) else if (!moveToPos.GetReachableRandomPointOnGround(bot, reclaimDist, urand(0, 1)))
moveToPos = corpsePos; moveToPos = corpsePos;
} }
@@ -162,7 +161,7 @@ bool FindCorpseAction::Execute(Event event)
if (!botAI->AllowActivity(ALL_ACTIVITY)) if (!botAI->AllowActivity(ALL_ACTIVITY))
{ {
uint32 delay = sServerFacade->GetDistance2d(bot, corpse) / uint32 delay = ServerFacade::instance().GetDistance2d(bot, corpse) /
bot->GetSpeed(MOVE_RUN); // Time a bot would take to travel to it's corpse. bot->GetSpeed(MOVE_RUN); // Time a bot would take to travel to it's corpse.
delay = std::min(delay, uint32(10 * MINUTE)); // Cap time to get to corpse at 10 minutes. delay = std::min(delay, uint32(10 * MINUTE)); // Cap time to get to corpse at 10 minutes.
@@ -170,7 +169,7 @@ bool FindCorpseAction::Execute(Event event)
{ {
bot->GetMotionMaster()->Clear(); bot->GetMotionMaster()->Clear();
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
bot->TeleportTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); bot->TeleportTo(moveToPos.GetMapId(), moveToPos.GetPositionX(), moveToPos.GetPositionY(), moveToPos.GetPositionZ(), 0);
} }
moved = true; moved = true;
@@ -184,7 +183,7 @@ bool FindCorpseAction::Execute(Event event)
if (deadTime < 10 * MINUTE && dCount < 5) // Look for corpse up to 30 minutes. if (deadTime < 10 * MINUTE && dCount < 5) // Look for corpse up to 30 minutes.
{ {
moved = moved =
MoveTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), false, false); MoveTo(moveToPos.GetMapId(), moveToPos.GetPositionX(), moveToPos.GetPositionY(), moveToPos.GetPositionZ(), false, false);
} }
if (!moved) if (!moved)
@@ -237,10 +236,10 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
{ {
uint32 areaId = 0; uint32 areaId = 0;
uint32 zoneId = 0; uint32 zoneId = 0;
sMapMgr->GetZoneAndAreaId(bot->GetPhaseMask(), zoneId, areaId, travelPos.getMapId(), travelPos.getX(), sMapMgr->GetZoneAndAreaId(bot->GetPhaseMask(), zoneId, areaId, travelPos.GetMapId(), travelPos.GetPositionX(),
travelPos.getY(), travelPos.getZ()); travelPos.GetPositionY(), travelPos.GetPositionZ());
ClosestGrave = sGraveyard->GetClosestGraveyard(travelPos.getMapId(), travelPos.getX(), travelPos.getY(), ClosestGrave = sGraveyard->GetClosestGraveyard(travelPos.GetMapId(), travelPos.GetPositionX(), travelPos.GetPositionY(),
travelPos.getZ(), bot->GetTeamId(), areaId, zoneId, travelPos.GetPositionZ(), bot->GetTeamId(), areaId, zoneId,
bot->getClass() == CLASS_DEATH_KNIGHT); bot->getClass() == CLASS_DEATH_KNIGHT);
if (ClosestGrave) if (ClosestGrave)
@@ -293,7 +292,7 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
return ClosestGrave; return ClosestGrave;
} }
bool SpiritHealerAction::Execute(Event event) bool SpiritHealerAction::Execute(Event /*event*/)
{ {
Corpse* corpse = bot->GetCorpse(); Corpse* corpse = bot->GetCorpse();
if (!corpse) if (!corpse)
@@ -308,7 +307,7 @@ bool SpiritHealerAction::Execute(Event event)
GraveyardStruct const* ClosestGrave = GraveyardStruct const* ClosestGrave =
GetGrave(dCount > 10 || deadTime > 15 * MINUTE || AI_VALUE(uint8, "durability") < 10); GetGrave(dCount > 10 || deadTime > 15 * MINUTE || AI_VALUE(uint8, "durability") < 10);
if (bot->GetDistance2d(ClosestGrave->x, ClosestGrave->y) < sPlayerbotAIConfig->sightDistance) if (bot->GetDistance2d(ClosestGrave->x, ClosestGrave->y) < sPlayerbotAIConfig.sightDistance)
{ {
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++) for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++)

View File

@@ -7,7 +7,6 @@
#include <random> #include <random>
#include "BattlegroundMgr.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "EmoteAction.h" #include "EmoteAction.h"
#include "Event.h" #include "Event.h"
@@ -16,7 +15,7 @@
#include "ServerFacade.h" #include "ServerFacade.h"
#include "RpgSubActions.h" #include "RpgSubActions.h"
bool RpgAction::Execute(Event event) bool RpgAction::Execute(Event /*event*/)
{ {
GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target"); GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target");
if (!guidP && botAI->GetMaster()) if (!guidP && botAI->GetMaster())
@@ -85,7 +84,7 @@ bool RpgAction::SetNextRpgAction()
isChecked = true; isChecked = true;
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName()); Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
if (!dynamic_cast<RpgEnabled*>(action) || !action->isPossible() || !action->isUseful()) if (!dynamic_cast<RpgEnabled*>(action) || !action->isUseful() || !action->isPossible())
continue; continue;
actions.push_back(action); actions.push_back(action);
@@ -130,7 +129,7 @@ bool RpgAction::SetNextRpgAction()
std::mt19937 gen(time(0)); std::mt19937 gen(time(0));
sTravelMgr->weighted_shuffle(actions.begin(), actions.end(), relevances.begin(), relevances.end(), gen); TravelMgr::instance().weighted_shuffle(actions.begin(), actions.end(), relevances.begin(), relevances.end(), gen);
Action* action = actions.front(); Action* action = actions.front();

View File

@@ -5,6 +5,7 @@
#include "RpgSubActions.h" #include "RpgSubActions.h"
#include "BudgetValues.h"
#include "ChooseRpgTargetAction.h" #include "ChooseRpgTargetAction.h"
#include "EmoteAction.h" #include "EmoteAction.h"
#include "Formations.h" #include "Formations.h"
@@ -51,10 +52,15 @@ GuidPosition RpgHelper::guidP() { return AI_VALUE(GuidPosition, "rpg target"); }
ObjectGuid RpgHelper::guid() { return (ObjectGuid)guidP(); } ObjectGuid RpgHelper::guid() { return (ObjectGuid)guidP(); }
bool RpgHelper::InRange() bool RpgHelper::InRange()
{ {
return guidP() ? (guidP().sqDistance2d(bot) < INTERACTION_DISTANCE * INTERACTION_DISTANCE) : false; GuidPosition targetGuid = guidP();
} if (!targetGuid)
return false;
return bot->GetExactDist2dSq(targetGuid.GetPositionX(), targetGuid.GetPositionY()) <
INTERACTION_DISTANCE * INTERACTION_DISTANCE;
}
void RpgHelper::setFacingTo(GuidPosition guidPosition) void RpgHelper::setFacingTo(GuidPosition guidPosition)
{ {
@@ -77,9 +83,9 @@ void RpgHelper::setFacing(GuidPosition guidPosition)
void RpgHelper::setDelay(bool waitForGroup) void RpgHelper::setDelay(bool waitForGroup)
{ {
if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupLeader() == bot && bot->GetGroup())) if (!botAI->HasRealPlayerMaster() || (waitForGroup && botAI->GetGroupLeader() == bot && bot->GetGroup()))
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay); botAI->SetNextCheckDelay(sPlayerbotAIConfig.rpgDelay);
else else
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay / 5); botAI->SetNextCheckDelay(sPlayerbotAIConfig.rpgDelay / 5);
} }
bool RpgSubAction::isPossible() { return rpg->guidP() && rpg->guidP().GetWorldObject(); } bool RpgSubAction::isPossible() { return rpg->guidP() && rpg->guidP().GetWorldObject(); }
@@ -99,7 +105,7 @@ Event RpgSubAction::ActionEvent(Event event) { return event; }
bool RpgStayAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); } bool RpgStayAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); }
bool RpgStayAction::Execute(Event event) bool RpgStayAction::Execute(Event /*event*/)
{ {
bot->PlayerTalkClass->SendCloseGossip(); bot->PlayerTalkClass->SendCloseGossip();
@@ -109,7 +115,7 @@ bool RpgStayAction::Execute(Event event)
bool RpgWorkAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); } bool RpgWorkAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); }
bool RpgWorkAction::Execute(Event event) bool RpgWorkAction::Execute(Event /*event*/)
{ {
bot->HandleEmoteCommand(EMOTE_STATE_USE_STANDING); bot->HandleEmoteCommand(EMOTE_STATE_USE_STANDING);
rpg->AfterExecute(); rpg->AfterExecute();
@@ -118,7 +124,7 @@ bool RpgWorkAction::Execute(Event event)
bool RpgEmoteAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); } bool RpgEmoteAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); }
bool RpgEmoteAction::Execute(Event event) bool RpgEmoteAction::Execute(Event /*event*/)
{ {
uint32 type = TalkAction::GetRandomEmote(rpg->guidP().GetUnit()); uint32 type = TalkAction::GetRandomEmote(rpg->guidP().GetUnit());
@@ -133,7 +139,7 @@ bool RpgEmoteAction::Execute(Event event)
return true; return true;
} }
bool RpgCancelAction::Execute(Event event) bool RpgCancelAction::Execute(Event /*event*/)
{ {
RESET_AI_VALUE(GuidPosition, "rpg target"); RESET_AI_VALUE(GuidPosition, "rpg target");
rpg->OnExecute(""); rpg->OnExecute("");
@@ -142,7 +148,7 @@ bool RpgCancelAction::Execute(Event event)
bool RpgTaxiAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); } bool RpgTaxiAction::isUseful() { return rpg->InRange() && !botAI->HasRealPlayerMaster(); }
bool RpgTaxiAction::Execute(Event event) bool RpgTaxiAction::Execute(Event /*event*/)
{ {
GuidPosition guidP = rpg->guidP(); GuidPosition guidP = rpg->guidP();
@@ -150,7 +156,7 @@ bool RpgTaxiAction::Execute(Event event)
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
uint32 node = uint32 node =
sObjectMgr->GetNearestTaxiNode(guidP.getX(), guidP.getY(), guidP.getZ(), guidP.getMapId(), bot->GetTeamId()); sObjectMgr->GetNearestTaxiNode(guidP.GetPositionX(), guidP.GetPositionY(), guidP.GetPositionZ(), guidP.GetMapId(), bot->GetTeamId());
std::vector<uint32> nodes; std::vector<uint32> nodes;
for (uint32 i = 0; i < sTaxiPathStore.GetNumRows(); ++i) for (uint32 i = 0; i < sTaxiPathStore.GetNumRows(); ++i)
@@ -203,12 +209,12 @@ bool RpgTaxiAction::Execute(Event event)
return true; return true;
} }
bool RpgDiscoverAction::Execute(Event event) bool RpgDiscoverAction::Execute(Event /*event*/)
{ {
GuidPosition guidP = rpg->guidP(); GuidPosition guidP = rpg->guidP();
uint32 node = uint32 node =
sObjectMgr->GetNearestTaxiNode(guidP.getX(), guidP.getY(), guidP.getZ(), guidP.getMapId(), bot->GetTeamId()); sObjectMgr->GetNearestTaxiNode(guidP.GetPositionX(), guidP.GetPositionY(), guidP.GetPositionZ(), guidP.GetMapId(), bot->GetTeamId());
if (!node) if (!node)
return false; return false;
@@ -222,7 +228,7 @@ bool RpgDiscoverAction::Execute(Event event)
std::string const RpgStartQuestAction::ActionName() { return "accept all quests"; } std::string const RpgStartQuestAction::ActionName() { return "accept all quests"; }
Event RpgStartQuestAction::ActionEvent(Event event) Event RpgStartQuestAction::ActionEvent(Event /*event*/)
{ {
WorldPacket p(CMSG_QUESTGIVER_ACCEPT_QUEST); WorldPacket p(CMSG_QUESTGIVER_ACCEPT_QUEST);
p << rpg->guid(); p << rpg->guid();
@@ -232,7 +238,7 @@ Event RpgStartQuestAction::ActionEvent(Event event)
std::string const RpgEndQuestAction::ActionName() { return "talk to quest giver"; } std::string const RpgEndQuestAction::ActionName() { return "talk to quest giver"; }
Event RpgEndQuestAction::ActionEvent(Event event) Event RpgEndQuestAction::ActionEvent(Event /*event*/)
{ {
WorldPacket p(CMSG_QUESTGIVER_COMPLETE_QUEST); WorldPacket p(CMSG_QUESTGIVER_COMPLETE_QUEST);
p << rpg->guid(); p << rpg->guid();
@@ -242,17 +248,71 @@ Event RpgEndQuestAction::ActionEvent(Event event)
std::string const RpgBuyAction::ActionName() { return "buy"; } std::string const RpgBuyAction::ActionName() { return "buy"; }
Event RpgBuyAction::ActionEvent(Event event) { return Event("rpg action", "vendor"); } Event RpgBuyAction::ActionEvent(Event /*event*/) { return Event("rpg action", "vendor"); }
std::string const RpgSellAction::ActionName() { return "sell"; } std::string const RpgSellAction::ActionName() { return "sell"; }
Event RpgSellAction::ActionEvent(Event event) { return Event("rpg action", "vendor"); } Event RpgSellAction::ActionEvent(Event /*event*/) { return Event("rpg action", "vendor"); }
std::string const RpgRepairAction::ActionName() { return "repair"; } std::string const RpgRepairAction::ActionName() { return "repair"; }
bool RpgTrainAction::isUseful()
{
if (!rpg->InRange())
return false;
Creature* creature = rpg->guidP().GetCreature();
if (!creature)
return false;
if (!creature->IsInWorld() || creature->IsDuringRemoveFromWorld() || !creature->IsAlive())
return false;
return true;
}
bool RpgTrainAction::isPossible()
{
GuidPosition gp = rpg->guidP();
CreatureTemplate const* cinfo = gp.GetCreatureTemplate();
if (!cinfo)
return false;
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(cinfo->Entry);
if (!trainer)
return false;
if (!trainer->IsTrainerValidForPlayer(bot))
return false;
FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cinfo->faction);
float reputationDiscount = bot->GetReputationPriceDiscount(factionTemplate);
uint32 currentGold = AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::spells);
for (auto& spell : trainer->GetSpells())
{
Trainer::Spell const* trainerSpell = trainer->GetSpell(spell.SpellId);
if (!trainerSpell)
continue;
if (!trainer->CanTeachSpell(bot, trainerSpell))
continue;
if (currentGold < static_cast<uint32>(floor(trainerSpell->MoneyCost * reputationDiscount)))
continue;
// we only check if at least one spell can be learned from the trainer;
// otherwise, the train action should not be allowed
return true;
}
return false;
}
std::string const RpgTrainAction::ActionName() { return "trainer"; } std::string const RpgTrainAction::ActionName() { return "trainer"; }
bool RpgHealAction::Execute(Event event) bool RpgHealAction::Execute(Event /*event*/)
{ {
bool retVal = false; bool retVal = false;
@@ -287,21 +347,21 @@ std::string const RpgBuyPetitionAction::ActionName() { return "buy petition"; }
std::string const RpgUseAction::ActionName() { return "use"; } std::string const RpgUseAction::ActionName() { return "use"; }
Event RpgUseAction::ActionEvent(Event event) Event RpgUseAction::ActionEvent(Event /*event*/)
{ {
return Event("rpg action", chat->FormatWorldobject(rpg->guidP().GetWorldObject())); return Event("rpg action", chat->FormatWorldobject(rpg->guidP().GetWorldObject()));
} }
std::string const RpgSpellAction::ActionName() { return "cast random spell"; } std::string const RpgSpellAction::ActionName() { return "cast random spell"; }
Event RpgSpellAction::ActionEvent(Event event) Event RpgSpellAction::ActionEvent(Event /*event*/)
{ {
return Event("rpg action", chat->FormatWorldobject(rpg->guidP().GetWorldObject())); return Event("rpg action", chat->FormatWorldobject(rpg->guidP().GetWorldObject()));
} }
std::string const RpgCraftAction::ActionName() { return "craft random item"; } std::string const RpgCraftAction::ActionName() { return "craft random item"; }
Event RpgCraftAction::ActionEvent(Event event) Event RpgCraftAction::ActionEvent(Event /*event*/)
{ {
return Event("rpg action", chat->FormatWorldobject(rpg->guidP().GetWorldObject())); return Event("rpg action", chat->FormatWorldobject(rpg->guidP().GetWorldObject()));
} }
@@ -341,7 +401,7 @@ std::vector<Item*> RpgTradeUsefulAction::CanGiveItems(GuidPosition guidPosition)
return giveItems; return giveItems;
} }
bool RpgTradeUsefulAction::Execute(Event event) bool RpgTradeUsefulAction::Execute(Event /*event*/)
{ {
GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target"); GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target");
@@ -392,7 +452,7 @@ bool RpgTradeUsefulAction::Execute(Event event)
bot->Say("Start trade with" + chat->FormatWorldobject(player), bot->Say("Start trade with" + chat->FormatWorldobject(player),
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
botAI->SetNextCheckDelay(sPlayerbotAIConfig->rpgDelay); botAI->SetNextCheckDelay(sPlayerbotAIConfig.rpgDelay);
return true; return true;
} }
@@ -402,7 +462,7 @@ bool RpgTradeUsefulAction::Execute(Event event)
bool RpgDuelAction::isUseful() bool RpgDuelAction::isUseful()
{ {
// do not offer duel in non pvp areas // do not offer duel in non pvp areas
if (sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId())) if (sPlayerbotAIConfig.IsInPvpProhibitedZone(bot->GetZoneId()))
return false; return false;
// Players can only fight a duel with each other outside (=not inside dungeons and not in capital cities) // Players can only fight a duel with each other outside (=not inside dungeons and not in capital cities)
@@ -416,7 +476,7 @@ bool RpgDuelAction::isUseful()
return true; return true;
} }
bool RpgDuelAction::Execute(Event event) bool RpgDuelAction::Execute(Event /*event*/)
{ {
GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target"); GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target");
@@ -434,10 +494,10 @@ bool RpgMountAnimAction::isUseful()
return AI_VALUE2(bool, "mounted", "self target") && !AI_VALUE2(bool, "moving", "self target"); return AI_VALUE2(bool, "mounted", "self target") && !AI_VALUE2(bool, "moving", "self target");
} }
bool RpgMountAnimAction::Execute(Event event) bool RpgMountAnimAction::Execute(Event /*event*/)
{ {
WorldPacket p; WorldPacket p;
bot->GetSession()->HandleMountSpecialAnimOpcode(p); bot->GetSession()->HandleMountSpecialAnimOpcode(p);
return true; return true;
} }

View File

@@ -165,6 +165,9 @@ class RpgTrainAction : public RpgSubAction
public: public:
RpgTrainAction(PlayerbotAI* botAI, std::string const name = "rpg train") : RpgSubAction(botAI, name) {} RpgTrainAction(PlayerbotAI* botAI, std::string const name = "rpg train") : RpgSubAction(botAI, name) {}
bool isPossible() override;
bool isUseful() override;
private: private:
std::string const ActionName() override; std::string const ActionName() override;
}; };

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