Compare commits

...

10 Commits

Author SHA1 Message Date
Alex King 12eb838ee9 [Bug Fix] Fix Scripted Spawns from spawning before Variables are loaded. 2025-03-22 12:57:22 -04:00
catapultam-habeo 213fe6a9e9 [Feature] Implement /changename & related script bindings. Clean up #set name (#4770)
* initial work, need to clean up gm commands still

* cleaned up command, works without kicking char select now

* remove thj-specific methods

* add script hooks

* actually clear flag

* rework questmgr::rename

* remove unnecessary logging

* revert

* added missing binding to perl api and updated some text

* don't return a value

* Fix some bad argument types.

* adjust case

* alpha order

* refactor some old string stuff

* don't quote integers, bob

---------

Co-authored-by: Zimp <zimp@zenryo.xyz>
Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-03-19 21:00:45 -05:00
Alex King be6a5d5f50 [Quest API] Add Support for NPC ID and NPC Name Specificity (#4781)
* [Quest API] Add Support for NPC ID and NPC Name Specificity

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2025-03-19 17:55:00 -05:00
nytmyr 1af29bd7b1 [Bots] Fix IsValidSpellTypeBySpellID to account for all types (#4764)
* [Bots] Fix IsValidSpellTypeBySpellID to account for all types

* Formatting
2025-03-19 17:43:15 -05:00
zimp-wow ef945e6e99 [Fix] Fix zone crash when attempting to add a disappearing client to hate list. (#4782) 2025-03-19 16:26:54 -05:00
Chris Miles 9528c1e7fc [Fix] Zone State Entity Variable Load Pre-Spawn (#4785)
* [Fix] Zone state ent variable pre-spawn

* Update zone_save_state.cpp

* Update zone_save_state.cpp

* Update spawn2.cpp

* Update zone_save_state.cpp

* Update zone_save_state.cpp
2025-03-19 16:21:36 -05:00
Chris Miles fd6e5f465d [Fix] Zone State Position Fix (#4784) 2025-03-19 16:17:25 -05:00
nytmyr d00125abe1 [Bots] Charmed Pets were breaking Mob respawns (#4780)
- When bots would charm a pet, once they zoned or camped the pet would poof and not trigger a respawn so the NPC which was charmed would never respawn until the zone was shut down or server restarted.
2025-03-16 19:12:48 -04:00
Akkadius 5d69235a4c [Release] 23.3.4 2025-03-14 13:09:53 -05:00
Mitch Freeman e93785f885 [Fix] Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase (#4778) 2025-03-13 22:30:00 -05:00
35 changed files with 852 additions and 572 deletions
+7
View File
@@ -1,3 +1,10 @@
## [23.3.4] 3/14/2025
### Fixes
* Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase ([#4778](https://github.com/EQEmu/Server/pull/4778)) @neckkola 2025-03-14
* Fix for rare circumstance where NPC's would have 0 health on restore @Akkadius
## [23.3.3] 3/13/2025
### Database
+37
View File
@@ -955,6 +955,29 @@ bool Database::UpdateName(const std::string& old_name, const std::string& new_na
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::UpdateNameByID(const int character_id, const std::string& new_name)
{
LogInfo("Renaming [{}] to [{}]", character_id, new_name);
auto l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`id` = {}",
character_id
)
);
if (l.empty()) {
return false;
}
auto& e = l.front();
e.name = new_name;
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::IsNameUsed(const std::string& name)
{
if (RuleB(Bots, Enabled)) {
@@ -982,6 +1005,20 @@ bool Database::IsNameUsed(const std::string& name)
return !character_data.empty();
}
// Players cannot have the same name as a pet vanity name, or memory corruption occurs.
bool Database::IsPetNameUsed(const std::string& name)
{
const auto& pet_name_data = CharacterPetNameRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
return !pet_name_data.empty();
}
uint32 Database::GetServerType()
{
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
+2
View File
@@ -103,6 +103,7 @@ public:
bool ReserveName(uint32 account_id, const std::string& name);
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp);
bool UpdateName(const std::string& old_name, const std::string& new_name);
bool UpdateNameByID(const int character_id, const std::string& new_name);
bool CopyCharacter(
const std::string& source_character_name,
const std::string& destination_character_name,
@@ -116,6 +117,7 @@ public:
bool CheckGMIPs(const std::string& login_ip, uint32 account_id);
bool CheckNameFilter(const std::string& name, bool surname = false);
bool IsNameUsed(const std::string& name);
bool IsPetNameUsed(const std::string& name);
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
uint32 GetAccountIDByChar(uint32 character_id);
+2
View File
@@ -287,6 +287,8 @@ N(OP_InstillDoubt),
N(OP_InterruptCast),
N(OP_InvokeChangePetName),
N(OP_InvokeChangePetNameImmediate),
N(OP_InvokeNameChangeImmediate),
N(OP_InvokeNameChangeLazy),
N(OP_ItemLinkClick),
N(OP_ItemLinkResponse),
N(OP_ItemLinkText),
+16 -9
View File
@@ -5832,21 +5832,28 @@ struct ChangeSize_Struct
/*16*/
};
enum ChangeNameResponse : int {
Denied = 0, // 5167: "You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name."
Accepted = 1, // 5976: "Your request for a name change was successful."
Timeout = -1, // 5977: "Your request for a name change has timed out. Please try again later."
ServerError = -2, // 5978: "The server had an error while processing your name request. Please try again later."
RateLimited = -3, // 5979: "You must wait longer before submitting another name request. Please try again in a few minutes."
Ineligible = -4, // 5980: "Your character is not eligible for a name change."
Pending = -5 // 5193: "You already have a name change pending. Please wait until it is fully processed before attempting another name change."
};
struct AltChangeName_Struct {
/*00*/ char new_name[64];
/*40*/ char old_name[64];
/*80*/ int response_code;
};
struct ChangePetName_Struct {
/*00*/ char new_pet_name[64];
/*40*/ char pet_owner_name[64];
/*80*/ int response_code;
};
enum ChangePetNameResponse : int {
Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name.
Accepted = 1, // 5976 Your request for a name change was successful.
Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes.
NotEligible = -4, // 5980 Your character is not eligible for a name change.
Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change.
Unhandled = -1
};
// New OpCode/Struct for SoD+
struct GroupMakeLeader_Struct
{
+2 -1
View File
@@ -231,6 +231,7 @@ RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will alway
RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false")
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.")
RULE_CATEGORY_END()
RULE_CATEGORY(Mercs)
@@ -835,7 +836,7 @@ RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range
RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.")
RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.")
RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.")
RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks")
RULE_BOOL(Bots, RunSpellTypeChecksOnBoot, false, "This will run a series of checks to find potential errors in your bot_spells_entries table on boot and output to LogBotSpellTypeChecks")
RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires")
RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list")
RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA")
+49 -37
View File
@@ -1456,41 +1456,42 @@ bool IsCompleteHealSpell(uint16 spell_id)
}
bool IsFastHealSpell(uint16 spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHP) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
);
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHP) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
);
if (!spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
);
}
if (!spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
);
}
if (spell_id && IsValidSpell(spell_id)) {
if (
spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
) {
for (int i = 0; i < EFFECT_COUNT; i++) {
if (
spells[spell_id].base_value[i] > 0 &&
(
spells[spell_id].effect_id[i] == SE_CurrentHP ||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
)
) {
return true;
}
}
}
}
if (IsValidSpell(spell_id)) {
if (
spell_id != SPELL_MINOR_HEALING &&
(spells[spell_id].cast_time > MAX_VERY_FAST_HEAL_CASTING_TIME && spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME) &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
) {
for (int i = 0; i < EFFECT_COUNT; i++) {
if (
spells[spell_id].base_value[i] > 0 &&
(
spells[spell_id].effect_id[i] == SE_CurrentHP ||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
)
) {
return true;
}
}
}
}
return false;
return false;
}
bool IsVeryFastHealSpell(uint16 spell_id)
@@ -1509,8 +1510,9 @@ bool IsVeryFastHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (
spell_id != SPELL_MINOR_HEALING &&
spells[spell_id].cast_time <= MAX_VERY_FAST_HEAL_CASTING_TIME &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
@@ -1548,8 +1550,13 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (spell_id == SPELL_MINOR_HEALING) {
return true;
}
if (
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
spells[spell_id].target_type == ST_Target &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
@@ -1589,9 +1596,14 @@ bool IsRegularPetHealSpell(uint16 spell_id)
);
}
if (spell_id && IsValidSpell(spell_id)) {
if (IsValidSpell(spell_id)) {
if (spell_id == SPELL_MINOR_HEALING) {
return true;
}
if (
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_Undead) &&
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet) &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
!IsGroupSpell(spell_id)
@@ -1630,7 +1642,7 @@ bool IsRegularGroupHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (
IsGroupSpell(spell_id) &&
!IsCompleteHealSpell(spell_id) &&
+1
View File
@@ -215,6 +215,7 @@
#define SPELL_AMPLIFICATION 2603
#define SPELL_DIVINE_REZ 2738
#define SPELL_NATURES_RECOVERY 2520
#define SPELL_MINOR_HEALING 200
#define SPELL_ADRENALINE_SWELL 14445
#define SPELL_ADRENALINE_SWELL_RK2 14446
#define SPELL_ADRENALINE_SWELL_RK3 14447
+12 -252
View File
@@ -1,4 +1,5 @@
#include "spdat.h"
#include "../zone/bot.h"
bool IsBotSpellTypeDetrimental(uint16 spell_type) {
switch (spell_type) {
@@ -417,264 +418,23 @@ uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id) {
return UINT16_MAX;
}
uint16 correct_type = UINT16_MAX;
SPDat_Spell_Struct spell = spells[spell_id];
std::string teleport_zone = spell.teleport_zone;
uint16 correct_type = spell_type;
if (IsCharmSpell(spell_id)) {
correct_type = BotSpellTypes::Charm;
}
else if (IsFearSpell(spell_id)) {
correct_type = BotSpellTypes::Fear;
}
else if (IsEffectInSpell(spell_id, SE_Revive)) {
correct_type = BotSpellTypes::Resurrect;
}
else if (IsHarmonySpell(spell_id)) {
correct_type = BotSpellTypes::Lull;
}
else if (
teleport_zone.compare("") &&
!IsEffectInSpell(spell_id, SE_GateToHomeCity) &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
) {
correct_type = BotSpellTypes::Teleport;
}
else if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Succor)
) {
correct_type = BotSpellTypes::Succor;
}
else if (IsEffectInSpell(spell_id, SE_BindAffinity)) {
correct_type = BotSpellTypes::BindAffinity;
}
else if (IsEffectInSpell(spell_id, SE_Identify)) {
correct_type = BotSpellTypes::Identify;
}
else if (
spell_type == BotSpellTypes::Levitate &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Levitate)
) {
correct_type = BotSpellTypes::Levitate;
}
else if (
spell_type == BotSpellTypes::Rune &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
) {
correct_type = BotSpellTypes::Rune;
}
else if (
spell_type == BotSpellTypes::WaterBreathing &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_WaterBreathing)
) {
correct_type = BotSpellTypes::WaterBreathing;
}
else if (
spell_type == BotSpellTypes::Size &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
) {
correct_type = BotSpellTypes::Size;
}
else if (
spell_type == BotSpellTypes::Invisibility &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id))
) {
correct_type = BotSpellTypes::Invisibility;
}
else if (
spell_type == BotSpellTypes::MovementSpeed &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
correct_type = BotSpellTypes::MovementSpeed;
}
else if (
!teleport_zone.compare("") &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_Translocate) || IsEffectInSpell(spell_id, SE_GateToHomeCity))
) {
correct_type = BotSpellTypes::SendHome;
}
else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) {
correct_type = BotSpellTypes::SummonCorpse;
}
if (!Bot::IsValidSpellTypeBySpellID(spell_type, spell_id)) {
correct_type = UINT16_MAX;
if (correct_type == UINT16_MAX) {
if (
IsSummonPetSpell(spell_id) ||
IsEffectInSpell(spell_id, SE_TemporaryPets)
) {
correct_type = BotSpellTypes::Pet;
}
else if (IsMesmerizeSpell(spell_id)) {
correct_type = BotSpellTypes::Mez;
}
else if (IsEscapeSpell(spell_id)) {
correct_type = BotSpellTypes::Escape;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Root)
) {
if (IsAnyAESpell(spell_id)) {
correct_type = BotSpellTypes::AERoot;
}
else {
correct_type = BotSpellTypes::Root;
}
}
else if (
IsDetrimentalSpell(spell_id) &&
IsLifetapSpell(spell_id)
) {
correct_type = BotSpellTypes::Lifetap;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
correct_type = BotSpellTypes::Snare;
}
else if (
IsDetrimentalSpell(spell_id) &&
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
) {
correct_type = BotSpellTypes::DOT;
}
else if (IsDispelSpell(spell_id)) {
correct_type = BotSpellTypes::Dispel;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsSlowSpell(spell_id)
) {
correct_type = BotSpellTypes::Slow;
}
else if (
IsDebuffSpell(spell_id) &&
!IsHateReduxSpell(spell_id) &&
!IsHateSpell(spell_id)
) {
correct_type = BotSpellTypes::Debuff;
}
else if (IsHateReduxSpell(spell_id)) {
correct_type = BotSpellTypes::HateRedux;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsHateSpell(spell_id)
) {
correct_type = BotSpellTypes::HateLine;
}
else if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
IsBardSong(spell_id)
) {
if (
spell_type == BotSpellTypes::InCombatBuffSong ||
spell_type == BotSpellTypes::OutOfCombatBuffSong ||
spell_type == BotSpellTypes::PreCombatBuffSong
) {
correct_type = spell_type;
}
else {
correct_type = BotSpellTypes::OutOfCombatBuffSong;
}
}
else if (
!IsBardSong(spell_id) &&
(
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
)
) {
correct_type = BotSpellTypes::InCombatBuff;
}
else if (
spell_type == BotSpellTypes::PreCombatBuff &&
IsAnyBuffSpell(spell_id) &&
!IsBardSong(spell_id)
) {
correct_type = BotSpellTypes::PreCombatBuff;
}
else if (
(IsCureSpell(spell_id) && spell_type == BotSpellTypes::Cure) ||
(IsCureSpell(spell_id) && !IsAnyHealSpell(spell_id))
) {
correct_type = BotSpellTypes::Cure;
}
else if (IsAnyNukeOrStunSpell(spell_id)) {
if (IsAnyAESpell(spell_id)) {
if (IsAERainSpell(spell_id)) {
correct_type = BotSpellTypes::AERains;
}
else if (IsPBAENukeSpell(spell_id)) {
correct_type = BotSpellTypes::PBAENuke;
}
else if (IsStunSpell(spell_id)) {
correct_type = BotSpellTypes::AEStun;
}
else {
correct_type = BotSpellTypes::AENukes;
}
}
else if (IsStunSpell(spell_id)) {
correct_type = BotSpellTypes::Stun;
}
else {
correct_type = BotSpellTypes::Nuke;
}
}
else if (IsAnyHealSpell(spell_id)) {
if (IsGroupSpell(spell_id)) {
if (IsGroupCompleteHealSpell(spell_id)) {
correct_type = BotSpellTypes::GroupCompleteHeals;
}
else if (IsGroupHealOverTimeSpell(spell_id)) {
correct_type = BotSpellTypes::GroupHoTHeals;
}
else if (IsRegularGroupHealSpell(spell_id)) {
correct_type = BotSpellTypes::GroupHeals;
}
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
return correct_type;
for (int i = end; i >= start; --i) {
if (!Bot::IsValidBotSpellType(i) || i == BotSpellTypes::InCombatBuff) {
continue;
}
if (IsVeryFastHealSpell(spell_id)) {
correct_type = BotSpellTypes::VeryFastHeals;
}
else if (IsFastHealSpell(spell_id)) {
correct_type = BotSpellTypes::FastHeals;
}
else if (IsCompleteHealSpell(spell_id)) {
correct_type = BotSpellTypes::CompleteHeal;
}
else if (IsHealOverTimeSpell(spell_id)) {
correct_type = BotSpellTypes::HoTHeals;
}
else if (IsRegularSingleTargetHealSpell(spell_id)) {
correct_type = BotSpellTypes::RegularHeal;
}
else if (IsRegularPetHealSpell(spell_id)) {
correct_type = BotSpellTypes::RegularHeal;
}
}
else if (IsAnyBuffSpell(spell_id)) {
correct_type = BotSpellTypes::Buff;
if (Bot::IsValidSpellTypeBySpellID(i, spell_id)) {
correct_type = i;
if (IsResistanceOnlySpell(spell_id)) {
correct_type = BotSpellTypes::ResistBuffs;
}
else if (IsDamageShieldOnlySpell(spell_id)) {
correct_type = BotSpellTypes::DamageShields;
break;
}
}
}
+1 -1
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.3.3-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.3.4-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.3.3",
"version": "23.3.4",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+3
View File
@@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e
OP_InvokeChangePetNameImmediate=0x046d
OP_InvokeChangePetName=0x4506
OP_ChangePetName=0x5dab
OP_InvokeNameChangeImmediate=0x4fe2
OP_InvokeNameChangeLazy=0x2f2e
+6
View File
@@ -3041,9 +3041,15 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
if (!other)
return;
if (other->IsDestroying())
return;
if (other == this)
return;
if (other->IsClient() && (other->CastToClient()->IsZoning() || other->CastToClient()->Connected() == false))
return;
if (other->IsTrap())
return;
+293 -79
View File
@@ -3578,8 +3578,16 @@ void Bot::Depop() {
entity_list.RemoveFromHateLists(this);
RemoveAllAuras();
if (HasPet())
GetPet()->Depop();
Mob* bot_pet = GetPet();
if (bot_pet) {
if (bot_pet->Charmed()) {
bot_pet->BuffFadeByEffect(SE_Charm);
}
else {
bot_pet->Depop();
}
}
_botOwner = nullptr;
_botOwnerCharacterID = 0;
@@ -3702,11 +3710,6 @@ bool Bot::Spawn(Client* botCharacterOwner) {
}
}
if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) {
OwnerMessage("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
CheckBotSpells(); //This runs through a series of checks and outputs any spells that are set to the wrong spell type in the database
}
if (IsBotRanged()) {
ChangeBotRangedWeapons(true);
}
@@ -9790,6 +9793,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
}
uint8 bot_class = GetClass();
auto spell = spells[spell_id];
switch (spell_type) {
case BotSpellTypes::Buff:
@@ -9813,7 +9817,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
case BotSpellTypes::SendHome:
if (
tar == this &&
spells[spell_id].target_type == ST_TargetsTarget
spell.target_type == ST_TargetsTarget
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to target_type checks. Using {}'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName(), GetSpellTargetType(spell_id));
return false;
@@ -9843,7 +9847,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
}
if (
spells[spell_id].target_type == ST_Pet &&
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet) &&
(
!tar->IsPet() ||
(
@@ -11566,18 +11570,265 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
}
if (IsBotSpellTypeDetrimental(spell_type) && !IsDetrimentalSpell(spell_id)) {
return false;
}
if (IsBotSpellTypeBeneficial(spell_type) && !IsBeneficialSpell(spell_id)) {
return false;
}
auto spell = spells[spell_id];
std::string teleport_zone = spell.teleport_zone;
switch (spell_type) {
case BotSpellTypes::Buff:
case BotSpellTypes::PetBuffs:
if (
IsResistanceOnlySpell(spell_id) ||
IsDamageShieldOnlySpell(spell_id) ||
IsDamageShieldAndResistSpell(spell_id)
) {
return false;
case BotSpellTypes::Nuke:
if (IsAnyNukeOrStunSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return true;
return false;
case BotSpellTypes::RegularHeal:
case BotSpellTypes::PetRegularHeals:
if (
IsAnyHealSpell(spell_id) &&
!IsVeryFastHealSpell(spell_id) &&
!IsFastHealSpell(spell_id) &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
!IsBuffSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Root:
case BotSpellTypes::AERoot:
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_Root)) {
return true;
}
return false;
case BotSpellTypes::Buff:
case BotSpellTypes::PreCombatBuff:
case BotSpellTypes::PetBuffs:
if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
!IsBardSong(spell_id) &&
!IsResistanceOnlySpell(spell_id) &&
!IsDamageShieldOnlySpell(spell_id) &&
!IsDamageShieldAndResistSpell(spell_id)
) {
if (
spell_type != BotSpellTypes::PetBuffs &&
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet)
) {
return false;
}
return true;
}
return false;
case BotSpellTypes::Escape:
if (IsEscapeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Pet:
if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) {
return true;
}
return false;
case BotSpellTypes::Lifetap:
case BotSpellTypes::AELifetap:
if (IsDetrimentalSpell(spell_id) && IsLifetapSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Snare:
case BotSpellTypes::AESnare:
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
return true;
}
return false;
case BotSpellTypes::DOT:
case BotSpellTypes::AEDoT:
if (
IsDetrimentalSpell(spell_id) &&
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
) {
return true;
}
return false;
case BotSpellTypes::Dispel:
case BotSpellTypes::AEDispel:
if (IsDispelSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::InCombatBuff:
if (
!IsBardSong(spell_id) &&
(
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
)
) {
return true;
}
return false;
case BotSpellTypes::Mez:
case BotSpellTypes::AEMez:
if (IsMesmerizeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Charm:
if (IsCharmSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Slow:
case BotSpellTypes::AESlow:
if (IsDetrimentalSpell(spell_id) && IsSlowSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Debuff:
case BotSpellTypes::AEDebuff:
if (
IsDebuffSpell(spell_id) &&
!IsHateReduxSpell(spell_id) &&
!IsHateSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Cure:
case BotSpellTypes::GroupCures:
case BotSpellTypes::PetCures:
if (IsCureSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Resurrect:
if (IsEffectInSpell(spell_id, SE_Revive)) {
return true;
}
return false;
case BotSpellTypes::HateRedux:
if (IsHateReduxSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::InCombatBuffSong:
case BotSpellTypes::OutOfCombatBuffSong:
case BotSpellTypes::PreCombatBuffSong:
if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
IsBardSong(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Fear:
case BotSpellTypes::AEFear:
if (IsFearSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Stun:
case BotSpellTypes::AEStun:
if (IsDetrimentalSpell(spell_id) && IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::HateLine:
case BotSpellTypes::AEHateLine:
if (IsDetrimentalSpell(spell_id) && IsHateSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::CompleteHeal:
case BotSpellTypes::GroupCompleteHeals:
case BotSpellTypes::PetCompleteHeals:
if (IsCompleteHealSpell(spell_id) || IsGroupCompleteHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::FastHeals:
case BotSpellTypes::PetFastHeals:
if (IsFastHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::VeryFastHeals:
case BotSpellTypes::PetVeryFastHeals:
if (IsVeryFastHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::GroupHeals:
if (IsRegularGroupHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::HoTHeals:
case BotSpellTypes::GroupHoTHeals:
case BotSpellTypes::PetHoTHeals:
if (IsHealOverTimeSpell(spell_id) || IsGroupHealOverTimeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::AENukes:
if (
IsDetrimentalSpell(spell_id) &&
!IsAERainSpell(spell_id) &&
!IsPBAENukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AERains:
if (IsAERainNukeSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::PBAENuke:
if (IsPBAENukeSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs:
if (IsResistanceBuffSpell(spell_id)) {
@@ -11587,44 +11838,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::DamageShields:
case BotSpellTypes::PetDamageShields:
if (IsEffectInSpell(spell_id, SE_DamageShield)) {
return true;
}
return false;
case BotSpellTypes::PBAENuke:
if (
IsPBAENukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AERains:
if (
IsAERainNukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AEStun:
case BotSpellTypes::Stun:
if (IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::AENukes:
case BotSpellTypes::Nuke:
if (!IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Lull:
if (IsHarmonySpell(spell_id)) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_DamageShield)) {
return true;
}
@@ -11632,13 +11846,18 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::Teleport:
if (
IsBeneficialSpell(spell_id) &&
(
IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate)
)
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
) {
return true;
}
return false;
case BotSpellTypes::Lull:
case BotSpellTypes::AELull:
if (IsHarmonySpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Succor:
if (
@@ -11662,25 +11881,21 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::Levitate:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Levitate)
) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Levitate)) {
return true;
}
return false;
case BotSpellTypes::Rune:
if (
IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) ||
IsEffectInSpell(spell_id, SE_Rune)
if (IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
) {
return true;
}
return false;
case BotSpellTypes::WaterBreathing:
if (IsEffectInSpell(spell_id, SE_WaterBreathing)) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) {
return true;
}
@@ -11688,29 +11903,22 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::Size:
if (
IsBeneficialSpell(spell_id) &&
(
IsEffectInSpell(spell_id, SE_ModelSize) ||
IsEffectInSpell(spell_id, SE_ChangeHeight)
)
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
) {
return true;
}
return false;
case BotSpellTypes::Invisibility:
if (
IsEffectInSpell(spell_id, SE_SeeInvis) ||
IsInvisibleSpell(spell_id)
if (IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_SeeInvis) ||IsInvisibleSpell(spell_id))
) {
return true;
}
return false;
case BotSpellTypes::MovementSpeed:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
return true;
}
@@ -11718,7 +11926,13 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::SendHome:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_GateToHomeCity)
(
IsEffectInSpell(spell_id, SE_GateToHomeCity) ||
(
teleport_zone.compare("") &&
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
)
)
) {
return true;
}
-1
View File
@@ -683,7 +683,6 @@ public:
void SetSitManaPct(uint8 value) { _SitManaPct = value; }
// Spell lists
void CheckBotSpells();
std::list<BotSpellTypeOrder> GetSpellTypesPrioritized(uint8 priority_type);
static uint16 GetParentSpellType(uint16 spell_type);
static bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
+6 -1
View File
@@ -220,6 +220,11 @@ int bot_command_init(void)
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
std::vector<std::string> orphaned_bot_command_settings;
if (RuleB(Bots, RunSpellTypeChecksOnBoot)) {
LogBotSpellTypeChecks("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
database.botdb.CheckBotSpells();
}
database.botdb.MapCommandedSpellTypeMinLevels();
for (auto bcs_iter : bot_command_settings) {
@@ -813,7 +818,7 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
auto spell_type_itr = spell_map.find(spell_type);
auto class_itr = spell_type_itr->second.find(i);
const auto& spell_info = class_itr->second;
if (spell_info.min_level < UINT8_MAX) {
found = true;
+136
View File
@@ -2523,6 +2523,142 @@ bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id)
return true;
}
void BotDatabase::CheckBotSpells() {
auto spell_list = BotSpellsEntriesRepository::All(content_db);
uint16 spell_id;
SPDat_Spell_Struct spell;
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
spell = spells[s.spell_id];
spell_id = spell.id;
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
}
else {
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.maxlevel
);
}
}
uint16 correct_type = GetCorrectBotSpellType(s.type, spell_id);
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
uint16 parent_type = Bot::GetParentSpellType(correct_type);
if (s.type == parent_type || s.type == correct_type) {
continue;
}
if (correct_type != parent_type) {
correct_type = parent_type;
}
}
else {
if (IsPetBotSpellType(s.type)) {
correct_type = GetPetBotSpellType(correct_type);
}
}
if (IsPetBotSpellType(correct_type) && (spell.target_type != ST_Pet && spell.target_type != ST_SummonedPet)) {
correct_type = Bot::GetParentSpellType(correct_type);
}
if (correct_type == s.type) {
continue;
}
if (correct_type == UINT16_MAX) {
LogBotSpellTypeChecks(
"{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown.",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type
);
}
else {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]",
correct_type,
spell_id,
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
}
}
}
void BotDatabase::MapCommandedSpellTypeMinLevels() {
commanded_spell_type_min_levels.clear();
+1
View File
@@ -130,6 +130,7 @@ public:
bool SaveBotSettings(Mob* m);
bool DeleteBotSettings(const uint32 bot_id);
void CheckBotSpells();
void MapCommandedSpellTypeMinLevels();
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; }
-129
View File
@@ -2865,132 +2865,3 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
return result;
}
void Bot::CheckBotSpells() {
auto spell_list = BotSpellsEntriesRepository::All(content_db);
uint16 spell_id;
SPDat_Spell_Struct spell;
uint16 correct_type;
uint16 parent_type;
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
spell = spells[s.spell_id];
spell_id = spell.id;
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
}
else {
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, spell_id
, s.npc_spells_id
, GetSpellName(spell_id)
, spell_id
, s.minlevel
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, spell_id
, s.npc_spells_id
, GetSpellName(spell_id)
, spell_id
, s.minlevel
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.maxlevel
);
}
}
correct_type = GetCorrectBotSpellType(s.type, spell_id);
parent_type = GetParentSpellType(correct_type);
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
if (s.type == parent_type || s.type == correct_type) {
continue;
}
}
else {
if (IsPetBotSpellType(s.type)) {
correct_type = GetPetBotSpellType(correct_type);
}
}
if (correct_type == s.type) {
continue;
}
if (correct_type == UINT16_MAX) {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown."
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
);
}
else {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]"
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
, GetSpellTypeNameByID(correct_type)
, correct_type
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]"
, correct_type
, spell_id
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
, GetSpellTypeNameByID(correct_type)
, correct_type
);
}
}
}
+86 -16
View File
@@ -2549,40 +2549,59 @@ void Client::ChangeLastName(std::string last_name) {
safe_delete(outapp);
}
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
// Deprecated, this packet does not actually work in ROF2
bool Client::ChangeFirstName(const std::string in_firstname, const std::string gmname)
{
// check duplicate name
bool used_name = database.IsNameUsed((const char*) in_firstname);
if (used_name) {
if (!ChangeFirstName(in_firstname)) {
return false;
}
// update character_
if(!database.UpdateName(GetName(), in_firstname))
return false;
// update pp
memset(m_pp.name, 0, sizeof(m_pp.name));
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname);
strcpy(name, m_pp.name);
Save();
// send name update packet
auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
strn0cpy(gmn->gmname,gmname,64);
strn0cpy(gmn->gmname,gmname.c_str(),64);
strn0cpy(gmn->oldname,GetName(),64);
strn0cpy(gmn->newname,in_firstname,64);
strn0cpy(gmn->newname,in_firstname.c_str(),64);
gmn->unknown[0] = 1;
gmn->unknown[1] = 1;
gmn->unknown[2] = 1;
entity_list.QueueClients(this, outapp, false);
safe_delete(outapp);
// success
return true;
}
bool Client::ChangeFirstName(const std::string in_firstname)
{
// check duplicate name
bool used_name = database.IsNameUsed(in_firstname) || database.IsPetNameUsed(in_firstname);
if (used_name || !database.CheckNameFilter(in_firstname, false)) {
return false;
}
// update character_
if(!database.UpdateNameByID(CharacterID(), in_firstname))
return false;
// Send Name Update to Clients
SendRename(this, GetName(), in_firstname.c_str());
SetName(in_firstname.c_str());
// update pp
memset(m_pp.name, 0, sizeof(m_pp.name));
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname.c_str());
strcpy(name, m_pp.name);
Save();
// Update the active char in account table
database.UpdateLiveChar(in_firstname, AccountID());
// finally, update the /who list
UpdateWho();
// success
ClearNameChange();
return true;
}
@@ -4737,6 +4756,57 @@ bool Client::KeyRingRemove(uint32 item_id)
);
}
bool Client::IsNameChangeAllowed() {
if (RuleB(Character, AlwaysAllowNameChange)) {
return true;
}
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
auto b = DataBucket::GetData(k);
if (!b.value.empty()) {
return true;
}
return false;
}
bool Client::ClearNameChange() {
if (!IsNameChangeAllowed()) {
return false;
}
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
DataBucket::DeleteData(k);
return true;
}
void Client::InvokeChangeNameWindow(bool immediate) {
if (!IsNameChangeAllowed()) {
return;
}
auto packet_op = immediate ? OP_InvokeNameChangeImmediate : OP_InvokeNameChangeLazy;
auto outapp = new EQApplicationPacket(packet_op, 0);
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::GrantNameChange() {
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
k.value = "allowed"; // potentially put a timestamp here
DataBucket::SetData(k);
InvokeChangeNameWindow(true);
}
bool Client::IsPetNameChangeAllowed() {
if (RuleB(Pets, AlwaysAllowPetRename)) {
return true;
+8 -1
View File
@@ -332,6 +332,10 @@ public:
bool KeyRingClear();
bool KeyRingRemove(uint32 item_id);
void KeyRingList();
bool IsNameChangeAllowed();
void InvokeChangeNameWindow(bool immediate = true);
bool ClearNameChange();
void GrantNameChange();
bool IsPetNameChangeAllowed();
void GrantPetNameChange();
void ClearPetNameChange();
@@ -443,6 +447,8 @@ public:
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
void CancelBuyerTradeWindow();
void CancelTraderTradeWindow();
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
@@ -509,7 +515,8 @@ public:
bool AutoAttackEnabled() const { return auto_attack; }
bool AutoFireEnabled() const { return auto_fire; }
bool ChangeFirstName(const char* in_firstname,const char* gmname);
bool ChangeFirstName(const std::string in_firstname,const std::string gmname);
bool ChangeFirstName(const std::string in_firstname);
void Duck();
void Stand();
+24 -5
View File
@@ -826,6 +826,10 @@ void Client::CompleteConnect()
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
InvokeChangePetName(false);
}
if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) {
InvokeChangeNameWindow(false);
}
}
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
@@ -4256,7 +4260,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
else {
OnDisconnect(true);
}
return;
}
@@ -4548,14 +4552,14 @@ void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) {
auto p = (ChangePetName_Struct *) app->pBuffer;
if (!IsPetNameChangeAllowed()) {
p->response_code = ChangePetNameResponse::NotEligible;
p->response_code = ChangeNameResponse::Ineligible;
QueuePacket(app);
return;
}
p->response_code = ChangePetNameResponse::Denied;
p->response_code = ChangeNameResponse::Denied;
if (ChangePetName(p->new_pet_name)) {
p->response_code = ChangePetNameResponse::Accepted;
p->response_code = ChangeNameResponse::Accepted;
}
QueuePacket(app);
@@ -6776,6 +6780,21 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app)
void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
{
if (app->size == sizeof(AltChangeName_Struct)) {
auto p = (AltChangeName_Struct *) app->pBuffer;
if (!IsNameChangeAllowed()) {
p->response_code = ChangeNameResponse::Ineligible;
QueuePacket(app);
return;
}
p->response_code = ChangeFirstName(p->new_name) ? ChangeNameResponse::Accepted : ChangeNameResponse::Denied;
QueuePacket(app);
return;
}
if (app->size != sizeof(GMName_Struct)) {
LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct));
return;
@@ -15463,7 +15482,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
);
Message(
Chat::Yellow,
"Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
"Direct inventory delivery is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
);
in->method = BazaarByDirectToInventory;
in->sub_action = Failed;
+2 -6
View File
@@ -14,7 +14,7 @@ void SetName(Client *c, const Seperator *sep)
std::string new_name = sep->arg[2];
std::string old_name = t->GetCleanName();
if (t->ChangeFirstName(new_name.c_str(), c->GetCleanName())) {
if (t->ChangeFirstName(new_name, c->GetCleanName())) {
c->Message(
Chat::White,
fmt::format(
@@ -24,17 +24,13 @@ void SetName(Client *c, const Seperator *sep)
).c_str()
);
c->Message(Chat::White, "Sending player to char select.");
t->Kick("Name was changed");
return;
}
c->Message(
Chat::White,
fmt::format(
"Unable to rename {}. Check that the new name '{}' isn't already taken.",
"Unable to rename {}. Check that the new name '{}' isn't already taken (Including Pet Names), or isn't invalid",
old_name,
new_name
).c_str()
+21
View File
@@ -3496,6 +3496,24 @@ std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name)
return self->GetAccountBucketRemaining(bucket_name);
}
void Lua_Client::GrantNameChange()
{
Lua_Safe_Call_Void();
self->GrantNameChange();
}
bool Lua_Client::IsNameChangeAllowed()
{
Lua_Safe_Call_Bool();
return self->IsNameChangeAllowed();
}
bool Lua_Client::ClearNameChange()
{
Lua_Safe_Call_Bool();
return self->ClearNameChange();
}
std::string Lua_Client::GetBandolierName(uint8 bandolier_slot)
{
Lua_Safe_Call_String();
@@ -3635,6 +3653,7 @@ luabind::scope lua_register_client() {
.def("CashReward", &Lua_Client::CashReward)
.def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName)
.def("GrantPetNameChange", &Lua_Client::GrantPetNameChange)
.def("ClearNameChange", &Lua_Client::ClearNameChange)
.def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID)
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill)
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill)
@@ -3851,6 +3870,7 @@ luabind::scope lua_register_client() {
.def("GrantAllAAPoints", (void(Lua_Client::*)(uint8,bool))&Lua_Client::GrantAllAAPoints)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GrantNameChange", &Lua_Client::GrantNameChange)
.def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID)
.def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank)
.def("HasAugmentEquippedByID", (bool(Lua_Client::*)(uint32))&Lua_Client::HasAugmentEquippedByID)
@@ -3881,6 +3901,7 @@ luabind::scope lua_register_client() {
.def("IsInAGuild", (bool(Lua_Client::*)(void))&Lua_Client::IsInAGuild)
.def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD)
.def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding)
.def("IsNameChangeAllowed", &Lua_Client::IsNameChangeAllowed)
.def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped)
.def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting)
.def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding)
+4
View File
@@ -609,6 +609,10 @@ public:
void ShowZoneShardMenu();
void GrantPetNameChange();
void GrantNameChange();
bool IsNameChangeAllowed();
bool ClearNameChange();
Lua_Expedition CreateExpedition(luabind::object expedition_info);
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players);
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages);
+4 -1
View File
@@ -131,7 +131,8 @@ Mob::Mob(
m_scan_close_mobs_timer(6000),
m_see_close_mobs_timer(1000),
m_mob_check_moving_timer(1000),
bot_attack_flag_timer(10000)
bot_attack_flag_timer(10000),
m_destroying(false)
{
mMovementManager = &MobMovementManager::Get();
mMovementManager->AddMob(this);
@@ -531,6 +532,8 @@ Mob::Mob(
Mob::~Mob()
{
m_destroying = true;
entity_list.RemoveMobFromCloseLists(this);
m_close_mobs.clear();
+2
View File
@@ -1510,6 +1510,7 @@ public:
void ClearDataBucketCache();
bool IsGuildmaster() const;
bool IsDestroying() const { return m_destroying; }
protected:
void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
@@ -1932,6 +1933,7 @@ private:
EQ::InventoryProfile m_inv;
std::shared_ptr<HealRotation> m_target_of_heal_rotation;
bool m_manual_follow;
bool m_destroying;
void SetHeroicStrBonuses(StatBonuses* n);
void SetHeroicStaBonuses(StatBonuses* n);
+18
View File
@@ -3261,6 +3261,21 @@ std::string Perl_Client_GetAccountBucketRemaining(Client* self, std::string buck
return self->GetAccountBucketRemaining(bucket_name);
}
void Perl_Client_GrantNameChange(Client* self)
{
self->GrantNameChange();
}
bool Perl_Client_IsNameChangeAllowed(Client* self)
{
return self->IsNameChangeAllowed();
}
bool Perl_Client_ClearNameChange(Client* self)
{
return self->ClearNameChange();
}
std::string Perl_Client_GetBandolierName(Client* self, uint8 bandolier_slot)
{
return self->GetBandolierName(bandolier_slot);
@@ -3393,6 +3408,7 @@ void perl_register_client()
package.add("CashReward", &Perl_Client_CashReward);
package.add("ChangeLastName", &Perl_Client_ChangeLastName);
package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange);
package.add("ClearNameChange", (bool(*)(Client*))&Perl_Client_ClearNameChange);
package.add("CharacterID", &Perl_Client_CharacterID);
package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill);
package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill);
@@ -3607,6 +3623,7 @@ void perl_register_client()
package.add("GrantAllAAPoints", (void(*)(Client*, uint8, bool))&Perl_Client_GrantAllAAPoints);
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int))&Perl_Client_GrantAlternateAdvancementAbility);
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int, bool))&Perl_Client_GrantAlternateAdvancementAbility);
package.add("GrantNameChange", (void(*)(Client*))&Perl_Client_GrantNameChange);
package.add("GuildID", &Perl_Client_GuildID);
package.add("GuildRank", &Perl_Client_GuildRank);
package.add("HasAugmentEquippedByID", &Perl_Client_HasAugmentEquippedByID);
@@ -3637,6 +3654,7 @@ void perl_register_client()
package.add("IsInAGuild", &Perl_Client_IsInAGuild);
package.add("IsLD", &Perl_Client_IsLD);
package.add("IsMedding", &Perl_Client_IsMedding);
package.add("IsNameChangeAllowed", (bool(*)(Client*))&Perl_Client_IsNameChangeAllowed);
package.add("IsRaidGrouped", &Perl_Client_IsRaidGrouped);
package.add("IsSitting", &Perl_Client_IsSitting);
package.add("IsStanding", &Perl_Client_IsStanding);
+12 -3
View File
@@ -939,6 +939,12 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
Strings::FindReplace(npc_name, "`", "-");
const std::string& npc_id_and_name = fmt::format(
"{}_{}",
npc_name,
npc_id
);
const std::string& global_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
@@ -959,13 +965,16 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
);
std::vector<std::string> file_names = {
fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID ./quests/zone/v0/10.ext
fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name ./quests/zone/v0/npc.ext
fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID (./quests/zone/v0/10.ext)
fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name (./quests/zone/v0/name.ext)
fmt::format("{}/{}", zone_versioned_path, npc_id_and_name), // Local versioned by NPC ID and NPC Name (./quests/zone/v0/10_name.ext)
fmt::format("{}/{}", zone_path, npc_id), // Local by NPC ID
fmt::format("{}/{}", zone_path, npc_name), // Local by NPC Name
fmt::format("{}/{}", zone_path, npc_id_and_name), // Local by NPC ID and NPC Name
fmt::format("{}/{}", global_path, npc_id), // Global by NPC ID
fmt::format("{}/{}", global_path, npc_name), // Global by NPC ID
fmt::format("{}/default", zone_versioned_path), // Zone Default ./quests/zone/v0/default.ext
fmt::format("{}/{}", global_path, npc_id_and_name), // Global by NPC ID and NPC Name
fmt::format("{}/default", zone_versioned_path), // Zone Versioned Default (./quests/zone/v0/default.ext)
fmt::format("{}/default", zone_path), // Zone Default
fmt::format("{}/default", global_path), // Global Default
};
+2 -3
View File
@@ -1310,15 +1310,14 @@ void QuestManager::rename(std::string name) {
QuestManagerCurrentQuestVars();
if (initiator) {
std::string current_name = initiator->GetName();
if (initiator->ChangeFirstName(name.c_str(), current_name.c_str())) {
if (initiator->ChangeFirstName(name)) {
initiator->Message(
Chat::White,
fmt::format(
"Successfully renamed to {}, kicking to character select.",
"Successfully renamed to {}.",
name
).c_str()
);
initiator->Kick("Name was changed.");
} else {
initiator->Message(
Chat::Red,
+7
View File
@@ -277,6 +277,13 @@ bool Spawn2::Process() {
npcthis = npc;
if (!m_entity_variables.empty()) {
for (auto &var : m_entity_variables) {
npc->SetEntityVariable(var.first, var.second);
}
m_entity_variables = {};
}
npc->SetResumedFromZoneSuspend(m_resumed_from_zone_suspend);
m_resumed_from_zone_suspend = false;
+2
View File
@@ -77,6 +77,7 @@ public:
inline NPC *GetNPC() const { return npcthis; }
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
protected:
friend class Zone;
@@ -104,6 +105,7 @@ private:
bool IsDespawned;
uint32 killcount;
bool m_resumed_from_zone_suspend = false;
std::map<std::string, std::string> m_entity_variables = {};
};
class SpawnCondition {
+22
View File
@@ -1894,6 +1894,13 @@ void Client::SellToBuyer(const EQApplicationPacket *app)
break;
}
if (sell_line.purchase_method == BarterInBazaar && buyer->IsThereACustomer()) {
auto customer = entity_list.GetClientByID(buyer->GetCustomerID());
if (customer) {
customer->CancelBuyerTradeWindow();
}
}
if (!DoBarterBuyerChecks(sell_line)) {
return;
};
@@ -3825,3 +3832,18 @@ bool Client::DoBarterSellerChecks(BuyerLineSellItem_Struct &sell_line)
return true;
}
void Client::CancelBuyerTradeWindow()
{
auto end_session = new EQApplicationPacket(OP_Barter, sizeof(BuyerRemoveItemFromMerchantWindow_Struct));
auto data = reinterpret_cast<BuyerRemoveItemFromMerchantWindow_Struct *>(end_session->pBuffer);
data->action = Barter_BuyerInspectBegin;
FastQueuePacket(&end_session);
}
void Client::CancelTraderTradeWindow()
{
auto end_session = new EQApplicationPacket(OP_ShopEnd);
FastQueuePacket(&end_session);
}
+13
View File
@@ -3786,6 +3786,13 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
return;
}
if (trader_pc->IsThereACustomer()) {
auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID());
if (customer) {
customer->CancelTraderTradeWindow();
}
}
auto item_sn = Strings::ToUnsignedBigInt(in->trader_buy_struct.serial_number);
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, sizeof(TraderBuy_Struct));
auto data = (TraderBuy_Struct *) outapp->pBuffer;
@@ -3981,6 +3988,12 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
worldserver.SendPacket(pack);
return;
}
if (buyer->IsThereACustomer()) {
auto customer = entity_list.GetClientByID(buyer->GetCustomerID());
if (customer) {
customer->CancelBuyerTradeWindow();
}
}
BuyerLineSellItem_Struct sell_line{};
sell_line.item_id = in->buy_item_id;
+50 -26
View File
@@ -204,6 +204,33 @@ inline std::string GetLootSerialized(Corpse *c)
return "";
}
inline std::map<std::string, std::string> GetVariablesDeserialized(const std::string &entity_variables)
{
std::map<std::string, std::string> deserialized_map;
if (entity_variables.empty()) {
return deserialized_map;
}
if (!Strings::IsValidJson(entity_variables)) {
LogZoneState("Invalid JSON data for entity variables");
return deserialized_map;
}
try {
std::stringstream ss;
{
ss << entity_variables;
cereal::JSONInputArchive ar(ss);
ar(deserialized_map);
}
} catch (const std::exception &e) {
LogZoneState("Failed to load entity variables [{}]", e.what());
}
return deserialized_map;
}
inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables)
{
if (!RuleB(Zone, StateSaveEntityVariables)) {
@@ -214,25 +241,7 @@ inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables)
return;
}
if (!Strings::IsValidJson(entity_variables)) {
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
return;
}
std::map<std::string, std::string> deserialized_map;
try {
std::istringstream is(entity_variables);
{
cereal::JSONInputArchive archive(is);
archive(deserialized_map);
}
}
catch (const std::exception &e) {
LogZoneState("Failed to load entity variables for NPC [{}] [{}]", n->GetNPCTypeID(), e.what());
return;
}
for (const auto &[key, value]: deserialized_map) {
for (const auto &[key, value]: GetVariablesDeserialized(entity_variables)) {
n->SetEntityVariable(key, value);
}
}
@@ -310,11 +319,22 @@ inline std::vector<uint32_t> GetLootdropIds(const std::vector<ZoneStateSpawnsRep
return lootdrop_ids;
}
inline void LoadNPCStatePreSpawn(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
{
LoadNPCEntityVariables(n, s.entity_variables);
}
inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
{
n->SetHP(s.hp);
n->SetMana(s.mana);
n->SetEndurance(s.endurance);
if (s.hp > 0) {
n->SetHP(s.hp);
}
if (s.mana > 0) {
n->SetMana(s.mana);
}
if (s.endurance > 0) {
n->SetEndurance(s.endurance);
}
if (s.grid) {
n->AssignWaypoints(s.grid, s.current_waypoint);
@@ -323,7 +343,6 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
n->SetResumedFromZoneSuspend(false);
LoadLootStateData(zone, n, s.loot_data);
n->SetResumedFromZoneSuspend(true);
LoadNPCEntityVariables(n, s.entity_variables);
LoadNPCBuffs(n, s.buffs);
if (s.is_corpse) {
@@ -336,7 +355,9 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
n->Depop();
}
}
n->SetPosition(s.x, s.y, s.z);
n->SetHeading(s.heading);
n->SetResumedFromZoneSuspend(true);
}
@@ -397,7 +418,7 @@ bool Zone::LoadZoneState(
auto spawn_states = ZoneStateSpawnsRepository::GetWhere(
database,
fmt::format(
"zone_id = {} AND instance_id = {} ORDER BY spawn2_id",
"zone_id = {} AND instance_id = {} ORDER BY is_zone DESC, spawn2_id ASC",
zoneid,
zone->GetInstanceID()
)
@@ -469,6 +490,7 @@ bool Zone::LoadZoneState(
if (spawn_time_left == 0) {
new_spawn->SetCurrentNPCID(s.npc_id);
new_spawn->SetResumedFromZoneSuspend(true);
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
}
spawn2_list.Insert(new_spawn);
@@ -505,6 +527,8 @@ bool Zone::LoadZoneState(
npc->SetQueuedToCorpse();
}
LoadNPCStatePreSpawn(zone, npc, s);
entity_list.AddNPC(npc, true, true);
LoadNPCState(zone, npc, s);
@@ -558,7 +582,7 @@ inline void SaveNPCState(NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
auto buffs = n->GetBuffs();
if (buffs) {
std::vector<Buffs_Struct> valid_buffs;
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
if (buffs[index].spellid != 0 && buffs[index].spellid != 65535) {
valid_buffs.push_back(buffs[index]);
}