Merge remote-tracking branch 'origin/master' into eqstream

This commit is contained in:
KimLS 2017-02-25 15:31:40 -08:00
commit 0d1e63c92a
25 changed files with 11829 additions and 3046 deletions

View File

@ -1,5 +1,32 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 02/25/2017 ==
Uleat: Implemented rule-based node pathing for bots
- This currently applies to out-of-combat following movement and blocked los in-combat movement
- The default is set to 'true' (use node pathing)..so, consider disabling it if cpu use is too high
- If you want to disable node pathing, apply the optional sql '2017_02_25_bots_use_pathing_rule.sql' file located in the utils/sql/git/bots/optional sub-directory. This will apply a 'false' rule..but, it can be changed as desired
- This helps with bot movement..but, there are still issues...
Uleat: Implemented rule-based position update packet with movement timer check for bots
- This currently only applies to out-of-combat movement
- The default is set to 'false' (original behavior) to help save bandwidth (each bot will send an update packet every 1/10th of a second when enabled)
- If you want to enable the position update packet, apply the optional sql '2017_02_25_bots_update_position_with_timer_rule.sql' file located in the utils/sql/git/bots/optional sub-directory. This will apply a 'true' rule..but, it can be changed as desired
- This appears to help with/eliminate rubber banding
== 02/23/2017 ==
Uleat: Moved bot spell casting chance values into database - this will allow admins to tailor their bots without having to rebuild server code
- Each entry uses a 4-dimensional identifier: [spell type index][class index][stance index][conditional index]
- [spell type index] is not the SpellType_## bit value..use SpellType_##Index instead
- [class index] values of 0-15 are valid and determined by subtracting 1 from the actual class value
- [stance index] is a direct correlation (0-6)
- the [conditional index] is currently predicated on 2 compounded boolean states:
- not primary healer/not primary slower: 0
- primary healer/not primary slower: 1
- not primary healer/ primary slower: 2
- primary healer/primary slower: 3
- Valid `value` entries are 0-100..though, the field accepts up to 255... Anything above 100 is clamped to 100 upon loading, however
- Not all conditions are currently coded and changing a field may not produce any results
- The 'default' database values will be changed and tweaked as bot spell code modifications occur
== 02/20/2017 ==
Uleat: Reworked bard bot spell twisting and updated their spell (song) list
Uleat: Added ability to shift to pre-combat song buffing by selecting a non-pet npc target, eliminating the need to mix all bard buff songs together

View File

@ -558,6 +558,8 @@ RULE_BOOL(Bots, PreferNoManaCommandSpells, true) // Give sorting priority to new
RULE_BOOL(Bots, QuestableSpawnLimit, false) // Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl
RULE_BOOL(Bots, QuestableSpells, false) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests.
RULE_INT(Bots, SpawnLimit, 71) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group pseudo-raid (bots are not raidable at this time)
RULE_BOOL(Bots, UpdatePositionWithTimer, false) // Sends a position update with every positive movement timer check
RULE_BOOL(Bots, UsePathing, true) // Bots will use node pathing when moving
RULE_BOOL(Bots, BotGroupXP, false) // Determines whether client gets xp for bots outside their group.
RULE_BOOL(Bots, BotBardUseOutOfCombatSongs, true) // Determines whether bard bots use additional out of combat songs (optional script)
RULE_BOOL(Bots, BotLevelsWithOwner, false) // Auto-updates spawned bots as owner levels/de-levels (false is original behavior)

View File

@ -32,7 +32,7 @@
#define CURRENT_BINARY_DATABASE_VERSION 9105
#ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9010
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9011
#else
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0
#endif

View File

@ -0,0 +1,5 @@
command_settings
launcher
rule_sets
rule_values
variables

View File

@ -9,6 +9,7 @@
9008|2016_06_28_bots_inventory_charges_update.sql|SELECT * FROM `information_schema`.`COLUMNS` isc WHERE isc.`TABLE_SCHEMA` = DATABASE() AND isc.`TABLE_NAME` = 'bot_inventories' AND isc.`COLUMN_NAME` = 'inst_charges' AND isc.`DATA_TYPE` = 'tinyint'|not_empty|
9009|2017_02_15_bots_bot_spells_entries.sql|SELECT `id` FROM `npc_spells_entries` WHERE `npc_spells_id` >= 701 AND `npc_spells_id` <= 712|not_empty|
9010|2017_02_20_bots_bard_spell_update.sql|SELECT * FROM `bot_spells_entries` WHERE `npc_spells_id` = 711 AND (`type` & 0xFFFF0000) = 0xFFFF0000|empty|
9011|2017_02_23_bots_spell_casting_chances.sql|SHOW TABLES LIKE 'bot_spell_casting_chances'|empty|
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:UpdatePositionWithTimer', 'true', 'Sends a position update with every positive movement timer check');

