liyunfan1223 / mod-playerbots

AzerothCore Playerbots Module
https://discord.gg/NQm5QShwf9
GNU Affero General Public License v3.0
281 stars 151 forks source link

[Question] Dungeon Strategies - How would you implement them? #172

Closed noisiver closed 5 months ago

noisiver commented 7 months ago

I'm looking at implementing some strategies for Wrath of the Lich King dungeons, mainly the heroic versions. I'm a bit stumped though as to where to begin. I don't know if it should be done like the strategies for Naxxramas are implemented or not. It seems a bit too complex, I would assume something simpler would be the way to go for dungeons.

~I'm curious about the table named playerbots_dungeon_suggestion_strategy too - what is it and how does it work? Could it be used to do this?~ I wasn't thinking about this one, it's just for suggesting dungeons. Duh.

My C++ isn't fantastic but I'm willing to learn, and submit any progress I make so others can enjoy it too, but I need some pointers to get me started.

Having to enable the module for each dungeon and raid too, couldn't it be automatically enabled when entering the corresponding instance? I'm thinking of the point where a lot of content has strategies, making sure you always enable the module for each instance you enter could become a problem.

Edit: After looking into the scripts for naxx and bwl, I realize it's the way to do it. I am however not sure exactly how to handle dungeons, and there's not yet any comparison for me to look at. A dungeon doesn't need as much complexity as Naxxramas after all. One great example is Utgarde Keep. Prince Keleseth spawns frost tombs that needs to be focused down and Ingvar has smash in both phases that really hurts if not avoided and his spinning axe is almost as important.

liyunfan1223 commented 7 months ago

Glad to hear that! I think the bwl strategy can be a reference point. Naxx strategy is complicated because it relies on AIScripts of certain bosses, and the bot should monitor detailed events of bosses, but it is unnecessary for most of the easier dungeon fights.

About enabling the strategy automatically, it's a good idea and I will find a way to implement it later.

I understand that it's a challenge to start and am glad to provide any kind of help. You can also start by modifying strategy for different classes, like FrostMageStrategy.cpp, which is not fully implemented. You can observe how your modifications on the bot's behaviors more intuitively.

noisiver commented 7 months ago

Thank you for giving me some pointers! Most dungeons can be done without any specific strategy for sure but some really need it and that's what I'd like to try to do. I am looking through the strategies for naxx and bwl to learn the system but didn't realize I could also look at the class-specific strategies.

I'll start with something easy like the frost tombs and work my way from there. Ingvar in Utgarde Keep though, I'm not sure if that would require me to get anything from the boss script or if it could be done without it. Tank should be the only one who stands in front at any time but when he casts smash the tank should move behind him too. If I don't check to make sure they're fighting Ingvar won't they always have that strategy active regardless of what they're fighting?

Edit: I'm overthinking that one, I just realized. When he casts smash everyone should move away from the front but if he isn't casting it then they don't have to care. That makes it a lot easier to handle.

noisiver commented 7 months ago

Just an update.

I managed to get dungeon strategies to.. kind of work. I didn't realize I actually needed a trigger for each action but now it does trigger but.. well.. Prince Keleseth, when he spawns frost tombs on people the bots do react but they basically dance in place switching between frost tomb and prince keleseth constantly so they're stuck in an infinite loop until the frost tomb goes away.

The thing is, most of this code is copy-pasted from bwl or naxx but modified to fit with this so I really don't understand why it's acting like this.

I can see when debugging that they're switching their target to frost tomb and then instantly back to boss because the frost tomb target is nullptr and they loop like that over and over again until the frost tomb is gone. I'm going to step away from this for a little bit so I can clear my head and hopefully I can find a solution to the problem.

liyunfan1223 commented 7 months ago

I'm not sure why the target is nullptr, but it's likely that the newly added action conflicts with the general target selection action. My current solution is to disable the general action when a certain boss is detected and only use the newly added target selection action to take over it.

For example, in Naxxramas FourHorsemen, we use a multiplier to disable the general target selection action for both DPS and tanks.

In RaidNaxxMultipliers.cpp:

if ((dynamic_cast<DpsAssistAction*>(action) || 
     dynamic_cast<TankAssistAction*>(action))) {
    return 0.0f;
}

And control the target selection only with HorsemanAttactInOrderAction.

By the way, there should be a better solution, for example, setting a high-priority target list "value" that makes the bot keep focusing on them until they are dead. However, the solution above can temporarily make it work.

liyunfan1223 commented 7 months ago

Of course, I haven't checked your implementation, so it's possible that the issue may be caused by other reasons that haven't been considered.

noisiver commented 7 months ago

I've been changing the code so many times I don't even remember what it looked like originally. This is the version I tried last and the version I have saved, although they all made the bots behave exactly the same.

Action:

bool KelesethChooseTargetAction::Execute(Event /*event*/)
{
    GuidVector targets = context->GetValue<GuidVector>("possible targets")->Get();
    Unit* target = nullptr;

    for (GuidVector::iterator i = targets.begin(); i != targets.end(); ++i)
    {
        if (Unit* unit = botAI->GetUnit(*i))
        {
            if (unit->IsAlive() && botAI->EqualLowercaseName(unit->GetName(), "frost tomb"))
            {
                target = unit;
            }
        }
    }

    if (!target || context->GetValue<Unit*>("current target")->Get() == target)
    {
        return false;
    }

    return Attack(target);
}

Trigger:

bool KelesethChooseTargetTrigger::IsActive()
{
    GuidVector targets = context->GetValue<GuidVector>("possible targets")->Get();

    for (GuidVector::iterator i = targets.begin(); i != targets.end(); ++i)
    {
        if (Unit* unit = botAI->GetUnit(*i))
        {
            if (unit->IsAlive() && botAI->EqualLowercaseName(unit->GetName(), "frost tomb") && !botAI->IsMainTank(bot))
            {
                return true;
            }
        }
    }

    return false;
}

Just to be clear, I've tried so many different ways of doing this and this is just the last one I tried with the least amount of code I could think of. I tried including the boss as a target if there is no frost tomb thinking maybe they are confused because of that but that didn't help either.

It's obvious that when there are no frost tombs, they behave perfectly as they handle the targeting themselves. The issue with the frost tomb appearing, them getting locked in a loop, has been there no matter what code I used. As you are more aware of how this system works, I'm hoping to get a lot better at it, you might see what I've done wrong or what I can do right.

noisiver commented 6 months ago

I'm curious and maybe you can answer this, being as you're the main developer. Do the bots prioritize marked targets? Adding skull to a mob seems to make them focus on that one when possible. That's one of my questions. The second one is if this is the case then couldn't the same system (but refined for that purpose) be used to let strategies automate the process instead of having to put markers on everything?

liyunfan1223 commented 5 months ago

Keep it simple, it does not apply to some special units currently, such as the frost tomb we memtioned, only to normal units. I think it's an important feature, but it may involve a series of modifications. Target selection is a bit complicated, and it can easily lead to bugs that cause the bot to get stuck. So I'll consider it carefully before implementing.

I agree that reusing priority targets can be a good implementation of these strategies.