[Bots] Add Basic Bot Raiding Functionality (#2782)

* Fix for GENERIC_9_STRINGS

* Add Bot Heal Message Display

Creates a new rule to display Bot heal messages to the Bot Owner

* 2021-03-25 11L04pm

Spell and Heal Rule added to allow for Bot spell and heal damage to be sent to the Bot Owner's Group.  Also added a check to remove duplicate message for #damage on self.

* Update .gitignore

* BOT work

Added BOT logging damage/heals to owner
Added BOT message to owner for harmony fails
Made var Critical global to remove duplicate crit messages
Added a NULL check to Mob:GetCleanname()

* Bot Group Work

Fixed botid=charid spawn on zone issue
Added a group_list update on zone to refresh from database to fix a dangling pointer to a Bot object that was camped but was previously in a group within the zone being entered.
Modified Bot::ProcessBotGroupInvite to use the client of the bot when doing the Bot initialization so that a leader can invite another owner's Bot

* Jan 4

Basic structure in place for Raid::AddBot though not working

* Basement Jan 5

* End of day Jan 5
Working Raid Invite to a Bot.

* Update to Client::QueuePacket to not attempt to send a packet to a BoT.  Not clean, but a broad solution.

* Updated Raid::VerifyRaid

* Some Bot Raid working

* Before VS Crash

* Use Case 1, 2, 3,4,7 working.
Need to fix 5, 6, 8

* Work on usecase 5

* A few more use cases working

* New work on Raid invite with a invitor having a group

* Bot Raid inviting working for all use cases

* A few changes

* end of day jan 10

* Jan 11

* end of day Jan 11

* Bot Invite/Accept cleanup

* Start of moving raid bot functions to their own methods

* More bot raid changes

* More raid spell work

* end of day Jan 16

* spawn work

* Spawn on login working

* End of Day Jan 18

* Raid leader and mana/hp updates fixed

* Spell Tracking

* Issue with Bot Death in raid when casted upon.  1741 raid.cpp

* Bot Death fixed and few other crashes

* Working on botgroup removal

* Bot Disbanding Work 90%

* Looks like BOTs are working

* Fixed a bot crash

* bug tracing on entity list mismatch

* safe_delete resoves problem.  No to track down leak

* seems to be working

* Memory corruption found - sending packets to BoTs using Client class

* added Raid::IsRaidMemberBot()

* Update p_raid_instance

* g3

* Final - Bot Raid Working

* Fixed IsRaidMemberBot to remove memory leak
Fixed altcombat crash though RaidMainAssist (428) needs fixing

* add RaidMember.IsBot

* Repaired IsBot function to be more preformant.  Now works on standard performance machine

* Fixed Bard AE Target Spells
Removed assert for buffs

* updated based on Feb 2022 master updates

* Added bot_db_updates and version increment

* Cleanup of bot raid work and inclusion of bot_raid in cmake

* Delete .gitignore

* Revert "Delete .gitignore"

This reverts commit 8523658d3bacdc068bcafaa652d2100afecddfc2.

* Fixed a packet issue

* Merged upstream/master

Merged upstream/master and removed ifdef BOTS as per recent dev approach for BOTS.  Functionality is there, compiles and tests ok.  A few problems to be resolved though this is a good baseline.

* Added sql update for raid_members to add isbot

* Updated Bot Follow Function

Bot will now follow the Group Leader if IsClient, otherwise follows the Bot Owner

* Updates to Bot Raid System

When camping a client, remove them from the raid.  If they are leader, place leadership to the next client.
Update a few crash checks in bot_raid.cpp

* [BOTS] Added RuleB Enabled checks and updated base repo for raid_members

Updated several RuleB(Bots, Enabled) checks
Updated the base repo to be autogenerated.
Raid functionality should work with a non-bots enabled database.

* Few quick updates

* Updates

Corrected a number of comments.  Compiled and tested against bot and non-bot database though requires the isbot column in raid_members for both.
Moved the db update out of the bot stream to make bot check code easier.

* Formatting and other small updates

* A few more RuleB(Bots, Enabled) additions

* Fix issue with conflict of bot ID versus character ID.

* Delete CMakeSettings.json

* Comment Updates and other

Several updates including
- fixed comments from PR
- added id to raid_members and unique index on name to avoid botid and charid conflicts
- updated a few raid functions for iterators
- reordered several raid operations to ensure send leader packet to be the last item to ensure proper updating on the client
- update sql to use Replace instead of Insert for botid conflicting with charid

* Exploit fix for Raid Bots

Added item from @Nite to disallow spawning or camping bots if Raid is engaged.  Avoids abusive situations.

* Initial Commit

* fix Raid Window after zoning

The raid window was not fully updating for clients not in the zone.

* Cleanup

* Update

Fixed comments

* Resolve crash for MOTD

Fixed a crash situation sending a raid MOTD to BOTS.

* Update ruletypes.h

* Updated to resolve a few recent comments

Fixed some comments within attack.cpp

* fix sql query

* update repository

* prevent duplicate entries in raid after group invite, and cleanup

* fixes for botgroups not following, and add already in raid messages.

* fix messagestring

* fixes

* Cleanup, and resolving issues with disbanding

* refactoring

* more cleanup/fixing.

* fixes for removing from ground in raid

* Refactoring/fixing multiple clients

* fix for compiling

* Bugs from refactoring fixed

* Testing completed, cleaning up unwanted items/duplicate code.

* Cleaned up AICastSpell

* fix typos

* Refactoring

* Adding Raid checks to AI_Process/cleanup

* Fix a typo

Was getting a SQL error on BOT spawn.  Fixed typo.

* fix for crash

* Fixed crash when inviting player, more refactoring

* AI_Process Refactoring work

* More Refactoring/fixes for follow

* Finish Refactoring AI_Process

* cleanup

* cleanup

* cleanup

* cleanup

* fix melee attack loop

* fix for leashowner.

* fix for leashowner.

* Bots persist in raid after client death/LD/Camp

* Fix Bot Groups when zoning after death.

* Fix Bots in group following after client death

* remove unnecessary query

* Allow Raid members to invite Bots if owner is in raid. cleanup

* optimization of raid verification

* remove this

* Code Cleanup

* formatting

* formatting

* formatting

* fix for macro

* add return for TryClassAttacks

* fix query

* fix for crash

* restrict camping/spawn in combat.

* Fix other crash issue.

* update learnmembers to use Strings::To, cleanup magic numbers

* fix for merge.

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
Co-authored-by: Aeadoin <109764533+Aeadoin@users.noreply.github.com>
This commit is contained in:
Mitch Freeman 2023-03-17 12:19:59 -03:00 committed by GitHub
parent e778041198
commit 45da8cab61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 5429 additions and 5050 deletions

View File

@ -16,11 +16,14 @@
#include "../../strings.h"
#include <ctime>
class BaseRaidMembersRepository {
public:
struct RaidMembers {
uint64_t id;
int32_t raidid;
int32_t charid;
int32_t bot_id;
uint32_t groupid;
int8_t _class;
int8_t level;
@ -32,14 +35,16 @@ public:
static std::string PrimaryKey()
{
return std::string("charid");
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"raidid",
"charid",
"bot_id",
"groupid",
"_class",
"level",
@ -53,8 +58,10 @@ public:
static std::vector<std::string> SelectColumns()
{
return {
"id",
"raidid",
"charid",
"bot_id",
"groupid",
"_class",
"level",
@ -102,8 +109,10 @@ public:
{
RaidMembers e{};
e.id = 0;
e.raidid = 0;
e.charid = 0;
e.bot_id = 0;
e.groupid = 0;
e._class = 0;
e.level = 0;
@ -121,7 +130,7 @@ public:
)
{
for (auto &raid_members : raid_memberss) {
if (raid_members.charid == raid_members_id) {
if (raid_members.id == raid_members_id) {
return raid_members;
}
}
@ -136,8 +145,9 @@ public:
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE id = {} LIMIT 1",
"{} WHERE {} = {} LIMIT 1",
BaseSelect(),
PrimaryKey(),
raid_members_id
)
);
@ -146,15 +156,17 @@ public:
if (results.RowCount() == 1) {
RaidMembers e{};
e.raidid = static_cast<int32_t>(atoi(row[0]));
e.charid = static_cast<int32_t>(atoi(row[1]));
e.groupid = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
e._class = static_cast<int8_t>(atoi(row[3]));
e.level = static_cast<int8_t>(atoi(row[4]));
e.name = row[5] ? row[5] : "";
e.isgroupleader = static_cast<int8_t>(atoi(row[6]));
e.israidleader = static_cast<int8_t>(atoi(row[7]));
e.islooter = static_cast<int8_t>(atoi(row[8]));
e.id = strtoull(row[0], nullptr, 10);
e.raidid = static_cast<int32_t>(atoi(row[1]));
e.charid = static_cast<int32_t>(atoi(row[2]));
e.bot_id = static_cast<int32_t>(atoi(row[3]));
e.groupid = static_cast<uint32_t>(strtoul(row[4], nullptr, 10));
e._class = static_cast<int8_t>(atoi(row[5]));
e.level = static_cast<int8_t>(atoi(row[6]));
e.name = row[7] ? row[7] : "";
e.isgroupleader = static_cast<int8_t>(atoi(row[8]));
e.israidleader = static_cast<int8_t>(atoi(row[9]));
e.islooter = static_cast<int8_t>(atoi(row[10]));
return e;
}
@ -188,15 +200,16 @@ public:
auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.raidid));
v.push_back(columns[1] + " = " + std::to_string(e.charid));
v.push_back(columns[2] + " = " + std::to_string(e.groupid));
v.push_back(columns[3] + " = " + std::to_string(e._class));
v.push_back(columns[4] + " = " + std::to_string(e.level));
v.push_back(columns[5] + " = '" + Strings::Escape(e.name) + "'");
v.push_back(columns[6] + " = " + std::to_string(e.isgroupleader));
v.push_back(columns[7] + " = " + std::to_string(e.israidleader));
v.push_back(columns[8] + " = " + std::to_string(e.islooter));
v.push_back(columns[1] + " = " + std::to_string(e.raidid));
v.push_back(columns[2] + " = " + std::to_string(e.charid));
v.push_back(columns[3] + " = " + std::to_string(e.bot_id));
v.push_back(columns[4] + " = " + std::to_string(e.groupid));
v.push_back(columns[5] + " = " + std::to_string(e._class));
v.push_back(columns[6] + " = " + std::to_string(e.level));
v.push_back(columns[7] + " = '" + Strings::Escape(e.name) + "'");
v.push_back(columns[8] + " = " + std::to_string(e.isgroupleader));
v.push_back(columns[9] + " = " + std::to_string(e.israidleader));
v.push_back(columns[10] + " = " + std::to_string(e.islooter));
auto results = db.QueryDatabase(
fmt::format(
@ -204,7 +217,7 @@ public:
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.charid
e.id
)
);
@ -218,8 +231,10 @@ public:
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.raidid));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.groupid));
v.push_back(std::to_string(e._class));
v.push_back(std::to_string(e.level));
@ -237,7 +252,7 @@ public:
);
if (results.Success()) {
e.charid = results.LastInsertedID();
e.id = results.LastInsertedID();
return e;
}
@ -256,8 +271,10 @@ public:
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.raidid));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.groupid));
v.push_back(std::to_string(e._class));
v.push_back(std::to_string(e.level));
@ -298,15 +315,17 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
RaidMembers e{};
e.raidid = static_cast<int32_t>(atoi(row[0]));
e.charid = static_cast<int32_t>(atoi(row[1]));
e.groupid = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
e._class = static_cast<int8_t>(atoi(row[3]));
e.level = static_cast<int8_t>(atoi(row[4]));
e.name = row[5] ? row[5] : "";
e.isgroupleader = static_cast<int8_t>(atoi(row[6]));
e.israidleader = static_cast<int8_t>(atoi(row[7]));
e.islooter = static_cast<int8_t>(atoi(row[8]));
e.id = strtoull(row[0], nullptr, 10);
e.raidid = static_cast<int32_t>(atoi(row[1]));
e.charid = static_cast<int32_t>(atoi(row[2]));
e.bot_id = static_cast<int32_t>(atoi(row[3]));
e.groupid = static_cast<uint32_t>(strtoul(row[4], nullptr, 10));
e._class = static_cast<int8_t>(atoi(row[5]));
e.level = static_cast<int8_t>(atoi(row[6]));
e.name = row[7] ? row[7] : "";
e.isgroupleader = static_cast<int8_t>(atoi(row[8]));
e.israidleader = static_cast<int8_t>(atoi(row[9]));
e.islooter = static_cast<int8_t>(atoi(row[10]));
all_entries.push_back(e);
}
@ -331,15 +350,17 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
RaidMembers e{};
e.raidid = static_cast<int32_t>(atoi(row[0]));
e.charid = static_cast<int32_t>(atoi(row[1]));
e.groupid = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
e._class = static_cast<int8_t>(atoi(row[3]));
e.level = static_cast<int8_t>(atoi(row[4]));
e.name = row[5] ? row[5] : "";
e.isgroupleader = static_cast<int8_t>(atoi(row[6]));
e.israidleader = static_cast<int8_t>(atoi(row[7]));
e.islooter = static_cast<int8_t>(atoi(row[8]));
e.id = strtoull(row[0], nullptr, 10);
e.raidid = static_cast<int32_t>(atoi(row[1]));
e.charid = static_cast<int32_t>(atoi(row[2]));
e.bot_id = static_cast<int32_t>(atoi(row[3]));
e.groupid = static_cast<uint32_t>(strtoul(row[4], nullptr, 10));
e._class = static_cast<int8_t>(atoi(row[5]));
e.level = static_cast<int8_t>(atoi(row[6]));
e.name = row[7] ? row[7] : "";
e.isgroupleader = static_cast<int8_t>(atoi(row[8]));
e.israidleader = static_cast<int8_t>(atoi(row[9]));
e.islooter = static_cast<int8_t>(atoi(row[10]));
all_entries.push_back(e);
}