View File

@ -0,0 +1 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:UsePathing', 'false', 'Bots will use node pathing when moving');

File diff suppressed because it is too large Load Diff

100
utils/sql/system_tables.txt Normal file
View File

@ -0,0 +1,100 @@
aa_ability
aa_actions
aa_effects
aa_required_level_cost
aa_ranks
aa_rank_effects
aa_rank_prereqs
activities
adventure_template
adventure_template_entry
adventure_template_entry_flavor
altadv_vars
alternate_currency
base_data
blocked_spells
books
char_create_combinations
char_create_point_allocations
class_skill
damageshieldtypes
doors
faction_list
faction_list_mod
fear_hints
fishing
forage
goallists
graveyard
grid
grid_entries
ground_spawns
horses
instance_list
items
ldon_trap_templates
ldon_trap_entries
level_exp_mods
lootdrop
lootdrop_entries
loottable
loottable_entries
merc_armorinfo
merc_weaponinfo
merc_stats
merc_merchant_entries
merc_merchant_template_entries
merc_merchant_templates
merc_stance_entries
merc_templates
merc_npc_types
merc_name_types
merc_subtypes
merc_types
merc_spell_list_entries
merc_spell_lists
merc_buffs
mercs
merc_inventory
merchantlist
npc_emotes
npc_faction
npc_faction_entries
npc_spells
npc_spells_entries
npc_spells_effects
npc_spells_effects_entries
npc_types
npc_types_metadata
npc_types_tint
object
pets
pets_equipmentset
pets_equipmentset_entries
proximities
races
saylink
skill_caps
spawn2
spawn_conditions
spawn_condition_values
spawn_events
spawnentry
spawngroup
spells_new
start_zones
starting_items
tasks
tasksets
titles
tradeskill_recipe
tradeskill_recipe_entries
traps
tribute_levels
tributes
veteran_reward_templates
zone
zone_points
zone_server
zone_state_dump
zoneserver_auth

93
utils/sql/user_tables.txt Normal file
View File

@ -0,0 +1,93 @@
aa_timers
account
account_flags
account_ip
account_rewards
adventure_details
adventure_members
adventure_stats
Banned_IPs
bugs
buyer
char_recipe_list
character_data
character_currency
character_alternate_abilities
character_bind
character_corpses
character_corpse_items
character_languages
character_lookup
character_skills
character_spells
character_memmed_spells
character_disciplines
character_material
character_tribute
character_bandolier
character_potionbelt
character_inspect_messages
character_leadership_abilities
character_activities
character_alt_currency
character_backup
character_buffs
character_enabledtasks
character_pet_buffs
character_pet_info
character_pet_inventory
character_tasks
chatchannels
completed_tasks
discovered_items
eventlog
faction_values
friends
gm_ips
group_id
group_leaders
guilds
guild_bank
guild_ranks
guild_relations
guild_members
hackers
instance_list_player
inventory
item_tick
keyring
launcher_zones
lfguild
mail
merchantlist_temp
name_filter
object_contents
petitions
player_titlesets
qs_player_move_record
qs_player_move_record_entries
qs_player_npc_kill_record
qs_player_npc_kill_record_entries
qs_player_speech
qs_player_trade_record
qs_player_trade_record_entries
qs_merchant_transaction_record
qs_merchant_transaction_record_entries
qs_player_delete_record
qs_player_delete_record_entries
qs_player_handin_record
qs_player_handin_record_entries
qs_player_aa_rate_hourly
qs_player_events
quest_globals
raid_details
raid_leaders
raid_members
reports
respawn_times
sharedbank
spell_globals
timers
trader
trader_audit
zone_flags

View File

@ -253,7 +253,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
//sometimes if a client has some lag while zoning into a dangerous place while either invis or a GM
//they will aggro mobs even though it's supposed to be impossible, to lets make sure we've finished connecting
if (mob->IsClient()) {
if (!mob->CastToClient()->ClientFinishedLoading() || mob->CastToClient()->IsHoveringForRespawn())
if (!mob->CastToClient()->ClientFinishedLoading() || mob->CastToClient()->IsHoveringForRespawn() || mob->CastToClient()->zoning)
return false;
}

