mirror of
https://github.com/EQEmu/Server.git
synced 2026-08-29 00:18:18 +00:00
Merge remote-tracking branch 'origin' into eqstream
This commit is contained in:
@@ -42,6 +42,10 @@ void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, u
|
||||
|
||||
//Dook- swarms and wards
|
||||
|
||||
// do nothing if it's a corpse
|
||||
if (targ != nullptr && targ->IsCorpse())
|
||||
return;
|
||||
|
||||
PetRecord record;
|
||||
if(!database.GetPetEntry(spells[spell_id].teleport_zone, &record))
|
||||
{
|
||||
|
||||
+5
-2
@@ -1302,9 +1302,12 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b
|
||||
my_hit.min_damage = 0;
|
||||
uint8 mylevel = GetLevel() ? GetLevel() : 1;
|
||||
uint32 hate = 0;
|
||||
if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt;
|
||||
if (weapon)
|
||||
hate = (weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt);
|
||||
|
||||
my_hit.base_damage = GetWeaponDamage(other, weapon, &hate);
|
||||
if (hate == 0 && my_hit.base_damage > 1) hate = my_hit.base_damage;
|
||||
if (hate == 0 && my_hit.base_damage > 1)
|
||||
hate = my_hit.base_damage;
|
||||
|
||||
//if weapon damage > 0 then we know we can hit the target with this weapon
|
||||
//otherwise we cannot and we set the damage to -5 later on
|
||||
|
||||
+492
-311
@@ -1724,7 +1724,7 @@ bool Bot::LoadPet()
|
||||
|
||||
bool Bot::SavePet()
|
||||
{
|
||||
if (!GetPet() || GetPet()->IsFamiliar() /*|| dead*/)
|
||||
if (!GetPet() || GetPet()->IsFamiliar()) // dead?
|
||||
return true;
|
||||
|
||||
NPC *pet_inst = GetPet()->CastToNPC();
|
||||
@@ -2156,7 +2156,7 @@ void Bot::AI_Process() {
|
||||
if(GetHasBeenSummoned()) {
|
||||
if(IsBotCaster() || IsBotArcher()) {
|
||||
if (AI_movement_timer->Check()) {
|
||||
if(!GetTarget() || (IsBotCaster() && !IsBotCasterCombatRange(GetTarget())) || (IsBotArcher() && IsArcheryRange(GetTarget())) || (DistanceSquaredNoZ(static_cast<glm::vec3>(m_Position), m_PreSummonLocation) < 10)) {
|
||||
if(!GetTarget() || (IsBotCaster() && !IsBotCasterAtCombatRange(GetTarget())) || (IsBotArcher() && IsArcheryRange(GetTarget())) || (DistanceSquaredNoZ(static_cast<glm::vec3>(m_Position), m_PreSummonLocation) < 10)) {
|
||||
if(GetTarget())
|
||||
FaceTarget(GetTarget());
|
||||
|
||||
@@ -2293,23 +2293,24 @@ void Bot::AI_Process() {
|
||||
ChangeBotArcherWeapons(IsBotArcher());
|
||||
}
|
||||
|
||||
if(IsBotArcher() && atArcheryRange) {
|
||||
if(IsMoving()) {
|
||||
if (IsBotArcher() && atArcheryRange) {
|
||||
if (IsMoving()) {
|
||||
SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()));
|
||||
SetRunAnimSpeed(0);
|
||||
SetCurrentSpeed(0);
|
||||
if(moved) {
|
||||
if (moved) {
|
||||
moved = false;
|
||||
SetCurrentSpeed(0);
|
||||
}
|
||||
}
|
||||
atCombatRange = true;
|
||||
} else if(IsBotCaster() && GetLevel() >= RuleI(Bots, CasterStopMeleeLevel)) {
|
||||
if(IsBotCasterCombatRange(GetTarget()))
|
||||
atCombatRange = true;
|
||||
}
|
||||
else if(DistanceSquared(m_Position, GetTarget()->GetPosition()) <= meleeDistance)
|
||||
else if (GetLevel() >= RuleI(Bots, CasterStopMeleeLevel) && IsBotCasterAtCombatRange(GetTarget())) {
|
||||
atCombatRange = true;
|
||||
}
|
||||
else if (DistanceSquared(m_Position, GetTarget()->GetPosition()) <= meleeDistance) {
|
||||
atCombatRange = true;
|
||||
}
|
||||
|
||||
if(atCombatRange) {
|
||||
if(IsMoving()) {
|
||||
@@ -3224,21 +3225,20 @@ void Bot::BotAddEquipItem(int slot, uint32 id) {
|
||||
}
|
||||
|
||||
// Erases the specified item from bot the NPC equipment array and from the bot inventory collection.
|
||||
void Bot::BotRemoveEquipItem(int slot) {
|
||||
if(slot > 0) {
|
||||
uint8 materialFromSlot = EQEmu::InventoryProfile::CalcMaterialFromSlot(slot);
|
||||
void Bot::BotRemoveEquipItem(int16 slot)
|
||||
{
|
||||
uint8 material_slot = EQEmu::InventoryProfile::CalcMaterialFromSlot(slot);
|
||||
|
||||
if (materialFromSlot != EQEmu::textures::materialInvalid) {
|
||||
equipment[slot] = 0; // npc has more than just material slots. Valid material should mean valid inventory index
|
||||
SendWearChange(materialFromSlot);
|
||||
if (materialFromSlot == EQEmu::textures::armorChest)
|
||||
SendWearChange(EQEmu::textures::armorArms);
|
||||
}
|
||||
|
||||
UpdateEquipmentLight();
|
||||
if (UpdateActiveLight())
|
||||
SendAppearancePacket(AT_Light, GetActiveLightType());
|
||||
if (material_slot != EQEmu::textures::materialInvalid) {
|
||||
equipment[slot] = 0; // npc has more than just material slots. Valid material should mean valid inventory index
|
||||
SendWearChange(material_slot);
|
||||
if (material_slot == EQEmu::textures::armorChest)
|
||||
SendWearChange(EQEmu::textures::armorArms);
|
||||
}
|
||||
|
||||
UpdateEquipmentLight();
|
||||
if (UpdateActiveLight())
|
||||
SendAppearancePacket(AT_Light, GetActiveLightType());
|
||||
}
|
||||
|
||||
void Bot::BotTradeSwapItem(Client* client, int16 lootSlot, const EQEmu::ItemInstance* inst, const EQEmu::ItemInstance* inst_swap, uint32 equipableSlots, std::string* errorMessage, bool swap) {
|
||||
@@ -3325,243 +3325,411 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) {
|
||||
}
|
||||
|
||||
// Completes a trade with a client bot owner
|
||||
void Bot::FinishTrade(Client* client, BotTradeType tradeType) {
|
||||
if(client && !client->GetTradeskillObject() && (client->trade->state != Trading)) {
|
||||
if(tradeType == BotTradeClientNormal) {
|
||||
// Items being traded are found in the normal trade window used to trade between a Client and a Client or NPC
|
||||
// Items in this mode are found in slot ids 3000 thru 3003 - thought bots used the full 8-slot window..?
|
||||
PerformTradeWithClient(EQEmu::legacy::TRADE_BEGIN, EQEmu::legacy::TRADE_END, client); // {3000..3007}
|
||||
}
|
||||
else if(tradeType == BotTradeClientNoDropNoTrade) {
|
||||
// Items being traded are found on the Client's cursor slot, slot id 30. This item can be either a single item or it can be a bag.
|
||||
// If it is a bag, then we have to search for items in slots 331 thru 340
|
||||
PerformTradeWithClient(EQEmu::inventory::slotCursor, EQEmu::inventory::slotCursor, client);
|
||||
void Bot::FinishTrade(Client* client, BotTradeType tradeType)
|
||||
{
|
||||
if (!client || (GetOwner() != client) || client->GetTradeskillObject() || client->trade->state == Trading) {
|
||||
if (client)
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Add logic here to test if the item in SLOT_CURSOR is a container type, if it is then we need to call the following:
|
||||
// PerformTradeWithClient(331, 340, client);
|
||||
}
|
||||
// these notes are not correct or obselete
|
||||
if (tradeType == BotTradeClientNormal) {
|
||||
// Items being traded are found in the normal trade window used to trade between a Client and a Client or NPC
|
||||
// Items in this mode are found in slot ids 3000 thru 3003 - thought bots used the full 8-slot window..?
|
||||
PerformTradeWithClient(EQEmu::legacy::TRADE_BEGIN, EQEmu::legacy::TRADE_END, client); // {3000..3007}
|
||||
}
|
||||
else if (tradeType == BotTradeClientNoDropNoTrade) {
|
||||
// Items being traded are found on the Client's cursor slot, slot id 30. This item can be either a single item or it can be a bag.
|
||||
// If it is a bag, then we have to search for items in slots 331 thru 340
|
||||
PerformTradeWithClient(EQEmu::inventory::slotCursor, EQEmu::inventory::slotCursor, client);
|
||||
|
||||
// TODO: Add logic here to test if the item in SLOT_CURSOR is a container type, if it is then we need to call the following:
|
||||
// PerformTradeWithClient(331, 340, client);
|
||||
}
|
||||
}
|
||||
|
||||
// Perfoms the actual trade action with a client bot owner
|
||||
void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client) {
|
||||
if(client) {
|
||||
// TODO: Figure out what the actual max slot id is
|
||||
const int MAX_SLOT_ID = EQEmu::legacy::TRADE_BAGS_END; // was the old incorrect 3179..
|
||||
uint32 items[MAX_SLOT_ID] = {0};
|
||||
uint8 charges[MAX_SLOT_ID] = {0};
|
||||
bool botCanWear[MAX_SLOT_ID] = {0};
|
||||
void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client)
|
||||
{
|
||||
using namespace EQEmu;
|
||||
|
||||
for(int16 i = beginSlotID; i <= endSlotID; ++i) {
|
||||
bool BotCanWear = false;
|
||||
bool UpdateClient = false;
|
||||
bool already_returned = false;
|
||||
struct ClientTrade {
|
||||
const ItemInstance* tradeItemInstance;
|
||||
int16 fromClientSlot;
|
||||
int16 toBotSlot;
|
||||
int adjustStackSize;
|
||||
std::string acceptedItemName;
|
||||
|
||||
ClientTrade(const ItemInstance* item, int16 from, const char* name = "") : tradeItemInstance(item), fromClientSlot(from), toBotSlot(legacy::SLOT_INVALID), adjustStackSize(0), acceptedItemName(name) { }
|
||||
};
|
||||
|
||||
EQEmu::InventoryProfile& clientInventory = client->GetInv();
|
||||
const EQEmu::ItemInstance* inst = clientInventory[i];
|
||||
if(inst) {
|
||||
items[i] = inst->GetItem()->ID;
|
||||
charges[i] = inst->GetCharges();
|
||||
}
|
||||
struct ClientReturn {
|
||||
const ItemInstance* returnItemInstance;
|
||||
int16 fromBotSlot;
|
||||
int16 toClientSlot;
|
||||
int adjustStackSize;
|
||||
std::string failedItemName;
|
||||
|
||||
ClientReturn(const ItemInstance* item, int16 from, const char* name = "") : returnItemInstance(item), fromBotSlot(from), toClientSlot(legacy::SLOT_INVALID), adjustStackSize(0), failedItemName(name) { }
|
||||
};
|
||||
|
||||
if (i == EQEmu::inventory::slotCursor)
|
||||
UpdateClient = true;
|
||||
static const int16 proxyPowerSource = 22;
|
||||
|
||||
//EQoffline: will give the items to the bots and change the bot stats
|
||||
if(inst && (GetBotOwner() == client->CastToMob()) && !IsEngaged()) {
|
||||
std::string TempErrorMessage;
|
||||
const EQEmu::ItemData* mWeaponItem = inst->GetItem();
|
||||
bool failedLoreCheck = false;
|
||||
for (int m = EQEmu::inventory::socketBegin; m < EQEmu::inventory::SocketCount; ++m) {
|
||||
EQEmu::ItemInstance *itm = inst->GetAugment(m);
|
||||
if(itm)
|
||||
{
|
||||
if(CheckLoreConflict(itm->GetItem())) {
|
||||
failedLoreCheck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(CheckLoreConflict(mWeaponItem)) {
|
||||
failedLoreCheck = true;
|
||||
}
|
||||
if(failedLoreCheck) {
|
||||
Message_StringID(0, DUP_LORE);
|
||||
}
|
||||
if(!failedLoreCheck && mWeaponItem && inst->IsEquipable(GetBaseRace(), GetClass()) && (GetLevel() >= mWeaponItem->ReqLevel)) {
|
||||
BotCanWear = true;
|
||||
botCanWear[i] = BotCanWear;
|
||||
EQEmu::ItemInstance* swap_item = nullptr;
|
||||
static const int16 bot_equip_order[(legacy::EQUIPMENT_SIZE + 1)] = {
|
||||
inventory::slotCharm, inventory::slotEar1, inventory::slotHead, inventory::slotFace,
|
||||
inventory::slotEar2, inventory::slotNeck, inventory::slotShoulders, inventory::slotArms,
|
||||
inventory::slotBack, inventory::slotWrist1, inventory::slotWrist2, inventory::slotRange,
|
||||
inventory::slotHands, inventory::slotPrimary, inventory::slotSecondary, inventory::slotFinger1,
|
||||
inventory::slotFinger2, inventory::slotChest, inventory::slotLegs, inventory::slotFeet,
|
||||
inventory::slotWaist, inventory::slotAmmo, proxyPowerSource // inventory::slotPowerSource
|
||||
};
|
||||
|
||||
const char* equipped[EQEmu::legacy::EQUIPMENT_SIZE + 1] = { "Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back",
|
||||
"Left Wrist", "Right Wrist", "Range", "Hands", "Primary Hand", "Secondary Hand",
|
||||
"Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo", "Powersource" };
|
||||
bool success = false;
|
||||
int how_many_slots = 0;
|
||||
for (int j = EQEmu::legacy::EQUIPMENT_BEGIN; j <= (EQEmu::legacy::EQUIPMENT_END + 1); ++j) {
|
||||
if((mWeaponItem->Slots & (1 << j))) {
|
||||
if (j == 22)
|
||||
j = 9999;
|
||||
enum { stageStackable = 0, stageEmpty, stageReplaceable };
|
||||
|
||||
if (!client) {
|
||||
Emote("NO CLIENT");
|
||||
return;
|
||||
}
|
||||
|
||||
how_many_slots++;
|
||||
if(!GetBotItem(j)) {
|
||||
if (j == EQEmu::inventory::slotPrimary) {
|
||||
if (mWeaponItem->IsType2HWeapon()) {
|
||||
if (GetBotItem(EQEmu::inventory::slotSecondary)) {
|
||||
if (mWeaponItem && mWeaponItem->IsType2HWeapon()) {
|
||||
if (client->CheckLoreConflict(GetBotItem(EQEmu::inventory::slotSecondary)->GetItem())) {
|
||||
failedLoreCheck = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotSecondary);
|
||||
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!failedLoreCheck) {
|
||||
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
|
||||
success = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (j == EQEmu::inventory::slotSecondary) {
|
||||
if(inst->IsWeapon()) {
|
||||
if(CanThisClassDualWield()) {
|
||||
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
BotGroupSay(this, "I can't Dual Wield yet.");
|
||||
--how_many_slots;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
|
||||
success = true;
|
||||
}
|
||||
if(success) {
|
||||
if (GetBotItem(EQEmu::inventory::slotPrimary)) {
|
||||
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotPrimary);
|
||||
if (remove_item->GetItem()->IsType2HWeapon()) {
|
||||
BotTradeSwapItem(client, EQEmu::inventory::slotPrimary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!success) {
|
||||
for (int j = EQEmu::legacy::EQUIPMENT_BEGIN; j <= (EQEmu::legacy::EQUIPMENT_END + 1); ++j) {
|
||||
if((mWeaponItem->Slots & (1 << j))) {
|
||||
if (j == 22)
|
||||
j = 9999;
|
||||
if (client != GetOwner()) {
|
||||
client->Message(CC_Red, "You are not the owner of this bot - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if ((beginSlotID != legacy::TRADE_BEGIN) && (beginSlotID != inventory::slotCursor)) {
|
||||
client->Message(CC_Red, "Trade request processing from illegal 'begin' slot - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if ((endSlotID != legacy::TRADE_END) && (endSlotID != inventory::slotCursor)) {
|
||||
client->Message(CC_Red, "Trade request processing from illegal 'end' slot - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if (((beginSlotID == inventory::slotCursor) && (endSlotID != inventory::slotCursor)) || ((beginSlotID != inventory::slotCursor) && (endSlotID == inventory::slotCursor))) {
|
||||
client->Message(CC_Red, "Trade request processing illegal slot range - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if (endSlotID < beginSlotID) {
|
||||
client->Message(CC_Red, "Trade request processing in reverse slot order - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if (client->IsEngaged() || IsEngaged()) {
|
||||
client->Message(CC_Yellow, "You may not perform a trade while engaged - Trade Canceled!");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
|
||||
swap_item = GetBotItem(j);
|
||||
failedLoreCheck = false;
|
||||
for (int k = EQEmu::inventory::socketBegin; k < EQEmu::inventory::SocketCount; ++k) {
|
||||
EQEmu::ItemInstance *itm = swap_item->GetAugment(k);
|
||||
if(itm)
|
||||
{
|
||||
if(client->CheckLoreConflict(itm->GetItem())) {
|
||||
failedLoreCheck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(client->CheckLoreConflict(swap_item->GetItem())) {
|
||||
failedLoreCheck = true;
|
||||
}
|
||||
if(!failedLoreCheck) {
|
||||
if (j == EQEmu::inventory::slotPrimary) {
|
||||
if (mWeaponItem->IsType2HWeapon()) {
|
||||
if (GetBotItem(EQEmu::inventory::slotSecondary)) {
|
||||
if (client->CheckLoreConflict(GetBotItem(EQEmu::inventory::slotSecondary)->GetItem())) {
|
||||
failedLoreCheck = true;
|
||||
}
|
||||
else {
|
||||
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotSecondary);
|
||||
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!failedLoreCheck) {
|
||||
BotTradeSwapItem(client, EQEmu::inventory::slotPrimary, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
|
||||
success = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (j == EQEmu::inventory::slotSecondary) {
|
||||
if(inst->IsWeapon()) {
|
||||
if(CanThisClassDualWield()) {
|
||||
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
botCanWear[i] = false;
|
||||
BotGroupSay(this, "I can't Dual Wield yet.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
|
||||
success = true;
|
||||
}
|
||||
if (success && GetBotItem(EQEmu::inventory::slotPrimary)) {
|
||||
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotPrimary);
|
||||
if (remove_item->GetItem()->IsType2HWeapon()) {
|
||||
BotTradeSwapItem(client, EQEmu::inventory::slotPrimary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
else {
|
||||
BotTradeSwapItem(client, j, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
botCanWear[i] = false;
|
||||
Message_StringID(0, PICK_LORE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(success) {
|
||||
if(how_many_slots > 1) {
|
||||
Message(300, "If you want this item in a different slot, use #bot inventory remove <slot_id> to clear the spot.");
|
||||
}
|
||||
CalcBotStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(inst) {
|
||||
client->DeleteItemInInventory(i, 0, UpdateClient);
|
||||
if(!botCanWear[i]) {
|
||||
client->PushItemOnCursor(*inst, true);
|
||||
}
|
||||
}
|
||||
std::list<ClientTrade> client_trade;
|
||||
std::list<ClientReturn> client_return;
|
||||
|
||||
// pre-checks for incoming illegal transfers
|
||||
for (int16 trade_index = beginSlotID; trade_index <= endSlotID; ++trade_index) {
|
||||
auto trade_instance = client->GetInv()[trade_index];
|
||||
if (!trade_instance)
|
||||
continue;
|
||||
|
||||
if (!trade_instance->GetItem()) {
|
||||
// TODO: add logging
|
||||
client->Message(CC_Red, "A server error was encountered while processing client slot %i - Trade Canceled.", trade_index);
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if ((trade_index != inventory::slotCursor) && !trade_instance->IsDroppable()) {
|
||||
// TODO: add logging
|
||||
client->Message(CC_Red, "Trade hack detected - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if (trade_instance->IsStackable() && (trade_instance->GetCharges() < trade_instance->GetItem()->StackSize)) { // temp until partial stacks are implemented
|
||||
client->Message(CC_Yellow, "'%s' is only a partially stacked item - Trade Canceled!", trade_instance->GetItem()->Name);
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if (CheckLoreConflict(trade_instance->GetItem())) {
|
||||
client->Message(CC_Yellow, "This bot already has lore equipment matching the item '%s' - Trade Canceled!", trade_instance->GetItem()->Name);
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
|
||||
const EQEmu::ItemData* item2 = 0;
|
||||
for(int y = beginSlotID; y <= endSlotID; ++y) {
|
||||
item2 = database.GetItem(items[y]);
|
||||
if(item2) {
|
||||
if(botCanWear[y]) {
|
||||
BotGroupSay(this, "Thank you for the %s, %s!", item2->Name, client->GetName());
|
||||
}
|
||||
else {
|
||||
BotGroupSay(this, "I can't use this %s!", item2->Name);
|
||||
}
|
||||
if (!trade_instance->IsType(item::ItemClassCommon)) {
|
||||
client_return.push_back(ClientReturn(trade_instance, trade_index, trade_instance->GetItem()->Name));
|
||||
continue;
|
||||
}
|
||||
if (!trade_instance->IsEquipable(GetBaseRace(), GetClass()) || (GetLevel() < trade_instance->GetItem()->ReqLevel)) { // deity checks will be handled within IsEquipable()
|
||||
client_return.push_back(ClientReturn(trade_instance, trade_index, trade_instance->GetItem()->Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
client_trade.push_back(ClientTrade(trade_instance, trade_index, trade_instance->GetItem()->Name));
|
||||
}
|
||||
|
||||
// check for incoming lore hacks
|
||||
for (auto& trade_iterator : client_trade) {
|
||||
if (!trade_iterator.tradeItemInstance->GetItem()->LoreFlag)
|
||||
continue;
|
||||
|
||||
for (const auto& check_iterator : client_trade) {
|
||||
if (check_iterator.fromClientSlot == trade_iterator.fromClientSlot)
|
||||
continue;
|
||||
if (!check_iterator.tradeItemInstance->GetItem()->LoreFlag)
|
||||
continue;
|
||||
|
||||
if ((trade_iterator.tradeItemInstance->GetItem()->LoreGroup == -1) && (check_iterator.tradeItemInstance->GetItem()->ID == trade_iterator.tradeItemInstance->GetItem()->ID)) {
|
||||
// TODO: add logging
|
||||
client->Message(CC_Red, "Trade hack detected - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if ((trade_iterator.tradeItemInstance->GetItem()->LoreGroup > 0) && (check_iterator.tradeItemInstance->GetItem()->LoreGroup == trade_iterator.tradeItemInstance->GetItem()->LoreGroup)) {
|
||||
// TODO: add logging
|
||||
client->Message(CC_Red, "Trade hack detected - Trade Canceled.");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find equipment slots
|
||||
const bool can_dual_wield = (GetSkill(EQEmu::skills::SkillDualWield) > 0);
|
||||
bool melee_2h_weapon = false;
|
||||
bool melee_secondary = false;
|
||||
|
||||
//for (unsigned stage_loop = stageStackable; stage_loop <= stageReplaceable; ++stage_loop) { // awaiting implementation
|
||||
for (unsigned stage_loop = stageEmpty; stage_loop <= stageReplaceable; ++stage_loop) {
|
||||
for (auto& trade_iterator : client_trade) {
|
||||
if (trade_iterator.toBotSlot != legacy::SLOT_INVALID)
|
||||
continue;
|
||||
|
||||
auto trade_instance = trade_iterator.tradeItemInstance;
|
||||
//if ((stage_loop == stageStackable) && !trade_instance->IsStackable())
|
||||
// continue;
|
||||
|
||||
for (auto index : bot_equip_order) {
|
||||
if (!(trade_instance->GetItem()->Slots & (1 << index)))
|
||||
continue;
|
||||
|
||||
//if (stage_loop == stageStackable) {
|
||||
// // TODO: implement
|
||||
// continue;
|
||||
//}
|
||||
|
||||
if (stage_loop != stageReplaceable) {
|
||||
if ((index == proxyPowerSource) && m_inv[inventory::slotPowerSource])
|
||||
continue;
|
||||
else if (m_inv[index])
|
||||
continue;
|
||||
}
|
||||
|
||||
bool slot_taken = false;
|
||||
for (const auto& check_iterator : client_trade) {
|
||||
if (check_iterator.fromClientSlot == trade_iterator.fromClientSlot)
|
||||
continue;
|
||||
|
||||
if (check_iterator.toBotSlot == index) {
|
||||
slot_taken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot_taken)
|
||||
continue;
|
||||
|
||||
if (index == inventory::slotPrimary) {
|
||||
if (trade_instance->GetItem()->IsType2HWeapon()) {
|
||||
if (!melee_secondary) {
|
||||
melee_2h_weapon = true;
|
||||
|
||||
auto equipped_secondary_weapon = m_inv[inventory::slotSecondary];
|
||||
if (equipped_secondary_weapon)
|
||||
client_return.push_back(ClientReturn(equipped_secondary_weapon, inventory::slotSecondary));
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index == inventory::slotSecondary) {
|
||||
if (!melee_2h_weapon) {
|
||||
if ((can_dual_wield && trade_instance->GetItem()->IsType1HWeapon()) || trade_instance->GetItem()->IsTypeShield() || !trade_instance->IsWeapon()) {
|
||||
melee_secondary = true;
|
||||
|
||||
auto equipped_primary_weapon = m_inv[inventory::slotPrimary];
|
||||
if (equipped_primary_weapon && equipped_primary_weapon->GetItem()->IsType2HWeapon())
|
||||
client_return.push_back(ClientReturn(equipped_primary_weapon, inventory::slotPrimary));
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == proxyPowerSource) {
|
||||
trade_iterator.toBotSlot = inventory::slotPowerSource;
|
||||
|
||||
if (m_inv[inventory::slotPowerSource])
|
||||
client_return.push_back(ClientReturn(m_inv[inventory::slotPowerSource], inventory::slotPowerSource));
|
||||
}
|
||||
else {
|
||||
trade_iterator.toBotSlot = index;
|
||||
|
||||
if (m_inv[index])
|
||||
client_return.push_back(ClientReturn(m_inv[index], index));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move unassignable items from trade list to return list
|
||||
for (std::list<ClientTrade>::iterator trade_iterator = client_trade.begin(); trade_iterator != client_trade.end();) {
|
||||
if (trade_iterator->toBotSlot == legacy::SLOT_INVALID) {
|
||||
client_return.push_back(ClientReturn(trade_iterator->tradeItemInstance, trade_iterator->fromClientSlot, trade_iterator->tradeItemInstance->GetItem()->Name));
|
||||
trade_iterator = client_trade.erase(trade_iterator);
|
||||
continue;
|
||||
}
|
||||
++trade_iterator;
|
||||
}
|
||||
|
||||
// out-going return checks for client
|
||||
for (auto& return_iterator : client_return) {
|
||||
auto return_instance = return_iterator.returnItemInstance;
|
||||
if (!return_instance)
|
||||
continue;
|
||||
|
||||
if (!return_instance->GetItem()) {
|
||||
// TODO: add logging
|
||||
client->Message(CC_Red, "A server error was encountered while processing bot slot %i - Trade Canceled.", return_iterator.fromBotSlot);
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
if (client->CheckLoreConflict(return_instance->GetItem())) {
|
||||
client->Message(CC_Yellow, "You already have lore equipment matching the item '%s' - Trade Canceled!", return_instance->GetItem()->Name);
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
|
||||
if (return_iterator.fromBotSlot == inventory::slotCursor) {
|
||||
return_iterator.toClientSlot = inventory::slotCursor;
|
||||
}
|
||||
else {
|
||||
int16 client_search_general = legacy::GENERAL_BEGIN;
|
||||
uint8 client_search_bag = inventory::containerBegin;
|
||||
|
||||
bool run_search = true;
|
||||
while (run_search) {
|
||||
int16 client_test_slot = client->GetInv().FindFreeSlotForTradeItem(return_instance, client_search_general, client_search_bag);
|
||||
if (client_test_slot == legacy::SLOT_INVALID) {
|
||||
run_search = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool slot_taken = false;
|
||||
for (const auto& check_iterator : client_return) {
|
||||
if (check_iterator.fromBotSlot == return_iterator.fromBotSlot)
|
||||
continue;
|
||||
|
||||
if ((check_iterator.toClientSlot == client_test_slot) && (client_test_slot != inventory::slotCursor)) {
|
||||
slot_taken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot_taken) {
|
||||
if ((client_test_slot >= legacy::GENERAL_BEGIN) && (client_test_slot <= legacy::GENERAL_END)) {
|
||||
++client_search_general;
|
||||
client_search_bag = inventory::containerBegin;
|
||||
}
|
||||
else {
|
||||
client_search_general = InventoryProfile::CalcSlotId(client_test_slot);
|
||||
client_search_bag = InventoryProfile::CalcBagIdx(client_test_slot);
|
||||
|
||||
++client_search_bag;
|
||||
if (client_search_bag >= inventory::ContainerCount) {
|
||||
// incrementing this past legacy::GENERAL_END triggers the (client_test_slot == legacy::SLOT_INVALID) at the beginning of the search loop
|
||||
// ideally, this will never occur because we always start fresh with each loop iteration and should receive SLOT_CURSOR as a return value
|
||||
++client_search_general;
|
||||
client_search_bag = inventory::containerBegin;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return_iterator.toClientSlot = client_test_slot;
|
||||
run_search = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (return_iterator.toClientSlot == legacy::SLOT_INVALID) {
|
||||
client->Message(CC_Yellow, "You do not have room to complete this trade - Trade Canceled!");
|
||||
client->ResetTrade();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// perform actual trades
|
||||
// returns first since clients have trade slots and bots do not
|
||||
for (auto& return_iterator : client_return) {
|
||||
// TODO: code for stackables
|
||||
|
||||
if (return_iterator.fromBotSlot == inventory::slotCursor) { // failed trade return
|
||||
// no movement action required
|
||||
}
|
||||
else if ((return_iterator.fromBotSlot >= legacy::TRADE_BEGIN) && (return_iterator.fromBotSlot <= legacy::TRADE_END)) { // failed trade returns
|
||||
client->PutItemInInventory(return_iterator.toClientSlot, *return_iterator.returnItemInstance);
|
||||
client->SendItemPacket(return_iterator.toClientSlot, return_iterator.returnItemInstance, ItemPacketTrade);
|
||||
client->DeleteItemInInventory(return_iterator.fromBotSlot);
|
||||
}
|
||||
else { // successful trade returns
|
||||
auto return_instance = m_inv.PopItem(return_iterator.fromBotSlot);
|
||||
//if (*return_instance != *return_iterator.returnItemInstance) {
|
||||
// // TODO: add logging
|
||||
//}
|
||||
|
||||
if (!botdb.DeleteItemBySlot(GetBotID(), return_iterator.fromBotSlot))
|
||||
client->Message(CC_Red, "%s (slot: %i, name: '%s')", BotDatabase::fail::DeleteItemBySlot(), return_iterator.fromBotSlot, (return_instance ? return_instance->GetItem()->Name : "nullptr"));
|
||||
|
||||
BotRemoveEquipItem(return_iterator.fromBotSlot);
|
||||
if (return_instance)
|
||||
client->PutItemInInventory(return_iterator.toClientSlot, *return_instance, true);
|
||||
InventoryProfile::MarkDirty(return_instance);
|
||||
}
|
||||
return_iterator.returnItemInstance = nullptr;
|
||||
}
|
||||
|
||||
// trades can now go in as empty slot inserts
|
||||
for (auto& trade_iterator : client_trade) {
|
||||
// TODO: code for stackables
|
||||
|
||||
if (!botdb.SaveItemBySlot(this, trade_iterator.toBotSlot, trade_iterator.tradeItemInstance))
|
||||
client->Message(CC_Red, "%s (slot: %i, name: '%s')", BotDatabase::fail::SaveItemBySlot(), trade_iterator.toBotSlot, (trade_iterator.tradeItemInstance ? trade_iterator.tradeItemInstance->GetItem()->Name : "nullptr"));
|
||||
|
||||
m_inv.PutItem(trade_iterator.toBotSlot, *trade_iterator.tradeItemInstance);
|
||||
this->BotAddEquipItem(trade_iterator.toBotSlot, (trade_iterator.tradeItemInstance ? trade_iterator.tradeItemInstance->GetID() : 0));
|
||||
client->DeleteItemInInventory(trade_iterator.fromClientSlot, 0, true);
|
||||
trade_iterator.tradeItemInstance = nullptr;
|
||||
}
|
||||
|
||||
// trade messages
|
||||
for (const auto& return_iterator : client_return) {
|
||||
if (return_iterator.failedItemName.size())
|
||||
client->Message(MT_Tell, "%s tells you, \"%s, I can't use this '%s.'\"", GetCleanName(), client->GetName(), return_iterator.failedItemName.c_str());
|
||||
}
|
||||
for (const auto& trade_iterator : client_trade) {
|
||||
if (trade_iterator.acceptedItemName.size())
|
||||
client->Message(MT_Tell, "%s tells you, \"Thank you for the '%s,' %s!\"", GetCleanName(), trade_iterator.acceptedItemName.c_str(), client->GetName());
|
||||
}
|
||||
|
||||
size_t accepted_count = client_trade.size();
|
||||
size_t returned_count = client_return.size();
|
||||
|
||||
client->Message(CC_Lime, "Trade with '%s' resulted in %i accepted item%s, %i returned item%s.", GetCleanName(), accepted_count, ((accepted_count == 1) ? "" : "s"), returned_count, ((returned_count == 1) ? "" : "s"));
|
||||
}
|
||||
|
||||
bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) {
|
||||
@@ -3681,7 +3849,8 @@ void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillT
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/) {
|
||||
//void Bot::AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false)
|
||||
void Bot::AddToHateList(Mob* other, uint32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic) {
|
||||
Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic);
|
||||
}
|
||||
|
||||
@@ -3692,11 +3861,22 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!GetTarget() || GetTarget() != other)
|
||||
SetTarget(other);
|
||||
if ((GetHP() <= 0) || (GetAppearance() == eaDead)) {
|
||||
SetTarget(nullptr);
|
||||
Log.Out(Logs::Detail, Logs::Combat, "Attempted to attack %s while unconscious or, otherwise, appearing dead", other->GetCleanName());
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Out(Logs::Detail, Logs::Combat, "Attacking %s with hand %d %s", other?other->GetCleanName():"(nullptr)", Hand, FromRiposte?"(this is a riposte)":"");
|
||||
if ((IsCasting() && (GetClass() != BARD) && !IsFromSpell) || other == nullptr || (GetHP() < 0) || (GetAppearance() == eaDead) || (!IsAttackAllowed(other))) {
|
||||
//if(!GetTarget() || GetTarget() != other) // NPC::Attack() doesn't do this
|
||||
// SetTarget(other);
|
||||
|
||||
// apparently, we always want our target to be 'other'..why not just set it?
|
||||
SetTarget(other);
|
||||
// takes more to compare a call result, load for a call, load a compare to address and compare, and finally
|
||||
// push a value to an address than to just load for a call and push a value to an address.
|
||||
|
||||
Log.Out(Logs::Detail, Logs::Combat, "Attacking %s with hand %d %s", other->GetCleanName(), Hand, (FromRiposte ? "(this is a riposte)" : ""));
|
||||
if ((IsCasting() && (GetClass() != BARD) && !IsFromSpell) || (!IsAttackAllowed(other))) {
|
||||
if(this->GetOwnerID())
|
||||
entity_list.MessageClose(this, 1, 200, 10, "%s says, '%s is not a legal target master.'", this->GetCleanName(), this->GetTarget()->GetCleanName());
|
||||
|
||||
@@ -3739,8 +3919,10 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
|
||||
DamageHitInfo my_hit;
|
||||
AttackAnimation(my_hit.skill, Hand, weapon);
|
||||
Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, my_hit.skill);
|
||||
/// Now figure out damage
|
||||
my_hit.damage_done =0;
|
||||
|
||||
// Now figure out damage
|
||||
my_hit.damage_done = 0;
|
||||
my_hit.min_damage = 0;
|
||||
uint8 mylevel = GetLevel() ? GetLevel() : 1;
|
||||
uint32 hate = 0;
|
||||
if (weapon)
|
||||
@@ -3786,6 +3968,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
|
||||
|
||||
Log.Out(Logs::Detail, Logs::Combat, "Damage calculated: base %d min damage %d skill %d", my_hit.base_damage, my_hit.min_damage, my_hit.skill);
|
||||
|
||||
int hit_chance_bonus = 0;
|
||||
my_hit.offense = offense(my_hit.skill);
|
||||
my_hit.hand = Hand;
|
||||
|
||||
@@ -3794,8 +3977,11 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
|
||||
my_hit.base_damage += opts->damage_flat;
|
||||
hate *= opts->hate_percent;
|
||||
hate += opts->hate_flat;
|
||||
hit_chance_bonus += opts->hit_chance;
|
||||
}
|
||||
|
||||
my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus);
|
||||
|
||||
DoAttack(other, my_hit, opts);
|
||||
|
||||
Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", my_hit.damage_done);
|
||||
@@ -4076,24 +4262,23 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16
|
||||
value = base1;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
case SE_SympatheticProc:
|
||||
{
|
||||
if(type == focusSympatheticProc)
|
||||
{
|
||||
float ProcChance, ProcBonus;
|
||||
int16 ProcRateMod = base1; //Baseline is 100 for most Sympathetic foci
|
||||
int32 cast_time = GetActSpellCasttime(spell_id, spells[spell_id].cast_time);
|
||||
GetSympatheticProcChances(ProcBonus, ProcChance, cast_time, ProcRateMod);
|
||||
//case SE_SympatheticProc:
|
||||
//{
|
||||
// if(type == focusSympatheticProc)
|
||||
// {
|
||||
// float ProcChance, ProcBonus;
|
||||
// int16 ProcRateMod = base1; //Baseline is 100 for most Sympathetic foci
|
||||
// int32 cast_time = GetActSpellCasttime(spell_id, spells[spell_id].cast_time);
|
||||
// GetSympatheticProcChances(ProcBonus, ProcChance, cast_time, ProcRateMod);
|
||||
|
||||
if(zone->random.Real(0, 1) <= ProcChance)
|
||||
value = focus_id;
|
||||
// if(zone->random.Real(0, 1) <= ProcChance)
|
||||
// value = focus_id;
|
||||
|
||||
else
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
}*/
|
||||
// else
|
||||
// value = 0;
|
||||
// }
|
||||
// break;
|
||||
//}
|
||||
case SE_FcDamageAmt: {
|
||||
if(type == focusFcDamageAmt)
|
||||
value = base1;
|
||||
@@ -4884,14 +5069,14 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32
|
||||
CheckNumHitsRemaining(NumHit::OutgoingHitSuccess);
|
||||
|
||||
//[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill
|
||||
/* if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skill){
|
||||
int kb_chance = 25;
|
||||
kb_chance += (kb_chance * (100 - aabonuses.SpecialAttackKBProc[0]) / 100);
|
||||
//if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skill){
|
||||
// int kb_chance = 25;
|
||||
// kb_chance += (kb_chance * (100 - aabonuses.SpecialAttackKBProc[0]) / 100);
|
||||
|
||||
if (zone->random.Int(0, 99) < kb_chance)
|
||||
SpellFinished(904, who, 10, 0, -1, spells[904].ResistDiff);
|
||||
//who->Stun(100); Kayen: This effect does not stun on live, it only moves the NPC.
|
||||
}*/
|
||||
// if (zone->random.Int(0, 99) < kb_chance)
|
||||
// SpellFinished(904, who, 10, 0, -1, spells[904].ResistDiff);
|
||||
// //who->Stun(100); Kayen: This effect does not stun on live, it only moves the NPC.
|
||||
//}
|
||||
|
||||
if (HasSkillProcs())
|
||||
TrySkillProc(who, skill, (ReuseTime * 1000));
|
||||
@@ -5426,7 +5611,7 @@ void Bot::SetAttackTimer() {
|
||||
}
|
||||
|
||||
speed = (RuleB(Spells, Jun182014HundredHandsRevamp) ? static_cast<int>(((delay / haste_mod) + ((hhe / 1000.0f) * (delay / haste_mod))) * 100) : static_cast<int>(((delay / haste_mod) + ((hhe / 100.0f) * delay)) * 100));
|
||||
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true);
|
||||
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true, true);
|
||||
|
||||
if (i == EQEmu::inventory::slotPrimary)
|
||||
PrimaryWeapon = ItemToUse;
|
||||
@@ -6856,20 +7041,31 @@ bool Bot::IsArcheryRange(Mob *target) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Bot::IsBotCasterCombatRange(Mob *target) {
|
||||
bool result = false;
|
||||
if(target) {
|
||||
float range = BotAISpellRange;
|
||||
range *= range;
|
||||
range *= .5;
|
||||
float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition());
|
||||
if(targetDistance > range)
|
||||
result = false;
|
||||
else
|
||||
result = true;
|
||||
}
|
||||
bool Bot::IsBotCasterAtCombatRange(Mob *target)
|
||||
{
|
||||
static const float local[PLAYER_CLASS_COUNT] = {
|
||||
0.0f, // WARRIOR
|
||||
1156.0f, // CLERIC as DSq value (34 units)
|
||||
0.0f, 0.0f, 0.0f, // PALADIN, RANGER, SHADOWKNIGHT
|
||||
1764.0f, // DRUID as DSq value (42 units)
|
||||
0.0f, 0.0f, 0.0f, // MONK, BARD, ROGUE
|
||||
1444.0f, // SHAMAN as DSq value (38 units)
|
||||
2916.0f, // NECROMANCER as DSq value (54 units)
|
||||
2304.0f, // WIZARD as DSq value (48 units)
|
||||
2704.0f, // MAGICIAN as DSq value (52 units)
|
||||
2500.0f, // ENCHANTER as DSq value (50 units)
|
||||
0.0f, 0.0f // BEASTLORD, BERSERKER
|
||||
};
|
||||
|
||||
return result;
|
||||
if (!target)
|
||||
return false;
|
||||
if (GetClass() < WARRIOR || GetClass() > BERSERKER)
|
||||
return false;
|
||||
|
||||
float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition());
|
||||
if (targetDistance < local[GetClass() - 1])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Bot::UpdateGroupCastingRoles(const Group* group, bool disband)
|
||||
@@ -7452,7 +7648,7 @@ void Bot::AddItemBonuses(const EQEmu::ItemInstance *inst, StatBonuses* newbon, b
|
||||
|
||||
switch(item->BardType)
|
||||
{
|
||||
case 51: /* All (e.g. Singing Short Sword) */
|
||||
case EQEmu::item::ItemTypeAllInstrumentTypes: // (e.g. Singing Short Sword)
|
||||
{
|
||||
if(item->BardValue > newbon->singingMod)
|
||||
newbon->singingMod = item->BardValue;
|
||||
@@ -7466,31 +7662,31 @@ void Bot::AddItemBonuses(const EQEmu::ItemInstance *inst, StatBonuses* newbon, b
|
||||
newbon->windMod = item->BardValue;
|
||||
break;
|
||||
}
|
||||
case 50: /* Singing */
|
||||
case EQEmu::item::ItemTypeSinging:
|
||||
{
|
||||
if(item->BardValue > newbon->singingMod)
|
||||
newbon->singingMod = item->BardValue;
|
||||
break;
|
||||
}
|
||||
case 23: /* Wind */
|
||||
case EQEmu::item::ItemTypeWindInstrument:
|
||||
{
|
||||
if(item->BardValue > newbon->windMod)
|
||||
newbon->windMod = item->BardValue;
|
||||
break;
|
||||
}
|
||||
case 24: /* stringed */
|
||||
case EQEmu::item::ItemTypeStringedInstrument:
|
||||
{
|
||||
if(item->BardValue > newbon->stringedMod)
|
||||
newbon->stringedMod = item->BardValue;
|
||||
break;
|
||||
}
|
||||
case 25: /* brass */
|
||||
case EQEmu::item::ItemTypeBrassInstrument:
|
||||
{
|
||||
if(item->BardValue > newbon->brassMod)
|
||||
newbon->brassMod = item->BardValue;
|
||||
break;
|
||||
}
|
||||
case 26: /* Percussion */
|
||||
case EQEmu::item::ItemTypePercussionInstrument:
|
||||
{
|
||||
if(item->BardValue > newbon->percussionMod)
|
||||
newbon->percussionMod = item->BardValue;
|
||||
@@ -7572,10 +7768,10 @@ void Bot::CalcBotStats(bool showtext) {
|
||||
GetSkill(EQEmu::skills::SkillBrassInstruments), GetSkill(EQEmu::skills::SkillPercussionInstruments), GetSkill(EQEmu::skills::SkillSinging), GetSkill(EQEmu::skills::SkillStringedInstruments), GetSkill(EQEmu::skills::SkillWindInstruments));
|
||||
}
|
||||
|
||||
/*if(this->Save())
|
||||
this->GetBotOwner()->CastToClient()->Message(0, "%s saved.", this->GetCleanName());
|
||||
else
|
||||
this->GetBotOwner()->CastToClient()->Message(13, "%s save failed!", this->GetCleanName());*/
|
||||
//if(this->Save())
|
||||
// this->GetBotOwner()->CastToClient()->Message(0, "%s saved.", this->GetCleanName());
|
||||
//else
|
||||
// this->GetBotOwner()->CastToClient()->Message(13, "%s save failed!", this->GetCleanName());
|
||||
|
||||
CalcBonuses();
|
||||
|
||||
@@ -7609,21 +7805,6 @@ bool Bot::CheckLoreConflict(const EQEmu::ItemData* item) {
|
||||
return (m_inv.HasItemByLoreGroup(item->LoreGroup, invWhereWorn) != INVALID_INDEX);
|
||||
}
|
||||
|
||||
bool Bot::GroupHasClass(Group* group, uint8 classId) {
|
||||
bool result = false;
|
||||
|
||||
if(group) {
|
||||
for(int counter = 0; counter < MAX_GROUP_MEMBERS; counter++) {
|
||||
if(group->members[counter] && group->members[counter]->GetClass() & classId) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes) {
|
||||
if((iSpellTypes&SpellTypes_Detrimental) != 0) {
|
||||
Log.Out(Logs::General, Logs::Error, "Error: detrimental spells requested from AICheckCloseBeneficialSpells!!");
|
||||
|
||||
+6
-8
@@ -337,7 +337,7 @@ public:
|
||||
bool IsStanding();
|
||||
int GetBotWalkspeed() const { return (int)((float)_GetWalkSpeed() * 1.786f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143
|
||||
int GetBotRunspeed() const { return (int)((float)_GetRunSpeed() * 1.786f); }
|
||||
bool IsBotCasterCombatRange(Mob *target);
|
||||
bool IsBotCasterAtCombatRange(Mob *target);
|
||||
bool UseDiscipline(uint32 spell_id, uint32 target);
|
||||
uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets);
|
||||
bool GetNeedsCured(Mob *tar);
|
||||
@@ -461,10 +461,12 @@ public:
|
||||
static int32 GetDisciplineRecastTimer(Bot *caster, int timer_index);
|
||||
static bool CheckDisciplineRecastTimers(Bot *caster, int timer_index);
|
||||
static uint32 GetDisciplineRemainingTime(Bot *caster, int timer_index);
|
||||
|
||||
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);
|
||||
static std::list<BotSpell_wPriority> GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint32 spellType);
|
||||
|
||||
static BotSpell GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType);
|
||||
static BotSpell GetBestBotSpellForFastHeal(Bot* botCaster);
|
||||
static BotSpell GetBestBotSpellForHealOverTime(Bot* botCaster);
|
||||
@@ -476,6 +478,7 @@ public:
|
||||
static BotSpell GetBestBotSpellForGroupHeal(Bot* botCaster);
|
||||
static BotSpell GetBestBotSpellForMagicBasedSlow(Bot* botCaster);
|
||||
static BotSpell GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster);
|
||||
|
||||
static Mob* GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell);
|
||||
static BotSpell GetBestBotSpellForMez(Bot* botCaster);
|
||||
static BotSpell GetBestBotMagicianPetSpell(Bot* botCaster);
|
||||
@@ -486,17 +489,12 @@ public:
|
||||
static BotSpell GetDebuffBotSpell(Bot* botCaster, Mob* target);
|
||||
static BotSpell GetBestBotSpellForCure(Bot* botCaster, Mob* target);
|
||||
static BotSpell GetBestBotSpellForResistDebuff(Bot* botCaster, Mob* target);
|
||||
|
||||
static NPCType CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender);
|
||||
|
||||
// Static Bot Group Methods
|
||||
static bool AddBotToGroup(Bot* bot, Group* group);
|
||||
static bool RemoveBotFromGroup(Bot* bot, Group* group);
|
||||
static bool GroupHasClass(Group* group, uint8 classId);
|
||||
static bool GroupHasClericClass(Group* group) { return GroupHasClass(group, CLERIC); }
|
||||
static bool GroupHasDruidClass(Group* group) { return GroupHasClass(group, DRUID); }
|
||||
static bool GroupHasShamanClass(Group* group) { return GroupHasClass(group, SHAMAN); }
|
||||
static bool GroupHasEnchanterClass(Group* group) { return GroupHasClass(group, ENCHANTER); }
|
||||
static bool GroupHasPriestClass(Group* group) { return GroupHasClass(group, CLERIC | DRUID | SHAMAN); }
|
||||
static void BotGroupSay(Mob *speaker, const char *msg, ...);
|
||||
|
||||
// "GET" Class Methods
|
||||
@@ -657,7 +655,7 @@ public:
|
||||
|
||||
// Publicized private functions
|
||||
static NPCType FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack);
|
||||
void BotRemoveEquipItem(int slot);
|
||||
void BotRemoveEquipItem(int16 slot);
|
||||
void RemoveBotItemBySlot(uint32 slotID, std::string* errorMessage);
|
||||
uint32 GetTotalPlayTime();
|
||||
|
||||
|
||||
+37
-6
@@ -3894,10 +3894,19 @@ void Client::Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app)
|
||||
|
||||
Mob *m = nullptr;
|
||||
|
||||
if (brrs->EntityID == GetID())
|
||||
if (brrs->EntityID == GetID()) {
|
||||
m = this;
|
||||
else if (brrs->EntityID == GetPetID())
|
||||
}
|
||||
else if (brrs->EntityID == GetPetID()) {
|
||||
m = GetPet();
|
||||
}
|
||||
#ifdef BOTS
|
||||
else {
|
||||
Mob* bot_test = entity_list.GetMob(brrs->EntityID);
|
||||
if (bot_test && bot_test->IsBot() && bot_test->GetOwner() == this)
|
||||
m = bot_test;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!m)
|
||||
return;
|
||||
@@ -12554,8 +12563,25 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
|
||||
// Now remove the item from the player, this happens regardless of outcome
|
||||
if (!inst->IsStackable())
|
||||
this->DeleteItemInInventory(mp->itemslot, 0, false);
|
||||
else
|
||||
this->DeleteItemInInventory(mp->itemslot, mp->quantity, false);
|
||||
else {
|
||||
// HACK: DeleteItemInInventory uses int8 for quantity type. There is no consistent use of types in code in this path so for now iteratively delete from inventory.
|
||||
if (mp->quantity > 255) {
|
||||
uint32 temp = mp->quantity;
|
||||
while (temp > 255 && temp != 0) {
|
||||
// Delete chunks of 255
|
||||
this->DeleteItemInInventory(mp->itemslot, 255, false);
|
||||
temp -= 255;
|
||||
}
|
||||
if (temp != 0) {
|
||||
// Delete remaining
|
||||
this->DeleteItemInInventory(mp->itemslot, temp, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->DeleteItemInInventory(mp->itemslot, mp->quantity, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//This forces the price to show up correctly for charged items.
|
||||
if (inst->IsCharged())
|
||||
@@ -13038,9 +13064,14 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
|
||||
}
|
||||
}
|
||||
if (GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs) || nt == this || inspect_buffs || (nt->IsClient() && !nt->CastToClient()->GetPVP()) ||
|
||||
(nt->IsPet() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP()) ||
|
||||
(nt->IsMerc() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP()))
|
||||
(nt->IsPet() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP()) ||
|
||||
#ifdef BOTS
|
||||
(nt->IsBot() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP()) || // TODO: bot pets
|
||||
#endif
|
||||
(nt->IsMerc() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP()))
|
||||
{
|
||||
nt->SendBuffsToClient(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+7
-1
@@ -2980,10 +2980,16 @@ void EntityList::Evade(Mob *who)
|
||||
//removes "targ" from all hate lists, including feigned, in the zone
|
||||
void EntityList::ClearAggro(Mob* targ)
|
||||
{
|
||||
Client *c = nullptr;
|
||||
if (targ->IsClient())
|
||||
c = targ->CastToClient();
|
||||
auto it = npc_list.begin();
|
||||
while (it != npc_list.end()) {
|
||||
if (it->second->CheckAggro(targ))
|
||||
if (it->second->CheckAggro(targ)) {
|
||||
if (c)
|
||||
c->RemoveXTarget(it->second, false);
|
||||
it->second->RemoveFromHateList(targ);
|
||||
}
|
||||
it->second->RemoveFromFeignMemory(targ->CastToClient()); //just in case we feigned
|
||||
++it;
|
||||
}
|
||||
|
||||
@@ -4156,8 +4156,12 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses)
|
||||
if(IsPet() && GetOwner() && GetOwner()->IsClient()) {
|
||||
SendPetBuffsToClient();
|
||||
}
|
||||
if((IsClient() && !CastToClient()->GetPVP()) || (IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
||||
(IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()))
|
||||
if((IsClient() && !CastToClient()->GetPVP()) ||
|
||||
(IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
||||
#ifdef BOTS
|
||||
(IsBot() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
||||
#endif
|
||||
(IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()))
|
||||
{
|
||||
EQApplicationPacket *outapp = MakeBuffsPacket();
|
||||
|
||||
|
||||
+6
-2
@@ -3291,8 +3291,12 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid
|
||||
if (IsPet() && GetOwner() && GetOwner()->IsClient())
|
||||
SendPetBuffsToClient();
|
||||
|
||||
if((IsClient() && !CastToClient()->GetPVP()) || (IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
||||
(IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()))
|
||||
if((IsClient() && !CastToClient()->GetPVP()) ||
|
||||
(IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
||||
#ifdef BOTS
|
||||
(IsBot() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
||||
#endif
|
||||
(IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()))
|
||||
{
|
||||
EQApplicationPacket *outapp = MakeBuffsPacket();
|
||||
|
||||
|
||||
+1
-1
@@ -2261,7 +2261,7 @@ const NPCType* ZoneDatabase::GetMercType(uint32 id, uint16 raceid, uint32 client
|
||||
tmpNPCType->gender = atoi(row[7]);
|
||||
tmpNPCType->texture = atoi(row[8]);
|
||||
tmpNPCType->helmtexture = atoi(row[9]);
|
||||
tmpNPCType->attack_delay = atoi(row[10]);
|
||||
tmpNPCType->attack_delay = atoi(row[10]) * 100; // TODO: fix DB
|
||||
tmpNPCType->STR = atoi(row[11]);
|
||||
tmpNPCType->STA = atoi(row[12]);
|
||||
tmpNPCType->DEX = atoi(row[13]);
|
||||
|
||||
Reference in New Issue
Block a user