mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-19 03:35:43 +00:00
[FIX] Finalized structure! (do not start fixing PR merge structure conflict till this is merged) (#2025)
Finalized
This commit is contained in:
459
src/Bot/BaseAi/ActionContext.h
Normal file
459
src/Bot/BaseAi/ActionContext.h
Normal file
@@ -0,0 +1,459 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_ACTIONCONTEXT_H
|
||||
|
||||
#include "AddLootAction.h"
|
||||
#include "AttackAction.h"
|
||||
#include "ShareQuestAction.h"
|
||||
#include "BattleGroundTactics.h"
|
||||
#include "AutoMaintenanceOnLevelupAction.h"
|
||||
#include "BattleGroundJoinAction.h"
|
||||
#include "BattleGroundTactics.h"
|
||||
#include "BuyAction.h"
|
||||
#include "CastCustomSpellAction.h"
|
||||
#include "ChangeStrategyAction.h"
|
||||
#include "ChangeTalentsAction.h"
|
||||
#include "CheckMailAction.h"
|
||||
#include "CheckValuesAction.h"
|
||||
#include "ChooseRpgTargetAction.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "ChooseTravelTargetAction.h"
|
||||
#include "CombatActions.h"
|
||||
#include "DelayAction.h"
|
||||
#include "DestroyItemAction.h"
|
||||
#include "EmoteAction.h"
|
||||
#include "FollowActions.h"
|
||||
#include "GenericActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "GiveItemAction.h"
|
||||
#include "GreetAction.h"
|
||||
#include "GuildAcceptAction.h"
|
||||
#include "GuildCreateActions.h"
|
||||
#include "GuildManagementActions.h"
|
||||
#include "ImbueAction.h"
|
||||
#include "InviteToGroupAction.h"
|
||||
#include "LeaveGroupAction.h"
|
||||
#include "LootAction.h"
|
||||
#include "LootRollAction.h"
|
||||
#include "MoveToRpgTargetAction.h"
|
||||
#include "MoveToTravelTargetAction.h"
|
||||
#include "MovementActions.h"
|
||||
#include "NonCombatActions.h"
|
||||
#include "OutfitAction.h"
|
||||
#include "PositionAction.h"
|
||||
#include "DropQuestAction.h"
|
||||
#include "RandomBotUpdateAction.h"
|
||||
#include "ReachTargetActions.h"
|
||||
#include "ReleaseSpiritAction.h"
|
||||
#include "RemoveAuraAction.h"
|
||||
#include "ResetInstancesAction.h"
|
||||
#include "RevealGatheringItemAction.h"
|
||||
#include "RpgAction.h"
|
||||
#include "RpgSubActions.h"
|
||||
#include "RtiAction.h"
|
||||
#include "SayAction.h"
|
||||
#include "StayActions.h"
|
||||
#include "SuggestWhatToDoAction.h"
|
||||
#include "TravelAction.h"
|
||||
#include "VehicleActions.h"
|
||||
#include "WorldBuffAction.h"
|
||||
#include "XpGainAction.h"
|
||||
#include "NewRpgAction.h"
|
||||
#include "FishingAction.h"
|
||||
#include "CancelChannelAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class ActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
ActionContext()
|
||||
{
|
||||
creators["mark rti"] = &ActionContext::mark_rti;
|
||||
creators["set return position"] = &ActionContext::set_return_position;
|
||||
creators["rpg"] = &ActionContext::rpg;
|
||||
creators["crpg"] = &ActionContext::crpg;
|
||||
creators["choose rpg target"] = &ActionContext::choose_rpg_target;
|
||||
creators["move to rpg target"] = &ActionContext::move_to_rpg_target;
|
||||
creators["travel"] = &ActionContext::travel;
|
||||
creators["choose travel target"] = &ActionContext::choose_travel_target;
|
||||
creators["move to travel target"] = &ActionContext::move_to_travel_target;
|
||||
creators["move out of collision"] = &ActionContext::move_out_of_collision;
|
||||
creators["move random"] = &ActionContext::move_random;
|
||||
creators["attack"] = &ActionContext::melee;
|
||||
creators["melee"] = &ActionContext::melee;
|
||||
creators["switch to melee"] = &ActionContext::switch_to_melee;
|
||||
creators["switch to ranged"] = &ActionContext::switch_to_ranged;
|
||||
creators["reach spell"] = &ActionContext::ReachSpell;
|
||||
creators["reach melee"] = &ActionContext::ReachMelee;
|
||||
creators["reach party member to heal"] = &ActionContext::reach_party_member_to_heal;
|
||||
creators["reach party member to resurrect"] = &ActionContext::reach_party_member_to_resurrect;
|
||||
creators["flee"] = &ActionContext::flee;
|
||||
creators["flee with pet"] = &ActionContext::flee_with_pet;
|
||||
creators["avoid aoe"] = &ActionContext::avoid_aoe;
|
||||
creators["combat formation move"] = &ActionContext::combat_formation_move;
|
||||
creators["tank face"] = &ActionContext::tank_face;
|
||||
creators["rear flank"] = &ActionContext::rear_flank;
|
||||
creators["disperse set"] = &ActionContext::disperse_set;
|
||||
creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru;
|
||||
creators["shoot"] = &ActionContext::shoot;
|
||||
creators["lifeblood"] = &ActionContext::lifeblood;
|
||||
creators["arcane torrent"] = &ActionContext::arcane_torrent;
|
||||
creators["end pull"] = &ActionContext::end_pull;
|
||||
creators["healthstone"] = &ActionContext::healthstone;
|
||||
creators["healing potion"] = &ActionContext::healing_potion;
|
||||
creators["mana potion"] = &ActionContext::mana_potion;
|
||||
creators["food"] = &ActionContext::food;
|
||||
creators["drink"] = &ActionContext::drink;
|
||||
creators["tank assist"] = &ActionContext::tank_assist;
|
||||
creators["dps assist"] = &ActionContext::dps_assist;
|
||||
creators["dps aoe"] = &ActionContext::dps_aoe;
|
||||
creators["attack rti target"] = &ActionContext::attack_rti_target;
|
||||
creators["loot"] = &ActionContext::loot;
|
||||
creators["add loot"] = &ActionContext::add_loot;
|
||||
creators["add gathering loot"] = &ActionContext::add_gathering_loot;
|
||||
creators["add all loot"] = &ActionContext::add_all_loot;
|
||||
creators["release loot"] = &ActionContext::release_loot;
|
||||
creators["shoot"] = &ActionContext::shoot;
|
||||
creators["follow"] = &ActionContext::follow;
|
||||
creators["move from group"] = &ActionContext::move_from_group;
|
||||
creators["flee to group leader"] = &ActionContext::flee_to_group_leader;
|
||||
creators["runaway"] = &ActionContext::runaway;
|
||||
creators["stay"] = &ActionContext::stay;
|
||||
creators["sit"] = &ActionContext::sit;
|
||||
creators["attack anything"] = &ActionContext::attack_anything;
|
||||
creators["attack least hp target"] = &ActionContext::attack_least_hp_target;
|
||||
creators["attack enemy player"] = &ActionContext::attack_enemy_player;
|
||||
creators["emote"] = &ActionContext::emote;
|
||||
creators["talk"] = &ActionContext::talk;
|
||||
creators["suggest what to do"] = &ActionContext::suggest_what_to_do;
|
||||
creators["suggest trade"] = &ActionContext::suggest_trade;
|
||||
creators["suggest dungeon"] = &ActionContext::suggest_dungeon;
|
||||
creators["return"] = &ActionContext::_return;
|
||||
creators["move to loot"] = &ActionContext::move_to_loot;
|
||||
creators["open loot"] = &ActionContext::open_loot;
|
||||
creators["guard"] = &ActionContext::guard;
|
||||
creators["return to stay position"] = &ActionContext::return_to_stay_position;
|
||||
creators["move out of enemy contact"] = &ActionContext::move_out_of_enemy_contact;
|
||||
creators["set facing"] = &ActionContext::set_facing;
|
||||
creators["set behind"] = &ActionContext::set_behind;
|
||||
creators["attack duel opponent"] = &ActionContext::attack_duel_opponent;
|
||||
creators["drop target"] = &ActionContext::drop_target;
|
||||
creators["check mail"] = &ActionContext::check_mail;
|
||||
creators["say"] = &ActionContext::say;
|
||||
creators["reveal gathering item"] = &ActionContext::reveal_gathering_item;
|
||||
creators["outfit"] = &ActionContext::outfit;
|
||||
creators["random bot update"] = &ActionContext::random_bot_update;
|
||||
creators["delay"] = &ActionContext::delay;
|
||||
creators["greet"] = &ActionContext::greet;
|
||||
creators["check values"] = &ActionContext::check_values;
|
||||
creators["ra"] = &ActionContext::ra;
|
||||
creators["apply poison"] = &ActionContext::apply_poison;
|
||||
creators["apply stone"] = &ActionContext::apply_stone;
|
||||
creators["apply oil"] = &ActionContext::apply_oil;
|
||||
creators["try emergency"] = &ActionContext::try_emergency;
|
||||
creators["give food"] = &ActionContext::give_food;
|
||||
creators["give water"] = &ActionContext::give_water;
|
||||
creators["mount"] = &ActionContext::mount;
|
||||
creators["war stomp"] = &ActionContext::war_stomp;
|
||||
creators["blood fury"] = &ActionContext::blood_fury;
|
||||
creators["berserking"] = &ActionContext::berserking;
|
||||
creators["use trinket"] = &ActionContext::use_trinket;
|
||||
creators["auto talents"] = &ActionContext::auto_talents;
|
||||
creators["auto share quest"] = &ActionContext::auto_share_quest;
|
||||
creators["auto maintenance on levelup"] = &ActionContext::auto_maintenance_on_levelup;
|
||||
creators["xp gain"] = &ActionContext::xp_gain;
|
||||
creators["invite nearby"] = &ActionContext::invite_nearby;
|
||||
creators["invite guild"] = &ActionContext::invite_guild;
|
||||
creators["leave far away"] = &ActionContext::leave_far_away;
|
||||
creators["move to dark portal"] = &ActionContext::move_to_dark_portal;
|
||||
creators["move from dark portal"] = &ActionContext::move_from_dark_portal;
|
||||
creators["use dark portal azeroth"] = &ActionContext::use_dark_portal_azeroth;
|
||||
creators["world buff"] = &ActionContext::world_buff;
|
||||
creators["hearthstone"] = &ActionContext::hearthstone;
|
||||
creators["cast random spell"] = &ActionContext::cast_random_spell;
|
||||
creators["free bg join"] = &ActionContext::free_bg_join;
|
||||
creators["use random recipe"] = &ActionContext::use_random_recipe;
|
||||
creators["use random quest item"] = &ActionContext::use_random_quest_item;
|
||||
creators["craft random item"] = &ActionContext::craft_random_item;
|
||||
creators["smart destroy item"] = &ActionContext::smart_destroy_item;
|
||||
creators["disenchant random item"] = &ActionContext::disenchant_random_item;
|
||||
creators["enchant random item"] = &ActionContext::enchant_random_item;
|
||||
creators["reset instances"] = &ActionContext::reset_instances;
|
||||
creators["buy petition"] = &ActionContext::buy_petition;
|
||||
creators["offer petition"] = &ActionContext::offer_petition;
|
||||
creators["offer petition nearby"] = &ActionContext::offer_petition_nearby;
|
||||
creators["turn in petition"] = &ActionContext::turn_in_petition;
|
||||
creators["buy tabard"] = &ActionContext::buy_tabard;
|
||||
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
|
||||
creators["clean quest log"] = &ActionContext::clean_quest_log;
|
||||
creators["move near water"] = &ActionContext::move_near_water;
|
||||
creators["go fishing"] = &ActionContext::go_fishing;
|
||||
creators["use fishing bobber"] = &ActionContext::use_fishing_bobber;
|
||||
creators["end master fishing"] = &ActionContext::end_master_fishing;
|
||||
creators["remove bobber strategy"] = &ActionContext::remove_bobber_strategy;
|
||||
creators["roll"] = &ActionContext::roll_action;
|
||||
creators["cancel channel"] = &ActionContext::cancel_channel;
|
||||
|
||||
// BG Tactics
|
||||
creators["bg tactics"] = &ActionContext::bg_tactics;
|
||||
creators["bg move to start"] = &ActionContext::bg_move_to_start;
|
||||
creators["bg reset objective force"] = &ActionContext::bg_reset_objective_force;
|
||||
creators["bg move to objective"] = &ActionContext::bg_move_to_objective;
|
||||
creators["bg select objective"] = &ActionContext::bg_select_objective;
|
||||
creators["bg check objective"] = &ActionContext::bg_check_objective;
|
||||
creators["bg attack fc"] = &ActionContext::bg_attack_fc;
|
||||
creators["bg protect fc"] = &ActionContext::bg_protect_fc;
|
||||
creators["bg use buff"] = &ActionContext::bg_use_buff;
|
||||
creators["attack enemy flag carrier"] = &ActionContext::attack_enemy_fc;
|
||||
creators["bg check flag"] = &ActionContext::bg_check_flag;
|
||||
|
||||
// Vehicles
|
||||
creators["enter vehicle"] = &ActionContext::enter_vehicle;
|
||||
creators["leave vehicle"] = &ActionContext::leave_vehicle;
|
||||
creators["hurl boulder"] = &ActionContext::hurl_boulder;
|
||||
creators["ram"] = &ActionContext::ram;
|
||||
creators["steam rush"] = &ActionContext::steam_rush;
|
||||
creators["steam blast"] = &ActionContext::steam_blast;
|
||||
creators["napalm"] = &ActionContext::napalm;
|
||||
creators["fire cannon"] = &ActionContext::fire_cannon;
|
||||
creators["incendiary rocket"] = &ActionContext::incendiary_rocket;
|
||||
creators["rocket blast"] = &ActionContext::rocket_blast;
|
||||
creators["blade salvo"] = &ActionContext::blade_salvo;
|
||||
creators["glaive throw"] = &ActionContext::glaive_throw;
|
||||
|
||||
//Rpg
|
||||
creators["rpg stay"] = &ActionContext::rpg_stay;
|
||||
creators["rpg work"] = &ActionContext::rpg_work;
|
||||
creators["rpg emote"] = &ActionContext::rpg_emote;
|
||||
creators["rpg cancel"] = &ActionContext::rpg_cancel;
|
||||
creators["rpg taxi"] = &ActionContext::rpg_taxi;
|
||||
creators["rpg discover"] = &ActionContext::rpg_discover;
|
||||
creators["rpg start quest"] = &ActionContext::rpg_start_quest;
|
||||
creators["rpg end quest"] = &ActionContext::rpg_end_quest;
|
||||
creators["rpg buy"] = &ActionContext::rpg_buy;
|
||||
creators["rpg sell"] = &ActionContext::rpg_sell;
|
||||
creators["rpg repair"] = &ActionContext::rpg_repair;
|
||||
creators["rpg train"] = &ActionContext::rpg_train;
|
||||
creators["rpg heal"] = &ActionContext::rpg_heal;
|
||||
creators["rpg home bind"] = &ActionContext::rpg_home_bind;
|
||||
creators["rpg queue bg"] = &ActionContext::rpg_queue_bg;
|
||||
creators["rpg buy petition"] = &ActionContext::rpg_buy_petition;
|
||||
creators["rpg use"] = &ActionContext::rpg_use;
|
||||
creators["rpg spell"] = &ActionContext::rpg_spell;
|
||||
creators["rpg craft"] = &ActionContext::rpg_craft;
|
||||
creators["rpg trade useful"] = &ActionContext::rpg_trade_useful;
|
||||
creators["rpg duel"] = &ActionContext::rpg_duel;
|
||||
creators["rpg mount anim"] = &ActionContext::rpg_mount_anim;
|
||||
|
||||
creators["toggle pet spell"] = &ActionContext::toggle_pet_spell;
|
||||
creators["pet attack"] = &ActionContext::pet_attack;
|
||||
creators["set pet stance"] = &ActionContext::set_pet_stance;
|
||||
|
||||
creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
|
||||
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
|
||||
creators["new rpg go camp"] = &ActionContext::new_rpg_go_camp;
|
||||
creators["new rpg wander random"] = &ActionContext::new_rpg_wander_random;
|
||||
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
|
||||
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
|
||||
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
|
||||
}
|
||||
|
||||
private:
|
||||
static Action* give_water(PlayerbotAI* botAI) { return new GiveWaterAction(botAI); }
|
||||
static Action* give_food(PlayerbotAI* botAI) { return new GiveFoodAction(botAI); }
|
||||
static Action* ra(PlayerbotAI* botAI) { return new RemoveAuraAction(botAI); }
|
||||
static Action* mark_rti(PlayerbotAI* botAI) { return new MarkRtiAction(botAI); }
|
||||
static Action* set_return_position(PlayerbotAI* botAI) { return new SetReturnPositionAction(botAI); }
|
||||
static Action* rpg(PlayerbotAI* botAI) { return new RpgAction(botAI); }
|
||||
static Action* crpg(PlayerbotAI* botAI) { return new CRpgAction(botAI); }
|
||||
static Action* choose_rpg_target(PlayerbotAI* botAI) { return new ChooseRpgTargetAction(botAI); }
|
||||
static Action* move_to_rpg_target(PlayerbotAI* botAI) { return new MoveToRpgTargetAction(botAI); }
|
||||
static Action* travel(PlayerbotAI* botAI) { return new TravelAction(botAI); }
|
||||
static Action* choose_travel_target(PlayerbotAI* botAI) { return new ChooseTravelTargetAction(botAI); }
|
||||
static Action* move_to_travel_target(PlayerbotAI* botAI) { return new MoveToTravelTargetAction(botAI); }
|
||||
static Action* move_out_of_collision(PlayerbotAI* botAI) { return new MoveOutOfCollisionAction(botAI); }
|
||||
static Action* move_random(PlayerbotAI* botAI) { return new MoveRandomAction(botAI); }
|
||||
static Action* check_values(PlayerbotAI* botAI) { return new CheckValuesAction(botAI); }
|
||||
static Action* greet(PlayerbotAI* botAI) { return new GreetAction(botAI); }
|
||||
static Action* check_mail(PlayerbotAI* botAI) { return new CheckMailAction(botAI); }
|
||||
static Action* drop_target(PlayerbotAI* botAI) { return new DropTargetAction(botAI); }
|
||||
static Action* attack_duel_opponent(PlayerbotAI* botAI) { return new AttackDuelOpponentAction(botAI); }
|
||||
static Action* guard(PlayerbotAI* botAI) { return new GuardAction(botAI); }
|
||||
static Action* return_to_stay_position(PlayerbotAI* botAI) { return new ReturnToStayPositionAction(botAI); }
|
||||
static Action* open_loot(PlayerbotAI* botAI) { return new OpenLootAction(botAI); }
|
||||
static Action* move_to_loot(PlayerbotAI* botAI) { return new MoveToLootAction(botAI); }
|
||||
static Action* _return(PlayerbotAI* botAI) { return new ReturnAction(botAI); }
|
||||
static Action* shoot(PlayerbotAI* botAI) { return new CastShootAction(botAI); }
|
||||
static Action* melee(PlayerbotAI* botAI) { return new MeleeAction(botAI); }
|
||||
static Action* switch_to_melee(PlayerbotAI* botAI) { return new SwitchToMeleeAction(botAI); }
|
||||
static Action* switch_to_ranged(PlayerbotAI* botAI) { return new SwitchToRangedAction(botAI); }
|
||||
static Action* ReachSpell(PlayerbotAI* botAI) { return new ReachSpellAction(botAI); }
|
||||
static Action* ReachMelee(PlayerbotAI* botAI) { return new ReachMeleeAction(botAI); }
|
||||
static Action* reach_party_member_to_heal(PlayerbotAI* botAI) { return new ReachPartyMemberToHealAction(botAI); }
|
||||
static Action* reach_party_member_to_resurrect(PlayerbotAI* botAI) { return new ReachPartyMemberToResurrectAction(botAI); }
|
||||
static Action* flee(PlayerbotAI* botAI) { return new FleeAction(botAI); }
|
||||
static Action* flee_with_pet(PlayerbotAI* botAI) { return new FleeWithPetAction(botAI); }
|
||||
static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); }
|
||||
static Action* combat_formation_move(PlayerbotAI* botAI) { return new CombatFormationMoveAction(botAI); }
|
||||
static Action* tank_face(PlayerbotAI* botAI) { return new TankFaceAction(botAI); }
|
||||
static Action* rear_flank(PlayerbotAI* botAI) { return new RearFlankAction(botAI); }
|
||||
static Action* disperse_set(PlayerbotAI* botAI) { return new DisperseSetAction(botAI); }
|
||||
static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); }
|
||||
static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); }
|
||||
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
|
||||
static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); }
|
||||
static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); }
|
||||
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
|
||||
|
||||
static Action* emote(PlayerbotAI* botAI) { return new EmoteAction(botAI); }
|
||||
static Action* talk(PlayerbotAI* botAI) { return new TalkAction(botAI); }
|
||||
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_dungeon(PlayerbotAI* botAI) { return new SuggestDungeonAction(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_enemy_player(PlayerbotAI* botAI) { return new AttackEnemyPlayerAction(botAI); }
|
||||
static Action* stay(PlayerbotAI* botAI) { return new StayAction(botAI); }
|
||||
static Action* sit(PlayerbotAI* botAI) { return new SitAction(botAI); }
|
||||
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
|
||||
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
|
||||
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
|
||||
static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); }
|
||||
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
|
||||
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
|
||||
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
|
||||
static Action* loot(PlayerbotAI* botAI) { return new LootAction(botAI); }
|
||||
static Action* release_loot(PlayerbotAI* botAI) { return new ReleaseLootAction(botAI); }
|
||||
static Action* dps_assist(PlayerbotAI* botAI) { return new DpsAssistAction(botAI); }
|
||||
static Action* dps_aoe(PlayerbotAI* botAI) { return new DpsAoeAction(botAI); }
|
||||
static Action* attack_rti_target(PlayerbotAI* botAI) { return new AttackRtiTargetAction(botAI); }
|
||||
static Action* tank_assist(PlayerbotAI* botAI) { return new TankAssistAction(botAI); }
|
||||
static Action* drink(PlayerbotAI* botAI) { return new DrinkAction(botAI); }
|
||||
static Action* food(PlayerbotAI* botAI) { return new EatAction(botAI); }
|
||||
static Action* mana_potion(PlayerbotAI* botAI) { return new UseManaPotion(botAI); }
|
||||
static Action* healing_potion(PlayerbotAI* botAI) { return new UseHealingPotion(botAI); }
|
||||
static Action* healthstone(PlayerbotAI* botAI) { return new UseItemAction(botAI, "healthstone"); }
|
||||
static Action* move_out_of_enemy_contact(PlayerbotAI* botAI) { return new MoveOutOfEnemyContactAction(botAI); }
|
||||
static Action* set_facing(PlayerbotAI* botAI) { return new SetFacingTargetAction(botAI); }
|
||||
static Action* set_behind(PlayerbotAI* botAI) { return new SetBehindTargetAction(botAI); }
|
||||
static Action* say(PlayerbotAI* botAI) { return new SayAction(botAI); }
|
||||
static Action* reveal_gathering_item(PlayerbotAI* botAI) { return new RevealGatheringItemAction(botAI); }
|
||||
static Action* outfit(PlayerbotAI* botAI) { return new OutfitAction(botAI); }
|
||||
static Action* random_bot_update(PlayerbotAI* botAI) { return new RandomBotUpdateAction(botAI); }
|
||||
static Action* delay(PlayerbotAI* botAI) { return new DelayAction(botAI); }
|
||||
|
||||
static Action* apply_poison(PlayerbotAI* botAI) { return new ImbueWithPoisonAction(botAI); }
|
||||
static Action* apply_oil(PlayerbotAI* botAI) { return new ImbueWithOilAction(botAI); }
|
||||
static Action* apply_stone(PlayerbotAI* botAI) { return new ImbueWithStoneAction(botAI); }
|
||||
static Action* try_emergency(PlayerbotAI* botAI) { return new TryEmergencyAction(botAI); }
|
||||
static Action* mount(PlayerbotAI* botAI) { return new CastSpellAction(botAI, "mount"); }
|
||||
static Action* war_stomp(PlayerbotAI* botAI) { return new CastWarStompAction(botAI); }
|
||||
static Action* blood_fury(PlayerbotAI* botAI) { return new CastBloodFuryAction(botAI); }
|
||||
static Action* berserking(PlayerbotAI* botAI) { return new CastBerserkingAction(botAI); }
|
||||
static Action* use_trinket(PlayerbotAI* botAI) { return new UseTrinketAction(botAI); }
|
||||
static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); }
|
||||
static Action* auto_share_quest(PlayerbotAI* ai) { return new AutoShareQuestAction(ai); }
|
||||
static Action* auto_maintenance_on_levelup(PlayerbotAI* botAI) { return new AutoMaintenanceOnLevelupAction(botAI); }
|
||||
static Action* xp_gain(PlayerbotAI* botAI) { return new XpGainAction(botAI); }
|
||||
static Action* invite_nearby(PlayerbotAI* botAI) { return new InviteNearbyToGroupAction(botAI); }
|
||||
static Action* invite_guild(PlayerbotAI* botAI) { return new InviteGuildToGroupAction(botAI); }
|
||||
static Action* leave_far_away(PlayerbotAI* botAI) { return new LeaveFarAwayAction(botAI); }
|
||||
static Action* move_to_dark_portal(PlayerbotAI* botAI) { return new MoveToDarkPortalAction(botAI); }
|
||||
static Action* use_dark_portal_azeroth(PlayerbotAI* botAI) { return new DarkPortalAzerothAction(botAI); }
|
||||
static Action* move_from_dark_portal(PlayerbotAI* botAI) { return new MoveFromDarkPortalAction(botAI); }
|
||||
static Action* world_buff(PlayerbotAI* botAI) { return new WorldBuffAction(botAI); }
|
||||
static Action* hearthstone(PlayerbotAI* botAI) { return new UseHearthStone(botAI); }
|
||||
static Action* cast_random_spell(PlayerbotAI* botAI) { return new CastRandomSpellAction(botAI); }
|
||||
static Action* free_bg_join(PlayerbotAI* botAI) { return new FreeBGJoinAction(botAI); }
|
||||
|
||||
static Action* use_random_recipe(PlayerbotAI* botAI) { return new UseRandomRecipe(botAI); }
|
||||
static Action* use_random_quest_item(PlayerbotAI* botAI) { return new UseRandomQuestItem(botAI); }
|
||||
static Action* craft_random_item(PlayerbotAI* botAI) { return new CraftRandomItemAction(botAI); }
|
||||
static Action* smart_destroy_item(PlayerbotAI* botAI) { return new SmartDestroyItemAction(botAI); }
|
||||
static Action* disenchant_random_item(PlayerbotAI* botAI) { return new DisEnchantRandomItemAction(botAI); }
|
||||
static Action* enchant_random_item(PlayerbotAI* botAI) { return new EnchantRandomItemAction(botAI); }
|
||||
static Action* reset_instances(PlayerbotAI* botAI) { return new ResetInstancesAction(botAI); }
|
||||
static Action* buy_petition(PlayerbotAI* botAI) { return new BuyPetitionAction(botAI); }
|
||||
static Action* offer_petition(PlayerbotAI* botAI) { return new PetitionOfferAction(botAI); }
|
||||
static Action* offer_petition_nearby(PlayerbotAI* botAI) { return new PetitionOfferNearbyAction(botAI); }
|
||||
static Action* turn_in_petition(PlayerbotAI* botAI) { return new PetitionTurnInAction(botAI); }
|
||||
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
|
||||
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
|
||||
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
|
||||
static Action* move_near_water(PlayerbotAI* botAI) { return new MoveNearWaterAction(botAI); }
|
||||
static Action* go_fishing(PlayerbotAI* botAI) { return new FishingAction(botAI);}
|
||||
static Action* use_fishing_bobber(PlayerbotAI* botAI) { return new UseBobberAction(botAI);}
|
||||
static Action* end_master_fishing(PlayerbotAI* botAI) { return new EndMasterFishingAction(botAI); }
|
||||
static Action* remove_bobber_strategy(PlayerbotAI* botAI) { return new RemoveBobberStrategyAction(botAI); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
|
||||
// BG Tactics
|
||||
static Action* bg_tactics(PlayerbotAI* botAI) { return new BGTactics(botAI); }
|
||||
static Action* bg_move_to_start(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to start"); }
|
||||
static Action* bg_reset_objective_force(PlayerbotAI* botAI) { return new BGTactics(botAI, "reset objective force"); }
|
||||
static Action* bg_move_to_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to objective"); }
|
||||
static Action* bg_select_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "select objective"); }
|
||||
static Action* bg_check_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "check objective"); }
|
||||
static Action* bg_attack_fc(PlayerbotAI* botAI) { return new BGTactics(botAI, "attack fc"); }
|
||||
static Action* bg_protect_fc(PlayerbotAI* botAI) { return new BGTactics(botAI, "protect fc"); }
|
||||
static Action* attack_enemy_fc(PlayerbotAI* botAI) { return new AttackEnemyFlagCarrierAction(botAI); }
|
||||
static Action* bg_use_buff(PlayerbotAI* botAI) { return new BGTactics(botAI, "use buff"); }
|
||||
static Action* bg_check_flag(PlayerbotAI* botAI) { return new BGTactics(botAI, "check flag"); }
|
||||
|
||||
// Vehicles
|
||||
static Action* enter_vehicle(PlayerbotAI* botAI) { return new EnterVehicleAction(botAI); }
|
||||
static Action* leave_vehicle(PlayerbotAI* botAI) { return new LeaveVehicleAction(botAI); }
|
||||
static Action* hurl_boulder(PlayerbotAI* botAI) { return new CastHurlBoulderAction(botAI); }
|
||||
static Action* ram(PlayerbotAI* botAI) { return new CastRamAction(botAI); }
|
||||
static Action* steam_blast(PlayerbotAI* botAI) { return new CastSteamBlastAction(botAI); }
|
||||
static Action* steam_rush(PlayerbotAI* botAI) { return new CastSteamRushAction(botAI); }
|
||||
static Action* napalm(PlayerbotAI* botAI) { return new CastNapalmAction(botAI); }
|
||||
static Action* fire_cannon(PlayerbotAI* botAI) { return new CastFireCannonAction(botAI); }
|
||||
static Action* incendiary_rocket(PlayerbotAI* botAI) { return new CastIncendiaryRocketAction(botAI); }
|
||||
static Action* rocket_blast(PlayerbotAI* botAI) { return new CastRocketBlastAction(botAI); }
|
||||
static Action* glaive_throw(PlayerbotAI* botAI) { return new CastGlaiveThrowAction(botAI); }
|
||||
static Action* blade_salvo(PlayerbotAI* botAI) { return new CastBladeSalvoAction(botAI); }
|
||||
|
||||
// Rpg
|
||||
static Action* rpg_stay(PlayerbotAI* botAI) { return new RpgStayAction(botAI); }
|
||||
static Action* rpg_work(PlayerbotAI* botAI) { return new RpgWorkAction(botAI); }
|
||||
static Action* rpg_emote(PlayerbotAI* botAI) { return new RpgEmoteAction(botAI); }
|
||||
static Action* rpg_cancel(PlayerbotAI* botAI) { return new RpgCancelAction(botAI); }
|
||||
static Action* rpg_taxi(PlayerbotAI* botAI) { return new RpgTaxiAction(botAI); }
|
||||
static Action* rpg_discover(PlayerbotAI* botAI) { return new RpgDiscoverAction(botAI); }
|
||||
static Action* rpg_start_quest(PlayerbotAI* botAI) { return new RpgStartQuestAction(botAI); }
|
||||
static Action* rpg_end_quest(PlayerbotAI* botAI) { return new RpgEndQuestAction(botAI); }
|
||||
static Action* rpg_buy(PlayerbotAI* botAI) { return new RpgBuyAction(botAI); }
|
||||
static Action* rpg_sell(PlayerbotAI* botAI) { return new RpgSellAction(botAI); }
|
||||
static Action* rpg_repair(PlayerbotAI* botAI) { return new RpgRepairAction(botAI); }
|
||||
static Action* rpg_train(PlayerbotAI* botAI) { return new RpgTrainAction(botAI); }
|
||||
static Action* rpg_heal(PlayerbotAI* botAI) { return new RpgHealAction(botAI); }
|
||||
static Action* rpg_home_bind(PlayerbotAI* botAI) { return new RpgHomeBindAction(botAI); }
|
||||
static Action* rpg_queue_bg(PlayerbotAI* botAI) { return new RpgQueueBgAction(botAI); }
|
||||
static Action* rpg_buy_petition(PlayerbotAI* botAI) { return new RpgBuyPetitionAction(botAI); }
|
||||
static Action* rpg_use(PlayerbotAI* botAI) { return new RpgUseAction(botAI); }
|
||||
static Action* rpg_spell(PlayerbotAI* botAI) { return new RpgSpellAction(botAI); }
|
||||
static Action* rpg_craft(PlayerbotAI* botAI) { return new RpgCraftAction(botAI); }
|
||||
static Action* rpg_trade_useful(PlayerbotAI* botAI) { return new RpgTradeUsefulAction(botAI); }
|
||||
static Action* rpg_duel(PlayerbotAI* botAI) { return new RpgDuelAction(botAI); }
|
||||
static Action* rpg_mount_anim(PlayerbotAI* botAI) { return new RpgMountAnimAction(botAI); }
|
||||
|
||||
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); }
|
||||
static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); }
|
||||
static Action* set_pet_stance(PlayerbotAI* ai) { return new SetPetStanceAction(ai); }
|
||||
|
||||
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
|
||||
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
|
||||
static Action* new_rpg_go_camp(PlayerbotAI* ai) { return new NewRpgGoCampAction(ai); }
|
||||
static Action* new_rpg_wander_random(PlayerbotAI* ai) { return new NewRpgWanderRandomAction(ai); }
|
||||
static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); }
|
||||
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
|
||||
static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AcceptBattlegroundInvitationAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool AcceptBgInvitationAction::Execute(Event event)
|
||||
{
|
||||
uint8 type = 0; // arenatype if arena
|
||||
uint8 unk2 = 0; // unk, can be 0x0 (may be if was invited?) and 0x1
|
||||
uint32 bgTypeId_ = BATTLEGROUND_WS; // type id from dbc
|
||||
uint16 unk = 0x1F90; // 0x1F90 constant?*/
|
||||
uint8 action = 1;
|
||||
|
||||
WorldPacket packet(CMSG_BATTLEFIELD_PORT, 20);
|
||||
packet << type << unk2 << (uint32)bgTypeId_ << unk << action;
|
||||
// packet << bgTypeId_ << action;
|
||||
bot->GetSession()->HandleBattleFieldPortOpcode(packet);
|
||||
|
||||
botAI->ResetStrategies();
|
||||
return true;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/AcceptBattlegroundInvitationAction.h
Normal file
21
src/Bot/BaseAi/Actions/AcceptBattlegroundInvitationAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACCEPTBATTLEGROUNDINVITATIONACTION_H
|
||||
#define _PLAYERBOT_ACCEPTBATTLEGROUNDINVITATIONACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class AcceptBgInvitationAction : public Action
|
||||
{
|
||||
public:
|
||||
AcceptBgInvitationAction(PlayerbotAI* botAI) : Action(botAI, "accept bg invitation") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
36
src/Bot/BaseAi/Actions/AcceptDuelAction.cpp
Normal file
36
src/Bot/BaseAi/Actions/AcceptDuelAction.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AcceptDuelAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool AcceptDuelAction::Execute(Event event)
|
||||
{
|
||||
WorldPacket p(event.getPacket());
|
||||
|
||||
ObjectGuid flagGuid;
|
||||
p >> flagGuid;
|
||||
ObjectGuid playerGuid;
|
||||
p >> playerGuid;
|
||||
|
||||
// do not auto duel with low hp
|
||||
if ((!botAI->HasRealPlayerMaster() || (botAI->GetMaster() && botAI->GetMaster()->GetGUID() != playerGuid)) &&
|
||||
AI_VALUE2(uint8, "health", "self target") < 90)
|
||||
{
|
||||
WorldPacket packet(CMSG_DUEL_CANCELLED, 8);
|
||||
packet << flagGuid;
|
||||
bot->GetSession()->HandleDuelCancelledOpcode(packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPacket packet(CMSG_DUEL_ACCEPTED, 8);
|
||||
packet << flagGuid;
|
||||
bot->GetSession()->HandleDuelAcceptedOpcode(packet);
|
||||
|
||||
botAI->ResetStrategies();
|
||||
return true;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/AcceptDuelAction.h
Normal file
21
src/Bot/BaseAi/Actions/AcceptDuelAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACCEPTDUELACTION_H
|
||||
#define _PLAYERBOT_ACCEPTDUELACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class AcceptDuelAction : public Action
|
||||
{
|
||||
public:
|
||||
AcceptDuelAction(PlayerbotAI* botAI) : Action(botAI, "accept duel") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
65
src/Bot/BaseAi/Actions/AcceptInvitationAction.cpp
Normal file
65
src/Bot/BaseAi/Actions/AcceptInvitationAction.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AcceptInvitationAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotSecurity.h"
|
||||
#include "Playerbots.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
bool AcceptInvitationAction::Execute(Event event)
|
||||
{
|
||||
Group* grp = bot->GetGroupInvite();
|
||||
if (!grp)
|
||||
return false;
|
||||
WorldPacket packet = event.getPacket();
|
||||
uint8 flag;
|
||||
std::string name;
|
||||
packet >> flag >> name;
|
||||
|
||||
Player* inviter = ObjectAccessor::FindPlayer(grp->GetLeaderGUID());
|
||||
if (!inviter)
|
||||
return false;
|
||||
|
||||
if (!botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter))
|
||||
{
|
||||
WorldPacket data(SMSG_GROUP_DECLINE, 10);
|
||||
data << bot->GetName();
|
||||
inviter->SendDirectMessage(&data);
|
||||
bot->UninviteFromGroup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot->isAFK())
|
||||
bot->ToggleAFK();
|
||||
|
||||
WorldPacket p;
|
||||
uint32 roles_mask = 0;
|
||||
p << roles_mask;
|
||||
bot->GetSession()->HandleGroupAcceptOpcode(p);
|
||||
|
||||
if (!bot->GetGroup() || !bot->GetGroup()->IsMember(inviter->GetGUID()))
|
||||
return false;
|
||||
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
botAI->SetMaster(inviter);
|
||||
// else
|
||||
// sPlayerbotRepository->Save(botAI);
|
||||
|
||||
botAI->ResetStrategies();
|
||||
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
|
||||
botAI->Reset();
|
||||
|
||||
botAI->TellMaster("Hello");
|
||||
|
||||
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance)
|
||||
{
|
||||
Teleport(inviter, bot, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
22
src/Bot/BaseAi/Actions/AcceptInvitationAction.h
Normal file
22
src/Bot/BaseAi/Actions/AcceptInvitationAction.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACCEPTINVITATIONACTION_H
|
||||
#define _PLAYERBOT_ACCEPTINVITATIONACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "UseMeetingStoneAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class AcceptInvitationAction : public SummonAction
|
||||
{
|
||||
public:
|
||||
AcceptInvitationAction(PlayerbotAI* botAI) : SummonAction(botAI, "accept invitation") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
198
src/Bot/BaseAi/Actions/AcceptQuestAction.cpp
Normal file
198
src/Bot/BaseAi/Actions/AcceptQuestAction.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AcceptQuestAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
|
||||
{
|
||||
if (!AcceptQuest(quest, questGiver->GetGUID())) return false;
|
||||
|
||||
auto text_quest = ChatHelper::FormatQuest(quest);
|
||||
bot->PlayDistanceSound(620);
|
||||
|
||||
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
LOG_INFO("playerbots", "{} => Quest [{}] accepted", bot->GetName(), quest->GetTitle());
|
||||
bot->Say("Quest [" + text_quest + "] accepted", LANG_UNIVERSAL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AcceptQuestAction::Execute(Event event)
|
||||
{
|
||||
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
|
||||
if (!requester)
|
||||
return false;
|
||||
|
||||
Player* bot = botAI->GetBot();
|
||||
uint64_t guid;
|
||||
uint32 quest = 0;
|
||||
|
||||
std::string const text = event.getParam();
|
||||
PlayerbotChatHandler ch(requester);
|
||||
quest = ch.extractQuestId(text);
|
||||
|
||||
bool hasAccept = false;
|
||||
|
||||
if (event.getPacket().empty())
|
||||
{
|
||||
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
|
||||
for (auto i = npcs.begin(); i != npcs.end(); i++)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(*i);
|
||||
if (unit && quest && unit->hasQuest(quest))
|
||||
{
|
||||
guid = unit->GetGUID().GetRawValue();
|
||||
break;
|
||||
}
|
||||
if (unit && text == "*" && bot->GetDistance(unit) <= INTERACTION_DISTANCE)
|
||||
hasAccept |= QuestAction::ProcessQuests(unit);
|
||||
}
|
||||
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
|
||||
for (auto i = gos.begin(); i != gos.end(); i++)
|
||||
{
|
||||
GameObject* go = botAI->GetGameObject(*i);
|
||||
if (go && quest && go->hasQuest(quest))
|
||||
{
|
||||
guid = go->GetGUID().GetRawValue();
|
||||
break;
|
||||
}
|
||||
if (go && text == "*" && bot->GetDistance(go) <= INTERACTION_DISTANCE)
|
||||
hasAccept |= QuestAction::ProcessQuests(go);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldPacket& p = event.getPacket();
|
||||
p.rpos(0);
|
||||
p >> guid >> quest;
|
||||
}
|
||||
|
||||
if (!quest || !guid)
|
||||
return false;
|
||||
|
||||
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest);
|
||||
if (!qInfo)
|
||||
return false;
|
||||
|
||||
hasAccept |= AcceptQuest(qInfo, ObjectGuid(guid));
|
||||
|
||||
if (hasAccept)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "AcceptQuestAction [" << qInfo->GetTitle() << "] - [" << std::to_string(qInfo->GetQuestId()) << "]";
|
||||
LOG_DEBUG("playerbots", "{}", ss.str().c_str());
|
||||
// botAI->TellMaster(ss.str());
|
||||
}
|
||||
|
||||
return hasAccept;
|
||||
}
|
||||
|
||||
bool AcceptQuestShareAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
WorldPacket& p = event.getPacket();
|
||||
p.rpos(0);
|
||||
uint32 quest;
|
||||
p >> quest;
|
||||
|
||||
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest);
|
||||
if (!qInfo || !bot->GetDivider())
|
||||
return false;
|
||||
|
||||
quest = qInfo->GetQuestId();
|
||||
|
||||
if (bot->HasQuest(quest))
|
||||
{
|
||||
bot->SetDivider(ObjectGuid::Empty);
|
||||
botAI->TellError("I have this quest");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->CanTakeQuest(qInfo, false))
|
||||
{
|
||||
// can't take quest
|
||||
bot->SetDivider(ObjectGuid::Empty);
|
||||
botAI->TellError("I can't take this quest");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->GetDivider().IsEmpty())
|
||||
{
|
||||
// send msg to quest giving player
|
||||
master->SendPushToPartyResponse(bot, QUEST_PARTY_MSG_ACCEPT_QUEST);
|
||||
bot->SetDivider(ObjectGuid::Empty);
|
||||
}
|
||||
|
||||
if (bot->CanAddQuest(qInfo, false))
|
||||
{
|
||||
bot->AddQuest(qInfo, master);
|
||||
|
||||
if (bot->CanCompleteQuest(quest))
|
||||
bot->CompleteQuest(quest);
|
||||
|
||||
// Runsttren: did not add typeid switch from WorldSession::HandleQuestgiverAcceptQuestOpcode!
|
||||
// I think it's not needed, cause typeid should be TYPEID_PLAYER - and this one is not handled
|
||||
// there and there is no default case also.
|
||||
|
||||
if (qInfo->GetSrcSpell() > 0)
|
||||
{
|
||||
bot->CastSpell(bot, qInfo->GetSrcSpell(), true);
|
||||
}
|
||||
|
||||
botAI->TellMaster("Quest accepted");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConfirmQuestAction::Execute(Event event)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
|
||||
|
||||
WorldPacket& p = event.getPacket();
|
||||
p.rpos(0);
|
||||
uint32 quest;
|
||||
p >> quest;
|
||||
Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest);
|
||||
|
||||
if (!qInfo)
|
||||
return false;
|
||||
|
||||
quest = qInfo->GetQuestId();
|
||||
if (!bot->CanTakeQuest(qInfo, false))
|
||||
{
|
||||
// can't take quest
|
||||
// botAI->TellError("quest_cant_take");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot->CanAddQuest(qInfo, false))
|
||||
{
|
||||
bot->AddQuest(qInfo, requester);
|
||||
|
||||
if (bot->CanCompleteQuest(quest))
|
||||
bot->CompleteQuest(quest);
|
||||
|
||||
if (qInfo->GetSrcSpell() > 0)
|
||||
{
|
||||
bot->CastSpell(bot, qInfo->GetSrcSpell(), true);
|
||||
}
|
||||
|
||||
// botAI->TellMaster("quest_accept");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
46
src/Bot/BaseAi/Actions/AcceptQuestAction.h
Normal file
46
src/Bot/BaseAi/Actions/AcceptQuestAction.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACCEPTQUESTACTION_H
|
||||
#define _PLAYERBOT_ACCEPTQUESTACTION_H
|
||||
|
||||
#include "QuestAction.h"
|
||||
|
||||
class Quest;
|
||||
class PlayerbotAI;
|
||||
class WorldObject;
|
||||
|
||||
class AcceptAllQuestsAction : public QuestAction
|
||||
{
|
||||
public:
|
||||
AcceptAllQuestsAction(PlayerbotAI* botAI, std::string const name = "accept all quests") : QuestAction(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
bool ProcessQuest(Quest const* quest, Object* questGiver) override;
|
||||
};
|
||||
|
||||
class AcceptQuestAction : public AcceptAllQuestsAction
|
||||
{
|
||||
public:
|
||||
AcceptQuestAction(PlayerbotAI* botAI) : AcceptAllQuestsAction(botAI, "accept quest") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AcceptQuestShareAction : public Action
|
||||
{
|
||||
public:
|
||||
AcceptQuestShareAction(PlayerbotAI* botAI) : Action(botAI, "accept quest share") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ConfirmQuestAction : public Action {
|
||||
public:
|
||||
ConfirmQuestAction(PlayerbotAI* ai) : Action(ai, "confirm quest") {}
|
||||
bool Execute(Event event);
|
||||
};
|
||||
|
||||
#endif
|
||||
27
src/Bot/BaseAi/Actions/AcceptResurrectAction.cpp
Normal file
27
src/Bot/BaseAi/Actions/AcceptResurrectAction.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AcceptResurrectAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool AcceptResurrectAction::Execute(Event event)
|
||||
{
|
||||
if (bot->IsAlive())
|
||||
return false;
|
||||
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
ObjectGuid guid;
|
||||
p >> guid;
|
||||
|
||||
WorldPacket packet(CMSG_RESURRECT_RESPONSE, 8 + 1);
|
||||
packet << guid;
|
||||
packet << uint8(1); // accept
|
||||
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
|
||||
|
||||
return true;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/AcceptResurrectAction.h
Normal file
21
src/Bot/BaseAi/Actions/AcceptResurrectAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ACCEPTRESURRECTACTION_H
|
||||
#define _PLAYERBOT_ACCEPTRESURRECTACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class AcceptResurrectAction : public Action
|
||||
{
|
||||
public:
|
||||
AcceptResurrectAction(PlayerbotAI* botAI) : Action(botAI, "accept resurrect") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
61
src/Bot/BaseAi/Actions/AddLootAction.cpp
Normal file
61
src/Bot/BaseAi/Actions/AddLootAction.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AddLootAction.h"
|
||||
|
||||
#include "CellImpl.h"
|
||||
#include "Event.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "LootObjectStack.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
bool AddLootAction::Execute(Event event)
|
||||
{
|
||||
ObjectGuid guid = event.getObject();
|
||||
if (!guid)
|
||||
return false;
|
||||
|
||||
return AI_VALUE(LootObjectStack*, "available loot")->Add(guid);
|
||||
}
|
||||
|
||||
bool AddAllLootAction::Execute(Event event)
|
||||
{
|
||||
bool added = false;
|
||||
|
||||
GuidVector gos = context->GetValue<GuidVector>("nearest game objects")->Get();
|
||||
for (GuidVector::iterator i = gos.begin(); i != gos.end(); i++)
|
||||
added |= AddLoot(*i);
|
||||
|
||||
GuidVector corpses = context->GetValue<GuidVector>("nearest corpses")->Get();
|
||||
for (GuidVector::iterator i = corpses.begin(); i != corpses.end(); i++)
|
||||
added |= AddLoot(*i);
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
bool AddLootAction::isUseful() { return true; }
|
||||
|
||||
bool AddAllLootAction::isUseful() { return true; }
|
||||
|
||||
bool AddAllLootAction::AddLoot(ObjectGuid guid) { return AI_VALUE(LootObjectStack*, "available loot")->Add(guid); }
|
||||
|
||||
bool AddGatheringLootAction::AddLoot(ObjectGuid guid)
|
||||
{
|
||||
LootObject loot(bot, guid);
|
||||
|
||||
WorldObject* wo = loot.GetWorldObject(bot);
|
||||
if (loot.IsEmpty() || !wo)
|
||||
return false;
|
||||
|
||||
if (loot.skillId == SKILL_NONE)
|
||||
return false;
|
||||
|
||||
if (!loot.IsLootPossible(bot))
|
||||
return false;
|
||||
|
||||
return AddAllLootAction::AddLoot(guid);
|
||||
}
|
||||
42
src/Bot/BaseAi/Actions/AddLootAction.h
Normal file
42
src/Bot/BaseAi/Actions/AddLootAction.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ADDLOOTACTION_H
|
||||
#define _PLAYERBOT_ADDLOOTACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class ObjectGuid;
|
||||
class PlayerbotAI;
|
||||
|
||||
class AddLootAction : public Action
|
||||
{
|
||||
public:
|
||||
AddLootAction(PlayerbotAI* botAI) : Action(botAI, "add loot") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class AddAllLootAction : public Action
|
||||
{
|
||||
public:
|
||||
AddAllLootAction(PlayerbotAI* botAI, std::string const name = "add all loot") : Action(botAI, name) {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
virtual bool AddLoot(ObjectGuid guid);
|
||||
};
|
||||
|
||||
class AddGatheringLootAction : public AddAllLootAction
|
||||
{
|
||||
public:
|
||||
AddGatheringLootAction(PlayerbotAI* botAI) : AddAllLootAction(botAI, "add gathering loot") {}
|
||||
|
||||
protected:
|
||||
bool AddLoot(ObjectGuid guid) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
81
src/Bot/BaseAi/Actions/AreaTriggerAction.cpp
Normal file
81
src/Bot/BaseAi/Actions/AreaTriggerAction.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AreaTriggerAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Transport.h"
|
||||
|
||||
bool ReachAreaTriggerAction::Execute(Event event)
|
||||
{
|
||||
if (botAI->IsRealPlayer()) // Do not trigger own area trigger.
|
||||
return false;
|
||||
|
||||
uint32 triggerId;
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
p >> triggerId;
|
||||
|
||||
AreaTrigger const* at = sObjectMgr->GetAreaTrigger(triggerId);
|
||||
if (!at)
|
||||
return false;
|
||||
|
||||
if (!sObjectMgr->GetAreaTriggerTeleport(triggerId))
|
||||
{
|
||||
WorldPacket p1(CMSG_AREATRIGGER);
|
||||
p1 << triggerId;
|
||||
p1.rpos(0);
|
||||
bot->GetSession()->HandleAreaTriggerOpcode(p1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bot->GetMapId() != at->map)
|
||||
{
|
||||
botAI->TellError("I won't follow: too far away");
|
||||
return true;
|
||||
}
|
||||
|
||||
bot->GetMotionMaster()->MovePoint(
|
||||
/*id*/ at->map,
|
||||
/*coords*/ at->x, at->y, at->z,
|
||||
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
|
||||
/*speed*/ 0.0f, // default speed (not handled here)
|
||||
/*orientation*/ 0.0f, // keep current orientation of bot
|
||||
/*generatePath*/ true, // true => terrain path (2d mmap); false => straight spline (3d vmap)
|
||||
/*forceDestination*/ false);
|
||||
|
||||
float distance = bot->GetDistance(at->x, at->y, at->z);
|
||||
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
||||
botAI->TellError("Wait for me");
|
||||
botAI->SetNextCheckDelay(delay);
|
||||
context->GetValue<LastMovement&>("last area trigger")->Get().lastAreaTrigger = triggerId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AreaTriggerAction::Execute(Event event)
|
||||
{
|
||||
LastMovement& movement = context->GetValue<LastMovement&>("last area trigger")->Get();
|
||||
|
||||
uint32 triggerId = movement.lastAreaTrigger;
|
||||
movement.lastAreaTrigger = 0;
|
||||
|
||||
if (!sObjectMgr->GetAreaTrigger(triggerId))
|
||||
return false;
|
||||
|
||||
if (!sObjectMgr->GetAreaTriggerTeleport(triggerId))
|
||||
return true;
|
||||
|
||||
WorldPacket p(CMSG_AREATRIGGER);
|
||||
p << triggerId;
|
||||
p.rpos(0);
|
||||
bot->GetSession()->HandleAreaTriggerOpcode(p);
|
||||
|
||||
botAI->TellMaster("Hello");
|
||||
return true;
|
||||
}
|
||||
29
src/Bot/BaseAi/Actions/AreaTriggerAction.h
Normal file
29
src/Bot/BaseAi/Actions/AreaTriggerAction.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_AREATRIGGERACTION_H
|
||||
#define _PLAYERBOT_AREATRIGGERACTION_H
|
||||
|
||||
#include "MovementActions.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class ReachAreaTriggerAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ReachAreaTriggerAction(PlayerbotAI* botAI) : MovementAction(botAI, "reach area trigger") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AreaTriggerAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AreaTriggerAction(PlayerbotAI* botAI) : MovementAction(botAI, "area trigger") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
57
src/Bot/BaseAi/Actions/ArenaTeamActions.cpp
Normal file
57
src/Bot/BaseAi/Actions/ArenaTeamActions.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ArenaTeamActions.h"
|
||||
|
||||
#include "ArenaTeamMgr.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool ArenaTeamAcceptAction::Execute(Event event)
|
||||
{
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
Player* inviter = nullptr;
|
||||
std::string Invitedname;
|
||||
p >> Invitedname;
|
||||
|
||||
if (normalizePlayerName(Invitedname))
|
||||
inviter = ObjectAccessor::FindPlayerByName(Invitedname.c_str());
|
||||
|
||||
if (!inviter)
|
||||
return false;
|
||||
|
||||
ArenaTeam* at = sArenaTeamMgr->GetArenaTeamById(bot->GetArenaTeamIdInvited());
|
||||
if (!at)
|
||||
return false;
|
||||
|
||||
bool accept = true;
|
||||
|
||||
if (bot->GetArenaTeamId(at->GetSlot()))
|
||||
{
|
||||
// bot is already in an arena team
|
||||
bot->Say("Sorry, I am already in such team", LANG_UNIVERSAL);
|
||||
accept = false;
|
||||
}
|
||||
|
||||
if (accept)
|
||||
{
|
||||
WorldPacket data(CMSG_ARENA_TEAM_ACCEPT);
|
||||
bot->GetSession()->HandleArenaTeamAcceptOpcode(data);
|
||||
bot->Say("Thanks for the invite!", LANG_UNIVERSAL);
|
||||
LOG_INFO("playerbots", "Bot {} <{}> accepts Arena Team invite", bot->GetGUID().ToString().c_str(),
|
||||
bot->GetName().c_str());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldPacket data(CMSG_ARENA_TEAM_DECLINE);
|
||||
bot->GetSession()->HandleArenaTeamDeclineOpcode(data);
|
||||
LOG_INFO("playerbots", "Bot {} <{}> declines Arena Team invite", bot->GetGUID().ToString().c_str(),
|
||||
bot->GetName().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/ArenaTeamActions.h
Normal file
21
src/Bot/BaseAi/Actions/ArenaTeamActions.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ARENATEAMACTION_H
|
||||
#define _PLAYERBOT_ARENATEAMACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class ArenaTeamAcceptAction : public Action
|
||||
{
|
||||
public:
|
||||
ArenaTeamAcceptAction(PlayerbotAI* botAI) : Action(botAI, "arena team accept") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
194
src/Bot/BaseAi/Actions/AttackAction.cpp
Normal file
194
src/Bot/BaseAi/Actions/AttackAction.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "AttackAction.h"
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "Event.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "LootObjectStack.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "Unit.h"
|
||||
|
||||
bool AttackAction::Execute(Event /*event*/)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!target->IsInWorld())
|
||||
return false;
|
||||
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AttackMyTargetAction::Execute(Event /*event*/)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
ObjectGuid guid = master->GetTarget();
|
||||
if (!guid)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("You have no target");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({guid});
|
||||
bool result = Attack(botAI->GetUnit(guid));
|
||||
if (result)
|
||||
context->GetValue<ObjectGuid>("pull target")->Set(guid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
||||
{
|
||||
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
||||
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
||||
|
||||
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
|
||||
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
|
||||
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
|
||||
|
||||
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
|
||||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack in flight");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!target)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I have no target");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!target->IsInWorld())
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if bot OR target is in prohibited zone/area (skip for duels)
|
||||
if ((target->IsPlayer() || target->IsPet()) &&
|
||||
(!bot->duel || bot->duel->Opponent != target) &&
|
||||
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
|
||||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot->IsFriendlyTo(target))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target->isDead())
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->IsWithinLOSInMap(target))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sameTarget && inCombat && sameAttackMode)
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bot->IsValidAttackTarget(target))
|
||||
{
|
||||
if (verbose)
|
||||
botAI->TellError("I cannot attack an invalid target.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (bot->IsMounted() && bot->IsWithinLOSInMap(target))
|
||||
// {
|
||||
// WorldPacket emptyPacket;
|
||||
// bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
|
||||
// }
|
||||
|
||||
ObjectGuid guid = target->GetGUID();
|
||||
bot->SetSelection(target->GetGUID());
|
||||
|
||||
context->GetValue<Unit*>("old target")->Set(oldTarget);
|
||||
|
||||
context->GetValue<Unit*>("current target")->Set(target);
|
||||
context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid);
|
||||
|
||||
LastMovement& lastMovement = AI_VALUE(LastMovement&, "last movement");
|
||||
bool moveControlled = bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE;
|
||||
if (lastMovement.priority < MovementPriority::MOVEMENT_COMBAT && bot->isMoving() && !moveControlled)
|
||||
{
|
||||
AI_VALUE(LastMovement&, "last movement").clear();
|
||||
bot->GetMotionMaster()->Clear(false);
|
||||
bot->StopMoving();
|
||||
}
|
||||
|
||||
if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
|
||||
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
||||
|
||||
bot->Attack(target, shouldMelee);
|
||||
/* prevent pet dead immediately in group */
|
||||
// if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat())
|
||||
// {
|
||||
// with_pet = false;
|
||||
// }
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
// {
|
||||
// if (with_pet)
|
||||
// {
|
||||
// pet->SetReactState(REACT_DEFENSIVE);
|
||||
// pet->SetTarget(target->GetGUID());
|
||||
// pet->GetCharmInfo()->SetIsCommandAttack(true);
|
||||
// pet->AI()->AttackStart(target);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// pet->SetReactState(REACT_PASSIVE);
|
||||
// pet->GetCharmInfo()->SetIsCommandFollow(true);
|
||||
// pet->GetCharmInfo()->IsReturning();
|
||||
// }
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
|
||||
|
||||
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||
45
src/Bot/BaseAi/Actions/AttackAction.h
Normal file
45
src/Bot/BaseAi/Actions/AttackAction.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_ATTACKACTION_H
|
||||
#define _PLAYERBOT_ATTACKACTION_H
|
||||
|
||||
#include "MovementActions.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class AttackAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AttackAction(PlayerbotAI* botAI, std::string const name) : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
bool Attack(Unit* target, bool with_pet = true);
|
||||
};
|
||||
|
||||
class AttackMyTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackMyTargetAction(PlayerbotAI* botAI, std::string const name = "attack my target") : AttackAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AttackDuelOpponentAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackDuelOpponentAction(PlayerbotAI* botAI, std::string const name = "attack duel opponent")
|
||||
: AttackAction(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
188
src/Bot/BaseAi/Actions/AutoMaintenanceOnLevelupAction.cpp
Normal file
188
src/Bot/BaseAi/Actions/AutoMaintenanceOnLevelupAction.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#include "AutoMaintenanceOnLevelupAction.h"
|
||||
|
||||
#include "GuildMgr.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "BroadcastHelper.h"
|
||||
|
||||
bool AutoMaintenanceOnLevelupAction::Execute(Event event)
|
||||
{
|
||||
AutoPickTalents();
|
||||
AutoLearnSpell();
|
||||
AutoUpgradeEquip();
|
||||
AutoTeleportForLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
|
||||
{
|
||||
if (!sPlayerbotAIConfig->autoTeleportForLevel || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (botAI->HasRealPlayerMaster())
|
||||
{
|
||||
return;
|
||||
}
|
||||
sRandomPlayerbotMgr->RandomTeleportForLevel(bot);
|
||||
return;
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::AutoPickTalents()
|
||||
{
|
||||
if (!sPlayerbotAIConfig->autoPickTalents || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return;
|
||||
|
||||
if (bot->GetFreeTalentPoints() <= 0)
|
||||
return;
|
||||
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
factory.InitTalentsTree(true, true, true);
|
||||
factory.InitPetTalents();
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::AutoLearnSpell()
|
||||
{
|
||||
std::ostringstream out;
|
||||
LearnSpells(&out);
|
||||
|
||||
if (!out.str().empty())
|
||||
{
|
||||
std::string const temp = out.str();
|
||||
out.seekp(0);
|
||||
out << "Learned spells: ";
|
||||
out << temp;
|
||||
out.seekp(-2, out.cur);
|
||||
out << ".";
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::LearnSpells(std::ostringstream* out)
|
||||
{
|
||||
BroadcastHelper::BroadcastLevelup(botAI, bot);
|
||||
if (sPlayerbotAIConfig->autoLearnTrainerSpells && sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
LearnTrainerSpells(out);
|
||||
|
||||
if (sPlayerbotAIConfig->autoLearnQuestSpells && sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
LearnQuestSpells(out);
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* out)
|
||||
{
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
factory.InitSkills();
|
||||
factory.InitClassSpells();
|
||||
factory.InitAvailableSpells();
|
||||
factory.InitPet();
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
|
||||
{
|
||||
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
|
||||
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
|
||||
{
|
||||
Quest const* quest = i->second;
|
||||
|
||||
// only process class-specific quests to learn class-related spells, cuz
|
||||
// we don't want all these bunch of entries to be handled!
|
||||
if (!quest->GetRequiredClasses())
|
||||
continue;
|
||||
|
||||
// skip quests that are repeatable, too low level, or above bots' level
|
||||
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
|
||||
continue;
|
||||
|
||||
// skip if bot doesnt satisfy class, race, or skill requirements
|
||||
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
|
||||
!bot->SatisfyQuestSkill(quest, false))
|
||||
continue;
|
||||
|
||||
// use the same logic and impl from Player::learnQuestRewardedSpells
|
||||
|
||||
int32 spellId = quest->GetRewSpellCast();
|
||||
if (!spellId)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
// xinef: find effect with learn spell and check if we have this spell
|
||||
bool found = false;
|
||||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||
{
|
||||
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
|
||||
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
|
||||
{
|
||||
// pusywizard: don't re-add profession specialties!
|
||||
if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell))
|
||||
if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
|
||||
break; // pussywizard: break and not cast the spell (found is false)
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// xinef: we know the spell, continue
|
||||
if (!found)
|
||||
continue;
|
||||
|
||||
bot->CastSpell(bot, spellId, true);
|
||||
|
||||
// Check if RewardDisplaySpell is set to output the proper spell learned
|
||||
// after processing quests. Output the original RewardSpell otherwise.
|
||||
uint32 rewSpellId = quest->GetRewSpell();
|
||||
if (rewSpellId)
|
||||
{
|
||||
if (SpellInfo const* rewSpellInfo = sSpellMgr->GetSpellInfo(rewSpellId))
|
||||
{
|
||||
*out << FormatSpell(rewSpellInfo) << ", ";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
*out << FormatSpell(spellInfo) << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* sInfo)
|
||||
{
|
||||
std::ostringstream out;
|
||||
std::string const rank = sInfo->Rank[0];
|
||||
|
||||
if (rank.empty())
|
||||
out << "|cffffffff|Hspell:" << sInfo->Id << "|h[" << sInfo->SpellName[LOCALE_enUS] << "]|h|r";
|
||||
else
|
||||
out << "|cffffffff|Hspell:" << sInfo->Id << "|h[" << sInfo->SpellName[LOCALE_enUS] << " " << rank << "]|h|r";
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
||||
{
|
||||
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return;
|
||||
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
|
||||
// Clean up old consumables before adding new ones
|
||||
factory.CleanupConsumables();
|
||||
|
||||
factory.InitAmmo();
|
||||
factory.InitReagents();
|
||||
factory.InitFood();
|
||||
factory.InitConsumables();
|
||||
factory.InitPotions();
|
||||
|
||||
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
|
||||
{
|
||||
if (sPlayerbotAIConfig->incrementalGearInit)
|
||||
factory.InitEquipment(true);
|
||||
}
|
||||
}
|
||||
34
src/Bot/BaseAi/Actions/AutoMaintenanceOnLevelupAction.h
Normal file
34
src/Bot/BaseAi/Actions/AutoMaintenanceOnLevelupAction.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_AUTOTELEPORTFORLEVELACTION_H
|
||||
#define _PLAYERBOT_AUTOTELEPORTFORLEVELACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class AutoMaintenanceOnLevelupAction : public Action
|
||||
{
|
||||
public:
|
||||
AutoMaintenanceOnLevelupAction(PlayerbotAI* botAI, std::string const name = "auto maintenance on levelup")
|
||||
: Action(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute(Event event);
|
||||
|
||||
protected:
|
||||
void AutoTeleportForLevel();
|
||||
void AutoPickTalents();
|
||||
void AutoLearnSpell();
|
||||
void AutoUpgradeEquip();
|
||||
void LearnSpells(std::ostringstream* out);
|
||||
void LearnTrainerSpells(std::ostringstream* out);
|
||||
void LearnQuestSpells(std::ostringstream* out);
|
||||
std::string const FormatSpell(SpellInfo const* sInfo);
|
||||
};
|
||||
|
||||
#endif
|
||||
173
src/Bot/BaseAi/Actions/BankAction.cpp
Normal file
173
src/Bot/BaseAi/Actions/BankAction.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "BankAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool BankAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
|
||||
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
|
||||
for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++)
|
||||
{
|
||||
Unit* npc = botAI->GetUnit(*i);
|
||||
if (!npc || !npc->HasNpcFlag(UNIT_NPC_FLAG_BANKER))
|
||||
continue;
|
||||
|
||||
return ExecuteBank(text, npc);
|
||||
}
|
||||
|
||||
botAI->TellError("Cannot find banker nearby");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BankAction::ExecuteBank(std::string const text, Unit* bank)
|
||||
{
|
||||
if (text.empty() || text == "?")
|
||||
{
|
||||
ListItems();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (text[0] == '-')
|
||||
{
|
||||
std::vector<Item*> found = parseItems(text.substr(1), ITERATE_ITEMS_IN_BANK);
|
||||
for (std::vector<Item*>::iterator i = found.begin(); i != found.end(); i++)
|
||||
{
|
||||
Item* item = *i;
|
||||
result &= Withdraw(item->GetTemplate()->ItemId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<Item*> found = parseItems(text, ITERATE_ITEMS_IN_BAGS);
|
||||
if (found.empty())
|
||||
return false;
|
||||
|
||||
for (std::vector<Item*>::iterator i = found.begin(); i != found.end(); i++)
|
||||
{
|
||||
Item* item = *i;
|
||||
if (!item)
|
||||
continue;
|
||||
|
||||
result &= Deposit(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BankAction::Withdraw(uint32 itemid)
|
||||
{
|
||||
Item* pItem = FindItemInBank(itemid);
|
||||
if (!pItem)
|
||||
return false;
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = bot->CanStoreItem(NULL_BAG, NULL_SLOT, dest, pItem, false);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
bot->SendEquipError(msg, pItem, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
bot->RemoveItem(pItem->GetBagSlot(), pItem->GetSlot(), true);
|
||||
bot->StoreItem(dest, pItem, true);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "got " << chat->FormatItem(pItem->GetTemplate(), pItem->GetCount()) << " from bank";
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BankAction::Deposit(Item* pItem)
|
||||
{
|
||||
std::ostringstream out;
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = bot->CanBankItem(NULL_BAG, NULL_SLOT, dest, pItem, false);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
bot->SendEquipError(msg, pItem, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
bot->RemoveItem(pItem->GetBagSlot(), pItem->GetSlot(), true);
|
||||
bot->BankItem(dest, pItem, true);
|
||||
|
||||
out << "put " << chat->FormatItem(pItem->GetTemplate(), pItem->GetCount()) << " to bank";
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void BankAction::ListItems()
|
||||
{
|
||||
botAI->TellMaster("=== Bank ===");
|
||||
|
||||
std::map<uint32, uint32> items;
|
||||
std::map<uint32, bool> soulbound;
|
||||
for (uint32 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i)
|
||||
if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
|
||||
if (pItem)
|
||||
{
|
||||
items[pItem->GetTemplate()->ItemId] += pItem->GetCount();
|
||||
soulbound[pItem->GetTemplate()->ItemId] = pItem->IsSoulBound();
|
||||
}
|
||||
|
||||
for (uint32 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
|
||||
if (Bag* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
|
||||
if (pBag)
|
||||
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
|
||||
if (Item* pItem = pBag->GetItemByPos(j))
|
||||
if (pItem)
|
||||
{
|
||||
items[pItem->GetTemplate()->ItemId] += pItem->GetCount();
|
||||
soulbound[pItem->GetTemplate()->ItemId] = pItem->IsSoulBound();
|
||||
}
|
||||
|
||||
TellItems(items, soulbound);
|
||||
}
|
||||
|
||||
Item* BankAction::FindItemInBank(uint32 ItemId)
|
||||
{
|
||||
for (uint8 slot = BANK_SLOT_ITEM_START; slot < BANK_SLOT_ITEM_END; slot++)
|
||||
{
|
||||
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
{
|
||||
ItemTemplate const* const pItemProto = pItem->GetTemplate();
|
||||
if (!pItemProto)
|
||||
continue;
|
||||
|
||||
if (pItemProto->ItemId == ItemId) // have required item
|
||||
return pItem;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 bag = BANK_SLOT_BAG_START; bag < BANK_SLOT_BAG_END; ++bag)
|
||||
{
|
||||
Bag const* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
|
||||
if (pBag)
|
||||
for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot)
|
||||
{
|
||||
Item* const pItem = bot->GetItemByPos(bag, slot);
|
||||
if (pItem)
|
||||
{
|
||||
ItemTemplate const* const pItemProto = pItem->GetTemplate();
|
||||
if (!pItemProto)
|
||||
continue;
|
||||
|
||||
if (pItemProto->ItemId == ItemId)
|
||||
return pItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
29
src/Bot/BaseAi/Actions/BankAction.h
Normal file
29
src/Bot/BaseAi/Actions/BankAction.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_BANKACTION_H
|
||||
#define _PLAYERBOT_BANKACTION_H
|
||||
|
||||
#include "InventoryAction.h"
|
||||
|
||||
class Item;
|
||||
class PlayerbotAI;
|
||||
|
||||
class BankAction : public InventoryAction
|
||||
{
|
||||
public:
|
||||
BankAction(PlayerbotAI* botAI) : InventoryAction(botAI, "bank") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
bool ExecuteBank(std::string const text, Unit* bank);
|
||||
void ListItems();
|
||||
bool Withdraw(uint32 itemid);
|
||||
bool Deposit(Item* pItem);
|
||||
Item* FindItemInBank(uint32 ItemId);
|
||||
};
|
||||
|
||||
#endif
|
||||
1098
src/Bot/BaseAi/Actions/BattleGroundJoinAction.cpp
Normal file
1098
src/Bot/BaseAi/Actions/BattleGroundJoinAction.cpp
Normal file
File diff suppressed because it is too large
Load Diff
79
src/Bot/BaseAi/Actions/BattleGroundJoinAction.h
Normal file
79
src/Bot/BaseAi/Actions/BattleGroundJoinAction.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_BATTLEGROUNDJOINACTION_H
|
||||
#define _PLAYERBOT_BATTLEGROUNDJOINACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "DBCEnums.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
struct CreatureData;
|
||||
|
||||
enum ArenaType : uint8;
|
||||
enum BattlegroundQueueTypeId : uint8;
|
||||
enum BattlegroundBracketId : uint8;
|
||||
|
||||
class BGJoinAction : public Action
|
||||
{
|
||||
public:
|
||||
BGJoinAction(PlayerbotAI* botAI, std::string const name = "bg join") : Action(botAI, name) {}
|
||||
|
||||
bool isUseful() override;
|
||||
bool canJoinBg(BattlegroundQueueTypeId queueTypeId, BattlegroundBracketId bracketId);
|
||||
virtual bool shouldJoinBg(BattlegroundQueueTypeId queueTypeId, BattlegroundBracketId bracketId);
|
||||
bool Execute(Event event) override;
|
||||
virtual bool gatherArenaTeam(ArenaType type);
|
||||
|
||||
protected:
|
||||
bool JoinQueue(uint32 type);
|
||||
std::vector<uint32> bgList;
|
||||
std::vector<uint32> ratedList;
|
||||
};
|
||||
|
||||
class FreeBGJoinAction : public BGJoinAction
|
||||
{
|
||||
public:
|
||||
FreeBGJoinAction(PlayerbotAI* botAI, std::string const name = "free bg join") : BGJoinAction(botAI, name) {}
|
||||
|
||||
bool shouldJoinBg(BattlegroundQueueTypeId queueTypeId, BattlegroundBracketId bracketId) override;
|
||||
};
|
||||
|
||||
class BGLeaveAction : public Action
|
||||
{
|
||||
public:
|
||||
BGLeaveAction(PlayerbotAI* botAI, std::string const name = "bg leave") : Action(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BGStatusAction : public Action
|
||||
{
|
||||
public:
|
||||
BGStatusAction(PlayerbotAI* botAI) : Action(botAI, "bg status") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
static bool LeaveBG(PlayerbotAI* botAI);
|
||||
};
|
||||
|
||||
class BGStatusCheckAction : public Action
|
||||
{
|
||||
public:
|
||||
BGStatusCheckAction(PlayerbotAI* botAI, std::string const name = "bg status check") : Action(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class BGStrategyCheckAction : public Action
|
||||
{
|
||||
public:
|
||||
BGStrategyCheckAction(PlayerbotAI* botAI, std::string const name = "bg strategy check") : Action(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
#endif
|
||||
4434
src/Bot/BaseAi/Actions/BattleGroundTactics.cpp
Normal file
4434
src/Bot/BaseAi/Actions/BattleGroundTactics.cpp
Normal file
File diff suppressed because it is too large
Load Diff
147
src/Bot/BaseAi/Actions/BattleGroundTactics.h
Normal file
147
src/Bot/BaseAi/Actions/BattleGroundTactics.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_BATTLEGROUNDTACTICSACTION_H
|
||||
#define _PLAYERBOT_BATTLEGROUNDTACTICSACTION_H
|
||||
|
||||
#include "BattlegroundAV.h"
|
||||
#include "MovementActions.h"
|
||||
|
||||
class ChatHandler;
|
||||
class Battleground;
|
||||
class PlayerbotAI;
|
||||
struct Position;
|
||||
|
||||
#define SPELL_CAPTURE_BANNER 21651
|
||||
|
||||
enum WSBotStrategy : uint8
|
||||
{
|
||||
WS_STRATEGY_BALANCED = 0,
|
||||
WS_STRATEGY_OFFENSIVE = 1,
|
||||
WS_STRATEGY_DEFENSIVE = 2,
|
||||
WS_STRATEGY_MAX = 3,
|
||||
};
|
||||
|
||||
enum ABBotStrategy : uint8
|
||||
{
|
||||
AB_STRATEGY_BALANCED = 0,
|
||||
AB_STRATEGY_OFFENSIVE = 1,
|
||||
AB_STRATEGY_DEFENSIVE = 2,
|
||||
AB_STRATEGY_MAX = 3,
|
||||
};
|
||||
|
||||
enum AVBotStrategy : uint8
|
||||
{
|
||||
AV_STRATEGY_BALANCED = 0,
|
||||
AV_STRATEGY_OFFENSIVE = 1,
|
||||
AV_STRATEGY_DEFENSIVE = 2,
|
||||
AV_STRATEGY_MAX = 3,
|
||||
};
|
||||
|
||||
enum EYBotStrategy : uint8
|
||||
{
|
||||
EY_STRATEGY_BALANCED = 0,
|
||||
EY_STRATEGY_FRONT_FOCUS = 1,
|
||||
EY_STRATEGY_BACK_FOCUS = 2,
|
||||
EY_STRATEGY_FLAG_FOCUS = 3,
|
||||
EY_STRATEGY_MAX = 4
|
||||
};
|
||||
|
||||
typedef void (*BattleBotWaypointFunc)();
|
||||
|
||||
struct BGStrategyData
|
||||
{
|
||||
uint8 allianceStrategy = 0;
|
||||
uint8 hordeStrategy = 0;
|
||||
};
|
||||
|
||||
extern std::unordered_map<uint32, BGStrategyData> bgStrategies;
|
||||
|
||||
struct BattleBotWaypoint
|
||||
{
|
||||
BattleBotWaypoint(float x_, float y_, float z_, BattleBotWaypointFunc func) : x(x_), y(y_), z(z_), pFunc(func){};
|
||||
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
BattleBotWaypointFunc pFunc = nullptr;
|
||||
};
|
||||
|
||||
struct AVNodePositionData
|
||||
{
|
||||
Position pos;
|
||||
float maxRadius;
|
||||
};
|
||||
|
||||
// Added to fix bot stuck at objectives
|
||||
static std::unordered_map<uint8, AVNodePositionData> AVNodeMovementTargets = {
|
||||
{BG_AV_NODES_FIRSTAID_STATION, {Position(640.364f, -36.535f, 45.625f), 15.0f}},
|
||||
{BG_AV_NODES_STORMPIKE_GRAVE, {Position(665.598f, -292.976f, 30.291f), 15.0f}},
|
||||
{BG_AV_NODES_STONEHEART_GRAVE, {Position(76.108f, -399.602f, 45.730f), 15.0f}},
|
||||
{BG_AV_NODES_SNOWFALL_GRAVE, {Position(-201.298f, -119.661f, 78.291f), 15.0f}},
|
||||
{BG_AV_NODES_ICEBLOOD_GRAVE, {Position(-617.858f, -400.654f, 59.692f), 15.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_GRAVE, {Position(-1083.803f, -341.520f, 55.304f), 15.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_HUT, {Position(-1405.678f, -309.108f, 89.377f, 0.392f), 10.0f}},
|
||||
{BG_AV_NODES_DUNBALDAR_SOUTH, {Position(556.551f, -77.240f, 51.931f), 0.0f}},
|
||||
{BG_AV_NODES_DUNBALDAR_NORTH, {Position(670.664f, -142.031f, 63.666f), 0.0f}},
|
||||
{BG_AV_NODES_ICEWING_BUNKER, {Position(200.310f, -361.232f, 56.387f), 0.0f}},
|
||||
{BG_AV_NODES_STONEHEART_BUNKER, {Position(-156.302f, -440.032f, 40.403f), 0.0f}},
|
||||
{BG_AV_NODES_ICEBLOOD_TOWER, {Position(-569.702f, -265.362f, 75.009f), 0.0f}},
|
||||
{BG_AV_NODES_TOWER_POINT, {Position(-767.439f, -360.200f, 90.895f), 0.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_ETOWER, {Position(-1303.737f, -314.070f, 113.868f), 0.0f}},
|
||||
{BG_AV_NODES_FROSTWOLF_WTOWER, {Position(-1300.648f, -267.356f, 114.151f), 0.0f}},
|
||||
};
|
||||
|
||||
typedef std::vector<BattleBotWaypoint> BattleBotPath;
|
||||
|
||||
extern std::vector<BattleBotPath*> const vPaths_WS;
|
||||
extern std::vector<BattleBotPath*> const vPaths_AB;
|
||||
extern std::vector<BattleBotPath*> const vPaths_AV;
|
||||
extern std::vector<BattleBotPath*> const vPaths_EY;
|
||||
extern std::vector<BattleBotPath*> const vPaths_IC;
|
||||
|
||||
class BGTactics : public MovementAction
|
||||
{
|
||||
public:
|
||||
static bool HandleConsoleCommand(ChatHandler* handler, char const* args);
|
||||
uint8 static GetBotStrategyForTeam(Battleground* bg, TeamId teamId);
|
||||
|
||||
BGTactics(PlayerbotAI* botAI, std::string const name = "bg tactics") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
static std::string const HandleConsoleCommandPrivate(WorldSession* session, char const* args);
|
||||
bool moveToStart(bool force = false);
|
||||
bool selectObjective(bool reset = false);
|
||||
bool moveToObjective(bool ignoreDist);
|
||||
bool selectObjectiveWp(std::vector<BattleBotPath*> const& vPaths);
|
||||
bool moveToObjectiveWp(BattleBotPath* const& currentPath, uint32 currentPoint, bool reverse = false);
|
||||
bool startNewPathBegin(std::vector<BattleBotPath*> const& vPaths);
|
||||
bool startNewPathFree(std::vector<BattleBotPath*> const& vPaths);
|
||||
bool resetObjective();
|
||||
bool wsJumpDown();
|
||||
bool eyJumpDown();
|
||||
bool atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<uint32> const& vFlagIds);
|
||||
bool flagTaken();
|
||||
bool teamFlagTaken();
|
||||
bool protectFC();
|
||||
bool useBuff();
|
||||
uint32 getPlayersInArea(TeamId teamId, Position point, float range, bool combat = true);
|
||||
bool IsLockedInsideKeep();
|
||||
};
|
||||
|
||||
class ArenaTactics : public MovementAction
|
||||
{
|
||||
public:
|
||||
ArenaTactics(PlayerbotAI* botAI, std::string const name = "arena tactics") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
bool moveToCenter(Battleground* bg);
|
||||
};
|
||||
|
||||
#endif
|
||||
69
src/Bot/BaseAi/Actions/BossAuraActions.cpp
Normal file
69
src/Bot/BaseAi/Actions/BossAuraActions.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include <HunterBuffStrategies.h>
|
||||
#include <PaladinBuffStrategies.h>
|
||||
#include <PlayerbotAI.h>
|
||||
|
||||
#include "BossAuraActions.h"
|
||||
#include "BossAuraTriggers.h"
|
||||
|
||||
const std::string ADD_STRATEGY_CHAR = "+";
|
||||
|
||||
bool BossFireResistanceAction::isUseful()
|
||||
{
|
||||
BossFireResistanceTrigger bossFireResistanceTrigger(botAI, bossName);
|
||||
return bossFireResistanceTrigger.IsActive();
|
||||
}
|
||||
|
||||
bool BossFireResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinFireResistanceStrategy paladinFireResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFireResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("fire resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BossFrostResistanceAction::isUseful()
|
||||
{
|
||||
BossFrostResistanceTrigger bossFrostResistanceTrigger(botAI, bossName);
|
||||
return bossFrostResistanceTrigger.IsActive();
|
||||
}
|
||||
|
||||
bool BossFrostResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinFrostResistanceStrategy paladinFrostResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinFrostResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("frost resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BossNatureResistanceAction::isUseful()
|
||||
{
|
||||
BossNatureResistanceTrigger bossNatureResistanceTrigger(botAI, bossName);
|
||||
return bossNatureResistanceTrigger.IsActive();
|
||||
}
|
||||
|
||||
bool BossNatureResistanceAction::Execute(Event event)
|
||||
{
|
||||
HunterNatureResistanceStrategy hunterNatureResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + hunterNatureResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("aspect of the wild", Event(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BossShadowResistanceAction::isUseful()
|
||||
{
|
||||
BossShadowResistanceTrigger bossShadowResistanceTrigger(botAI, bossName);
|
||||
return bossShadowResistanceTrigger.IsActive();
|
||||
}
|
||||
|
||||
bool BossShadowResistanceAction::Execute(Event event)
|
||||
{
|
||||
PaladinShadowResistanceStrategy paladinShadowResistanceStrategy(botAI);
|
||||
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + paladinShadowResistanceStrategy.getName(), BotState::BOT_STATE_COMBAT);
|
||||
botAI->DoSpecificAction("shadow resistance aura", Event(), true);
|
||||
return true;
|
||||
}
|
||||
71
src/Bot/BaseAi/Actions/BossAuraActions.h
Normal file
71
src/Bot/BaseAi/Actions/BossAuraActions.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_BOSSAURAACTION_H
|
||||
#define _PLAYERBOT_BOSSAURAACTION_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class BossFireResistanceAction : public Action
|
||||
{
|
||||
public:
|
||||
BossFireResistanceAction(PlayerbotAI* botAI, std::string const bossName)
|
||||
: Action(botAI, bossName + " fire resistance action"), bossName(bossName)
|
||||
{
|
||||
}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
std::string bossName;
|
||||
};
|
||||
|
||||
class BossFrostResistanceAction : public Action
|
||||
{
|
||||
public:
|
||||
BossFrostResistanceAction(PlayerbotAI* botAI, std::string const bossName)
|
||||
: Action(botAI, bossName + " frost resistance action"), bossName(bossName)
|
||||
{
|
||||
}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
std::string bossName;
|
||||
};
|
||||
|
||||
class BossNatureResistanceAction : public Action
|
||||
{
|
||||
public:
|
||||
BossNatureResistanceAction(PlayerbotAI* botAI, std::string const bossName)
|
||||
: Action(botAI, bossName + " nature resistance action"), bossName(bossName)
|
||||
{
|
||||
}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
std::string bossName;
|
||||
};
|
||||
|
||||
class BossShadowResistanceAction : public Action
|
||||
{
|
||||
public:
|
||||
BossShadowResistanceAction(PlayerbotAI* botAI, std::string const bossName)
|
||||
: Action(botAI, bossName + " shadow resistance action"), bossName(bossName)
|
||||
{
|
||||
}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
std::string bossName;
|
||||
};
|
||||
|
||||
#endif
|
||||
115
src/Bot/BaseAi/Actions/BuffAction.cpp
Normal file
115
src/Bot/BaseAi/Actions/BuffAction.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "BuffAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
class FindBuffVisitor : public IterateItemsVisitor
|
||||
{
|
||||
public:
|
||||
FindBuffVisitor(Player* bot) : IterateItemsVisitor(), bot(bot) {}
|
||||
|
||||
bool Visit(Item* item) override
|
||||
{
|
||||
if (bot->CanUseItem(item->GetTemplate()) != EQUIP_ERR_OK)
|
||||
return true;
|
||||
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
|
||||
if (proto->Class != ITEM_CLASS_CONSUMABLE)
|
||||
return true;
|
||||
|
||||
if (proto->SubClass != ITEM_SUBCLASS_ELIXIR && proto->SubClass != ITEM_SUBCLASS_FLASK &&
|
||||
proto->SubClass != ITEM_SUBCLASS_SCROLL && proto->SubClass != ITEM_SUBCLASS_FOOD &&
|
||||
proto->SubClass != ITEM_SUBCLASS_CONSUMABLE_OTHER && proto->SubClass != ITEM_SUBCLASS_ITEM_ENHANCEMENT)
|
||||
return true;
|
||||
|
||||
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; i++)
|
||||
{
|
||||
uint32 spellId = proto->Spells[i].SpellId;
|
||||
if (!spellId)
|
||||
continue;
|
||||
|
||||
if (bot->HasAura(spellId))
|
||||
return true;
|
||||
|
||||
Item* itemForSpell =
|
||||
*GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<Item*>("item for spell", spellId);
|
||||
if (itemForSpell && itemForSpell->IsInWorld() && itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
||||
return true;
|
||||
|
||||
if (items.find(proto->SubClass) == items.end())
|
||||
items[proto->SubClass] = std::vector<Item*>();
|
||||
|
||||
items[proto->SubClass].push_back(item);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<uint32, std::vector<Item*> > items;
|
||||
|
||||
private:
|
||||
Player* bot;
|
||||
};
|
||||
|
||||
void BuffAction::TellHeader(uint32 subClass)
|
||||
{
|
||||
switch (subClass)
|
||||
{
|
||||
case ITEM_SUBCLASS_ELIXIR:
|
||||
botAI->TellMaster("--- Elixir ---");
|
||||
return;
|
||||
case ITEM_SUBCLASS_FLASK:
|
||||
botAI->TellMaster("--- Flask ---");
|
||||
return;
|
||||
case ITEM_SUBCLASS_SCROLL:
|
||||
botAI->TellMaster("--- Scroll ---");
|
||||
return;
|
||||
case ITEM_SUBCLASS_FOOD:
|
||||
botAI->TellMaster("--- Food ---");
|
||||
return;
|
||||
case ITEM_SUBCLASS_ITEM_ENHANCEMENT:
|
||||
botAI->TellMaster("--- Enchant ---");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool BuffAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
|
||||
FindBuffVisitor visitor(bot);
|
||||
IterateItems(&visitor);
|
||||
|
||||
uint32 oldSubClass = -1;
|
||||
for (std::map<uint32, std::vector<Item*> >::iterator i = visitor.items.begin(); i != visitor.items.end(); ++i)
|
||||
{
|
||||
std::vector<Item*> items = i->second;
|
||||
|
||||
uint32 subClass = i->first;
|
||||
if (oldSubClass != subClass)
|
||||
{
|
||||
if (!items.empty())
|
||||
TellHeader(subClass);
|
||||
|
||||
oldSubClass = subClass;
|
||||
}
|
||||
|
||||
for (std::vector<Item*>::iterator j = items.begin(); j != items.end(); ++j)
|
||||
{
|
||||
Item* item = *j;
|
||||
std::ostringstream out;
|
||||
out << chat->FormatItem(item->GetTemplate(), item->GetCount());
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
24
src/Bot/BaseAi/Actions/BuffAction.h
Normal file
24
src/Bot/BaseAi/Actions/BuffAction.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_BUFFACTION_H
|
||||
#define _PLAYERBOT_BUFFACTION_H
|
||||
|
||||
#include "InventoryAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class BuffAction : public InventoryAction
|
||||
{
|
||||
public:
|
||||
BuffAction(PlayerbotAI* botAI) : InventoryAction(botAI, "buff") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
void TellHeader(uint32 subClass);
|
||||
};
|
||||
|
||||
#endif
|
||||
260
src/Bot/BaseAi/Actions/BuyAction.cpp
Normal file
260
src/Bot/BaseAi/Actions/BuyAction.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "BuyAction.h"
|
||||
|
||||
#include "BudgetValues.h"
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "ItemUsageValue.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "StatsWeightCalculator.h"
|
||||
|
||||
bool BuyAction::Execute(Event event)
|
||||
{
|
||||
bool buyUseful = false;
|
||||
ItemIds itemIds;
|
||||
std::string const link = event.getParam();
|
||||
|
||||
if (link == "vendor")
|
||||
buyUseful = true;
|
||||
else
|
||||
{
|
||||
itemIds = chat->parseItems(link);
|
||||
}
|
||||
|
||||
GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
|
||||
bool vendored = false;
|
||||
bool result = false;
|
||||
for (GuidVector::iterator i = vendors.begin(); i != vendors.end(); ++i)
|
||||
{
|
||||
ObjectGuid vendorguid = *i;
|
||||
Creature* pCreature = bot->GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR);
|
||||
if (!pCreature)
|
||||
continue;
|
||||
|
||||
vendored = true;
|
||||
|
||||
if (buyUseful)
|
||||
{
|
||||
// Items are evaluated from high-level to low level.
|
||||
// For each item the bot checks again if an item is usefull.
|
||||
// Bot will buy until no usefull items are left.
|
||||
|
||||
VendorItemData const* tItems = pCreature->GetVendorItems();
|
||||
if (!tItems)
|
||||
continue;
|
||||
|
||||
VendorItemList m_items_sorted = tItems->m_items;
|
||||
|
||||
m_items_sorted.erase(std::remove_if(m_items_sorted.begin(), m_items_sorted.end(),
|
||||
[](VendorItem* i)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(i->item);
|
||||
return !proto;
|
||||
}),
|
||||
m_items_sorted.end());
|
||||
|
||||
if (m_items_sorted.empty())
|
||||
continue;
|
||||
|
||||
StatsWeightCalculator calculator(bot);
|
||||
calculator.SetItemSetBonus(false);
|
||||
calculator.SetOverflowPenalty(false);
|
||||
|
||||
std::sort(m_items_sorted.begin(), m_items_sorted.end(),
|
||||
[&calculator](VendorItem* i, VendorItem* j)
|
||||
{
|
||||
ItemTemplate const* item1 = sObjectMgr->GetItemTemplate(i->item);
|
||||
ItemTemplate const* item2 = sObjectMgr->GetItemTemplate(j->item);
|
||||
|
||||
if (!item1 || !item2)
|
||||
return false;
|
||||
|
||||
float score1 = calculator.CalculateItem(item1->ItemId);
|
||||
float score2 = calculator.CalculateItem(item2->ItemId);
|
||||
|
||||
// Fallback to itemlevel if either score is 0
|
||||
if (score1 == 0 || score2 == 0)
|
||||
{
|
||||
score1 = item1->ItemLevel;
|
||||
score2 = item2->ItemLevel;
|
||||
}
|
||||
return score1 > score2; // Sort in descending order (highest score first)
|
||||
});
|
||||
|
||||
std::unordered_map<uint32, float> bestPurchasedItemScore; // Track best item score per InventoryType
|
||||
|
||||
for (auto& tItem : m_items_sorted)
|
||||
{
|
||||
uint32 maxPurchases = 1; // Default to buying once
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(tItem->item);
|
||||
if (!proto)
|
||||
continue;
|
||||
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE || proto->Class == ITEM_CLASS_PROJECTILE)
|
||||
{
|
||||
maxPurchases = 10; // Allow up to 10 purchases if it's a consumable or projectile
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < maxPurchases; i++)
|
||||
{
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", tItem->item);
|
||||
|
||||
uint32 invType = proto->InventoryType;
|
||||
|
||||
// Calculate item score
|
||||
float newScore = calculator.CalculateItem(proto->ItemId);
|
||||
|
||||
// Skip if we already bought a better item for this slot
|
||||
if (bestPurchasedItemScore.find(invType) != bestPurchasedItemScore.end() &&
|
||||
bestPurchasedItemScore[invType] > newScore)
|
||||
{
|
||||
break; // Skip lower-scoring items
|
||||
}
|
||||
|
||||
// Check the bot's currently equipped item for this slot
|
||||
uint8 dstSlot = botAI->FindEquipSlot(proto, NULL_SLOT, true);
|
||||
Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot);
|
||||
|
||||
float oldScore = 0.0f;
|
||||
if (oldItem)
|
||||
{
|
||||
ItemTemplate const* oldItemProto = oldItem->GetTemplate();
|
||||
if (oldItemProto)
|
||||
oldScore = calculator.CalculateItem(oldItemProto->ItemId);
|
||||
}
|
||||
|
||||
// Skip if the bot already has a better or equal item equipped
|
||||
if (oldScore > newScore)
|
||||
break;
|
||||
|
||||
uint32 price = proto->BuyPrice;
|
||||
price = uint32(floor(price * bot->GetReputationPriceDiscount(pCreature)));
|
||||
|
||||
NeedMoneyFor needMoneyFor = NeedMoneyFor::none;
|
||||
switch (usage)
|
||||
{
|
||||
case ITEM_USAGE_REPLACE:
|
||||
case ITEM_USAGE_EQUIP:
|
||||
case ITEM_USAGE_BAD_EQUIP:
|
||||
case ITEM_USAGE_BROKEN_EQUIP:
|
||||
needMoneyFor = NeedMoneyFor::gear;
|
||||
break;
|
||||
case ITEM_USAGE_AMMO:
|
||||
needMoneyFor = NeedMoneyFor::ammo;
|
||||
break;
|
||||
case ITEM_USAGE_QUEST:
|
||||
needMoneyFor = NeedMoneyFor::anything;
|
||||
break;
|
||||
case ITEM_USAGE_USE:
|
||||
needMoneyFor = NeedMoneyFor::consumables;
|
||||
break;
|
||||
case ITEM_USAGE_SKILL:
|
||||
needMoneyFor = NeedMoneyFor::tradeskill;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (needMoneyFor == NeedMoneyFor::none)
|
||||
break;
|
||||
|
||||
if (AI_VALUE2(uint32, "free money for", uint32(needMoneyFor)) < price)
|
||||
break;
|
||||
|
||||
if (!BuyItem(tItems, vendorguid, proto))
|
||||
break;
|
||||
|
||||
// Store the best item score per InventoryType
|
||||
bestPurchasedItemScore[invType] = newScore;
|
||||
|
||||
if (needMoneyFor == NeedMoneyFor::gear)
|
||||
{
|
||||
botAI->DoSpecificAction("equip upgrades");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (itemIds.empty())
|
||||
return false;
|
||||
|
||||
for (ItemIds::iterator i = itemIds.begin(); i != itemIds.end(); i++)
|
||||
{
|
||||
uint32 itemId = *i;
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!proto)
|
||||
continue;
|
||||
|
||||
result |= BuyItem(pCreature->GetVendorItems(), vendorguid, proto);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Nobody sells " << ChatHelper::FormatItem(proto) << " nearby";
|
||||
botAI->TellMaster(out.str());
|
||||
continue;
|
||||
}
|
||||
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemId);
|
||||
if (usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_EQUIP ||
|
||||
usage == ITEM_USAGE_BAD_EQUIP || usage == ITEM_USAGE_BROKEN_EQUIP)
|
||||
{
|
||||
botAI->DoSpecificAction("equip upgrades");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!vendored)
|
||||
{
|
||||
botAI->TellError("There are no vendors nearby");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
||||
{
|
||||
if (!tItems || !proto)
|
||||
return false;
|
||||
|
||||
uint32 itemId = proto->ItemId;
|
||||
uint32 oldCount = bot->GetItemCount(itemId, false);
|
||||
|
||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
|
||||
{
|
||||
if (tItems->GetItem(slot)->item != itemId)
|
||||
continue;
|
||||
|
||||
uint32 botMoney = bot->GetMoney();
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(10000000);
|
||||
|
||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (botAI->HasCheat(BotCheatMask::gold))
|
||||
bot->SetMoney(botMoney);
|
||||
|
||||
uint32 newCount = bot->GetItemCount(itemId, false);
|
||||
if (newCount > oldCount)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
33
src/Bot/BaseAi/Actions/BuyAction.h
Normal file
33
src/Bot/BaseAi/Actions/BuyAction.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_BUYACTION_H
|
||||
#define _PLAYERBOT_BUYACTION_H
|
||||
|
||||
#include "InventoryAction.h"
|
||||
|
||||
class FindItemVisitor;
|
||||
class ObjectGuid;
|
||||
class Item;
|
||||
class Player;
|
||||
class PlayerbotAI;
|
||||
|
||||
struct ItemTemplate;
|
||||
struct VendorItemData;
|
||||
|
||||
class BuyAction : public InventoryAction
|
||||
{
|
||||
public:
|
||||
BuyAction(PlayerbotAI* botAI) : InventoryAction(botAI, "buy") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
bool BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto);
|
||||
bool TradeItem(FindItemVisitor* visitor, int8 slot);
|
||||
bool TradeItem(Item const* item, int8 slot);
|
||||
};
|
||||
|
||||
#endif
|
||||
18
src/Bot/BaseAi/Actions/CancelChannelAction.cpp
Normal file
18
src/Bot/BaseAi/Actions/CancelChannelAction.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CancelChannelAction.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
bool CancelChannelAction::Execute(Event event)
|
||||
{
|
||||
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_CHANNELED_SPELL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/CancelChannelAction.h
Normal file
21
src/Bot/BaseAi/Actions/CancelChannelAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CANCELCHANNELACTION_H
|
||||
#define _PLAYERBOT_CANCELCHANNELACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class CancelChannelAction : public Action
|
||||
{
|
||||
public:
|
||||
CancelChannelAction(PlayerbotAI* botAI) : Action(botAI, "cancel channel") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
380
src/Bot/BaseAi/Actions/CastCustomSpellAction.cpp
Normal file
380
src/Bot/BaseAi/Actions/CastCustomSpellAction.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CastCustomSpellAction.h"
|
||||
|
||||
#include "ChatHelper.h"
|
||||
#include "Event.h"
|
||||
#include "ItemUsageValue.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
size_t FindLastSeparator(std::string const text, std::string const sep)
|
||||
{
|
||||
size_t pos = text.rfind(sep);
|
||||
if (pos == std::string::npos)
|
||||
return pos;
|
||||
|
||||
size_t lastLinkBegin = text.rfind("|H");
|
||||
size_t lastLinkEnd = text.find("|h|r", lastLinkBegin + 1);
|
||||
if (pos >= lastLinkBegin && pos <= lastLinkEnd)
|
||||
pos = text.find_last_of(sep, lastLinkBegin);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static inline void ltrim(std::string& s)
|
||||
{
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
|
||||
}
|
||||
|
||||
bool CastCustomSpellAction::Execute(Event event)
|
||||
{
|
||||
// only allow proper vehicle seats
|
||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
|
||||
return false;
|
||||
|
||||
Player* master = GetMaster();
|
||||
Unit* target = nullptr;
|
||||
std::string text = event.getParam();
|
||||
|
||||
GuidVector gos = chat->parseGameobjects(text);
|
||||
if (!gos.empty())
|
||||
{
|
||||
for (auto go : gos)
|
||||
{
|
||||
if (!target)
|
||||
target = botAI->GetUnit(go);
|
||||
|
||||
if (!botAI->GetUnit(go) || !botAI->GetUnit(go)->IsInWorld())
|
||||
continue;
|
||||
|
||||
chat->eraseAllSubStr(text, chat->FormatWorldobject(botAI->GetUnit(go)));
|
||||
}
|
||||
|
||||
ltrim(text);
|
||||
}
|
||||
|
||||
uint32 spell = 0;
|
||||
if (!target)
|
||||
{
|
||||
size_t onPos = FindLastSeparator(text, " on ");
|
||||
if (onPos != std::string::npos)
|
||||
{
|
||||
std::string targetName = text.substr(onPos + 4);
|
||||
ltrim(targetName);
|
||||
if (!targetName.empty())
|
||||
{
|
||||
// check if spell still exists after we remove " on PlayerName" part
|
||||
std::string truncatedText = text.substr(0, onPos);
|
||||
ltrim(truncatedText);
|
||||
spell = AI_VALUE2(uint32, "spell id", truncatedText);
|
||||
|
||||
if (spell)
|
||||
{
|
||||
if (Player* targetPlayer = ObjectAccessor::FindPlayerByName(targetName))
|
||||
{
|
||||
target = targetPlayer;
|
||||
text = truncatedText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!target)
|
||||
if (master && master->GetTarget())
|
||||
target = botAI->GetUnit(master->GetTarget());
|
||||
|
||||
if (!target)
|
||||
target = bot;
|
||||
|
||||
if (!master) // Use self as master for permissions.
|
||||
master = bot;
|
||||
|
||||
Item* itemTarget = nullptr;
|
||||
|
||||
size_t pos = FindLastSeparator(text, " ");
|
||||
uint32 castCount = 1;
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
std::string const param = text.substr(pos + 1);
|
||||
std::vector<Item*> items = InventoryAction::parseItems(param, ITERATE_ITEMS_IN_BAGS);
|
||||
if (!items.empty())
|
||||
itemTarget = *items.begin();
|
||||
else
|
||||
{
|
||||
if (atoi(param.c_str()) > 0)
|
||||
text = text.substr(0, pos);
|
||||
}
|
||||
}
|
||||
|
||||
if (!spell)
|
||||
spell = AI_VALUE2(uint32, "spell id", text);
|
||||
|
||||
std::ostringstream msg;
|
||||
if (!spell)
|
||||
{
|
||||
msg << "Unknown spell " << text;
|
||||
botAI->TellError(msg.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell);
|
||||
if (!spellInfo)
|
||||
{
|
||||
msg << "Unknown spell " << text;
|
||||
botAI->TellError(msg.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target != bot && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target, sPlayerbotAIConfig->sightDistance))
|
||||
{
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
|
||||
|
||||
msg << "cast " << text;
|
||||
botAI->HandleCommand(CHAT_MSG_WHISPER, msg.str(), master);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ostringstream spellName;
|
||||
spellName << ChatHelper::FormatSpell(spellInfo) << " on ";
|
||||
|
||||
if (bot->GetTrader())
|
||||
spellName << "trade item";
|
||||
else if (itemTarget)
|
||||
spellName << chat->FormatItem(itemTarget->GetTemplate());
|
||||
else if (target == bot)
|
||||
spellName << "self";
|
||||
else
|
||||
spellName << target->GetName();
|
||||
|
||||
if (!bot->GetTrader() && !botAI->CanCastSpell(spell, target, true, itemTarget))
|
||||
{
|
||||
msg << "Cannot cast " << spellName.str();
|
||||
botAI->TellError(msg.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = spell ? botAI->CastSpell(spell, target, itemTarget) : botAI->CastSpell(text, target, itemTarget);
|
||||
if (result)
|
||||
{
|
||||
msg << "Casting " << spellName.str();
|
||||
|
||||
if (castCount > 1)
|
||||
{
|
||||
std::ostringstream cmd;
|
||||
cmd << castString(target) << " " << text << " " << (castCount - 1);
|
||||
botAI->HandleCommand(CHAT_MSG_WHISPER, cmd.str(), master);
|
||||
msg << "|cffffff00(x" << (castCount - 1) << " left)|r";
|
||||
}
|
||||
|
||||
botAI->TellMasterNoFacing(msg.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
msg << "Cast " << spellName.str() << " is failed";
|
||||
botAI->TellError(msg.str());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CastCustomNcSpellAction::isUseful() { return !bot->IsInCombat(); }
|
||||
|
||||
std::string const CastCustomNcSpellAction::castString(WorldObject* target)
|
||||
{
|
||||
return "castnc " + chat->FormatWorldobject(target);
|
||||
}
|
||||
|
||||
bool CastRandomSpellAction::AcceptSpell(SpellInfo const* spellInfo)
|
||||
{
|
||||
bool isTradeSkill = spellInfo->Effects[EFFECT_0].Effect == SPELL_EFFECT_CREATE_ITEM &&
|
||||
spellInfo->ReagentCount[EFFECT_0] > 0 && spellInfo->SchoolMask == 1;
|
||||
return !isTradeSkill && spellInfo->GetRecoveryTime() < MINUTE * IN_MILLISECONDS;
|
||||
}
|
||||
|
||||
bool CastRandomSpellAction::Execute(Event event)
|
||||
{
|
||||
std::vector<std::pair<uint32, std::string>> spellMap = GetSpellList();
|
||||
Player* master = GetMaster();
|
||||
|
||||
Unit* target = nullptr;
|
||||
GameObject* got = nullptr;
|
||||
|
||||
std::string name = event.getParam();
|
||||
if (name.empty())
|
||||
name = getName();
|
||||
|
||||
GuidVector wos = chat->parseGameobjects(name);
|
||||
for (auto wo : wos)
|
||||
{
|
||||
target = botAI->GetUnit(wo);
|
||||
got = botAI->GetGameObject(wo);
|
||||
|
||||
if (got || target)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!got && !target && bot->GetTarget())
|
||||
{
|
||||
target = botAI->GetUnit(bot->GetTarget());
|
||||
got = botAI->GetGameObject(bot->GetTarget());
|
||||
}
|
||||
|
||||
if (!got && !target)
|
||||
if (master && master->GetTarget())
|
||||
target = botAI->GetUnit(master->GetTarget());
|
||||
|
||||
if (!got && !target)
|
||||
target = bot;
|
||||
|
||||
std::vector<std::pair<uint32, std::pair<uint32, WorldObject*>>> spellList;
|
||||
|
||||
for (auto& spell : spellMap)
|
||||
{
|
||||
uint32 spellId = spell.first;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (!AcceptSpell(spellInfo))
|
||||
continue;
|
||||
|
||||
if (bot->HasSpell(spellId))
|
||||
{
|
||||
uint32 spellPriority = GetSpellPriority(spellInfo);
|
||||
|
||||
if (target && botAI->CanCastSpell(spellId, target, true))
|
||||
spellList.push_back(std::make_pair(spellId, std::make_pair(spellPriority, target)));
|
||||
if (got &&
|
||||
botAI->CanCastSpell(spellId, got->GetPositionX(), got->GetPositionY(), got->GetPositionZ(), true))
|
||||
spellList.push_back(std::make_pair(spellId, std::make_pair(spellPriority, got)));
|
||||
if (botAI->CanCastSpell(spellId, bot, true))
|
||||
spellList.push_back(std::make_pair(spellId, std::make_pair(spellPriority, bot)));
|
||||
}
|
||||
}
|
||||
|
||||
if (spellList.empty())
|
||||
return false;
|
||||
|
||||
// bool isCast = false; //not used, line marked for removal.
|
||||
|
||||
std::sort(spellList.begin(), spellList.end(),
|
||||
[](std::pair<uint32, std::pair<uint32, WorldObject*>> i,
|
||||
std::pair<uint32, std::pair<uint32, WorldObject*>> j) { return i.first > j.first; });
|
||||
|
||||
uint32 rndBound = spellList.size() / 4;
|
||||
|
||||
rndBound = std::min(rndBound, (uint32)10);
|
||||
rndBound = std::max(rndBound, (uint32)0);
|
||||
|
||||
for (uint32 i = 0; i < 5; i++)
|
||||
{
|
||||
uint32 rnd = urand(0, rndBound);
|
||||
|
||||
auto spell = spellList[rnd];
|
||||
|
||||
uint32 spellId = spell.first;
|
||||
WorldObject* wo = spell.second.second;
|
||||
|
||||
bool isCast = castSpell(spellId, wo);
|
||||
|
||||
if (isCast)
|
||||
{
|
||||
if (MultiCast && ((wo && bot->HasInArc(CAST_ANGLE_IN_FRONT, wo, sPlayerbotAIConfig->sightDistance))))
|
||||
{
|
||||
std::ostringstream cmd;
|
||||
cmd << "castnc " << chat->FormatWorldobject(wo) + " " << spellId << " " << 19;
|
||||
botAI->HandleCommand(CHAT_MSG_WHISPER, cmd.str(), bot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CraftRandomItemAction::AcceptSpell(SpellInfo const* spellInfo)
|
||||
{
|
||||
return spellInfo->Effects[EFFECT_0].Effect == SPELL_EFFECT_CREATE_ITEM && spellInfo->ReagentCount[EFFECT_0] > 0 &&
|
||||
spellInfo->SchoolMask == 0;
|
||||
}
|
||||
|
||||
uint32 CraftRandomItemAction::GetSpellPriority(SpellInfo const* spellInfo)
|
||||
{
|
||||
if (spellInfo->Effects[EFFECT_0].Effect != SPELL_EFFECT_CREATE_ITEM)
|
||||
{
|
||||
uint32 newItemId = spellInfo->Effects[EFFECT_0].ItemType;
|
||||
if (newItemId)
|
||||
{
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", newItemId);
|
||||
|
||||
if (usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_AMMO ||
|
||||
usage == ITEM_USAGE_QUEST || usage == ITEM_USAGE_SKILL || usage == ITEM_USAGE_USE)
|
||||
return 10;
|
||||
}
|
||||
|
||||
if (ItemUsageValue::SpellGivesSkillUp(spellInfo->Id, bot))
|
||||
return 8;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool CastRandomSpellAction::castSpell(uint32 spellId, WorldObject* wo)
|
||||
{
|
||||
if (wo->GetGUID().IsUnit())
|
||||
return botAI->CastSpell(spellId, (Unit*)(wo));
|
||||
else
|
||||
return botAI->CastSpell(spellId, wo->GetPositionX(), wo->GetPositionY(), wo->GetPositionZ());
|
||||
}
|
||||
|
||||
bool DisEnchantRandomItemAction::Execute(Event event)
|
||||
{
|
||||
std::vector<Item*> items =
|
||||
AI_VALUE2(std::vector<Item*>, "inventory items", "usage " + std::to_string(ITEM_USAGE_DISENCHANT));
|
||||
std::reverse(items.begin(), items.end());
|
||||
|
||||
for (auto& item : items)
|
||||
{
|
||||
// don't touch rare+ items if with real player/guild
|
||||
if ((botAI->HasRealPlayerMaster() || botAI->IsInRealGuild()) &&
|
||||
item->GetTemplate()->Quality > ITEM_QUALITY_UNCOMMON)
|
||||
return false;
|
||||
|
||||
if (CastCustomSpellAction::Execute(
|
||||
Event("disenchant random item", "13262 " + chat->FormatQItem(item->GetEntry()))))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DisEnchantRandomItemAction::isUseful()
|
||||
{
|
||||
return botAI->HasSkill(SKILL_ENCHANTING) && !bot->IsInCombat() &&
|
||||
AI_VALUE2(uint32, "item count", "usage " + std::to_string(ITEM_USAGE_DISENCHANT)) > 0;
|
||||
}
|
||||
|
||||
bool EnchantRandomItemAction::isUseful() { return botAI->HasSkill(SKILL_ENCHANTING) && !bot->IsInCombat(); }
|
||||
|
||||
bool EnchantRandomItemAction::AcceptSpell(SpellInfo const* spellInfo)
|
||||
{
|
||||
return spellInfo->Effects[EFFECT_0].Effect == SPELL_EFFECT_ENCHANT_ITEM && spellInfo->ReagentCount[EFFECT_0] > 0;
|
||||
}
|
||||
|
||||
uint32 EnchantRandomItemAction::GetSpellPriority(SpellInfo const* spellInfo)
|
||||
{
|
||||
if (spellInfo->Effects[EFFECT_0].Effect == SPELL_EFFECT_ENCHANT_ITEM)
|
||||
{
|
||||
if (AI_VALUE2(Item*, "item for spell", spellInfo->Id) && ItemUsageValue::SpellGivesSkillUp(spellInfo->Id, bot))
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
88
src/Bot/BaseAi/Actions/CastCustomSpellAction.h
Normal file
88
src/Bot/BaseAi/Actions/CastCustomSpellAction.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CASTCUSTOMSPELLACTION_H
|
||||
#define _PLAYERBOT_CASTCUSTOMSPELLACTION_H
|
||||
|
||||
#include "ListSpellsAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
class SpellInfo;
|
||||
class WorldObject;
|
||||
|
||||
class CastCustomSpellAction : public InventoryAction
|
||||
{
|
||||
public:
|
||||
CastCustomSpellAction(PlayerbotAI* botAI, std::string const name = "cast custom spell")
|
||||
: InventoryAction(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
virtual std::string const castString(WorldObject* target) { return "cast"; }
|
||||
|
||||
protected:
|
||||
bool ncCast = false;
|
||||
};
|
||||
|
||||
class CastCustomNcSpellAction : public CastCustomSpellAction
|
||||
{
|
||||
public:
|
||||
CastCustomNcSpellAction(PlayerbotAI* botAI, std::string const name = "cast custom nc spell")
|
||||
: CastCustomSpellAction(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
bool isUseful() override;
|
||||
std::string const castString(WorldObject* target) override;
|
||||
};
|
||||
|
||||
class CastRandomSpellAction : public ListSpellsAction
|
||||
{
|
||||
public:
|
||||
CastRandomSpellAction(PlayerbotAI* botAI, std::string const name = "cast random spell")
|
||||
: ListSpellsAction(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
bool isUseful() override { return false; }
|
||||
virtual bool AcceptSpell(SpellInfo const* spellInfo);
|
||||
virtual uint32 GetSpellPriority(SpellInfo const* spellInfo) { return 1; }
|
||||
virtual bool castSpell(uint32 spellId, WorldObject* wo);
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
bool MultiCast = false;
|
||||
};
|
||||
|
||||
class CraftRandomItemAction : public CastRandomSpellAction
|
||||
{
|
||||
public:
|
||||
CraftRandomItemAction(PlayerbotAI* botAI) : CastRandomSpellAction(botAI, "craft random item") { MultiCast = true; }
|
||||
|
||||
bool AcceptSpell(SpellInfo const* spellInfo) override;
|
||||
uint32 GetSpellPriority(SpellInfo const* spellInfo) override;
|
||||
};
|
||||
|
||||
class DisEnchantRandomItemAction : public CastCustomSpellAction
|
||||
{
|
||||
public:
|
||||
DisEnchantRandomItemAction(PlayerbotAI* botAI) : CastCustomSpellAction(botAI, "disenchant random item") {}
|
||||
|
||||
bool isUseful() override;
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class EnchantRandomItemAction : public CastRandomSpellAction
|
||||
{
|
||||
public:
|
||||
EnchantRandomItemAction(PlayerbotAI* botAI) : CastRandomSpellAction(botAI, "enchant random item") {}
|
||||
|
||||
bool isUseful() override;
|
||||
bool AcceptSpell(SpellInfo const* spellInfo) override;
|
||||
uint32 GetSpellPriority(SpellInfo const* spellInfo) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
31
src/Bot/BaseAi/Actions/ChangeChatAction.cpp
Normal file
31
src/Bot/BaseAi/Actions/ChangeChatAction.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ChangeChatAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool ChangeChatAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
ChatMsg parsed = chat->parseChat(text);
|
||||
if (parsed == CHAT_MSG_SYSTEM)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Current chat is " << chat->FormatChat(*context->GetValue<ChatMsg>("chat"));
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
else
|
||||
{
|
||||
context->GetValue<ChatMsg>("chat")->Set(parsed);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Chat set to " << chat->FormatChat(parsed);
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/ChangeChatAction.h
Normal file
21
src/Bot/BaseAi/Actions/ChangeChatAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHANGECHATACTION_H
|
||||
#define _PLAYERBOT_CHANGECHATACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class ChangeChatAction : public Action
|
||||
{
|
||||
public:
|
||||
ChangeChatAction(PlayerbotAI* botAI) : Action(botAI, "chat") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
81
src/Bot/BaseAi/Actions/ChangeStrategyAction.cpp
Normal file
81
src/Bot/BaseAi/Actions/ChangeStrategyAction.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ChangeStrategyAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "PlayerbotRepository.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool ChangeCombatStrategyAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
botAI->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT);
|
||||
if (event.GetSource() == "co")
|
||||
{
|
||||
std::vector<std::string> splitted = split(text, ',');
|
||||
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
|
||||
{
|
||||
const char* name = i->c_str();
|
||||
switch (name[0])
|
||||
{
|
||||
case '+':
|
||||
case '-':
|
||||
case '~':
|
||||
sPlayerbotRepository->Save(botAI);
|
||||
break;
|
||||
case '?':
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChangeNonCombatStrategyAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
|
||||
uint32 account = bot->GetSession()->GetAccountId();
|
||||
if (sPlayerbotAIConfig->IsInRandomAccountList(account) && botAI->GetMaster() &&
|
||||
botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
||||
{
|
||||
if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos)
|
||||
{
|
||||
botAI->TellError("You can change any strategy except loot and gather");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
botAI->ChangeStrategy(text, BOT_STATE_NON_COMBAT);
|
||||
if (event.GetSource() == "nc")
|
||||
{
|
||||
std::vector<std::string> splitted = split(text, ',');
|
||||
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
|
||||
{
|
||||
const char* name = i->c_str();
|
||||
switch (name[0])
|
||||
{
|
||||
case '+':
|
||||
case '-':
|
||||
case '~':
|
||||
sPlayerbotRepository->Save(botAI);
|
||||
break;
|
||||
case '?':
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChangeDeadStrategyAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
botAI->ChangeStrategy(text, BOT_STATE_DEAD);
|
||||
return true;
|
||||
}
|
||||
37
src/Bot/BaseAi/Actions/ChangeStrategyAction.h
Normal file
37
src/Bot/BaseAi/Actions/ChangeStrategyAction.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHANGESTRATEGYACTION_H
|
||||
#define _PLAYERBOT_CHANGESTRATEGYACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class ChangeCombatStrategyAction : public Action
|
||||
{
|
||||
public:
|
||||
ChangeCombatStrategyAction(PlayerbotAI* botAI, std::string const name = "co") : Action(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ChangeNonCombatStrategyAction : public Action
|
||||
{
|
||||
public:
|
||||
ChangeNonCombatStrategyAction(PlayerbotAI* botAI) : Action(botAI, "nc") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class ChangeDeadStrategyAction : public Action
|
||||
{
|
||||
public:
|
||||
ChangeDeadStrategyAction(PlayerbotAI* botAI) : Action(botAI, "de") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
387
src/Bot/BaseAi/Actions/ChangeTalentsAction.cpp
Normal file
387
src/Bot/BaseAi/Actions/ChangeTalentsAction.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ChangeTalentsAction.h"
|
||||
|
||||
#include "AiFactory.h"
|
||||
#include "ChatHelper.h"
|
||||
#include "Event.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Log.h"
|
||||
|
||||
bool ChangeTalentsAction::Execute(Event event)
|
||||
{
|
||||
auto* flag = botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs"); // Added for custom Glyphs
|
||||
|
||||
if (flag->Get()) // Added for custom Glyphs
|
||||
{
|
||||
flag->Set(false);
|
||||
LOG_INFO("playerbots", "Custom Glyph Flag set to OFF");
|
||||
}
|
||||
std::string param = event.getParam();
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
if (!param.empty())
|
||||
{
|
||||
if (param.find("help") != std::string::npos)
|
||||
{
|
||||
out << TalentsHelp();
|
||||
}
|
||||
else if (param.find("switch") != std::string::npos)
|
||||
{
|
||||
if (param.find("switch 1") != std::string::npos)
|
||||
{
|
||||
bot->ActivateSpec(0);
|
||||
out << "Active first talent";
|
||||
botAI->ResetStrategies();
|
||||
}
|
||||
else if (param.find("switch 2") != std::string::npos)
|
||||
{
|
||||
if (bot->GetSpecsCount() == 1 && bot->GetLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))
|
||||
{
|
||||
bot->CastSpell(bot, 63680, true, nullptr, nullptr, bot->GetGUID());
|
||||
bot->CastSpell(bot, 63624, true, nullptr, nullptr, bot->GetGUID());
|
||||
}
|
||||
bot->ActivateSpec(1);
|
||||
out << "Active second talent";
|
||||
botAI->ResetStrategies();
|
||||
}
|
||||
}
|
||||
else if (param.find("autopick") != std::string::npos)
|
||||
{
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
factory.InitTalentsTree(true);
|
||||
out << "Auto pick talents";
|
||||
botAI->ResetStrategies();
|
||||
}
|
||||
else if (param.find("spec list") != std::string::npos)
|
||||
{
|
||||
out << SpecList();
|
||||
}
|
||||
else if (param.find("spec ") != std::string::npos)
|
||||
{
|
||||
param = param.substr(5);
|
||||
out << SpecPick(param);
|
||||
botAI->ResetStrategies();
|
||||
}
|
||||
else if (param.find("apply ") != std::string::npos)
|
||||
{
|
||||
param = param.substr(6);
|
||||
out << SpecApply(param);
|
||||
botAI->ResetStrategies();
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "Unknown command.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 tab = AiFactory::GetPlayerSpecTab(bot);
|
||||
out << "My current talent spec is: "
|
||||
<< "|h|cffffffff";
|
||||
out << chat->FormatClass(bot, tab) << "\n";
|
||||
out << TalentsHelp();
|
||||
}
|
||||
|
||||
botAI->TellMaster(out);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ChangeTalentsAction::TalentsHelp()
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Talents usage: talents switch <1/2>, talents autopick, talents spec list, "
|
||||
"talents spec <specName>, talents apply <link>.";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string ChangeTalentsAction::SpecList()
|
||||
{
|
||||
int cls = bot->getClass();
|
||||
int specFound = 0;
|
||||
std::ostringstream out;
|
||||
for (int specNo = 0; specNo < MAX_SPECNO; ++specNo)
|
||||
{
|
||||
if (sPlayerbotAIConfig->premadeSpecName[cls][specNo].size() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
specFound++;
|
||||
std::ostringstream out;
|
||||
std::vector<std::vector<uint32>> parsed = sPlayerbotAIConfig->parsedSpecLinkOrder[cls][specNo][80];
|
||||
std::unordered_map<int, int> tabCount;
|
||||
tabCount[0] = tabCount[1] = tabCount[2] = 0;
|
||||
for (auto& item : parsed)
|
||||
{
|
||||
tabCount[item[0]] += item[3];
|
||||
}
|
||||
out << specFound << ". " << sPlayerbotAIConfig->premadeSpecName[cls][specNo] << " (";
|
||||
out << tabCount[0] << "-" << tabCount[1] << "-" << tabCount[2] << ")";
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
}
|
||||
out << "Total " << specFound << " specs found";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string ChangeTalentsAction::SpecPick(std::string param)
|
||||
{
|
||||
int cls = bot->getClass();
|
||||
// int specFound = 0; //not used, line marked for removal.
|
||||
for (int specNo = 0; specNo < MAX_SPECNO; ++specNo)
|
||||
{
|
||||
if (sPlayerbotAIConfig->premadeSpecName[cls][specNo].size() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (sPlayerbotAIConfig->premadeSpecName[cls][specNo] == param)
|
||||
{
|
||||
PlayerbotFactory::InitTalentsBySpecNo(bot, specNo, true);
|
||||
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
factory.InitGlyphs(false);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Picking " << sPlayerbotAIConfig->premadeSpecName[cls][specNo];
|
||||
return out.str();
|
||||
}
|
||||
}
|
||||
std::ostringstream out;
|
||||
out << "Spec " << param << " not found";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string ChangeTalentsAction::SpecApply(std::string param)
|
||||
{
|
||||
int cls = bot->getClass();
|
||||
std::ostringstream out;
|
||||
std::vector<std::vector<uint32>> parsedSpecLink = PlayerbotAIConfig::ParseTempTalentsOrder(cls, param);
|
||||
if (parsedSpecLink.size() == 0)
|
||||
{
|
||||
out << "Invalid link " << param;
|
||||
return out.str();
|
||||
}
|
||||
PlayerbotFactory::InitTalentsByParsedSpecLink(bot, parsedSpecLink, true);
|
||||
out << "Applying " << param;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(std::string const findName)
|
||||
// {
|
||||
// std::vector<TalentPath*> ret;
|
||||
// // for (auto& path : sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath)
|
||||
// // {
|
||||
// // if (findName.empty() || path.name.find(findName) != std::string::npos)
|
||||
// // {
|
||||
// // ret.push_back(&path);
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// return std::move(ret);
|
||||
// }
|
||||
|
||||
// std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(TalentSpec* oldSpec)
|
||||
// {
|
||||
// std::vector<TalentPath*> ret;
|
||||
|
||||
// // for (auto& path : sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath)
|
||||
// // {
|
||||
// // TalentSpec newSpec = *GetBestPremadeSpec(path.id);
|
||||
// // newSpec.CropTalents(bot->GetLevel());
|
||||
// // if (oldSpec->isEarlierVersionOf(newSpec))
|
||||
// // {
|
||||
// // ret.push_back(&path);
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// return std::move(ret);
|
||||
// }
|
||||
|
||||
// TalentPath* ChangeTalentsAction::getPremadePath(uint32 id)
|
||||
// {
|
||||
// // for (auto& path : sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath)
|
||||
// // {
|
||||
// // if (id == path.id)
|
||||
// // {
|
||||
// // return &path;
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // return &sPlayerbotAIConfig->classSpecs[bot->getClass()].talentPath[0];
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// void ChangeTalentsAction::listPremadePaths(std::vector<TalentPath*> paths, std::ostringstream* out)
|
||||
// {
|
||||
// if (paths.size() == 0)
|
||||
// {
|
||||
// *out << "No predefined talents found..";
|
||||
// }
|
||||
|
||||
// *out << "|h|cffffffff";
|
||||
|
||||
// for (auto path : paths)
|
||||
// {
|
||||
// *out << path->name << " (" << path->talentSpec.back().FormatSpec(bot) << "), ";
|
||||
// }
|
||||
|
||||
// out->seekp(-2, out->cur);
|
||||
// *out << ".";
|
||||
// }
|
||||
|
||||
// TalentPath* ChangeTalentsAction::PickPremadePath(std::vector<TalentPath*> paths, bool useProbability)
|
||||
// {
|
||||
// uint32 totProbability = 0;
|
||||
// uint32 curProbability = 0;
|
||||
|
||||
// if (paths.size() == 1)
|
||||
// return paths[0];
|
||||
|
||||
// for (auto path : paths)
|
||||
// {
|
||||
// totProbability += useProbability ? path->probability : 1;
|
||||
// }
|
||||
|
||||
// totProbability = urand(0, totProbability);
|
||||
|
||||
// for (auto path : paths)
|
||||
// {
|
||||
// curProbability += (useProbability ? path->probability : 1);
|
||||
// if (curProbability >= totProbability)
|
||||
// return path;
|
||||
// }
|
||||
|
||||
// return paths[0];
|
||||
// }
|
||||
|
||||
// bool ChangeTalentsAction::AutoSelectTalents(std::ostringstream* out)
|
||||
// {
|
||||
// // Does the bot have talentpoints?
|
||||
// if (bot->GetLevel() < 10)
|
||||
// {
|
||||
// *out << "No free talent points.";
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// uint32 specNo = sRandomPlayerbotMgr->GetValue(bot->GetGUID().GetCounter(), "specNo");
|
||||
// uint32 specId = specNo - 1;
|
||||
// std::string specLink = sRandomPlayerbotMgr->GetData(bot->GetGUID().GetCounter(), "specLink");
|
||||
|
||||
// //Continue the current spec
|
||||
// if (specNo > 0)
|
||||
// {
|
||||
// TalentSpec newSpec = *GetBestPremadeSpec(specId);
|
||||
// newSpec.CropTalents(bot->GetLevel());
|
||||
// newSpec.ApplyTalents(bot, out);
|
||||
// if (newSpec.GetTalentPoints() > 0)
|
||||
// {
|
||||
// *out << "Upgrading spec " << "|h|cffffffff" << getPremadePath(specId)->name << "" <<
|
||||
// newSpec.FormatSpec(bot);
|
||||
// }
|
||||
// }
|
||||
// else if (!specLink.empty())
|
||||
// {
|
||||
// TalentSpec newSpec(bot, specLink);
|
||||
// newSpec.CropTalents(bot->GetLevel());
|
||||
// newSpec.ApplyTalents(bot, out);
|
||||
// if (newSpec.GetTalentPoints() > 0)
|
||||
// {
|
||||
// *out << "Upgrading saved spec "
|
||||
// << "|h|cffffffff" << chat->FormatClass(bot, newSpec.highestTree()) << " (" <<
|
||||
// newSpec.FormatSpec(bot) << ")";
|
||||
// }
|
||||
// }
|
||||
|
||||
// //Spec was not found or not sufficient
|
||||
// if (bot->GetFreeTalentPoints() > 0 || (!specNo && specLink.empty()))
|
||||
// {
|
||||
// TalentSpec oldSpec(bot);
|
||||
// std::vector<TalentPath*> paths = getPremadePaths(&oldSpec);
|
||||
|
||||
// if (paths.size() == 0) //No spec like the old one found. Pick any.
|
||||
// {
|
||||
// if (bot->CalculateTalentsPoints() > 0)
|
||||
// *out << "No specs like the current spec found. ";
|
||||
|
||||
// paths = getPremadePaths("");
|
||||
// }
|
||||
|
||||
// if (paths.size() == 0)
|
||||
// {
|
||||
// *out << "No predefined talents found for this class.";
|
||||
// specId = -1;
|
||||
// // specLink = "";
|
||||
// }
|
||||
// else if (paths.size() > 1 && false/*!sPlayerbotAIConfig->autoPickTalents*/ &&
|
||||
// !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
// {
|
||||
// *out << "Found multiple specs: ";
|
||||
// listPremadePaths(paths, out);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// specId = PickPremadePath(paths, sRandomPlayerbotMgr->IsRandomBot(bot))->id;
|
||||
// TalentSpec newSpec = *GetBestPremadeSpec(specId);
|
||||
// specLink = newSpec.GetTalentLink();
|
||||
// newSpec.CropTalents(bot->GetLevel());
|
||||
// newSpec.ApplyTalents(bot, out);
|
||||
|
||||
// if (paths.size() > 1)
|
||||
// *out << "Found " << paths.size() << " possible specs to choose from. ";
|
||||
|
||||
// *out << "Apply spec " << "|h|cffffffff" << getPremadePath(specId)->name << " " <<
|
||||
// newSpec.FormatSpec(bot);
|
||||
// }
|
||||
// }
|
||||
|
||||
// sRandomPlayerbotMgr->SetValue(bot->GetGUID().GetCounter(), "specNo", specId + 1);
|
||||
|
||||
// if (!specLink.empty() && specId == -1)
|
||||
// sRandomPlayerbotMgr->SetValue(bot->GetGUID().GetCounter(), "specLink", 1, specLink);
|
||||
// else
|
||||
// sRandomPlayerbotMgr->SetValue(bot->GetGUID().GetCounter(), "specLink", 0);
|
||||
|
||||
// return (specNo == 0) ? false : true;
|
||||
// }
|
||||
|
||||
// //Returns a pre-made talentspec that best suits the bots current talents.
|
||||
// TalentSpec* ChangeTalentsAction::GetBestPremadeSpec(uint32 specId)
|
||||
// {
|
||||
// TalentPath* path = getPremadePath(specId);
|
||||
// for (auto& spec : path->talentSpec)
|
||||
// {
|
||||
// if (spec.points >= bot->CalculateTalentsPoints())
|
||||
// return &spec;
|
||||
// }
|
||||
|
||||
// if (path->talentSpec.size())
|
||||
// return &path->talentSpec.back();
|
||||
|
||||
// // return &sPlayerbotAIConfig->classSpecs[bot->getClassMask()].baseSpec;
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
bool AutoSetTalentsAction::Execute(Event event)
|
||||
{
|
||||
std::ostringstream out;
|
||||
|
||||
if (!sPlayerbotAIConfig->autoPickTalents || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return false;
|
||||
|
||||
if (bot->GetFreeTalentPoints() <= 0)
|
||||
return false;
|
||||
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
factory.InitTalentsTree(true, true, true);
|
||||
factory.InitPetTalents();
|
||||
botAI->TellMaster(out);
|
||||
|
||||
return true;
|
||||
}
|
||||
43
src/Bot/BaseAi/Actions/ChangeTalentsAction.h
Normal file
43
src/Bot/BaseAi/Actions/ChangeTalentsAction.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHANGETALENTSACTION_H
|
||||
#define _PLAYERBOT_CHANGETALENTSACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "Talentspec.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class ChangeTalentsAction : public Action
|
||||
{
|
||||
public:
|
||||
ChangeTalentsAction(PlayerbotAI* botAI, std::string const name = "talents") : Action(botAI, name) {}
|
||||
|
||||
bool Execute(Event event);
|
||||
// bool AutoSelectTalents(std::ostringstream* out);
|
||||
|
||||
private:
|
||||
// std::vector<TalentPath*> getPremadePaths(std::string const findName);
|
||||
// std::vector<TalentPath*> getPremadePaths(TalentSpec* oldSpec);
|
||||
// TalentPath* getPremadePath(uint32 id);
|
||||
// void listPremadePaths(std::vector<TalentPath*> paths, std::ostringstream* out);
|
||||
// TalentPath* PickPremadePath(std::vector<TalentPath*> paths, bool useProbability);
|
||||
// TalentSpec* GetBestPremadeSpec(uint32 spec);
|
||||
std::string TalentsHelp();
|
||||
std::string SpecList();
|
||||
std::string SpecPick(std::string param);
|
||||
std::string SpecApply(std::string param);
|
||||
};
|
||||
|
||||
class AutoSetTalentsAction : public ChangeTalentsAction
|
||||
{
|
||||
public:
|
||||
AutoSetTalentsAction(PlayerbotAI* botAI) : ChangeTalentsAction(botAI, "auto talents") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
255
src/Bot/BaseAi/Actions/ChatShortcutActions.cpp
Normal file
255
src/Bot/BaseAi/Actions/ChatShortcutActions.cpp
Normal file
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ChatShortcutActions.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Formations.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PositionValue.h"
|
||||
|
||||
void PositionsResetAction::ResetReturnPosition()
|
||||
{
|
||||
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
|
||||
PositionInfo pos = posMap["return"];
|
||||
pos.Reset();
|
||||
posMap["return"] = pos;
|
||||
}
|
||||
|
||||
void PositionsResetAction::SetReturnPosition(float x, float y, float z)
|
||||
{
|
||||
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
|
||||
PositionInfo pos = posMap["return"];
|
||||
pos.Set(x, y, z, botAI->GetBot()->GetMapId());
|
||||
posMap["return"] = pos;
|
||||
}
|
||||
|
||||
void PositionsResetAction::ResetStayPosition()
|
||||
{
|
||||
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
|
||||
PositionInfo pos = posMap["stay"];
|
||||
pos.Reset();
|
||||
posMap["stay"] = pos;
|
||||
}
|
||||
|
||||
void PositionsResetAction::SetStayPosition(float x, float y, float z)
|
||||
{
|
||||
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
|
||||
PositionInfo pos = posMap["stay"];
|
||||
pos.Set(x, y, z, botAI->GetBot()->GetMapId());
|
||||
posMap["stay"] = pos;
|
||||
}
|
||||
|
||||
bool FollowChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
// botAI->Reset();
|
||||
botAI->ChangeStrategy("+follow,-passive,-grind,-move from group", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("-stay,-follow,-passive,-grind,-move from group", BOT_STATE_COMBAT);
|
||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Reset();
|
||||
|
||||
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
|
||||
PositionInfo pos = posMap["return"];
|
||||
pos.Reset();
|
||||
posMap["return"] = pos;
|
||||
|
||||
pos = posMap["stay"];
|
||||
pos.Reset();
|
||||
posMap["stay"] = pos;
|
||||
|
||||
if (bot->IsInCombat())
|
||||
{
|
||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||
std::string const target = formation->GetTargetName();
|
||||
bool moved = false;
|
||||
if (!target.empty())
|
||||
{
|
||||
moved = Follow(AI_VALUE(Unit*, target));
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldLocation loc = formation->GetLocation();
|
||||
if (Formation::IsNullLocation(loc) || loc.GetMapId() == -1)
|
||||
return false;
|
||||
|
||||
MovementPriority priority = botAI->GetState() == BOT_STATE_COMBAT ? MovementPriority::MOVEMENT_COMBAT : MovementPriority::MOVEMENT_NORMAL;
|
||||
moved = MoveTo(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), false, false, false,
|
||||
true, priority);
|
||||
}
|
||||
|
||||
if (Pet* pet = bot->GetPet())
|
||||
{
|
||||
botAI->PetFollow();
|
||||
}
|
||||
|
||||
if (moved)
|
||||
{
|
||||
botAI->TellMaster("Following");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Default mechanics takes care of this now.
|
||||
if (bot->GetMapId() != master->GetMapId() || (master && bot->GetDistance(master) >
|
||||
sPlayerbotAIConfig->sightDistance))
|
||||
{
|
||||
if (bot->isDead())
|
||||
{
|
||||
bot->ResurrectPlayer(1.0f, false);
|
||||
botAI->TellMasterNoFacing("Back from the grave!");
|
||||
}
|
||||
else
|
||||
botAI->TellMaster("You are too far away from me! I will there soon.");
|
||||
|
||||
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
bot->TeleportTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(),
|
||||
master->GetOrientation()); return true;
|
||||
}
|
||||
*/
|
||||
|
||||
botAI->TellMaster("Following");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StayChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("+stay,-passive,-move from group", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+stay,-follow,-passive,-move from group", BOT_STATE_COMBAT);
|
||||
|
||||
SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||
SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||
|
||||
botAI->TellMaster("Staying");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MoveFromGroupChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
// dont need to remove stay or follow, move from group takes priority over both
|
||||
// (see their isUseful() methods)
|
||||
botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT);
|
||||
|
||||
botAI->TellMaster("Moving away from group");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("+follow,-stay,+passive", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+follow,-stay,+passive", BOT_STATE_COMBAT);
|
||||
|
||||
ResetReturnPosition();
|
||||
ResetStayPosition();
|
||||
|
||||
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig->sightDistance)
|
||||
{
|
||||
botAI->TellError("I will not flee with you - too far away");
|
||||
return true;
|
||||
}
|
||||
|
||||
botAI->TellMaster("Fleeing");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GoawayChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("+runaway,-stay", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+runaway,-stay", BOT_STATE_COMBAT);
|
||||
|
||||
ResetReturnPosition();
|
||||
ResetStayPosition();
|
||||
|
||||
botAI->TellMaster("Running away");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GrindChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("+grind,-passive,-stay", BOT_STATE_NON_COMBAT);
|
||||
|
||||
ResetReturnPosition();
|
||||
ResetStayPosition();
|
||||
|
||||
botAI->TellMaster("Grinding");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TankAttackChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
if (!botAI->IsTank(bot))
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("-passive", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("-passive", BOT_STATE_COMBAT);
|
||||
|
||||
ResetReturnPosition();
|
||||
ResetStayPosition();
|
||||
|
||||
botAI->TellMaster("Attacking");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaxDpsChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
if (!botAI->ContainsStrategy(STRATEGY_TYPE_DPS))
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
|
||||
botAI->ChangeStrategy("-threat,-conserve mana,-cast time,+dps debuff,+boost", BOT_STATE_COMBAT);
|
||||
botAI->TellMaster("Max DPS!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BwlChatShortcutAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
botAI->Reset();
|
||||
botAI->ChangeStrategy("+bwl", BOT_STATE_NON_COMBAT);
|
||||
botAI->ChangeStrategy("+bwl", BOT_STATE_COMBAT);
|
||||
botAI->TellMasterNoFacing("Add Bwl Strategies!");
|
||||
return true;
|
||||
}
|
||||
94
src/Bot/BaseAi/Actions/ChatShortcutActions.h
Normal file
94
src/Bot/BaseAi/Actions/ChatShortcutActions.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHATSHORTCUTACTION_H
|
||||
#define _PLAYERBOT_CHATSHORTCUTACTION_H
|
||||
|
||||
#include "MovementActions.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class PositionsResetAction : public Action
|
||||
{
|
||||
public:
|
||||
PositionsResetAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) {}
|
||||
|
||||
void ResetReturnPosition();
|
||||
void SetReturnPosition(float x, float y, float z);
|
||||
void ResetStayPosition();
|
||||
void SetStayPosition(float x, float y, float z);
|
||||
};
|
||||
|
||||
class FollowChatShortcutAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
FollowChatShortcutAction(PlayerbotAI* botAI) : MovementAction(botAI, "follow chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class StayChatShortcutAction : public PositionsResetAction
|
||||
{
|
||||
public:
|
||||
StayChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "stay chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MoveFromGroupChatShortcutAction : public Action
|
||||
{
|
||||
public:
|
||||
MoveFromGroupChatShortcutAction(PlayerbotAI* botAI) : Action(botAI, "move from group chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class FleeChatShortcutAction : public PositionsResetAction
|
||||
{
|
||||
public:
|
||||
FleeChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "flee chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class GoawayChatShortcutAction : public PositionsResetAction
|
||||
{
|
||||
public:
|
||||
GoawayChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "runaway chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class GrindChatShortcutAction : public PositionsResetAction
|
||||
{
|
||||
public:
|
||||
GrindChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "grind chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TankAttackChatShortcutAction : public PositionsResetAction
|
||||
{
|
||||
public:
|
||||
TankAttackChatShortcutAction(PlayerbotAI* botAI) : PositionsResetAction(botAI, "tank attack chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MaxDpsChatShortcutAction : public Action
|
||||
{
|
||||
public:
|
||||
MaxDpsChatShortcutAction(PlayerbotAI* botAI) : Action(botAI, "max dps chat shortcut") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BwlChatShortcutAction : public Action
|
||||
{
|
||||
public:
|
||||
BwlChatShortcutAction(PlayerbotAI* ai) : Action(ai, "bwl chat shortcut") {}
|
||||
virtual bool Execute(Event event);
|
||||
};
|
||||
#endif
|
||||
96
src/Bot/BaseAi/Actions/CheatAction.cpp
Normal file
96
src/Bot/BaseAi/Actions/CheatAction.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CheatAction.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool CheatAction::Execute(Event event)
|
||||
{
|
||||
std::string const param = event.getParam();
|
||||
|
||||
uint32 cheatMask = (uint32)botAI->GetCheat();
|
||||
|
||||
std::vector<std::string> splitted = split(param, ',');
|
||||
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
|
||||
{
|
||||
const char* name = i->c_str();
|
||||
switch (name[0])
|
||||
{
|
||||
case '+':
|
||||
cheatMask |= (uint32)GetCheatMask(name + 1);
|
||||
break;
|
||||
case '-':
|
||||
cheatMask ^= (uint32)GetCheatMask(name + 1);
|
||||
break;
|
||||
case '?':
|
||||
ListCheats();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
botAI->SetCheat(BotCheatMask(cheatMask));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BotCheatMask CheatAction::GetCheatMask(std::string const cheat)
|
||||
{
|
||||
if (cheat == "taxi")
|
||||
return BotCheatMask::taxi;
|
||||
|
||||
if (cheat == "gold")
|
||||
return BotCheatMask::gold;
|
||||
|
||||
if (cheat == "health")
|
||||
return BotCheatMask::health;
|
||||
|
||||
if (cheat == "mana")
|
||||
return BotCheatMask::mana;
|
||||
|
||||
if (cheat == "power")
|
||||
return BotCheatMask::power;
|
||||
|
||||
if (cheat == "raid")
|
||||
return BotCheatMask::raid;
|
||||
|
||||
return BotCheatMask::none;
|
||||
}
|
||||
|
||||
std::string const CheatAction::GetCheatName(BotCheatMask cheatMask)
|
||||
{
|
||||
switch (cheatMask)
|
||||
{
|
||||
case BotCheatMask::taxi:
|
||||
return "taxi";
|
||||
case BotCheatMask::gold:
|
||||
return "gold";
|
||||
case BotCheatMask::health:
|
||||
return "health";
|
||||
case BotCheatMask::mana:
|
||||
return "mana";
|
||||
case BotCheatMask::power:
|
||||
return "power";
|
||||
case BotCheatMask::raid:
|
||||
return "raid";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
void CheatAction::ListCheats()
|
||||
{
|
||||
std::ostringstream out;
|
||||
for (int i = 0; i < log2((uint32)BotCheatMask::maxMask); i++)
|
||||
{
|
||||
BotCheatMask cheatMask = BotCheatMask(1 << i);
|
||||
if ((uint32)cheatMask & (uint32)sPlayerbotAIConfig->botCheatMask)
|
||||
out << "[conf:" << GetCheatName(BotCheatMask(cheatMask)) << "]";
|
||||
else if (botAI->HasCheat(cheatMask))
|
||||
out << "[" << GetCheatName(BotCheatMask(cheatMask)) << "]";
|
||||
}
|
||||
|
||||
botAI->TellMasterNoFacing(out);
|
||||
}
|
||||
23
src/Bot/BaseAi/Actions/CheatAction.h
Normal file
23
src/Bot/BaseAi/Actions/CheatAction.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
enum class BotCheatMask : uint32;
|
||||
|
||||
class CheatAction : public Action
|
||||
{
|
||||
public:
|
||||
CheatAction(PlayerbotAI* botAI) : Action(botAI, "cheat") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
static BotCheatMask GetCheatMask(std::string const cheat);
|
||||
static std::string const GetCheatName(BotCheatMask cheatMask);
|
||||
void ListCheats();
|
||||
};
|
||||
103
src/Bot/BaseAi/Actions/CheckMailAction.cpp
Normal file
103
src/Bot/BaseAi/Actions/CheckMailAction.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CheckMailAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "GuildTaskMgr.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool CheckMailAction::Execute(Event event)
|
||||
{
|
||||
WorldPacket p;
|
||||
bot->GetSession()->HandleQueryNextMailTime(p);
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
std::vector<uint32> ids;
|
||||
for (PlayerMails::const_iterator i = bot->GetMails().begin(); i != bot->GetMails().end(); ++i)
|
||||
{
|
||||
Mail* mail = *i;
|
||||
if (!mail || mail->state == MAIL_STATE_DELETED)
|
||||
continue;
|
||||
|
||||
Player* owner = ObjectAccessor::FindConnectedPlayer(ObjectGuid::Create<HighGuid::Player>(mail->sender));
|
||||
if (!owner)
|
||||
continue;
|
||||
|
||||
uint32 account = owner->GetSession()->GetAccountId();
|
||||
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
|
||||
continue;
|
||||
|
||||
ProcessMail(mail, owner, trans);
|
||||
ids.push_back(mail->messageID);
|
||||
mail->state = MAIL_STATE_DELETED;
|
||||
}
|
||||
|
||||
for (uint32 id : ids)
|
||||
{
|
||||
bot->SendMailResult(id, MAIL_DELETED, MAIL_OK);
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
|
||||
stmt->SetData(0, id);
|
||||
trans->Append(stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
|
||||
stmt->SetData(0, id);
|
||||
trans->Append(stmt);
|
||||
|
||||
bot->RemoveMail(id);
|
||||
}
|
||||
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckMailAction::isUseful()
|
||||
{
|
||||
if (botAI->GetMaster() || !bot->GetMailSize() || bot->InBattleground())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheckMailAction::ProcessMail(Mail* mail, Player* owner, CharacterDatabaseTransaction trans)
|
||||
{
|
||||
if (mail->items.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (mail->subject.find("Item(s) you asked for") != std::string::npos)
|
||||
return;
|
||||
|
||||
for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i)
|
||||
{
|
||||
Item* item = bot->GetMItem(i->item_guid);
|
||||
if (!item)
|
||||
continue;
|
||||
|
||||
if (!sGuildTaskMgr->CheckItemTask(i->item_template, item->GetCount(), owner, bot, true))
|
||||
{
|
||||
std::ostringstream body;
|
||||
body << "Hello, " << owner->GetName() << ",\n";
|
||||
body << "\n";
|
||||
body << "Here are the item(s) you've sent me by mistake";
|
||||
body << "\n";
|
||||
body << "Thanks,\n";
|
||||
body << bot->GetName() << "\n";
|
||||
|
||||
MailDraft draft("Item(s) you've sent me", body.str());
|
||||
draft.AddItem(item);
|
||||
bot->RemoveMItem(i->item_guid);
|
||||
draft.SendMailTo(trans, MailReceiver(owner), MailSender(bot));
|
||||
return;
|
||||
}
|
||||
|
||||
bot->RemoveMItem(i->item_guid);
|
||||
item->DestroyForPlayer(bot);
|
||||
}
|
||||
}
|
||||
28
src/Bot/BaseAi/Actions/CheckMailAction.h
Normal file
28
src/Bot/BaseAi/Actions/CheckMailAction.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHECKMAILACTION_H
|
||||
#define _PLAYERBOT_CHECKMAILACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "DatabaseEnvFwd.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
struct Mail;
|
||||
|
||||
class CheckMailAction : public Action
|
||||
{
|
||||
public:
|
||||
CheckMailAction(PlayerbotAI* botAI) : Action(botAI, "check mail") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
void ProcessMail(Mail* mail, Player* owner, CharacterDatabaseTransaction trans);
|
||||
};
|
||||
|
||||
#endif
|
||||
492
src/Bot/BaseAi/Actions/CheckMountStateAction.cpp
Normal file
492
src/Bot/BaseAi/Actions/CheckMountStateAction.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CheckMountStateAction.h"
|
||||
#include "BattleGroundTactics.h"
|
||||
#include "BattlegroundEY.h"
|
||||
#include "BattlegroundWS.h"
|
||||
#include "Event.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
|
||||
// Define the static map / init bool for caching bot preferred mount data globally
|
||||
std::unordered_map<uint32, PreferredMountCache> CheckMountStateAction::mountCache;
|
||||
bool CheckMountStateAction::preferredMountTableChecked = false;
|
||||
|
||||
MountData CollectMountData(const Player* bot)
|
||||
{
|
||||
MountData data;
|
||||
for (auto& entry : bot->GetSpellMap())
|
||||
{
|
||||
uint32 spellId = entry.first;
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo || spellInfo->Effects[0].ApplyAuraName != SPELL_AURA_MOUNTED)
|
||||
continue;
|
||||
|
||||
if (entry.second->State == PLAYERSPELL_REMOVED || !entry.second->Active || spellInfo->IsPassive())
|
||||
continue;
|
||||
|
||||
int32 effect1 = spellInfo->Effects[1].BasePoints;
|
||||
int32 effect2 = spellInfo->Effects[2].BasePoints;
|
||||
|
||||
int32 speed = std::max(effect1, effect2);
|
||||
|
||||
// Update max speed if appropriate.
|
||||
if (speed > data.maxSpeed)
|
||||
data.maxSpeed = speed; // In BG, clamp max speed to 99 later; here we just store the maximum found.
|
||||
|
||||
// Determine index: flight if either effect has flight aura or specific mount ID.
|
||||
uint32 index = (spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
|
||||
spellInfo->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
|
||||
// Winged Steed of the Ebon Blade
|
||||
// This mount is meant to autoscale from a 150% flyer
|
||||
// up to a 280% as you train your flying skill up.
|
||||
// This incorrectly gets categorised as a ground mount, force this to flyer only.
|
||||
// TODO: Add other scaling mounts here if they have the same issue, or adjust above
|
||||
// checks so that they are all correctly detected.
|
||||
spellInfo->Id == 54729) ? 1 : 0;
|
||||
data.allSpells[index][speed].push_back(spellId);
|
||||
}
|
||||
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*/)
|
||||
{
|
||||
// Determine if there are no attackers
|
||||
bool noAttackers = !AI_VALUE2(bool, "combat", "self target") || !AI_VALUE(uint8, "attacker count");
|
||||
bool enemy = AI_VALUE(Unit*, "enemy player target");
|
||||
bool dps = AI_VALUE(Unit*, "dps target");
|
||||
bool shouldDismount = false;
|
||||
bool shouldMount = false;
|
||||
|
||||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||
if (currentTarget)
|
||||
{
|
||||
float dismountDistance = CalculateDismountDistance();
|
||||
float mountDistance = CalculateMountDistance();
|
||||
float combatReach = bot->GetCombatReach() + currentTarget->GetCombatReach();
|
||||
float distanceToTarget = bot->GetExactDist(currentTarget);
|
||||
|
||||
shouldDismount = (distanceToTarget <= dismountDistance + combatReach);
|
||||
shouldMount = (distanceToTarget > mountDistance + combatReach);
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldMount = true;
|
||||
}
|
||||
|
||||
// If should dismount, or master (if any) is no longer in travel form, yet bot still is, remove the shapeshifts
|
||||
if (shouldDismount ||
|
||||
(masterInShapeshiftForm != FORM_TRAVEL && botInShapeshiftForm == FORM_TRAVEL) ||
|
||||
(masterInShapeshiftForm != FORM_FLIGHT && botInShapeshiftForm == FORM_FLIGHT && master && !master->IsMounted()) ||
|
||||
(masterInShapeshiftForm != FORM_FLIGHT_EPIC && botInShapeshiftForm == FORM_FLIGHT_EPIC && master && !master->IsMounted()))
|
||||
botAI->RemoveShapeshift();
|
||||
|
||||
if (shouldDismount && bot->IsMounted())
|
||||
{
|
||||
Dismount();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inBattleground = bot->InBattleground();
|
||||
|
||||
// If there is a master and bot not in BG, follow master's mount state regardless of group leader
|
||||
if (master && !inBattleground)
|
||||
{
|
||||
if (ShouldFollowMasterMountState(master, noAttackers, shouldMount))
|
||||
return Mount();
|
||||
|
||||
else if (ShouldDismountForMaster(master) && bot->IsMounted())
|
||||
{
|
||||
Dismount();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no master or bot in BG
|
||||
if ((!master || inBattleground) && !bot->IsMounted() &&
|
||||
noAttackers && shouldMount && !bot->IsInCombat())
|
||||
return Mount();
|
||||
|
||||
if (!bot->IsFlying() && shouldDismount && bot->IsMounted() &&
|
||||
(enemy || dps || (!noAttackers && bot->IsInCombat())))
|
||||
{
|
||||
Dismount();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::Mount()
|
||||
{
|
||||
// Remove current Shapeshift if need be
|
||||
if (botInShapeshiftForm != FORM_TRAVEL &&
|
||||
botInShapeshiftForm != FORM_FLIGHT &&
|
||||
botInShapeshiftForm != FORM_FLIGHT_EPIC)
|
||||
{
|
||||
botAI->RemoveShapeshift();
|
||||
botAI->RemoveAura("tree of life");
|
||||
}
|
||||
|
||||
if (TryPreferredMount(master))
|
||||
return true;
|
||||
|
||||
// Get bot mount data
|
||||
MountData mountData = CollectMountData(bot);
|
||||
int32 masterMountType = GetMountType(master);
|
||||
int32 masterSpeed = CalculateMasterMountSpeed(master, mountData);
|
||||
|
||||
// Try shapeshift
|
||||
if (TryForms(master, masterMountType, masterSpeed))
|
||||
return true;
|
||||
|
||||
// Try random mount
|
||||
auto spellsIt = mountData.allSpells.find(masterMountType);
|
||||
if (spellsIt != mountData.allSpells.end())
|
||||
{
|
||||
auto& spells = spellsIt->second;
|
||||
if (TryRandomMountFiltered(spells, masterSpeed))
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<Item*> items = AI_VALUE2(std::vector<Item*>, "inventory items", "mount");
|
||||
if (!items.empty())
|
||||
return UseItemAuto(*items.begin());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheckMountStateAction::Dismount()
|
||||
{
|
||||
if (bot->isMoving())
|
||||
bot->StopMoving();
|
||||
|
||||
WorldPacket emptyPacket;
|
||||
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const
|
||||
{
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
// If both master and bot are in matching forms or master is mounted with corresponding speed, nothing to do
|
||||
else if
|
||||
((masterInShapeshiftForm == FORM_TRAVEL && botInShapeshiftForm == FORM_TRAVEL) ||
|
||||
((masterInShapeshiftForm == FORM_FLIGHT || (masterMountType == 1 && masterSpeed == 149)) && botInShapeshiftForm == FORM_FLIGHT) ||
|
||||
((masterInShapeshiftForm == FORM_FLIGHT_EPIC || (masterMountType == 1 && masterSpeed == 279)) && botInShapeshiftForm == FORM_FLIGHT_EPIC))
|
||||
return true;
|
||||
|
||||
// Check if master is in Travel Form and bot can do the same
|
||||
if (botAI->CanCastSpell(SPELL_TRAVEL_FORM, bot, true) &&
|
||||
masterInShapeshiftForm == FORM_TRAVEL && botInShapeshiftForm != FORM_TRAVEL)
|
||||
{
|
||||
botAI->CastSpell(SPELL_TRAVEL_FORM, bot);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if master is in Flight Form or has a flying mount and bot can flight form
|
||||
if (botAI->CanCastSpell(SPELL_FLIGHT_FORM, bot, true) &&
|
||||
((masterInShapeshiftForm == FORM_FLIGHT && botInShapeshiftForm != FORM_FLIGHT) ||
|
||||
(masterMountType == 1 && masterSpeed == 149)))
|
||||
{
|
||||
botAI->CastSpell(SPELL_FLIGHT_FORM, bot);
|
||||
|
||||
// Compensate speedbuff
|
||||
bot->SetSpeed(MOVE_RUN, 2.5, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if master is in Swift Flight Form or has an epic flying mount and bot can swift flight form
|
||||
if (botAI->CanCastSpell(SPELL_SWIFT_FLIGHT_FORM, bot, true) &&
|
||||
((masterInShapeshiftForm == FORM_FLIGHT_EPIC && botInShapeshiftForm != FORM_FLIGHT_EPIC) ||
|
||||
(masterMountType == 1 && masterSpeed == 279)))
|
||||
{
|
||||
botAI->CastSpell(SPELL_SWIFT_FLIGHT_FORM, bot);
|
||||
|
||||
// Compensate speedbuff
|
||||
bot->SetSpeed(MOVE_RUN, 3.8, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::TryPreferredMount(Player* master) const
|
||||
{
|
||||
uint32 botGUID = bot->GetGUID().GetRawValue();
|
||||
|
||||
// Build cache (only once)
|
||||
if (!preferredMountTableChecked)
|
||||
{
|
||||
// Verify preferred mounts table existance in the database
|
||||
QueryResult checkTable = PlayerbotsDatabase.Query(
|
||||
"SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_schema = 'acore_playerbots' AND table_name = 'playerbots_preferred_mounts')");
|
||||
|
||||
if (checkTable && checkTable->Fetch()[0].Get<uint32>() == 1)
|
||||
{
|
||||
preferredMountTableChecked = true;
|
||||
|
||||
// Cache all mounts of both types globally, for all entries
|
||||
QueryResult result = PlayerbotsDatabase.Query("SELECT guid, spellid, type FROM playerbots_preferred_mounts");
|
||||
|
||||
if (result)
|
||||
{
|
||||
uint32 totalResults = 0;
|
||||
while (auto row = result->Fetch())
|
||||
{
|
||||
uint32 guid = row[0].Get<uint32>();
|
||||
uint32 spellId = row[1].Get<uint32>();
|
||||
uint32 mountType = row[2].Get<uint32>();
|
||||
|
||||
if (mountType == 0)
|
||||
mountCache[guid].groundMounts.push_back(spellId);
|
||||
|
||||
else if (mountType == 1)
|
||||
mountCache[guid].flightMounts.push_back(spellId);
|
||||
|
||||
totalResults++;
|
||||
|
||||
result->NextRow();
|
||||
}
|
||||
LOG_INFO("playerbots", "Preferred mounts initialized | Total records: {}", totalResults);
|
||||
}
|
||||
}
|
||||
else // If the SQL table is missing, log an error and return false
|
||||
{
|
||||
preferredMountTableChecked = true;
|
||||
|
||||
LOG_DEBUG("playerbots", "Preferred mounts SQL table playerbots_preferred_mounts does not exist!");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Pick a random preferred mount from the selection, if available
|
||||
uint32 chosenMountId = 0;
|
||||
|
||||
if (GetMountType(master) == 0 && !mountCache[botGUID].groundMounts.empty())
|
||||
{
|
||||
uint32 index = urand(0, mountCache[botGUID].groundMounts.size() - 1);
|
||||
chosenMountId = mountCache[botGUID].groundMounts[index];
|
||||
}
|
||||
|
||||
else if (GetMountType(master) == 1 && !mountCache[botGUID].flightMounts.empty())
|
||||
{
|
||||
uint32 index = urand(0, mountCache[botGUID].flightMounts.size() - 1);
|
||||
chosenMountId = mountCache[botGUID].flightMounts[index];
|
||||
}
|
||||
|
||||
// No suitable preferred mount found
|
||||
if (chosenMountId == 0)
|
||||
return false;
|
||||
|
||||
// Check if spell exists
|
||||
if (!sSpellMgr->GetSpellInfo(chosenMountId))
|
||||
{
|
||||
LOG_ERROR("playerbots", "Preferred mount failed: Invalid spell {} | Bot Guid: {}", chosenMountId, botGUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Required here as otherwise bots won't mount in BG's due to them constant moving
|
||||
if (bot->isMoving())
|
||||
bot->StopMoving();
|
||||
|
||||
// Check if spell can be cast - for now allow all, even if the bot does not have the actual mount
|
||||
//if (botAI->CanCastSpell(mountId, botAI->GetBot()))
|
||||
//{
|
||||
botAI->CastSpell(chosenMountId, botAI->GetBot());
|
||||
return true;
|
||||
//}
|
||||
|
||||
LOG_DEBUG("playerbots", "Preferred mount failed! | Bot Guid: {}", botGUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::TryRandomMountFiltered(const std::map<int32, std::vector<uint32>>& spells, int32 masterSpeed) const
|
||||
{
|
||||
for (auto const& pair : spells)
|
||||
{
|
||||
int32 currentSpeed = pair.first;
|
||||
|
||||
if ((masterSpeed > 59 && currentSpeed < 99) || (masterSpeed > 149 && currentSpeed < 279))
|
||||
continue;
|
||||
|
||||
// Pick a random mount from the candidate group.
|
||||
auto const& ids = pair.second;
|
||||
if (!ids.empty())
|
||||
{
|
||||
// Required here as otherwise bots won't mount in BG's due to them constant moving
|
||||
if (bot->isMoving())
|
||||
bot->StopMoving();
|
||||
|
||||
uint32 index = urand(0, ids.size() - 1);
|
||||
|
||||
if (botAI->CanCastSpell(ids[index], bot))
|
||||
{
|
||||
botAI->CastSpell(ids[index], bot);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float CheckMountStateAction::CalculateDismountDistance() const
|
||||
{
|
||||
// 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.
|
||||
bool isMelee = PlayerbotAI::IsMelee(bot);
|
||||
float dismountDistance = isMelee ? sPlayerbotAIConfig->meleeDistance + 2.0f : sPlayerbotAIConfig->spellDistance + 2.0f;
|
||||
return bot->getClass() == CLASS_WARRIOR ? std::max(18.0f, dismountDistance) : dismountDistance;
|
||||
}
|
||||
|
||||
float CheckMountStateAction::CalculateMountDistance() const
|
||||
{
|
||||
// Mount distance should be >= 21 regardless of class, because when travelling a distance < 21 it takes longer
|
||||
// to cast mount-spell than the time saved from the speed increase. At a distance of 21 both approaches take 3
|
||||
// seconds:
|
||||
// 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);
|
||||
float baseDistance = isMelee ? sPlayerbotAIConfig->meleeDistance + 10.0f : sPlayerbotAIConfig->spellDistance + 10.0f;
|
||||
return std::max(21.0f, baseDistance);
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::ShouldFollowMasterMountState(Player* master, bool noAttackers, bool shouldMount) const
|
||||
{
|
||||
bool isMasterMounted = master->IsMounted() || (masterInShapeshiftForm == FORM_FLIGHT ||
|
||||
masterInShapeshiftForm == FORM_FLIGHT_EPIC ||
|
||||
masterInShapeshiftForm == FORM_TRAVEL);
|
||||
return isMasterMounted && !bot->IsMounted() && noAttackers &&
|
||||
shouldMount && !bot->IsInCombat() && botAI->GetState() != BOT_STATE_COMBAT;
|
||||
}
|
||||
|
||||
bool CheckMountStateAction::ShouldDismountForMaster(Player* master) const
|
||||
{
|
||||
bool isMasterMounted = master->IsMounted() || (masterInShapeshiftForm == FORM_FLIGHT ||
|
||||
masterInShapeshiftForm == FORM_FLIGHT_EPIC ||
|
||||
masterInShapeshiftForm == FORM_TRAVEL);
|
||||
return !isMasterMounted && bot->IsMounted();
|
||||
}
|
||||
|
||||
int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const MountData& mountData) const
|
||||
{
|
||||
// Check riding skill and level requirements
|
||||
int32 ridingSkill = bot->GetPureSkillValue(SKILL_RIDING);
|
||||
int32 botLevel = bot->GetLevel();
|
||||
|
||||
if (ridingSkill <= 75 && botLevel < static_cast<int32>(sPlayerbotAIConfig->useFastGroundMountAtMinLevel))
|
||||
return 59;
|
||||
|
||||
// If there is a master and bot not in BG, use master's aura effects.
|
||||
if (master && !bot->InBattleground())
|
||||
{
|
||||
auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
|
||||
if (!auraEffects.empty())
|
||||
{
|
||||
SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo();
|
||||
int32 effect1 = masterSpell->Effects[1].BasePoints;
|
||||
int32 effect2 = masterSpell->Effects[2].BasePoints;
|
||||
return std::max(effect1, effect2);
|
||||
}
|
||||
else if (masterInShapeshiftForm == FORM_FLIGHT_EPIC)
|
||||
return 279;
|
||||
else if (masterInShapeshiftForm == FORM_FLIGHT)
|
||||
return 149;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bots on their own.
|
||||
int32 speed = mountData.maxSpeed;
|
||||
if (bot->InBattleground() && speed > 99)
|
||||
return 99;
|
||||
|
||||
return speed;
|
||||
}
|
||||
|
||||
return 59;
|
||||
}
|
||||
|
||||
uint32 CheckMountStateAction::GetMountType(Player* master) const
|
||||
{
|
||||
if (!master)
|
||||
return 0;
|
||||
|
||||
auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
|
||||
|
||||
if (!auraEffects.empty())
|
||||
{
|
||||
SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo();
|
||||
return (masterSpell->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED ||
|
||||
masterSpell->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) ? 1 : 0;
|
||||
}
|
||||
else if (masterInShapeshiftForm == FORM_FLIGHT || masterInShapeshiftForm == FORM_FLIGHT_EPIC)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
65
src/Bot/BaseAi/Actions/CheckMountStateAction.h
Normal file
65
src/Bot/BaseAi/Actions/CheckMountStateAction.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHECKMOUNTSTATEACTION_H
|
||||
#define _PLAYERBOT_CHECKMOUNTSTATEACTION_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "UseItemAction.h"
|
||||
|
||||
const uint16 SPELL_TRAVEL_FORM = 783;
|
||||
const uint16 SPELL_FLIGHT_FORM = 33943;
|
||||
const uint16 SPELL_SWIFT_FLIGHT_FORM = 40120;
|
||||
|
||||
struct MountData
|
||||
{
|
||||
bool swiftMount = false;
|
||||
// Outer map: index (0 for ground, 1 for flight), inner map: effect speed -> vector of spell IDs.
|
||||
std::map<uint32, std::map<int32, std::vector<uint32>>> allSpells;
|
||||
// Default mount speed.
|
||||
int32 maxSpeed = 59;
|
||||
};
|
||||
|
||||
struct PreferredMountCache
|
||||
{
|
||||
std::vector<uint32> groundMounts;
|
||||
std::vector<uint32> flightMounts;
|
||||
};
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class CheckMountStateAction : public UseItemAction
|
||||
{
|
||||
public:
|
||||
CheckMountStateAction(PlayerbotAI* botAI) : UseItemAction(botAI, "check mount state", true) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool isPossible() override { return true; }
|
||||
bool Mount();
|
||||
|
||||
private:
|
||||
Player* master;
|
||||
ShapeshiftForm masterInShapeshiftForm;
|
||||
ShapeshiftForm botInShapeshiftForm;
|
||||
static std::unordered_map<uint32, PreferredMountCache> mountCache;
|
||||
static bool preferredMountTableChecked;
|
||||
float CalculateDismountDistance() const;
|
||||
float CalculateMountDistance() const;
|
||||
void Dismount();
|
||||
bool ShouldFollowMasterMountState(Player* master, bool noAttackers, bool shouldMount) const;
|
||||
bool ShouldDismountForMaster(Player* master) const;
|
||||
int32 CalculateMasterMountSpeed(Player* master, const MountData& mountData) const;
|
||||
bool CheckForSwiftMount() const;
|
||||
std::map<uint32, std::map<int32, std::vector<uint32>>> GetAllMountSpells() const;
|
||||
bool TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const;
|
||||
bool TryPreferredMount(Player* master) const;
|
||||
uint32 GetMountType(Player* master) const;
|
||||
bool TryRandomMountFiltered(const std::map<int32, std::vector<uint32>>& spells, int32 masterSpeed) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
34
src/Bot/BaseAi/Actions/CheckValuesAction.cpp
Normal file
34
src/Bot/BaseAi/Actions/CheckValuesAction.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CheckValuesAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {}
|
||||
|
||||
bool CheckValuesAction::Execute(Event event)
|
||||
{
|
||||
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||||
{
|
||||
botAI->Ping(bot->GetPositionX(), bot->GetPositionY());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets");
|
||||
GuidVector all_targets = *context->GetValue<GuidVector>("all targets");
|
||||
GuidVector npcs = *context->GetValue<GuidVector>("nearest npcs");
|
||||
GuidVector corpses = *context->GetValue<GuidVector>("nearest corpses");
|
||||
GuidVector gos = *context->GetValue<GuidVector>("nearest game objects");
|
||||
GuidVector nfp = *context->GetValue<GuidVector>("nearest friendly players");
|
||||
|
||||
return true;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/CheckValuesAction.h
Normal file
21
src/Bot/BaseAi/Actions/CheckValuesAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHECKVALUESACTION_H
|
||||
#define _PLAYERBOT_CHECKVALUESACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class CheckValuesAction : public Action
|
||||
{
|
||||
public:
|
||||
CheckValuesAction(PlayerbotAI* botAI);
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
357
src/Bot/BaseAi/Actions/ChooseRpgTargetAction.cpp
Normal file
357
src/Bot/BaseAi/Actions/ChooseRpgTargetAction.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "ChooseRpgTargetAction.h"
|
||||
#include "BattlegroundMgr.h"
|
||||
#include "BudgetValues.h"
|
||||
#include "ChatHelper.h"
|
||||
#include "Event.h"
|
||||
#include "Formations.h"
|
||||
#include "GuildCreateActions.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RpgSubActions.h"
|
||||
#include "Util.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "PossibleRpgTargetsValue.h"
|
||||
|
||||
bool ChooseRpgTargetAction::HasSameTarget(ObjectGuid guid, uint32 max, GuidVector const& nearGuids)
|
||||
{
|
||||
if (botAI->HasRealPlayerMaster())
|
||||
return false;
|
||||
|
||||
uint32 num = 0;
|
||||
|
||||
for (auto& i : nearGuids)
|
||||
{
|
||||
Player* player = ObjectAccessor::FindPlayer(i);
|
||||
if (!player)
|
||||
continue;
|
||||
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
|
||||
if (!botAI)
|
||||
continue;
|
||||
|
||||
if (!botAI->AllowActivity(GRIND_ACTIVITY))
|
||||
continue;
|
||||
|
||||
if (PAI_VALUE(GuidPosition, "rpg target") != guid)
|
||||
continue;
|
||||
|
||||
num++;
|
||||
if (num >= max)
|
||||
break;
|
||||
}
|
||||
|
||||
return num > 0;
|
||||
}
|
||||
|
||||
float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
|
||||
{
|
||||
GuidPosition currentRpgTarget = AI_VALUE(GuidPosition, "rpg target");
|
||||
SET_AI_VALUE(GuidPosition, "rpg target", guidP);
|
||||
|
||||
Strategy* rpgStrategy = botAI->GetAiObjectContext()->GetStrategy("rpg");
|
||||
if (!rpgStrategy) return 0.0f;
|
||||
|
||||
std::vector<TriggerNode*> triggerNodes;
|
||||
rpgStrategy->InitTriggers(triggerNodes);
|
||||
|
||||
float maxRelevance = 0.0f;
|
||||
|
||||
for (auto triggerNode : triggerNodes)
|
||||
{
|
||||
Trigger* trigger = context->GetTrigger(triggerNode->getName());
|
||||
|
||||
if (trigger)
|
||||
{
|
||||
triggerNode->setTrigger(trigger);
|
||||
|
||||
if (triggerNode->getFirstRelevance() < maxRelevance || triggerNode->getFirstRelevance() > 2.0f)
|
||||
continue;
|
||||
|
||||
Trigger* trigger = triggerNode->getTrigger();
|
||||
|
||||
if (!trigger->IsActive())
|
||||
continue;
|
||||
|
||||
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||
|
||||
bool isRpg = false;
|
||||
|
||||
for (NextAction nextAction : nextActions)
|
||||
{
|
||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
|
||||
|
||||
if (dynamic_cast<RpgEnabled*>(action))
|
||||
isRpg = true;
|
||||
}
|
||||
|
||||
if (isRpg)
|
||||
{
|
||||
maxRelevance = triggerNode->getFirstRelevance();
|
||||
rgpActionReason[guidP] = triggerNode->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto trigger : triggerNodes)
|
||||
{
|
||||
delete trigger;
|
||||
}
|
||||
triggerNodes.clear();
|
||||
|
||||
SET_AI_VALUE(GuidPosition, "rpg target", currentRpgTarget);
|
||||
|
||||
if (!maxRelevance)
|
||||
return 0.0;
|
||||
|
||||
return floor((maxRelevance - 1.0) * 1000.0f);
|
||||
}
|
||||
|
||||
bool ChooseRpgTargetAction::Execute(Event event)
|
||||
{
|
||||
//TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target"); //not used, line marked for removal.
|
||||
Player* master = botAI->GetMaster();
|
||||
GuidPosition masterRpgTarget;
|
||||
if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported())
|
||||
{
|
||||
Player* player = botAI->GetMaster();
|
||||
//GuidPosition masterRpgTarget = PAI_VALUE(GuidPosition, "rpg target"); //not used, line marked for removal.
|
||||
}
|
||||
else
|
||||
master = nullptr;
|
||||
|
||||
std::unordered_map<ObjectGuid, uint32> targets;
|
||||
// uint32 num = 0; //not used, line marked for removal.
|
||||
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
|
||||
GuidVector possibleObjects = AI_VALUE(GuidVector, "nearest game objects no los");
|
||||
GuidVector possiblePlayers = AI_VALUE(GuidVector, "nearest friendly players");
|
||||
GuidSet& ignoreList = AI_VALUE(GuidSet&, "ignore rpg target");
|
||||
|
||||
for (auto target : possibleTargets)
|
||||
targets[target] = 0.0f;
|
||||
|
||||
for (auto target : possibleObjects)
|
||||
targets[target] = 0.0f;
|
||||
|
||||
for (auto target : possiblePlayers)
|
||||
targets[target] = 0.0f;
|
||||
|
||||
if (targets.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (urand(0, 9))
|
||||
{
|
||||
for (auto target : ignoreList)
|
||||
targets.erase(target);
|
||||
}
|
||||
|
||||
SET_AI_VALUE(std::string, "next rpg action", this->getName());
|
||||
|
||||
bool hasGoodRelevance = false;
|
||||
|
||||
for (auto& target : targets)
|
||||
{
|
||||
Unit* unit = ObjectAccessor::GetUnit(*bot, target.first);
|
||||
if (!unit)
|
||||
continue;
|
||||
|
||||
GuidPosition guidP(unit);
|
||||
if (!guidP || !guidP.getMap())
|
||||
continue;
|
||||
|
||||
// float priority = 1; //not used, line marked for removal.
|
||||
|
||||
if (guidP.GetWorldObject() && !isFollowValid(bot, guidP.GetWorldObject()))
|
||||
continue;
|
||||
|
||||
if (guidP.IsGameObject())
|
||||
{
|
||||
GameObject* go = guidP.GetGameObject();
|
||||
if (!go || !go->isSpawned() || go->GetGoState() != GO_STATE_READY)
|
||||
continue;
|
||||
}
|
||||
else if (guidP.IsPlayer())
|
||||
{
|
||||
Player* player = guidP.GetPlayer();
|
||||
if (!player)
|
||||
continue;
|
||||
|
||||
if (GET_PLAYERBOT_AI(player))
|
||||
{
|
||||
GuidPosition guidPP = PAI_VALUE(GuidPosition, "rpg target");
|
||||
if (guidPP.IsPlayer())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (possiblePlayers.size() > 200 || HasSameTarget(guidP, urand(5, 15), possiblePlayers))
|
||||
// continue;
|
||||
|
||||
float relevance = getMaxRelevance(guidP);
|
||||
|
||||
if (!hasGoodRelevance || relevance > 1)
|
||||
target.second = relevance;
|
||||
|
||||
if (target.second > 1)
|
||||
hasGoodRelevance = true;
|
||||
}
|
||||
|
||||
SET_AI_VALUE(std::string, "next rpg action", "");
|
||||
|
||||
for (auto it = begin(targets); it != end(targets);)
|
||||
{
|
||||
//Remove empty targets.
|
||||
if (it->second == 0)
|
||||
it = targets.erase(it);
|
||||
//Remove useless targets if there's any good ones
|
||||
else if (hasGoodRelevance && it->second <= 1.0)
|
||||
it = targets.erase(it);
|
||||
//Remove useless targets if it's not masters target.
|
||||
else if (!hasGoodRelevance && master && (!masterRpgTarget || it->first != masterRpgTarget))
|
||||
it = targets.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
if (targets.empty())
|
||||
{
|
||||
LOG_DEBUG("playerbots", "{} can't choose RPG target: all {} targets are not available", bot->GetName().c_str(), possibleTargets.size());
|
||||
RESET_AI_VALUE(GuidSet&, "ignore rpg target");
|
||||
RESET_AI_VALUE(GuidPosition, "rpg target");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<GuidPosition> guidps;
|
||||
std::vector<int32> relevances;
|
||||
|
||||
for (auto& target : targets)
|
||||
{
|
||||
Unit* unit = ObjectAccessor::GetUnit(*bot, target.first);
|
||||
if (!unit)
|
||||
continue;
|
||||
|
||||
GuidPosition guidP(unit);
|
||||
if (!guidP)
|
||||
continue;
|
||||
|
||||
guidps.push_back(guidP);
|
||||
relevances.push_back(target.second);
|
||||
}
|
||||
|
||||
std::mt19937 gen(time(0));
|
||||
sTravelMgr->weighted_shuffle(guidps.begin(), guidps.end(), relevances.begin(), relevances.end(), gen);
|
||||
|
||||
GuidPosition guidP(guidps.front());
|
||||
if (!guidP)
|
||||
{
|
||||
RESET_AI_VALUE(GuidPosition, "rpg target");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (botAI->HasStrategy("debug", BOT_STATE_NON_COMBAT) && guidP.GetWorldObject())
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "found: ";
|
||||
out << chat->FormatWorldobject(guidP.GetWorldObject());
|
||||
out << " " << relevances.front();
|
||||
|
||||
botAI->TellMasterNoFacing(out);
|
||||
}
|
||||
|
||||
SET_AI_VALUE(GuidPosition, "rpg target", guidP);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChooseRpgTargetAction::isUseful()
|
||||
{
|
||||
if (!botAI->AllowActivity(RPG_ACTIVITY))
|
||||
return false;
|
||||
|
||||
GuidPosition guidP = AI_VALUE(GuidPosition, "rpg target");
|
||||
|
||||
if (guidP && guidP.distance(bot) < sPlayerbotAIConfig->reactDistance * 2)
|
||||
return false;
|
||||
|
||||
// TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target"); //not used, line marked for removal.
|
||||
|
||||
//if (travelTarget->isTraveling() && AI_VALUE2(bool, "can free move to", *travelTarget->getPosition()))
|
||||
//return false;
|
||||
|
||||
if (AI_VALUE(GuidVector, "possible rpg targets").empty())
|
||||
return false;
|
||||
|
||||
//Not stay, not guard, not combat, not trading and group ready.
|
||||
if (!AI_VALUE(bool, "can move around"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
|
||||
{
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return isFollowValid(bot, WorldPosition(target));
|
||||
}
|
||||
|
||||
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
Player* groupLeader = botAI->GetGroupLeader();
|
||||
Player* realMaster = botAI->GetMaster();
|
||||
AiObjectContext* context = botAI->GetAiObjectContext();
|
||||
|
||||
bool inDungeon = false;
|
||||
|
||||
if (botAI->HasActivePlayerMaster())
|
||||
{
|
||||
if (realMaster->IsInWorld() && realMaster->GetMap()->IsDungeon() && bot->GetMapId() == realMaster->GetMapId())
|
||||
inDungeon = true;
|
||||
|
||||
if (realMaster && realMaster->IsInWorld() && realMaster->GetMap()->IsDungeon() &&
|
||||
(realMaster->GetMapId() != pos.getMapId()))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!groupLeader || bot == groupLeader)
|
||||
return true;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return true;
|
||||
|
||||
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||
return false;
|
||||
|
||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY());
|
||||
|
||||
if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
|
||||
{
|
||||
Player* player = groupLeader;
|
||||
if (groupLeader && !groupLeader->isMoving() ||
|
||||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f)
|
||||
return false;
|
||||
|
||||
if (!groupLeader->isMoving() && distance < 25.0f)
|
||||
return true;
|
||||
|
||||
if (distance < formation->GetMaxDistance())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
45
src/Bot/BaseAi/Actions/ChooseRpgTargetAction.h
Normal file
45
src/Bot/BaseAi/Actions/ChooseRpgTargetAction.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHOOSERPGTARGETACTION_H
|
||||
#define _PLAYERBOT_CHOOSERPGTARGETACTION_H
|
||||
|
||||
#include "ObjectGuid.h"
|
||||
#include "RpgAction.h"
|
||||
|
||||
class GuidPosition;
|
||||
class Player;
|
||||
class PlayerbotAI;
|
||||
class WorldObject;
|
||||
class WorldPosition;
|
||||
|
||||
class ChooseRpgTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
ChooseRpgTargetAction(PlayerbotAI* botAI, std::string const name = "choose rpg target") : Action(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
static bool isFollowValid(Player* bot, WorldObject* target);
|
||||
static bool isFollowValid(Player* bot, WorldPosition pos);
|
||||
|
||||
private:
|
||||
float getMaxRelevance(GuidPosition guidP);
|
||||
bool HasSameTarget(ObjectGuid guid, uint32 max, GuidVector const& nearGuids);
|
||||
|
||||
std::unordered_map <ObjectGuid, std::string> rgpActionReason;
|
||||
};
|
||||
|
||||
class ClearRpgTargetAction : public ChooseRpgTargetAction
|
||||
{
|
||||
public:
|
||||
ClearRpgTargetAction(PlayerbotAI* botAI) : ChooseRpgTargetAction(botAI, "clear rpg target") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
183
src/Bot/BaseAi/Actions/ChooseTargetActions.cpp
Normal file
183
src/Bot/BaseAi/Actions/ChooseTargetActions.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ChooseTargetActions.h"
|
||||
|
||||
#include "ChooseRpgTargetAction.h"
|
||||
#include "Event.h"
|
||||
#include "LootObjectStack.h"
|
||||
#include "NewRpgStrategy.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "PossibleRpgTargetsValue.h"
|
||||
#include "PvpTriggers.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
bool AttackEnemyPlayerAction::isUseful()
|
||||
{
|
||||
if (PlayerHasFlag::IsCapturingFlag(bot))
|
||||
return false;
|
||||
|
||||
return !sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId());
|
||||
}
|
||||
|
||||
bool AttackEnemyFlagCarrierAction::isUseful()
|
||||
{
|
||||
Unit* target = context->GetValue<Unit*>("enemy flag carrier")->Get();
|
||||
return target && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), 100.0f) &&
|
||||
PlayerHasFlag::IsCapturingFlag(bot);
|
||||
}
|
||||
|
||||
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 DropTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = context->GetValue<Unit*>("current target")->Get();
|
||||
if (target && target->isDead())
|
||||
{
|
||||
ObjectGuid guid = target->GetGUID();
|
||||
if (guid)
|
||||
context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid);
|
||||
}
|
||||
|
||||
// ObjectGuid pullTarget = context->GetValue<ObjectGuid>("pull target")->Get();
|
||||
// GuidVector possible = botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los")->Get();
|
||||
|
||||
// if (pullTarget && find(possible.begin(), possible.end(), pullTarget) == possible.end())
|
||||
// {
|
||||
// context->GetValue<ObjectGuid>("pull target")->Set(ObjectGuid::Empty);
|
||||
// }
|
||||
|
||||
context->GetValue<Unit*>("current target")->Set(nullptr);
|
||||
|
||||
bot->SetTarget(ObjectGuid::Empty);
|
||||
bot->SetSelection(ObjectGuid());
|
||||
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
|
||||
if (bot->getClass() == CLASS_HUNTER) // Check for Hunter Class
|
||||
{
|
||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||
}
|
||||
bot->AttackStop();
|
||||
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
// {
|
||||
// if (CreatureAI* creatureAI = ((Creature*)pet)->AI())
|
||||
// {
|
||||
// pet->SetReactState(REACT_PASSIVE);
|
||||
// pet->GetCharmInfo()->SetCommandState(COMMAND_FOLLOW);
|
||||
// pet->GetCharmInfo()->SetIsCommandFollow(true);
|
||||
// pet->AttackStop();
|
||||
// pet->GetCharmInfo()->IsReturning();
|
||||
// pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, pet->GetFollowAngle());
|
||||
// }
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttackAnythingAction::Execute(Event event)
|
||||
{
|
||||
bool result = AttackAction::Execute(event);
|
||||
if (result)
|
||||
{
|
||||
if (Unit* grindTarget = GetTarget())
|
||||
{
|
||||
if (char const* grindName = grindTarget->GetName().c_str())
|
||||
{
|
||||
context->GetValue<ObjectGuid>("pull target")->Set(grindTarget->GetGUID());
|
||||
bot->GetMotionMaster()->Clear();
|
||||
// bot->StopMoving();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AttackAnythingAction::isPossible() { return AttackAction::isPossible() && GetTarget(); }
|
||||
|
||||
bool DpsAssistAction::isUseful()
|
||||
{
|
||||
if (PlayerHasFlag::IsCapturingFlag(bot))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttackRtiTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
|
||||
|
||||
// Fallback: if the "rti target" value did not resolve a valid unit yet,
|
||||
// try to resolve the raid icon directly from the group.
|
||||
if (!rtiTarget)
|
||||
{
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
std::string const rti = AI_VALUE(std::string, "rti");
|
||||
int32 const index = RtiTargetValue::GetRtiIndex(rti);
|
||||
if (index >= 0)
|
||||
{
|
||||
ObjectGuid const guid = group->GetTargetIcon(index);
|
||||
if (!guid.IsEmpty())
|
||||
rtiTarget = botAI->GetUnit(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
|
||||
{
|
||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
|
||||
bool result = Attack(botAI->GetUnit(rtiTarget->GetGUID()));
|
||||
if (result)
|
||||
{
|
||||
context->GetValue<ObjectGuid>("pull target")->Set(rtiTarget->GetGUID());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
botAI->TellError("I dont see my rti attack target");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AttackRtiTargetAction::isUseful()
|
||||
{
|
||||
if (botAI->ContainsStrategy(STRATEGY_TYPE_HEAL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
93
src/Bot/BaseAi/Actions/ChooseTargetActions.h
Normal file
93
src/Bot/BaseAi/Actions/ChooseTargetActions.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHOOSETARGETACTIONS_H
|
||||
#define _PLAYERBOT_CHOOSETARGETACTIONS_H
|
||||
|
||||
#include "AttackAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class DpsAoeAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
DpsAoeAction(PlayerbotAI* botAI) : AttackAction(botAI, "dps aoe") {}
|
||||
|
||||
std::string const GetTargetName() override { return "dps aoe target"; }
|
||||
};
|
||||
|
||||
class DpsAssistAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
DpsAssistAction(PlayerbotAI* botAI) : AttackAction(botAI, "dps assist") {}
|
||||
|
||||
std::string const GetTargetName() override { return "dps target"; }
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class TankAssistAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
TankAssistAction(PlayerbotAI* botAI) : AttackAction(botAI, "tank assist") {}
|
||||
|
||||
std::string const GetTargetName() override { return "tank target"; }
|
||||
};
|
||||
|
||||
class AttackAnythingAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackAnythingAction(PlayerbotAI* botAI) : AttackAction(botAI, "attack anything") {}
|
||||
|
||||
std::string const GetTargetName() override { return "grind target"; }
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool isPossible() override;
|
||||
};
|
||||
|
||||
class AttackLeastHpTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackLeastHpTargetAction(PlayerbotAI* botAI) : AttackAction(botAI, "attack least hp target") {}
|
||||
|
||||
std::string const GetTargetName() override { return "least hp target"; }
|
||||
};
|
||||
|
||||
class AttackEnemyPlayerAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackEnemyPlayerAction(PlayerbotAI* botAI) : AttackAction(botAI, "attack enemy player") {}
|
||||
|
||||
std::string const GetTargetName() override { return "enemy player target"; }
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class AttackRtiTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackRtiTargetAction(PlayerbotAI* botAI) : AttackAction(botAI, "attack rti target") {}
|
||||
|
||||
std::string const GetTargetName() override { return "rti target"; }
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class AttackEnemyFlagCarrierAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackEnemyFlagCarrierAction(PlayerbotAI* botAI) : AttackAction(botAI, "attack enemy flag carrier") {}
|
||||
|
||||
std::string const GetTargetName() override { return "enemy flag carrier"; }
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class DropTargetAction : public Action
|
||||
{
|
||||
public:
|
||||
DropTargetAction(PlayerbotAI* botAI) : Action(botAI, "drop target") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
997
src/Bot/BaseAi/Actions/ChooseTravelTargetAction.cpp
Normal file
997
src/Bot/BaseAi/Actions/ChooseTravelTargetAction.cpp
Normal file
@@ -0,0 +1,997 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "ChooseTravelTargetAction.h"
|
||||
|
||||
#include "ChatHelper.h"
|
||||
#include "LootObjectStack.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool ChooseTravelTargetAction::Execute(Event event)
|
||||
{
|
||||
// Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); //not used, line marked for removal.
|
||||
|
||||
//Get the current travel target. This target is no longer active.
|
||||
TravelTarget* oldTarget = context->GetValue<TravelTarget*>("travel target")->Get();
|
||||
|
||||
//Select a new target to travel to.
|
||||
TravelTarget newTarget = TravelTarget(botAI);
|
||||
|
||||
if (!oldTarget) return false;
|
||||
|
||||
if (!oldTarget->isForced() || oldTarget->getStatus() == TravelStatus::TRAVEL_STATUS_EXPIRED)
|
||||
getNewTarget(&newTarget, oldTarget);
|
||||
else
|
||||
newTarget.copyTarget(oldTarget);
|
||||
|
||||
//If the new target is not active we failed.
|
||||
if (!newTarget.isActive() && !newTarget.isForced())
|
||||
return false;
|
||||
|
||||
setNewTarget(&newTarget, oldTarget);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Select a new travel target.
|
||||
// Currently this selectes mostly based on priority (current quest > new quest).
|
||||
// This works fine because destinations can be full (max 15 bots per quest giver, max 1 bot per quest mob).
|
||||
//
|
||||
// Eventually we want to rewrite this to be more intelligent.
|
||||
void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||
{
|
||||
// Join groups members
|
||||
bool foundTarget = foundTarget = SetGroupTarget(newTarget);
|
||||
|
||||
//Empty bags/repair
|
||||
if (!foundTarget && urand(1, 100) > 10 && bot->GetLevel() > 5) //90% chance
|
||||
{
|
||||
if (AI_VALUE2(bool, "group or", "should sell,can sell,following party,near leader") ||
|
||||
AI_VALUE2(bool, "group or", "should repair,can repair,following party,near leader")
|
||||
)
|
||||
{
|
||||
foundTarget = SetRpgTarget(newTarget); //Go to town to sell items or repair
|
||||
}
|
||||
}
|
||||
|
||||
//Rpg in city
|
||||
if (!foundTarget && urand(1, 100) > 90 && bot->GetLevel() > 5) //10% chance
|
||||
{
|
||||
foundTarget = SetNpcFlagTarget(newTarget, { UNIT_NPC_FLAG_BANKER,UNIT_NPC_FLAG_BATTLEMASTER,UNIT_NPC_FLAG_AUCTIONEER });
|
||||
}
|
||||
|
||||
// PvP activities
|
||||
bool pvpActivate = false;
|
||||
if (pvpActivate && !foundTarget && urand(0, 4) && bot->GetLevel() > 50)
|
||||
{
|
||||
WorldPosition pos = WorldPosition(bot);
|
||||
WorldPosition* botPos = &pos;
|
||||
TravelTarget* target = context->GetValue<TravelTarget*>("travel target")->Get();
|
||||
|
||||
TravelDestination* dest = ChooseTravelTargetAction::FindDestination(bot, "Tarren Mill");
|
||||
if (dest)
|
||||
{
|
||||
std::vector<WorldPosition*> points = dest->nextPoint(botPos, true);
|
||||
if (!points.empty())
|
||||
{
|
||||
target->setTarget(dest, points.front());
|
||||
target->setForced(true);
|
||||
|
||||
std::ostringstream out; out << "Traveling to " << dest->getTitle();
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
foundTarget = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Grind for money
|
||||
if (!foundTarget && AI_VALUE(bool, "should get money"))
|
||||
{
|
||||
//Empty mail for money
|
||||
//if (AI_VALUE(bool, "can get mail"))
|
||||
//{
|
||||
// foundTarget = SetGOTypeTarget(requester, newTarget, GAMEOBJECT_TYPE_MAILBOX, "", false); //Find a mailbox
|
||||
//}
|
||||
|
||||
if (!foundTarget)
|
||||
{
|
||||
// 50% Focus on active quests for money.
|
||||
if (urand(1, 100) > 50)
|
||||
{
|
||||
// 50% Focus on active quests for money.
|
||||
if (urand(1, 100) > 50)
|
||||
{
|
||||
// Turn in quests for money.
|
||||
foundTarget = SetQuestTarget(newTarget, true, false, true, true);
|
||||
}
|
||||
|
||||
if (!foundTarget)
|
||||
{
|
||||
// Find new (low) level quests
|
||||
foundTarget = SetQuestTarget(newTarget, false, true, false, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Go grind mobs for money
|
||||
foundTarget = SetGrindTarget(newTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Continue current target. 90% chance
|
||||
if (!foundTarget && urand(1, 100) > 10)
|
||||
{
|
||||
// Extend current target.
|
||||
foundTarget = SetCurrentTarget(newTarget, oldTarget);
|
||||
}
|
||||
|
||||
//Get mail 30% chance
|
||||
//if (!foundTarget && urand(1, 100) > 70)
|
||||
//{
|
||||
// if (AI_VALUE(bool, "can get mail"))
|
||||
// {
|
||||
// foundTarget = SetGOTypeTarget(requester, newTarget, GAMEOBJECT_TYPE_MAILBOX, "", false); //Find a mailbox
|
||||
// }
|
||||
//}
|
||||
|
||||
//Dungeon in group. 50% chance
|
||||
if (!foundTarget && urand(1, 100) > 50)
|
||||
{
|
||||
if (AI_VALUE(bool, "can fight boss"))
|
||||
{
|
||||
// Go fight a (dungeon boss)
|
||||
foundTarget = SetBossTarget(newTarget);
|
||||
}
|
||||
}
|
||||
|
||||
//Do quests (start, do, end) 95% chance
|
||||
if (!foundTarget && urand(1, 100) > 5)
|
||||
{
|
||||
// Do any nearby
|
||||
foundTarget = SetQuestTarget(newTarget, false, true, true, true);
|
||||
}
|
||||
|
||||
//Explore a nearby unexplored area.
|
||||
if (!foundTarget && botAI->HasStrategy("explore", BotState::BOT_STATE_NON_COMBAT) && urand(1, 100) > 90) //10% chance Explore a unexplored sub-zone.
|
||||
{
|
||||
foundTarget = SetExploreTarget(newTarget);
|
||||
}
|
||||
|
||||
//Just hang with an npc 50% chance
|
||||
if (!foundTarget && urand(1, 100) > 50)
|
||||
{
|
||||
foundTarget = SetRpgTarget(newTarget);
|
||||
if (foundTarget)
|
||||
newTarget->setForced(true);
|
||||
}
|
||||
|
||||
if (!foundTarget)
|
||||
{
|
||||
foundTarget = SetGrindTarget(newTarget);
|
||||
}
|
||||
|
||||
// Idle a bit.
|
||||
if (!foundTarget)
|
||||
SetNullTarget(newTarget);
|
||||
}
|
||||
|
||||
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||
{
|
||||
// Tell the master where we are going.
|
||||
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot))
|
||||
ReportTravelTarget(newTarget, oldTarget);
|
||||
|
||||
// If we are heading to a creature/npc clear it from the ignore list.
|
||||
if (oldTarget && oldTarget == newTarget && newTarget->getEntry())
|
||||
{
|
||||
GuidSet& ignoreList = context->GetValue<GuidSet&>("ignore rpg target")->Get();
|
||||
|
||||
for (ObjectGuid const guid : ignoreList)
|
||||
{
|
||||
if (guid.GetEntry() == newTarget->getEntry())
|
||||
{
|
||||
ignoreList.erase(guid);
|
||||
}
|
||||
}
|
||||
|
||||
context->GetValue<GuidSet&>("ignore rpg target")->Set(ignoreList);
|
||||
}
|
||||
|
||||
// Actually apply the new target to the travel target used by the bot.
|
||||
oldTarget->copyTarget(newTarget);
|
||||
|
||||
// If we are idling but have a master. Idle only 10 seconds.
|
||||
if (botAI->GetMaster() && oldTarget->isActive() &&
|
||||
oldTarget->getDestination()->getName() == "NullTravelDestination")
|
||||
oldTarget->setExpireIn(10 * IN_MILLISECONDS);
|
||||
else if (oldTarget->isForced()) // Make sure travel goes into cooldown after getting to the destination.
|
||||
oldTarget->setExpireIn(HOUR * IN_MILLISECONDS);
|
||||
|
||||
// Clear rpg and pull/grind target. We want to travel, not hang around some more.
|
||||
RESET_AI_VALUE(GuidPosition, "rpg target");
|
||||
RESET_AI_VALUE(ObjectGuid, "pull target");
|
||||
}
|
||||
|
||||
// Tell the master what travel target we are moving towards.
|
||||
// This should at some point be rewritten to be denser or perhaps logic moved to ->getTitle()
|
||||
void ChooseTravelTargetAction::ReportTravelTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||
{
|
||||
TravelDestination* destination = newTarget->getDestination();
|
||||
TravelDestination* oldDestination = oldTarget->getDestination();
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
if (newTarget->isForced())
|
||||
out << "(Forced) ";
|
||||
|
||||
if (destination->getName() == "QuestRelationTravelDestination" ||
|
||||
destination->getName() == "QuestObjectiveTravelDestination")
|
||||
{
|
||||
QuestTravelDestination* QuestDestination = (QuestTravelDestination*)destination;
|
||||
Quest const* quest = QuestDestination->GetQuestTemplate();
|
||||
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;
|
||||
|
||||
if (newTarget->isGroupCopy())
|
||||
out << "Following group ";
|
||||
else if (oldDestination && oldDestination == destination)
|
||||
out << "Continuing ";
|
||||
else
|
||||
out << "Traveling ";
|
||||
|
||||
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
|
||||
|
||||
out << " for " << chat->FormatQuest(quest);
|
||||
|
||||
out << " to " << QuestDestination->getTitle();
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
else if (destination->getName() == "RpgTravelDestination")
|
||||
{
|
||||
RpgTravelDestination* RpgDestination = (RpgTravelDestination*)destination;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
if (newTarget->isGroupCopy())
|
||||
out << "Following group ";
|
||||
else if (oldDestination && oldDestination == destination)
|
||||
out << "Continuing ";
|
||||
else
|
||||
out << "Traveling ";
|
||||
|
||||
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
|
||||
|
||||
out << " for ";
|
||||
|
||||
if (AI_VALUE2(bool, "group or", "should sell,can sell"))
|
||||
out << "selling items";
|
||||
else if (AI_VALUE2(bool, "group or", "should repair,can repair"))
|
||||
out << "repairing";
|
||||
else
|
||||
out << "rpg";
|
||||
|
||||
out << " to " << RpgDestination->getTitle();
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
else if (destination->getName() == "ExploreTravelDestination")
|
||||
{
|
||||
ExploreTravelDestination* ExploreDestination = (ExploreTravelDestination*)destination;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
if (newTarget->isGroupCopy())
|
||||
out << "Following group ";
|
||||
else if (oldDestination && oldDestination == destination)
|
||||
out << "Continuing ";
|
||||
else
|
||||
out << "Traveling ";
|
||||
|
||||
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
|
||||
|
||||
out << " for exploration";
|
||||
|
||||
out << " to " << ExploreDestination->getTitle();
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
else if (destination->getName() == "GrindTravelDestination")
|
||||
{
|
||||
GrindTravelDestination* GrindDestination = (GrindTravelDestination*)destination;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
if (newTarget->isGroupCopy())
|
||||
out << "Following group ";
|
||||
else if (oldDestination && oldDestination == destination)
|
||||
out << "Continuing ";
|
||||
else
|
||||
out << "Traveling ";
|
||||
|
||||
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
|
||||
|
||||
out << " for grinding money";
|
||||
|
||||
out << " to " << GrindDestination->getTitle();
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
else if (destination->getName() == "BossTravelDestination")
|
||||
{
|
||||
BossTravelDestination* BossDestination = (BossTravelDestination*)destination;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
if (newTarget->isGroupCopy())
|
||||
out << "Following group ";
|
||||
else if (oldDestination && oldDestination == destination)
|
||||
out << "Continuing ";
|
||||
else
|
||||
out << "Traveling ";
|
||||
|
||||
out << round(newTarget->getDestination()->distanceTo(&botLocation)) << "y";
|
||||
|
||||
out << " for good loot";
|
||||
|
||||
out << " to " << BossDestination->getTitle();
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
else if (destination->getName() == "NullTravelDestination")
|
||||
{
|
||||
if (!oldTarget->getDestination() || oldTarget->getDestination()->getName() != "NullTravelDestination")
|
||||
{
|
||||
botAI->TellMaster("No where to travel. Idling a bit.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::getBestDestination(std::vector<TravelDestination*>* activeDestinations,
|
||||
std::vector<WorldPosition*>* activePoints)
|
||||
{
|
||||
if (activeDestinations->empty() || activePoints->empty()) // No targets or no points.
|
||||
return false;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
std::vector<WorldPosition*> availablePoints =
|
||||
sTravelMgr->getNextPoint(&botLocation, *activePoints); // Pick a good point.
|
||||
|
||||
if (availablePoints.empty()) // No points available.
|
||||
return false;
|
||||
|
||||
TravelDestination* targetDestination;
|
||||
|
||||
for (auto activeTarget : *activeDestinations) // Pick the destination that has this point.
|
||||
if (activeTarget->distanceTo(availablePoints.front()) == 0)
|
||||
targetDestination = activeTarget;
|
||||
|
||||
if (!targetDestination)
|
||||
return false;
|
||||
|
||||
activeDestinations->clear();
|
||||
activePoints->clear();
|
||||
|
||||
activeDestinations->push_back(targetDestination);
|
||||
activePoints->push_back(availablePoints.front());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetGroupTarget(TravelTarget* target)
|
||||
{
|
||||
std::vector<TravelDestination*> activeDestinations;
|
||||
std::vector<WorldPosition*> activePoints;
|
||||
|
||||
GuidList groupPlayers;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
if (ref->GetSource() != bot)
|
||||
{
|
||||
if (ref->getSubGroup() != bot->GetSubGroup())
|
||||
{
|
||||
groupPlayers.push_back(ref->GetSource()->GetGUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
groupPlayers.push_front(ref->GetSource()->GetGUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find targets of the group.
|
||||
for (auto& member : groupPlayers)
|
||||
{
|
||||
Player* player = ObjectAccessor::FindPlayer(member);
|
||||
if (!player)
|
||||
continue;
|
||||
|
||||
PlayerbotAI* playerBotAI = GET_PLAYERBOT_AI(player);
|
||||
if (!playerBotAI)
|
||||
continue;
|
||||
|
||||
if (!playerBotAI->GetAiObjectContext())
|
||||
continue;
|
||||
|
||||
TravelTarget* groupTarget = playerBotAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
|
||||
|
||||
if (groupTarget->isGroupCopy())
|
||||
continue;
|
||||
|
||||
if (!groupTarget->isActive())
|
||||
continue;
|
||||
|
||||
if (!groupTarget->getDestination()->isActive(bot) ||
|
||||
groupTarget->getDestination()->getName() == "RpgTravelDestination")
|
||||
continue;
|
||||
|
||||
activeDestinations.push_back(groupTarget->getDestination());
|
||||
activePoints.push_back(groupTarget->getPosition());
|
||||
}
|
||||
|
||||
if (!getBestDestination(&activeDestinations, &activePoints))
|
||||
return false;
|
||||
|
||||
target->setTarget(activeDestinations.front(), activePoints.front(), true);
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetCurrentTarget(TravelTarget* target, TravelTarget* oldTarget)
|
||||
{
|
||||
TravelDestination* oldDestination = oldTarget->getDestination();
|
||||
|
||||
if (oldTarget->isMaxRetry(false))
|
||||
return false;
|
||||
|
||||
if (!oldDestination) // Does this target have a destination?
|
||||
return false;
|
||||
|
||||
if (!oldDestination->isActive(bot)) // Is the destination still valid?
|
||||
return false;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
std::vector<WorldPosition*> availablePoints = oldDestination->nextPoint(&botLocation);
|
||||
if (availablePoints.empty())
|
||||
return false;
|
||||
|
||||
target->setTarget(oldTarget->getDestination(), availablePoints.front());
|
||||
target->setStatus(TRAVEL_STATUS_TRAVEL);
|
||||
target->setRetry(false, oldTarget->getRetryCount(false) + 1);
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetQuestTarget(TravelTarget* target, bool onlyCompleted, bool newQuests, bool activeQuests, bool completedQuests)
|
||||
{
|
||||
std::vector<TravelDestination*> activeDestinations;
|
||||
std::vector<WorldPosition*> activePoints;
|
||||
WorldPosition botLocation(bot);
|
||||
bool onlyClassQuest = !urand(0, 10);
|
||||
|
||||
if (newQuests)
|
||||
{
|
||||
// Prefer new quests near the player at lower levels.
|
||||
activeDestinations = sTravelMgr->getQuestTravelDestinations(bot, -1, true, false, 400 + bot->GetLevel() * 10);
|
||||
}
|
||||
if (activeQuests || completedQuests)
|
||||
{
|
||||
QuestStatusMap& questMap = bot->getQuestStatusMap();
|
||||
//Find destinations related to the active quests.
|
||||
for (auto& quest : questMap)
|
||||
{
|
||||
if (bot->IsQuestRewarded(quest.first))
|
||||
continue;
|
||||
|
||||
uint32 questId = quest.first;
|
||||
// QuestStatusData* questStatus = &quest.second; //not used, line marked for removal.
|
||||
const auto questTemplate = sObjectMgr->GetQuestTemplate(questId);
|
||||
|
||||
if (!activeQuests && !bot->CanRewardQuest(questTemplate, false))
|
||||
continue;
|
||||
|
||||
if (!completedQuests && bot->CanRewardQuest(questTemplate, false))
|
||||
continue;
|
||||
|
||||
//Find quest takers or objectives
|
||||
std::vector<TravelDestination*> questDestinations = sTravelMgr->getQuestTravelDestinations(bot, questId, true, false, 0);
|
||||
|
||||
if (onlyClassQuest && activeDestinations.size() && questDestinations.size()) //Only do class quests if we have any.
|
||||
{
|
||||
if (activeDestinations.front()->GetQuestTemplate()->GetRequiredClasses() && !questTemplate->GetRequiredClasses())
|
||||
continue;
|
||||
|
||||
if (!activeDestinations.front()->GetQuestTemplate()->GetRequiredClasses() && questTemplate->GetRequiredClasses())
|
||||
activeDestinations.clear();
|
||||
}
|
||||
|
||||
activeDestinations.insert(activeDestinations.end(), questDestinations.begin(), questDestinations.end());
|
||||
}
|
||||
}
|
||||
if (newQuests && activeDestinations.empty())
|
||||
activeDestinations = sTravelMgr->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))
|
||||
botAI->TellMasterNoFacing(std::to_string(activeDestinations.size()) + " quest destinations found.");
|
||||
|
||||
if (!getBestDestination(&activeDestinations, &activePoints))
|
||||
return false;
|
||||
|
||||
target->setTarget(activeDestinations.front(), activePoints.front());
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetNewQuestTarget(TravelTarget* target)
|
||||
{
|
||||
std::vector<TravelDestination*> activeDestinations;
|
||||
std::vector<WorldPosition*> activePoints;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
// Find quest givers.
|
||||
std::vector<TravelDestination*> TravelDestinations =
|
||||
sTravelMgr->getQuestTravelDestinations(bot, -1, botAI->HasRealPlayerMaster());
|
||||
|
||||
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
|
||||
|
||||
// Pick one good point per destination.
|
||||
for (auto& activeTarget : activeDestinations)
|
||||
{
|
||||
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
|
||||
if (!points.empty())
|
||||
activePoints.push_back(points.front());
|
||||
}
|
||||
|
||||
if (!getBestDestination(&activeDestinations, &activePoints))
|
||||
return false;
|
||||
|
||||
target->setTarget(activeDestinations.front(), activePoints.front());
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetRpgTarget(TravelTarget* target)
|
||||
{
|
||||
std::vector<TravelDestination*> activeDestinations;
|
||||
std::vector<WorldPosition*> activePoints;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
// Find rpg npcs
|
||||
std::vector<TravelDestination*> TravelDestinations =
|
||||
sTravelMgr->getRpgTravelDestinations(bot, botAI->HasRealPlayerMaster());
|
||||
|
||||
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
|
||||
|
||||
// Pick one good point per destination.
|
||||
for (auto& activeTarget : activeDestinations)
|
||||
{
|
||||
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
|
||||
if (!points.empty())
|
||||
activePoints.push_back(points.front());
|
||||
}
|
||||
|
||||
if (!getBestDestination(&activeDestinations, &activePoints))
|
||||
return false;
|
||||
|
||||
target->setTarget(activeDestinations.front(), activePoints.front());
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetGrindTarget(TravelTarget* target)
|
||||
{
|
||||
std::vector<TravelDestination*> activeDestinations;
|
||||
std::vector<WorldPosition*> activePoints;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
// Find grind mobs.
|
||||
std::vector<TravelDestination*> TravelDestinations =
|
||||
sTravelMgr->getGrindTravelDestinations(bot, botAI->HasRealPlayerMaster());
|
||||
|
||||
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
|
||||
|
||||
// Pick one good point per destination.
|
||||
for (auto& activeTarget : activeDestinations)
|
||||
{
|
||||
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
|
||||
if (!points.empty())
|
||||
activePoints.push_back(points.front());
|
||||
}
|
||||
|
||||
if (!getBestDestination(&activeDestinations, &activePoints))
|
||||
return false;
|
||||
|
||||
target->setTarget(activeDestinations.front(), activePoints.front());
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetBossTarget(TravelTarget* target)
|
||||
{
|
||||
std::vector<TravelDestination*> activeDestinations;
|
||||
std::vector<WorldPosition*> activePoints;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
// Find boss mobs.
|
||||
std::vector<TravelDestination*> TravelDestinations =
|
||||
sTravelMgr->getBossTravelDestinations(bot, botAI->HasRealPlayerMaster());
|
||||
|
||||
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
|
||||
|
||||
// Pick one good point per destination.
|
||||
for (auto& activeTarget : activeDestinations)
|
||||
{
|
||||
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
|
||||
if (!points.empty())
|
||||
activePoints.push_back(points.front());
|
||||
}
|
||||
|
||||
if (!getBestDestination(&activeDestinations, &activePoints))
|
||||
return false;
|
||||
|
||||
target->setTarget(activeDestinations.front(), activePoints.front());
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetExploreTarget(TravelTarget* target)
|
||||
{
|
||||
std::vector<TravelDestination*> activeDestinations;
|
||||
std::vector<WorldPosition*> activePoints;
|
||||
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
// Find quest givers.
|
||||
std::vector<TravelDestination*> TravelDestinations = sTravelMgr->getExploreTravelDestinations(bot, true, true);
|
||||
|
||||
activeDestinations.insert(activeDestinations.end(), TravelDestinations.begin(), TravelDestinations.end());
|
||||
/*
|
||||
//Pick one good point per destination.
|
||||
for (auto& activeTarget : activeDestinations)
|
||||
{
|
||||
//271 south shore
|
||||
//35 booty bay
|
||||
//380 The Barrens The Crossroads
|
||||
if (((ExploreTravelDestination * )activeTarget)->getAreaId() == 380)
|
||||
{
|
||||
activePoints.push_back(activeTarget->getPoints(true)[0]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (activePoints.empty())
|
||||
{
|
||||
TravelDestinations = sTravelMgr->getExploreTravelDestinations(bot, botAI->HasRealPlayerMaster());
|
||||
|
||||
for (auto& activeTarget : activeDestinations)
|
||||
{
|
||||
std::vector<WorldPosition*> points = activeTarget->nextPoint(&botLocation);
|
||||
if (!points.empty())
|
||||
{
|
||||
activePoints.push_back(points.front());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!getBestDestination(&activeDestinations, &activePoints))
|
||||
return false;
|
||||
|
||||
target->setTarget(activeDestinations.front(), activePoints.front());
|
||||
|
||||
return target->isActive();
|
||||
}
|
||||
|
||||
char* strstri(char const* haystack, char const* needle);
|
||||
|
||||
bool ChooseTravelTargetAction::SetNpcFlagTarget(TravelTarget* target, std::vector<NPCFlags> flags,
|
||||
std::string const name, std::vector<uint32> items)
|
||||
{
|
||||
WorldPosition botPos(bot);
|
||||
|
||||
std::vector<TravelDestination*> dests;
|
||||
|
||||
for (auto& d : sTravelMgr->getRpgTravelDestinations(bot, true, true))
|
||||
{
|
||||
if (!d->getEntry())
|
||||
continue;
|
||||
|
||||
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(d->getEntry());
|
||||
if (!cInfo)
|
||||
continue;
|
||||
|
||||
bool foundFlag = false;
|
||||
for (auto flag : flags)
|
||||
if (cInfo->npcflag & flag)
|
||||
{
|
||||
foundFlag = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!foundFlag)
|
||||
continue;
|
||||
|
||||
if (!name.empty() && !strstri(cInfo->Name.c_str(), name.c_str()) &&
|
||||
!strstri(cInfo->SubName.c_str(), name.c_str()))
|
||||
continue;
|
||||
|
||||
if (!items.empty())
|
||||
{
|
||||
bool foundItem = false;
|
||||
VendorItemData const* vItems = sObjectMgr->GetNpcVendorItemList(d->getEntry());
|
||||
if (vItems)
|
||||
{
|
||||
for (auto item : items)
|
||||
{
|
||||
for (auto vitem : vItems->m_items)
|
||||
{
|
||||
if (vitem->item == item)
|
||||
{
|
||||
foundItem = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundItem)
|
||||
continue;
|
||||
}
|
||||
|
||||
FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction);
|
||||
ReputationRank reaction = Unit::GetFactionReactionTo(botAI->GetBot()->GetFactionTemplateEntry(), factionEntry);
|
||||
|
||||
if (reaction < REP_NEUTRAL)
|
||||
continue;
|
||||
|
||||
dests.push_back(d);
|
||||
}
|
||||
|
||||
if (!dests.empty())
|
||||
{
|
||||
TravelDestination* dest = *std::min_element(dests.begin(), dests.end(),
|
||||
[botPos](TravelDestination* i, TravelDestination* j) {
|
||||
return i->distanceTo(const_cast<WorldPosition*>(&botPos)) <
|
||||
j->distanceTo(const_cast<WorldPosition*>(&botPos));
|
||||
});
|
||||
|
||||
std::vector<WorldPosition*> points = dest->nextPoint(const_cast<WorldPosition*>(&botPos), true);
|
||||
if (points.empty())
|
||||
return false;
|
||||
|
||||
target->setTarget(dest, points.front());
|
||||
target->setForced(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<TravelDestination*> TravelMgr::getBossTravelDestinations(Player* bot, bool ignoreFull, bool ignoreInactive,
|
||||
float maxDistance)
|
||||
{
|
||||
WorldPosition botLocation(bot);
|
||||
|
||||
std::vector<TravelDestination*> retTravelLocations;
|
||||
|
||||
for (auto& dest : bossMobs)
|
||||
{
|
||||
if (!ignoreInactive && !dest->isActive(bot))
|
||||
continue;
|
||||
|
||||
if (dest->isFull(ignoreFull))
|
||||
continue;
|
||||
|
||||
if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
|
||||
continue;
|
||||
|
||||
retTravelLocations.push_back(dest);
|
||||
}
|
||||
|
||||
return retTravelLocations;
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::SetNullTarget(TravelTarget* target)
|
||||
{
|
||||
target->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(std::string const s, char delim);
|
||||
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)
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
|
||||
// AiObjectContext* context = botAI->GetAiObjectContext(); //not used, line marked for removal.
|
||||
|
||||
std::vector<TravelDestination*> dests;
|
||||
|
||||
//Quests
|
||||
if (quests)
|
||||
{
|
||||
for (auto& d : sTravelMgr->getQuestTravelDestinations(bot, 0, true, true))
|
||||
{
|
||||
if (strstri(d->getTitle().c_str(), name.c_str()))
|
||||
dests.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
//Zones
|
||||
if (zones)
|
||||
{
|
||||
for (auto& d : sTravelMgr->getExploreTravelDestinations(bot, true, true))
|
||||
{
|
||||
if (strstri(d->getTitle().c_str(), name.c_str()))
|
||||
dests.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
//Npcs
|
||||
if (npcs)
|
||||
{
|
||||
for (auto& d : sTravelMgr->getRpgTravelDestinations(bot, true, true))
|
||||
{
|
||||
if (strstri(d->getTitle().c_str(), name.c_str()))
|
||||
dests.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
//Mobs
|
||||
if (mobs)
|
||||
{
|
||||
for (auto& d : sTravelMgr->getGrindTravelDestinations(bot, true, true, 5000.0f))
|
||||
{
|
||||
if (strstri(d->getTitle().c_str(), name.c_str()))
|
||||
dests.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
//Bosses
|
||||
if (bosses)
|
||||
{
|
||||
for (auto& d : sTravelMgr->getBossTravelDestinations(bot, true, true))
|
||||
{
|
||||
if (strstri(d->getTitle().c_str(), name.c_str()))
|
||||
dests.push_back(d);
|
||||
}
|
||||
}
|
||||
|
||||
WorldPosition botPos(bot);
|
||||
|
||||
if (dests.empty())
|
||||
return nullptr;
|
||||
|
||||
return *std::min_element(dests.begin(), dests.end(),
|
||||
[botPos](TravelDestination* i, TravelDestination* j)
|
||||
{
|
||||
return i->distanceTo(const_cast<WorldPosition*>(&botPos)) < j->distanceTo(const_cast<WorldPosition*>(&botPos));
|
||||
});
|
||||
};
|
||||
|
||||
bool ChooseTravelTargetAction::isUseful()
|
||||
{
|
||||
if (!botAI->AllowActivity(TRAVEL_ACTIVITY))
|
||||
return false;
|
||||
|
||||
return !context->GetValue<TravelTarget*>("travel target")->Get()->isActive() &&
|
||||
!context->GetValue<LootObject>("loot target")->Get().IsLootPossible(bot) && !bot->IsInCombat();
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::needForQuest(Unit* target)
|
||||
{
|
||||
bool justCheck = (bot->GetGUID() == target->GetGUID());
|
||||
|
||||
QuestStatusMap& questMap = bot->getQuestStatusMap();
|
||||
for (auto& quest : questMap)
|
||||
{
|
||||
Quest const* questTemplate = sObjectMgr->GetQuestTemplate(quest.first);
|
||||
if (!questTemplate)
|
||||
continue;
|
||||
|
||||
uint32 questId = questTemplate->GetQuestId();
|
||||
|
||||
if (!questId)
|
||||
continue;
|
||||
|
||||
QuestStatus status = bot->GetQuestStatus(questId);
|
||||
|
||||
if ((status == QUEST_STATUS_COMPLETE && !bot->GetQuestRewardStatus(questId)))
|
||||
{
|
||||
if (!justCheck && !target->hasInvolvedQuest(questId))
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (status == QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
QuestStatusData questStatus = quest.second;
|
||||
|
||||
if (questTemplate->GetQuestLevel() > bot->GetLevel())
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < QUEST_OBJECTIVES_COUNT; j++)
|
||||
{
|
||||
int32 entry = questTemplate->RequiredNpcOrGo[j];
|
||||
|
||||
if (entry && entry > 0)
|
||||
{
|
||||
int required = questTemplate->RequiredNpcOrGoCount[j];
|
||||
int available = questStatus.CreatureOrGOCount[j];
|
||||
|
||||
if (required && available < required && (target->GetEntry() == entry || justCheck))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (justCheck)
|
||||
{
|
||||
int32 itemId = questTemplate->RequiredItemId[j];
|
||||
|
||||
if (itemId && itemId > 0)
|
||||
{
|
||||
int required = questTemplate->RequiredItemCount[j];
|
||||
int available = questStatus.ItemCount[j];
|
||||
|
||||
if (required && available < required)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!justCheck)
|
||||
{
|
||||
if (CreatureTemplate const* data = sObjectMgr->GetCreatureTemplate(target->GetEntry()))
|
||||
{
|
||||
if (uint32 lootId = data->lootid)
|
||||
{
|
||||
if (LootTemplates_Creature.HaveQuestLootForPlayer(lootId, bot))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChooseTravelTargetAction::needItemForQuest(uint32 itemId, const Quest* questTemplate,
|
||||
const QuestStatusData* questStatus)
|
||||
{
|
||||
for (uint32 i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
|
||||
{
|
||||
if (questTemplate->RequiredItemId[i] != itemId)
|
||||
continue;
|
||||
|
||||
uint32 required = questTemplate->RequiredItemCount[i];
|
||||
uint32 available = questStatus->ItemCount[i];
|
||||
|
||||
if (!required)
|
||||
continue;
|
||||
|
||||
return available < required;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
53
src/Bot/BaseAi/Actions/ChooseTravelTargetAction.h
Normal file
53
src/Bot/BaseAi/Actions/ChooseTravelTargetAction.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CHOOSETRAVELTARGETACTION_H
|
||||
#define _PLAYERBOT_CHOOSETRAVELTARGETACTION_H
|
||||
|
||||
#include "MovementActions.h"
|
||||
#include "TravelMgr.h"
|
||||
|
||||
class Quest;
|
||||
class PlayerbotAI;
|
||||
class Unit;
|
||||
|
||||
struct QuestStatusData;
|
||||
|
||||
class ChooseTravelTargetAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ChooseTravelTargetAction(PlayerbotAI* botAI, std::string const name = "choose travel target")
|
||||
: MovementAction(botAI, name)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
|
||||
static TravelDestination* FindDestination(Player* bot, std::string const name, bool zones = true, bool npcs = true, bool quests = true, bool mobs = true, bool bosses = true);
|
||||
|
||||
protected:
|
||||
void getNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget);
|
||||
void setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget);
|
||||
void ReportTravelTarget(TravelTarget* newTarget, TravelTarget* oldTarget);
|
||||
|
||||
bool getBestDestination(std::vector<TravelDestination*>* activeDestinations, std::vector<WorldPosition*>* activePoints);
|
||||
bool SetGroupTarget(TravelTarget* target);
|
||||
bool SetCurrentTarget(TravelTarget* target, TravelTarget* oldTarget);
|
||||
bool SetQuestTarget(TravelTarget* target, bool onlyCompleted = false, bool newQuests = true, bool activeQuests = true, bool completedQuests = true);
|
||||
bool SetNewQuestTarget(TravelTarget* target);
|
||||
bool SetRpgTarget(TravelTarget* target);
|
||||
bool SetGrindTarget(TravelTarget* target);
|
||||
bool SetBossTarget(TravelTarget* target);
|
||||
bool SetExploreTarget(TravelTarget* target);
|
||||
bool SetNpcFlagTarget(TravelTarget* target, std::vector<NPCFlags> flags, std::string const name = "", std::vector<uint32> items = { });
|
||||
bool SetNullTarget(TravelTarget* target);
|
||||
|
||||
private:
|
||||
virtual bool needForQuest(Unit* target);
|
||||
virtual bool needItemForQuest(uint32 itemId, Quest const* questTemplate, QuestStatusData const* questStatus);
|
||||
};
|
||||
|
||||
#endif
|
||||
55
src/Bot/BaseAi/Actions/CombatActions.cpp
Normal file
55
src/Bot/BaseAi/Actions/CombatActions.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CombatActions.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
bool SwitchToMeleeAction::Execute(Event event)
|
||||
{
|
||||
// botAI->TellMasterNoFacing("Switching to melee!");
|
||||
return ChangeCombatStrategyAction::Execute(event);
|
||||
}
|
||||
|
||||
bool SwitchToMeleeAction::isUseful()
|
||||
{
|
||||
if (bot->getClass() == CLASS_HUNTER)
|
||||
{
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
time_t lastFlee = AI_VALUE(LastMovement&, "last movement").lastFlee;
|
||||
return botAI->HasStrategy("ranged", BOT_STATE_COMBAT) &&
|
||||
((bot->IsInCombat() && target &&
|
||||
(target->GetVictim() == bot && (!bot->GetGroup() || lastFlee) &&
|
||||
sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "current target"), 8.0f))) ||
|
||||
(!bot->IsInCombat()));
|
||||
}
|
||||
|
||||
return botAI->HasStrategy("ranged", BOT_STATE_COMBAT);
|
||||
}
|
||||
|
||||
bool SwitchToRangedAction::Execute(Event event)
|
||||
{
|
||||
// botAI->TellMasterNoFacing("Switching to ranged!");
|
||||
return ChangeCombatStrategyAction::Execute(event);
|
||||
}
|
||||
|
||||
bool SwitchToRangedAction::isUseful()
|
||||
{
|
||||
if (bot->getClass() == CLASS_HUNTER)
|
||||
{
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
bool hasAmmo = AI_VALUE2(uint32, "item count", "ammo");
|
||||
return botAI->HasStrategy("close", BOT_STATE_COMBAT) && hasAmmo &&
|
||||
((bot->IsInCombat() && target &&
|
||||
((target->GetVictim() != bot || target->GetTarget() != bot->GetGUID()) ||
|
||||
sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "current target"), 8.0f))) ||
|
||||
(!bot->IsInCombat()));
|
||||
}
|
||||
|
||||
return botAI->HasStrategy("close", BOT_STATE_COMBAT);
|
||||
}
|
||||
31
src/Bot/BaseAi/Actions/CombatActions.h
Normal file
31
src/Bot/BaseAi/Actions/CombatActions.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_COMBATACTIONS_H
|
||||
#define _PLAYERBOT_COMBATACTIONS_H
|
||||
|
||||
#include "ChangeStrategyAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class SwitchToMeleeAction : public ChangeCombatStrategyAction
|
||||
{
|
||||
public:
|
||||
SwitchToMeleeAction(PlayerbotAI* botAI) : ChangeCombatStrategyAction(botAI, "-ranged,+close") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class SwitchToRangedAction : public ChangeCombatStrategyAction
|
||||
{
|
||||
public:
|
||||
SwitchToRangedAction(PlayerbotAI* botAI) : ChangeCombatStrategyAction(botAI, "-close,+ranged") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
144
src/Bot/BaseAi/Actions/CustomStrategyEditAction.cpp
Normal file
144
src/Bot/BaseAi/Actions/CustomStrategyEditAction.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "CustomStrategyEditAction.h"
|
||||
|
||||
#include "CustomStrategy.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool CustomStrategyEditAction::Execute(Event event)
|
||||
{
|
||||
std::string text = event.getParam();
|
||||
size_t pos = text.find(" ");
|
||||
if (pos == std::string::npos)
|
||||
return PrintHelp();
|
||||
|
||||
std::string const name = text.substr(0, pos);
|
||||
text = text.substr(pos + 1);
|
||||
|
||||
pos = text.find(" ");
|
||||
if (pos == std::string::npos)
|
||||
pos = text.size();
|
||||
|
||||
std::string const idx = text.substr(0, pos);
|
||||
text = pos >= text.size() ? "" : text.substr(pos + 1);
|
||||
|
||||
return idx == "?" ? Print(name) : Edit(name, atoi(idx.c_str()), text);
|
||||
}
|
||||
|
||||
bool CustomStrategyEditAction::PrintHelp()
|
||||
{
|
||||
botAI->TellMaster("=== Custom strategies ===");
|
||||
|
||||
uint32 owner = botAI->GetBot()->GetGUID().GetCounter();
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER);
|
||||
stmt->SetData(0, owner);
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
std::string const name = fields[0].Get<std::string>();
|
||||
botAI->TellMaster(name);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
botAI->TellMaster("Usage: cs <name> <idx> <command>");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CustomStrategyEditAction::Print(std::string const name)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "=== " << name << " ===";
|
||||
botAI->TellMaster(out.str());
|
||||
|
||||
uint32 owner = botAI->GetBot()->GetGUID().GetCounter();
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER_AND_NAME);
|
||||
stmt->SetData(0, owner);
|
||||
stmt->SetData(1, name);
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 idx = fields[0].Get<uint32>();
|
||||
std::string const action = fields[1].Get<std::string>();
|
||||
|
||||
PrintActionLine(idx, action);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CustomStrategyEditAction::Edit(std::string const name, uint32 idx, std::string const command)
|
||||
{
|
||||
uint32 owner = botAI->GetBot()->GetGUID().GetCounter();
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER_AND_NAME_AND_IDX);
|
||||
stmt->SetData(0, owner);
|
||||
stmt->SetData(1, name);
|
||||
stmt->SetData(2, idx);
|
||||
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
|
||||
{
|
||||
if (command.empty())
|
||||
{
|
||||
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_CUSTOM_STRATEGY);
|
||||
stmt->SetData(0, name);
|
||||
stmt->SetData(1, owner);
|
||||
stmt->SetData(2, idx);
|
||||
PlayerbotsDatabase.Execute(stmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_UPD_CUSTOM_STRATEGY);
|
||||
stmt->SetData(0, command);
|
||||
stmt->SetData(1, name);
|
||||
stmt->SetData(2, owner);
|
||||
stmt->SetData(3, idx);
|
||||
PlayerbotsDatabase.Execute(stmt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_CUSTOM_STRATEGY);
|
||||
stmt->SetData(0, name);
|
||||
stmt->SetData(1, owner);
|
||||
stmt->SetData(2, idx);
|
||||
stmt->SetData(3, command);
|
||||
PlayerbotsDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
PrintActionLine(idx, command);
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << "custom::" << name;
|
||||
|
||||
if (Strategy* strategy = botAI->GetAiObjectContext()->GetStrategy(ss.str()))
|
||||
{
|
||||
if (CustomStrategy* cs = dynamic_cast<CustomStrategy*>(strategy))
|
||||
{
|
||||
cs->Reset();
|
||||
botAI->ReInitCurrentEngine();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CustomStrategyEditAction::PrintActionLine(uint32 idx, std::string const command)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "#" << idx << " " << command;
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
27
src/Bot/BaseAi/Actions/CustomStrategyEditAction.h
Normal file
27
src/Bot/BaseAi/Actions/CustomStrategyEditAction.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_CUSTOMSTRATEGYEDITACTION_H
|
||||
#define _PLAYERBOT_CUSTOMSTRATEGYEDITACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class CustomStrategyEditAction : public Action
|
||||
{
|
||||
public:
|
||||
CustomStrategyEditAction(PlayerbotAI* botAI) : Action(botAI, "cs") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
bool PrintHelp();
|
||||
bool PrintActionLine(uint32 idx, std::string const command);
|
||||
bool Print(std::string const name);
|
||||
bool Edit(std::string const name, uint32 idx, std::string const command);
|
||||
};
|
||||
|
||||
#endif
|
||||
1053
src/Bot/BaseAi/Actions/DebugAction.cpp
Normal file
1053
src/Bot/BaseAi/Actions/DebugAction.cpp
Normal file
File diff suppressed because it is too large
Load Diff
29
src/Bot/BaseAi/Actions/DebugAction.h
Normal file
29
src/Bot/BaseAi/Actions/DebugAction.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_DEBUGACTION_H
|
||||
#define _PLAYERBOT_DEBUGACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "TravelMgr.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
class Unit;
|
||||
|
||||
class DebugAction : public Action
|
||||
{
|
||||
public:
|
||||
DebugAction(PlayerbotAI* botAI) : Action(botAI, "Debug") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
void FakeSpell(uint32 spellId, Unit* truecaster, Unit* caster, ObjectGuid target = ObjectGuid::Empty,
|
||||
GuidVector otherTargets = {}, GuidVector missTargets = {}, WorldPosition source = WorldPosition(),
|
||||
WorldPosition dest = WorldPosition(), bool forceDest = false);
|
||||
void addAura(uint32 spellId, Unit* target);
|
||||
};
|
||||
|
||||
#endif
|
||||
20
src/Bot/BaseAi/Actions/DelayAction.cpp
Normal file
20
src/Bot/BaseAi/Actions/DelayAction.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "DelayAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool DelayAction::Execute(Event event)
|
||||
{
|
||||
uint32 delay = sPlayerbotAIConfig->passiveDelay + sPlayerbotAIConfig->globalCoolDown;
|
||||
|
||||
botAI->SetNextCheckDelay(delay);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DelayAction::isUseful() { return !botAI->AllowActivity(ALL_ACTIVITY); }
|
||||
22
src/Bot/BaseAi/Actions/DelayAction.h
Normal file
22
src/Bot/BaseAi/Actions/DelayAction.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_DELAYACTION_H
|
||||
#define _PLAYERBOT_DELAYACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class DelayAction : public Action
|
||||
{
|
||||
public:
|
||||
DelayAction(PlayerbotAI* botAI) : Action(botAI, "delay") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
106
src/Bot/BaseAi/Actions/DestroyItemAction.cpp
Normal file
106
src/Bot/BaseAi/Actions/DestroyItemAction.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "DestroyItemAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool DestroyItemAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
ItemIds ids = chat->parseItems(text);
|
||||
|
||||
for (ItemIds::iterator i = ids.begin(); i != ids.end(); i++)
|
||||
{
|
||||
FindItemByIdVisitor visitor(*i);
|
||||
DestroyItem(&visitor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DestroyItemAction::DestroyItem(FindItemVisitor* visitor)
|
||||
{
|
||||
IterateItems(visitor);
|
||||
std::vector<Item*> items = visitor->GetResult();
|
||||
for (Item* item : items)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << chat->FormatItem(item->GetTemplate()) << " destroyed";
|
||||
botAI->TellMaster(out);
|
||||
|
||||
bot->DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
|
||||
}
|
||||
}
|
||||
|
||||
bool SmartDestroyItemAction::isUseful() { return !botAI->HasActivePlayerMaster(); }
|
||||
|
||||
bool SmartDestroyItemAction::Execute(Event event)
|
||||
{
|
||||
uint8 bagSpace = AI_VALUE(uint8, "bag space");
|
||||
|
||||
if (bagSpace < 90)
|
||||
return false;
|
||||
|
||||
// only destoy grey items if with real player/guild
|
||||
if (botAI->HasRealPlayerMaster() && botAI->IsInRealGuild())
|
||||
{
|
||||
std::set<Item*> items;
|
||||
FindItemsToTradeByQualityVisitor visitor(ITEM_QUALITY_POOR, 5);
|
||||
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
|
||||
items.insert(visitor.GetResult().begin(), visitor.GetResult().end());
|
||||
|
||||
for (auto& item : items)
|
||||
{
|
||||
FindItemByIdVisitor visitor(item->GetTemplate()->ItemId);
|
||||
DestroyItem(&visitor);
|
||||
|
||||
bagSpace = AI_VALUE(uint8, "bag space");
|
||||
|
||||
if (bagSpace < 90)
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint32> bestToDestroy = {ITEM_USAGE_NONE}; // First destroy anything useless.
|
||||
|
||||
if (!AI_VALUE(bool, "can sell") &&
|
||||
AI_VALUE(
|
||||
bool,
|
||||
"should get money")) // We need money so quest items are less important since they can't directly be sold.
|
||||
bestToDestroy.push_back(ITEM_USAGE_QUEST);
|
||||
else // We don't need money so destroy the cheapest stuff.
|
||||
{
|
||||
bestToDestroy.push_back(ITEM_USAGE_VENDOR);
|
||||
bestToDestroy.push_back(ITEM_USAGE_AH);
|
||||
}
|
||||
|
||||
// If we still need room
|
||||
bestToDestroy.push_back(
|
||||
ITEM_USAGE_SKILL); // Items that might help tradeskill are more important than above but still expenable.
|
||||
bestToDestroy.push_back(ITEM_USAGE_USE); // These are more likely to be usefull 'soon' but still expenable.
|
||||
|
||||
for (auto& usage : bestToDestroy)
|
||||
{
|
||||
std::vector<Item*> items = AI_VALUE2(std::vector<Item*>, "inventory items", "usage " + std::to_string(usage));
|
||||
std::reverse(items.begin(), items.end());
|
||||
|
||||
for (auto& item : items)
|
||||
{
|
||||
FindItemByIdVisitor visitor(item->GetTemplate()->ItemId);
|
||||
DestroyItem(&visitor);
|
||||
|
||||
bagSpace = AI_VALUE(uint8, "bag space");
|
||||
|
||||
if (bagSpace < 90)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
34
src/Bot/BaseAi/Actions/DestroyItemAction.h
Normal file
34
src/Bot/BaseAi/Actions/DestroyItemAction.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_DESTROYITEMACTION_H
|
||||
#define _PLAYERBOT_DESTROYITEMACTION_H
|
||||
|
||||
#include "InventoryAction.h"
|
||||
|
||||
class FindItemVisitor;
|
||||
class PlayerbotAI;
|
||||
|
||||
class DestroyItemAction : public InventoryAction
|
||||
{
|
||||
public:
|
||||
DestroyItemAction(PlayerbotAI* botAI, std::string const name = "destroy") : InventoryAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
void DestroyItem(FindItemVisitor* visitor);
|
||||
};
|
||||
|
||||
class SmartDestroyItemAction : public DestroyItemAction
|
||||
{
|
||||
public:
|
||||
SmartDestroyItemAction(PlayerbotAI* botAI) : DestroyItemAction(botAI, "smart destroy") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
276
src/Bot/BaseAi/Actions/DropQuestAction.cpp
Normal file
276
src/Bot/BaseAi/Actions/DropQuestAction.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "DropQuestAction.h"
|
||||
|
||||
#include "ChatHelper.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool DropQuestAction::Execute(Event event)
|
||||
{
|
||||
std::string const link = event.getParam();
|
||||
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
PlayerbotChatHandler handler(master);
|
||||
uint32 entry = handler.extractQuestId(link);
|
||||
|
||||
// remove all quest entries for 'entry' from quest log
|
||||
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
|
||||
{
|
||||
uint32 logQuest = bot->GetQuestSlotQuestId(slot);
|
||||
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(logQuest);
|
||||
if (!quest)
|
||||
continue;
|
||||
|
||||
if (logQuest == entry || link.find(quest->GetTitle()) != std::string::npos)
|
||||
{
|
||||
bot->SetQuestSlot(slot, 0);
|
||||
|
||||
// we ignore unequippable quest items in this case, its' still be equipped
|
||||
bot->TakeQuestSourceItem(logQuest, false);
|
||||
entry = logQuest;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entry)
|
||||
return false;
|
||||
|
||||
bot->RemoveRewardedQuest(entry);
|
||||
bot->RemoveActiveQuest(entry, false);
|
||||
|
||||
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry);
|
||||
const std::string text_quest = ChatHelper::FormatQuest(pQuest);
|
||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), pQuest->GetTitle());
|
||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
||||
}
|
||||
|
||||
botAI->TellMaster("Quest removed");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CleanQuestLogAction::Execute(Event event)
|
||||
{
|
||||
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
|
||||
if (!requester)
|
||||
{
|
||||
botAI->TellMaster("No event owner detected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sPlayerbotAIConfig->dropObsoleteQuests)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only output this message if "debug rpg" strategy is enabled
|
||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests...");
|
||||
}
|
||||
|
||||
uint8 botLevel = bot->GetLevel(); // Get bot's level
|
||||
uint8 numQuest = 0;
|
||||
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
|
||||
{
|
||||
if (bot->GetQuestSlotQuestId(slot))
|
||||
{
|
||||
numQuest++;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
|
||||
{
|
||||
uint32 questId = bot->GetQuestSlotQuestId(slot);
|
||||
if (!questId)
|
||||
continue;
|
||||
|
||||
const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (!quest)
|
||||
continue;
|
||||
|
||||
// Determine if quest is trivial by comparing levels
|
||||
int32 questLevel = quest->GetQuestLevel();
|
||||
if (questLevel == -1) // For scaling quests, default to bot level
|
||||
{
|
||||
questLevel = botLevel;
|
||||
}
|
||||
|
||||
// Set the level difference for when a quest becomes trivial
|
||||
// This was determined by using the Lua code the client uses
|
||||
int32 trivialLevel = 5;
|
||||
if (botLevel >= 40)
|
||||
{
|
||||
trivialLevel = 8;
|
||||
}
|
||||
else if (botLevel >= 30)
|
||||
{
|
||||
trivialLevel = 7;
|
||||
}
|
||||
else if (botLevel >= 20)
|
||||
{
|
||||
trivialLevel = 6;
|
||||
}
|
||||
|
||||
// Check if the quest is trivial (grey) for the bot
|
||||
if ((botLevel - questLevel) > trivialLevel)
|
||||
{
|
||||
// Output only if "debug rpg" strategy is enabled
|
||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey).");
|
||||
}
|
||||
|
||||
// Remove quest
|
||||
botAI->rpgStatistic.questDropped++;
|
||||
bot->SetQuestSlot(slot, 0);
|
||||
bot->TakeQuestSourceItem(questId, false);
|
||||
bot->SetQuestStatus(questId, QUEST_STATUS_NONE);
|
||||
bot->RemoveRewardedQuest(questId);
|
||||
|
||||
numQuest--;
|
||||
|
||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
||||
}
|
||||
|
||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only output if "debug rpg" strategy is enabled
|
||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete)
|
||||
{
|
||||
std::vector<uint8> slots;
|
||||
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
|
||||
slots.push_back(slot);
|
||||
|
||||
if (wantNum < 100)
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
|
||||
std::shuffle(slots.begin(), slots.end(), g);
|
||||
}
|
||||
|
||||
for (uint8 slot : slots)
|
||||
{
|
||||
uint32 questId = bot->GetQuestSlotQuestId(slot);
|
||||
if (!questId)
|
||||
continue;
|
||||
|
||||
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
|
||||
if (!quest)
|
||||
continue;
|
||||
|
||||
// Do not drop class quest, may be not rewarding gold but important spells
|
||||
if (quest->GetRequiredClasses())
|
||||
continue;
|
||||
|
||||
if (wantNum == 100)
|
||||
numQuest++;
|
||||
|
||||
int32 lowLevelDiff = sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF);
|
||||
if (lowLevelDiff < 0 ||
|
||||
bot->GetLevel() <= bot->GetQuestLevel(quest) + uint32(lowLevelDiff)) // Quest is not gray
|
||||
{
|
||||
if (bot->GetLevel() + 5 > bot->GetQuestLevel(quest)) // Quest is not red
|
||||
if (!isGreen)
|
||||
continue;
|
||||
}
|
||||
else // Quest is gray
|
||||
{
|
||||
if (isGreen)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HasProgress(bot, quest) && !hasProgress && bot->GetQuestStatus(questId) != QUEST_STATUS_FAILED)
|
||||
continue;
|
||||
|
||||
if (bot->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE && !isComplete)
|
||||
continue;
|
||||
|
||||
// Always drop failed quests
|
||||
if (numQuest <= wantNum && bot->GetQuestStatus(questId) != QUEST_STATUS_FAILED)
|
||||
continue;
|
||||
|
||||
// Drop quest.
|
||||
bot->SetQuestSlot(slot, 0);
|
||||
|
||||
// We ignore unequippable quest items in this case, its' still be equipped
|
||||
bot->TakeQuestSourceItem(questId, false);
|
||||
|
||||
bot->SetQuestStatus(questId, QUEST_STATUS_NONE);
|
||||
bot->RemoveRewardedQuest(questId);
|
||||
|
||||
numQuest--;
|
||||
|
||||
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
{
|
||||
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
||||
}
|
||||
botAI->TellMaster("Quest removed" + chat->FormatQuest(quest));
|
||||
}
|
||||
}
|
||||
|
||||
bool CleanQuestLogAction::HasProgress(Player* bot, Quest const* quest)
|
||||
{
|
||||
uint32 questId = quest->GetQuestId();
|
||||
|
||||
if (bot->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE)
|
||||
return true;
|
||||
|
||||
QuestStatusData questStatus = bot->getQuestStatusMap()[questId];
|
||||
|
||||
for (uint32 i = 0; i < QUEST_OBJECTIVES_COUNT; i++)
|
||||
{
|
||||
if (!quest->ObjectiveText[i].empty())
|
||||
return true;
|
||||
|
||||
if (quest->RequiredItemId[i])
|
||||
{
|
||||
int required = quest->RequiredItemCount[i];
|
||||
int available = questStatus.ItemCount[i];
|
||||
if (available > 0 && required > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (quest->RequiredNpcOrGo[i])
|
||||
{
|
||||
int required = quest->RequiredNpcOrGoCount[i];
|
||||
int available = questStatus.CreatureOrGOCount[i];
|
||||
|
||||
if (available > 0 && required > 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
35
src/Bot/BaseAi/Actions/DropQuestAction.h
Normal file
35
src/Bot/BaseAi/Actions/DropQuestAction.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_DROPQUESTACTION_H
|
||||
#define _PLAYERBOT_DROPQUESTACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class Player;
|
||||
class PlayerbotAI;
|
||||
class Quest;
|
||||
|
||||
class DropQuestAction : public Action
|
||||
{
|
||||
public:
|
||||
DropQuestAction(PlayerbotAI* botAI) : Action(botAI, "drop quest") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class CleanQuestLogAction : public Action
|
||||
{
|
||||
public:
|
||||
CleanQuestLogAction(PlayerbotAI* botAI) : Action(botAI, "clean quest log") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
void DropQuestType(uint8& numQuest, uint8 wantNum = 100, bool isGreen = false, bool hasProgress = false,
|
||||
bool isComplete = false);
|
||||
|
||||
static bool HasProgress(Player* bot, Quest const* quest);
|
||||
};
|
||||
|
||||
#endif
|
||||
1441
src/Bot/BaseAi/Actions/EmoteAction.cpp
Normal file
1441
src/Bot/BaseAi/Actions/EmoteAction.cpp
Normal file
File diff suppressed because it is too large
Load Diff
54
src/Bot/BaseAi/Actions/EmoteAction.h
Normal file
54
src/Bot/BaseAi/Actions/EmoteAction.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_EMOTEACTION_H
|
||||
#define _PLAYERBOT_EMOTEACTION_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
|
||||
class Player;
|
||||
class PlayerbotAI;
|
||||
class Unit;
|
||||
|
||||
enum TextEmotes : uint32;
|
||||
|
||||
class EmoteActionBase : public Action
|
||||
{
|
||||
public:
|
||||
EmoteActionBase(PlayerbotAI* botAI, std::string const name);
|
||||
|
||||
static uint32 GetNumberOfEmoteVariants(TextEmotes emote, uint8 race, uint8 gender);
|
||||
|
||||
protected:
|
||||
bool Emote(Unit* target, uint32 type, bool textEmote = false);
|
||||
bool ReceiveEmote(Player* source, uint32 emote, bool verbal = false);
|
||||
Unit* GetTarget();
|
||||
void InitEmotes();
|
||||
static std::map<std::string, uint32> emotes;
|
||||
static std::map<std::string, uint32> textEmotes;
|
||||
};
|
||||
|
||||
class EmoteAction : public EmoteActionBase, public Qualified
|
||||
{
|
||||
public:
|
||||
EmoteAction(PlayerbotAI* botAI);
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class TalkAction : public EmoteActionBase
|
||||
{
|
||||
public:
|
||||
TalkAction(PlayerbotAI* botAI) : EmoteActionBase(botAI, "talk") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
static uint32 GetRandomEmote(Unit* unit, bool textEmote = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
432
src/Bot/BaseAi/Actions/EquipAction.cpp
Normal file
432
src/Bot/BaseAi/Actions/EquipAction.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "EquipAction.h"
|
||||
#include <utility>
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "ItemUsageValue.h"
|
||||
#include "ItemVisitors.h"
|
||||
#include "Playerbots.h"
|
||||
#include "StatsWeightCalculator.h"
|
||||
#include "ItemPackets.h"
|
||||
|
||||
bool EquipAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
ItemIds ids = chat->parseItems(text);
|
||||
EquipItems(ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EquipAction::EquipItems(ItemIds ids)
|
||||
{
|
||||
for (ItemIds::iterator i = ids.begin(); i != ids.end(); i++)
|
||||
{
|
||||
FindItemByIdVisitor visitor(*i);
|
||||
EquipItem(&visitor);
|
||||
}
|
||||
}
|
||||
|
||||
// Return bagslot with smalest bag.
|
||||
uint8 EquipAction::GetSmallestBagSlot()
|
||||
{
|
||||
int8 curBag = 0;
|
||||
uint32 curSlots = 0;
|
||||
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||
{
|
||||
const Bag* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
|
||||
if (pBag)
|
||||
{
|
||||
if (curBag > 0 && curSlots < pBag->GetBagSize())
|
||||
continue;
|
||||
|
||||
curBag = bag;
|
||||
curSlots = pBag->GetBagSize();
|
||||
}
|
||||
else
|
||||
return bag;
|
||||
}
|
||||
|
||||
return curBag;
|
||||
}
|
||||
|
||||
void EquipAction::EquipItem(FindItemVisitor* visitor)
|
||||
{
|
||||
IterateItems(visitor);
|
||||
std::vector<Item*> items = visitor->GetResult();
|
||||
if (!items.empty())
|
||||
EquipItem(*items.begin());
|
||||
}
|
||||
|
||||
void EquipAction::EquipItem(Item* item)
|
||||
{
|
||||
uint8 bagIndex = item->GetBagSlot();
|
||||
uint8 slot = item->GetSlot();
|
||||
const ItemTemplate* itemProto = item->GetTemplate();
|
||||
uint32 itemId = itemProto->ItemId;
|
||||
uint8 invType = itemProto->InventoryType;
|
||||
|
||||
// Handle ammunition separately
|
||||
if (invType == INVTYPE_AMMO)
|
||||
{
|
||||
bot->SetAmmo(itemId);
|
||||
std::ostringstream out;
|
||||
out << "equipping " << chat->FormatItem(itemProto);
|
||||
botAI->TellMaster(out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle bags first
|
||||
bool equippedBag = false;
|
||||
if (itemProto->Class == ITEM_CLASS_CONTAINER)
|
||||
{
|
||||
// Attempt to equip as a bag
|
||||
Bag* pBag = reinterpret_cast<Bag*>(item);
|
||||
uint8 newBagSlot = GetSmallestBagSlot();
|
||||
if (newBagSlot > 0)
|
||||
{
|
||||
uint16 src = ((bagIndex << 8) | slot);
|
||||
uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | newBagSlot);
|
||||
bot->SwapItem(src, dst);
|
||||
equippedBag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't equip as a bag, try to equip as gear
|
||||
if (!equippedBag)
|
||||
{
|
||||
// Ranged weapons aren't handled by the rest of the weapon equip logic
|
||||
// Handle them early here to avoid issues.
|
||||
if (invType == INVTYPE_RANGED || invType == INVTYPE_THROWN || invType == INVTYPE_RANGEDRIGHT)
|
||||
{
|
||||
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid itemguid = item->GetGUID();
|
||||
packet << itemguid << uint8(EQUIPMENT_SLOT_RANGED);
|
||||
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in ranged slot";
|
||||
botAI->TellMaster(out);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
|
||||
|
||||
// Check if the item is a weapon and whether the bot can dual wield or use Titan Grip
|
||||
bool isWeapon = (itemProto->Class == ITEM_CLASS_WEAPON);
|
||||
bool canTitanGrip = bot->CanTitanGrip();
|
||||
bool canDualWield = bot->CanDualWield();
|
||||
|
||||
bool isTwoHander = (invType == INVTYPE_2HWEAPON);
|
||||
bool isValidTGWeapon = false;
|
||||
if (canTitanGrip && isTwoHander)
|
||||
{
|
||||
// Titan Grip-valid 2H weapon subclasses: Axe2, Mace2, Sword2
|
||||
isValidTGWeapon = (itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
|
||||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
|
||||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
|
||||
}
|
||||
|
||||
// Check if the main hand currently has a 2H weapon equipped
|
||||
Item* currentMHItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
bool have2HWeaponEquipped = (currentMHItem && currentMHItem->GetTemplate()->InventoryType == INVTYPE_2HWEAPON);
|
||||
|
||||
// bool canDualWieldOrTG = (canDualWield || (canTitanGrip && isTwoHander));
|
||||
bool canDualWieldOrTG = (canDualWield || isTwoHander);
|
||||
|
||||
// If this is a weapon and we can dual wield or Titan Grip, check if we can improve main/off-hand setup
|
||||
if (isWeapon && canDualWieldOrTG)
|
||||
{
|
||||
// Fetch current main hand and offhand items
|
||||
Item* mainHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
Item* offHandItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
||||
|
||||
// Set up the stats calculator once and reuse results for performance
|
||||
StatsWeightCalculator calculator(bot);
|
||||
calculator.SetItemSetBonus(false);
|
||||
calculator.SetOverflowPenalty(false);
|
||||
|
||||
// Calculate item scores once and store them
|
||||
float newItemScore = calculator.CalculateItem(itemId);
|
||||
float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f;
|
||||
float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f;
|
||||
|
||||
// Determine where this weapon can go
|
||||
bool canGoMain = (invType == INVTYPE_WEAPON ||
|
||||
invType == INVTYPE_WEAPONMAINHAND ||
|
||||
isTwoHander);
|
||||
|
||||
bool canTGOff = false;
|
||||
if (canTitanGrip && isTwoHander && isValidTGWeapon)
|
||||
canTGOff = true;
|
||||
|
||||
bool canGoOff = (invType == INVTYPE_WEAPON ||
|
||||
invType == INVTYPE_WEAPONOFFHAND ||
|
||||
canTGOff);
|
||||
|
||||
// Check if the main hand item can go to offhand if needed
|
||||
bool mainHandCanGoOff = false;
|
||||
if (mainHandItem)
|
||||
{
|
||||
const ItemTemplate* mhProto = mainHandItem->GetTemplate();
|
||||
bool mhIsValidTG = false;
|
||||
if (canTitanGrip && mhProto->InventoryType == INVTYPE_2HWEAPON)
|
||||
{
|
||||
mhIsValidTG = (mhProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
|
||||
mhProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
|
||||
mhProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
|
||||
}
|
||||
|
||||
mainHandCanGoOff = (mhProto->InventoryType == INVTYPE_WEAPON ||
|
||||
mhProto->InventoryType == INVTYPE_WEAPONOFFHAND ||
|
||||
(mhProto->InventoryType == INVTYPE_2HWEAPON && mhIsValidTG));
|
||||
}
|
||||
|
||||
// Priority 1: Replace main hand if the new weapon is strictly better
|
||||
// and if conditions allow (e.g. no conflicting 2H logic)
|
||||
bool betterThanMH = (newItemScore > mainHandScore);
|
||||
// If a one-handed weapon is better, we can still use it instead of a two-handed weapon
|
||||
bool mhConditionOK = (invType != INVTYPE_2HWEAPON ||
|
||||
(isTwoHander && !canTitanGrip) ||
|
||||
(canTitanGrip && isValidTGWeapon));
|
||||
|
||||
if (canGoMain && betterThanMH && mhConditionOK)
|
||||
{
|
||||
// Equip new weapon in main hand
|
||||
{
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid newItemGuid = item->GetGUID();
|
||||
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_MAINHAND);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
}
|
||||
|
||||
// Try moving old main hand weapon to offhand if beneficial
|
||||
if (mainHandItem && mainHandCanGoOff && (!offHandItem || mainHandScore > offHandScore))
|
||||
{
|
||||
const ItemTemplate* oldMHProto = mainHandItem->GetTemplate();
|
||||
|
||||
WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid oldMHGuid = mainHandItem->GetGUID();
|
||||
offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(offhandPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream moveMsg;
|
||||
moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand";
|
||||
botAI->TellMaster(moveMsg);
|
||||
}
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in main hand";
|
||||
botAI->TellMaster(out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Priority 2: If not better than main hand, check if better than offhand
|
||||
else if (canGoOff && newItemScore > offHandScore)
|
||||
{
|
||||
// Equip in offhand
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid newItemGuid = item->GetGUID();
|
||||
eqPacket << newItemGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in offhand";
|
||||
botAI->TellMaster(out);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No improvement, do nothing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If not a special dual-wield/TG scenario or no improvement found, fall back to original logic
|
||||
if (dstSlot == EQUIPMENT_SLOT_FINGER1 ||
|
||||
dstSlot == EQUIPMENT_SLOT_TRINKET1 ||
|
||||
(dstSlot == EQUIPMENT_SLOT_MAINHAND && canDualWield &&
|
||||
((invType != INVTYPE_2HWEAPON && !have2HWeaponEquipped) || (canTitanGrip && isValidTGWeapon))))
|
||||
{
|
||||
// Handle ring/trinket dual-slot logic
|
||||
Item* const equippedItems[2] = {
|
||||
bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot),
|
||||
bot->GetItemByPos(INVENTORY_SLOT_BAG_0, dstSlot + 1)
|
||||
};
|
||||
|
||||
if (equippedItems[0])
|
||||
{
|
||||
if (equippedItems[1])
|
||||
{
|
||||
// Both slots are full - pick the worst item to replace, but only if new item is better
|
||||
StatsWeightCalculator calc(bot);
|
||||
calc.SetItemSetBonus(false);
|
||||
calc.SetOverflowPenalty(false);
|
||||
|
||||
// Calculate new item score with random properties
|
||||
int32 newItemRandomProp = item->GetItemRandomPropertyId();
|
||||
float newItemScore = calc.CalculateItem(itemId, newItemRandomProp);
|
||||
|
||||
// Calculate equipped items scores with random properties
|
||||
int32 firstRandomProp = equippedItems[0]->GetItemRandomPropertyId();
|
||||
int32 secondRandomProp = equippedItems[1]->GetItemRandomPropertyId();
|
||||
float firstItemScore = calc.CalculateItem(equippedItems[0]->GetTemplate()->ItemId, firstRandomProp);
|
||||
float secondItemScore = calc.CalculateItem(equippedItems[1]->GetTemplate()->ItemId, secondRandomProp);
|
||||
|
||||
// Determine which slot (if any) should be replaced
|
||||
bool betterThanFirst = newItemScore > firstItemScore;
|
||||
bool betterThanSecond = newItemScore > secondItemScore;
|
||||
|
||||
// Early return if new item is not better than either equipped item
|
||||
if (!betterThanFirst && !betterThanSecond)
|
||||
return;
|
||||
|
||||
if (betterThanFirst && betterThanSecond)
|
||||
{
|
||||
// New item is better than both - replace the worse of the two equipped items
|
||||
if (firstItemScore > secondItemScore)
|
||||
dstSlot++; // Replace second slot (worse)
|
||||
// else: keep dstSlot as-is (replace first slot)
|
||||
}
|
||||
else if (betterThanSecond)
|
||||
dstSlot++; // Only better than second slot - replace it
|
||||
}
|
||||
else
|
||||
{
|
||||
// Second slot empty, use it
|
||||
dstSlot++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Equip the item in the chosen slot
|
||||
{
|
||||
WorldPacket packet(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid itemguid = item->GetGUID();
|
||||
packet << itemguid << dstSlot;
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto);
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
|
||||
bool EquipUpgradesAction::Execute(Event event)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return false;
|
||||
|
||||
if (event.GetSource() == "trade status")
|
||||
{
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
uint32 status;
|
||||
p >> status;
|
||||
|
||||
if (status != TRADE_STATUS_TRADE_ACCEPT)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.GetSource() == "item push result")
|
||||
{
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
ObjectGuid playerGuid;
|
||||
uint32 received, created, sendChatMessage, itemSlot, itemId;
|
||||
uint8 bagSlot;
|
||||
|
||||
p >> playerGuid;
|
||||
p >> received;
|
||||
p >> created;
|
||||
p >> sendChatMessage;
|
||||
p >> bagSlot;
|
||||
p >> itemSlot;
|
||||
p >> itemId;
|
||||
|
||||
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT)
|
||||
return false;
|
||||
}
|
||||
|
||||
CollectItemsVisitor visitor;
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EquipUpgradeAction::Execute(Event event)
|
||||
{
|
||||
CollectItemsVisitor visitor;
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
46
src/Bot/BaseAi/Actions/EquipAction.h
Normal file
46
src/Bot/BaseAi/Actions/EquipAction.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_EQUIPACTION_H
|
||||
#define _PLAYERBOT_EQUIPACTION_H
|
||||
|
||||
#include "ChatHelper.h"
|
||||
#include "InventoryAction.h"
|
||||
|
||||
class FindItemVisitor;
|
||||
class Item;
|
||||
class PlayerbotAI;
|
||||
|
||||
class EquipAction : public InventoryAction
|
||||
{
|
||||
public:
|
||||
EquipAction(PlayerbotAI* botAI, std::string const name = "equip") : InventoryAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
void EquipItems(ItemIds ids);
|
||||
|
||||
private:
|
||||
void EquipItem(FindItemVisitor* visitor);
|
||||
uint8 GetSmallestBagSlot();
|
||||
void EquipItem(Item* item);
|
||||
};
|
||||
|
||||
class EquipUpgradesAction : public EquipAction
|
||||
{
|
||||
public:
|
||||
EquipUpgradesAction(PlayerbotAI* botAI, std::string const name = "equip upgrades") : EquipAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class EquipUpgradeAction : public EquipAction
|
||||
{
|
||||
public:
|
||||
EquipUpgradeAction(PlayerbotAI* botAI, std::string const name = "equip upgrade") : EquipAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
159
src/Bot/BaseAi/Actions/EquipGlyphsAction.cpp
Normal file
159
src/Bot/BaseAi/Actions/EquipGlyphsAction.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "EquipGlyphsAction.h"
|
||||
|
||||
#include "Playerbots.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "DBCStores.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace
|
||||
{
|
||||
// itemId -> GlyphInfo
|
||||
std::unordered_map<uint32, EquipGlyphsAction::GlyphInfo> s_GlyphCache;
|
||||
}
|
||||
|
||||
void EquipGlyphsAction::BuildGlyphCache()
|
||||
{
|
||||
if (!s_GlyphCache.empty())
|
||||
return;
|
||||
|
||||
ItemTemplateContainer const* store = sObjectMgr->GetItemTemplateStore();
|
||||
|
||||
for (auto const& kv : *store)
|
||||
{
|
||||
uint32 itemId = kv.first;
|
||||
ItemTemplate const* proto = &kv.second;
|
||||
if (!proto || proto->Class != ITEM_CLASS_GLYPH)
|
||||
continue;
|
||||
|
||||
// inspect item spell
|
||||
for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
||||
{
|
||||
uint32 spellId = proto->Spells[i].SpellId;
|
||||
if (!spellId) continue;
|
||||
|
||||
SpellInfo const* si = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!si) continue;
|
||||
|
||||
for (uint8 eff = 0; eff <= EFFECT_2; ++eff)
|
||||
{
|
||||
if (si->Effects[eff].Effect != SPELL_EFFECT_APPLY_GLYPH)
|
||||
continue;
|
||||
|
||||
uint32 glyphId = si->Effects[eff].MiscValue;
|
||||
if (!glyphId) continue;
|
||||
|
||||
if (auto const* gp = sGlyphPropertiesStore.LookupEntry(glyphId))
|
||||
s_GlyphCache[itemId] = {gp, proto};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EquipGlyphsAction::GlyphInfo const* EquipGlyphsAction::GetGlyphInfo(uint32 itemId)
|
||||
{
|
||||
BuildGlyphCache();
|
||||
auto it = s_GlyphCache.find(itemId);
|
||||
return (it == s_GlyphCache.end()) ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
/// -----------------------------------------------------------------
|
||||
/// Validation and collect
|
||||
/// -----------------------------------------------------------------
|
||||
bool EquipGlyphsAction::CollectGlyphs(std::vector<uint32> const& itemIds,
|
||||
std::vector<GlyphInfo const*>& out) const
|
||||
{
|
||||
std::unordered_set<uint32> seen;
|
||||
|
||||
for (uint32 itemId : itemIds)
|
||||
{
|
||||
if (!seen.insert(itemId).second)
|
||||
return false; // double
|
||||
|
||||
auto const* info = GetGlyphInfo(itemId);
|
||||
if (!info) // no good glyph
|
||||
return false;
|
||||
|
||||
// check class by AllowableClass
|
||||
if ((info->proto->AllowableClass & bot->getClassMask()) == 0)
|
||||
return false;
|
||||
|
||||
out.push_back(info);
|
||||
}
|
||||
return out.size() <= 6 && !out.empty();
|
||||
}
|
||||
|
||||
/// -----------------------------------------------------------------
|
||||
/// Action
|
||||
/// -----------------------------------------------------------------
|
||||
bool EquipGlyphsAction::Execute(Event event)
|
||||
{
|
||||
// 1) parse IDs
|
||||
std::vector<uint32> itemIds;
|
||||
std::istringstream iss(event.getParam());
|
||||
for (uint32 id; iss >> id; ) itemIds.push_back(id);
|
||||
|
||||
std::vector<GlyphInfo const*> glyphs;
|
||||
if (!CollectGlyphs(itemIds, glyphs))
|
||||
{
|
||||
botAI->TellMaster("Usage: glyph equip <6 glyph item IDs> (3 major, 3 minor).");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2) prepare a empty slots table ?
|
||||
bool used[6] = {false,false,false,false,false,false};
|
||||
|
||||
// 3) for each glyph, find the first available and compatible socket
|
||||
for (auto const* g : glyphs)
|
||||
{
|
||||
bool placed = false;
|
||||
|
||||
for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
|
||||
{
|
||||
if (used[i]) continue;
|
||||
|
||||
uint32 slotId = bot->GetGlyphSlot(i);
|
||||
auto const* gs = sGlyphSlotStore.LookupEntry(slotId);
|
||||
if (!gs || gs->TypeFlags != g->prop->TypeFlags)
|
||||
continue; // major/minor don't match
|
||||
|
||||
// Remove aura if exist
|
||||
uint32 cur = bot->GetGlyph(i);
|
||||
if (cur)
|
||||
if (auto* old = sGlyphPropertiesStore.LookupEntry(cur))
|
||||
bot->RemoveAurasDueToSpell(old->SpellId);
|
||||
|
||||
// Apply new one
|
||||
bot->CastSpell(bot, g->prop->SpellId, true);
|
||||
bot->SetGlyph(i, g->prop->Id, true);
|
||||
|
||||
used[i] = true;
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!placed)
|
||||
{
|
||||
botAI->TellMaster("Not enought empty sockets for all glyphs.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
botAI->TellMaster("Glyphs updated.");
|
||||
|
||||
// Flag for custom glyphs
|
||||
botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs")->Set(true);
|
||||
LOG_INFO("playerbots", "Custom Glyph Flag set to ON");
|
||||
|
||||
return true;
|
||||
}
|
||||
37
src/Bot/BaseAi/Actions/EquipGlyphsAction.h
Normal file
37
src/Bot/BaseAi/Actions/EquipGlyphsAction.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_EQUIPGLYPHSACTION_H
|
||||
#define _PLAYERBOT_EQUIPGLYPHSACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
// 1 = major, 2 = minor dans GlyphProperties.dbc
|
||||
enum class GlyphKind : uint32 { MAJOR = 1, MINOR = 2 };
|
||||
|
||||
class EquipGlyphsAction : public Action
|
||||
{
|
||||
public:
|
||||
EquipGlyphsAction(PlayerbotAI* ai) : Action(ai, "glyph equip") {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
/// ---- Rendu public pour être utilisable par le cache global ----
|
||||
struct GlyphInfo
|
||||
{
|
||||
GlyphPropertiesEntry const* prop; ///< entrée GlyphProperties.dbc
|
||||
ItemTemplate const* proto; ///< template de l’objet glyphe
|
||||
};
|
||||
|
||||
private:
|
||||
/// Construit la cache {itemId -> GlyphInfo}
|
||||
static void BuildGlyphCache();
|
||||
static GlyphInfo const* GetGlyphInfo(uint32 itemId);
|
||||
|
||||
/// Parse & valide la liste d’items glyphes
|
||||
bool CollectGlyphs(std::vector<uint32> const& itemIds,
|
||||
std::vector<GlyphInfo const*>& out) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
510
src/Bot/BaseAi/Actions/FishingAction.cpp
Normal file
510
src/Bot/BaseAi/Actions/FishingAction.cpp
Normal file
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "FishingAction.h"
|
||||
#include "FishValues.h"
|
||||
#include "Event.h"
|
||||
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "ItemPackets.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "Map.h"
|
||||
#include "MovementActions.h"
|
||||
#include "Object.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Position.h"
|
||||
|
||||
uint32 const FISHING_SPELL = 7620;
|
||||
uint32 const FISHING_POLE = 6256;
|
||||
uint32 const FISHING_BOBBER = 35591;
|
||||
float const MIN_DISTANCE_TO_WATER = 10.0f; // Minimum spell distance
|
||||
float const MAX_DISTANCE_TO_WATER = 20.0f; // Maximum spell distance
|
||||
float const HEIGHT_ABOVE_WATER_TOLERANCE = 1.0f; // Can stand in up to 1 unit of water and still fish.
|
||||
float const SEARCH_INCREMENT = 2.5f;
|
||||
float const HEIGHT_SEARCH_BUFFER = 10.0f; // Height buffer to prevent potentially missing the model the bot is standing on.
|
||||
float const SEARCH_LAND_BUFFER = 0.5f;
|
||||
uint32 const FISHING_LOCATION_TIMEOUT = 180000; //Three minutes
|
||||
|
||||
static bool IsFishingPole(Item* const item)
|
||||
{
|
||||
if (!item)
|
||||
return false;
|
||||
const ItemTemplate* proto = item->GetTemplate();
|
||||
return proto && proto->Class == ITEM_CLASS_WEAPON &&
|
||||
proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE;
|
||||
}
|
||||
|
||||
float HasFishableWaterOrLand(float x, float y, float z, Map* map, uint32 phaseMask, bool checkForLand=false)
|
||||
{
|
||||
if (!map)
|
||||
return INVALID_HEIGHT;
|
||||
|
||||
LiquidData const& liq = map->GetLiquidData(phaseMask, x, y, z+HEIGHT_ABOVE_WATER_TOLERANCE, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||
float ground = map->GetHeight(phaseMask, x, y, z + HEIGHT_SEARCH_BUFFER, true);
|
||||
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER)
|
||||
{
|
||||
if (checkForLand)
|
||||
return ground;
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
if (checkForLand)
|
||||
{
|
||||
if (ground > liq.Level - HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||
return ground;
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
|
||||
if (liq.Level + HEIGHT_ABOVE_WATER_TOLERANCE > ground)
|
||||
{
|
||||
if (abs(liq.DepthLevel) < 0.5f) // too shallow to fish in.
|
||||
return INVALID_HEIGHT;
|
||||
return liq.Level;
|
||||
}
|
||||
return INVALID_HEIGHT;
|
||||
}
|
||||
|
||||
bool HasLosToWater(Player* bot, float wx, float wy, float waterZ)
|
||||
{
|
||||
float z = bot->GetCollisionHeight() + bot->GetPositionZ();
|
||||
return bot->GetMap()->isInLineOfSight(
|
||||
bot->GetPositionX(), bot->GetPositionY(), z,
|
||||
wx, wy, waterZ,
|
||||
bot->GetPhaseMask(),
|
||||
LINEOFSIGHT_ALL_CHECKS,
|
||||
VMAP::ModelIgnoreFlags::Nothing);
|
||||
}
|
||||
|
||||
WorldPosition FindLandFromPosition(PlayerbotAI* botAI, float startDistance, float endDistance, float increment, float orientation, WorldPosition targetPos, float fishingSearchWindow, bool checkLOS = true)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
Map* map = bot->GetMap();
|
||||
uint32 phaseMask = bot->GetPhaseMask();
|
||||
Player* master = botAI->GetMaster();
|
||||
|
||||
float targetX = targetPos.GetPositionX();
|
||||
float targetY = targetPos.GetPositionY();
|
||||
float targetZ = targetPos.GetPositionZ();
|
||||
|
||||
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||
{
|
||||
//step backwards from position to bot to find edge of shore.
|
||||
float checkX = targetX - dist * cos(orientation);
|
||||
float checkY = targetY - dist * sin(orientation);
|
||||
|
||||
float groundZ = map->GetHeight(phaseMask, checkX, checkY, targetZ + HEIGHT_SEARCH_BUFFER, true);
|
||||
|
||||
if (groundZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
LiquidData const& liq = map->GetLiquidData(phaseMask, checkX, checkY, targetZ, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER || groundZ > liq.DepthLevel + HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||
{
|
||||
if (checkLOS)
|
||||
{
|
||||
bool hasLOS = map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
|
||||
if (!hasLOS)
|
||||
continue;
|
||||
}
|
||||
// Add a distance check for the position to prevent the bot from moving out of range to the master.
|
||||
if (master && botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||
continue;
|
||||
|
||||
return WorldPosition(bot->GetMapId(), checkX, checkY, groundZ);
|
||||
}
|
||||
}
|
||||
|
||||
return WorldPosition();
|
||||
}
|
||||
|
||||
WorldPosition FindLandRadialFromPosition (PlayerbotAI* botAI, WorldPosition targetPos, float startDistance, float endDistance, float increment, float fishingSearchWindow, int angles = 16)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
const int numDirections = angles;
|
||||
std::vector<WorldPosition> boundaryPoints;
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master)
|
||||
return WorldPosition();
|
||||
|
||||
Map* map = bot->GetMap();
|
||||
uint32 phaseMask = bot->GetPhaseMask();
|
||||
|
||||
float targetX = targetPos.GetPositionX();
|
||||
float targetY = targetPos.GetPositionY();
|
||||
float targetZ = targetPos.GetPositionZ();
|
||||
|
||||
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||
{
|
||||
for (int i = 0; i < numDirections; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numDirections;
|
||||
float checkX = targetX - cos(angle) * dist;
|
||||
float checkY = targetY - sin(angle) * dist;
|
||||
|
||||
float groundZ = HasFishableWaterOrLand(checkX, checkY, targetZ, map, phaseMask, true);
|
||||
|
||||
if (groundZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
if (map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||
continue;
|
||||
|
||||
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, groundZ));
|
||||
}
|
||||
|
||||
if (!boundaryPoints.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
if (boundaryPoints.empty())
|
||||
return WorldPosition();
|
||||
|
||||
if (boundaryPoints.size() == 1)
|
||||
return boundaryPoints[0];
|
||||
|
||||
float minDistance = FLT_MAX;
|
||||
WorldLocation closestPoint = WorldPosition();
|
||||
for (auto const& pos : boundaryPoints)
|
||||
{
|
||||
float distance = bot->GetExactDist2d(&pos);
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closestPoint = pos;
|
||||
}
|
||||
}
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS, int numDirections)
|
||||
{
|
||||
std::vector<WorldPosition> boundaryPoints;
|
||||
|
||||
float dist = minDistance;
|
||||
while (dist <= maxDistance)
|
||||
{
|
||||
for (int i = 0; i < numDirections; ++i)
|
||||
{
|
||||
float angle = (2.0f * M_PI * i) / numDirections;
|
||||
float checkX = x + cos(angle) * dist;
|
||||
float checkY = y + sin(angle) * dist;
|
||||
|
||||
float waterZ = HasFishableWaterOrLand(checkX, checkY, z, map, phaseMask);
|
||||
|
||||
if (waterZ == INVALID_HEIGHT)
|
||||
continue;
|
||||
|
||||
if (checkLOS && !HasLosToWater(bot, checkX, checkY, waterZ))
|
||||
continue;
|
||||
|
||||
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, waterZ));
|
||||
}
|
||||
|
||||
if (!boundaryPoints.empty())
|
||||
break;
|
||||
|
||||
dist += increment;
|
||||
}
|
||||
|
||||
if (boundaryPoints.empty())
|
||||
return WorldPosition();
|
||||
|
||||
if (boundaryPoints.size() == 1)
|
||||
return boundaryPoints[0];
|
||||
// return the central point in the identified positions in to try to be perpendicular to the shore.
|
||||
return boundaryPoints[boundaryPoints.size() / 2];
|
||||
}
|
||||
|
||||
WorldPosition FindFishingHole(PlayerbotAI* botAI)
|
||||
{
|
||||
Player* player = botAI->GetBot();
|
||||
GuidVector gos = PAI_VALUE(GuidVector, "nearest game objects no los");
|
||||
GameObject* nearestFishingHole = nullptr;
|
||||
float minDist = std::numeric_limits<float>::max();
|
||||
for (auto const& guid : gos)
|
||||
{
|
||||
GameObject* go = botAI->GetGameObject(guid);
|
||||
if (!go)
|
||||
continue;
|
||||
if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
|
||||
{
|
||||
float dist = player->GetDistance2d(go);
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
nearestFishingHole = go;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestFishingHole)
|
||||
return WorldPosition(nearestFishingHole->GetMapId(), nearestFishingHole->GetPositionX(), nearestFishingHole->GetPositionY(), nearestFishingHole->GetPositionZ());
|
||||
|
||||
return WorldPosition();
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::Execute(Event event)
|
||||
{
|
||||
WorldPosition landSpot = AI_VALUE(WorldPosition, "fishing spot");
|
||||
if (landSpot.IsValid())
|
||||
return MoveTo(landSpot.GetMapId(), landSpot.GetPositionX(), landSpot.GetPositionY(), landSpot.GetPositionZ());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::isUseful()
|
||||
{
|
||||
if (!AI_VALUE(bool, "can fish"))
|
||||
return false;
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) ||
|
||||
bot->GetExactDist(&pos) < 0.1f;
|
||||
|
||||
}
|
||||
|
||||
bool MoveNearWaterAction::isPossible()
|
||||
{
|
||||
Player* master = botAI->GetMaster();
|
||||
float fishingSearchWindow;
|
||||
|
||||
if (master)
|
||||
fishingSearchWindow = sPlayerbotAIConfig->fishingDistanceFromMaster;
|
||||
else
|
||||
fishingSearchWindow = sPlayerbotAIConfig->fishingDistance;
|
||||
|
||||
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||
|
||||
if (fishingHole.IsValid())
|
||||
{
|
||||
float distance = bot->GetExactDist2d(&fishingHole);
|
||||
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||
// Water spot is in range, and we have LOS to it. Set bot position to fishing spot and do not move
|
||||
if (distance >= MIN_DISTANCE_TO_WATER &&
|
||||
distance <= MAX_DISTANCE_TO_WATER && hasLOS)
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", WorldPosition(WorldPosition(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())));
|
||||
return false;
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
if (landSpot.IsValid())
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Can the bot fish from current position?
|
||||
WorldPosition waterAtCurrentPos =
|
||||
FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMap(),
|
||||
bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, true);
|
||||
if (waterAtCurrentPos.IsValid())
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot",
|
||||
WorldPosition(WorldPosition(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ())));
|
||||
return false;
|
||||
}
|
||||
// Lets find some water where we can fish.
|
||||
WorldPosition water = FindWaterRadial(
|
||||
bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
bot->GetMap(), bot->GetPhaseMask(),
|
||||
MIN_DISTANCE_TO_WATER,
|
||||
fishingSearchWindow + MAX_DISTANCE_TO_WATER,
|
||||
SEARCH_INCREMENT, false);
|
||||
|
||||
if (!water.IsValid())
|
||||
return false;
|
||||
|
||||
bool hasLOS = bot->IsWithinLOS(water.GetPositionX(), water.GetPositionY(), water.GetPositionZ());
|
||||
float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY());
|
||||
WorldPosition landSpot =
|
||||
FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false);
|
||||
|
||||
if (landSpot.IsValid())
|
||||
{
|
||||
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EquipFishingPoleAction::Execute(Event event)
|
||||
{
|
||||
if (!_pole)
|
||||
return false;
|
||||
|
||||
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
eqPacket << _pole->GetGUID() << uint8(EQUIPMENT_SLOT_MAINHAND);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EquipFishingPoleAction::isUseful()
|
||||
{
|
||||
Item* mainHand = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
if (IsFishingPole(mainHand))
|
||||
return false;
|
||||
|
||||
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||
{
|
||||
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
{
|
||||
if (IsFishingPole(item))
|
||||
{
|
||||
_pole = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||
{
|
||||
if (Bag* pBag = bot->GetBagByPos(bag))
|
||||
{
|
||||
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
|
||||
{
|
||||
if (Item* item = pBag->GetItemByPos(j))
|
||||
{
|
||||
if (IsFishingPole(item))
|
||||
{
|
||||
_pole = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
{
|
||||
bot->StoreNewItemInBestSlots(FISHING_POLE, 1); // Try to get a fishing pole
|
||||
return true;
|
||||
}
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
std::string masterName = master->GetName();
|
||||
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||
"no_fishing_pole_error", "I don't have a Fishing Pole",{});
|
||||
botAI->Whisper(text, masterName);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FishingAction::Execute(Event event)
|
||||
{
|
||||
WorldPosition target = WorldPosition();
|
||||
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||
if (fishingHole.IsValid())
|
||||
{
|
||||
Position pos = fishingHole;
|
||||
float distance = bot->GetExactDist2d(&pos);
|
||||
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||
if (distance < MAX_DISTANCE_TO_WATER &&
|
||||
distance > MIN_DISTANCE_TO_WATER && hasLOS)
|
||||
target = fishingHole;
|
||||
}
|
||||
if (!target.IsValid())
|
||||
{
|
||||
target = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), bot->GetMap(), bot->GetPhaseMask(),
|
||||
MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, true, 32);
|
||||
if (!target.IsValid())
|
||||
return false;
|
||||
}
|
||||
Position pos = target;
|
||||
|
||||
if (!bot->HasInArc(1.0, &pos, 1.0))
|
||||
{
|
||||
float angle = bot->GetAngle(pos.GetPositionX(), pos.GetPositionY());
|
||||
bot->SetOrientation(angle);
|
||||
if (!bot->IsRooted())
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
|
||||
EquipFishingPoleAction equipAction(botAI);
|
||||
if (equipAction.isUseful())
|
||||
return equipAction.Execute(event);
|
||||
|
||||
botAI->CastSpell(FISHING_SPELL, bot);
|
||||
botAI->ChangeStrategy("+use bobber", BOT_STATE_NON_COMBAT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FishingAction::isUseful()
|
||||
{
|
||||
if (!AI_VALUE(bool, "can fish"))
|
||||
return false;
|
||||
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
|
||||
if (!pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT))
|
||||
return false;
|
||||
|
||||
return bot->GetExactDist(&pos) < 0.1f;
|
||||
}
|
||||
|
||||
bool UseBobberAction::isUseful()
|
||||
{
|
||||
return AI_VALUE(bool, "can use fishing bobber");
|
||||
}
|
||||
|
||||
bool UseBobberAction::Execute(Event event)
|
||||
{
|
||||
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
|
||||
for (auto const& guid : gos)
|
||||
{
|
||||
if (GameObject* go = botAI->GetGameObject(guid))
|
||||
{
|
||||
if (go->GetEntry() != FISHING_BOBBER)
|
||||
continue;
|
||||
if (go->GetOwnerGUID() != bot->GetGUID())
|
||||
continue;
|
||||
if (go->getLootState() == GO_READY)
|
||||
{
|
||||
go->Use(bot);
|
||||
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EndMasterFishingAction::Execute(Event event)
|
||||
{
|
||||
botAI->ChangeStrategy("-master fishing", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndMasterFishingAction::isUseful()
|
||||
{
|
||||
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||
WorldPosition pos = fishingSpotValueObject->Get();
|
||||
if (pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition())
|
||||
return false;
|
||||
|
||||
WorldPosition nearWater = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
bot->GetMap(), bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, sPlayerbotAIConfig->endFishingWithMaster, 10.0f);
|
||||
return !nearWater.IsValid();
|
||||
}
|
||||
|
||||
bool RemoveBobberStrategyAction::Execute(Event event)
|
||||
{
|
||||
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||
return true;
|
||||
}
|
||||
71
src/Bot/BaseAi/Actions/FishingAction.h
Normal file
71
src/Bot/BaseAi/Actions/FishingAction.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_FISHINGACTION_H
|
||||
#define _PLAYERBOT_FISHINGACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "MovementActions.h"
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
extern const uint32 FISHING_SPELL;
|
||||
extern const uint32 FISHING_POLE;
|
||||
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);
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class FishingAction : public Action
|
||||
{
|
||||
public:
|
||||
FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing"){}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class EquipFishingPoleAction : public Action
|
||||
{
|
||||
public:
|
||||
EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
private:
|
||||
Item* _pole = nullptr;
|
||||
};
|
||||
|
||||
class MoveNearWaterAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveNearWaterAction(PlayerbotAI* botAI): MovementAction(botAI, "move near water") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool isPossible() override;
|
||||
};
|
||||
|
||||
class UseBobberAction : public Action
|
||||
{
|
||||
public:
|
||||
UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class EndMasterFishingAction : public Action
|
||||
{
|
||||
public:
|
||||
EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class RemoveBobberStrategyAction : public Action
|
||||
{
|
||||
public:
|
||||
RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
#endif
|
||||
62
src/Bot/BaseAi/Actions/FlagAction.cpp
Normal file
62
src/Bot/BaseAi/Actions/FlagAction.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "FlagAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool FlagAction::TellUsage()
|
||||
{
|
||||
botAI->TellError("Usage: flag cloak/helm/pvp on/set/off/clear/toggle/?");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FlagAction::Execute(Event event)
|
||||
{
|
||||
std::string const cmd = event.getParam();
|
||||
std::vector<std::string> ss = split(cmd, ' ');
|
||||
if (ss.size() != 2)
|
||||
return TellUsage();
|
||||
|
||||
bool setFlag = (ss[1] == "set" || ss[1] == "on");
|
||||
bool clearFlag = (ss[1] == "clear" || ss[1] == "off");
|
||||
bool toggleFlag = (ss[1] == "toggle");
|
||||
if (ss[0] == "pvp")
|
||||
{
|
||||
if (setFlag)
|
||||
bot->SetPvP(true);
|
||||
else if (clearFlag)
|
||||
bot->SetPvP(false);
|
||||
else if (toggleFlag)
|
||||
bot->SetPvP(!bot->IsPvP());
|
||||
|
||||
std::ostringstream out;
|
||||
out << ss[0] << " flag is " << chat->FormatBoolean(bot->IsPvP());
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 playerFlags;
|
||||
if (ss[0] == "cloak")
|
||||
playerFlags = PLAYER_FLAGS_HIDE_CLOAK;
|
||||
|
||||
if (ss[0] == "helm")
|
||||
playerFlags = PLAYER_FLAGS_HIDE_HELM;
|
||||
|
||||
if (clearFlag)
|
||||
bot->SetFlag(PLAYER_FLAGS, playerFlags);
|
||||
else if (setFlag)
|
||||
bot->RemoveFlag(PLAYER_FLAGS, playerFlags);
|
||||
else if (toggleFlag && bot->HasFlag(PLAYER_FLAGS, playerFlags))
|
||||
bot->RemoveFlag(PLAYER_FLAGS, playerFlags);
|
||||
else if (toggleFlag && !bot->HasFlag(PLAYER_FLAGS, playerFlags))
|
||||
bot->SetFlag(PLAYER_FLAGS, playerFlags);
|
||||
|
||||
std::ostringstream out;
|
||||
out << ss[0] << " flag is " << chat->FormatBoolean(!bot->HasFlag(PLAYER_FLAGS, playerFlags));
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
24
src/Bot/BaseAi/Actions/FlagAction.h
Normal file
24
src/Bot/BaseAi/Actions/FlagAction.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_FLAGACTION_H
|
||||
#define _PLAYERBOT_FLAGACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class FlagAction : public Action
|
||||
{
|
||||
public:
|
||||
FlagAction(PlayerbotAI* botAI) : Action(botAI, "flag") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
private:
|
||||
bool TellUsage();
|
||||
};
|
||||
|
||||
#endif
|
||||
172
src/Bot/BaseAi/Actions/FollowActions.cpp
Normal file
172
src/Bot/BaseAi/Actions/FollowActions.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "FollowActions.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "Event.h"
|
||||
#include "Formations.h"
|
||||
#include "LastMovementValue.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "SharedDefines.h"
|
||||
|
||||
bool FollowAction::Execute(Event event)
|
||||
{
|
||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||
std::string const target = formation->GetTargetName();
|
||||
|
||||
bool moved = false;
|
||||
if (!target.empty())
|
||||
{
|
||||
moved = Follow(AI_VALUE(Unit*, target));
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldLocation loc = formation->GetLocation();
|
||||
if (Formation::IsNullLocation(loc) || loc.GetMapId() == -1)
|
||||
return false;
|
||||
|
||||
MovementPriority priority = botAI->GetState() == BOT_STATE_COMBAT ? MovementPriority::MOVEMENT_COMBAT : MovementPriority::MOVEMENT_NORMAL;
|
||||
moved = MoveTo(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), false, false, false,
|
||||
true, priority, true);
|
||||
}
|
||||
|
||||
// This section has been commented out because it was forcing the pet to
|
||||
// follow the bot on every "follow" action tick, overriding any attack or
|
||||
// stay commands that might have been issued by the player.
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
// {
|
||||
// botAI->PetFollow();
|
||||
// }
|
||||
// if (moved)
|
||||
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
|
||||
|
||||
return moved;
|
||||
}
|
||||
|
||||
bool FollowAction::isUseful()
|
||||
{
|
||||
// move from group takes priority over follow as it's added and removed automatically
|
||||
// (without removing/adding follow)
|
||||
if (botAI->HasStrategy("move from group", BOT_STATE_COMBAT) ||
|
||||
botAI->HasStrategy("move from group", BOT_STATE_NON_COMBAT))
|
||||
return false;
|
||||
|
||||
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
|
||||
return false;
|
||||
|
||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||
if (!formation)
|
||||
return false;
|
||||
|
||||
std::string const target = formation->GetTargetName();
|
||||
|
||||
Unit* fTarget = nullptr;
|
||||
if (!target.empty())
|
||||
fTarget = AI_VALUE(Unit*, target);
|
||||
else
|
||||
fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (fTarget)
|
||||
{
|
||||
if (fTarget->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||
return false;
|
||||
|
||||
if (!CanDeadFollow(fTarget))
|
||||
return false;
|
||||
|
||||
if (fTarget->GetGUID() == bot->GetGUID())
|
||||
return false;
|
||||
}
|
||||
|
||||
float distance = 0.f;
|
||||
if (!target.empty())
|
||||
{
|
||||
distance = AI_VALUE2(float, "distance", target);
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldLocation loc = formation->GetLocation();
|
||||
if (Formation::IsNullLocation(loc) || bot->GetMapId() != loc.GetMapId())
|
||||
return false;
|
||||
|
||||
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
|
||||
}
|
||||
if (botAI->HasStrategy("master fishing", BOT_STATE_NON_COMBAT))
|
||||
return sServerFacade->IsDistanceGreaterThan(distance, sPlayerbotAIConfig->fishingDistanceFromMaster);
|
||||
|
||||
return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance());
|
||||
}
|
||||
|
||||
bool FollowAction::CanDeadFollow(Unit* target)
|
||||
{
|
||||
// In battleground, wait for spirit healer
|
||||
if (bot->InBattleground() && !bot->IsAlive())
|
||||
return false;
|
||||
|
||||
// Move to corpse when dead and player is alive or not a ghost.
|
||||
if (!bot->IsAlive() && (target->IsAlive() || !target->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToGroupLeaderAction::Execute(Event event)
|
||||
{
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
bool canFollow = Follow(fTarget);
|
||||
if (!canFollow)
|
||||
{
|
||||
// botAI->SetNextCheckDelay(5000);
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPosition targetPos(fTarget);
|
||||
WorldPosition bosPos(bot);
|
||||
float distance = bosPos.fDist(targetPos);
|
||||
|
||||
if (distance < sPlayerbotAIConfig->reactDistance * 3)
|
||||
{
|
||||
if (!urand(0, 3))
|
||||
botAI->TellMaster("I am close, wait for me!");
|
||||
}
|
||||
else if (distance < 1000)
|
||||
{
|
||||
if (!urand(0, 10))
|
||||
botAI->TellMaster("I heading to your position.");
|
||||
}
|
||||
else if (!urand(0, 20))
|
||||
botAI->TellMaster("I am traveling to your position.");
|
||||
|
||||
botAI->SetNextCheckDelay(3000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FleeToGroupLeaderAction::isUseful()
|
||||
{
|
||||
if (!botAI->GetGroupLeader())
|
||||
return false;
|
||||
|
||||
if (botAI->GetGroupLeader() == bot)
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID())
|
||||
return false;
|
||||
|
||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||
return false;
|
||||
|
||||
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||
|
||||
if (!CanDeadFollow(fTarget))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
32
src/Bot/BaseAi/Actions/FollowActions.h
Normal file
32
src/Bot/BaseAi/Actions/FollowActions.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_FOLLOWACTIONS_H
|
||||
#define _PLAYERBOT_FOLLOWACTIONS_H
|
||||
|
||||
#include "MovementActions.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class FollowAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
FollowAction(PlayerbotAI* botAI, std::string const name = "follow") : MovementAction(botAI, name) {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool CanDeadFollow(Unit* target);
|
||||
};
|
||||
|
||||
class FleeToGroupLeaderAction : public FollowAction
|
||||
{
|
||||
public:
|
||||
FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
228
src/Bot/BaseAi/Actions/GenericActions.cpp
Normal file
228
src/Bot/BaseAi/Actions/GenericActions.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GenericActions.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Player.h"
|
||||
#include "Pet.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "CharmInfo.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
enum PetSpells
|
||||
{
|
||||
PET_PROWL_1 = 24450,
|
||||
PET_PROWL_2 = 24452,
|
||||
PET_PROWL_3 = 24453,
|
||||
PET_COWER = 1742,
|
||||
PET_LEAP = 47482,
|
||||
PET_SPELL_LOCK_1 = 19244,
|
||||
PET_SPELL_LOCK_2 = 19647,
|
||||
PET_DEVOUR_MAGIC_1 = 19505,
|
||||
PET_DEVOUR_MAGIC_2 = 19731,
|
||||
PET_DEVOUR_MAGIC_3 = 19734,
|
||||
PET_DEVOUR_MAGIC_4 = 19736,
|
||||
PET_DEVOUR_MAGIC_5 = 27276,
|
||||
PET_DEVOUR_MAGIC_6 = 27277,
|
||||
PET_DEVOUR_MAGIC_7 = 48011,
|
||||
PET_SPIRIT_WOLF_LEAP = 58867
|
||||
};
|
||||
|
||||
static std::vector<uint32> disabledPetSpells = {
|
||||
PET_PROWL_1, PET_PROWL_2, PET_PROWL_3,
|
||||
PET_COWER, PET_LEAP,
|
||||
PET_SPELL_LOCK_1, PET_SPELL_LOCK_2,
|
||||
PET_DEVOUR_MAGIC_1, PET_DEVOUR_MAGIC_2, PET_DEVOUR_MAGIC_3,
|
||||
PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7, PET_SPIRIT_WOLF_LEAP
|
||||
};
|
||||
|
||||
bool MeleeAction::isUseful()
|
||||
{
|
||||
// do not allow if can't attack from vehicle
|
||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TogglePetSpellAutoCastAction::Execute(Event event)
|
||||
{
|
||||
Pet* pet = bot->GetPet();
|
||||
if (!pet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// hack on high level spell after low level initialization
|
||||
std::vector<unsigned int> shouldRemove;
|
||||
for (unsigned int& m_autospell : pet->m_autospells)
|
||||
{
|
||||
if (!pet->HasSpell(m_autospell))
|
||||
{
|
||||
shouldRemove.push_back(m_autospell);
|
||||
}
|
||||
}
|
||||
for (unsigned int spellId : shouldRemove)
|
||||
{
|
||||
auto autospellItr = std::find(pet->m_autospells.begin(), pet->m_autospells.end(), spellId);
|
||||
if (autospellItr != pet->m_autospells.end())
|
||||
pet->m_autospells.erase(autospellItr);
|
||||
}
|
||||
bool toggled = false;
|
||||
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
|
||||
{
|
||||
if (itr->second.state == PETSPELL_REMOVED)
|
||||
continue;
|
||||
|
||||
uint32 spellId = itr->first;
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo->IsAutocastable())
|
||||
continue;
|
||||
|
||||
bool shouldApply = true;
|
||||
for (uint32 disabledSpell : disabledPetSpells)
|
||||
{
|
||||
if (spellId == disabledSpell)
|
||||
{
|
||||
shouldApply = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool isAutoCast = false;
|
||||
for (unsigned int& m_autospell : pet->m_autospells)
|
||||
{
|
||||
if (m_autospell == spellId)
|
||||
{
|
||||
isAutoCast = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shouldApply != isAutoCast)
|
||||
{
|
||||
pet->ToggleAutocast(spellInfo, shouldApply);
|
||||
toggled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Debug message if pet spells have been toggled and debug is enabled
|
||||
if (toggled && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet autocast spells have been toggled.");
|
||||
|
||||
return toggled;
|
||||
}
|
||||
|
||||
bool PetAttackAction::Execute(Event event)
|
||||
{
|
||||
Guardian* pet = bot->GetGuardianPet();
|
||||
if (!pet)
|
||||
return false;
|
||||
|
||||
// Do not attack if the pet's stance is set to "passive".
|
||||
if (pet->GetReactState() == REACT_PASSIVE)
|
||||
return false;
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!bot->IsValidAttackTarget(target))
|
||||
return false;
|
||||
|
||||
// This section has been commented because it was overriding the
|
||||
// pet's stance to "passive" every time the attack action was executed.
|
||||
// pet->SetReactState(REACT_PASSIVE);
|
||||
|
||||
pet->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||
pet->AttackStop();
|
||||
pet->SetTarget(target->GetGUID());
|
||||
|
||||
pet->GetCharmInfo()->SetIsCommandAttack(true);
|
||||
pet->GetCharmInfo()->SetIsAtStay(false);
|
||||
pet->GetCharmInfo()->SetIsFollowing(false);
|
||||
pet->GetCharmInfo()->SetIsCommandFollow(false);
|
||||
pet->GetCharmInfo()->SetIsReturning(false);
|
||||
|
||||
pet->ToCreature()->AI()->AttackStart(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetPetStanceAction::Execute(Event /*event*/)
|
||||
{
|
||||
// Prepare a list to hold all controlled pet and guardian creatures
|
||||
std::vector<Creature*> targets;
|
||||
|
||||
// Add the bot's main pet (if it exists) to the target list
|
||||
Pet* pet = bot->GetPet();
|
||||
if (pet)
|
||||
targets.push_back(pet);
|
||||
|
||||
// Loop through all units controlled by the bot (could be pets, guardians, etc.)
|
||||
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
|
||||
{
|
||||
// Only add creatures (skip players, vehicles, etc.)
|
||||
Creature* creature = dynamic_cast<Creature*>(*itr);
|
||||
if (!creature)
|
||||
continue;
|
||||
// Avoid adding the main pet twice
|
||||
if (pet && creature == pet)
|
||||
continue;
|
||||
targets.push_back(creature);
|
||||
}
|
||||
|
||||
// If there are no controlled pets or guardians, notify the player and exit
|
||||
if (targets.empty())
|
||||
{
|
||||
botAI->TellError("You have no pet or guardian pet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the default pet stance from the configuration
|
||||
int32 stance = sPlayerbotAIConfig->defaultPetStance;
|
||||
ReactStates react = REACT_DEFENSIVE;
|
||||
std::string stanceText = "defensive (from config, fallback)";
|
||||
|
||||
// Map the config stance integer to a ReactStates value and a message
|
||||
switch (stance)
|
||||
{
|
||||
case 0:
|
||||
react = REACT_PASSIVE;
|
||||
stanceText = "passive (from config)";
|
||||
break;
|
||||
case 1:
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive (from config)";
|
||||
break;
|
||||
case 2:
|
||||
react = REACT_AGGRESSIVE;
|
||||
stanceText = "aggressive (from config)";
|
||||
break;
|
||||
default:
|
||||
react = REACT_DEFENSIVE;
|
||||
stanceText = "defensive (from config, fallback)";
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply the stance to all target creatures (pets/guardians)
|
||||
for (Creature* target : targets)
|
||||
{
|
||||
target->SetReactState(react);
|
||||
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||
// If the creature has a CharmInfo, set the player-visible stance as well
|
||||
if (charmInfo)
|
||||
charmInfo->SetPlayerReactState(react);
|
||||
}
|
||||
|
||||
// If debug is enabled in config, inform the master of the new stance
|
||||
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||
botAI->TellMaster("Pet stance set to " + stanceText + " (applied to all pets/guardians).");
|
||||
|
||||
return true;
|
||||
}
|
||||
46
src/Bot/BaseAi/Actions/GenericActions.h
Normal file
46
src/Bot/BaseAi/Actions/GenericActions.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GENERICACTIONS_H
|
||||
#define _PLAYERBOT_GENERICACTIONS_H
|
||||
|
||||
#include "AttackAction.h"
|
||||
#include "Action.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class MeleeAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
MeleeAction(PlayerbotAI* botAI) : AttackAction(botAI, "melee") {}
|
||||
|
||||
std::string const GetTargetName() override { return "current target"; }
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class TogglePetSpellAutoCastAction : public Action
|
||||
{
|
||||
public:
|
||||
TogglePetSpellAutoCastAction(PlayerbotAI* ai) : Action(ai, "toggle pet spell") {}
|
||||
virtual bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class PetAttackAction : public Action
|
||||
{
|
||||
public:
|
||||
PetAttackAction(PlayerbotAI* ai) : Action(ai, "pet attack") {}
|
||||
virtual bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class SetPetStanceAction : public Action
|
||||
{
|
||||
public:
|
||||
SetPetStanceAction(PlayerbotAI* botAI) : Action(botAI, "set pet stance") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
145
src/Bot/BaseAi/Actions/GenericBuffUtils.cpp
Normal file
145
src/Bot/BaseAi/Actions/GenericBuffUtils.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GenericBuffUtils.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Player.h"
|
||||
#include "Group.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Value.h"
|
||||
#include "Config.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
|
||||
namespace ai::buff
|
||||
{
|
||||
std::string MakeAuraQualifierForBuff(std::string const& name)
|
||||
{
|
||||
// Paladin
|
||||
if (name == "blessing of kings") return "blessing of kings,greater blessing of kings";
|
||||
if (name == "blessing of might") return "blessing of might,greater blessing of might";
|
||||
if (name == "blessing of wisdom") return "blessing of wisdom,greater blessing of wisdom";
|
||||
if (name == "blessing of sanctuary") return "blessing of sanctuary,greater blessing of sanctuary";
|
||||
// Druid
|
||||
if (name == "mark of the wild") return "mark of the wild,gift of the wild";
|
||||
// Mage
|
||||
if (name == "arcane intellect") return "arcane intellect,arcane brilliance";
|
||||
// Priest
|
||||
if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude";
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string GroupVariantFor(std::string const& name)
|
||||
{
|
||||
// Paladin
|
||||
if (name == "blessing of kings") return "greater blessing of kings";
|
||||
if (name == "blessing of might") return "greater blessing of might";
|
||||
if (name == "blessing of wisdom") return "greater blessing of wisdom";
|
||||
if (name == "blessing of sanctuary") return "greater blessing of sanctuary";
|
||||
// Druid
|
||||
if (name == "mark of the wild") return "gift of the wild";
|
||||
// Mage
|
||||
if (name == "arcane intellect") return "arcane brilliance";
|
||||
// Priest
|
||||
if (name == "power word: fortitude") return "prayer of fortitude";
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool HasRequiredReagents(Player* bot, uint32 spellId)
|
||||
{
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId))
|
||||
{
|
||||
for (int i = 0; i < MAX_SPELL_REAGENTS; ++i)
|
||||
{
|
||||
if (info->Reagent[i] > 0)
|
||||
{
|
||||
uint32 const itemId = info->Reagent[i];
|
||||
int32 const need = info->ReagentCount[i];
|
||||
if ((int32)bot->GetItemCount(itemId, false) < need)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// No reagent required
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string UpgradeToGroupIfAppropriate(
|
||||
Player* bot,
|
||||
PlayerbotAI* botAI,
|
||||
std::string const& baseName,
|
||||
bool announceOnMissing,
|
||||
std::function<void(std::string const&)> announce)
|
||||
{
|
||||
std::string castName = baseName;
|
||||
Group* g = bot->GetGroup();
|
||||
if (!g || g->GetMembersCount() < static_cast<uint32>(sPlayerbotAIConfig->minBotsForGreaterBuff))
|
||||
return castName; // Group too small: stay in solo mode
|
||||
|
||||
if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty())
|
||||
{
|
||||
uint32 const groupVariantSpellId = botAI->GetAiObjectContext()
|
||||
->GetValue<uint32>("spell id", groupName)->Get();
|
||||
|
||||
// We check usefulness on the **basic** buff (not the greater version),
|
||||
// because "spell cast useful" may return false for the greater variant.
|
||||
bool const usefulBase = botAI->GetAiObjectContext()
|
||||
->GetValue<bool>("spell cast useful", baseName)->Get();
|
||||
|
||||
if (groupVariantSpellId && HasRequiredReagents(bot, groupVariantSpellId))
|
||||
{
|
||||
// Learned + reagents OK -> switch to greater
|
||||
return groupName;
|
||||
}
|
||||
|
||||
// Missing reagents -> announce if (a) greater is known, (b) base buff is useful,
|
||||
// (c) announce was requested, (d) a callback is provided.
|
||||
if (announceOnMissing && groupVariantSpellId && usefulBase && announce)
|
||||
{
|
||||
static std::map<std::pair<uint32, std::string>, time_t> s_lastWarn; // par bot & par buff
|
||||
time_t now = std::time(nullptr);
|
||||
uint32 botLow = static_cast<uint32>(bot->GetGUID().GetCounter());
|
||||
time_t& last = s_lastWarn[ std::make_pair(botLow, groupName) ];
|
||||
if (!last || now - last >= sPlayerbotAIConfig->rpWarningCooldown) // Configurable anti-spam
|
||||
{
|
||||
// DB Key choice in regard of the buff
|
||||
std::string key;
|
||||
if (groupName.find("greater blessing") != std::string::npos)
|
||||
key = "rp_missing_reagent_greater_blessing";
|
||||
else if (groupName == "gift of the wild")
|
||||
key = "rp_missing_reagent_gift_of_the_wild";
|
||||
else if (groupName == "arcane brilliance")
|
||||
key = "rp_missing_reagent_arcane_brilliance";
|
||||
else
|
||||
key = "rp_missing_reagent_generic";
|
||||
|
||||
// Placeholders
|
||||
std::map<std::string, std::string> placeholders;
|
||||
placeholders["%group_spell"] = groupName;
|
||||
placeholders["%base_spell"] = baseName;
|
||||
|
||||
std::string announceText = sPlayerbotTextMgr->GetBotTextOrDefault(key,
|
||||
"Out of components for %group_spell. Using %base_spell!", placeholders);
|
||||
|
||||
announce(announceText);
|
||||
last = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
return castName;
|
||||
}
|
||||
}
|
||||
63
src/Bot/BaseAi/Actions/GenericBuffUtils.h
Normal file
63
src/Bot/BaseAi/Actions/GenericBuffUtils.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "Common.h"
|
||||
#include "Group.h"
|
||||
#include "Chat.h"
|
||||
#include "Language.h"
|
||||
|
||||
class Player;
|
||||
class PlayerbotAI;
|
||||
|
||||
namespace ai::buff
|
||||
{
|
||||
|
||||
// Build an aura qualifier "single + greater" to avoid double-buffing
|
||||
std::string MakeAuraQualifierForBuff(std::string const& name);
|
||||
|
||||
// Returns the group spell name for a given single-target buff.
|
||||
// If no group equivalent exists, returns "".
|
||||
std::string GroupVariantFor(std::string const& name);
|
||||
|
||||
// Checks if the bot has the required reagents to cast a spell (by its spellId).
|
||||
// Returns false if the spellId is invalid.
|
||||
bool HasRequiredReagents(Player* bot, uint32 spellId);
|
||||
|
||||
// Applies the "switch to group buff" policy if: the bot is in a group of size x+,
|
||||
// the group variant is known/useful, and reagents are available. Otherwise, returns baseName.
|
||||
// If announceOnMissing == true and reagents are missing, calls the 'announce' callback
|
||||
// (if provided) to notify the party/raid.
|
||||
std::string UpgradeToGroupIfAppropriate(
|
||||
Player* bot,
|
||||
PlayerbotAI* botAI,
|
||||
std::string const& baseName,
|
||||
bool announceOnMissing = false,
|
||||
std::function<void(std::string const&)> announce = {}
|
||||
);
|
||||
}
|
||||
|
||||
namespace ai::chat {
|
||||
inline std::function<void(std::string const&)> MakeGroupAnnouncer(Player* me)
|
||||
{
|
||||
return [me](std::string const& msg)
|
||||
{
|
||||
if (Group* g = me->GetGroup())
|
||||
{
|
||||
WorldPacket data;
|
||||
ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY;
|
||||
ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str());
|
||||
g->BroadcastPacket(&data, true, -1, me->GetGUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
me->Say(msg, LANG_UNIVERSAL);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
410
src/Bot/BaseAi/Actions/GenericSpellActions.cpp
Normal file
410
src/Bot/BaseAi/Actions/GenericSpellActions.cpp
Normal file
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GenericSpellActions.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "ObjectDefines.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "Group.h"
|
||||
#include "Chat.h"
|
||||
#include "Language.h"
|
||||
#include "GenericBuffUtils.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
using ai::buff::MakeAuraQualifierForBuff;
|
||||
using ai::buff::UpgradeToGroupIfAppropriate;
|
||||
|
||||
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
|
||||
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell)
|
||||
{
|
||||
}
|
||||
|
||||
bool CastSpellAction::Execute(Event event)
|
||||
{
|
||||
if (spell == "conjure food" || spell == "conjure water")
|
||||
{
|
||||
// uint32 id = AI_VALUE2(uint32, "spell id", spell);
|
||||
// if (!id)
|
||||
// return false;
|
||||
|
||||
uint32 castId = 0;
|
||||
|
||||
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
|
||||
{
|
||||
uint32 spellId = itr->first;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
std::string const namepart = spellInfo->SpellName[0];
|
||||
std::wstring wnamepart;
|
||||
if (!Utf8toWStr(namepart, wnamepart))
|
||||
return false;
|
||||
|
||||
wstrToLower(wnamepart);
|
||||
|
||||
if (!Utf8FitTo(spell, wnamepart))
|
||||
continue;
|
||||
|
||||
if (spellInfo->Effects[0].Effect != SPELL_EFFECT_CREATE_ITEM)
|
||||
continue;
|
||||
|
||||
uint32 itemId = spellInfo->Effects[0].ItemType;
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!proto)
|
||||
continue;
|
||||
|
||||
if (bot->CanUseItem(proto) != EQUIP_ERR_OK)
|
||||
continue;
|
||||
|
||||
if (spellInfo->Id > castId)
|
||||
castId = spellInfo->Id;
|
||||
}
|
||||
|
||||
return botAI->CastSpell(castId, bot);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
|
||||
return false;
|
||||
|
||||
if (spell == "mount" && !bot->IsMounted() && !bot->IsInCombat())
|
||||
return true;
|
||||
|
||||
if (spell == "mount" && bot->IsInCombat())
|
||||
{
|
||||
bot->Dismount();
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* spellTarget = GetTarget();
|
||||
if (!spellTarget)
|
||||
return false;
|
||||
|
||||
if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId())
|
||||
return false;
|
||||
|
||||
// float combatReach = bot->GetCombatReach() + spellTarget->GetCombatReach();
|
||||
// if (!botAI->IsRanged(bot))
|
||||
// combatReach += 4.0f / 3.0f;
|
||||
|
||||
return spellTarget &&
|
||||
AI_VALUE2(bool, "spell cast useful",
|
||||
spell); // && sServerFacade->GetDistance2d(bot, spellTarget) <= (range + combatReach);
|
||||
}
|
||||
|
||||
CastMeleeSpellAction::CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
|
||||
{
|
||||
range = ATTACK_DISTANCE;
|
||||
}
|
||||
|
||||
bool CastMeleeSpellAction::isUseful()
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!bot->IsWithinMeleeRange(target))
|
||||
return false;
|
||||
|
||||
return CastSpellAction::isUseful();
|
||||
}
|
||||
|
||||
CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
|
||||
{
|
||||
range = ATTACK_DISTANCE;
|
||||
}
|
||||
|
||||
bool CastMeleeDebuffSpellAction::isUseful()
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!bot->IsWithinMeleeRange(target))
|
||||
return false;
|
||||
|
||||
return CastDebuffSpellAction::isUseful();
|
||||
}
|
||||
|
||||
bool CastAuraSpellAction::isUseful()
|
||||
{
|
||||
if (!GetTarget() || !CastSpellAction::isUseful())
|
||||
return false;
|
||||
Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration);
|
||||
if (!aura)
|
||||
return true;
|
||||
if (beforeDuration && aura->GetDuration() < beforeDuration)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
CastEnchantItemAction::CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell)
|
||||
: CastSpellAction(botAI, spell)
|
||||
{
|
||||
range = botAI->GetRange("spell");
|
||||
}
|
||||
|
||||
bool CastEnchantItemAction::isPossible()
|
||||
{
|
||||
// if (!CastSpellAction::isPossible())
|
||||
// {
|
||||
// botAI->TellMasterNoFacing("Impossible: " + spell);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
|
||||
|
||||
// bool ok = AI_VALUE2(Item*, "item for spell", spellId);
|
||||
// Item* item = AI_VALUE2(Item*, "item for spell", spellId);
|
||||
// botAI->TellMasterNoFacing("spell: " + spell + ", spell id: " + std::to_string(spellId) + " item for spell: " +
|
||||
// std::to_string(ok));
|
||||
return spellId && AI_VALUE2(Item*, "item for spell", spellId);
|
||||
}
|
||||
|
||||
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
|
||||
HealingManaEfficiency manaEfficiency, bool isOwner)
|
||||
: CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency)
|
||||
{
|
||||
range = botAI->GetRange("heal");
|
||||
}
|
||||
|
||||
bool CastHealingSpellAction::isUseful() { return CastAuraSpellAction::isUseful(); }
|
||||
|
||||
bool CastAoeHealSpellAction::isUseful() { return CastSpellAction::isUseful(); }
|
||||
|
||||
CastCureSpellAction::CastCureSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
|
||||
{
|
||||
range = botAI->GetRange("heal");
|
||||
}
|
||||
|
||||
Value<Unit*>* CurePartyMemberAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member to dispel", dispelType);
|
||||
}
|
||||
|
||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
||||
// TODO Priest doen't verify il he have components
|
||||
Value<Unit*>* BuffOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
|
||||
}
|
||||
|
||||
bool BuffOnPartyAction::Execute(Event event)
|
||||
{
|
||||
std::string castName = spell; // default = mono
|
||||
|
||||
auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP);
|
||||
|
||||
return botAI->CastSpell(castName, GetTarget());
|
||||
}
|
||||
// End greater buff fix
|
||||
|
||||
CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot")
|
||||
{
|
||||
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
|
||||
{
|
||||
spell = "shoot";
|
||||
|
||||
switch (pItem->GetTemplate()->SubClass)
|
||||
{
|
||||
case ITEM_SUBCLASS_WEAPON_GUN:
|
||||
spell += " gun";
|
||||
break;
|
||||
case ITEM_SUBCLASS_WEAPON_BOW:
|
||||
spell += " bow";
|
||||
break;
|
||||
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
|
||||
spell += " crossbow";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("attacker without aura", spell);
|
||||
}
|
||||
|
||||
Value<Unit*>* CastDebuffSpellOnMeleeAttackerAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("melee attacker without aura", spell);
|
||||
}
|
||||
|
||||
CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration)
|
||||
: CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration)
|
||||
{
|
||||
range = botAI->GetRange("spell");
|
||||
}
|
||||
|
||||
Value<Unit*>* CastSpellOnEnemyHealerAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("enemy healer target", spell);
|
||||
}
|
||||
|
||||
Value<Unit*>* CastSnareSpellAction::GetTargetValue() { return context->GetValue<Unit*>("snare target", spell); }
|
||||
|
||||
Value<Unit*>* CastCrowdControlSpellAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); }
|
||||
|
||||
bool CastCrowdControlSpellAction::Execute(Event event) { return botAI->CastSpell(getName(), GetTarget()); }
|
||||
|
||||
bool CastCrowdControlSpellAction::isPossible() { return botAI->CanCastSpell(getName(), GetTarget()); }
|
||||
|
||||
bool CastCrowdControlSpellAction::isUseful() { return true; }
|
||||
|
||||
std::string const CastProtectSpellAction::GetTargetName() { return "party member to protect"; }
|
||||
|
||||
bool CastProtectSpellAction::isUseful() { return GetTarget() && !botAI->HasAura(spell, GetTarget()); }
|
||||
|
||||
bool CastVehicleSpellAction::isPossible()
|
||||
{
|
||||
uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", spell);
|
||||
return botAI->CanCastVehicleSpell(spellId, GetTarget());
|
||||
}
|
||||
|
||||
bool CastVehicleSpellAction::isUseful() { return botAI->IsInVehicle(false, true); }
|
||||
|
||||
bool CastVehicleSpellAction::Execute(Event event)
|
||||
{
|
||||
uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", spell);
|
||||
return botAI->CastVehicleSpell(spellId, GetTarget());
|
||||
}
|
||||
|
||||
bool UseTrinketAction::Execute(Event event)
|
||||
{
|
||||
Item* trinket1 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET1);
|
||||
|
||||
if (trinket1 && UseTrinket(trinket1))
|
||||
return true;
|
||||
|
||||
Item* trinket2 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET2);
|
||||
if (trinket2 && UseTrinket(trinket2))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UseTrinketAction::UseTrinket(Item* item)
|
||||
{
|
||||
if (bot->CanUseItem(item) != EQUIP_ERR_OK)
|
||||
return false;
|
||||
|
||||
if (bot->IsNonMeleeSpellCast(true))
|
||||
return false;
|
||||
|
||||
uint8 bagIndex = item->GetBagSlot();
|
||||
uint8 slot = item->GetSlot();
|
||||
// uint8 spell_index = 0; //not used, line marked for removal.
|
||||
uint8 cast_count = 1;
|
||||
ObjectGuid item_guid = item->GetGUID();
|
||||
uint32 glyphIndex = 0;
|
||||
uint8 castFlags = 0;
|
||||
uint32 targetFlag = TARGET_FLAG_NONE;
|
||||
uint32 spellId = 0;
|
||||
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
||||
{
|
||||
if (item->GetTemplate()->Spells[i].SpellId > 0 && item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
|
||||
{
|
||||
spellId = item->GetTemplate()->Spells[i].SpellId;
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
if (!spellInfo || !spellInfo->IsPositive())
|
||||
return false;
|
||||
|
||||
bool applyAura = false;
|
||||
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
|
||||
{
|
||||
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
|
||||
if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
|
||||
{
|
||||
applyAura = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!applyAura)
|
||||
return false;
|
||||
|
||||
uint32 spellProcFlag = spellInfo->ProcFlags;
|
||||
|
||||
// Handle items with procflag "if you kill a target that grants honor or experience"
|
||||
// Bots will "learn" the trinket proc, so CanCastSpell() will be true
|
||||
// e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to
|
||||
// constant casting of the proc spell onto themselfes https://www.wowhead.com/wotlk/spell=59787/oracle-ablutions
|
||||
// This will lead to multiple hundreds of entries in m_appliedAuras -> Once killing an enemy -> Big diff time spikes
|
||||
if (spellProcFlag != 0) return false;
|
||||
|
||||
if (!botAI->CanCastSpell(spellId, bot, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!spellId)
|
||||
return false;
|
||||
WorldPacket packet(CMSG_USE_ITEM);
|
||||
packet << bagIndex << slot << cast_count << spellId << item_guid << glyphIndex << castFlags;
|
||||
|
||||
targetFlag = TARGET_FLAG_NONE;
|
||||
packet << targetFlag << bot->GetPackGUID();
|
||||
bot->GetSession()->HandleUseItemOpcode(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
Value<Unit*>* BuffOnMainTankAction::GetTargetValue() { return context->GetValue<Unit*>("main tank", spell); }
|
||||
|
||||
bool CastDebuffSpellAction::isUseful()
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return CastAuraSpellAction::isUseful() &&
|
||||
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
|
||||
}
|
||||
438
src/Bot/BaseAi/Actions/GenericSpellActions.h
Normal file
438
src/Bot/BaseAi/Actions/GenericSpellActions.h
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GENERICSPELLACTIONS_H
|
||||
#define _PLAYERBOT_GENERICSPELLACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "UseItemAction.h"
|
||||
#include "Value.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
class Unit;
|
||||
class WorldObject;
|
||||
|
||||
class CastSpellAction : public Action
|
||||
{
|
||||
public:
|
||||
CastSpellAction(PlayerbotAI* botAI, std::string const spell);
|
||||
|
||||
std::string const GetTargetName() override { return "current target"; };
|
||||
bool Execute(Event event) override;
|
||||
bool isPossible() override;
|
||||
bool isUseful() override;
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::Single; }
|
||||
|
||||
std::vector<NextAction> getPrerequisites() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string const getSpell() { return spell; }
|
||||
|
||||
protected:
|
||||
std::string spell;
|
||||
float range;
|
||||
};
|
||||
|
||||
class CastAuraSpellAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastAuraSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, bool checkDuration = false, uint32 beforeDuration = 0)
|
||||
: CastSpellAction(botAI, spell)
|
||||
{
|
||||
this->isOwner = isOwner;
|
||||
this->beforeDuration = beforeDuration;
|
||||
this->checkDuration = checkDuration;
|
||||
}
|
||||
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
bool isOwner;
|
||||
bool checkDuration;
|
||||
uint32 beforeDuration;
|
||||
};
|
||||
|
||||
class CastMeleeSpellAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell);
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class CastDebuffSpellAction : public CastAuraSpellAction
|
||||
{
|
||||
public:
|
||||
CastDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, float needLifeTime = 8.0f)
|
||||
: CastAuraSpellAction(botAI, spell, isOwner), needLifeTime(needLifeTime)
|
||||
{
|
||||
}
|
||||
bool isUseful() override;
|
||||
|
||||
private:
|
||||
float needLifeTime;
|
||||
};
|
||||
|
||||
class CastMeleeDebuffSpellAction : public CastDebuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastMeleeDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, float needLifeTime = 8.0f);
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class CastDebuffSpellOnAttackerAction : public CastDebuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastDebuffSpellOnAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
|
||||
float needLifeTime = 8.0f)
|
||||
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
|
||||
{
|
||||
}
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
std::string const getName() override { return spell + " on attacker"; }
|
||||
// ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
|
||||
};
|
||||
|
||||
class CastDebuffSpellOnMeleeAttackerAction : public CastDebuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastDebuffSpellOnMeleeAttackerAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = true,
|
||||
float needLifeTime = 8.0f)
|
||||
: CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
|
||||
{
|
||||
}
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
std::string const getName() override { return spell + " on attacker"; }
|
||||
// ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
|
||||
};
|
||||
|
||||
class CastBuffSpellAction : public CastAuraSpellAction
|
||||
{
|
||||
public:
|
||||
CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, uint32 beforeDuration = 0);
|
||||
|
||||
std::string const GetTargetName() override { return "self target"; }
|
||||
};
|
||||
|
||||
class CastEnchantItemAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell);
|
||||
|
||||
bool isPossible() override;
|
||||
std::string const GetTargetName() override { return "self target"; }
|
||||
};
|
||||
|
||||
class CastHealingSpellAction : public CastAuraSpellAction
|
||||
{
|
||||
public:
|
||||
CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
|
||||
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true);
|
||||
|
||||
std::string const GetTargetName() override { return "self target"; }
|
||||
bool isUseful() override;
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
|
||||
// Yunfan: Mana efficiency tell the bot how to save mana. The higher the better.
|
||||
HealingManaEfficiency manaEfficiency;
|
||||
uint8 estAmount;
|
||||
|
||||
// protected:
|
||||
};
|
||||
|
||||
class CastAoeHealSpellAction : public CastHealingSpellAction
|
||||
{
|
||||
public:
|
||||
CastAoeHealSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
|
||||
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM)
|
||||
: CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency)
|
||||
{
|
||||
}
|
||||
|
||||
std::string const GetTargetName() override { return "party member to heal"; }
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class CastCureSpellAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastCureSpellAction(PlayerbotAI* botAI, std::string const spell);
|
||||
|
||||
std::string const GetTargetName() override { return "self target"; }
|
||||
};
|
||||
|
||||
class PartyMemberActionNameSupport
|
||||
{
|
||||
public:
|
||||
PartyMemberActionNameSupport(std::string const spell) { name = std::string(spell + " on party"); }
|
||||
|
||||
std::string const getName() { return name; }
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class HealPartyMemberAction : public CastHealingSpellAction, public PartyMemberActionNameSupport
|
||||
{
|
||||
public:
|
||||
HealPartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
|
||||
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true)
|
||||
: CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell)
|
||||
{
|
||||
}
|
||||
|
||||
std::string const GetTargetName() override { return "party member to heal"; }
|
||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||
};
|
||||
|
||||
class ResurrectPartyMemberAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
ResurrectPartyMemberAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
||||
|
||||
std::string const GetTargetName() override { return "party member to resurrect"; }
|
||||
std::vector<NextAction> getPrerequisites() override
|
||||
{
|
||||
return NextAction::merge(
|
||||
{ NextAction("reach party member to resurrect") },
|
||||
Action::getPrerequisites()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class CurePartyMemberAction : public CastSpellAction, public PartyMemberActionNameSupport
|
||||
{
|
||||
public:
|
||||
CurePartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint32 dispelType)
|
||||
: CastSpellAction(botAI, spell), PartyMemberActionNameSupport(spell), dispelType(dispelType)
|
||||
{
|
||||
}
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||
|
||||
protected:
|
||||
uint32 dispelType;
|
||||
};
|
||||
|
||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
||||
class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport
|
||||
{
|
||||
public:
|
||||
BuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
|
||||
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) { }
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
bool Execute(Event event) override;
|
||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||
};
|
||||
// End Fix
|
||||
|
||||
class CastShootAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastShootAction(PlayerbotAI* botAI);
|
||||
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::None; }
|
||||
};
|
||||
|
||||
class CastLifeBloodAction : public CastHealingSpellAction
|
||||
{
|
||||
public:
|
||||
CastLifeBloodAction(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, "lifeblood") {}
|
||||
};
|
||||
|
||||
class CastGiftOfTheNaaruAction : public CastHealingSpellAction
|
||||
{
|
||||
public:
|
||||
CastGiftOfTheNaaruAction(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, "gift of the naaru") {}
|
||||
};
|
||||
|
||||
class CastArcaneTorrentAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastArcaneTorrentAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "arcane torrent") {}
|
||||
};
|
||||
|
||||
class CastManaTapAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastManaTapAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mana tap") {}
|
||||
};
|
||||
|
||||
class CastWarStompAction : public CastMeleeSpellAction
|
||||
{
|
||||
public:
|
||||
CastWarStompAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "war stomp") {}
|
||||
};
|
||||
|
||||
class CastBloodFuryAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastBloodFuryAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "blood fury") {}
|
||||
};
|
||||
|
||||
class CastBerserkingAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastBerserkingAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "berserking") {}
|
||||
};
|
||||
|
||||
class UseTrinketAction : public Action
|
||||
{
|
||||
public:
|
||||
UseTrinketAction(PlayerbotAI* botAI) : Action(botAI, "use trinket") {}
|
||||
bool Execute(Event event) override;
|
||||
protected:
|
||||
bool UseTrinket(Item* trinket);
|
||||
};
|
||||
|
||||
class CastSpellOnEnemyHealerAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastSpellOnEnemyHealerAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
std::string const getName() override { return spell + " on enemy healer"; }
|
||||
};
|
||||
|
||||
class CastSnareSpellAction : public CastDebuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastSnareSpellAction(PlayerbotAI* botAI, std::string const spell) : CastDebuffSpellAction(botAI, spell) {}
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
std::string const getName() override { return spell + " on snare target"; }
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::None; }
|
||||
};
|
||||
|
||||
class CastCrowdControlSpellAction : public CastBuffSpellAction
|
||||
{
|
||||
public:
|
||||
CastCrowdControlSpellAction(PlayerbotAI* botAI, std::string const spell) : CastBuffSpellAction(botAI, spell) {}
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
bool Execute(Event event) override;
|
||||
bool isPossible() override;
|
||||
bool isUseful() override;
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::None; }
|
||||
};
|
||||
|
||||
class CastProtectSpellAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastProtectSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
|
||||
|
||||
std::string const GetTargetName() override;
|
||||
bool isUseful() override;
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::None; }
|
||||
};
|
||||
|
||||
class CastVehicleSpellAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastVehicleSpellAction(PlayerbotAI* botAI, std::string const& spell) : CastSpellAction(botAI, spell)
|
||||
{
|
||||
range = 120.0f;
|
||||
}
|
||||
|
||||
std::string const GetTargetName() override { return "current target"; }
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
bool isPossible() override;
|
||||
ActionThreatType getThreatType() override { return ActionThreatType::None; }
|
||||
|
||||
protected:
|
||||
WorldObject* spellTarget;
|
||||
};
|
||||
|
||||
class CastHurlBoulderAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastHurlBoulderAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "hurl boulder") {}
|
||||
};
|
||||
|
||||
class CastSteamRushAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastSteamRushAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "steam rush") {}
|
||||
};
|
||||
|
||||
class CastRamAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastRamAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "ram") {}
|
||||
};
|
||||
|
||||
class CastNapalmAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastNapalmAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "napalm") {}
|
||||
};
|
||||
|
||||
class CastFireCannonAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastFireCannonAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "fire cannon") {}
|
||||
};
|
||||
|
||||
class CastSteamBlastAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastSteamBlastAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "steam blast") {}
|
||||
};
|
||||
|
||||
class CastIncendiaryRocketAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastIncendiaryRocketAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "incendiary rocket") {}
|
||||
};
|
||||
|
||||
class CastRocketBlastAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastRocketBlastAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "rocket blast") {}
|
||||
};
|
||||
|
||||
class CastGlaiveThrowAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastGlaiveThrowAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "glaive throw") {}
|
||||
};
|
||||
|
||||
class CastBladeSalvoAction : public CastVehicleSpellAction
|
||||
{
|
||||
public:
|
||||
CastBladeSalvoAction(PlayerbotAI* botAI) : CastVehicleSpellAction(botAI, "blade salvo") {}
|
||||
};
|
||||
|
||||
class MainTankActionNameSupport
|
||||
{
|
||||
public:
|
||||
MainTankActionNameSupport(std::string spell) { name = std::string(spell) + " on main tank"; }
|
||||
|
||||
virtual std::string const getName() { return name; }
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class BuffOnMainTankAction : public CastBuffSpellAction, public MainTankActionNameSupport
|
||||
{
|
||||
public:
|
||||
BuffOnMainTankAction(PlayerbotAI* ai, std::string spell, bool checkIsOwner = false)
|
||||
: CastBuffSpellAction(ai, spell, checkIsOwner), MainTankActionNameSupport(spell)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Value<Unit*>* GetTargetValue();
|
||||
virtual std::string const getName() { return MainTankActionNameSupport::getName(); }
|
||||
};
|
||||
#endif
|
||||
92
src/Bot/BaseAi/Actions/GiveItemAction.cpp
Normal file
92
src/Bot/BaseAi/Actions/GiveItemAction.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GiveItemAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemCountValue.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
std::vector<std::string> split(std::string const s, char delim);
|
||||
|
||||
bool GiveItemAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = GetTarget();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
Player* receiver = dynamic_cast<Player*>(target);
|
||||
if (!receiver)
|
||||
return false;
|
||||
|
||||
PlayerbotAI* receiverAi = GET_PLAYERBOT_AI(receiver);
|
||||
if (!receiverAi)
|
||||
return false;
|
||||
|
||||
if (receiverAi->GetAiObjectContext()->GetValue<uint32>("item count", item)->Get())
|
||||
return true;
|
||||
|
||||
bool moved = false;
|
||||
std::vector<Item*> items = InventoryAction::parseItems(item, ITERATE_ITEMS_IN_BAGS);
|
||||
for (Item* item : items)
|
||||
{
|
||||
if (receiver->CanUseItem(item->GetTemplate()) != EQUIP_ERR_OK)
|
||||
continue;
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = receiver->CanStoreItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
if (msg == EQUIP_ERR_OK)
|
||||
{
|
||||
bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
|
||||
item->SetOwnerGUID(target->GetGUID());
|
||||
receiver->MoveItemToInventory(dest, item, true);
|
||||
moved = true;
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Got " << chat->FormatItem(item->GetTemplate(), item->GetCount()) << " from " << bot->GetName();
|
||||
receiverAi->TellMasterNoFacing(out.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Cannot get " << chat->FormatItem(item->GetTemplate(), item->GetCount()) << " from "
|
||||
<< bot->GetName() << "- my bags are full";
|
||||
receiverAi->TellError(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Unit* GiveItemAction::GetTarget() { return AI_VALUE2(Unit*, "party member without item", item); }
|
||||
|
||||
bool GiveItemAction::isUseful()
|
||||
{
|
||||
return GetTarget() && AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig->lowMana;
|
||||
}
|
||||
|
||||
Unit* GiveFoodAction::GetTarget() { return AI_VALUE(Unit*, "party member without food"); }
|
||||
|
||||
bool GiveFoodAction::isUseful()
|
||||
{
|
||||
if (!GetTarget())
|
||||
return false;
|
||||
|
||||
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget());
|
||||
|
||||
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
|
||||
}
|
||||
|
||||
Unit* GiveWaterAction::GetTarget() { return AI_VALUE(Unit*, "party member without water"); }
|
||||
|
||||
bool GiveWaterAction::isUseful()
|
||||
{
|
||||
if (!GetTarget())
|
||||
return false;
|
||||
|
||||
bool isRandomBot = GetTarget()->IsPlayer() && sRandomPlayerbotMgr->IsRandomBot((Player*)GetTarget());
|
||||
|
||||
return !isRandomBot || (isRandomBot && !botAI->HasCheat(BotCheatMask::food));
|
||||
}
|
||||
47
src/Bot/BaseAi/Actions/GiveItemAction.h
Normal file
47
src/Bot/BaseAi/Actions/GiveItemAction.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GIVEITEMACTION_H
|
||||
#define _PLAYERBOT_GIVEITEMACTION_H
|
||||
|
||||
#include "InventoryAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class GiveItemAction : public InventoryAction
|
||||
{
|
||||
public:
|
||||
GiveItemAction(PlayerbotAI* botAI, std::string const name, std::string const item)
|
||||
: InventoryAction(botAI, name), item(item)
|
||||
{
|
||||
}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
Unit* GetTarget() override;
|
||||
|
||||
protected:
|
||||
std::string const item;
|
||||
};
|
||||
|
||||
class GiveFoodAction : public GiveItemAction
|
||||
{
|
||||
public:
|
||||
GiveFoodAction(PlayerbotAI* botAI) : GiveItemAction(botAI, "give food", "conjured food") {}
|
||||
|
||||
bool isUseful() override;
|
||||
Unit* GetTarget() override;
|
||||
};
|
||||
|
||||
class GiveWaterAction : public GiveItemAction
|
||||
{
|
||||
public:
|
||||
GiveWaterAction(PlayerbotAI* botAI) : GiveItemAction(botAI, "give water", "conjured water") {}
|
||||
|
||||
bool isUseful() override;
|
||||
Unit* GetTarget() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
227
src/Bot/BaseAi/Actions/GoAction.cpp
Normal file
227
src/Bot/BaseAi/Actions/GoAction.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GoAction.h"
|
||||
|
||||
#include "ChooseTravelTargetAction.h"
|
||||
#include "Event.h"
|
||||
#include "Formations.h"
|
||||
#include "PathGenerator.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PositionValue.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
std::vector<std::string> split(std::string const s, char delim);
|
||||
char* strstri(char const* haystack, char const* needle);
|
||||
|
||||
bool GoAction::Execute(Event event)
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
std::string const param = event.getParam();
|
||||
if (param == "?")
|
||||
{
|
||||
float x = bot->GetPositionX();
|
||||
float y = bot->GetPositionY();
|
||||
Map2ZoneCoordinates(x, y, bot->GetZoneId());
|
||||
|
||||
std::ostringstream out;
|
||||
out << "I am at " << x << "," << y;
|
||||
botAI->TellMaster(out.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (param.find("travel") != std::string::npos && param.size() > 7)
|
||||
{
|
||||
WorldPosition botPos(bot);
|
||||
|
||||
std::string const destination = param.substr(7);
|
||||
|
||||
TravelTarget* target = context->GetValue<TravelTarget*>("travel target")->Get();
|
||||
|
||||
if (TravelDestination* dest = ChooseTravelTargetAction::FindDestination(bot, destination))
|
||||
{
|
||||
std::vector<WorldPosition*> points = dest->nextPoint(const_cast<WorldPosition*>(&botPos), true);
|
||||
if (points.empty())
|
||||
return false;
|
||||
|
||||
target->setTarget(dest, points.front());
|
||||
target->setForced(true);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Traveling to " << dest->getTitle();
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
botAI->TellMasterNoFacing("Clearing travel target");
|
||||
target->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition);
|
||||
target->setForced(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GuidVector gos = ChatHelper::parseGameobjects(param);
|
||||
if (!gos.empty())
|
||||
{
|
||||
for (ObjectGuid const guid : gos)
|
||||
{
|
||||
if (GameObject* go = botAI->GetGameObject(guid))
|
||||
if (go->isSpawned())
|
||||
{
|
||||
if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, go),
|
||||
sPlayerbotAIConfig->reactDistance))
|
||||
{
|
||||
botAI->TellError("It is too far away");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Moving to " << ChatHelper::FormatGameobject(go);
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
return MoveNear(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ() + 0.5f,
|
||||
sPlayerbotAIConfig->followDistance);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
GuidVector units;
|
||||
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
|
||||
units.insert(units.end(), npcs.begin(), npcs.end());
|
||||
GuidVector players = AI_VALUE(GuidVector, "nearest friendly players");
|
||||
units.insert(units.end(), players.begin(), players.end());
|
||||
for (ObjectGuid const guid : units)
|
||||
{
|
||||
if (Unit* unit = botAI->GetUnit(guid))
|
||||
if (strstri(unit->GetName().c_str(), param.c_str()))
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Moving to " << unit->GetName();
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
return MoveNear(bot->GetMapId(), unit->GetPositionX(), unit->GetPositionY(),
|
||||
unit->GetPositionZ() + 0.5f, sPlayerbotAIConfig->followDistance);
|
||||
}
|
||||
}
|
||||
|
||||
if (param.find(";") != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> coords = split(param, ';');
|
||||
float x = atof(coords[0].c_str());
|
||||
float y = atof(coords[1].c_str());
|
||||
float z;
|
||||
if (coords.size() > 2)
|
||||
z = atof(coords[2].c_str());
|
||||
else
|
||||
z = bot->GetPositionZ();
|
||||
|
||||
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||||
{
|
||||
PathGenerator path(bot);
|
||||
|
||||
path.CalculatePath(x, y, z, false);
|
||||
|
||||
Movement::Vector3 end = path.GetEndPosition();
|
||||
Movement::Vector3 aend = path.GetActualEndPosition();
|
||||
|
||||
Movement::PointsArray const& points = path.GetPath();
|
||||
PathType type = path.GetPathType();
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
out << x << ";" << y << ";" << z << " =";
|
||||
|
||||
out << "path is: ";
|
||||
|
||||
out << type;
|
||||
|
||||
out << " of length ";
|
||||
|
||||
out << points.size();
|
||||
|
||||
out << " with offset ";
|
||||
|
||||
out << (end - aend).length();
|
||||
|
||||
for (auto i : points)
|
||||
{
|
||||
CreateWp(bot, i.x, i.y, i.z, 0.f, 11144);
|
||||
}
|
||||
|
||||
botAI->TellMaster(out);
|
||||
}
|
||||
|
||||
if (bot->IsWithinLOS(x, y, z))
|
||||
return MoveNear(bot->GetMapId(), x, y, z, 0);
|
||||
else
|
||||
return MoveTo(bot->GetMapId(), x, y, z, false, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (param.find(",") != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> coords = split(param, ',');
|
||||
float x = atof(coords[0].c_str());
|
||||
float y = atof(coords[1].c_str());
|
||||
Zone2MapCoordinates(x, y, bot->GetZoneId());
|
||||
|
||||
Map* map = bot->GetMap();
|
||||
float z = bot->GetPositionZ();
|
||||
bot->UpdateAllowedPositionZ(x, y, z);
|
||||
|
||||
if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, x, y),
|
||||
sPlayerbotAIConfig->reactDistance))
|
||||
{
|
||||
botAI->TellMaster("It is too far away");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight()))
|
||||
{
|
||||
botAI->TellError("It is in water");
|
||||
return false;
|
||||
}
|
||||
|
||||
float ground = map->GetHeight(x, y, z + 0.5f);
|
||||
if (ground <= INVALID_HEIGHT)
|
||||
{
|
||||
botAI->TellError("I can't go there");
|
||||
return false;
|
||||
}
|
||||
|
||||
float x1 = x, y1 = y;
|
||||
Map2ZoneCoordinates(x1, y1, bot->GetZoneId());
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Moving to " << x1 << "," << y1;
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
|
||||
return MoveNear(bot->GetMapId(), x, y, z + 0.5f, sPlayerbotAIConfig->followDistance);
|
||||
}
|
||||
|
||||
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[param];
|
||||
if (pos.isSet())
|
||||
{
|
||||
if (sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, pos.x, pos.y),
|
||||
sPlayerbotAIConfig->reactDistance))
|
||||
{
|
||||
botAI->TellError("It is too far away");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Moving to position " << param;
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
23
src/Bot/BaseAi/Actions/GoAction.h
Normal file
23
src/Bot/BaseAi/Actions/GoAction.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GOACTION_H
|
||||
#define _PLAYERBOT_GOACTION_H
|
||||
|
||||
#include "MovementActions.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
class TravelDestination;
|
||||
class WorldPosition;
|
||||
|
||||
class GoAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
GoAction(PlayerbotAI* botAI) : MovementAction(botAI, "Go") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
152
src/Bot/BaseAi/Actions/GossipHelloAction.cpp
Normal file
152
src/Bot/BaseAi/Actions/GossipHelloAction.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GossipHelloAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "GossipDef.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool GossipHelloAction::Execute(Event event)
|
||||
{
|
||||
ObjectGuid guid;
|
||||
|
||||
WorldPacket& p = event.getPacket();
|
||||
if (p.empty())
|
||||
{
|
||||
Player* master = GetMaster();
|
||||
if (master)
|
||||
guid = master->GetTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
p.rpos(0);
|
||||
p >> guid;
|
||||
}
|
||||
|
||||
std::string const text = event.getParam();
|
||||
int32 menuToSelect = -1;
|
||||
if (!text.empty())
|
||||
menuToSelect = atoi(text.c_str());
|
||||
|
||||
return Execute(guid, menuToSelect, false);
|
||||
}
|
||||
|
||||
void GossipHelloAction::TellGossipText(uint32 textId)
|
||||
{
|
||||
if (!textId)
|
||||
return;
|
||||
|
||||
if (GossipText const* text = sObjectMgr->GetGossipText(textId))
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_GOSSIP_TEXT_OPTIONS; i++)
|
||||
{
|
||||
std::string const text0 = text->Options[i].Text_0;
|
||||
if (!text0.empty())
|
||||
botAI->TellMasterNoFacing(text0);
|
||||
|
||||
std::string const text1 = text->Options[i].Text_1;
|
||||
if (!text1.empty())
|
||||
botAI->TellMasterNoFacing(text1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GossipHelloAction::TellGossipMenus()
|
||||
{
|
||||
if (!bot->PlayerTalkClass)
|
||||
return;
|
||||
|
||||
Creature* pCreature = bot->GetNPCIfCanInteractWith(GetMaster()->GetTarget(), UNIT_NPC_FLAG_NONE);
|
||||
GossipMenu& menu = bot->PlayerTalkClass->GetGossipMenu();
|
||||
if (pCreature)
|
||||
{
|
||||
uint32 textId = bot->GetGossipTextId(menu.GetMenuId(), pCreature);
|
||||
TellGossipText(textId);
|
||||
}
|
||||
|
||||
GossipMenuItemContainer const& items = menu.GetMenuItems();
|
||||
for (auto iter = items.begin(); iter != items.end(); iter++)
|
||||
{
|
||||
GossipMenuItem const* item = &(iter->second);
|
||||
std::ostringstream out;
|
||||
out << "[" << iter->first << "] " << item->Message;
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
bool GossipHelloAction::ProcessGossip(int32 menuToSelect, bool silent)
|
||||
{
|
||||
GossipMenu& menu = bot->PlayerTalkClass->GetGossipMenu();
|
||||
if (menuToSelect != -1 && !menu.GetItem(menuToSelect))
|
||||
{
|
||||
if (!silent)
|
||||
botAI->TellError("Unknown gossip option");
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPacket p;
|
||||
std::string code;
|
||||
p << GetMaster()->GetTarget();
|
||||
p << menu.GetMenuId() << menuToSelect;
|
||||
p << code;
|
||||
bot->GetSession()->HandleGossipSelectOptionOpcode(p);
|
||||
|
||||
if (!silent)
|
||||
TellGossipMenus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GossipHelloAction::Execute(ObjectGuid guid, int32 menuToSelect, bool silent)
|
||||
{
|
||||
if (!guid)
|
||||
return false;
|
||||
|
||||
Creature* pCreature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
|
||||
if (!pCreature)
|
||||
{
|
||||
LOG_DEBUG("playerbots",
|
||||
"[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO {} not found or you can't "
|
||||
"interact with him.",
|
||||
guid.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
GossipMenuItemsMapBounds pMenuItemBounds =
|
||||
sObjectMgr->GetGossipMenuItemsMapBounds(pCreature->GetCreatureTemplate()->GossipMenuId);
|
||||
if (pMenuItemBounds.first == pMenuItemBounds.second)
|
||||
return false;
|
||||
|
||||
if (menuToSelect == -1)
|
||||
{
|
||||
WorldPacket p1;
|
||||
p1 << guid;
|
||||
bot->GetSession()->HandleGossipHelloOpcode(p1);
|
||||
bot->SetFacingToObject(pCreature);
|
||||
|
||||
if (!silent)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "--- " << pCreature->GetName() << " ---";
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
TellGossipMenus();
|
||||
}
|
||||
}
|
||||
else if (!bot->PlayerTalkClass)
|
||||
{
|
||||
if (!silent)
|
||||
botAI->TellError("I need to talk first");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ProcessGossip(menuToSelect, silent))
|
||||
return false;
|
||||
}
|
||||
|
||||
bot->TalkedToCreature(pCreature->GetEntry(), pCreature->GetGUID());
|
||||
return true;
|
||||
}
|
||||
28
src/Bot/BaseAi/Actions/GossipHelloAction.h
Normal file
28
src/Bot/BaseAi/Actions/GossipHelloAction.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GOSSIPHELLOACTION_H
|
||||
#define _PLAYERBOT_GOSSIPHELLOACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class GossipHelloAction : public Action
|
||||
{
|
||||
public:
|
||||
GossipHelloAction(PlayerbotAI* botAI) : Action(botAI, "gossip hello") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
// Overload for direct usage
|
||||
bool Execute(ObjectGuid guid, int32 menuToSelect, bool silent = false);
|
||||
|
||||
private:
|
||||
void TellGossipMenus();
|
||||
bool ProcessGossip(int32 menuToSelect, bool silent);
|
||||
void TellGossipText(uint32 textId);
|
||||
};
|
||||
|
||||
#endif
|
||||
42
src/Bot/BaseAi/Actions/GreetAction.cpp
Normal file
42
src/Bot/BaseAi/Actions/GreetAction.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GreetAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
GreetAction::GreetAction(PlayerbotAI* botAI) : Action(botAI, "greet") {}
|
||||
|
||||
bool GreetAction::Execute(Event event)
|
||||
{
|
||||
ObjectGuid guid = AI_VALUE(ObjectGuid, "new player nearby");
|
||||
if (!guid || !guid.IsPlayer())
|
||||
return false;
|
||||
|
||||
Player* player = dynamic_cast<Player*>(botAI->GetUnit(guid));
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
if (!bot->HasInArc(CAST_ANGLE_IN_FRONT, player, sPlayerbotAIConfig->sightDistance))
|
||||
bot->SetFacingToObject(player);
|
||||
|
||||
ObjectGuid oldSel = bot->GetTarget();
|
||||
bot->SetTarget(guid);
|
||||
// bot->HandleEmote(EMOTE_ONESHOT_WAVE);
|
||||
botAI->PlayEmote(TEXT_EMOTE_HELLO);
|
||||
bot->SetTarget(oldSel);
|
||||
|
||||
GuidSet& alreadySeenPlayers = botAI->GetAiObjectContext()->GetValue<GuidSet&>("already seen players")->Get();
|
||||
alreadySeenPlayers.insert(guid);
|
||||
|
||||
GuidVector nearestPlayers = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest friendly players")->Get();
|
||||
for (ObjectGuid const guid : nearestPlayers)
|
||||
{
|
||||
alreadySeenPlayers.insert(guid);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/GreetAction.h
Normal file
21
src/Bot/BaseAi/Actions/GreetAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GREETACTION_H
|
||||
#define _PLAYERBOT_GREETACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class GreetAction : public Action
|
||||
{
|
||||
public:
|
||||
GreetAction(PlayerbotAI* botAI);
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
57
src/Bot/BaseAi/Actions/GuildAcceptAction.cpp
Normal file
57
src/Bot/BaseAi/Actions/GuildAcceptAction.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GuildAcceptAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "GuildPackets.h"
|
||||
#include "PlayerbotSecurity.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool GuildAcceptAction::Execute(Event event)
|
||||
{
|
||||
WorldPacket p(event.getPacket());
|
||||
p.rpos(0);
|
||||
Player* inviter = nullptr;
|
||||
std::string Invitedname;
|
||||
p >> Invitedname;
|
||||
|
||||
if (normalizePlayerName(Invitedname))
|
||||
inviter = ObjectAccessor::FindPlayerByName(Invitedname.c_str());
|
||||
|
||||
if (!inviter)
|
||||
return false;
|
||||
|
||||
bool accept = true;
|
||||
uint32 guildId = inviter->GetGuildId();
|
||||
if (!guildId)
|
||||
{
|
||||
botAI->TellError("You are not in a guild!");
|
||||
accept = false;
|
||||
}
|
||||
else if (bot->GetGuildId())
|
||||
{
|
||||
botAI->TellError("Sorry, I am in a guild already");
|
||||
accept = false;
|
||||
}
|
||||
else if (!botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter, true))
|
||||
{
|
||||
botAI->TellError("Sorry, I don't want to join your guild :(");
|
||||
accept = false;
|
||||
}
|
||||
|
||||
if (accept)
|
||||
{
|
||||
WorldPackets::Guild::AcceptGuildInvite data = WorldPacket(CMSG_GUILD_ACCEPT);
|
||||
bot->GetSession()->HandleGuildAcceptOpcode(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldPackets::Guild::GuildDeclineInvitation data = WorldPacket(CMSG_GUILD_DECLINE);
|
||||
bot->GetSession()->HandleGuildDeclineOpcode(data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
21
src/Bot/BaseAi/Actions/GuildAcceptAction.h
Normal file
21
src/Bot/BaseAi/Actions/GuildAcceptAction.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#ifndef _PLAYERBOT_GUILDACCEPTACTION_H
|
||||
#define _PLAYERBOT_GUILDACCEPTACTION_H
|
||||
|
||||
#include "Action.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
class GuildAcceptAction : public Action
|
||||
{
|
||||
public:
|
||||
GuildAcceptAction(PlayerbotAI* botAI) : Action(botAI, "guild accept") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
78
src/Bot/BaseAi/Actions/GuildBankAction.cpp
Normal file
78
src/Bot/BaseAi/Actions/GuildBankAction.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GuildBankAction.h"
|
||||
|
||||
#include "GuildMgr.h"
|
||||
#include "Playerbots.h"
|
||||
|
||||
bool GuildBankAction::Execute(Event event)
|
||||
{
|
||||
std::string const text = event.getParam();
|
||||
if (text.empty())
|
||||
return false;
|
||||
|
||||
if (!bot->GetGuildId() || (GetMaster() && GetMaster()->GetGuildId() != bot->GetGuildId()))
|
||||
{
|
||||
botAI->TellMaster("I'm not in your guild!");
|
||||
return false;
|
||||
}
|
||||
|
||||
GuidVector gos = *botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest game objects");
|
||||
for (GuidVector::iterator i = gos.begin(); i != gos.end(); ++i)
|
||||
{
|
||||
GameObject* go = botAI->GetGameObject(*i);
|
||||
if (!go || !bot->GetGameObjectIfCanInteractWith(go->GetGUID(), GAMEOBJECT_TYPE_GUILD_BANK))
|
||||
continue;
|
||||
|
||||
return Execute(text, go);
|
||||
}
|
||||
|
||||
botAI->TellMaster("Cannot find the guild bank nearby");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GuildBankAction::Execute(std::string const text, GameObject* bank)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
std::vector<Item*> found = parseItems(text);
|
||||
if (found.empty())
|
||||
return false;
|
||||
|
||||
for (std::vector<Item*>::iterator i = found.begin(); i != found.end(); i++)
|
||||
{
|
||||
Item* item = *i;
|
||||
if (item)
|
||||
result &= MoveFromCharToBank(item, bank);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GuildBankAction::MoveFromCharToBank(Item* item, GameObject* bank)
|
||||
{
|
||||
uint32 playerSlot = item->GetSlot();
|
||||
uint32 playerBag = item->GetBagSlot();
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId());
|
||||
// guild->SwapItems(bot, 0, playerSlot, 0, INVENTORY_SLOT_BAG_0, 0);
|
||||
|
||||
// check source pos rights (item moved to bank)
|
||||
if (!guild->MemberHasTabRights(bot->GetGUID(), 0, GUILD_BANK_RIGHT_DEPOSIT_ITEM))
|
||||
out << "I can't put " << chat->FormatItem(item->GetTemplate())
|
||||
<< " to guild bank. I have no rights to put items in the first guild bank tab";
|
||||
else
|
||||
{
|
||||
out << chat->FormatItem(item->GetTemplate()) << " put to guild bank";
|
||||
guild->SwapItemsWithInventory(bot, false, 0, 255, playerBag, playerSlot, 0);
|
||||
}
|
||||
|
||||
botAI->TellMaster(out);
|
||||
|
||||
return true;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user