View File

@ -1564,7 +1564,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk
}
}
entity_list.RemoveFromTargets(this);
entity_list.RemoveFromTargets(this, true);
hate_list.RemoveEntFromHateList(this);
RemoveAutoXTargets();

View File

@ -77,7 +77,7 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm
SetShowHelm(true);
SetPauseAI(false);
rest_timer.Disable();
SetFollowDistance(BOT_DEFAULT_FOLLOW_DISTANCE);
SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT);
// Do this once and only in this constructor
GenerateAppearance();
GenerateBaseStats();
@ -151,7 +151,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to
SetPauseAI(false);
rest_timer.Disable();
SetFollowDistance(BOT_DEFAULT_FOLLOW_DISTANCE);
SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT);
strcpy(this->name, this->GetCleanName());
memset(&_botInspectMessage, 0, sizeof(InspectMessage_Struct));
@ -176,6 +176,9 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to
if (GetClass() == ROGUE)
evade_timer.Start();
m_CastingRoles.GroupHealer = false;
m_CastingRoles.GroupSlower = false;
GenerateBaseStats();
if (!botdb.LoadTimers(this) && bot_owner)
@ -2062,6 +2065,8 @@ float Bot::GetMaxMeleeRangeToTarget(Mob* target) {
// AI Processing for the Bot object
void Bot::AI_Process() {
// TODO: Need to add root checks to all movement code
if (!IsAIControlled())
return;
if (GetPauseAI())
@ -2214,7 +2219,7 @@ void Bot::AI_Process() {
// Let's check if we have a los with our target.
// If we don't, our hate_list is wiped.
// Else, it was causing the bot to aggro behind wall etc... causing massive trains.
if(!CheckLosFN(GetTarget()) || GetTarget()->IsMezzed() || !IsAttackAllowed(GetTarget())) {
if(GetTarget()->IsMezzed() || !IsAttackAllowed(GetTarget())) {
WipeHateList();
if(IsMoving()) {
SetHeading(0);
@ -2225,6 +2230,26 @@ void Bot::AI_Process() {
}
return;
}
else if (!CheckLosFN(GetTarget())) {
if (RuleB(Bots, UsePathing) && zone->pathing) {
bool WaypointChanged, NodeReached;
glm::vec3 Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(),
GetRunspeed(), WaypointChanged, NodeReached);
if (WaypointChanged)
tar_ndx = 20;
CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetRunspeed());
}
else {
Mob* follow = entity_list.GetMob(GetFollowID());
if (follow)
CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), GetRunspeed());
}
return;
}
if (!(m_PlayerState & static_cast<uint32>(PlayerState::Aggressive)))
SendAddPlayerState(PlayerState::Aggressive);
@ -2339,6 +2364,7 @@ void Bot::AI_Process() {
}
}
// TODO: Test RuleB(Bots, UpdatePositionWithTimer)
if(IsMoving())
SendPosUpdate();
else
@ -2503,33 +2529,60 @@ void Bot::AI_Process() {
}
}
else if(AI_movement_timer->Check()) {
// something is wrong with bot movement spell bonuses - based on logging..
// ..this code won't need to be so complex once fixed...
int speed = GetRunspeed();
if (cur_dist < GetFollowDistance() + 2000) {
speed = GetWalkspeed();
}
else if (cur_dist >= GetFollowDistance() + 10000) { // 100
if (cur_dist >= 22500) { // 150
auto leader = follow;
while (leader->GetFollowID()) {
leader = entity_list.GetMob(leader->GetFollowID());
if (!leader || leader == this)
break;
if (leader->GetRunspeed() > speed)
speed = leader->GetRunspeed();
if (leader->IsClient())
break;
// Something is still wrong with bot the follow code...
// Shows up really bad over long distances when movement bonuses are involved
// The flip-side is that too much speed adversely affects node pathing...
if (cur_dist > GetFollowDistance()) {
if (RuleB(Bots, UsePathing) && zone->pathing) {
if (cur_dist <= BOT_FOLLOW_DISTANCE_WALK) {
bool WaypointChanged, NodeReached;
glm::vec3 Goal = UpdatePath(follow->GetX(), follow->GetY(), follow->GetZ(),
GetWalkspeed(), WaypointChanged, NodeReached);
if (WaypointChanged)
tar_ndx = 20;
CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetWalkspeed());
}
else {
int speed = GetRunspeed();
if (cur_dist > BOT_FOLLOW_DISTANCE_CRITICAL)
speed = ((float)speed * 1.333f); // sprint mod (1/3 boost)
bool WaypointChanged, NodeReached;
glm::vec3 Goal = UpdatePath(follow->GetX(), follow->GetY(), follow->GetZ(),
speed, WaypointChanged, NodeReached);
if (WaypointChanged)
tar_ndx = 20;
CalculateNewPosition2(Goal.x, Goal.y, Goal.z, speed);
}
}
speed = (float)speed * (1.0f + ((float)speed * 0.03125f)); // 1/32 - special bot sprint mod
}
else {
if (cur_dist <= BOT_FOLLOW_DISTANCE_WALK) {
CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), GetWalkspeed());
}
else {
int speed = GetRunspeed();
if (cur_dist > BOT_FOLLOW_DISTANCE_CRITICAL)
speed = ((float)speed * 1.333f); // sprint mod (1/3 boost)
if (cur_dist > GetFollowDistance()) {
CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed);
CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed);
}
}
if (rest_timer.Enabled())
rest_timer.Disable();
return;
if (RuleB(Bots, UpdatePositionWithTimer)) { // this helps with rubber-banding effect
if (IsMoving())
SendPosUpdate();
//else
// SendPosition(); // enabled - no discernable difference..disabled - saves on no movement packets
}
}
else {
if (moved) {
@ -2537,9 +2590,14 @@ void Bot::AI_Process() {
SetCurrentSpeed(0);
}
}
if (GetClass() == BARD && GetBotStance() != BotStancePassive && !spellend_timer.Enabled() && AI_think_timer->Check())
AI_IdleCastCheck();
return;
}
else if (IsMoving()) {
if (GetBotStance() != BotStancePassive && GetClass() == BARD && !spellend_timer.Enabled() && AI_think_timer->Check()) {
if (GetClass() == BARD && GetBotStance() != BotStancePassive && !spellend_timer.Enabled() && AI_think_timer->Check()) {
AI_IdleCastCheck();
}
}
@ -6835,66 +6893,116 @@ bool Bot::IsBotCasterCombatRange(Mob *target) {
return result;
}
bool Bot::IsGroupPrimaryHealer() {
bool result = false;
uint8 botclass = GetClass();
if(HasGroup()) {
Group *g = GetGroup();
switch(botclass) {
case CLERIC: {
result = true;
break;
}
case DRUID: {
result = GroupHasClericClass(g) ? false : true;
break;
}
case SHAMAN: {
result = (GroupHasClericClass(g) || GroupHasDruidClass(g)) ? false : true;
break;
}
case PALADIN:
case RANGER:
case BEASTLORD: {
result = GroupHasPriestClass(g) ? false : true;
break;
}
default: {
result = false;
break;
}
void Bot::UpdateGroupCastingRoles(const Group* group, bool disband)
{
if (!group)
return;
for (auto iter : group->members) {
if (!iter)
continue;
if (iter->IsBot()) {
iter->CastToBot()->SetGroupHealer(false);
iter->CastToBot()->SetGroupSlower(false);
}
}
return result;
}
if (disband)
return;
bool Bot::IsGroupPrimarySlower() {
bool result = false;
uint8 botclass = GetClass();
if(HasGroup()) {
Group *g = GetGroup();
switch(botclass) {
case SHAMAN: {
result = true;
break;
}
case ENCHANTER: {
result = GroupHasShamanClass(g) ? false : true;
break;
}
case BEASTLORD: {
result = (GroupHasShamanClass(g) || GroupHasEnchanterClass(g)) ? false : true;
break;
}
default: {
result = false;
break;
}
Mob* healer = nullptr;
Mob* slower = nullptr;
for (auto iter : group->members) {
if (!iter)
continue;
switch (iter->GetClass()) {
case CLERIC:
if (!healer)
healer = iter;
else
switch (healer->GetClass()) {
case CLERIC:
break;
default:
healer = iter;
}
break;
case DRUID:
if (!healer)
healer = iter;
else
switch (healer->GetClass()) {
case CLERIC:
case DRUID:
break;
default:
healer = iter;
}
break;
case SHAMAN:
if (!healer)
healer = iter;
else
switch (healer->GetClass()) {
case CLERIC:
case DRUID:
case SHAMAN:
break;
default:
healer = iter;
}
break;
case PALADIN:
case RANGER:
case BEASTLORD:
if (!healer)
healer = iter;
break;
default:
break;
}
switch (iter->GetClass()) {
case SHAMAN:
if (!slower)
slower = iter;
else
switch (slower->GetClass()) {
case SHAMAN:
break;
default:
slower = iter;
}
break;
case ENCHANTER:
if (!slower)
slower = iter;
else
switch (slower->GetClass()) {
case SHAMAN:
case ENCHANTER:
break;
default:
slower = iter;
}
break;
case BEASTLORD:
if (!slower)
slower = iter;
break;
default:
break;
}
}
return result;
if (healer && healer->IsBot())
healer->CastToBot()->SetGroupHealer();
if (slower && slower->IsBot())
slower->CastToBot()->SetGroupSlower();
}
bool Bot::CanHeal() {

View File

@ -38,7 +38,10 @@
#include <sstream>
#define BOT_DEFAULT_FOLLOW_DISTANCE 184
#define BOT_FOLLOW_DISTANCE_DEFAULT 184 // as DSq value (~13.565 units)
#define BOT_FOLLOW_DISTANCE_DEFAULT_MAX 2500 // as DSq value (50 units)
#define BOT_FOLLOW_DISTANCE_WALK 400 // as DSq value (20 units)
#define BOT_FOLLOW_DISTANCE_CRITICAL 22500 // as DSq value (150 units)
extern WorldServer worldserver;
@ -132,6 +135,10 @@ enum SpellTypeIndex {
MaxSpellTypes
};
// negative Healer/Slower, positive Healer, postive Slower, positive Healer/Slower
enum BotCastingChanceConditional : uint8 { nHS = 0, pH, pS, pHS, cntHS = 4 };
class Bot : public NPC {
friend class Mob;
public:
@ -472,8 +479,11 @@ public:
BotRoleType GetBotRole() { return _botRole; }
BotStanceType GetBotStance() { return _botStance; }
uint8 GetChanceToCastBySpellType(uint32 spellType);
bool IsGroupPrimaryHealer();
bool IsGroupPrimarySlower();
bool IsGroupHealer() { return m_CastingRoles.GroupHealer; }
bool IsGroupSlower() { return m_CastingRoles.GroupSlower; }
static void UpdateGroupCastingRoles(const Group* group, bool disband = false);
bool IsBotCaster() { return IsCasterClass(GetClass()); }
bool IsBotINTCaster() { return IsINTCasterClass(GetClass()); }
bool IsBotWISCaster() { return IsWISCasterClass(GetClass()); }
@ -637,6 +647,10 @@ protected:
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
virtual float GetMaxMeleeRangeToTarget(Mob* target);
BotCastingRoles& GetCastingRoles() { return m_CastingRoles; }
void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; }
void SetGroupSlower(bool flag = true) { m_CastingRoles.GroupSlower = flag; }
private:
// Class Members
uint32 _botID;
@ -676,6 +690,8 @@ private:
Timer evade_timer;
BotCastingRoles m_CastingRoles;
std::shared_ptr<HealRotation> m_member_of_heal_rotation;
std::map<uint32, BotAA> botAAs;

View File

@ -4550,7 +4550,7 @@ void bot_subcommand_bot_follow_distance(Client *c, const Seperator *sep)
}
const int ab_mask = ActionableBots::ABM_NoFilter;
uint32 bfd = BOT_DEFAULT_FOLLOW_DISTANCE;
uint32 bfd = BOT_FOLLOW_DISTANCE_DEFAULT;
bool set_flag = false;
int ab_arg = 2;
@ -4561,6 +4561,10 @@ void bot_subcommand_bot_follow_distance(Client *c, const Seperator *sep)
}
bfd = atoi(sep->arg[2]);
if (bfd < 1)
bfd = 1;
if (bfd > BOT_FOLLOW_DISTANCE_DEFAULT_MAX)
bfd = BOT_FOLLOW_DISTANCE_DEFAULT_MAX;
set_flag = true;
ab_arg = 3;
}

View File

@ -82,6 +82,50 @@ bool BotDatabase::LoadBotCommandSettings(std::map<std::string, std::pair<uint8,
return true;
}
static uint8 spell_casting_chances[MaxSpellTypes][PLAYER_CLASS_COUNT][MaxStances][cntHS];
bool BotDatabase::LoadBotSpellCastingChances()
{
memset(spell_casting_chances, 0, sizeof(spell_casting_chances));
query =
"SELECT"
" `spell_type_index`,"
" `class_index`,"
" `stance_index`,"
" `conditional_index`,"
" `value` "
"FROM"
" `bot_spell_casting_chances` "
"WHERE"
" `value` != '0'";
auto results = QueryDatabase(query);
if (!results.Success())
return false;
for (auto row = results.begin(); row != results.end(); ++row) {
uint8 spell_type_index = atoi(row[0]);
if (spell_type_index >= MaxSpellTypes)
continue;
uint8 class_index = atoi(row[1]);
if (class_index >= PLAYER_CLASS_COUNT)
continue;
uint8 stance_index = atoi(row[2]);
if (stance_index >= MaxStances)
continue;
uint8 conditional_index = atoi(row[3]);
if (conditional_index >= cntHS)
continue;
uint8 value = atoi(row[4]);
if (value > 100)
value = 100;
spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index] = value;
}
return true;
}
/* Bot functions */
bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& available_flag)
@ -334,7 +378,13 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot)
loaded_bot = new Bot(bot_id, atoi(row[0]), atoi(row[1]), atof(row[14]), atoi(row[6]), tempNPCStruct);
if (loaded_bot) {
loaded_bot->SetShowHelm((atoi(row[43]) > 0 ? true : false));
loaded_bot->SetFollowDistance(atoi(row[44]));
uint32 bfd = atoi(row[44]);
if (bfd < 1)
bfd = 1;
if (bfd > BOT_FOLLOW_DISTANCE_DEFAULT_MAX)
bfd = BOT_FOLLOW_DISTANCE_DEFAULT_MAX;
loaded_bot->SetFollowDistance(bfd);
}
return true;
@ -471,7 +521,7 @@ bool BotDatabase::SaveNewBot(Bot* bot_inst, uint32& bot_id)
bot_inst->GetPR(),
bot_inst->GetDR(),
bot_inst->GetCorrup(),
BOT_DEFAULT_FOLLOW_DISTANCE
BOT_FOLLOW_DISTANCE_DEFAULT
);
auto results = QueryDatabase(query);
if (!results.Success())
@ -2711,6 +2761,19 @@ bool BotDatabase::DeleteAllHealRotations(const uint32 owner_id)
/* Bot miscellaneous functions */
uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_index, uint8 stance_index, uint8 conditional_index)
{
if (spell_type_index >= MaxSpellTypes)
return 0;
if (class_index >= PLAYER_CLASS_COUNT)
return 0;
if (stance_index >= MaxStances)
return 0;
if (conditional_index >= cntHS)
return 0;
return spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index];
}
/* fail::Bot functions */