View File

@ -99,8 +99,9 @@ public:
}
}
~Seperator() {
for (int i=0; i<=maxargnum; i++)
for (int i=0; i<=maxargnum; i++) {
safe_delete_array(arg[i]);
}
safe_delete_array(arg);
safe_delete_array(argplus);
safe_delete_array(msg);

View File

@ -42,7 +42,8 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9224
#define CURRENT_BINARY_DATABASE_VERSION 9225
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9038
#endif

View File

@ -478,6 +478,7 @@
9222|2023_02_28_npc_scaling_zone_list_version_list.sql|SHOW COLUMNS FROM `npc_scale_global_base` LIKE 'zone_id_list'|empty|
9223|2023_03_04_npc_scale_global_base_heroic_strikethrough.sql|SHOW COLUMNS FROM `npc_scale_global_base` LIKE 'heroic_strikethrough'|empty|
9224|2023_03_08_npc_scale_global_base_avoidance.sql|SHOW COLUMNS FROM `npc_scale_global_base` LIKE 'hp_regen_per_second'|empty|
9225|2023_01_21_bots_raid_members.sql|SHOW COLUMNS FROM `raid_members` LIKE 'botid'|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,4 @@
DROP INDEX IF EXISTS `PRIMARY` ON `raid_members`;
CREATE UNIQUE INDEX IF NOT EXISTS `UNIQUE` ON `raid_members`(`name`);
ALTER TABLE `raid_members` ADD COLUMN `bot_id` int(4) NOT NULL DEFAULT 0 AFTER `charid`;
ALTER TABLE `raid_members` ADD COLUMN `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST;

View File

@ -120,8 +120,9 @@ ClientListEntry::~ClientListEntry()
Camp(); // updates zoneserver's numplayers
client_list.RemoveCLEReferances(this);
}
for (auto &elem : tell_queue)
safe_delete_array(elem);
for (auto& elem: tell_queue) {
safe_delete_array(elem)
};
tell_queue.clear();
}
@ -282,8 +283,9 @@ void ClientListEntry::ClearVars(bool iAll)
pLFG = 0;
gm = 0;
pClientVersion = 0;
for (auto &elem : tell_queue)
safe_delete_array(elem);
for (auto& elem: tell_queue) {
safe_delete_array(elem)
};
tell_queue.clear();
}

View File

@ -11,6 +11,7 @@ SET(zone_sources
beacon.cpp
bonuses.cpp
bot.cpp
bot_raid.cpp
bot_command.cpp
bot_database.cpp
botspellsai.cpp

View File

@ -3829,11 +3829,11 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
} //end `if there is some damage being done and theres anattacker person involved`
Mob *pet = GetPet();
// pets that have GHold will never automatically add NPCs
// pets that have Hold and no Focus will add NPCs if they're engaged
// pets that have Hold and Focus will not add NPCs
if (
Mob* pet = GetPet();
pet &&
!pet->IsFamiliar() &&
!pet->GetSpecialAbility(IMMUNE_AGGRO) &&
@ -3844,26 +3844,25 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
attacker != this &&
!attacker->IsCorpse() &&
!pet->IsGHeld() &&
!attacker->IsTrap()
!attacker->IsTrap() &&
!pet->IsHeld()
) {
if (!pet->IsHeld()) {
LogAggro("Sending pet [{}] into battle due to attack", pet->GetName());
if (IsClient()) {
// if pet was sitting his new mode is follow
// following after the battle (live verified)
if (pet->GetPetOrder() == SPO_Sit) {
pet->SetPetOrder(SPO_Follow);
}
// fix GUI sit button to be unpressed and stop sitting regen
CastToClient()->SetPetCommandState(PET_BUTTON_SIT, 0);
pet->SetAppearance(eaStanding);
LogAggro("Sending pet [{}] into battle due to attack", pet->GetName());
if (IsClient()) {
// if pet was sitting his new mode is follow
// following after the battle (live verified)
if (pet->GetPetOrder() == SPO_Sit) {
pet->SetPetOrder(SPO_Follow);
}
pet->AddToHateList(attacker, 1, 0, true, false, false, spell_id);
pet->SetTarget(attacker);
MessageString(Chat::NPCQuestSay, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName());
// fix GUI sit button to be unpressed and stop sitting regen
CastToClient()->SetPetCommandState(PET_BUTTON_SIT, 0);
pet->SetAppearance(eaStanding);
}
pet->AddToHateList(attacker, 1, 0, true, false, false, spell_id);
pet->SetTarget(attacker);
MessageString(Chat::NPCQuestSay, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName());
}
if (GetTempPetCount()) {
@ -3884,9 +3883,18 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
else {
int64 origdmg = damage;
damage = AffectMagicalDamage(damage, spell_id, iBuffTic, attacker);
if (origdmg != damage && attacker && attacker->IsClient()) {
if (attacker->CastToClient()->GetFilter(FilterDamageShields) != FilterHide)
attacker->Message(Chat::Yellow, "The Spellshield absorbed %d of %d points of damage", origdmg - damage, origdmg);
if (
origdmg != damage &&
attacker &&
attacker->IsClient() &&
attacker->CastToClient()->GetFilter(FilterDamageShields) != FilterHide
) {
attacker->Message(
Chat::Yellow,
"The Spellshield absorbed %d of %d points of damage",
origdmg - damage,
origdmg
);
}
if (damage == 0 && attacker && origdmg != damage && IsClient()) {
//Kayen: Probably need to add a filter for this - Not sure if this msg is correct but there should be a message for spell negate/runes.
@ -4212,7 +4220,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
//Note: if players can become pets, they will not receive damage messages of their own
//this was done to simplify the code here (since we can only effectively skip one mob on queue)
eqFilterType filter;
Mob *skip = attacker;
Mob* skip = attacker;
if (attacker && attacker->GetOwnerID()) {
//attacker is a pet, let pet owners see their pet's damage
Mob* owner = attacker->GetOwner();
@ -4239,6 +4247,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
owner->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter);
}
}
skip = owner;
}
else {
@ -4385,11 +4394,11 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
spells[spell_id].name /* Message4 */
);
}
} //end packet sending
}
}
} //end packet sending
void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id)
void Mob::HealDamage(uint64 amount, Mob* caster, uint16 spell_id)
{
int64 maxhp = GetMaxHP();
int64 curhp = GetHP();
@ -4403,16 +4412,17 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id)
if (acthealed > 100) {
if (caster) {
if (IsBuffSpell(spell_id)) { // hots
// message to caster
if (caster->IsClient() && caster == this) {
if (caster->CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater)
// message to caster
if ((caster->IsClient() && caster == this)) {
if (caster->CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater) {
FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime,
HOT_HEAL_SELF, itoa(acthealed), spells[spell_id].name);
}
else
FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime,
YOU_HEALED, GetCleanName(), itoa(acthealed));
}
else if (caster->IsClient() && caster != this) {
else if ((caster->IsClient() && caster != this)) {
if (caster->CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater)
caster->FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime,
HOT_HEAL_OTHER, GetCleanName(), itoa(acthealed),
@ -4421,6 +4431,7 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id)
caster->FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime,
YOU_HEAL, GetCleanName(), itoa(acthealed));
}
// message to target
if (IsClient() && caster != this) {
if (CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater)
@ -4435,7 +4446,7 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id)
else { // normal heals
FilteredMessageString(caster, Chat::NonMelee, FilterSpellDamage,
YOU_HEALED, caster->GetCleanName(), itoa(acthealed));
if (caster != this)
caster->FilteredMessageString(caster, Chat::NonMelee, FilterSpellDamage,
YOU_HEAL, GetCleanName(), itoa(acthealed));
}
@ -4446,10 +4457,12 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id)
}
if (curhp < maxhp) {
if ((curhp + amount) > maxhp)
if ((curhp + amount) > maxhp) {
curhp = maxhp;
else
}
else {
curhp += amount;
}
SetHP(curhp);
SendHPUpdate();

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@
#include "../common/global_define.h"
#include "guild_mgr.h"
#include "worldserver.h"
#include "raids.h"
#include <sstream>
@ -42,9 +43,6 @@ constexpr uint32 BOT_FOLLOW_DISTANCE_WALK = 1000; // as DSq value (~31.623 units
constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds
//constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 5000; // 5 seconds
//constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 20000; // 20 seconds
extern WorldServer worldserver;
constexpr int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this
@ -53,8 +51,6 @@ constexpr int MaxDisciplineTimer = 10;
constexpr int DisciplineReuseStart = MaxSpellTimer + 1;
constexpr int MaxTimer = MaxSpellTimer + MaxDisciplineTimer;
// nHSND negative Healer/Slower/Nuker/Doter
// pH positive Healer
// pS positive Slower
@ -141,13 +137,13 @@ public:
void Damage(Mob* from, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1,
bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) override;
bool HasRaid() final { return (GetRaid() ? true : false); }
bool HasGroup() final { return (GetGroup() ? true : false); }
Raid* GetRaid() final { return entity_list.GetRaidByMob(this); }
bool HasRaid() final { return GetRaid() != nullptr; }
bool HasGroup() final { return GetGroup() != nullptr; }
Raid* GetRaid() final { return entity_list.GetRaidByBot(this); }
Group* GetGroup() final { return entity_list.GetGroupByMob(this); }
// Common, but informal "interfaces" with Client object
uint32 CharacterID() { return GetBotID(); } // Just returns the Bot Id
uint32 CharacterID() const { return GetBotID(); }
inline bool IsInAGuild() const { return (_guildId != GUILD_NONE && _guildId != 0); }
inline bool IsInGuild(uint32 in_gid) const { return (in_gid == _guildId && IsInAGuild()); }
inline uint32 GuildID() const { return _guildId; }
@ -173,20 +169,19 @@ public:
int GetHandToHandDamage(void) override;
bool TryFinishingBlow(Mob *defender, int64 &damage) override;
void DoRiposte(Mob* defender) override;
inline int32 GetATK() const override { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQ::skills::SkillOffense)) * 9 / 10); }
inline int32 GetATK() { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQ::skills::SkillOffense)) * 9 / 10); }
inline int32 GetATKBonus() const override { return itembonuses.ATK + spellbonuses.ATK; }
uint32 GetTotalATK();
uint32 GetATKRating();
uint16 GetPrimarySkillValue();
uint16 MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const;
inline uint16 MaxSkill(EQ::skills::SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); }
inline uint16 MaxSkill(EQ::skills::SkillType skillid) { return MaxSkill(skillid, GetClass(), GetLevel()); }
int GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target = nullptr) override;
void DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance = false);
void TryBackstab(Mob *other,int ReuseTime = 10) override;
void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10) override;
void RogueAssassinate(Mob* other) override;
void DoClassAttacks(Mob *target, bool IsRiposte=false);
bool CanDoSpecialAttack(Mob *other);
void CalcBonuses() override;
void CalcItemBonuses(StatBonuses* newbon);
void AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool isAug = false, bool isTribute = false, int rec_override = 0);
@ -197,7 +192,7 @@ public:
bool IsNPC() const override { return false; }
Mob* GetOwner() override;
Mob* GetOwnerOrSelf() override;
inline bool HasOwner() override { return (GetBotOwner() ? true : false); }
inline bool HasOwner() override { return GetBotOwner() != nullptr; }
int64 CalcMaxMana() override;
void SetAttackTimer() override;
uint64 GetClassHPFactor();
@ -215,36 +210,35 @@ public:
void Stand();
bool IsSitting() const override;
bool IsStanding();
int GetWalkspeed() const override { return (int)((float)_GetWalkSpeed() * 1.785714285f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143
int GetRunspeed() const override { return (int)((float)_GetRunSpeed() * 1.785714285f); }
int GetWalkspeed() { return (int)((float)_GetWalkSpeed() * 1.785714285f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143
int GetRunspeed() { return (int)((float)_GetRunSpeed() * 1.785714285f); }
void WalkTo(float x, float y, float z) override;
void RunTo(float x, float y, float z) override;
void StopMoving() override;
void StopMoving(float new_heading) override;
//bool GetCombatJitterFlag() { return m_combat_jitter_flag; }
bool GetGuardFlag() { return m_guard_flag; }
bool GetGuardFlag() const { return m_guard_flag; }
void SetGuardFlag(bool flag = true) { m_guard_flag = flag; }
bool GetHoldFlag() { return m_hold_flag; }
bool GetHoldFlag() const { return m_hold_flag; }
void SetHoldFlag(bool flag = true) { m_hold_flag = flag; }
bool GetAttackFlag() { return m_attack_flag; }
bool GetAttackFlag() const { return m_attack_flag; }
void SetAttackFlag(bool flag = true) { m_attack_flag = flag; }
bool GetAttackingFlag() { return m_attacking_flag; }
bool GetPullFlag() { return m_pull_flag; }
bool GetAttackingFlag() const { return m_attacking_flag; }
bool GetPullFlag() const { return m_pull_flag; }
void SetPullFlag(bool flag = true) { m_pull_flag = flag; }
bool GetPullingFlag() { return m_pulling_flag; }
bool GetReturningFlag() { return m_returning_flag; }
bool GetPullingFlag() const { return m_pulling_flag; }
bool GetReturningFlag() const { return m_returning_flag; }
bool UseDiscipline(uint32 spell_id, uint32 target);
uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets);
uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid);
uint8 GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid);
bool GetNeedsCured(Mob *tar);
bool GetNeedsHateRedux(Mob *tar);
bool HasOrMayGetAggro();
void SetDefaultBotStance();
void SetSurname(std::string bot_surname);
void SetTitle(std::string bot_title);
void SetSuffix(std::string bot_suffix);
std::string GetSurname() { return _surname; }
std::string GetTitle() { return _title; }
std::string GetSuffix() { return _suffix; }
void SetSurname(std::string_view bot_surname);
void SetTitle(std::string_view bot_title);
void SetSuffix(std::string_view bot_suffix);
std::string GetSurname() const { return _surname; }
std::string GetTitle() const { return _title; }
std::string GetSuffix() const { return _suffix; }
inline virtual int32 GetMaxStat();
inline virtual int32 GetMaxResist();
inline virtual int32 GetMaxSTR();
@ -282,19 +276,18 @@ public:
uint32 CalcCurrentWeight();
int GroupLeadershipAAHealthEnhancement();
int GroupLeadershipAAManaEnhancement();
int GroupLeadershipAAHealthRegeneration();
int GroupLeadershipAAHealthRegeneration();
int GroupLeadershipAAOffenseEnhancement();
void CalcRestState();
int64 CalcMaxEndurance(); //This calculates the maximum endurance we can have
int64 CalcBaseEndurance(); //Calculates Base End
int64 CalcEnduranceRegen(); //Calculates endurance regen used in DoEnduranceRegen()
int64 GetEndurance() const {return cur_end;} //This gets our current endurance
int64 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call
int64 CalcEnduranceRegenCap();
inline uint8 GetEndurancePercent() { return (uint8)((float)cur_end / (float)max_end * 100.0f); }
void SetEndurance(int32 newEnd); //This sets the current endurance to the new value
void DoEnduranceRegen(); //This Regenerates endurance
void DoEnduranceUpkeep(); //does the endurance upkeep
int64 CalcMaxEndurance();
int64 CalcBaseEndurance();
int64 CalcEnduranceRegen();
int64 GetEndurance() const override {return cur_end;}
int64 GetMaxEndurance() const override {return max_end;}
int64 CalcEnduranceRegenCap();
inline uint8 GetEndurancePercent() override { return (uint8)((float)cur_end / (float)max_end * 100.0f); }
void SetEndurance(int32 newEnd) override;
void DoEnduranceUpkeep();
bool AI_AddBotSpells(uint32 bot_spell_id);
void AddSpellToBotList(
@ -330,19 +323,20 @@ public:
);
void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot);
// AI Methods
bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes);
bool AI_EngagedCastCheck() override;
bool AI_PursueCastCheck() override;
bool AI_IdleCastCheck() override;
bool AIHealRotation(Mob* tar, bool useFastHeals);
bool GetPauseAI() { return _pauseAI; }
bool GetPauseAI() const { return _pauseAI; }
void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; }
uint8 GetStopMeleeLevel() { return _stopMeleeLevel; }
uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; }
void SetStopMeleeLevel(uint8 level);
void SetGuardMode();
void SetHoldMode();
uint32 GetBotCasterRange() { return m_bot_caster_range; }
uint32 GetBotCasterRange() const { return m_bot_caster_range; }
bool IsValidSpellRange(uint16 spell_id, Mob const* tar);
// Bot AI Methods
@ -375,7 +369,7 @@ public:
bool IsImmuneToSpell(uint16 spell_id, Mob *caster) override;
virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot);
virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot slot = EQ::spells::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1,
uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0);
uint32* oSpellWillFinish = nullptr, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0);
inline int64 GetFocusEffect(focusType type, uint16 spell_id, Mob *caster = nullptr, bool from_buff_tic = false) override
{ return Mob::GetFocusEffect(type, spell_id, caster, from_buff_tic); }
inline bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false,
@ -389,40 +383,51 @@ public:
bool GetBotOwnerDataBuckets();
bool GetBotDataBuckets();
bool CheckDataBucket(std::string bucket_name, std::string bucket_value, uint8 bucket_comparison);
bool CheckDataBucket(const std::string& bucket_name, const std::string& bucket_value, uint8 bucket_comparison);
// Bot Equipment & Inventory Class Methods
void BotTradeAddItem(const EQ::ItemInstance* inst, uint16 slot_id, std::string* error_message, bool save_to_database = true);
void EquipBot(std::string* error_message);
bool CheckLoreConflict(const EQ::ItemData* item);
void UpdateEquipmentLight() override { m_Light.Type[EQ::lightsource::LightEquipment] = m_inv.FindBrightestLightType(); m_Light.Level[EQ::lightsource::LightEquipment] = EQ::lightsource::TypeToLevel(m_Light.Type[EQ::lightsource::LightEquipment]); }
inline EQ::InventoryProfile& GetInv() { return m_inv; }
void UpdateEquipmentLight() override
{
m_Light.Type[EQ::lightsource::LightEquipment] = m_inv.FindBrightestLightType();
m_Light.Level[EQ::lightsource::LightEquipment] = EQ::lightsource::TypeToLevel(m_Light.Type[EQ::lightsource::LightEquipment]);
}
inline EQ::InventoryProfile& GetInv() override { return m_inv; }
// Static Class Methods
//static void DestroyBotRaidObjects(Client* client); // Can be removed after bot raids are dumped
static Bot* LoadBot(uint32 botID);
static uint32 SpawnedBotCount(const uint32 owner_id, uint8 class_id = NO_CLASS);
static void LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp);
//static bool SetBotOwnerCharacterID(uint32 botID, uint32 botOwnerCharacterID, std::string* error_message);
static bool IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined);
static Bot* GetBotByBotClientOwnerAndBotName(Client* c, std::string botName);
static void ProcessBotGroupInvite(Client* c, std::string botName);
static void ProcessBotGroupDisband(Client* c, std::string botName);
static Bot* GetBotByBotClientOwnerAndBotName(Client* c, const std::string& botName);
static void ProcessBotGroupInvite(Client* c, std::string const& botName);
static void ProcessBotGroupDisband(Client* c, const std::string& botName);
static void BotOrderCampAll(Client* c, uint8 class_id = NO_CLASS);
static void ProcessBotInspectionRequest(Bot* inspectedBot, Client* client);
static void LoadAndSpawnAllZonedBots(Client* bot_owner);
static bool GroupHasBot(Group* group);
static Bot* GetFirstBotInGroup(Group* group);
static void ProcessClientZoneChange(Client* botOwner);
static void ProcessBotOwnerRefDelete(Mob* botOwner); // Removes a Client* reference when the Client object is destroyed
static void ProcessGuildInvite(Client* guildOfficer, Bot* botToGuild); // Processes a client's request to guild a bot
static bool ProcessGuildRemoval(Client* guildOfficer, std::string botName); // Processes a client's request to deguild a bot
static void ProcessBotOwnerRefDelete(Mob* botOwner); // Removes a Client* reference when the Client object is destroyed
static int32 GetSpellRecastTimer(Bot *caster, int timer_index);
static bool CheckSpellRecastTimers(Bot *caster, int SpellIndex);
static int32 GetDisciplineRecastTimer(Bot *caster, int timer_index);
static bool CheckDisciplineRecastTimers(Bot *caster, int timer_index);
static uint32 GetDisciplineRemainingTime(Bot *caster, int timer_index);
//Raid methods
static void ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite = false);
static void RemoveBotFromRaid(Bot* bot);
inline void SetDirtyAutoHaters() { m_dirtyautohaters = true; }
static void CreateBotRaid(Mob* invitee, Client* invitor, bool group_invite, Raid* raid);
static void
ProcessBotGroupAdd(Group* group, Raid* raid, Client* client = nullptr, bool new_raid = false, bool initial = false);
static std::list<BotSpell> GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect);
static std::list<BotSpell> GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType);
static std::list<BotSpell> GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType);
@ -452,8 +457,8 @@ public:
static BotSpell GetBestBotSpellForResistDebuff(Bot* botCaster, Mob* target);
static NPCType *CreateDefaultNPCTypeStructForBot(
std::string botName,
std::string botLastName,
const std::string& botName,
const std::string& botLastName,
uint8 botLevel,
uint16 botRace,
uint8 botClass,
@ -467,8 +472,8 @@ public:
// "GET" Class Methods
uint32 GetBotID() const { return _botID; }
uint32 GetBotOwnerCharacterID() { return _botOwnerCharacterID; }
uint32 GetBotSpellID() { return npc_spells_id; }
uint32 GetBotOwnerCharacterID() const { return _botOwnerCharacterID; }
uint32 GetBotSpellID() const { return npc_spells_id; }
Mob* GetBotOwner() { return this->_botOwner; }
uint32 GetBotArcheryRange();
EQ::ItemInstance* GetBotItem(uint16 slot_id);
@ -486,18 +491,12 @@ public:
uint8 GetChanceToCastBySpellType(uint32 spellType);
bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; }
float GetBotCasterMaxRange(float melee_distance_max);
bool IsGroupHealer() { return m_CastingRoles.GroupHealer; }
bool IsGroupSlower() { return m_CastingRoles.GroupSlower; }
bool IsGroupNuker() { return m_CastingRoles.GroupNuker; }
bool IsGroupDoter() { return m_CastingRoles.GroupDoter; }
bool IsGroupHealer() const { return m_CastingRoles.GroupHealer; }
bool IsGroupSlower() const { return m_CastingRoles.GroupSlower; }
bool IsGroupNuker() const { return m_CastingRoles.GroupNuker; }
bool IsGroupDoter() const { return m_CastingRoles.GroupDoter; }
static void UpdateGroupCastingRoles(const Group* group, bool disband = false);
//bool IsRaidHealer() { return m_CastingRoles.RaidHealer; }
//bool IsRaidSlower() { return m_CastingRoles.RaidSlower; }
//bool IsRaidNuker() { return m_CastingRoles.RaidNuker; }
//bool IsRaidDoter() { return m_CastingRoles.RaidDoter; }
//static void UpdateRaidCastingRoles(const Raid* raid, bool disband = false);
bool IsBotCaster() { return IsCasterClass(GetClass()); }
bool IsBotHybrid() { return IsHybridClass(GetClass()); }
bool IsBotINTCaster() { return IsINTCasterClass(GetClass()); }
@ -506,7 +505,6 @@ public:
bool IsBotFighter() { return IsFighterClass(GetClass()); }
bool IsBotNonSpellFighter() { return IsNonSpellFighterClass(GetClass()); }
uint8 GetBotClass() { return GetClass(); }
bool CanHeal();
int GetRawACNoShield(int &shield_ac);
// new heal rotation code
@ -532,8 +530,8 @@ public:
std::shared_ptr<HealRotation>* MemberOfHealRotation() { return &m_member_of_heal_rotation; }
bool GetAltOutOfCombatBehavior() { return _altoutofcombatbehavior;}
bool GetShowHelm() { return _showhelm; }
bool GetAltOutOfCombatBehavior() const { return _altoutofcombatbehavior;}
bool GetShowHelm() const { return _showhelm; }
inline int32 GetSTR() const override { return STR; }
inline int32 GetSTA() const override { return STA; }
inline int32 GetDEX() const override { return DEX; }
@ -609,7 +607,6 @@ public:
void SetBotCharmer(bool c) { _botCharmer = c; }
void SetPetChooser(bool p) { _petChooser = p; }
void SetBotOwner(Mob* botOwner) { this->_botOwner = botOwner; }
// void SetBotOwnerCharacterID(uint32 botOwnerCharacterID) { _botOwnerCharacterID = botOwnerCharacterID; }
void SetRangerAutoWeaponSelect(bool enable) { GetClass() == RANGER ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; }
void SetBotStance(EQ::constants::StanceType botStance) {
if (botStance >= EQ::constants::stancePassive && botStance <= EQ::constants::stanceBurnAE)
@ -650,7 +647,7 @@ public:
void SetBotEnforceSpellSetting(bool enforcespellsettings, bool save = false);
bool GetBotEnforceSpellSetting() const { return m_enforce_spell_settings; }
static void SpawnBotGroupByName(Client* c, std::string botgroup_name, uint32 leader_id);
static void SpawnBotGroupByName(Client* c, const std::string& botgroup_name, uint32 leader_id);
std::string CreateSayLink(Client* botOwner, const char* message, const char* name);
@ -663,8 +660,8 @@ public:
// Publicized private functions
static NPCType *FillNPCTypeStruct(
uint32 botSpellsID,
std::string botName,
std::string botLastName,
const std::string& botName,
const std::string& botLastName,
uint8 botLevel,
uint16 botRace,
uint8 botClass,
@ -722,43 +719,116 @@ public:
// New accessors for BotDatabase access
bool DeleteBot();
uint32* GetTimers() { return timers; }
uint32 GetLastZoneID() { return _lastZoneId; }
int32 GetBaseAC() { return _baseAC; }
int32 GetBaseATK() { return _baseATK; }
int32 GetBaseSTR() { return _baseSTR; }
int32 GetBaseSTA() { return _baseSTA; }
int32 GetBaseCHA() { return _baseCHA; }
int32 GetBaseDEX() { return _baseDEX; }
int32 GetBaseINT() { return _baseINT; }
int32 GetBaseAGI() { return _baseAGI; }
int32 GetBaseWIS() { return _baseWIS; }
int32 GetBaseFR() { return _baseFR; }
int32 GetBaseCR() { return _baseCR; }
int32 GetBaseMR() { return _baseMR; }
int32 GetBasePR() { return _basePR; }
int32 GetBaseDR() { return _baseDR; }
int32 GetBaseCorrup() { return _baseCorrup; }
uint32 GetLastZoneID() const { return _lastZoneId; }
int32 GetBaseAC() const { return _baseAC; }
int32 GetBaseATK() const { return _baseATK; }
int32 GetBaseSTR() const { return _baseSTR; }
int32 GetBaseSTA() const { return _baseSTA; }
int32 GetBaseCHA() const { return _baseCHA; }
int32 GetBaseDEX() const { return _baseDEX; }
int32 GetBaseINT() const { return _baseINT; }
int32 GetBaseAGI() const { return _baseAGI; }
int32 GetBaseWIS() const { return _baseWIS; }
int32 GetBaseFR() const { return _baseFR; }
int32 GetBaseCR() const { return _baseCR; }
int32 GetBaseMR() const { return _baseMR; }
int32 GetBasePR() const { return _basePR; }
int32 GetBaseDR() const { return _baseDR; }
int32 GetBaseCorrup() const { return _baseCorrup; }
void Signal(int signal_id);
void SendPayload(int payload_id, std::string payload_value = std::string());
void OwnerMessage(std::string message);
void OwnerMessage(const std::string& message);
//Raid additions
Raid* p_raid_instance;
static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND];
bool BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid);
bool BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid);
bool BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los);
bool BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass);
bool BotCastEscape(Mob*& tar, uint8 botClass, BotSpell& botSpell, uint32 iSpellTypes);
bool BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los);
bool BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool& checked_los);
bool BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell);
bool BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass);
bool BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes);
bool BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes);
bool BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const bool& checked_los);
bool BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los, Raid* raid);
bool BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool checked_los);
bool BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, Raid* raid);
bool BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpell);
bool BotCastCombatSong(Mob* tar, uint8 botLevel);
bool BotCastSong(Mob* tar, uint8 botLevel);
bool CheckIfIncapacitated();
bool IsAIProcessValid(const Client* bot_owner, const Group* bot_group, const Raid* raid);
Client* SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint32 r_group) const;
Mob* SetFollowMob(Client* leash_owner);
Mob* GetBotTarget(Client* bot_owner);
void AcquireBotTarget(Group* bot_group, Raid* raid, Client* leash_owner, float leash_distance);
void SetBotTarget(Client* bot_owner, Raid* raid, Group* bot_group, Client* leash_owner, float lo_distance, float leash_distance, bool bo_alt_combat);
void SetLeashOwnerTarget(Client* leash_owner, Client* bot_owner, float lo_distance, float leash_distance);
void SetOwnerTarget(Client* bot_owner);
void SetBotGroupTarget(const Client* bot_owner, Client* leash_owner, float lo_distance, float leash_distance, Mob* const& bg_member, Mob* bgm_target);
bool IsValidTarget(Client* bot_owner, Client* leash_owner, float lo_distance, float leash_distance, bool bo_alt_combat, Mob* tar, float tar_distance);
bool PullingFlagChecks(Client* bot_owner);
bool ReturningFlagChecks(Client* bot_owner, float fm_distance);
void BotPullerProcess(Client* bot_owner, Raid* raid);
// Movement Methods
void CalcMeleeDistances(
const Mob* tar,
const EQ::ItemInstance* const& p_item,
const EQ::ItemInstance* const& s_item,
bool behind_mob,
bool backstab_weapon,
float& melee_distance_max,
float& melee_distance
) const;
// Combat Checks
void SetBerserkState();
bool CheckIfCasting(float fm_distance);
void HealRotationChecks();
void CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item);
// Try Combat Methods
bool TryEvade(Mob* tar);
bool TryFacingTarget(Mob* tar);
bool TryRangedAttack(Mob* tar);
bool TryClassAttacks(Mob* tar);
bool TryPrimaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* p_item);
bool TrySecondaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* s_item);
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
bool TryMeditate();
bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
bool TryBardMovementCasts();
void SetRangerCombatWeapon(bool atArcheryRange);
// Public "Refactor" Methods
static bool CheckSpawnConditions(Client* c);
protected:
void PetAIProcess();
void BotMeditate(bool isSitting);
bool CheckBotDoubleAttack(bool Triple = false);
void PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* client);
bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0) override;
bool AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = nullptr) override;
BotCastingRoles& GetCastingRoles() { return m_CastingRoles; }
void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; }
void SetGroupSlower(bool flag = true) { m_CastingRoles.GroupSlower = flag; }
void SetGroupNuker(bool flag = true) { m_CastingRoles.GroupNuker = flag; }
void SetGroupDoter(bool flag = true) { m_CastingRoles.GroupDoter = flag; }
//void SetRaidHealer(bool flag = true) { m_CastingRoles.RaidHealer = flag; }
//void SetRaidSlower(bool flag = true) { m_CastingRoles.RaidSlower = flag; }
//void SetRaidNuker(bool flag = true) { m_CastingRoles.RaidNuker = flag; }
//void SetRaidDoter(bool flag = true) { m_CastingRoles.RaidDoter = flag; }
std::deque<int> bot_signal_q;
std::vector<BotSpells_Struct> AIBot_spells;
@ -801,8 +871,7 @@ private:
Timer m_evade_timer; // can be moved to pTimers at some point
Timer m_alt_combat_hate_timer;
Timer m_auto_defend_timer;
//Timer m_combat_jitter_timer;
//bool m_combat_jitter_flag;
bool m_dirtyautohaters;
bool m_guard_flag;
bool m_hold_flag;
bool m_attack_flag;
@ -849,15 +918,12 @@ private:
// Class Methods
void LoadAAs();
int32 acmod();
void GenerateBaseStats();
void GenerateAppearance();
void GenerateArmorClass();
int32 GenerateBaseHitPoints();
int32 GenerateBaseManaPoints();
void GenerateSpecialAttacks();
void SetBotID(uint32 botID);
//void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; }
void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; }
void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; }
void SetReturningFlag(bool flag = true) { m_returning_flag = flag; }
@ -870,9 +936,6 @@ private:
bool LoadPet(); // Load and spawn bot pet if there is one
bool SavePet(); // Save and depop bot pet if there is one
bool DeletePet();
public:
static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND];
};
bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID);

File diff suppressed because it is too large Load Diff

287
zone/bot_raid.cpp Normal file
View File

@ -0,0 +1,287 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "bot.h"
#include "object.h"
#include "raids.h"
#include "doors.h"
#include "quest_parser_collection.h"
#include "../common/data_verification.h"
extern volatile bool is_zone_loaded;
extern bool Critical;
std::vector<RaidMember> Raid::GetRaidGroupMembers(uint32 gid)
{
std::vector<RaidMember> raid_group_members;
raid_group_members.clear();
for (int i = 0; i < MAX_RAID_MEMBERS; ++i)
{
if (members[i].member && members[i].GroupNumber == gid)
{
raid_group_members.push_back(members[i]);
}
}
return raid_group_members;
}
// Returns Bot members that are in the raid
// passing in owner will return all Bots that have the same owner.
std::vector<Bot*> Raid::GetRaidBotMembers(uint32 owner)
{
std::vector<Bot*> raid_members_bots;
raid_members_bots.clear();
for (int i = 0; i < MAX_RAID_MEMBERS; i++) {
if (
members[i].member &&
members[i].member->IsBot()
) {
auto b_member = members[i].member->CastToBot();
if (owner && b_member->GetBotOwnerCharacterID() == owner) {
raid_members_bots.emplace_back(b_member);
} else if (!owner) {
raid_members_bots.emplace_back(b_member);
}
}
}
return raid_members_bots;
}
// Returns Bot members that are in the group specified
// passing in owner will return only Bots that have the same owner.
std::vector<Bot*> Raid::GetRaidGroupBotMembers(uint32 gid)
{
std::vector<Bot*> raid_members_bots;
raid_members_bots.clear();
for (int i = 0; i < MAX_RAID_MEMBERS; i++) {
if (
members[i].member &&
members[i].member->IsBot() &&
members[i].GroupNumber == gid
) {
auto b_member = members[i].member->CastToBot();
raid_members_bots.emplace_back(b_member);
raid_members_bots.emplace_back(b_member);
}
}
return raid_members_bots;
}
void Raid::HandleBotGroupDisband(uint32 owner, uint32 gid)
{
auto raid_members_bots = gid != RAID_GROUPLESS ? GetRaidGroupBotMembers(gid) : GetRaidBotMembers(owner);
// If any of the bots are a group leader then re-create the botgroup on disband, dropping any clients
for (auto& bot_iter: raid_members_bots) {
// Remove the entire BOT group in this case
if (
bot_iter &&
gid != RAID_GROUPLESS &&
IsRaidMember(bot_iter->GetName()) &&
IsGroupLeader(bot_iter->GetName())
) {
auto r_group_members = GetRaidGroupMembers(GetGroup(bot_iter->GetName()));
auto group_inst = new Group(bot_iter);
entity_list.AddGroup(group_inst);
database.SetGroupID(bot_iter->GetCleanName(), group_inst->GetID(), bot_iter->GetBotID());
database.SetGroupLeaderName(group_inst->GetID(), bot_iter->GetName());
for (auto member_iter: r_group_members) {
if (member_iter.member->IsBot()) {
auto b_member = member_iter.member->CastToBot();
if (strcmp(b_member->GetName(), bot_iter->GetName()) == 0) {
bot_iter->SetFollowID(owner);
} else {
Bot::AddBotToGroup(b_member, group_inst);
}
Bot::RemoveBotFromRaid(b_member);
}
}
} else {
Bot::RemoveBotFromRaid(bot_iter);
}
}
}
uint8 Bot::GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid) {
if (raid) {
uint32 r_group = raid->GetGroup(GetName());
auto raid_group_members = raid->GetRaidGroupMembers(r_group);
for (auto& m: raid_group_members) {
if (m.member && !m.member->qglobal) {
if (m.member->GetHPRatio() <= hpr) {
need_healed++;
}
if (includePets) {
if (m.member->GetPet() && m.member->GetPet()->GetHPRatio() <= hpr) {
need_healed++;
}
}
}
}
}
return need_healed;
}
void Bot::ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite) {
if (!invitee || !invitor) {
return;
}
if (invitee->IsBot() &&
invitor->HasRaid() &&
invitee->GetOwner()->HasRaid() &&
invitor->GetRaid()->IsRaidMember(invitee->GetOwner()->GetName())
) {
// If the Bot Owner is in our raid we need to be able to invite their Bots
}
else if (invitee->IsBot() && (invitee->CastToBot()->GetBotOwnerCharacterID() != invitor->CharacterID())) {
invitor->Message(
Chat::Red,
fmt::format(
"{} is not your Bot. You can only invite your own Bots, or Bots that belong to a Raid member.",
invitee->GetCleanName()
).c_str()
);
return;
}
Raid* raid = entity_list.GetRaidByClient(invitor);
Bot::CreateBotRaid(invitee, invitor, group_invite, raid);
}
void Bot::CreateBotRaid(Mob* invitee, Client* invitor, bool group_invite, Raid* raid) {
Group* g_invitee = invitee->GetGroup();
Group* g_invitor = invitor->GetGroup();
if (g_invitee && invitor->IsClient()) {
if (!g_invitee->IsLeader(invitee)) {
invitor->Message(
Chat::Red,
fmt::format(
"You can only invite group leaders or ungrouped bots. Try {} instead.",
g_invitee->GetLeader()->GetCleanName()
).c_str()
);
return;
}
}
bool new_raid = false;
if (!raid) {
new_raid = true;
raid = new Raid(invitor);
entity_list.AddRaid(raid);
raid->SetRaidDetails();
}
// Add Invitor to new raid
if (new_raid) {
if (g_invitor) {
ProcessBotGroupAdd(g_invitor, raid, nullptr, true, true);
} else {
raid->SendRaidCreate(invitor);
raid->AddMember(invitor, 0xFFFFFFFF, true, false, true);
raid->SendMakeLeaderPacketTo(invitor->GetName(), invitor);
if (raid->IsLocked()) {
raid->SendRaidLockTo(invitor);
}
}
}
// Add Bot Group or Client Bot Group to raid
if (g_invitee) {
ProcessBotGroupAdd(g_invitee, raid);
// Add individual client to raid
} else if (invitee->IsClient()) {
ProcessBotGroupAdd(g_invitee, raid, invitee->CastToClient());
// Add individual bot to raid
} else {
auto gid = raid->GetGroup(invitor->GetName());
auto b = invitee->CastToBot();
// gives us a choice to either invite directly into the clients Raid Group, or just into the Raid
if (group_invite && raid->GroupCount(gid) < MAX_GROUP_MEMBERS) {
raid->AddBot(b, gid);
} else {
raid->AddBot(b);
}
if (new_raid) {
invitee->SetFollowID(invitor->GetID());
}
}
}
void Bot::ProcessBotGroupAdd(Group* group, Raid* raid, Client* client, bool new_raid, bool initial) {
uint32 raid_free_group_id = raid->GetFreeGroup();
if (group) {
for (int x = 0; x < MAX_GROUP_MEMBERS; x++) {
if (group->members[x]) {
Client* c = nullptr;
Bot* b = nullptr;
if (group->members[x] && group->members[x]->IsBot()) {
b = group->members[x]->CastToBot();
raid->AddBot(b, raid_free_group_id, false, x == 0, false);
} else if (group->members[x] && group->members[x]->IsClient()) {
c = group->members[x]->CastToClient();
raid->SendRaidCreate(c);
raid->AddMember(
c,
raid_free_group_id,
new_raid,
x == 0,
false
);
raid->SendMakeLeaderPacketTo(raid->leadername, c);
raid->SendBulkRaid(c);
}
}
}
group->JoinRaidXTarget(raid, initial);
group->DisbandGroup(true);
} else if (client) {
raid->SendRaidCreate(client);
raid->AddMember(client, RAID_GROUPLESS, false, false, true);
raid->SendMakeLeaderPacketTo(raid->leadername, client);
raid->SendBulkRaid(client);
if (raid->IsLocked()) {
raid->SendRaidLockTo(client);
}
}
raid->GroupUpdate(raid_free_group_id);
}

41
zone/bot_raid.h Normal file
View File

@ -0,0 +1,41 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef BOT_RAID_H
#define BOT_RAID_H
#include "bot_structs.h"
#include "mob.h"
#include "client.h"
#include "pets.h"
#include "heal_rotation.h"
#include "groups.h"
#include "corpse.h"
#include "zonedb.h"
#include "../common/zone_store.h"
#include "string_ids.h"
#include "../common/misc_functions.h"
#include "../common/global_define.h"
#include "guild_mgr.h"
#include "worldserver.h"
#include "raids.h"
#include <sstream>
#endif // BOT_RAID_H

File diff suppressed because it is too large Load Diff

View File

@ -807,16 +807,17 @@ void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CO
AddPacket(app, ack_req);
return;
}
// if the program doesnt care about the status or if the status isnt what we requested
if (required_state != CLIENT_CONNECTINGALL && client_state != required_state)
{
// todo: save packets for later use
AddPacket(app, ack_req);
}
else
if(eqs)
eqs->QueuePacket(app, ack_req);
else if (eqs)
{
eqs->QueuePacket(app, ack_req);
}
}
void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CONN_STATUS required_state) {
@ -827,7 +828,7 @@ void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CON
return;
}
else {
if(eqs)
if(eqs)
eqs->FastQueuePacket((EQApplicationPacket **)app, ack_req);
else if (app && (*app))
delete *app;
@ -1330,6 +1331,7 @@ void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num
cm->chan_num = chan_num;
strcpy(&cm->message[0], buffer);
QueuePacket(&app);
bool senderCanTrainSelf = RuleB(Client, SelfLanguageLearning);

View File

@ -2047,6 +2047,7 @@ private:
bool CanTradeFVNoDropItem();
void SendMobPositions();
void PlayerTradeEventLog(Trade *t, Trade *t2);
};
#endif

View File

@ -51,7 +51,7 @@ int32 Client::GetMaxStat() const
else {
base = 330;
}
return (base);
return base;
}
int32 Client::GetMaxResist() const

File diff suppressed because it is too large Load Diff

View File

@ -194,6 +194,9 @@ bool Client::Process() {
}
if (camp_timer.Check()) {
Raid* raid = entity_list.GetRaidByClient(this);
if (raid)
raid->RemoveMember(this->GetName());
LeaveGroup();
Save();
if (GetMerc())

View File

@ -204,7 +204,7 @@ void command_pvp(Client *c, const Seperator *sep);
void command_qglobal(Client *c, const Seperator *sep);
void command_questerrors(Client *c, const Seperator *sep);
void command_race(Client *c, const Seperator *sep);
void command_raidloot(Client *c, const Seperator *sep);
void command_raidloot(Client* c, const Seperator* sep);
void command_randomfeatures(Client *c, const Seperator *sep);
void command_refreshgroup(Client *c, const Seperator *sep);
void command_reload(Client *c, const Seperator *sep);

View File

@ -389,9 +389,7 @@ void EntityList::GroupProcess()
for (auto &group : group_list)
group->Process();
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
}
void EntityList::QueueToGroupsForNPCHealthAA(Mob *sender, const EQApplicationPacket *app)
@ -609,9 +607,7 @@ void EntityList::AddGroup(Group *group)
}
AddGroup(group, gid);
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
}
void EntityList::AddGroup(Group *group, uint32 gid)
@ -2115,9 +2111,6 @@ Group *EntityList::GetGroupByMob(Mob *mob)
return *iterator;
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return nullptr;
}
@ -2132,9 +2125,6 @@ Group *EntityList::GetGroupByLeaderName(const char *leader)
return *iterator;
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return nullptr;
}
@ -2149,9 +2139,6 @@ Group *EntityList::GetGroupByID(uint32 group_id)
return *iterator;
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return nullptr;
}
@ -2170,13 +2157,12 @@ Group *EntityList::GetGroupByClient(Client *client)
iterator = group_list.begin();
while (iterator != group_list.end()) {
if ((*iterator)->IsGroupMember(client->CastToMob()))
if ((*iterator)->IsGroupMember(client->CastToMob())) {
return *iterator;
}
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return nullptr;
}
@ -2187,9 +2173,9 @@ Raid *EntityList::GetRaidByLeaderName(const char *leader)
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
if ((*iterator)->GetLeader())
if(strcmp((*iterator)->GetLeader()->GetName(), leader) == 0)
return *iterator;
if ((*iterator)->GetLeader() && strcmp((*iterator)->GetLeader()->GetName(), leader) == 0) {
return *iterator;
}
++iterator;
}
return nullptr;
@ -2209,22 +2195,20 @@ Raid *EntityList::GetRaidByID(uint32 id)
return nullptr;
}
Raid *EntityList::GetRaidByClient(Client* client)
Raid* EntityList::GetRaidByClient(Client* client)
{
if (client->p_raid_instance) {
return client->p_raid_instance;
}
std::list<Raid *>::iterator iterator;
std::list<Raid*>::iterator iterator;
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
for (auto &member : (*iterator)->members) {
if (member.member) {
if (member.member == client) {
client->p_raid_instance = *iterator;
return *iterator;
}
for (const auto& member : (*iterator)->members) {
if (member.member && member.member == client) {
client->p_raid_instance = *iterator;
return *iterator;
}
}
@ -2233,6 +2217,49 @@ Raid *EntityList::GetRaidByClient(Client* client)
return nullptr;
}
Raid* EntityList::GetRaidByBotName(const char* name)
{
std::list<RaidMember> rm;
auto GetMembersWithNames = [&rm](Raid const* r) -> std::list<RaidMember> {
for (const auto& m : r->members) {
if (strlen(m.membername) > 0)
rm.push_back(m);
}
return rm;
};
for (const auto& r : raid_list) {
for (const auto& m : GetMembersWithNames(r)) {
if (strcmp(m.membername, name) == 0) {
return r;
}
}
}
return nullptr;
}
Raid* EntityList::GetRaidByBot(const Bot* bot)
{
std::list<RaidMember> rm;
auto GetMembersWhoAreBots = [&rm](Raid* r) -> std::list<RaidMember> {
for (auto const& m : r->members) {
if (m.IsBot) {
rm.push_back(m);
}
}
return rm;
};
for (const auto& r : raid_list) {
for (const auto& m : GetMembersWhoAreBots(r)) {
if (m.member->CastToBot() == bot) {
return r;
}
}
}
return nullptr;
}
Raid *EntityList::GetRaidByMob(Mob *mob)
{
@ -2658,6 +2685,12 @@ void EntityList::RemoveAllNPCs()
npc_limit_list.clear();
}
void EntityList::RemoveAllBots()
{
// doesn't clear the data
bot_list.clear();
}
void EntityList::RemoveAllMercs()
{
// doesn't clear the data
@ -2671,9 +2704,6 @@ void EntityList::RemoveAllGroups()
group_list.pop_front();
safe_delete(group);
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
}
void EntityList::RemoveAllRaids()
@ -3081,6 +3111,8 @@ void EntityList::Clear()
{
RemoveAllClients();
entity_list.RemoveAllTraps(); //we can have child npcs so we go first
entity_list.RemoveAllMercs();
entity_list.RemoveAllBots();
entity_list.RemoveAllNPCs();
entity_list.RemoveAllMobs();
entity_list.RemoveAllCorpses();

View File

@ -192,7 +192,8 @@ public:
Client* GetRandomClient(const glm::vec3& location = glm::vec3(0.f), float distance = 0, Client* exclude_client = nullptr);
NPC* GetRandomNPC(const glm::vec3& location = glm::vec3(0.f), float distance = 0, NPC* exclude_npc = nullptr);
Mob* GetRandomMob(const glm::vec3& location = glm::vec3(0.f), float distance = 0, Mob* exclude_mob = nullptr);
Group *GetGroupByMob(Mob* mob);
Group* GetGroupByMob(Mob* mob);
Group* GetGroupByBot(Bot* bot);
bool IsInSameGroupOrRaidGroup(Client *client1, Client *client2);
Group *GetGroupByClient(Client* client);
Group *GetGroupByID(uint32 id);
@ -201,6 +202,8 @@ public:
Raid *GetRaidByClient(Client* client);
Raid *GetRaidByID(uint32 id);
Raid *GetRaidByLeaderName(const char *leader);
Raid* GetRaidByBotName(const char* name);
Raid* GetRaidByBot(const Bot* bot);
Corpse *GetCorpseByOwner(Client* client);
Corpse *GetCorpseByOwnerWithinRange(Client* client, Mob* center, int range);
@ -304,6 +307,7 @@ public:
void RemoveAllMobs();
void RemoveAllClients();
void RemoveAllNPCs();
void RemoveAllBots();
void RemoveAllMercs();
void RemoveAllGroups();
void RemoveAllCorpses();
@ -619,7 +623,7 @@ private:
bool RemoveBot(uint16 entityID);
Mob* GetMobByBotID(uint32 botID);
Bot* GetBotByBotID(uint32 botID);
Bot* GetBotByBotName(std::string botName);
Bot* GetBotByBotName(std::string_view botName);
Client* GetBotOwnerByBotEntityID(uint32 entity_id);
Client* GetBotOwnerByBotID(const uint32 bot_id);
std::list<Bot*> GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID);

View File

@ -1200,7 +1200,7 @@ void Raid::SplitExp(const uint64 exp, Mob* other) {
}
for (const auto& m : members) {
if (m.member) {
if (m.member && !m.IsBot) {
const int32 diff = m.member->GetLevel() - highest_level;
int32 max_diff = -(m.member->GetLevel() * 15 / 10 - m.member->GetLevel());

View File

@ -67,4 +67,3 @@ void command_raidloot(Client *c, const Seperator *sep)
).c_str()
);
}

View File

@ -9,7 +9,7 @@
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
@ -26,6 +26,7 @@
#include "worldserver.h"
#include "string_ids.h"
#include "../common/events/player_event_logs.h"
#include "../common/repositories/group_id_repository.h"
extern EntityList entity_list;
extern WorldServer worldserver;
@ -246,7 +247,7 @@ bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 Characte
uint32 i = 0;
for (i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if(!strcasecmp(membername[i], NewMemberName))
if (!strcasecmp(membername[i], NewMemberName))
{
return false;
}
@ -495,7 +496,7 @@ void Group::SendEndurancePacketFrom(Mob* member)
//updates a group member's client pointer when they zone in
//if the group was in the zone already
bool Group::UpdatePlayer(Mob* update){
bool Group::UpdatePlayer(Mob* update) {
if (!update)
return false;
@ -1138,29 +1139,30 @@ void Group::TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float
}
bool Group::LearnMembers() {
std::string query = StringFormat("SELECT name FROM group_id WHERE groupid = %lu", (unsigned long)GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
return false;
if (results.RowCount() == 0) {
auto rows = GroupIdRepository::GetWhere(
database,
fmt::format(
"groupid = {}",
GetID()
)
);
if (rows.empty()) {
LogError(
"Error getting group members for group [{}]: [{}]",
(unsigned long) GetID(),
results.ErrorMessage().c_str()
"Error getting group members for group [{}]",
GetID()
);
return false;
}
int memberIndex = 0;
for(auto row = results.begin(); row != results.end(); ++row) {
if(!row[0])
for (const auto& member : rows) {
if (member.name.empty()) {
continue;
}
members[memberIndex] = nullptr;
strn0cpy(membername[memberIndex], row[0], 64);
strn0cpy(membername[memberIndex], member.name.c_str(), 64);
memberIndex++;
}
@ -1174,36 +1176,23 @@ void Group::VerifyGroup() {
Only called every once in a while (on member re-join for now).
*/
uint32 i;
for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (membername[i][0] == '\0') {
#if EQDEBUG >= 7
LogDebug("Group [{}]: Verify [{}]: Empty.\n", (unsigned long)GetID(), i);
#endif
members[i] = nullptr;
continue;
}
Mob *them = entity_list.GetMob(membername[i]);
if(them == nullptr && members[i] != nullptr) { //they aren't in zone
#if EQDEBUG >= 6
LogDebug("Member of group [{}] named [{}] has disappeared!!", (unsigned long)GetID(), membername[i]);
#endif
membername[i][0] = '\0';
members[i] = nullptr;
continue;
}
if(them != nullptr && members[i] != them) { //our pointer is out of date... not so good.
#if EQDEBUG >= 5
LogDebug("Member of group [{}] named [{}] had an out of date pointer!!", (unsigned long)GetID(), membername[i]);
#endif
members[i] = them;
continue;
}
#if EQDEBUG >= 8
LogDebug("Member of group [{}] named [{}] is valid", (unsigned long)GetID(), membername[i]);
#endif
}
}
@ -2497,4 +2486,4 @@ bool Group::IsLeader(const char* name) {
}
return false;
}
}

View File

@ -85,6 +85,7 @@ volatile bool RunLoops = true;
#endif
extern volatile bool is_zone_loaded;
extern bool Critical = false;
#include "zone_event_scheduler.h"
#include "../common/file.h"

View File

@ -3999,7 +3999,7 @@ void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str, Journal::O
const char *Mob::GetCleanName()
{
if (!strlen(clean_name)) {
if (!strlen(clean_name)) {
CleanMobName(GetName(), clean_name);
}

View File

@ -1833,6 +1833,8 @@ private:
EQ::InventoryProfile m_inv;
std::shared_ptr<HealRotation> m_target_of_heal_rotation;
bool m_manual_follow;
void DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana);
};
#endif

View File

@ -369,7 +369,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates
return false;
}
bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) {
bool NPC::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) {
LogAI("spellid [{}] tar [{}] mana [{}] Name [{}]", AIspells[i].spellid, tar->GetName(), mana_cost, spells[AIspells[i].spellid].name);
casting_spell_AIindex = i;

View File

@ -589,7 +589,7 @@ protected:
std::vector<AISpells_Struct> AIspells;
bool HasAISpell;
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates = false);
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
virtual bool AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
AISpellsVar_Struct AISpellVar;
int64 GetFocusEffect(focusType type, uint16 spell_id, Mob *caster = nullptr, bool from_buff_tic = false) override;
uint16 innate_proc_spell_id;

View File

@ -26,6 +26,7 @@
#include "mob.h"
#include "raids.h"
#include "string_ids.h"
#include "bot.h"
#include "worldserver.h"
@ -94,41 +95,57 @@ bool Raid::Process()
}
void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bool looter){
if(!c)
if (!c) {
return;
}
std::string query = StringFormat("INSERT INTO raid_members SET raidid = %lu, charid = %lu, "
"groupid = %lu, _class = %d, level = %d, name = '%s', "
"isgroupleader = %d, israidleader = %d, islooter = %d",
(unsigned long)GetID(), (unsigned long)c->CharacterID(),
(unsigned long)group, c->GetClass(), c->GetLevel(),
c->GetName(), groupleader, rleader, looter);
auto results = database.QueryDatabase(query);
const auto query = fmt::format(
"REPLACE INTO raid_members SET raidid = {}, charid = {}, bot_id = 0, "
"groupid = {}, _class = {}, level = {}, name = '{}', "
"isgroupleader = {}, israidleader = {}, islooter = {}",
GetID(),
c->CharacterID(),
group,
c->GetClass(),
c->GetLevel(),
c->GetName(),
groupleader,
rleader,
looter
);
auto results = database.QueryDatabase(query);
if(!results.Success()) {
LogError("Error inserting into raid members: [{}]", results.ErrorMessage().c_str());
}
LearnMembers();
VerifyRaid();
if (rleader) {
database.SetRaidGroupLeaderInfo(group, GetID());
UpdateRaidAAs();
} else if (rleader) {
database.SetRaidGroupLeaderInfo(RAID_GROUPLESS, GetID());
UpdateRaidAAs();
}
if (group != RAID_GROUPLESS && groupleader) {
database.SetRaidGroupLeaderInfo(group, GetID());
UpdateGroupAAs(group);
}
if(group < 12)
if (group < MAX_RAID_GROUPS) {
GroupUpdate(group);
else // get raid AAs, GroupUpdate will handles it otherwise
} else { // get raid AAs, GroupUpdate will handles it otherwise
SendGroupLeadershipAA(c, RAID_GROUPLESS);
}
SendRaidAddAll(c->GetName());
c->SetRaidGrouped(true);
SendRaidMOTD(c);
// xtarget shit ..........
if (group == RAID_GROUPLESS) {
if (rleader) {
GetXTargetAutoMgr()->merge(*c->GetXTargetAutoMgr());
@ -148,54 +165,115 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo
}
}
Raid *raid_update = nullptr;
raid_update = c->GetRaid();
auto* raid_update = c->GetRaid();
if (raid_update) {
raid_update->SendHPManaEndPacketsTo(c);
raid_update->SendHPManaEndPacketsFrom(c);
}
auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct));
ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
rga->rid = GetID();
strn0cpy(rga->playername, c->GetName(), 64);
rga->zoneid = zone->GetZoneID();
auto* rga = (ServerRaidGeneralAction_Struct*) pack->pBuffer;
strn0cpy(rga->playername, c->GetName(), sizeof(rga->playername));
rga->rid = GetID();
rga->zoneid = zone->GetZoneID();
rga->instance_id = zone->GetInstanceID();
worldserver.SendPacket(pack);
safe_delete(pack);
}
void Raid::AddBot(Bot* b, uint32 group, bool rleader, bool groupleader, bool looter) {
if (!b) {
return;
}
const auto query = fmt::format(
"REPLACE INTO raid_members SET raidid = {}, "
"charid = 0, bot_id = {}, groupid = {}, _class = {}, level = {}, name = '{}', "
"isgroupleader = {}, israidleader = {}, islooter = {}",
GetID(),
b->GetBotID(),
group,
b->GetClass(),
b->GetLevel(),
b->GetName(),
groupleader,
rleader,
looter
);
auto results = database.QueryDatabase(query);
LearnMembers();
VerifyRaid();
if (group < MAX_RAID_GROUPS) {
GroupUpdate(group);
} else { // get raid AAs, GroupUpdate will handle it otherwise
SendGroupLeadershipAA(b->GetOwner()->CastToClient(), RAID_GROUPLESS);
}
SendRaidAddAll(b->GetName());
b->SetRaidGrouped(true);
b->p_raid_instance = this;
auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct));
auto* rga = (ServerRaidGeneralAction_Struct*) pack->pBuffer;
strn0cpy(rga->playername, b->GetName(), sizeof(rga->playername));
rga->rid = GetID();
rga->zoneid = zone->GetZoneID();
rga->instance_id = zone->GetInstanceID();
worldserver.SendPacket(pack);
safe_delete(pack);
}
void Raid::RemoveMember(const char *characterName)
{
std::string query = StringFormat("DELETE FROM raid_members where name='%s'", characterName);
auto results = database.QueryDatabase(query);
Client *client = entity_list.GetClientByName(characterName);
auto* b = entity_list.GetBotByBotName(characterName);
auto* c = entity_list.GetClientByName(characterName);
if (RuleB(Bots, Enabled) && b) {
b = entity_list.GetBotByBotName(characterName);
b->SetFollowID(b->GetOwner()->CastToClient()->GetID());
b->SetTarget(nullptr);
b->SetRaidGrouped(false);
}
disbandCheck = true;
SendRaidRemoveAll(characterName);
SendRaidDisband(client);
SendRaidDisband(c);
LearnMembers();
VerifyRaid();
if(client) {
client->SetRaidGrouped(false);
client->LeaveRaidXTargets(this);
client->p_raid_instance = nullptr;
if (c) {
c->SetRaidGrouped(false);
c->LeaveRaidXTargets(this);
c->p_raid_instance = nullptr;
}
auto pack = new ServerPacket(ServerOP_RaidRemove, sizeof(ServerRaidGeneralAction_Struct));
ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
rga->rid = GetID();
auto* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
rga->rid = GetID();
rga->instance_id = zone->GetInstanceID();
strn0cpy(rga->playername, characterName, 64);
rga->zoneid = zone->GetZoneID();
rga->zoneid = zone->GetZoneID();
strn0cpy(rga->playername, characterName, sizeof(rga->playername));
worldserver.SendPacket(pack);
safe_delete(pack);
}
void Raid::DisbandRaid()
{
std::string query = StringFormat("DELETE FROM raid_members WHERE raidid = %lu", (unsigned long)GetID());
const auto query = fmt::format(
"DELETE FROM raid_members WHERE raidid = {}",
GetID()
);
auto results = database.QueryDatabase(query);
LearnMembers();
@ -203,11 +281,11 @@ void Raid::DisbandRaid()
SendRaidDisbandAll();
auto pack = new ServerPacket(ServerOP_RaidDisband, sizeof(ServerRaidGeneralAction_Struct));
ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
rga->rid = GetID();
strn0cpy(rga->playername, " ", 64);
rga->zoneid = zone->GetZoneID();
auto* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
rga->rid = GetID();
rga->zoneid = zone->GetZoneID();
rga->instance_id = zone->GetInstanceID();
strn0cpy(rga->playername, " ", sizeof(rga->playername));
worldserver.SendPacket(pack);
safe_delete(pack);
@ -216,19 +294,23 @@ void Raid::DisbandRaid()
void Raid::MoveMember(const char *name, uint32 newGroup)
{
std::string query = StringFormat("UPDATE raid_members SET groupid = %lu WHERE name = '%s'",
(unsigned long)newGroup, name);
auto results = database.QueryDatabase(query);
const auto query = fmt::format(
"UPDATE raid_members SET groupid = {} WHERE name = '{}'",
newGroup,
name
);
auto results = database.QueryDatabase(query);
LearnMembers();
VerifyRaid();
SendRaidMoveAll(name);
auto pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct));
ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
rga->rid = GetID();
strn0cpy(rga->playername, name, 64);
rga->zoneid = zone->GetZoneID();
auto* rga = (ServerRaidGeneralAction_Struct*) pack->pBuffer;
strn0cpy(rga->playername, name, sizeof(rga->playername));
rga->rid = GetID();
rga->zoneid = zone->GetZoneID();
rga->instance_id = zone->GetInstanceID();
worldserver.SendPacket(pack);
safe_delete(pack);
@ -236,18 +318,21 @@ void Raid::MoveMember(const char *name, uint32 newGroup)
void Raid::SetGroupLeader(const char *who, bool glFlag)
{
std::string query = StringFormat("UPDATE raid_members SET isgroupleader = %lu WHERE name = '%s'",
(unsigned long)glFlag, who);
auto results = database.QueryDatabase(query);
const auto query = fmt::format(
"UPDATE raid_members SET isgroupleader = {} WHERE name = '{}'",
glFlag,
who
);
auto results = database.QueryDatabase(query);
LearnMembers();
VerifyRaid();
auto pack = new ServerPacket(ServerOP_RaidGroupLeader, sizeof(ServerRaidGeneralAction_Struct));
ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
rga->rid = GetID();
strn0cpy(rga->playername, who, 64);
rga->zoneid = zone->GetZoneID();
auto* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer;
strn0cpy(rga->playername, who, sizeof(rga->playername));
rga->rid = GetID();
rga->zoneid = zone->GetZoneID();
rga->instance_id = zone->GetInstanceID();
worldserver.SendPacket(pack);
safe_delete(pack);
@ -406,13 +491,13 @@ uint32 Raid::GetFreeGroup()
return x;
}
//if we get to here then there were no free groups so we added the group as free floating members.
return 0xFFFFFFFF;
return RAID_GROUPLESS;
}
uint8 Raid::GroupCount(uint32 gid)
{
uint8 count = 0;
if(gid < 12)
if(gid < MAX_RAID_GROUPS)
{
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
{
@ -453,7 +538,7 @@ uint32 Raid::GetGroup(const char *name)
if(strcmp(members[x].membername, name) == 0)
return members[x].GroupNumber;
}
return 0xFFFFFFFF;
return RAID_GROUPLESS;
}
uint32 Raid::GetGroup(Client *c)
@ -463,7 +548,7 @@ uint32 Raid::GetGroup(Client *c)
if(members[x].member == c)
return members[x].GroupNumber;
}
return 0xFFFFFFFF;
return RAID_GROUPLESS;
}
void Raid::RaidSay(const char *msg, Client *c, uint8 language, uint8 lang_skill)
@ -474,7 +559,7 @@ void Raid::RaidSay(const char *msg, Client *c, uint8 language, uint8 lang_skill)
auto pack = new ServerPacket(ServerOP_RaidSay, sizeof(ServerRaidMessage_Struct) + strlen(msg) + 1);
ServerRaidMessage_Struct *rga = (ServerRaidMessage_Struct*)pack->pBuffer;
rga->rid = GetID();
rga->gid = 0xFFFFFFFF;
rga->gid = RAID_GROUPLESS;
rga->language = language;
rga->lang_skill = lang_skill;
strn0cpy(rga->from, c->GetName(), 64);
@ -992,19 +1077,21 @@ void Raid::SendRaidAdd(const char *who, Client *to)
if(!to)
return;
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
std::vector<RaidMember> rm = GetMembers();
for (const auto& m : rm)
{
if(strcmp(members[x].membername, who) == 0)
if (strcmp(m.membername, who) == 0)
{
auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidAddMember_Struct));
RaidAddMember_Struct *ram = (RaidAddMember_Struct*)outapp->pBuffer;
RaidAddMember_Struct* ram = (RaidAddMember_Struct*)outapp->pBuffer;
ram->raidGen.action = raidAdd;
ram->raidGen.parameter = members[x].GroupNumber;
strn0cpy(ram->raidGen.leader_name, members[x].membername, 64);
strn0cpy(ram->raidGen.player_name, members[x].membername, 64);
ram->_class = members[x]._class;
ram->level = members[x].level;
ram->isGroupLeader = members[x].IsGroupLeader;
ram->raidGen.parameter = m.GroupNumber;
strn0cpy(ram->raidGen.leader_name, m.membername, 64);
strn0cpy(ram->raidGen.player_name, m.membername, 64);
ram->_class = m._class;
ram->level = m.level;
ram->isGroupLeader = m.IsGroupLeader;
to->QueuePacket(outapp);
safe_delete(outapp);
return;
@ -1014,19 +1101,21 @@ void Raid::SendRaidAdd(const char *who, Client *to)
void Raid::SendRaidAddAll(const char *who)
{
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
{
if(strcmp(members[x].membername, who) == 0)
std::vector<RaidMember> rm = GetMembers();
for (const auto& m : rm) {
if (strcmp(m.membername, who) == 0)
{
auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidAddMember_Struct));
RaidAddMember_Struct *ram = (RaidAddMember_Struct*)outapp->pBuffer;
RaidAddMember_Struct* ram = (RaidAddMember_Struct*)outapp->pBuffer;
ram->raidGen.action = raidAdd;
ram->raidGen.parameter = members[x].GroupNumber;
strcpy(ram->raidGen.leader_name, members[x].membername);
strcpy(ram->raidGen.player_name, members[x].membername);
ram->_class = members[x]._class;
ram->level = members[x].level;
ram->isGroupLeader = members[x].IsGroupLeader;
ram->raidGen.parameter = m.GroupNumber;
strcpy(ram->raidGen.leader_name, m.membername);
strcpy(ram->raidGen.player_name, m.membername);
ram->isGroupLeader = m.IsGroupLeader;
ram->_class = m._class;
ram->level = m.level;
QueuePacket(outapp);
safe_delete(outapp);
return;
@ -1129,12 +1218,13 @@ void Raid::SendRaidMoveAll(const char* who)
SendRaidRemoveAll(who);
if(c)
SendRaidCreate(c);
SendMakeLeaderPacket(leadername);
SendRaidAddAll(who);
if(c){
SendBulkRaid(c);
if(IsLocked()) { SendRaidLockTo(c); }
}
SendRaidAddAll(who);
SendMakeLeaderPacket(leadername);
}
void Raid::SendBulkRaid(Client *to)
@ -1144,7 +1234,7 @@ void Raid::SendBulkRaid(Client *to)
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
{
if(strlen(members[x].membername) > 0 && (strcmp(members[x].membername, to->GetName()) != 0)) //don't send ourself
if (strlen(members[x].membername) > 0 && (strcmp(members[x].membername, to->GetName()) != 0)) //don't send ourself
{
SendRaidAdd(members[x].membername, to);
}
@ -1155,7 +1245,7 @@ void Raid::QueuePacket(const EQApplicationPacket *app, bool ack_req)
{
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
{
if(members[x].member)
if(members[x].member && !members[x].IsBot)
{
members[x].member->QueuePacket(app, ack_req);
}
@ -1164,6 +1254,11 @@ void Raid::QueuePacket(const EQApplicationPacket *app, bool ack_req)
void Raid::SendMakeLeaderPacket(const char *who) //30
{
if (RuleB(Bots, Enabled) && entity_list.GetBotByBotName(who) && members[GetPlayerIndex(who)].IsBot) {
return;
}
auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct));
RaidLeadershipUpdate_Struct *rg = (RaidLeadershipUpdate_Struct*)outapp->pBuffer;
rg->action = raidMakeLeader;
@ -1176,8 +1271,13 @@ void Raid::SendMakeLeaderPacket(const char *who) //30
void Raid::SendMakeLeaderPacketTo(const char *who, Client *to)
{
if(!to)
if (!to) {
return;
}
if (RuleB(Bots, Enabled) && members[GetPlayerIndex(who)].IsBot) {
return;
}
auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct));
RaidLeadershipUpdate_Struct *rg = (RaidLeadershipUpdate_Struct*)outapp->pBuffer;
@ -1206,8 +1306,13 @@ void Raid::SendMakeGroupLeaderPacketTo(const char *who, Client *to)
void Raid::SendGroupUpdate(Client *to)
{
if(!to)
if (!to) {
return;
}
if (RuleB(Bots, Enabled) && members[GetPlayerIndex(to)].IsBot) {
return;
}
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate2_Struct));
GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer;
@ -1249,13 +1354,14 @@ void Raid::SendGroupUpdate(Client *to)
void Raid::GroupUpdate(uint32 gid, bool initial)
{
if(gid > 11) //ungrouped member doesn't need grouping.
if(gid > 11) {//ungrouped member doesn't need grouping.
return;
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
}
for (int x = 0; x < MAX_RAID_MEMBERS; x++)
{
if(strlen(members[x].membername) > 0){
if(members[x].GroupNumber == gid){
if(members[x].member) {
if (members[x].member) {
SendGroupUpdate(members[x].member);
SendGroupLeadershipAA(members[x].member, gid);
}
@ -1362,6 +1468,10 @@ void Raid::SendRaidMOTD(Client *c)
if (!c || motd.empty())
return;
if (members[GetPlayerIndex(c)].IsBot) {
return;
}
size_t size = motd.size() + 1;
auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidMOTD_Struct) + size);
RaidMOTD_Struct *rmotd = (RaidMOTD_Struct *)outapp->pBuffer;
@ -1397,6 +1507,10 @@ void Raid::SendRaidMOTDToWorld()
void Raid::SendGroupLeadershipAA(Client *c, uint32 gid)
{
if (RuleB(Bots, Enabled) && members[GetPlayerIndex(c)].IsBot) {
return;
}
auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct));
RaidLeadershipUpdate_Struct *rlaa = (RaidLeadershipUpdate_Struct *)outapp->pBuffer;
rlaa->action = raidSetLeaderAbilities;
@ -1411,18 +1525,23 @@ void Raid::SendGroupLeadershipAA(Client *c, uint32 gid)
void Raid::SendGroupLeadershipAA(uint32 gid)
{
for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++)
if (members[i].member && members[i].GroupNumber == gid)
SendGroupLeadershipAA(members[i].member, gid);
for (const auto& m : members) {
if (m.member && m.GroupNumber == gid && !m.IsBot) {
SendGroupLeadershipAA(m.member, gid);
}
}
}
void Raid::SendAllRaidLeadershipAA()
{
for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++)
if (members[i].member)
SendGroupLeadershipAA(members[i].member, members[i].GroupNumber);
for (const auto& m : members) {
if (m.member && !m.IsBot) {
SendGroupLeadershipAA(m.member, m.GroupNumber);
}
}
}
void Raid::LockRaid(bool lockFlag)
{
std::string query = StringFormat("UPDATE raid_details SET locked = %d WHERE raidid = %lu",
@ -1486,70 +1605,84 @@ void Raid::SaveRaidMOTD()
bool Raid::LearnMembers()
{
memset(members, 0, (sizeof(RaidMember)*MAX_RAID_MEMBERS));
memset(members, 0, (sizeof(RaidMember) * MAX_RAID_MEMBERS));
std::string query = StringFormat("SELECT name, groupid, _class, level, "
"isgroupleader, israidleader, islooter "
"FROM raid_members WHERE raidid = %lu",
(unsigned long)GetID());
auto results = database.QueryDatabase(query);
if (!results.Success())
return false;
const auto query = fmt::format(
"SELECT name, groupid, _class, level, "
"isgroupleader, israidleader, islooter, bot_id "
"FROM raid_members WHERE raidid = {} ORDER BY groupid",
GetID()
);
if(results.RowCount() == 0) {
LogError("Error getting raid members for raid [{}]: [{}]", (unsigned long)GetID(), results.ErrorMessage().c_str());
disbandCheck = true;
return false;
}
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return false;
}
int index = 0;
for(auto row = results.begin(); row != results.end(); ++row) {
if(!row[0])
continue;
if (!results.RowCount()) {
LogError("Error getting raid members for raid [{}]: [{}]", GetID(), results.ErrorMessage());
disbandCheck = true;
return false;
}
members[index].member = nullptr;
strn0cpy(members[index].membername, row[0], 64);
uint32 groupNum = Strings::ToUnsignedInt(row[1]);
if(groupNum > 11)
members[index].GroupNumber = 0xFFFFFFFF;
else
members[index].GroupNumber = groupNum;
int index = 0;
for (auto row: results) {
if (!row[0]) {
continue;
}
members[index]._class = Strings::ToInt(row[2]);
members[index].level = Strings::ToInt(row[3]);
members[index].IsGroupLeader = Strings::ToInt(row[4]);
members[index].IsRaidLeader = Strings::ToInt(row[5]);
members[index].IsLooter = Strings::ToInt(row[6]);
++index;
}
members[index].member = nullptr;
strn0cpy(members[index].membername, row[0], sizeof(members[index].membername));
uint32 group_id = Strings::ToUnsignedInt(row[1]);
if (group_id > 11) {
members[index].GroupNumber = RAID_GROUPLESS;
} else {
members[index].GroupNumber = group_id;
}
members[index]._class = Strings::ToUnsignedInt(row[2]);
members[index].level = Strings::ToUnsignedInt(row[3]);
members[index].IsGroupLeader = Strings::ToBool(row[4]);
members[index].IsRaidLeader = Strings::ToBool(row[5]);
members[index].IsLooter = Strings::ToBool(row[6]);
members[index].IsBot = Strings::ToBool(row[7]) > 0;
++index;
}
return true;
}
void Raid::VerifyRaid()
{
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
{
if(strlen(members[x].membername) == 0){
members[x].member = nullptr;
}
else{
Client *c = entity_list.GetClientByName(members[x].membername);
if(c){
members[x].member = c;
}
else{
members[x].member = nullptr;
for (auto& m : members) {
if(strlen(m.membername) == 0){
m.member = nullptr;
} else {
auto* c = entity_list.GetClientByName(m.membername);
auto* b = entity_list.GetBotByBotName(m.membername);
if (c) {
m.member = c;
m.IsBot = false;
} else if(RuleB(Bots, Enabled) && b){
//Raid requires client* we are forcing it here to be a BOT. Care is needed here as any client function that
//does not exist within the Bot Class will likely corrupt memory for the member object. Good practice to test the IsBot
//attribute before calling a client function or casting to client.
b = entity_list.GetBotByBotName(m.membername);
m.member = b->CastToClient();
m.IsBot = true; //Used to identify those members who are Bots
} else {
m.member = nullptr;
m.IsBot = false;
}
}
if(members[x].IsRaidLeader){
if(strlen(members[x].membername) > 0){
SetLeader(members[x].member);
strn0cpy(leadername, members[x].membername, 64);
}
else
{
//should never happen, but maybe it is?
if (m.IsRaidLeader) {
if (strlen(m.membername) > 0){
SetLeader(m.member);
strn0cpy(leadername, m.membername, sizeof(leadername));
} else {
SetLeader(nullptr);
}
}
@ -1577,7 +1710,7 @@ void Raid::MemberZoned(Client *c)
}
}
if (gid < 12 && group_mentor[gid].mentoree == c)
if (gid < MAX_RAID_GROUPS && group_mentor[gid].mentoree == c)
group_mentor[gid].mentoree = nullptr;
}
@ -1592,7 +1725,7 @@ void Raid::SendHPManaEndPacketsTo(Client *client)
EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
for(int x = 0; x < MAX_RAID_MEMBERS; x++) {
if(members[x].member) {
if(members[x].member && !members[x].IsBot) {
if((members[x].member != client) && (members[x].GroupNumber == group_id)) {
members[x].member->CreateHPPacket(&hp_packet);
@ -1634,7 +1767,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob)
mob->CreateHPPacket(&hpapp);
for(int x = 0; x < MAX_RAID_MEMBERS; x++) {
if(members[x].member) {
if(members[x].member && !members[x].IsBot) {
if(!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) {
members[x].member->QueuePacket(&hpapp, false);
if (members[x].member->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
@ -1667,7 +1800,7 @@ void Raid::SendManaPacketFrom(Mob *mob)
EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
for (int x = 0; x < MAX_RAID_MEMBERS; x++) {
if (members[x].member) {
if (members[x].member && !members[x].IsBot) {
if (!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) {
if (members[x].member->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
outapp.SetOpcode(OP_MobManaUpdate);
@ -1694,7 +1827,7 @@ void Raid::SendEndurancePacketFrom(Mob *mob)
EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
for (int x = 0; x < MAX_RAID_MEMBERS; x++) {
if (members[x].member) {
if (members[x].member && !members[x].IsBot) {
if (!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) {
if (members[x].member->ClientVersion() >= EQ::versions::ClientVersion::SoD) {
outapp.SetOpcode(OP_MobEnduranceUpdate);
@ -1801,9 +1934,14 @@ void Raid::CheckGroupMentor(uint32 group_id, Client *c)
void Raid::SetDirtyAutoHaters()
{
for (int i = 0; i < MAX_RAID_MEMBERS; ++i)
if (members[i].member)
if (members[i].member && members[i].IsBot)
{
members[i].member->CastToBot()->SetDirtyAutoHaters();
}
else if (members[i].member && !members[i].IsBot)
{
members[i].member->SetDirtyAutoHaters();
}
}
void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/, bool group_only /*= true*/) {
@ -1812,22 +1950,25 @@ void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_re
uint32 group_id = GetGroup(sender->CastToClient());
/* If this is a group only packet and we're not in a group -- return */
if (group_id == 0xFFFFFFFF && group_only)
if (group_id == RAID_GROUPLESS && group_only)
return;
for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++) {
if (!members[i].member)
if (!members[i].member) {
continue;
if (!members[i].member->IsClient())
}
if (!members[i].member->IsClient()) {
continue;
if (ignore_sender && members[i].member == sender)
}
if (members[i].IsBot) {
continue;
if (group_only && members[i].GroupNumber != group_id)
}
if (ignore_sender && members[i].member == sender) {
continue;
}
if (group_only && members[i].GroupNumber != group_id) {
continue;
}
/* If we don't have a distance requirement - send to all members */
if (distance == 0) {
members[i].member->CastToClient()->QueuePacket(app, ack_required);
@ -1884,3 +2025,84 @@ bool Raid::DoesAnyMemberHaveExpeditionLockout(
return Expedition::HasLockoutByCharacterName(raid_member.membername, expedition_name, event_name);
});
}
Mob* Raid::GetRaidMainAssistOneByName(const char* name)
{
Raid* raid = entity_list.GetRaidByBotName(name);
std::vector<RaidMember> raid_members = raid->GetMembers();
for (RaidMember iter : raid_members)
{
if (iter.IsRaidMainAssistOne) {
return iter.member->CastToMob();
}
}
return nullptr;
}
bool Raid::IsEngaged() {
std::vector<RaidMember> rm = GetMembers();
for (const auto& m : rm) {
if (m.member && !m.IsBot && (m.member->IsEngaged() || m.member->GetAggroCount() > 0)) {
return 1;
}
}
return 0;
}
void Raid::RaidGroupSay(const char* msg, const char* from, uint8 language, uint8 lang_skill)
{
if (!from)
return;
uint32 groupToUse = GetGroup(from);
if (groupToUse > 11)
return;
auto pack = new ServerPacket(ServerOP_RaidGroupSay, sizeof(ServerRaidMessage_Struct) + strlen(msg) + 1);
ServerRaidMessage_Struct* rga = (ServerRaidMessage_Struct*)pack->pBuffer;
rga->rid = GetID();
rga->gid = groupToUse;
rga->language = language;
rga->lang_skill = lang_skill;
strn0cpy(rga->from, from, 64);
strcpy(rga->message, msg); // this is safe because we are allocating enough space for the entire msg above
worldserver.SendPacket(pack);
safe_delete(pack);
}
void Raid::RaidSay(const char* msg, const char* from, uint8 language, uint8 lang_skill)
{
if (!from)
return;
auto pack = new ServerPacket(ServerOP_RaidSay, sizeof(ServerRaidMessage_Struct) + strlen(msg) + 1);
ServerRaidMessage_Struct* rga = (ServerRaidMessage_Struct*)pack->pBuffer;
rga->rid = GetID();
rga->gid = RAID_GROUPLESS;
rga->language = language;
rga->lang_skill = lang_skill;
strn0cpy(rga->from, from, 64);
strcpy(rga->message, msg);
worldserver.SendPacket(pack);
safe_delete(pack);
}
void Raid::SetNewRaidLeader(uint32 i)
{
if (members[i].IsRaidLeader) {
for (int x = 0; x < MAX_RAID_MEMBERS; x++) {
if (!members[x].IsBot) {
if (strlen(members[x].membername) > 0 && strcmp(members[x].membername, members[i].membername) != 0) {
SetRaidLeader(members[i].membername, members[x].membername);
UpdateRaidAAs();
SendAllRaidLeadershipAA();
break;
}
}
}
}
}

View File

@ -88,6 +88,8 @@ struct RaidMember{
bool IsGroupLeader;
bool IsRaidLeader;
bool IsLooter;
bool IsBot = false;
bool IsRaidMainAssistOne = false;
};
struct GroupMentor {
@ -113,6 +115,11 @@ public:
bool IsRaid() { return true; }
void AddMember(Client *c, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false);
void AddBot(Bot* b, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false);
void RaidGroupSay(const char* msg, const char* from, uint8 language, uint8 lang_skill);
void RaidSay(const char* msg, const char* from, uint8 language, uint8 lang_skill);
bool IsEngaged();
Mob* GetRaidMainAssistOneByName(const char* name);
void RemoveMember(const char *c);
void DisbandRaid();
void MoveMember(const char *name, uint32 newGroup);
@ -124,6 +131,7 @@ public:
bool IsRaidMember(const char* name);
bool IsRaidMember(Client *c);
void UpdateLevel(const char *name, int newLevel);
void SetNewRaidLeader(uint32 i);
uint32 GetFreeGroup();
uint8 GroupCount(uint32 gid);
@ -244,6 +252,10 @@ public:
bool DoesAnyMemberHaveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, int max_check_count = 0);
std::vector<RaidMember> GetMembers() const;
std::vector<RaidMember> GetRaidGroupMembers(uint32 gid);
std::vector<Bot*> GetRaidGroupBotMembers(uint32 gid);
std::vector<Bot*> GetRaidBotMembers(uint32 owner = 0);
void HandleBotGroupDisband(uint32 owner, uint32 gid = RAID_GROUPLESS);
RaidMember members[MAX_RAID_MEMBERS];
char leadername[64];

View File

@ -430,26 +430,16 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
// If you're at full mana, let it cast even if you dont have enough mana
// we calculated this above, now enforce it
if (mana_cost > 0 && slot != CastingSlot::Item) {
if (mana_cost > 0 && slot != CastingSlot::Item || (IsBot() && !CastToBot()->IsBotNonSpellFighter())) {
int my_curmana = GetMana();
int my_maxmana = GetMaxMana();
if (my_curmana < mana_cost) {// not enough mana
//this is a special case for NPCs with no mana...
if (IsNPC() && my_curmana == my_maxmana){
if (IsNPC() && my_curmana == my_maxmana) {
mana_cost = 0;
} else {
//The client will prevent spell casting if insufficient mana, this is only for serverside enforcement.
LogSpells("Spell Error not enough mana spell=[{}] mymana=[{}] cost=[{}]\n", spell_id, my_curmana, mana_cost);
if (IsClient()) {
//clients produce messages... npcs should not for this case
MessageString(Chat::Red, INSUFFICIENT_MANA);
InterruptSpell();
} else {
InterruptSpell(0, 0, 0); //the 0 args should cause no messages
}
ZeroCastingVars();
return(false);
}
DoSpellInterrupt(spell_id, mana_cost, my_curmana);
return false;
}
}
@ -475,7 +465,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
// cast time is 0, just finish it right now and be done with it
if(cast_time == 0) {
CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); //
return(true);
return true;
}
// ok we know it has a cast time so we can start the timer now
@ -501,7 +491,20 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
}
}
return(true);
return true;
}
void Mob::DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana) {
//The client will prevent spell casting if insufficient mana, this is only for serverside enforcement.
LogSpells("Spell Error not enough mana spell=[{}] mymana=[{}] cost=[{}]\n", spell_id, my_curmana, mana_cost);
if (IsClient()) {
//clients produce messages... npcs should not for this case
MessageString(Chat::Red, INSUFFICIENT_MANA);
InterruptSpell();
} else {
InterruptSpell(0, 0, 0); //the 0 args should cause no messages
}
ZeroCastingVars();
}
void Mob::SendBeginCast(uint16 spell_id, uint32 casttime)
@ -4153,7 +4156,6 @@ bool Mob::SpellOnTarget(
return false;
}
}
if (spelltar->IsClient()){
spelltar->CastToClient()->BreakSneakWhenCastOn(this, false);
spelltar->CastToClient()->BreakFeignDeathWhenCastOn(false);

View File

@ -362,6 +362,7 @@
#define PETITION_NO_DELETE 5053 //You do not have a petition in the queue.
#define PETITION_DELETED 5054 //Your petition was successfully deleted.
#define ALREADY_IN_RAID 5060 //%1 is already in a raid.
#define ALREADY_IN_YOUR_RAID 5077 //%1 is already in your raid.
#define GAIN_RAIDEXP 5085 //You gained raid experience!
#define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there.
#define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure.

View File

@ -1500,7 +1500,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
if (strcmp(rmsg->from, r->members[x].member->GetName()) != 0)
{
if (r->members[x].GroupNumber == rmsg->gid) {
if (r->members[x].member->GetFilter(FilterGroupChat) != 0)
if (!r->members[x].IsBot && r->members[x].member->GetFilter(FilterGroupChat) != 0)
{
r->members[x].member->ChannelMessageSend(rmsg->from, r->members[x].member->GetName(), ChatChannel_Group, rmsg->language, rmsg->lang_skill, rmsg->message);
}
@ -1524,7 +1524,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
if (r->members[x].member) {
if (strcmp(rmsg->from, r->members[x].member->GetName()) != 0)
{
if (r->members[x].member->GetFilter(FilterGroupChat) != 0)
if (!r->members[x].IsBot && r->members[x].member->GetFilter(FilterGroupChat) != 0)
{
r->members[x].member->ChannelMessageSend(rmsg->from, r->members[x].member->GetName(), ChatChannel_Raid, rmsg->language, rmsg->lang_skill, rmsg->message);
}