diff --git a/common/ruletypes.h b/common/ruletypes.h index e033ffd82..278701b78 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -836,7 +836,7 @@ RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will b RULE_BOOL(Bots, AllowBotEquipAnyClassGear, false, "Allows Bots to wear Equipment even if their class is not valid") RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery Ammo Consumption") RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") -RULE_INT(Bots, StackSizeMin, 100, "100 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") +RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.") RULE_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.") RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.") @@ -871,6 +871,8 @@ RULE_INT(Bots, StatusSpawnLimit, 120, "Minimum status to bypass spawn limit. Def RULE_INT(Bots, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass the anti-spam system") RULE_INT(Bots, StatusCreateLimit, 120, "Minimum status to bypass spawn limit. Default 120.") RULE_BOOL(Bots, BardsAnnounceCasts, false, "This determines whether or not Bard bots will announce that they're casting songs (Buffs, Heals, Nukes, Slows, etc.) they will always announce Mez.") +RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.") +RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/common/spdat.cpp b/common/spdat.cpp index 53bf03f6d..48b6351fb 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2782,15 +2782,16 @@ int8 SpellEffectsCount(uint16 spell_id) { if (!IsValidSpell(spell_id)) { return false; } - int8 i = 0; + + int8 x = 0; for (int i = 0; i < EFFECT_COUNT; i++) { if (!IsBlankSpellEffect(spell_id, i)) { - ++i; + ++x; } } - return i; + return x; } bool IsLichSpell(uint16 spell_id) @@ -3175,3 +3176,72 @@ bool RequiresStackCheck(uint16 spellType) { return true; } + +bool IsResistanceOnlySpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] == SE_ResistFire || + spell.effect_id[i] == SE_ResistCold || + spell.effect_id[i] == SE_ResistPoison || + spell.effect_id[i] == SE_ResistDisease || + spell.effect_id[i] == SE_ResistMagic || + spell.effect_id[i] == SE_ResistCorruption || + spell.effect_id[i] == SE_ResistAll + ) { + continue; + } + + return false; + } + + return true; +} + +bool IsDamageShieldOnlySpell(uint16 spell_id) { + if (SpellEffectsCount(spell_id) == 1 && IsEffectInSpell(spell_id, SE_DamageShield)) { + return true; + } + + return false; +} + +bool IsDamageShieldAndResistanceSpellOnly(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] == SE_DamageShield || + spell.effect_id[i] == SE_ResistFire || + spell.effect_id[i] == SE_ResistCold || + spell.effect_id[i] == SE_ResistPoison || + spell.effect_id[i] == SE_ResistDisease || + spell.effect_id[i] == SE_ResistMagic || + spell.effect_id[i] == SE_ResistCorruption || + spell.effect_id[i] == SE_ResistAll + ) { + continue; + } + + return false; + } + + return true; +} diff --git a/common/spdat.h b/common/spdat.h index 432774b95..b07bf7d96 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -1722,5 +1722,8 @@ bool IsLichSpell(uint16 spell_id); bool IsInstantHealSpell(uint32 spell_id); bool IsResurrectSpell(uint16 spell_id); bool RequiresStackCheck(uint16 spellType); +bool IsResistanceOnlySpell(uint16 spell_id); +bool IsDamageShieldOnlySpell(uint16 spell_id); +bool IsDamageShieldAndResistanceSpellOnly(uint16 spell_id); #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index 0b7fd0cc8..8052aa8d1 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -87,6 +87,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -210,6 +211,7 @@ Bot::Bot( SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -268,146 +270,146 @@ Bot::Bot( for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { switch (spell.effect_id[x1]) { - case SE_IllusionCopy: - case SE_Illusion: { - if (GetIllusionBlock()) { - break; - } - - if (spell.base_value[x1] == -1) { - if (gender == Gender::Female) { - gender = Gender::Male; - } else if (gender == Gender::Male) { - gender = Gender::Female; + case SE_IllusionCopy: + case SE_Illusion: { + if (GetIllusionBlock()) { + break; } - SendIllusionPacket( - AppearanceStruct{ - .gender_id = gender, - .race_id = GetRace(), + if (spell.base_value[x1] == -1) { + if (gender == Gender::Female) { + gender = Gender::Male; + } else if (gender == Gender::Male) { + gender = Gender::Female; } - ); - } else if (spell.base_value[x1] == -2) // WTF IS THIS - { - if (GetRace() == IKSAR || GetRace() == VAHSHIR || GetRace() <= GNOME) { + SendIllusionPacket( AppearanceStruct{ - .gender_id = GetGender(), - .helmet_texture = static_cast(spell.max_value[x1]), + .gender_id = gender, .race_id = GetRace(), + } + ); + } else if (spell.base_value[x1] == -2) // WTF IS THIS + { + if (GetRace() == IKSAR || GetRace() == VAHSHIR || GetRace() <= GNOME) { + SendIllusionPacket( + AppearanceStruct{ + .gender_id = GetGender(), + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = GetRace(), + .texture = static_cast(spell.limit_value[x1]), + } + ); + } + } else if (spell.max_value[x1] > 0) { + SendIllusionPacket( + AppearanceStruct{ + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = static_cast(spell.base_value[x1]), + .texture = static_cast(spell.limit_value[x1]), + } + ); + } else { + SendIllusionPacket( + AppearanceStruct{ + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = static_cast(spell.base_value[x1]), .texture = static_cast(spell.limit_value[x1]), } ); } - } else if (spell.max_value[x1] > 0) { - SendIllusionPacket( - AppearanceStruct{ - .helmet_texture = static_cast(spell.max_value[x1]), - .race_id = static_cast(spell.base_value[x1]), - .texture = static_cast(spell.limit_value[x1]), - } - ); - } else { - SendIllusionPacket( - AppearanceStruct{ - .helmet_texture = static_cast(spell.max_value[x1]), - .race_id = static_cast(spell.base_value[x1]), - .texture = static_cast(spell.limit_value[x1]), - } - ); - } - switch (spell.base_value[x1]) { - case OGRE: - SendAppearancePacket(AppearanceType::Size, 9); - break; - case TROLL: - SendAppearancePacket(AppearanceType::Size, 8); - break; - case VAHSHIR: - case BARBARIAN: - SendAppearancePacket(AppearanceType::Size, 7); - break; - case HALF_ELF: - case WOOD_ELF: - case DARK_ELF: - case FROGLOK: - SendAppearancePacket(AppearanceType::Size, 5); - break; - case DWARF: - SendAppearancePacket(AppearanceType::Size, 4); - break; - case HALFLING: - case GNOME: - SendAppearancePacket(AppearanceType::Size, 3); - break; - default: - SendAppearancePacket(AppearanceType::Size, 6); + switch (spell.base_value[x1]) { + case OGRE: + SendAppearancePacket(AppearanceType::Size, 9); + break; + case TROLL: + SendAppearancePacket(AppearanceType::Size, 8); + break; + case VAHSHIR: + case BARBARIAN: + SendAppearancePacket(AppearanceType::Size, 7); + break; + case HALF_ELF: + case WOOD_ELF: + case DARK_ELF: + case FROGLOK: + SendAppearancePacket(AppearanceType::Size, 5); + break; + case DWARF: + SendAppearancePacket(AppearanceType::Size, 4); + break; + case HALFLING: + case GNOME: + SendAppearancePacket(AppearanceType::Size, 3); + break; + default: + SendAppearancePacket(AppearanceType::Size, 6); + break; + } break; } - break; - } - case SE_Silence: - { - Silence(true); - break; - } - case SE_Amnesia: - { - Amnesia(true); - break; - } - case SE_DivineAura: - { - invulnerable = true; - break; - } - case SE_Invisibility2: - case SE_Invisibility: - { - invisible = true; - SendAppearancePacket(AppearanceType::Invisibility, 1); - break; - } - case SE_Levitate: - { - if (!zone->CanLevitate()) + case SE_Silence: { - SendAppearancePacket(AppearanceType::FlyMode, 0); - BuffFadeByEffect(SE_Levitate); + Silence(true); + break; } - else { - SendAppearancePacket(AppearanceType::FlyMode, 2); + case SE_Amnesia: + { + Amnesia(true); + break; + } + case SE_DivineAura: + { + invulnerable = true; + break; + } + case SE_Invisibility2: + case SE_Invisibility: + { + invisible = true; + SendAppearancePacket(AppearanceType::Invisibility, 1); + break; + } + case SE_Levitate: + { + if (!zone->CanLevitate()) + { + SendAppearancePacket(AppearanceType::FlyMode, 0); + BuffFadeByEffect(SE_Levitate); + } + else { + SendAppearancePacket(AppearanceType::FlyMode, 2); + } + break; + } + case SE_InvisVsUndead2: + case SE_InvisVsUndead: + { + invisible_undead = true; + break; + } + case SE_InvisVsAnimals: + { + invisible_animals = true; + break; + } + case SE_AddMeleeProc: + case SE_WeaponProc: + { + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); + break; + } + case SE_DefensiveProc: + { + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + break; + } + case SE_RangedProc: + { + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + break; } - break; - } - case SE_InvisVsUndead2: - case SE_InvisVsUndead: - { - invisible_undead = true; - break; - } - case SE_InvisVsAnimals: - { - invisible_animals = true; - break; - } - case SE_AddMeleeProc: - case SE_WeaponProc: - { - AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); - break; - } - case SE_DefensiveProc: - { - AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); - break; - } - case SE_RangedProc: - { - AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); - break; - } } } } @@ -1797,7 +1799,9 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { ) ) { if (!Ammo || ammoItem->GetCharges() < 1) { - GetOwner()->Message(Chat::Yellow, "I do not have enough any ammo."); + if (!GetCombatRoundForAlerts()) { + GetOwner()->Message(Chat::Yellow, "I do not have enough any ammo."); + } } return; @@ -1917,20 +1921,20 @@ bool Bot::CheckTripleAttack() ) ) { switch (GetClass()) { - case Class::Warrior: - chance = RuleI(Combat, ClassicTripleAttackChanceWarrior); - break; - case Class::Ranger: - chance = RuleI(Combat, ClassicTripleAttackChanceRanger); - break; - case Class::Monk: - chance = RuleI(Combat, ClassicTripleAttackChanceMonk); - break; - case Class::Berserker: - chance = RuleI(Combat, ClassicTripleAttackChanceBerserker); - break; - default: - break; + case Class::Warrior: + chance = RuleI(Combat, ClassicTripleAttackChanceWarrior); + break; + case Class::Ranger: + chance = RuleI(Combat, ClassicTripleAttackChanceRanger); + break; + case Class::Monk: + chance = RuleI(Combat, ClassicTripleAttackChanceMonk); + break; + case Class::Berserker: + chance = RuleI(Combat, ClassicTripleAttackChanceBerserker); + break; + default: + break; } } } @@ -2014,15 +2018,26 @@ void Bot::AI_Process() return; } - auto leash_owner = SetLeashOwner(bot_owner, bot_group, raid, r_group); + Client* leash_owner = bot_owner; if (!leash_owner) { return; } - SetFollowID(leash_owner->GetID()); + Mob* follow_mob = nullptr; - auto follow_mob = SetFollowMob(leash_owner); + if (!GetFollowID()) { + follow_mob = leash_owner; + } + else { + follow_mob = entity_list.GetMob(GetFollowID()); + + if (!follow_mob || !IsInGroupOrRaid(follow_mob)) { + follow_mob = leash_owner; + } + } + + SetFollowID(follow_mob->GetID()); SetBerserkState(); @@ -2094,6 +2109,7 @@ void Bot::AI_Process() // ATTACKING FLAG (HATE VALIDATION) if (GetAttackingFlag() && tar->CheckAggro(this)) { + SetCombatRoundForAlerts(true); SetAttackingFlag(false); } @@ -2264,6 +2280,7 @@ void Bot::AI_Process() } else { // Out-of-combat behavior SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); if (!bot_owner->GetBotPulling()) { @@ -2408,6 +2425,7 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { AddToHateList(hater, 1); SetTarget(hater); SetAttackingFlag(); + SetCombatRoundForAlerts(); if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { GetPet()->AddToHateList(hater, 1); @@ -2869,6 +2887,7 @@ bool Bot::IsValidTarget( SetTarget(nullptr); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); if (PULLING_BOT) { @@ -2902,6 +2921,7 @@ Mob* Bot::GetBotTarget(Client* bot_owner) WipeHateList(); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); if (PULLING_BOT) { @@ -3120,6 +3140,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { } SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -3134,6 +3155,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { WipeHateList(); AddToHateList(attack_target, 1); SetTarget(attack_target); + SetCombatRoundForAlerts(); SetAttackingFlag(); if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { GetPet()->WipeHateList(); @@ -3146,6 +3168,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -3838,7 +3861,6 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) { if (bot && group->AddMember(bot)) { if (group->GetLeader()) { - bot->SetFollowID(group->GetLeader()->GetID()); // Need to send this only once when a group is formed with a bot so the client knows it is also the group leader if (group->GroupCount() == 2 && group->GetLeader()->IsClient()) { group->UpdateGroupAAs(); @@ -4621,15 +4643,15 @@ float Bot::GetProcChances(float ProcBonus, uint16 hand) { float ProcChance = 0.0f; uint32 weapon_speed = 0; switch (hand) { - case EQ::invslot::slotPrimary: - weapon_speed = attack_timer.GetDuration(); - break; - case EQ::invslot::slotSecondary: - weapon_speed = attack_dw_timer.GetDuration(); - break; - case EQ::invslot::slotRange: - weapon_speed = ranged_timer.GetDuration(); - break; + case EQ::invslot::slotPrimary: + weapon_speed = attack_timer.GetDuration(); + break; + case EQ::invslot::slotSecondary: + weapon_speed = attack_dw_timer.GetDuration(); + break; + case EQ::invslot::slotRange: + weapon_speed = ranged_timer.GetDuration(); + break; } if (weapon_speed < RuleI(Combat, MinHastedDelay)) @@ -4738,84 +4760,84 @@ int Bot::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) int base = EQ::skills::GetBaseDamage(skill); auto skill_level = GetSkill(skill); switch (skill) { - case EQ::skills::SkillDragonPunch: - case EQ::skills::SkillEagleStrike: - case EQ::skills::SkillTigerClaw: - if (skill_level >= 25) - base++; - if (skill_level >= 75) - base++; - if (skill_level >= 125) - base++; - if (skill_level >= 175) - base++; - return base; - case EQ::skills::SkillFrenzy: - if (GetBotItem(EQ::invslot::slotPrimary)) { - if (GetLevel() > 15) - base += GetLevel() - 15; - if (base > 23) - base = 23; - if (GetLevel() > 50) - base += 2; - if (GetLevel() > 54) + case EQ::skills::SkillDragonPunch: + case EQ::skills::SkillEagleStrike: + case EQ::skills::SkillTigerClaw: + if (skill_level >= 25) base++; - if (GetLevel() > 59) + if (skill_level >= 75) base++; - } - return base; - case EQ::skills::SkillFlyingKick: { - float skill_bonus = skill_level / 9.0f; - float ac_bonus = 0.0f; - auto inst = GetBotItem(EQ::invslot::slotFeet); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillKick: { - float skill_bonus = skill_level / 10.0f; - float ac_bonus = 0.0f; - auto inst = GetBotItem(EQ::invslot::slotFeet); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillBash: { - float skill_bonus = skill_level / 10.0f; - float ac_bonus = 0.0f; - const EQ::ItemInstance *inst = nullptr; - if (HasShieldEquipped()) - inst = GetBotItem(EQ::invslot::slotSecondary); - else if (HasTwoHanderEquipped()) - inst = GetBotItem(EQ::invslot::slotPrimary); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillBackstab: { - float skill_bonus = static_cast(skill_level) * 0.02f; - auto inst = GetBotItem(EQ::invslot::slotPrimary); - if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQ::item::ItemType1HPiercing) { - base = inst->GetItemBackstabDamage(true); - if (!inst->GetItemBackstabDamage()) - base += inst->GetItemWeaponDamage(true); - if (target) { - if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) - base += target->ResistElementalWeaponDmg(inst); - if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) - base += target->CheckBaneDamage(inst); + if (skill_level >= 125) + base++; + if (skill_level >= 175) + base++; + return base; + case EQ::skills::SkillFrenzy: + if (GetBotItem(EQ::invslot::slotPrimary)) { + if (GetLevel() > 15) + base += GetLevel() - 15; + if (base > 23) + base = 23; + if (GetLevel() > 50) + base += 2; + if (GetLevel() > 54) + base++; + if (GetLevel() > 59) + base++; } + return base; + case EQ::skills::SkillFlyingKick: { + float skill_bonus = skill_level / 9.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQ::invslot::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); } - return static_cast(static_cast(base) * (skill_bonus + 2.0f)); - } - default: - return 0; + case EQ::skills::SkillKick: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQ::invslot::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQ::skills::SkillBash: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + const EQ::ItemInstance *inst = nullptr; + if (HasShieldEquipped()) + inst = GetBotItem(EQ::invslot::slotSecondary); + else if (HasTwoHanderEquipped()) + inst = GetBotItem(EQ::invslot::slotPrimary); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQ::skills::SkillBackstab: { + float skill_bonus = static_cast(skill_level) * 0.02f; + auto inst = GetBotItem(EQ::invslot::slotPrimary); + if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQ::item::ItemType1HPiercing) { + base = inst->GetItemBackstabDamage(true); + if (!inst->GetItemBackstabDamage()) + base += inst->GetItemWeaponDamage(true); + if (target) { + if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) + base += target->ResistElementalWeaponDmg(inst); + if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) + base += target->CheckBaneDamage(inst); + } + } + return static_cast(static_cast(base) * (skill_bonus + 2.0f)); + } + default: + return 0; } } @@ -4882,7 +4904,10 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { botpiercer = inst->GetItem(); if (!botpiercer || (botpiercer->ItemType != EQ::item::ItemType1HPiercing)) { - BotGroupSay(this, "I can't backstab with this weapon!"); + if (!GetCombatRoundForAlerts()) { + BotGroupSay(this, "I can't backstab with this weapon!"); + } + return; } @@ -5818,7 +5843,7 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe GetClass() != Class::Bard && (IsGrouped() || (IsRaidGrouped() && GetRaid()->GetGroup(GetCleanName()) != RAID_GROUPLESS)) && (spellTarget->IsBot() || spellTarget->IsClient()) && - (RuleB(Bots, GroupBuffing) || RuleB(Bots, CrossRaidBuffingAndHealing)) + (RuleB(Bots, GroupBuffing) || RuleB(Bots, RaidBuffing)) ) { bool noGroupSpell = false; uint16 thespell = spell_id; @@ -5847,7 +5872,8 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe if (!noGroupSpell) { std::vector v; - if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + + if (RuleB(Bots, RaidBuffing)) { v = GatherSpellTargets(true); } else { @@ -5906,8 +5932,20 @@ bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spel } if (spellTarget->IsOfClientBotMerc()) { - const std::vector v = GatherGroupSpellTargets(spellTarget); + std::vector v; + + if (RuleB(Bots, RaidBuffing)) { + v = GatherSpellTargets(true); + } + else { + v = GatherGroupSpellTargets(spellTarget); + } + for (Mob* m : v) { + if (m == this && spellTarget != this) { + continue; + } + SpellOnTarget(spell_id, m); if (m->GetPetID() && (!RuleB(Bots, RequirePetAffinity) || m->HasPetAffinity())) { @@ -6595,40 +6633,6 @@ void Bot::DoEnduranceUpkeep() { } void Bot::Camp(bool save_to_database) { - - if (RuleB(Bots, PreventBotCampOnFD) && GetBotOwner()->GetFeigned()) { - GetBotOwner()->Message(Chat::White, "You cannot camp bots while feigned."); - return; - } - - if (RuleB(Bots, PreventBotCampOnEngaged)) { - Raid* raid = entity_list.GetRaidByClient(GetBotOwner()->CastToClient()); - if (raid && raid->IsEngaged()) { - GetBotOwner()->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); - return; - } - - auto* owner_group = GetBotOwner()->GetGroup(); - if (owner_group) { - std::list member_list; - owner_group->GetClientList(member_list); - member_list.remove(nullptr); - - for (auto member_iter : member_list) { - if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - GetBotOwner()->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); - return; - } - } - } - else { - if (GetBotOwner()->CastToClient()->GetAggroCount() > 0) { - GetBotOwner()->Message(Chat::White, "You cannot spawn bots while you are engaged,"); - return; - } - } - } - Sit(); LeaveHealRotationMemberPool(); @@ -6700,107 +6704,107 @@ void Bot::UpdateGroupCastingRoles(const Group* group, bool disband) // GroupHealer switch (iter->GetClass()) { - case Class::Cleric: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - break; - default: + case Class::Cleric: + if (!healer) healer = iter; - } + else + switch (healer->GetClass()) { + case Class::Cleric: + break; + default: + healer = iter; + } - break; - case Class::Druid: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - case Class::Druid: - break; - default: + break; + case Class::Druid: + if (!healer) healer = iter; - } - break; - case Class::Shaman: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - case Class::Druid: - case Class::Shaman: - break; - default: + else + switch (healer->GetClass()) { + case Class::Cleric: + case Class::Druid: + break; + default: + healer = iter; + } + break; + case Class::Shaman: + if (!healer) healer = iter; - } - break; - case Class::Paladin: - case Class::Ranger: - case Class::Beastlord: - if (!healer) - healer = iter; - break; - default: - break; - } + else + switch (healer->GetClass()) { + case Class::Cleric: + case Class::Druid: + case Class::Shaman: + break; + default: + healer = iter; + } + break; + case Class::Paladin: + case Class::Ranger: + case Class::Beastlord: + if (!healer) + healer = iter; + break; + default: + break; + } - // GroupSlower - switch (iter->GetClass()) { - case Class::Shaman: - if (!slower) - slower = iter; - else - switch (slower->GetClass()) { + // GroupSlower + switch (iter->GetClass()) { case Class::Shaman: + if (!slower) + slower = iter; + else + switch (slower->GetClass()) { + case Class::Shaman: + break; + default: + slower = iter; + } break; - default: - slower = iter; - } - break; - case Class::Enchanter: - if (!slower) - slower = iter; - else - switch (slower->GetClass()) { - case Class::Shaman: case Class::Enchanter: + if (!slower) + slower = iter; + else + switch (slower->GetClass()) { + case Class::Shaman: + case Class::Enchanter: + break; + default: + slower = iter; + } + break; + case Class::Beastlord: + if (!slower) + slower = iter; break; default: - slower = iter; - } - break; - case Class::Beastlord: - if (!slower) - slower = iter; - break; - default: - break; - } + break; + } - // GroupNuker - switch (iter->GetClass()) { - // wizard - // magician - // necromancer - // enchanter - // druid - // cleric - // shaman - // shadowknight - // paladin - // ranger - // beastlord - default: - break; - } + // GroupNuker + switch (iter->GetClass()) { + // wizard + // magician + // necromancer + // enchanter + // druid + // cleric + // shaman + // shadowknight + // paladin + // ranger + // beastlord + default: + break; + } - // GroupDoter - switch (iter->GetClass()) { - default: - break; + // GroupDoter + switch (iter->GetClass()) { + default: + break; } } @@ -6828,11 +6832,26 @@ Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, const std::string& botName void Bot::ProcessBotGroupInvite(Client* c, std::string const& botName) { if (c && !c->HasRaid()) { - Bot* invitedBot = GetBotByBotClientOwnerAndBotName(c, botName); + Bot* invitedBot = entity_list.GetBotByBotName(botName); + if (!invitedBot) { return; } + if ( + invitedBot->GetBotOwnerCharacterID() != c->CharacterID() && + !( + c->GetGroup() && + !invitedBot->HasGroup() && + invitedBot->GetOwner()->HasGroup() && + c->GetGroup() == invitedBot->GetOwner()->GetGroup() + ) + ) { + c->Message(Chat::Red, "%s's owner needs to be in your group to be able to invite them.", invitedBot->GetCleanName()); + + return; + } + if (!invitedBot->HasGroup() && !invitedBot->HasRaid()) { if (!c->IsGrouped()) { auto g = new Group(c); @@ -7074,7 +7093,6 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl else { v = caster->GatherGroupSpellTargets(); } - v = caster->GatherSpellTargets(); } Mob* tar = nullptr; @@ -8433,17 +8451,19 @@ int32 Bot::CalcItemATKCap() return RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; } -bool Bot::CheckSpawnConditions(Client* c) { +bool Bot::CheckCampSpawnConditions(Client* c) { if (RuleB(Bots, PreventBotSpawnOnFD) && c->GetFeigned()) { - c->Message(Chat::White, "You cannot spawn bots while feigned."); + c->Message(Chat::White, "You cannot camp or spawn bots while feigned."); + return false; } if (RuleB(Bots, PreventBotSpawnOnEngaged)) { Raid* raid = entity_list.GetRaidByClient(c); if (raid && raid->IsEngaged()) { - c->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); + c->Message(Chat::White, "You cannot camp or spawn bots while your raid is engaged."); + return false; } @@ -8455,14 +8475,14 @@ bool Bot::CheckSpawnConditions(Client* c) { for (auto member_iter : member_list) { if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); + c->Message(Chat::White, "You cannot camp or spawn bots while your group is engaged,"); return false; } } } else { if (c->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); + c->Message(Chat::White, "You cannot camp or spawn bots while you are engaged,"); return false; } } @@ -9397,6 +9417,10 @@ bool Bot::CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrecheck return false; } + if (IsBeneficialSpell(spellid) && tar->BuffCount() >= tar->GetCurrentBuffSlots() && CalcBuffDuration(this, tar, spellid) != 0) { + return false; + } + LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme if (!CanCastSpellType(spellType, spellid, tar)) { return false; @@ -9426,8 +9450,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { spells[spellid].target_type == ST_Pet || (tar == this && spells[spellid].target_type != ST_TargetsTarget) || spells[spellid].target_type == ST_Group || - spells[spellid].target_type == ST_GroupTeleport //|| - //(botClass == Class::Bard && spells[spellid].target_type == ST_AEBard) //TODO bot rewrite - is this needed? + spells[spellid].target_type == ST_GroupTeleport ) ) { LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme @@ -9471,9 +9494,9 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && ( IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || - (SpellEffectsCount(spellid) == 1 && IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR) - ) - ) { + (SpellEffectsCount(spellid) == 1 && (IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR)) + ) + ) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme return false; } @@ -9516,18 +9539,6 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { case BotSpellTypes::PreCombatBuffSong: case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: - //switch (spells[spellid].target_type) { //TODO bot rewrite - is this needed? - // case ST_AEBard: - // case ST_AECaster: - // case ST_GroupTeleport: - // case ST_Group: - // case ST_Self: - // break; - // default: - // LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme - // return false; - //} - if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spellid)) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme return false; @@ -9540,9 +9551,9 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && ( IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || - (SpellEffectsCount(spellid) == 1 && IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR) - ) - ) { + (SpellEffectsCount(spellid) == 1 && (IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR)) + ) + ) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme return false; } @@ -9597,7 +9608,15 @@ bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { return true; } - const std::vector v = GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = GatherSpellTargets(true); + } + else { + v = GatherGroupSpellTargets(); + } + for (Mob* m : v) { if ( m->IsBot() && @@ -9618,9 +9637,10 @@ bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { m->CastingSpellID() == spellid ) { - const std::vector v = GatherGroupSpellTargets(); - for (Mob* m : v) { - if (entity_list.GetMobID(m->CastToBot()->casting_spell_targetid) == entity_list.GetMobID(m->GetID())) { + std::vector x = GatherGroupSpellTargets(); + + for (Mob* t : x) { + if (entity_list.GetMobID(t->CastToBot()->casting_spell_targetid) == entity_list.GetMobID(t->GetID())) { return true; } } @@ -10264,60 +10284,56 @@ uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass) { priority = 10; break; - case BotSpellTypes::InCombatBuff: // this has a check at the end to decrement everything below if it's not a SK (SK use InCombatBuffs as it is their hate line so it's not use when Idle) + case BotSpellTypes::PetVeryFastHeals: priority = 11; break; - case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetFastHeals: priority = 12; break; - case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetRegularHeals: priority = 13; break; - case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: priority = 14; break; - case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetHoTHeals: priority = 15; break; - case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Pet: priority = 16; break; - case BotSpellTypes::Pet: + case BotSpellTypes::Buff: priority = 17; break; - case BotSpellTypes::Buff: + case BotSpellTypes::OutOfCombatBuffSong: priority = 18; break; - case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::ResistBuffs: priority = 19; break; - case BotSpellTypes::ResistBuffs: + case BotSpellTypes::DamageShields: priority = 20; break; - case BotSpellTypes::DamageShields: + case BotSpellTypes::PetBuffs: priority = 21; break; - case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: priority = 22; - break; - case BotSpellTypes::PreCombatBuff: - priority = 23; - break; case BotSpellTypes::PreCombatBuffSong: - priority = 24; + priority = 23; break; default: @@ -10326,14 +10342,6 @@ uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass) { break; } - if ( - priority >= 11 && - botClass && botClass == Class::ShadowKnight && - spellType != BotSpellTypes::InCombatBuff - ) { - --priority; - } - return priority; } @@ -10839,39 +10847,19 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid) { switch (spellType) { //TODO bot rewrite - fix Buff/ResistBuff case BotSpellTypes::Buff: - if (IsEffectInSpell(spellid, SE_DamageShield)) { - return false; - } - - if ( - IsEffectInSpell(spellid, SE_ResistMagic) || - IsEffectInSpell(spellid, SE_ResistFire) || - IsEffectInSpell(spellid, SE_ResistCold) || - IsEffectInSpell(spellid, SE_ResistPoison) || - IsEffectInSpell(spellid, SE_ResistDisease) || - IsEffectInSpell(spellid, SE_ResistCorruption) || - IsEffectInSpell(spellid, SE_ResistAll) - ) { + if (IsResistanceOnlySpell(spellid) || IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { return false; } return true; case BotSpellTypes::ResistBuffs: - if ( - IsEffectInSpell(spellid, SE_ResistMagic) || - IsEffectInSpell(spellid, SE_ResistFire) || - IsEffectInSpell(spellid, SE_ResistCold) || - IsEffectInSpell(spellid, SE_ResistPoison) || - IsEffectInSpell(spellid, SE_ResistDisease) || - IsEffectInSpell(spellid, SE_ResistCorruption) || - IsEffectInSpell(spellid, SE_ResistAll) - ) { + if (IsResistanceOnlySpell(spellid)) { return true; } return false; case BotSpellTypes::DamageShields: - if (IsEffectInSpell(spellid, SE_DamageShield)) { + if (IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { return true; } @@ -11086,55 +11074,6 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) { return true; } -bool Bot::IsInGroupOrRaid(bool announce) { - if (!GetOwner()) { - return false; - } - - Mob* c = GetOwner(); - - if ( - (!GetRaid() && !GetGroup()) || - (!c->GetRaid() && !c->GetGroup()) - ) { - return false; - } - - if ( - c->GetRaid() && - ( - !GetRaid() || - c->GetRaid() != GetRaid() || - GetRaid()->GetGroup(GetCleanName()) == RAID_GROUPLESS - ) - ) { - return false; - } - - if ( - c->GetGroup() && - ( - !GetGroup() || - c->GetGroup() != GetGroup() - ) - ) { - return false; - } - - - if (announce) { - c->Message( - Chat::Yellow, - fmt::format( - "{} says, 'I am not currently in your group or raid.", - GetCleanName() - ).c_str() - ); - } - - return true; -} - bool Bot::HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob* tar) { int spellRange = botCaster->GetActSpellRange(spellid, spells[spellid].range); int spellAERange = botCaster->GetActSpellRange(spellid, spells[spellid].aoe_range); diff --git a/zone/bot.h b/zone/bot.h index 97d987e56..5836af133 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -278,6 +278,7 @@ public: void SetHoldFlag(bool flag = true) { m_hold_flag = flag; } bool GetAttackFlag() const { return m_attack_flag; } void SetAttackFlag(bool flag = true) { m_attack_flag = flag; } + bool GetCombatRoundForAlerts() const { return m_combat_round_alert_flag; } bool GetAttackingFlag() const { return m_attacking_flag; } bool GetPullFlag() const { return m_pull_flag; } void SetPullFlag(bool flag = true) { m_pull_flag = flag; } @@ -403,7 +404,7 @@ public: void SetGuardMode(); void SetHoldMode(); - bool IsValidSpellRange(uint16 spell_id, Mob const* tar); + bool IsValidSpellRange(uint16 spell_id, Mob* tar); // Bot AI Methods void AI_Bot_Init(); @@ -519,7 +520,6 @@ public: void SetHasLoS(bool hasLoS) { _hasLoS = hasLoS; } bool HasLoS() const { return _hasLoS; } - bool IsInGroupOrRaid(bool announce = false); void SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, std::string arg2, bool helpPrompt = false); std::list GetSpellTypesPrioritized(uint8 priorityType); @@ -981,7 +981,7 @@ public: bool CheckDoubleRangedAttack(); // Public "Refactor" Methods - static bool CheckSpawnConditions(Client* c); + static bool CheckCampSpawnConditions(Client* c); inline bool CommandedDoSpellCast(int32 i, Mob* tar, int32 mana_cost) { return AIDoSpellCast(i, tar, mana_cost); } @@ -1047,6 +1047,7 @@ private: bool m_guard_flag; bool m_hold_flag; bool m_attack_flag; + bool m_combat_round_alert_flag; bool m_attacking_flag; bool m_pull_flag; bool m_pulling_flag; @@ -1104,6 +1105,7 @@ private: int32 GenerateBaseManaPoints(); void GenerateSpecialAttacks(); void SetBotID(uint32 botID); + void SetCombatRoundForAlerts(bool flag = true) { m_combat_round_alert_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; } diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index 7da91504d..ccf1e51ff 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -30,12 +30,19 @@ void bot_command_bot(Client *c, const Seperator *sep) void bot_command_camp(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) + if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } + + if (!Bot::CheckCampSpawnConditions(c)) { + return; + } + const int ab_mask = ActionableBots::ABM_Type1; std::string class_race_arg = sep->arg[1]; @@ -49,8 +56,14 @@ void bot_command_camp(Client *c, const Seperator *sep) return; } - for (auto bot_iter : sbl) + uint16 campCount; + + for (auto bot_iter : sbl) { bot_iter->Camp(); + ++campCount; + } + + c->Message(Chat::White, "%i of your bots have been camped.", campCount); } void bot_command_clone(Client *c, const Seperator *sep) @@ -428,6 +441,10 @@ void bot_command_delete(Client *c, const Seperator *sep) return; } + if (!Bot::CheckCampSpawnConditions(c)) { + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); if (!my_bot) { c->Message(Chat::White, "You must a bot that you own to use this command"); @@ -829,7 +846,7 @@ void bot_command_spawn(Client *c, const Seperator *sep) return; } - if (!Bot::CheckSpawnConditions(c)) { + if (!Bot::CheckCampSpawnConditions(c)) { return; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 77d3ab23b..70b6e659c 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -174,7 +174,7 @@ void bot_command_cast(Client* c, const Seperator* sep) if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { c->Message(Chat::Yellow, "[%s] is an invalid target.", tar->GetCleanName()); return; - } + } } } LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme @@ -223,7 +223,7 @@ void bot_command_cast(Client* c, const Seperator* sep) Bot* firstFound = nullptr; for (auto bot_iter : sbl) { - if (!bot_iter->IsInGroupOrRaid()) { + if (!bot_iter->IsInGroupOrRaid(c)) { continue; } @@ -248,6 +248,14 @@ void bot_command_cast(Client* c, const Seperator* sep) continue; } + if ( + BOT_SPELL_TYPES_BENEFICIAL(spellType) && + !RuleB(Bots, CrossRaidBuffingAndHealing) && + !bot_iter->IsInGroupOrRaid(newTar, true) + ) { + continue; + } + if (BOT_SPELL_TYPES_DETRIMENTAL(spellType, bot_iter->GetClass()) && !bot_iter->IsAttackAllowed(newTar)) { bot_iter->BotGroupSay( bot_iter, diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp index 3ff0cb44b..9e7c33eaf 100644 --- a/zone/bot_commands/follow.cpp +++ b/zone/bot_commands/follow.cpp @@ -2,47 +2,112 @@ void bot_command_follow(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | ownerraid | namesgroup | mmr | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); - c->Message(Chat::White, "usage: %s chain", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) { return; } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Sets bots of your choosing to follow your target, view their current following state or reset their following state." + }; + + std::vector notes = + { + "- You can only follow players, bots or mercenaries belonging to your group or raid." + }; + + std::vector example_format = + { + fmt::format( + "{} [optional] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all Clerics to follow your target:", + fmt::format( + "{} byclass {}", + sep->arg[0], + Class::Cleric + ) + }; + std::vector examples_two = + { + "To check the current state of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + std::vector examples_three = + { + "To reset all bots:", + fmt::format( + "{} reset spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + const int ab_mask = ActionableBots::ABM_Type2; + bool chain = false; bool reset = false; + bool currentCheck = false; int ab_arg = 1; - int name_arg = 2; Mob* target_mob = nullptr; std::string optional_arg = sep->arg[1]; - if (!optional_arg.compare("chain")) { - - auto chain_count = helper_bot_follow_option_chain(c); - c->Message(Chat::White, "%i of your bots %s now chain following you", chain_count, (chain_count == 1 ? "is" : "are")); - - return; - } - else if (!optional_arg.compare("reset")) { + + if (!optional_arg.compare("reset")) { + target_mob = c; reset = true; ++ab_arg; - ++name_arg ; + } + else if (!optional_arg.compare("current")) { + currentCheck = true; + ++ab_arg; } else { - //target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single); target_mob = c->GetTarget(); - if (!target_mob) { - c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); + + if (!target_mob || !target_mob->IsOfClientBotMerc() || !c->IsInGroupOrRaid(target_mob)) { + c->Message(Chat::Yellow, "You must a friendly player, bot or merc within your group or raid to use this command"); return; } - else if (!target_mob->IsBot() && !target_mob->IsClient()) { - c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); - return; - } - else if ((target_mob->GetGroup() && target_mob->GetGroup() != c->GetGroup()) || (target_mob->GetRaid() && target_mob->GetRaid() != c->GetRaid())) { - c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); - return; + + if (!optional_arg.compare("chain")) { + chain = true; + ++ab_arg; } } @@ -53,12 +118,66 @@ void bot_command_follow(Client* c, const Seperator* sep) } std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { + //if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg] : nullptr, class_race_check ? atoi(sep->arg[ab_arg]) : 0) == ActionableBots::ABT_None) { + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } sbl.remove(nullptr); + + auto botCount = sbl.size(); + Mob* follow_mob = nullptr; + std::list chainList; + std::list::const_iterator it = chainList.begin(); + uint16 count = 0; for (auto bot_iter : sbl) { + if (currentCheck) { + follow_mob = entity_list.GetMob(bot_iter->GetFollowID()); + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I am currently following {}.'", + bot_iter->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "no one" + ).c_str() + ); + + if (!follow_mob && RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(28); + } + + continue; + } + + if (bot_iter == target_mob) { + if (botCount == 1) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I cannot follow myself, you want me to run circles?", + bot_iter->GetCleanName() + ).c_str() + ); + + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(60); + } + + return; + } + + bot_iter->WipeHateList(); + --botCount; + + continue; + } + + if (!bot_iter->IsInGroupOrRaid(target_mob)) { + --botCount; + + continue; + } + bot_iter->WipeHateList(); if (!bot_iter->GetGroup() && !bot_iter->GetRaid()) { @@ -71,57 +190,52 @@ void bot_command_follow(Client* c, const Seperator* sep) bot_iter->SetManualFollow(false); } else { - if (target_mob->IsGrouped() || target_mob->IsRaidGrouped()) { - bot_iter->SetFollowID(target_mob->GetID()); - bot_iter->SetManualFollow(true); - } - else if (bot_iter == target_mob) { - bot_iter->SetFollowID(c->GetID()); - bot_iter->SetManualFollow(true); + if (chain) { + Mob* nextTar = target_mob; + + if (count > 0) { + nextTar = *it; + + if (!nextTar) { + nextTar = target_mob; + } + } + LogTestDebug("{} is now following {}.", bot_iter->GetCleanName(), nextTar->GetCleanName()); //deleteme + chainList.push_back(bot_iter); + ++it; + ++count; + bot_iter->SetFollowID(nextTar->GetID()); } else { - bot_iter->SetFollowID(0); - bot_iter->SetManualFollow(false); + bot_iter->SetFollowID(target_mob->GetID()); } + + bot_iter->SetManualFollow(true); } } - //auto my_group = bot_iter->GetGroup(); - //if (my_group) { - // if (reset) { - // if (!my_group->GetLeader() || my_group->GetLeader() == bot_iter) - // bot_iter->SetFollowID(c->GetID()); - // else - // bot_iter->SetFollowID(my_group->GetLeader()->GetID()); - // - // bot_iter->SetManualFollow(false); - // } - // else { - // if (bot_iter == target_mob) - // bot_iter->SetFollowID(c->GetID()); - // else - // bot_iter->SetFollowID(target_mob->GetID()); - // - // bot_iter->SetManualFollow(true); - // } - //} - //else { - // bot_iter->SetFollowID(0); - // bot_iter->SetManualFollow(false); - //} - if (!bot_iter->GetPet()) + + if (!bot_iter->GetPet()) { continue; + } bot_iter->GetPet()->WipeHateList(); bot_iter->GetPet()->SetFollowID(bot_iter->GetID()); } - Mob* follow_mob = nullptr; - if (sbl.size() == 1) { + if (currentCheck || !botCount) { + return; + } + + follow_mob = target_mob; + + if (botCount == 1) { follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + c->Message( - Chat::White, + Chat::Green, fmt::format( - "Following {}.", + "{} says, 'Following {}.'", + sbl.front()->GetCleanName(), follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); @@ -129,22 +243,20 @@ void bot_command_follow(Client* c, const Seperator* sep) else { if (reset) { c->Message( - Chat::White, + Chat::Green, fmt::format( "{} of your bots are following you.", - sbl.size() + botCount ).c_str() ); } else { - if (!sbl.empty()) { - follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); - } c->Message( - Chat::White, + Chat::Green, fmt::format( - "{} of your bots are following {}.", - sbl.size(), + "{} of your bots are {} {}.", + botCount, + chain ? "chain following" : "following", follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); diff --git a/zone/bot_commands/item_use.cpp b/zone/bot_commands/item_use.cpp index 1640ff0e3..975d30b90 100644 --- a/zone/bot_commands/item_use.cpp +++ b/zone/bot_commands/item_use.cpp @@ -179,7 +179,9 @@ void bot_command_item_use(Client* c, const Seperator* sep) ).c_str() ); - bot_iter->DoAnim(29); + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(29); + } } else if (!equipped_item) { c->Message( @@ -204,7 +206,9 @@ void bot_command_item_use(Client* c, const Seperator* sep) ).c_str() ); - bot_iter->DoAnim(29); + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(29); + } } } } diff --git a/zone/bot_commands/mesmerize.cpp b/zone/bot_commands/mesmerize.cpp index 1f51a70d3..d86fba9f7 100644 --- a/zone/bot_commands/mesmerize.cpp +++ b/zone/bot_commands/mesmerize.cpp @@ -20,7 +20,7 @@ void bot_command_mesmerize(Client *c, const Seperator *sep) continue; } - if (!bot_iter->IsInGroupOrRaid()) { + if (!bot_iter->IsInGroupOrRaid(c)) { continue; } diff --git a/zone/bot_raid.cpp b/zone/bot_raid.cpp index 25d98ef86..458ae03c4 100644 --- a/zone/bot_raid.cpp +++ b/zone/bot_raid.cpp @@ -176,13 +176,7 @@ void Bot::ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite) { // 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() - ); + invitor->Message(Chat::Red, "%s's owner needs to be in your raid to be able to invite them.", invitee->GetCleanName()); return; } @@ -257,10 +251,6 @@ void Bot::CreateBotRaid(Mob* invitee, Client* invitor, bool group_invite, Raid* } else { raid->AddBot(b); } - - if (new_raid) { - invitee->SetFollowID(invitor->GetID()); - } } } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index ee3aaf3a3..d7e633822 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -994,11 +994,28 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa continue; } + if ( + !RuleB(Bots, EnableBotTGB) && + IsGroupSpell(botSpellList[i].spellid) && + !IsTGBCompatibleSpell(botSpellList[i].spellid) && + !botCaster->IsInGroupOrRaid(tar, true) + ) { + continue; + } + if ( ( - !botCaster->IsCommandedSpell() || (botCaster->IsCommandedSpell() && (spellType != BotSpellTypes::Mez && spellType != BotSpellTypes::AEMez)) + !botCaster->IsCommandedSpell() || + ( + botCaster->IsCommandedSpell() && + (spellType != BotSpellTypes::Mez && spellType != BotSpellTypes::AEMez) + ) + ) + && + ( + !IsPBAESpell(botSpellList[i].spellid) && + !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsAEBotSpellType(spellType)) ) - && (!IsPBAESpell(botSpellList[i].spellid) && !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsGroupBotSpellType(spellType))) // TODO bot rewrite - needed for ae spells? ) { continue; } @@ -1232,7 +1249,15 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster, Mob* tar, uint16 spell if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CurrentHP); - const std::vector v = botCaster->GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = botCaster->GatherSpellTargets(true); + } + else { + v = botCaster->GatherGroupSpellTargets(); + } + int targetCount = 0; for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { @@ -1273,7 +1298,15 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster, Mob* tar, uint if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_HealOverTime); - const std::vector v = botCaster->GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = botCaster->GatherSpellTargets(true); + } + else { + v = botCaster->GatherGroupSpellTargets(); + } + int targetCount = 0; for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { @@ -1314,7 +1347,15 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster, Mob* tar, uint if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CompleteHeal); - const std::vector v = botCaster->GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = botCaster->GatherSpellTargets(true); + } + else { + v = botCaster->GatherGroupSpellTargets(); + } + int targetCount = 0; for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { @@ -1629,7 +1670,7 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType continue; } - if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsGroupBotSpellType(spellType))) { + if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsAEBotSpellType(spellType))) { continue; } @@ -1679,7 +1720,7 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType continue; } - if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsGroupBotSpellType(spellType))) { + if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsAEBotSpellType(spellType))) { continue; } @@ -2583,19 +2624,27 @@ bool Bot::HasBotSpellEntry(uint16 spellid) { return false; } -bool Bot::IsValidSpellRange(uint16 spell_id, Mob const* tar) { +bool Bot::IsValidSpellRange(uint16 spell_id, Mob* tar) { if (!IsValidSpell(spell_id)) { return false; } if (tar) { - int spellrange = (GetActSpellRange(spell_id, spells[spell_id].range) * GetActSpellRange(spell_id, spells[spell_id].range)); - if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { + float range = spells[spell_id].range; + + if (tar != this) { + range += GetRangeDistTargetSizeMod(tar); + } + + range = GetActSpellRange(spell_id, range); + + if (range >= Distance(m_Position, tar->GetPosition())) { return true; } - spellrange = (GetActSpellRange(spell_id, spells[spell_id].aoe_range) * GetActSpellRange(spell_id, spells[spell_id].aoe_range)); - if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { + range = GetActSpellRange(spell_id, spells[spell_id].aoe_range); + + if (range >= Distance(m_Position, tar->GetPosition())) { return true; } } diff --git a/zone/groups.cpp b/zone/groups.cpp index e61bbade7..512cccfc3 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -1017,6 +1017,27 @@ void Group::GetBotList(std::list& bot_list, bool clear_list) } } +void Group::GetRawBotList(std::list& bot_list, bool clear_list) +{ + if (clear_list) { + bot_list.clear(); + } + + const auto& l = GroupIdRepository::GetWhere( + database, + fmt::format( + "`group_id` = {}", + GetID() + ) + ); + + for (const auto& e : l) { + if (e.bot_id) { + bot_list.push_back(e.bot_id); + } + } +} + bool Group::Process() { if(disbandcheck && !GroupCount()) return false; @@ -1234,30 +1255,49 @@ void Group::GroupMessageString(Mob* sender, uint32 type, uint32 string_id, const void Client::LeaveGroup() { Group *g = GetGroup(); - if(g) - { + if (g) { int32 MemberCount = g->GroupCount(); // Account for both client and merc leaving the group - if (GetMerc() && g == GetMerc()->GetGroup()) - { + if (GetMerc() && g == GetMerc()->GetGroup()) { MemberCount -= 1; } - if(MemberCount < 3) - { + if (RuleB(Bots, Enabled)) { + std::list sbl; + g->GetRawBotList(sbl); + + for (auto botID : sbl) { + auto b = entity_list.GetBotByBotID(botID); + + if (b) { + if (b->GetBotOwnerCharacterID() == CharacterID()) { + MemberCount -= 1; + } + } + else { + if (database.botdb.GetOwnerID(botID) == CharacterID()) { + MemberCount -= 1; + } + } + } + } + + if (MemberCount < 3) { g->DisbandGroup(); } - else - { + else { g->DelMember(this); - if (GetMerc() != nullptr && g == GetMerc()->GetGroup() ) - { + + if (GetMerc() != nullptr && g == GetMerc()->GetGroup()) { GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); } + + if (RuleB(Bots, Enabled)) { + g->RemoveClientsBots(this); + } } } - else - { + else { //force things a little Group::RemoveFromGroup(this); @@ -2576,3 +2616,77 @@ void Group::AddToGroup(AddToGroupRequest r) } ); } + +void Group::RemoveClientsBots(Client* c) { + std::list sbl; + GetRawBotList(sbl); + + for (auto botID : sbl) { + auto b = entity_list.GetBotByBotID(botID); + + if (b) { + if (b->GetBotOwnerCharacterID() == c->CharacterID()) { + b->RemoveBotFromGroup(b, this); + } + } + else { + if (database.botdb.GetOwnerID(botID) == c->CharacterID()) { + auto botName = database.botdb.GetBotNameByID(botID); + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (membername[i] == botName) { + members[i] = nullptr; + membername[i][0] = '\0'; + memset(membername[i], 0, 64); + MemberRoles[i] = 0; + break; + } + } + + auto pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct)); + ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer; + gl->gid = GetID(); + gl->zoneid = zone->GetZoneID(); + gl->instance_id = zone->GetInstanceID(); + strcpy(gl->member_name, botName.c_str()); + worldserver.SendPacket(pack); + safe_delete(pack); + + auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct)); + GroupJoin_Struct* gu = (GroupJoin_Struct*)outapp->pBuffer; + gu->action = groupActLeave; + strcpy(gu->membername, botName.c_str()); + strcpy(gu->yourname, botName.c_str()); + + gu->leader_aas = LeaderAbilities; + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == nullptr) { + //if (DEBUG>=5) LogFile->write(EQEMuLog::Debug, "Group::DelMember() null member at slot %i", i); + continue; + } + + if (membername[i] != botName.c_str()) { + strcpy(gu->yourname, members[i]->GetCleanName()); + + if (members[i]->IsClient()) { + members[i]->CastToClient()->QueuePacket(outapp); + } + } + } + + safe_delete(outapp); + + DelMemberOOZ(botName.c_str()); + + GroupIdRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + botID + ) + ); + } + } + } +} diff --git a/zone/groups.h b/zone/groups.h index 9cd1a594f..f5e2b2baa 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -69,6 +69,7 @@ public: void GetMemberList(std::list& member_list, bool clear_list = true); void GetClientList(std::list& client_list, bool clear_list = true); void GetBotList(std::list& bot_list, bool clear_list = true); + void GetRawBotList(std::list& bot_list, bool clear_list = true); bool IsGroupMember(Mob* c); bool IsGroupMember(const char* name); bool Process(); @@ -153,6 +154,7 @@ public: void AddToGroup(AddToGroupRequest r); void AddToGroup(Mob* m); static void RemoveFromGroup(Mob* m); + void RemoveClientsBots(Client* c); void SetGroupMentor(int percent, char *name); void ClearGroupMentor(); diff --git a/zone/mob.cpp b/zone/mob.cpp index 65374ce65..926ea3382 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -6112,18 +6112,17 @@ int32 Mob::GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool Mob::IsTargetedFocusEffect(int focus_type) { switch (focus_type) { - case focusSpellVulnerability: - case focusFcSpellDamagePctIncomingPC: - case focusFcDamageAmtIncoming: - case focusFcSpellDamageAmtIncomingPC: - case focusFcCastSpellOnLand: - case focusFcHealAmtIncoming: - case focusFcHealPctCritIncoming: - case focusFcHealPctIncoming: - return true; - default: - return false; - + case focusSpellVulnerability: + case focusFcSpellDamagePctIncomingPC: + case focusFcDamageAmtIncoming: + case focusFcSpellDamageAmtIncomingPC: + case focusFcCastSpellOnLand: + case focusFcHealAmtIncoming: + case focusFcHealPctCritIncoming: + case focusFcHealPctIncoming: + return true; + default: + return false; } } @@ -7254,56 +7253,56 @@ void Mob::SlowMitigation(Mob* caster) EQ::skills::SkillType Mob::GetSkillByItemType(int ItemType) { switch (ItemType) { - case EQ::item::ItemType1HSlash: - return EQ::skills::Skill1HSlashing; - case EQ::item::ItemType2HSlash: - return EQ::skills::Skill2HSlashing; - case EQ::item::ItemType1HPiercing: - return EQ::skills::Skill1HPiercing; - case EQ::item::ItemType1HBlunt: - return EQ::skills::Skill1HBlunt; - case EQ::item::ItemType2HBlunt: - return EQ::skills::Skill2HBlunt; - case EQ::item::ItemType2HPiercing: - if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) + case EQ::item::ItemType1HSlash: + return EQ::skills::Skill1HSlashing; + case EQ::item::ItemType2HSlash: + return EQ::skills::Skill2HSlashing; + case EQ::item::ItemType1HPiercing: return EQ::skills::Skill1HPiercing; - else - return EQ::skills::Skill2HPiercing; - case EQ::item::ItemTypeBow: - return EQ::skills::SkillArchery; - case EQ::item::ItemTypeLargeThrowing: - case EQ::item::ItemTypeSmallThrowing: - return EQ::skills::SkillThrowing; - case EQ::item::ItemTypeMartial: - return EQ::skills::SkillHandtoHand; - default: - return EQ::skills::SkillHandtoHand; + case EQ::item::ItemType1HBlunt: + return EQ::skills::Skill1HBlunt; + case EQ::item::ItemType2HBlunt: + return EQ::skills::Skill2HBlunt; + case EQ::item::ItemType2HPiercing: + if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) + return EQ::skills::Skill1HPiercing; + else + return EQ::skills::Skill2HPiercing; + case EQ::item::ItemTypeBow: + return EQ::skills::SkillArchery; + case EQ::item::ItemTypeLargeThrowing: + case EQ::item::ItemTypeSmallThrowing: + return EQ::skills::SkillThrowing; + case EQ::item::ItemTypeMartial: + return EQ::skills::SkillHandtoHand; + default: + return EQ::skills::SkillHandtoHand; } } uint8 Mob::GetItemTypeBySkill(EQ::skills::SkillType skill) { switch (skill) { - case EQ::skills::SkillThrowing: - return EQ::item::ItemTypeSmallThrowing; - case EQ::skills::SkillArchery: - return EQ::item::ItemTypeArrow; - case EQ::skills::Skill1HSlashing: - return EQ::item::ItemType1HSlash; - case EQ::skills::Skill2HSlashing: - return EQ::item::ItemType2HSlash; - case EQ::skills::Skill1HPiercing: - return EQ::item::ItemType1HPiercing; - case EQ::skills::Skill2HPiercing: // watch for undesired client behavior - return EQ::item::ItemType2HPiercing; - case EQ::skills::Skill1HBlunt: - return EQ::item::ItemType1HBlunt; - case EQ::skills::Skill2HBlunt: - return EQ::item::ItemType2HBlunt; - case EQ::skills::SkillHandtoHand: - return EQ::item::ItemTypeMartial; - default: - return EQ::item::ItemTypeMartial; + case EQ::skills::SkillThrowing: + return EQ::item::ItemTypeSmallThrowing; + case EQ::skills::SkillArchery: + return EQ::item::ItemTypeArrow; + case EQ::skills::Skill1HSlashing: + return EQ::item::ItemType1HSlash; + case EQ::skills::Skill2HSlashing: + return EQ::item::ItemType2HSlash; + case EQ::skills::Skill1HPiercing: + return EQ::item::ItemType1HPiercing; + case EQ::skills::Skill2HPiercing: // watch for undesired client behavior + return EQ::item::ItemType2HPiercing; + case EQ::skills::Skill1HBlunt: + return EQ::item::ItemType1HBlunt; + case EQ::skills::Skill2HBlunt: + return EQ::item::ItemType2HBlunt; + case EQ::skills::SkillHandtoHand: + return EQ::item::ItemTypeMartial; + default: + return EQ::item::ItemTypeMartial; } } @@ -7311,7 +7310,6 @@ uint16 Mob::GetWeaponSpeedbyHand(uint16 hand) { uint16 weapon_speed = 0; switch (hand) { - case 13: weapon_speed = attack_timer.GetDuration(); break; @@ -9416,14 +9414,14 @@ void Mob::SetBaseSetting(uint16 baseSetting, int settingValue) { } bool Mob::TargetValidation(Mob* other) { - if (!other || GetAppearance() == eaDead) { - return false; - } + if (!other || GetAppearance() == eaDead) { + return false; + } - return true; + return true; } -std::unordered_map &Mob::GetCloseMobList(float distance) +std::unordered_map& Mob::GetCloseMobList(float distance) { return entity_list.GetCloseMobList(this, distance); } @@ -9445,3 +9443,41 @@ void Mob::ClearDataBucketCache() DataBucket::DeleteFromCache(id, t); } } + +bool Mob::IsInGroupOrRaid(Mob *other, bool sameRaidGroup) { + if (!other || !IsOfClientBotMerc() || !other->IsOfClientBotMerc()) { + return false; + } + + auto* r = GetRaid(); + auto* rO = other->GetRaid(); + + if (r) { + if (!rO || r != rO) { + return false; + } + + auto rG = r->GetGroup(GetCleanName()); + auto rOG = rO->GetGroup(other->GetCleanName()); + + if (rG == RAID_GROUPLESS || rOG == RAID_GROUPLESS || (sameRaidGroup && rG != rOG)) { + return false; + } + + return true; + } + else { + auto* g = GetGroup(); + auto* gO = other->GetGroup(); + + if (g) { + if (!gO || g != gO) { + return false; + } + + return true; + } + } + + return false; +} diff --git a/zone/mob.h b/zone/mob.h index a9d4e5857..f2e8e9abd 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -774,6 +774,7 @@ public: virtual bool HasGroup() = 0; virtual Raid* GetRaid() = 0; virtual Group* GetGroup() = 0; + bool IsInGroupOrRaid(Mob* other, bool sameRaidGroup = false); //Faction virtual inline int32 GetPrimaryFaction() const { return 0; } diff --git a/zone/spells.cpp b/zone/spells.cpp index 292106f77..4f54b38c5 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2182,14 +2182,42 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_Group: case ST_GroupNoPets: { - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB))) { - if( (!target) || - (target->IsNPC() && !(target->GetOwner() && target->GetOwner()->IsClient())) || - (target->IsCorpse()) ) + if ( + IsClient() && CastToClient()->TGB() && + IsTGBCompatibleSpell(spell_id) && + (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB)) + ) { + if ( + !target || + target->IsCorpse() || + ( + target->IsNPC() && + !(target->GetOwner() && target->GetOwner()->IsClient()) + ) + ) { spell_target = this; - else + } + else { spell_target = target; - } else { + } + } + else if ( + IsBot() && RuleB(Bots, EnableBotTGB) && + IsTGBCompatibleSpell(spell_id) && + (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB)) + ) { + if ( + !spell_target || + spell_target->IsCorpse() || + ( + spell_target->IsNPC() && + !(spell_target->GetOwner() && spell_target->GetOwner()->IsOfClientBot()) + ) + ) { + spell_target = this; + } + } + else { spell_target = this; } @@ -2516,8 +2544,14 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in //range check our target, if we have one and it is not us float range = spells[spell_id].range + GetRangeDistTargetSizeMod(spell_target); - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) + if ( + ( + (IsClient() && CastToClient()->TGB()) || (IsBot() && RuleB(Bots, EnableBotTGB) + ) && + IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) + ) { range = spells[spell_id].aoe_range; + } range = GetActSpellRange(spell_id, range); if(IsClient() && IsIllusionSpell(spell_id) && (HasProjectIllusion())){