View File

@ -50,6 +50,7 @@ public:
bool Connect(const char* host, const char* user, const char* passwd, const char* database, uint32 port);
bool LoadBotCommandSettings(std::map<std::string, std::pair<uint8, std::vector<std::string>>> &bot_command_settings);
bool LoadBotSpellCastingChances();
/* Bot functions */
@ -183,6 +184,7 @@ public:
bool DeleteAllHealRotations(const uint32 owner_id);
/* Bot miscellaneous functions */
uint8 GetSpellCastingChance(uint8 spell_type_index, uint8 class_index, uint8 stance_index, uint8 conditional_index);
class fail {

View File

@ -60,6 +60,11 @@ struct BotSpell_wPriority : public BotSpell {
uint8 Priority;
};
struct BotCastingRoles {
bool GroupHealer;
bool GroupSlower;
};
struct BotAA {
uint32 aa_id;
uint8 req_level;

File diff suppressed because it is too large Load Diff

View File

@ -8902,7 +8902,7 @@ void Client::ProcessAggroMeter()
if (m_aggrometer.set_pct(AggroMeter::AT_Group3, 0))
add_entry(AggroMeter::AT_Group3);
if (m_aggrometer.set_pct(AggroMeter::AT_Group4, 0))
add_entry(AggroMeter::AT_Group5);
add_entry(AggroMeter::AT_Group4);
if (m_aggrometer.set_pct(AggroMeter::AT_Group5, 0))
add_entry(AggroMeter::AT_Group5);
}

View File

@ -6505,6 +6505,16 @@ void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app)
if (!group) //We must recheck this here.. incase the final bot disbanded the party..otherwise we crash
return;
#endif
Mob* memberToDisband = GetTarget();
if (!memberToDisband)
memberToDisband = entity_list.GetMob(gd->name2);
if (memberToDisband) {
auto group2 = memberToDisband->GetGroup();
if (group2 != group) // they're not in our group!
memberToDisband = this;
}
if (group->GroupCount() < 3)
{
@ -6526,7 +6536,7 @@ void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app)
GetMerc()->Suspend();
}
}
else if (group->IsLeader(this) && GetTarget() == this)
else if (group->IsLeader(this) && (GetTarget() == this || memberToDisband == this))
{
LeaveGroup();
if (GetMerc() && !GetMerc()->IsSuspended())
@ -6536,12 +6546,6 @@ void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app)
}
else
{
Mob* memberToDisband = nullptr;
memberToDisband = GetTarget();
if (!memberToDisband)
memberToDisband = entity_list.GetMob(gd->name2);
if (memberToDisband)
{
if (group->IsLeader(this))

View File

@ -796,7 +796,7 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
continue;
if (dist_targ < min_range2) //make sure they are in range
continue;
if (isnpc && curmob->IsNPC()) { //check npc->npc casting
if (isnpc && curmob->IsNPC() && spells[spell_id].targettype != ST_AreaNPCOnly) { //check npc->npc casting
FACTION_VALUE f = curmob->GetReverseFactionCon(caster);
if (bad) {
//affect mobs that are on our hate list, or

View File

@ -347,6 +347,10 @@ bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 Characte
safe_delete(outapp);
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
return true;
}
@ -481,6 +485,10 @@ bool Group::UpdatePlayer(Mob* update){
if (update->IsClient() && !mentoree && mentoree_name.length() && !mentoree_name.compare(update->GetName()))
mentoree = update->CastToClient();
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
return updateSuccess;
}
@ -513,6 +521,10 @@ void Group::MemberZoned(Mob* removemob) {
if (removemob->IsClient() && removemob == mentoree)
mentoree = nullptr;
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
}
void Group::SendGroupJoinOOZ(Mob* NewMember) {
@ -581,6 +593,16 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender)
return false;
}
// TODO: fix this shit
// okay, so there is code below that tries to handle this. It does not.
// So instead of figuring it out now, lets just disband the group so the client doesn't
// sit there with a broken group and there isn't any group leader shuffling going on
// since the code below doesn't work.
if (oldmember == GetLeader()) {
DisbandGroup();
return true;
}
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++)
{
if (members[i] == oldmember)
@ -721,6 +743,10 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender)
ClearAllNPCMarks();
}
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this);
#endif
return true;
}
@ -864,6 +890,10 @@ uint32 Group::GetTotalGroupDamage(Mob* other) {
}
void Group::DisbandGroup(bool joinraid) {
#ifdef BOTS
Bot::UpdateGroupCastingRoles(this, true);
#endif
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate_Struct));
GroupUpdate_Struct* gu = (GroupUpdate_Struct*) outapp->pBuffer;

View File

@ -371,6 +371,10 @@ int main(int argc, char** argv) {
Log.Out(Logs::General, Logs::Error, "Bot command loading FAILED");
else
Log.Out(Logs::General, Logs::Zone_Server, "%d bot commands loaded", botretval);
Log.Out(Logs::General, Logs::Zone_Server, "Loading bot spell casting chances");
if (!botdb.LoadBotSpellCastingChances())
Log.Out(Logs::General, Logs::Error, "Bot spell casting chances loading FAILED");
#endif
if(RuleB(TaskSystem, EnableTaskSystem)) {

View File

@ -304,6 +304,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower,
// 2 - `s Warder
// 3 - Random name if client, `s pet for others
// 4 - Keep DB name
// 5 - `s ward
if (petname != nullptr) {
@ -325,6 +326,10 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower,
// Keep the DB name
} else if (record.petnaming == 3 && IsClient()) {
strcpy(npc_type->name, GetRandPetName());
} else if (record.petnaming == 5 && IsClient()) {
strcpy(npc_type->name, this->GetName());
npc_type->name[24] = '\0';
strcat(npc_type->name, "`s_ward");
} else {
strcpy(npc_type->name, this->GetCleanName());
npc_type->name[25] = '\0';