diff --git a/changelog.txt b/changelog.txt index 4b717ed5b..cf052e03d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,43 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 12/16/2018 == +Akkadius: + - Implemented: Global Base Scaling: https://github.com/EQEmu/Server/wiki/NPC-Scaling + - Implemented: Beginning of DevTools - more to be added over time (Status >= 200) + - Display NPC info on target + - Display Client info on target + - Display NPC show commands on target + - Loot + - Grids + - Emotes + - Implemented: Support for saylinks to be used in GM commands + - Deprecated: Commands + - #listnpcs + - Implemented: Commands + - #list [npcs|players|corpses|doors|objects] [search] + - Provides clickable saylinks to go to entities + - #scale [static/dynamic] (With targeted NPC) + - #scale [npc_name_search] [static/dynamic] (To make zone-wide changes) + - #scale all [static/dynamic] + - #devtools (alias #dev) + - Implemented: New Logging categories automatically injected into `logsys_categories` table + - Fixed an issue where newly injected categories were turned on by default + +== 12/15/2018 == +Kinglykrab: Added multiple new instance related quest functions. + 1. quest::GetInstanceIDByCharID(const char *zone, int16 version, uint32 char_id) + - Allows you to pull the instance ID of a client by character ID. + 2. quest::AssignToInstanceByCharID(uint16 instance_id, uint32 char_id) + - Allows you to assign an instance to a client by character ID. + 3. quest::RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id) + - Allows you to remove a client from an instance by character ID. + + Added spell buckets, similar to spell globals. + - Uses a new spell_buckets table and the Spells:EnableSpellBuckets rule. + + Added max level by data bucket. + - Uses data bucket char_id-CharMaxLevel and Character:PerCharacterBucketMaxLevel rule. + == 10/09/2018 == Uleat: Added bot owner options - usage: ^owneroption [option] (or aliased as: ^oo [option]) diff --git a/common/emu_constants.h b/common/emu_constants.h index db49252a3..4fbdd107d 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -77,6 +77,11 @@ namespace EQEmu } // namespace invtype + namespace popupresponse { + const int32 SERVER_INTERNAL_USE_BASE = 2000000000; + const int32 MOB_INFO_DISMISS = 2000000001; + } + namespace invslot { using namespace RoF2::invslot::enum_; diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index bde77822b..db97818a5 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -306,7 +306,6 @@ union uint32 DestructibleUnk9; bool targetable_with_hotkey; bool show_name; - }; struct PlayerState_Struct { diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index 9e99e6cbe..89728c809 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -112,7 +112,7 @@ void EQEmuLogSys::LoadLogSettingsDefaults() * Get Executable platform currently running this code (Zone/World/etc) */ log_platform = GetExecutablePlatformInt(); - + for (int log_category_id = Logs::AA; log_category_id != Logs::MaxCategoryID; log_category_id++) { log_settings[log_category_id].log_to_console = 0; log_settings[log_category_id].log_to_file = 0; @@ -122,11 +122,6 @@ void EQEmuLogSys::LoadLogSettingsDefaults() file_logs_enabled = false; - /** - * Zero out Array - */ - memset(log_settings, 0, sizeof(LogSettings) * Logs::LogCategory::MaxCategoryID); - /** * Set Defaults */ @@ -138,17 +133,20 @@ void EQEmuLogSys::LoadLogSettingsDefaults() log_settings[Logs::MySQLError].log_to_console = Logs::General; log_settings[Logs::Login_Server].log_to_console = Logs::General; log_settings[Logs::Headless_Client].log_to_console = Logs::General; + log_settings[Logs::NPCScaling].log_to_gmsay = Logs::General; /** * Set Category enabled status on defaults */ - log_settings[Logs::World_Server].is_category_enabled = 1; - log_settings[Logs::Zone_Server].is_category_enabled = 1; - log_settings[Logs::QS_Server].is_category_enabled = 1; - log_settings[Logs::UCS_Server].is_category_enabled = 1; - log_settings[Logs::Crash].is_category_enabled = 1; - log_settings[Logs::MySQLError].is_category_enabled = 1; - log_settings[Logs::Login_Server].is_category_enabled = 1; + for (int log_category_id = Logs::AA; log_category_id != Logs::MaxCategoryID; log_category_id++) { + const bool log_to_console = log_settings[log_category_id].log_to_console > 0; + const bool log_to_file = log_settings[log_category_id].log_to_file > 0; + const bool log_to_gmsay = log_settings[log_category_id].log_to_gmsay > 0; + const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay; + if (is_category_enabled) { + log_settings[log_category_id].is_category_enabled = 1; + } + } /** * Declare process file names for log writing= diff --git a/common/features.h b/common/features.h index 3f42b062d..6881e2e1b 100644 --- a/common/features.h +++ b/common/features.h @@ -272,10 +272,6 @@ enum { commandInvSnapshot = 150 //ability to clear/restore snapshots }; -//default states for logging flag on NPCs and clients (having NPCs on by default is prolly a bad idea) -#define CLIENT_DEFAULT_LOGGING_ENABLED true -#define NPC_DEFAULT_LOGGING_ENABLED false - // This is the item ID we use for say links, we use the max that fits in 5 ASCII chars #define SAYLINK_ITEM_ID 0xFFFFF diff --git a/common/net/packet.cpp b/common/net/packet.cpp index 4c7dc74b6..deaa07843 100644 --- a/common/net/packet.cpp +++ b/common/net/packet.cpp @@ -350,4 +350,5 @@ bool EQ::Net::StaticPacket::Resize(size_t new_size) } m_data_length = new_size; -} \ No newline at end of file + return true; +} diff --git a/common/rulesys.cpp b/common/rulesys.cpp index 3ce748fb9..61bb72357 100644 --- a/common/rulesys.cpp +++ b/common/rulesys.cpp @@ -382,15 +382,18 @@ bool RuleManager::ListRulesets(Database *database, std::map &i return true; } -int32 RuleManager::GetIntRule(RuleManager::IntType t) const{ - return(m_RuleIntValues[t]); +int32 RuleManager::GetIntRule(RuleManager::IntType t) const +{ + return (m_RuleIntValues[t]); } -float RuleManager::GetRealRule(RuleManager::RealType t) const{ - return(m_RuleRealValues[t]); +float RuleManager::GetRealRule(RuleManager::RealType t) const +{ + return (m_RuleRealValues[t]); } -bool RuleManager::GetBoolRule(RuleManager::BoolType t) const{ +bool RuleManager::GetBoolRule(RuleManager::BoolType t) const +{ return (m_RuleBoolValues[t] == 1); } diff --git a/common/ruletypes.h b/common/ruletypes.h index b76f3ab15..c8b927b4f 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -38,6 +38,7 @@ RULE_CATEGORY(Character) RULE_INT(Character, MaxLevel, 65) RULE_BOOL(Character, PerCharacterQglobalMaxLevel, false) // This will check for qglobal 'CharMaxLevel' character qglobal (Type 5), if player tries to level beyond that point, it will not go beyond that level +RULE_BOOL(Character, PerCharacterBucketMaxLevel, false) // This will check for data bucket 'CharMaxLevel', if player tries to level beyond that point, it will not go beyond that level RULE_INT(Character, MaxExpLevel, 0) //Sets the Max Level attainable via Experience RULE_INT(Character, DeathExpLossLevel, 10) // Any level greater than this will lose exp on death RULE_INT(Character, DeathExpLossMaxLevel, 255) // Any level greater than this will no longer lose exp on death @@ -343,7 +344,8 @@ RULE_INT(Spells, TranslocateTimeLimit, 0) // If not zero, time in seconds to acc RULE_INT(Spells, SacrificeMinLevel, 46) //first level Sacrifice will work on RULE_INT(Spells, SacrificeMaxLevel, 69) //last level Sacrifice will work on RULE_INT(Spells, SacrificeItemID, 9963) //Item ID of the item Sacrifice will return (defaults to an EE) -RULE_BOOL(Spells, EnableSpellGlobals, false) // If Enabled, spells check the spell_globals table and compare character data from the quest globals before allowing that spell to scribe with scribespells +RULE_BOOL(Spells, EnableSpellGlobals, false) // If Enabled, spells check the spell_globals table and compare character data from their quest globals before allowing the spell to scribe with scribespells/traindiscs +RULE_BOOL(Spells, EnableSpellBuckets, false) // If Enabled, spells check the spell_buckets table and compare character data from their data buckets before allowing the spell to scribe with scribespells/traindiscs RULE_INT(Spells, MaxBuffSlotsNPC, 60) // default to Tit's limit RULE_INT(Spells, MaxSongSlotsNPC, 0) // NPCs don't have songs ... RULE_INT(Spells, MaxDiscSlotsNPC, 0) // NPCs don't have discs ... @@ -510,6 +512,7 @@ RULE_BOOL(Combat, UseRevampHandToHand, false) // use h2h revamped dmg/delays I b RULE_BOOL(Combat, ClassicMasterWu, false) // classic master wu uses a random special, modern doesn't RULE_INT(Combat, LevelToStopDamageCaps, 0) // 1 will effectively disable them, 20 should give basically same results as old incorrect system RULE_BOOL(Combat, ClassicNPCBackstab, false) // true disables npc facestab - npcs get normal attack if not behind +RULE_BOOL(Combat, UseNPCDamageClassLevelMods, true) // Uses GetClassLevelDamageMod calc in npc_scale_manager RULE_CATEGORY_END() RULE_CATEGORY(NPC) diff --git a/common/say_link.cpp b/common/say_link.cpp index a215bdc46..4b8ddc009 100644 --- a/common/say_link.cpp +++ b/common/say_link.cpp @@ -26,30 +26,31 @@ #include "../zone/zonedb.h" -bool EQEmu::saylink::DegenerateLinkBody(SayLinkBody_Struct& say_link_body_struct, const std::string& say_link_body) +bool EQEmu::saylink::DegenerateLinkBody(SayLinkBody_Struct &say_link_body_struct, const std::string &say_link_body) { memset(&say_link_body_struct, 0, sizeof(say_link_body_struct)); - if (say_link_body.length() != EQEmu::constants::SAY_LINK_BODY_SIZE) + if (say_link_body.length() != EQEmu::constants::SAY_LINK_BODY_SIZE) { return false; + } - say_link_body_struct.action_id = (uint8)strtol(say_link_body.substr(0, 1).c_str(), nullptr, 16); - say_link_body_struct.item_id = (uint32)strtol(say_link_body.substr(1, 5).c_str(), nullptr, 16); - say_link_body_struct.augment_1 = (uint32)strtol(say_link_body.substr(6, 5).c_str(), nullptr, 16); - say_link_body_struct.augment_2 = (uint32)strtol(say_link_body.substr(11, 5).c_str(), nullptr, 16); - say_link_body_struct.augment_3 = (uint32)strtol(say_link_body.substr(16, 5).c_str(), nullptr, 16); - say_link_body_struct.augment_4 = (uint32)strtol(say_link_body.substr(21, 5).c_str(), nullptr, 16); - say_link_body_struct.augment_5 = (uint32)strtol(say_link_body.substr(26, 5).c_str(), nullptr, 16); - say_link_body_struct.augment_6 = (uint32)strtol(say_link_body.substr(31, 5).c_str(), nullptr, 16); - say_link_body_struct.is_evolving = (uint8)strtol(say_link_body.substr(36, 1).c_str(), nullptr, 16); - say_link_body_struct.evolve_group = (uint32)strtol(say_link_body.substr(37, 4).c_str(), nullptr, 16); - say_link_body_struct.evolve_level = (uint8)strtol(say_link_body.substr(41, 2).c_str(), nullptr, 16); - say_link_body_struct.ornament_icon = (uint32)strtol(say_link_body.substr(43, 5).c_str(), nullptr, 16); - say_link_body_struct.hash = (uint32)strtol(say_link_body.substr(48, 8).c_str(), nullptr, 16); + say_link_body_struct.action_id = (uint8) strtol(say_link_body.substr(0, 1).c_str(), nullptr, 16); + say_link_body_struct.item_id = (uint32) strtol(say_link_body.substr(1, 5).c_str(), nullptr, 16); + say_link_body_struct.augment_1 = (uint32) strtol(say_link_body.substr(6, 5).c_str(), nullptr, 16); + say_link_body_struct.augment_2 = (uint32) strtol(say_link_body.substr(11, 5).c_str(), nullptr, 16); + say_link_body_struct.augment_3 = (uint32) strtol(say_link_body.substr(16, 5).c_str(), nullptr, 16); + say_link_body_struct.augment_4 = (uint32) strtol(say_link_body.substr(21, 5).c_str(), nullptr, 16); + say_link_body_struct.augment_5 = (uint32) strtol(say_link_body.substr(26, 5).c_str(), nullptr, 16); + say_link_body_struct.augment_6 = (uint32) strtol(say_link_body.substr(31, 5).c_str(), nullptr, 16); + say_link_body_struct.is_evolving = (uint8) strtol(say_link_body.substr(36, 1).c_str(), nullptr, 16); + say_link_body_struct.evolve_group = (uint32) strtol(say_link_body.substr(37, 4).c_str(), nullptr, 16); + say_link_body_struct.evolve_level = (uint8) strtol(say_link_body.substr(41, 2).c_str(), nullptr, 16); + say_link_body_struct.ornament_icon = (uint32) strtol(say_link_body.substr(43, 5).c_str(), nullptr, 16); + say_link_body_struct.hash = (uint32) strtol(say_link_body.substr(48, 8).c_str(), nullptr, 16); return true; } -bool EQEmu::saylink::GenerateLinkBody(std::string& say_link_body, const SayLinkBody_Struct& say_link_body_struct) +bool EQEmu::saylink::GenerateLinkBody(std::string &say_link_body, const SayLinkBody_Struct &say_link_body_struct) { say_link_body = StringFormat( "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X", @@ -68,8 +69,9 @@ bool EQEmu::saylink::GenerateLinkBody(std::string& say_link_body, const SayLinkB (0xFFFFFFFF & say_link_body_struct.hash) ); - if (say_link_body.length() != EQEmu::constants::SAY_LINK_BODY_SIZE) + if (say_link_body.length() != EQEmu::constants::SAY_LINK_BODY_SIZE) { return false; + } return true; } @@ -79,7 +81,7 @@ EQEmu::SayLinkEngine::SayLinkEngine() Reset(); } -const std::string& EQEmu::SayLinkEngine::GenerateLink() +const std::string &EQEmu::SayLinkEngine::GenerateLink() { m_Link.clear(); m_LinkBody.clear(); @@ -97,7 +99,7 @@ const std::string& EQEmu::SayLinkEngine::GenerateLink() if ((m_Link.length() == 0) || (m_Link.length() > (EQEmu::constants::SAY_LINK_MAXIMUM_SIZE))) { m_Error = true; - m_Link = ""; + m_Link = ""; Log(Logs::General, Logs::Error, "SayLinkEngine::GenerateLink() failed to generate a useable say link"); Log(Logs::General, Logs::Error, ">> LinkType: %i, Lengths: {link: %u(%u), body: %u(%u), text: %u(%u)}", m_LinkType, @@ -145,83 +147,97 @@ void EQEmu::SayLinkEngine::generate_body() memset(&m_LinkBodyStruct, 0, sizeof(SayLinkBody_Struct)); - const EQEmu::ItemData* item_data = nullptr; + const EQEmu::ItemData *item_data = nullptr; switch (m_LinkType) { - case saylink::SayLinkBlank: - break; - case saylink::SayLinkItemData: - if (m_ItemData == nullptr) { break; } - m_LinkBodyStruct.item_id = m_ItemData->ID; - m_LinkBodyStruct.evolve_group = m_ItemData->LoreGroup; // this probably won't work for all items - //m_LinkBodyStruct.evolve_level = m_ItemData->EvolvingLevel; - // TODO: add hash call - break; - case saylink::SayLinkLootItem: - if (m_LootData == nullptr) { break; } - item_data = database.GetItem(m_LootData->item_id); - if (item_data == nullptr) { break; } - m_LinkBodyStruct.item_id = item_data->ID; - m_LinkBodyStruct.augment_1 = m_LootData->aug_1; - m_LinkBodyStruct.augment_2 = m_LootData->aug_2; - m_LinkBodyStruct.augment_3 = m_LootData->aug_3; - m_LinkBodyStruct.augment_4 = m_LootData->aug_4; - m_LinkBodyStruct.augment_5 = m_LootData->aug_5; - m_LinkBodyStruct.augment_6 = m_LootData->aug_6; - m_LinkBodyStruct.evolve_group = item_data->LoreGroup; // see note above - //m_LinkBodyStruct.evolve_level = item_data->EvolvingLevel; - // TODO: add hash call - break; - case saylink::SayLinkItemInst: - if (m_ItemInst == nullptr) { break; } - if (m_ItemInst->GetItem() == nullptr) { break; } - m_LinkBodyStruct.item_id = m_ItemInst->GetItem()->ID; - m_LinkBodyStruct.augment_1 = m_ItemInst->GetAugmentItemID(0); - m_LinkBodyStruct.augment_2 = m_ItemInst->GetAugmentItemID(1); - m_LinkBodyStruct.augment_3 = m_ItemInst->GetAugmentItemID(2); - m_LinkBodyStruct.augment_4 = m_ItemInst->GetAugmentItemID(3); - m_LinkBodyStruct.augment_5 = m_ItemInst->GetAugmentItemID(4); - m_LinkBodyStruct.augment_6 = m_ItemInst->GetAugmentItemID(5); - m_LinkBodyStruct.is_evolving = (m_ItemInst->IsEvolving() ? 1 : 0); - m_LinkBodyStruct.evolve_group = m_ItemInst->GetItem()->LoreGroup; // see note above - m_LinkBodyStruct.evolve_level = m_ItemInst->GetEvolveLvl(); - m_LinkBodyStruct.ornament_icon = m_ItemInst->GetOrnamentationIcon(); - // TODO: add hash call - break; - default: - break; + case saylink::SayLinkBlank: + break; + case saylink::SayLinkItemData: + if (m_ItemData == nullptr) { break; } + m_LinkBodyStruct.item_id = m_ItemData->ID; + m_LinkBodyStruct.evolve_group = m_ItemData->LoreGroup; // this probably won't work for all items + //m_LinkBodyStruct.evolve_level = m_ItemData->EvolvingLevel; + // TODO: add hash call + break; + case saylink::SayLinkLootItem: + if (m_LootData == nullptr) { break; } + item_data = database.GetItem(m_LootData->item_id); + if (item_data == nullptr) { break; } + m_LinkBodyStruct.item_id = item_data->ID; + m_LinkBodyStruct.augment_1 = m_LootData->aug_1; + m_LinkBodyStruct.augment_2 = m_LootData->aug_2; + m_LinkBodyStruct.augment_3 = m_LootData->aug_3; + m_LinkBodyStruct.augment_4 = m_LootData->aug_4; + m_LinkBodyStruct.augment_5 = m_LootData->aug_5; + m_LinkBodyStruct.augment_6 = m_LootData->aug_6; + m_LinkBodyStruct.evolve_group = item_data->LoreGroup; // see note above + //m_LinkBodyStruct.evolve_level = item_data->EvolvingLevel; + // TODO: add hash call + break; + case saylink::SayLinkItemInst: + if (m_ItemInst == nullptr) { break; } + if (m_ItemInst->GetItem() == nullptr) { break; } + m_LinkBodyStruct.item_id = m_ItemInst->GetItem()->ID; + m_LinkBodyStruct.augment_1 = m_ItemInst->GetAugmentItemID(0); + m_LinkBodyStruct.augment_2 = m_ItemInst->GetAugmentItemID(1); + m_LinkBodyStruct.augment_3 = m_ItemInst->GetAugmentItemID(2); + m_LinkBodyStruct.augment_4 = m_ItemInst->GetAugmentItemID(3); + m_LinkBodyStruct.augment_5 = m_ItemInst->GetAugmentItemID(4); + m_LinkBodyStruct.augment_6 = m_ItemInst->GetAugmentItemID(5); + m_LinkBodyStruct.is_evolving = (m_ItemInst->IsEvolving() ? 1 : 0); + m_LinkBodyStruct.evolve_group = m_ItemInst->GetItem()->LoreGroup; // see note above + m_LinkBodyStruct.evolve_level = m_ItemInst->GetEvolveLvl(); + m_LinkBodyStruct.ornament_icon = m_ItemInst->GetOrnamentationIcon(); + // TODO: add hash call + break; + default: + break; } - if (m_LinkProxyStruct.action_id) + if (m_LinkProxyStruct.action_id) { m_LinkBodyStruct.action_id = m_LinkProxyStruct.action_id; - if (m_LinkProxyStruct.item_id) + } + if (m_LinkProxyStruct.item_id) { m_LinkBodyStruct.item_id = m_LinkProxyStruct.item_id; - if (m_LinkProxyStruct.augment_1) + } + if (m_LinkProxyStruct.augment_1) { m_LinkBodyStruct.augment_1 = m_LinkProxyStruct.augment_1; - if (m_LinkProxyStruct.augment_2) + } + if (m_LinkProxyStruct.augment_2) { m_LinkBodyStruct.augment_2 = m_LinkProxyStruct.augment_2; - if (m_LinkProxyStruct.augment_3) + } + if (m_LinkProxyStruct.augment_3) { m_LinkBodyStruct.augment_3 = m_LinkProxyStruct.augment_3; - if (m_LinkProxyStruct.augment_4) + } + if (m_LinkProxyStruct.augment_4) { m_LinkBodyStruct.augment_4 = m_LinkProxyStruct.augment_4; - if (m_LinkProxyStruct.augment_5) + } + if (m_LinkProxyStruct.augment_5) { m_LinkBodyStruct.augment_5 = m_LinkProxyStruct.augment_5; - if (m_LinkProxyStruct.augment_6) + } + if (m_LinkProxyStruct.augment_6) { m_LinkBodyStruct.augment_6 = m_LinkProxyStruct.augment_6; - if (m_LinkProxyStruct.is_evolving) + } + if (m_LinkProxyStruct.is_evolving) { m_LinkBodyStruct.is_evolving = m_LinkProxyStruct.is_evolving; - if (m_LinkProxyStruct.evolve_group) + } + if (m_LinkProxyStruct.evolve_group) { m_LinkBodyStruct.evolve_group = m_LinkProxyStruct.evolve_group; - if (m_LinkProxyStruct.evolve_level) + } + if (m_LinkProxyStruct.evolve_level) { m_LinkBodyStruct.evolve_level = m_LinkProxyStruct.evolve_level; - if (m_LinkProxyStruct.ornament_icon) + } + if (m_LinkProxyStruct.ornament_icon) { m_LinkBodyStruct.ornament_icon = m_LinkProxyStruct.ornament_icon; - if (m_LinkProxyStruct.hash) + } + if (m_LinkProxyStruct.hash) { m_LinkBodyStruct.hash = m_LinkProxyStruct.hash; + } - if (m_TaskUse) + if (m_TaskUse) { m_LinkBodyStruct.hash = 0x14505DC2; + } m_LinkBody = StringFormat( "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X", @@ -248,29 +264,79 @@ void EQEmu::SayLinkEngine::generate_text() return; } - const EQEmu::ItemData* item_data = nullptr; + const EQEmu::ItemData *item_data = nullptr; switch (m_LinkType) { - case saylink::SayLinkBlank: - break; - case saylink::SayLinkItemData: - if (m_ItemData == nullptr) { break; } - m_LinkText = m_ItemData->Name; - return; - case saylink::SayLinkLootItem: - if (m_LootData == nullptr) { break; } - item_data = database.GetItem(m_LootData->item_id); - if (item_data == nullptr) { break; } - m_LinkText = item_data->Name; - return; - case saylink::SayLinkItemInst: - if (m_ItemInst == nullptr) { break; } - if (m_ItemInst->GetItem() == nullptr) { break; } - m_LinkText = m_ItemInst->GetItem()->Name; - return; - default: - break; + case saylink::SayLinkBlank: + break; + case saylink::SayLinkItemData: + if (m_ItemData == nullptr) { break; } + m_LinkText = m_ItemData->Name; + return; + case saylink::SayLinkLootItem: + if (m_LootData == nullptr) { break; } + item_data = database.GetItem(m_LootData->item_id); + if (item_data == nullptr) { break; } + m_LinkText = item_data->Name; + return; + case saylink::SayLinkItemInst: + if (m_ItemInst == nullptr) { break; } + if (m_ItemInst->GetItem() == nullptr) { break; } + m_LinkText = m_ItemInst->GetItem()->Name; + return; + default: + break; } m_LinkText = "null"; } + +std::string EQEmu::SayLinkEngine::GenerateQuestSaylink(std::string saylink_text, bool silent, std::string link_name) +{ + uint32 saylink_id = 0; + + /** + * Query for an existing phrase and id in the saylink table + */ + std::string query = StringFormat( + "SELECT `id` FROM `saylink` WHERE `phrase` = '%s' LIMIT 1", + EscapeString(saylink_text).c_str()); + + auto results = database.QueryDatabase(query); + + if (results.Success()) { + if (results.RowCount() >= 1) { + for (auto row = results.begin(); row != results.end(); ++row) + saylink_id = static_cast(atoi(row[0])); + } + else { + std::string insert_query = StringFormat( + "INSERT INTO `saylink` (`phrase`) VALUES ('%s')", + EscapeString(saylink_text).c_str()); + + results = database.QueryDatabase(insert_query); + if (!results.Success()) { + Log(Logs::General, Logs::Error, "Error in saylink phrase queries %s", results.ErrorMessage().c_str()); + } + else { + saylink_id = results.LastInsertedID(); + } + } + } + + /** + * Generate the actual link + */ + EQEmu::SayLinkEngine linker; + linker.SetProxyItemID(SAYLINK_ITEM_ID); + if (silent) { + linker.SetProxyAugment2ID(saylink_id); + } + else { + linker.SetProxyAugment1ID(saylink_id); + } + + linker.SetProxyText(link_name.c_str()); + + return linker.GenerateLink(); +} \ No newline at end of file diff --git a/common/say_link.h b/common/say_link.h index 55e3a8237..c282bde05 100644 --- a/common/say_link.h +++ b/common/say_link.h @@ -101,6 +101,8 @@ namespace EQEmu const std::string& LinkBody() { return m_LinkBody; } // contains string format: '' const std::string& LinkText() { return m_LinkText; } // contains string format: '' + static std::string GenerateQuestSaylink(std::string saylink_text, bool silent, std::string link_name); + void Reset(); private: diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 9e0cd5f7e..c4dd86f32 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1175,19 +1175,17 @@ void SharedDatabase::LoadItems(void *data, uint32 size, int32 items, uint32 max_ } } -const EQEmu::ItemData* SharedDatabase::GetItem(uint32 id) { - if (id == 0) - { +const EQEmu::ItemData *SharedDatabase::GetItem(uint32 id) +{ + if (id == 0) { return nullptr; } - if(!items_hash || id > items_hash->max_key()) - { + if (!items_hash || id > items_hash->max_key()) { return nullptr; } - if(items_hash->exists(id)) - { + if (items_hash->exists(id)) { return &(items_hash->at(id)); } diff --git a/common/spdat.cpp b/common/spdat.cpp index a5764a625..cb41c5158 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1122,6 +1122,20 @@ bool IsStackableDot(uint16 spell_id) return IsEffectInSpell(spell_id, SE_CurrentHP) || IsEffectInSpell(spell_id, SE_GravityEffect); } +bool IsBardOnlyStackEffect(int effect) +{ + switch(effect) { + case SE_CurrentMana: + case SE_ManaRegen_v2: + case SE_CurrentHP: + case SE_HealOverTime: + case SE_BardAEDot: + return true; + default: + return false; + } +} + bool IsCastWhileInvis(uint16 spell_id) { if (!IsValidSpell(spell_id)) diff --git a/common/spdat.h b/common/spdat.h index c6aab7e7e..cb9a301d8 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -968,6 +968,7 @@ uint32 GetPartialMeleeRuneAmount(uint32 spell_id); uint32 GetPartialMagicRuneAmount(uint32 spell_id); bool NoDetrimentalSpellAggro(uint16 spell_id); bool IsStackableDot(uint16 spell_id); +bool IsBardOnlyStackEffect(int effect); bool IsCastWhileInvis(uint16 spell_id); bool IsEffectIgnoredInStacking(int spa); diff --git a/common/string_util.cpp b/common/string_util.cpp index 3e9986b48..508304ce1 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -63,11 +63,50 @@ const std::string vStringFormat(const char* format, va_list args) return output; } -const std::string StringFormat(const char* format, ...) +const std::string str_tolower(std::string s) +{ + std::transform( + s.begin(), s.end(), s.begin(), + [](unsigned char c) { return ::tolower(c); } + ); + return s; +} + +std::vector split(std::string str_to_split, char delimiter) +{ + std::stringstream ss(str_to_split); + std::string item; + std::vector exploded_values; + while (std::getline(ss, item, delimiter)) { + exploded_values.push_back(item); + } + + return exploded_values; +} + +const std::string str_toupper(std::string s) +{ + std::transform( + s.begin(), s.end(), s.begin(), + [](unsigned char c) { return ::toupper(c); } + ); + return s; +} + +const std::string ucfirst(std::string s) +{ + std::string output = s; + if (!s.empty()) + output[0] = static_cast(::toupper(s[0])); + + return output; +} + +const std::string StringFormat(const char *format, ...) { va_list args; va_start(args, format); - std::string output = vStringFormat(format,args); + std::string output = vStringFormat(format, args); va_end(args); return output; } @@ -188,12 +227,18 @@ std::string JoinString(const std::vector& ar, const std::string &de return ret; } -void find_replace(std::string& string_subject, const std::string& search_string, const std::string& replace_string) { - auto index = string_subject.find_first_of(search_string); - while (index != std::string::npos) { - string_subject.replace(index, index + 1, replace_string); - index = string_subject.find_first_of(search_string); +void find_replace(std::string &string_subject, const std::string &search_string, const std::string &replace_string) +{ + if (string_subject.find(search_string) == std::string::npos) { + return; } + + size_t start_pos = 0; + while((start_pos = string_subject.find(search_string, start_pos)) != std::string::npos) { + string_subject.replace(start_pos, search_string.length(), replace_string); + start_pos += replace_string.length(); + } + } //Const char based diff --git a/common/string_util.h b/common/string_util.h index 0db8ff2c9..03848813c 100644 --- a/common/string_util.h +++ b/common/string_util.h @@ -24,6 +24,10 @@ #include "types.h" //std::string based +const std::string str_tolower(std::string s); +const std::string str_toupper(std::string s); +const std::string ucfirst(std::string s); +std::vector split(std::string str_to_split, char delimiter); const std::string StringFormat(const char* format, ...); const std::string vStringFormat(const char* format, va_list args); std::vector SplitString(const std::string &s, char delim); diff --git a/common/version.h b/common/version.h index e5603c247..157a82139 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9130 +#define CURRENT_BINARY_DATABASE_VERSION 9133 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9021 #else diff --git a/loginserver/options.h b/loginserver/options.h index f215e443b..5aecbad0a 100644 --- a/loginserver/options.h +++ b/loginserver/options.h @@ -116,17 +116,17 @@ public: inline void AccountTable(std::string t) { account_table = t; } /** - * Return the value of local_network. + * Return the value of world account table. */ inline std::string GetAccountTable() const { return account_table; } /** - * Sets world account table. + * Sets world registration table. */ inline void WorldRegistrationTable(std::string t) { world_registration_table = t; } /** - * Return the value of world account table. + * Return the value of world registration table. */ inline std::string GetWorldRegistrationTable() const { return world_registration_table; } @@ -146,7 +146,7 @@ public: inline void WorldServerTypeTable(std::string t) { world_server_type_table = t; } /** - * Return the value of world admin account table. + * Return the value of world server type table. */ inline std::string GetWorldServerTypeTable() const { return world_server_type_table; } diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 017407752..7850a0d42 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -383,8 +383,10 @@ 9127|2018_09_07_NPCMaxAggroDist.sql|SHOW COLUMNS FROM `zone` LIKE 'npc_max_aggro_dist'|empty| 9128|2018_08_13_inventory_version_update.sql|SHOW TABLES LIKE 'inventory_version'|not_empty| 9129|2018_08_13_inventory_update.sql|SHOW TABLES LIKE 'inventory_versions'|empty| -9130|2018_11_25_name_filter_update.sql|SHOW COLUMS FROM `name_filter` LIKE 'id'|empty| -9131|2018_11_25_StuckBehavior.sql|SHOW COLUMNS FROM `npc_types` LIKE 'stuck_behavior'|empty| +9130|2018_11_25_name_filter_update.sql|SHOW COLUMNS FROM `name_filter` LIKE 'id'|empty| +9131|2018_12_13_spell_buckets.sql|SHOW TABLES LIKE 'spell_buckets'|empty| +9132|2018_12_16_global_base_scaling.sql|SHOW TABLES LIKE 'npc_scale_global_base'|empty| +9133|2018_11_25_StuckBehavior.sql|SHOW COLUMNS FROM `npc_types` LIKE 'stuck_behavior'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2018_12_13_spell_buckets.sql b/utils/sql/git/required/2018_12_13_spell_buckets.sql new file mode 100644 index 000000000..1c56799f0 --- /dev/null +++ b/utils/sql/git/required/2018_12_13_spell_buckets.sql @@ -0,0 +1,10 @@ +CREATE TABLE `spell_buckets` ( + `spellid` bigint(11) unsigned NOT NULL, + `key` varchar(100) DEFAULT NULL, + `value` text, + PRIMARY KEY (`spellid`), + KEY `key_index` (`key`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:EnableSpellBuckets', 'false', 'Enables spell buckets'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Character:PerCharacterBucketMaxLevel', 'false', 'Enables data bucket-based max level.'); \ No newline at end of file diff --git a/utils/sql/git/required/2018_12_16_global_base_scaling.sql b/utils/sql/git/required/2018_12_16_global_base_scaling.sql new file mode 100644 index 000000000..2c07d1fb6 --- /dev/null +++ b/utils/sql/git/required/2018_12_16_global_base_scaling.sql @@ -0,0 +1,314 @@ +-- INSERT #devtools / #dev command + +INSERT INTO `command_settings` (`command`, `access`, `aliases`) +VALUES + ('devtools', 200, 'dev'); + +-- CREATE 'npc_scale_global_base' + +CREATE TABLE `npc_scale_global_base` ( + `type` int(11) NOT NULL DEFAULT '0', + `level` int(11) NOT NULL, + `ac` int(11) DEFAULT NULL, + `hp` int(11) DEFAULT NULL, + `accuracy` int(11) DEFAULT NULL, + `slow_mitigation` int(11) DEFAULT NULL, + `attack` int(11) DEFAULT NULL, + `strength` int(11) DEFAULT NULL, + `stamina` int(11) DEFAULT NULL, + `dexterity` int(11) DEFAULT NULL, + `agility` int(11) DEFAULT NULL, + `intelligence` int(11) DEFAULT NULL, + `wisdom` int(11) DEFAULT NULL, + `charisma` int(11) DEFAULT NULL, + `magic_resist` int(11) DEFAULT NULL, + `cold_resist` int(11) DEFAULT NULL, + `fire_resist` int(11) DEFAULT NULL, + `poison_resist` int(11) DEFAULT NULL, + `disease_resist` int(11) DEFAULT NULL, + `corruption_resist` int(11) DEFAULT NULL, + `physical_resist` int(11) DEFAULT NULL, + `min_dmg` int(11) DEFAULT NULL, + `max_dmg` int(11) DEFAULT NULL, + `hp_regen_rate` int(11) DEFAULT NULL, + `attack_delay` int(11) DEFAULT NULL, + `spell_scale` int(11) DEFAULT '100', + `heal_scale` int(11) DEFAULT '100', + `special_abilities` text, + PRIMARY KEY (`type`,`level`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- INSERT 'npc_scale_global_base' + +INSERT INTO `npc_scale_global_base` (`type`, `level`, `ac`, `hp`, `accuracy`, `slow_mitigation`, `attack`, `strength`, `stamina`, `dexterity`, `agility`, `intelligence`, `wisdom`, `charisma`, `magic_resist`, `cold_resist`, `fire_resist`, `poison_resist`, `disease_resist`, `corruption_resist`, `physical_resist`, `min_dmg`, `max_dmg`, `hp_regen_rate`, `attack_delay`, `spell_scale`, `heal_scale`, `special_abilities`) +VALUES + (0,1,8,11,0,0,0,8,8,8,8,8,8,8,1,1,1,1,1,1,10,1,6,1,30,100,100,''), + (0,2,11,27,0,0,0,11,11,11,11,11,11,11,1,1,1,1,1,2,10,1,8,2,30,100,100,''), + (0,3,14,43,0,0,0,14,14,14,14,14,14,14,2,2,2,2,2,2,10,1,10,3,30,100,100,''), + (0,4,16,59,0,0,0,17,17,17,17,17,17,17,2,2,2,2,2,3,10,1,12,4,30,100,100,''), + (0,5,19,75,0,0,0,20,20,20,20,20,20,20,2,2,2,2,2,3,10,1,14,5,30,100,100,''), + (0,6,22,100,0,0,0,23,23,23,23,23,23,23,2,2,2,2,2,4,10,1,16,6,30,100,100,''), + (0,7,25,125,0,0,0,26,26,26,26,26,26,26,3,3,3,3,3,4,10,1,18,7,30,100,100,''), + (0,8,28,150,0,0,0,29,29,29,29,29,29,29,3,3,3,3,3,5,10,1,20,8,30,100,100,''), + (0,9,31,175,0,0,0,32,32,32,32,32,32,32,4,4,4,4,4,5,10,1,22,9,30,100,100,''), + (0,10,34,200,0,0,0,35,35,35,35,35,35,35,4,4,4,4,4,6,10,1,24,10,30,100,100,''), + (0,11,37,234,0,0,0,38,38,38,38,38,38,38,5,5,5,5,5,7,10,3,27,11,30,100,100,''), + (0,12,40,268,0,0,0,42,42,42,42,42,42,42,5,5,5,5,5,8,10,4,30,12,30,100,100,''), + (0,13,43,302,0,0,0,45,45,45,45,45,45,45,6,6,6,6,6,8,10,6,32,13,30,100,100,''), + (0,14,46,336,0,0,0,48,48,48,48,48,48,48,6,6,6,6,6,9,10,7,35,14,30,100,100,''), + (0,15,52,381,0,0,0,51,51,51,51,51,51,51,6,6,6,6,6,10,10,7,37,15,30,100,100,''), + (0,16,59,426,0,0,0,54,54,54,54,54,54,54,7,7,7,7,7,10,10,8,39,16,30,100,100,''), + (0,17,65,471,0,0,0,57,57,57,57,57,57,57,7,7,7,7,7,11,10,8,41,17,30,100,100,''), + (0,18,72,516,0,0,0,60,60,60,60,60,60,60,7,7,7,7,7,11,10,9,42,18,30,100,100,''), + (0,19,78,561,0,0,0,63,63,63,63,63,63,63,7,7,7,7,7,12,10,9,44,19,30,100,100,''), + (0,20,85,606,0,0,0,66,66,66,66,66,66,66,8,8,8,8,8,12,10,10,46,20,30,100,100,''), + (0,21,91,651,0,0,0,69,69,69,69,69,69,69,8,8,8,8,8,13,10,10,48,21,30,100,100,''), + (0,22,95,712,0,0,0,72,72,72,72,72,72,72,8,8,8,8,8,14,10,10,50,22,30,100,100,''), + (0,23,99,773,0,0,0,75,75,75,75,75,75,75,9,9,9,9,9,14,10,10,52,23,30,100,100,''), + (0,24,103,834,0,0,0,78,78,78,78,78,78,78,9,9,9,9,9,15,10,11,55,24,30,100,100,''), + (0,25,107,895,0,0,0,81,81,81,81,81,81,81,10,10,10,10,10,16,10,11,57,25,30,100,100,''), + (0,26,111,956,0,0,0,85,85,85,85,85,85,85,10,10,10,10,10,16,10,11,59,26,30,100,100,''), + (0,27,115,1017,0,0,0,88,88,88,88,88,88,88,11,11,11,11,11,17,10,11,61,27,30,100,100,''), + (0,28,119,1078,0,0,0,91,91,91,91,91,91,91,11,11,11,11,11,18,10,12,64,28,30,100,100,''), + (0,29,123,1139,0,0,0,94,94,94,94,94,94,94,12,12,12,12,12,18,10,12,66,29,30,100,100,''), + (0,30,127,1200,0,0,0,97,97,97,97,97,97,97,12,12,12,12,12,19,10,12,68,30,30,100,100,''), + (0,31,135,1580,4,0,4,104,104,104,104,104,104,104,13,13,13,13,13,20,10,14,74,31,30,100,100,''), + (0,32,142,1960,8,0,8,110,110,110,110,110,110,110,14,14,14,14,14,22,10,16,79,32,30,100,100,''), + (0,33,150,2340,12,0,12,117,117,117,117,117,117,117,15,15,15,15,15,23,10,18,85,33,29,100,100,''), + (0,34,158,2720,16,0,16,123,123,123,123,123,123,123,16,16,16,16,16,25,10,20,90,34,28,100,100,''), + (0,35,166,3100,20,0,20,130,130,130,130,130,130,130,17,17,17,17,17,26,10,22,96,35,27,100,100,''), + (0,36,173,3480,24,0,24,137,137,137,137,137,137,137,17,17,17,17,17,27,10,24,102,36,25,100,100,''), + (0,37,181,3860,28,0,28,143,143,143,143,143,143,143,18,18,18,18,18,29,10,26,107,37,24,100,100,''), + (0,38,189,4240,32,0,32,150,150,150,150,150,150,150,19,19,19,19,19,30,10,28,113,38,23,100,100,''), + (0,39,196,4620,36,0,36,156,156,156,156,156,156,156,20,20,20,20,20,32,10,30,118,39,22,100,100,''), + (0,40,204,5000,40,0,40,163,163,163,163,163,163,163,21,21,21,21,21,33,10,32,124,40,21,100,100,''), + (0,41,208,5300,42,0,42,166,166,166,166,166,166,166,22,22,22,22,22,34,10,33,127,41,21,100,100,''), + (0,42,212,5600,44,0,44,169,169,169,169,169,169,169,22,22,22,22,22,35,10,34,130,42,21,100,100,''), + (0,43,217,5900,46,0,46,173,173,173,173,173,173,173,23,23,23,23,23,35,10,34,133,43,21,100,100,''), + (0,44,221,6200,48,0,48,176,176,176,176,176,176,176,23,23,23,23,23,36,10,35,136,44,21,100,100,''), + (0,45,225,6500,50,0,50,179,179,179,179,179,179,179,24,24,24,24,24,37,10,36,139,45,21,100,100,''), + (0,46,229,7200,50,0,50,182,182,182,182,182,182,182,24,24,24,24,24,38,10,44,152,46,21,100,100,''), + (0,47,233,7900,50,0,50,185,185,185,185,185,185,185,25,25,25,25,25,39,10,51,165,47,21,100,100,''), + (0,48,237,8600,50,0,50,188,188,188,188,188,188,188,25,25,25,25,25,39,10,59,178,48,21,100,100,''), + (0,49,241,9300,50,0,50,191,191,191,191,191,191,191,26,26,26,26,26,40,10,66,191,49,21,100,100,''), + (0,50,245,10000,50,0,50,194,194,194,194,194,194,194,26,26,26,26,26,41,10,74,204,50,21,100,100,''), + (0,51,249,11700,53,0,53,197,197,197,197,197,197,197,27,27,27,27,27,42,11,78,231,51,20,100,100,''), + (0,52,253,13400,56,0,56,200,200,200,200,200,200,200,27,27,27,27,27,43,12,81,258,52,20,100,100,''), + (0,53,257,15100,59,0,59,203,203,203,203,203,203,203,28,28,28,28,28,43,13,85,284,53,20,100,100,''), + (0,54,261,16800,62,10,62,206,206,206,206,206,206,206,28,28,28,28,28,44,14,89,311,54,20,100,100,''), + (0,55,266,18500,65,10,65,210,210,210,210,210,210,210,29,29,29,29,29,45,15,93,338,55,20,100,100,''), + (0,56,270,20200,68,10,68,213,213,213,213,213,213,213,29,29,29,29,29,46,16,96,365,56,20,100,100,''), + (0,57,274,21900,71,10,71,216,216,216,216,216,216,216,30,30,30,30,30,47,17,100,392,57,19,100,100,''), + (0,58,278,23600,74,10,74,219,219,219,219,219,219,219,30,30,30,30,30,47,18,104,418,58,19,100,100,'8,1'), + (0,59,282,25300,77,10,77,222,222,222,222,222,222,222,31,31,31,31,31,48,19,107,445,59,19,100,100,'8,1'), + (0,60,286,27000,80,20,80,225,225,225,225,225,225,225,31,31,31,31,31,49,20,111,472,60,19,100,100,'8,1^21,1'), + (0,61,290,28909,85,20,84,228,228,228,228,228,228,228,32,32,32,32,32,50,24,128,536,61,19,100,100,'8,1^21,1'), + (0,62,294,30818,91,20,87,231,231,231,231,231,231,231,32,32,32,32,32,51,28,145,599,62,18,100,100,'8,1^21,1'), + (0,63,299,32727,96,20,91,234,234,234,234,234,234,234,33,33,33,33,33,51,32,162,663,63,18,100,100,'8,1^21,1'), + (0,64,303,34636,102,20,95,237,237,237,237,237,237,237,33,33,33,33,33,52,36,179,727,64,18,100,100,'8,1^21,1'), + (0,65,307,36545,107,25,98,240,240,240,240,240,240,240,34,34,34,34,34,53,40,196,790,65,18,100,100,'8,1^21,1'), + (0,66,311,38455,113,25,102,244,244,244,244,244,244,244,34,34,34,34,34,54,44,213,854,66,18,100,100,'8,1^21,1'), + (0,67,315,40364,118,25,105,247,247,247,247,247,247,247,35,35,35,35,35,55,48,230,917,67,17,100,100,'8,1^21,1'), + (0,68,319,42273,124,25,109,250,250,250,250,250,250,250,35,35,35,35,35,56,52,247,981,68,17,100,100,'8,1^21,1'), + (0,69,324,44182,129,25,113,253,253,253,253,253,253,253,36,36,36,36,36,56,56,264,1045,69,17,100,100,'8,1^21,1'), + (0,70,328,46091,135,30,116,256,256,256,256,256,256,256,36,36,36,36,36,57,60,281,1108,70,17,100,100,'8,1^21,1'), + (0,71,332,48000,140,30,120,259,259,259,259,259,259,259,37,37,37,37,37,58,64,298,1172,71,17,100,100,'8,1^21,1'), + (0,72,336,49909,143,30,128,262,262,262,262,262,262,262,38,38,38,38,38,59,68,305,1193,72,17,100,100,'8,1^21,1'), + (0,73,340,51818,145,30,135,265,265,265,265,265,265,265,39,39,39,39,39,60,72,312,1214,73,17,100,100,'8,1^21,1'), + (0,74,344,53727,148,30,143,268,268,268,268,268,268,268,39,39,39,39,39,61,76,318,1235,74,17,100,100,'8,1^21,1'), + (0,75,348,55636,150,30,150,271,271,271,271,271,271,271,40,40,40,40,40,62,80,325,1256,75,17,100,100,'8,1^21,1'), + (0,76,352,75000,160,30,160,274,274,274,274,274,274,274,41,41,41,41,41,63,84,400,1600,76,17,100,100,'8,1^21,1'), + (0,77,356,90000,170,30,170,277,277,277,277,277,277,277,42,42,42,42,42,64,88,500,2050,77,17,100,100,'8,1^21,1'), + (0,78,360,113000,180,30,180,280,280,280,280,280,280,280,43,43,43,43,43,65,92,594,2323,120,17,100,100,'8,1^21,1'), + (0,79,364,130000,190,30,190,283,283,283,283,283,283,283,44,44,44,44,44,66,96,650,2500,130,17,100,100,'8,1^21,1'), + (0,80,368,140000,200,30,200,286,286,286,286,286,286,286,45,45,45,45,45,67,100,720,2799,140,16,100,100,'8,1^21,1'), + (0,81,372,240000,300,30,300,289,289,289,289,289,289,289,46,46,46,46,46,68,104,800,3599,240,16,100,100,'8,1^21,1'), + (0,82,376,340000,400,30,400,292,292,292,292,292,292,292,47,47,47,47,47,69,108,900,4599,340,16,100,100,'8,1^21,1'), + (0,83,380,440000,410,30,410,295,295,295,295,295,295,295,48,48,48,48,48,70,112,1275,4904,440,16,100,100,'8,1^21,1'), + (0,84,384,445000,420,30,420,298,298,298,298,298,298,298,49,49,49,49,49,71,116,1300,5100,445,16,100,100,'8,1^21,1'), + (0,85,388,450000,430,30,430,301,301,301,301,301,301,301,50,50,50,50,50,72,120,1359,5292,450,16,100,100,'8,1^21,1'), + (0,86,392,455000,440,30,440,304,304,304,304,304,304,304,51,51,51,51,51,73,124,1475,5578,455,16,100,100,'8,1^21,1'), + (0,87,396,460000,450,30,450,307,307,307,307,307,307,307,52,52,52,52,52,74,128,1510,5918,460,16,100,100,'8,1^21,1'), + (0,88,400,465000,460,30,460,310,310,310,310,310,310,310,53,53,53,53,53,75,132,1610,6200,465,16,100,100,'8,1^21,1'), + (0,89,404,470000,470,30,470,313,313,313,313,313,313,313,54,54,54,54,54,76,136,1650,6275,470,16,100,100,'8,1^21,1'), + (0,90,408,475000,480,30,480,316,316,316,316,316,316,316,55,55,55,55,55,77,140,1700,6350,475,16,100,100,'8,1^21,1'), + (1,1,10,13,0,0,0,10,10,10,10,10,10,10,1,1,1,1,1,1,12,1,7,1,30,100,100,'13,1^14,1^21,1'), + (1,2,13,32,0,0,0,13,13,13,13,13,13,13,1,1,1,1,1,2,12,1,10,2,30,100,100,'13,1^14,1^21,1'), + (1,3,17,52,0,0,0,17,17,17,17,17,17,17,2,2,2,2,2,2,12,1,12,4,30,100,100,'13,1^14,1^21,1'), + (1,4,19,71,0,0,0,20,20,20,20,20,20,20,2,2,2,2,2,4,12,1,14,5,30,100,100,'13,1^14,1^21,1'), + (1,5,23,90,0,0,0,24,24,24,24,24,24,24,2,2,2,2,2,4,12,1,17,6,30,100,100,'13,1^14,1^21,1'), + (1,6,26,120,0,0,0,28,28,28,28,28,28,28,2,2,2,2,2,5,12,1,19,7,30,100,100,'13,1^14,1^21,1'), + (1,7,30,150,0,0,0,31,31,31,31,31,31,31,4,4,4,4,4,5,12,1,22,8,30,100,100,'13,1^14,1^21,1'), + (1,8,34,180,0,0,0,35,35,35,35,35,35,35,4,4,4,4,4,6,12,1,24,10,30,100,100,'13,1^14,1^21,1'), + (1,9,37,210,0,0,0,38,38,38,38,38,38,38,5,5,5,5,5,6,12,1,26,11,30,100,100,'13,1^14,1^21,1'), + (1,10,41,240,0,0,0,42,42,42,42,42,42,42,5,5,5,5,5,7,12,1,29,12,30,100,100,'13,1^14,1^21,1'), + (1,11,44,281,0,0,0,46,46,46,46,46,46,46,6,6,6,6,6,8,12,4,32,13,30,100,100,'13,1^14,1^21,1'), + (1,12,48,322,0,0,0,50,50,50,50,50,50,50,6,6,6,6,6,10,12,5,36,14,30,100,100,'13,1^14,1^21,1'), + (1,13,52,362,0,0,0,54,54,54,54,54,54,54,7,7,7,7,7,10,12,7,38,16,30,100,100,'13,1^14,1^21,1'), + (1,14,55,403,0,0,0,58,58,58,58,58,58,58,7,7,7,7,7,11,12,8,42,17,30,100,100,'13,1^14,1^21,1'), + (1,15,62,457,0,0,0,61,61,61,61,61,61,61,7,7,7,7,7,12,12,8,44,18,30,100,100,'13,1^14,1^21,1'), + (1,16,71,511,0,0,0,65,65,65,65,65,65,65,8,8,8,8,8,12,12,10,47,19,30,100,100,'13,1^14,1^21,1'), + (1,17,78,565,0,0,0,68,68,68,68,68,68,68,8,8,8,8,8,13,12,10,49,20,30,100,100,'13,1^14,1^21,1'), + (1,18,86,619,0,0,0,72,72,72,72,72,72,72,8,8,8,8,8,13,12,11,50,22,30,100,100,'13,1^14,1^21,1'), + (1,19,94,673,0,0,0,76,76,76,76,76,76,76,8,8,8,8,8,14,12,11,53,23,30,100,100,'13,1^14,1^21,1'), + (1,20,102,727,0,0,0,79,79,79,79,79,79,79,10,10,10,10,10,14,12,12,55,24,30,100,100,'13,1^14,1^21,1'), + (1,21,109,781,0,0,0,83,83,83,83,83,83,83,10,10,10,10,10,16,12,12,58,25,30,100,100,'13,1^14,1^21,1'), + (1,22,114,854,0,0,0,86,86,86,86,86,86,86,10,10,10,10,10,17,12,12,60,26,30,100,100,'13,1^14,1^21,1'), + (1,23,119,928,0,0,0,90,90,90,90,90,90,90,11,11,11,11,11,17,12,12,62,28,30,100,100,'13,1^14,1^21,1'), + (1,24,124,1001,0,0,0,94,94,94,94,94,94,94,11,11,11,11,11,18,12,13,66,29,30,100,100,'13,1^14,1^21,1'), + (1,25,128,1074,0,0,0,97,97,97,97,97,97,97,12,12,12,12,12,19,12,13,68,30,30,100,100,'13,1^14,1^21,1'), + (1,26,133,1147,0,0,0,102,102,102,102,102,102,102,12,12,12,12,12,19,12,13,71,31,30,100,100,'13,1^14,1^21,1'), + (1,27,138,1220,0,0,0,106,106,106,106,106,106,106,13,13,13,13,13,20,12,13,73,32,30,100,100,'13,1^14,1^21,1'), + (1,28,143,1294,0,0,0,109,109,109,109,109,109,109,13,13,13,13,13,22,12,14,77,34,30,100,100,'13,1^14,1^21,1'), + (1,29,148,1367,0,0,0,113,113,113,113,113,113,113,14,14,14,14,14,22,12,14,79,35,30,100,100,'13,1^14,1^21,1'), + (1,30,152,1440,0,0,0,116,116,116,116,116,116,116,14,14,14,14,14,23,12,14,82,36,30,100,100,'13,1^14,1^21,1'), + (1,31,162,1896,5,0,5,125,125,125,125,125,125,125,16,16,16,16,16,24,12,17,89,37,30,100,100,'13,1^14,1^21,1'), + (1,32,170,2352,10,0,10,132,132,132,132,132,132,132,17,17,17,17,17,26,12,19,95,38,30,100,100,'13,1^14,1^21,1'), + (1,33,180,2808,14,0,14,140,140,140,140,140,140,140,18,18,18,18,18,28,12,22,102,40,29,100,100,'13,1^14,1^21,1'), + (1,34,190,3264,19,0,19,148,148,148,148,148,148,148,19,19,19,19,19,30,12,24,108,41,28,100,100,'13,1^14,1^21,1'), + (1,35,199,3720,24,0,24,156,156,156,156,156,156,156,20,20,20,20,20,31,12,26,115,42,27,100,100,'13,1^14,1^21,1'), + (1,36,208,4176,29,0,29,164,164,164,164,164,164,164,20,20,20,20,20,32,12,29,122,43,25,100,100,'13,1^14,1^21,1'), + (1,37,217,4632,34,0,34,172,172,172,172,172,172,172,22,22,22,22,22,35,12,31,128,44,24,100,100,'13,1^14,1^21,1'), + (1,38,227,5088,38,0,38,180,180,180,180,180,180,180,23,23,23,23,23,36,12,34,136,46,23,100,100,'13,1^14,1^21,1'), + (1,39,235,5544,43,0,43,187,187,187,187,187,187,187,24,24,24,24,24,38,12,36,142,47,22,100,100,'13,1^14,1^21,1'), + (1,40,245,6000,48,0,48,196,196,196,196,196,196,196,25,25,25,25,25,40,12,38,149,48,21,100,100,'13,1^14,1^21,1'), + (1,41,250,6360,50,0,50,199,199,199,199,199,199,199,26,26,26,26,26,41,12,40,152,49,21,100,100,'13,1^14,1^21,1'), + (1,42,254,6720,53,0,53,203,203,203,203,203,203,203,26,26,26,26,26,42,12,41,156,50,21,100,100,'13,1^14,1^21,1'), + (1,43,260,7080,55,0,55,208,208,208,208,208,208,208,28,28,28,28,28,42,12,41,160,52,21,100,100,'13,1^14,1^21,1'), + (1,44,265,7440,58,0,58,211,211,211,211,211,211,211,28,28,28,28,28,43,12,42,163,53,21,100,100,'13,1^14,1^21,1'), + (1,45,270,7800,60,0,60,215,215,215,215,215,215,215,29,29,29,29,29,44,12,43,167,54,21,100,100,'13,1^14,1^21,1'), + (1,46,275,8640,60,0,60,218,218,218,218,218,218,218,29,29,29,29,29,46,12,53,182,55,21,100,100,'13,1^14,1^21,1'), + (1,47,280,9480,60,0,60,222,222,222,222,222,222,222,30,30,30,30,30,47,12,61,198,56,21,100,100,'13,1^14,1^21,1'), + (1,48,284,10320,60,0,60,226,226,226,226,226,226,226,30,30,30,30,30,47,12,71,214,58,21,100,100,'13,1^14,1^21,1'), + (1,49,289,11160,60,0,60,229,229,229,229,229,229,229,31,31,31,31,31,48,12,79,229,59,21,100,100,'13,1^14,1^21,1'), + (1,50,294,12000,60,0,60,233,233,233,233,233,233,233,31,31,31,31,31,49,12,89,245,60,21,100,100,'13,1^14,1^21,1'), + (1,51,299,14040,64,0,64,236,236,236,236,236,236,236,32,32,32,32,32,50,13,94,277,61,20,100,100,'13,1^14,1^21,1'), + (1,52,304,16080,67,0,67,240,240,240,240,240,240,240,32,32,32,32,32,52,14,97,310,62,20,100,100,'13,1^14,1^21,1'), + (1,53,308,18120,71,0,71,244,244,244,244,244,244,244,34,34,34,34,34,52,16,102,341,64,20,100,100,'13,1^14,1^21,1'), + (1,54,313,20160,74,0,74,247,247,247,247,247,247,247,34,34,34,34,34,53,17,107,373,65,20,100,100,'13,1^14,1^21,1'), + (1,55,319,22200,78,0,78,252,252,252,252,252,252,252,35,35,35,35,35,54,18,112,406,66,20,100,100,'13,1^14,1^21,1'), + (1,56,324,24240,82,0,82,256,256,256,256,256,256,256,35,35,35,35,35,55,19,115,438,67,20,100,100,'13,1^14,1^21,1'), + (1,57,329,26280,85,0,85,259,259,259,259,259,259,259,36,36,36,36,36,56,20,120,470,68,19,100,100,'13,1^14,1^21,1'), + (1,58,334,28320,89,0,89,263,263,263,263,263,263,263,36,36,36,36,36,56,22,125,502,70,19,100,100,'13,1^14,1^21,1'), + (1,59,338,30360,92,0,92,266,266,266,266,266,266,266,37,37,37,37,37,58,23,128,534,71,19,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,60,343,32400,96,30,96,270,270,270,270,270,270,270,37,37,37,37,37,59,24,133,566,72,19,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,61,348,34691,102,30,101,274,274,274,274,274,274,274,38,38,38,38,38,60,29,154,643,73,19,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,62,353,36982,109,30,104,277,277,277,277,277,277,277,38,38,38,38,38,61,34,174,719,74,18,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,63,359,39272,115,30,109,281,281,281,281,281,281,281,40,40,40,40,40,61,38,194,796,76,18,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,64,364,41563,122,30,114,284,284,284,284,284,284,284,40,40,40,40,40,62,43,215,872,77,18,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,65,368,43854,128,35,118,288,288,288,288,288,288,288,41,41,41,41,41,64,48,235,948,78,18,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,66,373,46146,136,35,122,293,293,293,293,293,293,293,41,41,41,41,41,65,53,256,1025,79,18,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,67,378,48437,142,35,126,296,296,296,296,296,296,296,42,42,42,42,42,66,58,276,1100,80,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,68,383,50728,149,35,131,300,300,300,300,300,300,300,42,42,42,42,42,67,62,296,1177,82,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,69,389,53018,155,35,136,304,304,304,304,304,304,304,43,43,43,43,43,67,67,317,1254,83,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,70,394,55309,162,40,139,307,307,307,307,307,307,307,43,43,43,43,43,68,72,337,1330,84,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,71,398,57600,168,40,144,311,311,311,311,311,311,311,44,44,44,44,44,70,77,358,1406,85,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,72,403,57600,172,40,154,314,314,314,314,314,314,314,46,46,46,46,46,71,82,366,1432,86,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,73,408,57600,174,40,162,318,318,318,318,318,318,318,47,47,47,47,47,72,86,374,1457,88,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,74,413,57600,178,40,172,322,322,322,322,322,322,322,47,47,47,47,47,73,91,382,1482,89,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,75,418,57600,180,40,180,325,325,325,325,325,325,325,48,48,48,48,48,74,96,390,1507,90,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,76,423,90000,192,45,192,329,329,329,329,329,329,329,49,49,49,49,49,76,101,480,1920,91,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,77,428,108000,204,45,204,332,332,332,332,332,332,332,50,50,50,50,50,77,106,600,2460,92,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,78,433,135600,216,45,216,336,336,336,336,336,336,336,52,52,52,52,52,78,110,713,2788,144,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,79,438,156000,228,45,228,340,340,340,340,340,340,340,53,53,53,53,53,79,115,780,3000,156,17,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,80,443,168000,240,45,240,343,343,343,343,343,343,343,54,54,54,54,54,80,120,864,3359,168,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,81,448,288000,360,45,360,347,347,347,347,347,347,347,55,55,55,55,55,82,125,960,4319,288,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,82,453,408000,480,45,480,350,350,350,350,350,350,350,56,56,56,56,56,83,130,1080,5519,408,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,83,458,528000,492,45,492,354,354,354,354,354,354,354,58,58,58,58,58,84,134,1530,5885,528,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,84,463,534000,504,45,504,358,358,358,358,358,358,358,59,59,59,59,59,85,139,1560,6120,534,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,85,468,540000,516,45,516,361,361,361,361,361,361,361,60,60,60,60,60,86,144,1631,6350,540,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,86,473,546000,528,45,528,365,365,365,365,365,365,365,61,61,61,61,61,88,149,1770,6694,546,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,87,478,552000,540,45,540,368,368,368,368,368,368,368,62,62,62,62,62,89,154,1812,7102,552,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,88,483,558000,552,45,552,372,372,372,372,372,372,372,64,64,64,64,64,90,158,1932,7440,558,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,89,488,564000,564,45,564,376,376,376,376,376,376,376,65,65,65,65,65,91,163,1980,7530,564,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (1,90,490,570000,576,45,576,379,379,379,379,379,379,379,66,66,66,66,66,92,168,2040,7620,570,16,100,100,'1,1^8,1^13,1^14,1^21,1'), + (2,1,12,17,0,0,0,12,12,12,12,12,12,12,2,2,2,2,2,2,15,2,9,2,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,2,17,41,0,0,0,17,17,17,17,17,17,17,4,4,4,4,4,4,15,2,12,3,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,3,21,65,0,0,0,21,21,21,21,21,21,21,6,6,6,6,6,6,15,2,15,5,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,4,24,89,0,0,0,26,26,26,26,26,26,26,8,8,8,8,8,8,15,2,18,6,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,5,29,113,0,0,0,30,30,30,30,30,30,30,10,10,10,10,10,10,15,2,21,8,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,6,33,150,0,0,0,35,35,35,35,35,35,35,12,12,12,12,12,12,15,2,24,9,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,7,38,188,0,0,0,39,39,39,39,39,39,39,14,14,14,14,14,14,15,2,27,11,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,8,42,225,0,0,0,44,44,44,44,44,44,44,16,16,16,16,16,16,15,2,30,12,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,9,47,263,0,0,0,48,48,48,48,48,48,48,18,18,18,18,18,18,15,2,33,14,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,10,51,300,0,0,0,53,53,53,53,53,53,53,20,20,20,20,20,20,15,2,36,15,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,11,56,351,0,0,0,57,57,57,57,57,57,57,22,22,22,22,22,22,15,5,41,17,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,12,60,402,0,0,0,63,63,63,63,63,63,63,24,24,24,24,24,24,15,6,45,18,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,13,65,453,0,0,0,68,68,68,68,68,68,68,26,26,26,26,26,26,15,9,48,20,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,14,69,504,0,0,0,72,72,72,72,72,72,72,28,28,28,28,28,28,15,11,53,21,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,15,78,572,0,0,0,77,77,77,77,77,77,77,30,30,30,30,30,30,15,11,56,23,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,16,89,639,0,0,0,81,81,81,81,81,81,81,32,32,32,32,32,32,15,12,59,24,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,17,98,707,0,0,0,86,86,86,86,86,86,86,34,34,34,34,34,34,15,12,62,26,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,18,108,774,0,0,0,90,90,90,90,90,90,90,36,36,36,36,36,36,15,14,63,27,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,19,117,842,0,0,0,95,95,95,95,95,95,95,38,38,38,38,38,38,15,14,66,29,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,20,128,909,0,0,0,99,99,99,99,99,99,99,40,40,40,40,40,40,15,15,69,30,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,21,137,977,0,0,0,104,104,104,104,104,104,104,42,42,42,42,42,42,15,15,72,32,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,22,143,1068,0,0,0,108,108,108,108,108,108,108,44,44,44,44,44,44,15,15,75,33,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,23,149,1160,0,0,0,113,113,113,113,113,113,113,46,46,46,46,46,46,15,15,78,35,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,24,155,1251,0,0,0,117,117,117,117,117,117,117,48,48,48,48,48,48,15,17,83,36,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,25,161,1343,0,0,0,122,122,122,122,122,122,122,50,50,50,50,50,50,15,17,86,38,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,26,167,1434,0,0,0,128,128,128,128,128,128,128,52,52,52,52,52,52,15,17,89,39,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,27,173,1526,0,0,0,132,132,132,132,132,132,132,54,54,54,54,54,54,15,17,92,41,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,28,179,1617,0,0,0,137,137,137,137,137,137,137,56,56,56,56,56,56,15,18,96,42,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,29,185,1709,0,0,0,141,141,141,141,141,141,141,58,58,58,58,58,58,15,18,99,44,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,30,191,1800,0,0,0,146,146,146,146,146,146,146,60,60,60,60,60,60,15,18,102,45,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,31,203,2370,6,0,6,156,156,156,156,156,156,156,62,62,62,62,62,62,15,21,111,47,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,32,213,2940,12,0,12,165,165,165,165,165,165,165,64,64,64,64,64,64,15,24,119,48,30,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,33,225,3510,18,0,18,176,176,176,176,176,176,176,66,66,66,66,66,66,15,27,128,50,29,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,34,237,4080,24,0,24,185,185,185,185,185,185,185,68,68,68,68,68,68,15,30,135,51,28,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,35,249,4650,30,0,30,195,195,195,195,195,195,195,70,70,70,70,70,70,15,33,144,53,27,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,36,260,5220,36,0,36,206,206,206,206,206,206,206,72,72,72,72,72,72,15,36,153,54,25,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,37,272,5790,42,0,42,215,215,215,215,215,215,215,74,74,74,74,74,74,15,39,161,56,24,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,38,284,6360,48,0,48,225,225,225,225,225,225,225,76,76,76,76,76,76,15,42,170,57,23,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,39,294,6930,54,0,54,234,234,234,234,234,234,234,78,78,78,78,78,78,15,45,177,59,22,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,40,306,7500,60,0,60,245,245,245,245,245,245,245,80,80,80,80,80,80,15,48,186,60,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,41,312,7950,63,0,63,249,249,249,249,249,249,249,82,82,82,82,82,82,15,50,191,62,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,42,318,8400,66,0,66,254,254,254,254,254,254,254,84,84,84,84,84,84,15,51,195,63,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,43,326,11000,69,0,69,260,260,260,260,260,260,260,86,86,86,86,86,86,15,51,200,65,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,44,332,25000,72,0,72,264,264,264,264,264,264,264,88,88,88,88,88,88,15,53,204,66,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,45,338,35000,75,0,75,269,269,269,269,269,269,269,90,90,90,90,90,90,15,54,209,68,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,46,344,45000,75,0,75,273,273,273,273,273,273,273,92,92,92,92,92,92,15,66,228,69,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,47,350,55000,75,0,75,278,278,278,278,278,278,278,94,94,94,94,94,94,15,77,248,71,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,48,356,70000,75,0,75,282,282,282,282,282,282,282,96,96,96,96,96,96,15,89,267,72,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,49,362,85000,75,0,75,287,287,287,287,287,287,287,98,98,98,98,98,98,15,99,287,74,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,50,368,100000,75,0,75,291,291,291,291,291,291,291,100,100,100,100,100,100,15,111,306,75,21,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,51,374,130000,80,0,80,296,296,296,296,296,296,296,102,102,102,102,102,102,17,117,347,77,20,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,52,380,140000,84,0,84,300,300,300,300,300,300,300,104,104,104,104,104,104,18,122,387,78,20,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,53,386,150000,89,0,89,305,305,305,305,305,305,305,106,106,106,106,106,106,20,128,426,80,19,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,54,392,160000,93,0,93,309,309,309,309,309,309,309,108,108,108,108,108,108,21,134,467,81,19,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,55,399,170000,98,0,98,315,315,315,315,315,315,315,110,110,110,110,110,110,23,140,507,83,18,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,56,405,180000,102,0,102,320,320,320,320,320,320,320,112,112,112,112,112,112,24,144,548,84,17,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,57,411,190000,107,0,107,324,324,324,324,324,324,324,114,114,114,114,114,114,26,150,588,86,17,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,58,417,200000,111,0,111,329,329,329,329,329,329,329,116,116,116,116,116,116,27,156,627,87,16,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,59,434,400750,130,0,125,253,253,253,253,253,253,253,118,118,118,118,118,118,19,170,700,141,16,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,60,476,450813,140,0,129,258,258,258,258,258,258,258,120,120,120,120,120,120,22,185,740,194,15,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,61,517,500875,145,0,130,263,263,263,263,263,263,263,122,122,122,122,122,122,26,195,780,246,15,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,62,559,550938,150,0,140,268,268,268,268,268,268,268,124,124,124,124,124,124,29,210,800,299,15,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,63,600,601000,155,0,160,273,273,273,273,273,273,273,126,126,126,126,126,126,32,220,825,351,15,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,64,563,734167,160,0,170,277,277,277,277,277,277,277,128,128,128,128,128,128,50,241,850,401,15,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,65,525,867333,165,0,180,281,281,281,281,281,281,281,130,130,130,130,130,130,67,262,875,450,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,66,488,1000500,170,0,190,285,285,285,285,285,285,285,132,132,132,132,132,132,85,283,904,500,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,67,498,1013841,175,10,200,292,292,292,292,292,292,292,134,134,134,134,134,134,81,312,1071,600,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,68,508,1027182,180,20,225,300,300,300,300,300,300,300,136,136,136,136,136,136,77,341,1238,700,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,69,519,1040522,185,30,239,307,307,307,307,307,307,307,138,138,138,138,138,138,72,369,1404,800,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,70,529,1053863,190,40,245,315,315,315,315,315,315,315,140,140,140,140,140,140,68,398,1571,900,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,71,539,1067204,200,50,255,322,322,322,322,322,322,322,142,142,142,142,142,142,64,427,1738,1000,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,72,547,1262903,220,56,265,327,327,327,327,327,327,327,144,144,144,144,144,144,68,520,1979,1125,14,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,73,556,1458602,253,63,285,332,332,332,332,332,332,332,146,146,146,146,146,146,72,614,2219,1250,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,74,564,1654301,306,69,300,337,337,337,337,337,337,337,148,148,148,148,148,148,76,707,2460,1375,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,75,572,1850000,330,75,310,342,342,342,342,342,342,342,150,150,150,150,150,150,80,725,2700,1500,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,76,579,1900000,348,77,320,346,346,346,346,346,346,346,152,152,152,152,152,152,84,750,2960,1600,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,77,586,1950000,355,79,330,350,350,350,350,350,350,350,154,154,154,154,154,154,88,775,3000,1700,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,78,594,2000000,365,81,340,354,354,354,354,354,354,354,156,156,156,156,156,156,92,800,3100,1800,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,79,601,2050000,375,83,350,358,358,358,358,358,358,358,158,158,158,158,158,158,96,825,3200,1900,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,80,608,2100000,380,85,360,362,362,362,362,362,362,362,160,160,160,160,160,160,100,850,3300,2000,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,81,615,2480000,385,83,370,366,366,366,366,366,366,366,162,162,162,162,162,162,104,875,3350,3000,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,82,622,2860000,390,81,380,370,370,370,370,370,370,370,164,164,164,164,164,164,108,900,3400,4000,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,83,629,3240000,395,79,390,375,375,375,375,375,375,375,166,166,166,166,166,166,112,925,3450,5000,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,84,636,3620000,400,77,400,379,379,379,379,379,379,379,168,168,168,168,168,168,116,940,3500,6000,13,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,85,643,4000000,405,75,410,383,383,383,383,383,383,383,170,170,170,170,170,170,120,960,3550,7000,12,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,86,650,4800000,410,77,420,387,387,387,387,387,387,387,172,172,172,172,172,172,124,980,3600,8000,12,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,87,657,5600000,415,79,430,391,391,391,391,391,391,391,174,174,174,174,174,174,128,1000,3650,9000,12,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,88,665,6400000,420,81,440,395,395,395,395,395,395,395,176,176,176,176,176,176,132,1010,3700,10000,12,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,89,672,7200000,420,83,445,399,399,399,399,399,399,399,178,178,178,178,178,178,136,1018,3800,11000,12,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'), + (2,90,679,8000000,420,85,450,403,403,403,403,403,403,403,180,180,180,180,180,180,140,1050,3900,12000,12,100,100,'1,1^2,1^8,1^13,1^14,1^15,1^16,1^17,1^21,1^31,1'); diff --git a/utils/sql/system_tables.txt b/utils/sql/system_tables.txt index 4822f8f97..e3eb0cdcc 100644 --- a/utils/sql/system_tables.txt +++ b/utils/sql/system_tables.txt @@ -60,9 +60,11 @@ merc_types merc_weaponinfo merchantlist mercs +name_filter npc_emotes npc_faction npc_faction_entries +npc_scale_global_base npc_spells npc_spells_effects npc_spells_effects_entries diff --git a/utils/sql/user_tables.txt b/utils/sql/user_tables.txt index 941e30c47..a76e110f3 100644 --- a/utils/sql/user_tables.txt +++ b/utils/sql/user_tables.txt @@ -59,7 +59,6 @@ launcher_zones lfguild mail merchantlist_temp -name_filter object_contents petitions player_titlesets @@ -86,6 +85,7 @@ reports respawn_times sharedbank spell_globals +spell_buckets timers trader trader_audit diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 616f76c5f..103771f06 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -83,10 +83,12 @@ SET(zone_sources mob.cpp mob_ai.cpp mob_movement_manager.cpp + mob_info.cpp mod_functions.cpp net.cpp npc.cpp npc_ai.cpp + npc_scale_manager.cpp object.cpp oriented_bounding_box.cpp pathfinder_interface.cpp @@ -138,8 +140,7 @@ SET(zone_sources zone.cpp zone_config.cpp zonedb.cpp - zoning.cpp -) + zoning.cpp) SET(zone_headers aa.h @@ -209,6 +210,7 @@ SET(zone_headers net.h npc.h npc_ai.h + npc_scale_manager.h object.h oriented_bounding_box.h pathfinder_interface.h @@ -244,8 +246,7 @@ SET(zone_headers zone.h zone_config.h zonedb.h - zonedump.h -) + zonedump.h) IF(EQEMU_DEPOP_INVALIDATES_CACHE) ADD_DEFINITIONS(-DDEPOP_INVALIDATES_NPC_TYPES_CACHE) diff --git a/zone/attack.cpp b/zone/attack.cpp index f5a126b32..db991ca72 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -37,6 +37,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include #ifdef BOTS #include "bot.h" @@ -5457,3 +5458,13 @@ bool Mob::GetWasSpawnedInWater() const { void Mob::SetSpawnedInWater(bool spawned_in_water) { Mob::spawned_in_water = spawned_in_water; } + +int32 Mob::GetHPRegen() const +{ + return hp_regen; +} + +int32 Mob::GetManaRegen() const +{ + return mana_regen; +} \ No newline at end of file diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index d2b0d11f8..c7d5cf2a8 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -58,12 +58,12 @@ void Mob::CalcBonuses() void NPC::CalcBonuses() { memset(&itembonuses, 0, sizeof(StatBonuses)); - if(RuleB(NPC, UseItemBonusesForNonPets)){ + if (RuleB(NPC, UseItemBonusesForNonPets)) { memset(&itembonuses, 0, sizeof(StatBonuses)); CalcItemBonuses(&itembonuses); } - else{ - if(GetOwner()){ + else { + if (GetOwner()) { memset(&itembonuses, 0, sizeof(StatBonuses)); CalcItemBonuses(&itembonuses); } diff --git a/zone/client.cpp b/zone/client.cpp index 0f32da9ea..3bdd647a2 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -38,6 +38,7 @@ extern volatile bool RunLoops; #include "../common/rulesys.h" #include "../common/string_util.h" #include "../common/data_verification.h" +#include "data_bucket.h" #include "position.h" #include "net.h" #include "worldserver.h" @@ -254,7 +255,6 @@ Client::Client(EQStreamInterface* ieqs) InitializeMercInfo(); SetMerc(0); if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true, false); - logging_enabled = CLIENT_DEFAULT_LOGGING_ENABLED; //for good measure: memset(&m_pp, 0, sizeof(m_pp)); @@ -263,6 +263,16 @@ Client::Client(EQStreamInterface* ieqs) PendingSacrifice = false; controlling_boat_id = 0; + if (!RuleB(Character, PerCharacterQglobalMaxLevel) && !RuleB(Character, PerCharacterBucketMaxLevel)) { + SetClientMaxLevel(0); + } else if (RuleB(Character, PerCharacterQglobalMaxLevel)) { + int client_max_level = GetCharMaxLevelFromQGlobal(); + SetClientMaxLevel(client_max_level); + } else if (RuleB(Character, PerCharacterBucketMaxLevel)) { + int client_max_level = GetCharMaxLevelFromBucket(); + SetClientMaxLevel(client_max_level); + } + KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS)); GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS)); AttemptedMessages = 0; @@ -327,6 +337,12 @@ Client::Client(EQStreamInterface* ieqs) temp_pvp = false; is_client_moving = false; + /** + * GM + */ + display_mob_info_window = true; + dev_tools_window_enabled = true; + #ifdef BOTS bot_owner_options = DefaultBotOwnerOptions; #endif @@ -1642,7 +1658,6 @@ void Client::FriendsWho(char *FriendsString) { } } - void Client::UpdateAdmin(bool iFromDB) { int16 tmp = admin; if (iFromDB) @@ -1960,7 +1975,6 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) // ns->spawn.pvp = GetPVP(false) ? 1 : 0; ns->spawn.show_name = true; - strcpy(ns->spawn.title, m_pp.title); strcpy(ns->spawn.suffix, m_pp.suffix); @@ -4026,9 +4040,9 @@ void Client::SetHoTT(uint32 mobid) { void Client::SendPopupToClient(const char *Title, const char *Text, uint32 PopupID, uint32 Buttons, uint32 Duration) { - auto outapp = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); - OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *)outapp->pBuffer; + + OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *) outapp->pBuffer; if ((strlen(Title) > (sizeof(olms->Title) - 1)) || (strlen(Text) > (sizeof(olms->Text) - 1))) { safe_delete(outapp); @@ -4040,12 +4054,14 @@ void Client::SendPopupToClient(const char *Title, const char *Text, uint32 Popup olms->Buttons = Buttons; - if (Duration > 0) + if (Duration > 0) { olms->Duration = Duration * 1000; - else + } + else { olms->Duration = 0xffffffff; + } - olms->PopupID = PopupID; + olms->PopupID = PopupID; olms->NegativeID = 0; sprintf(olms->ButtonName0, "%s", "Yes"); @@ -4054,16 +4070,29 @@ void Client::SendPopupToClient(const char *Title, const char *Text, uint32 Popup safe_delete(outapp); } -void Client::SendFullPopup(const char *Title, const char *Text, uint32 PopupID, uint32 NegativeID, uint32 Buttons, uint32 Duration, const char *ButtonName0, const char *ButtonName1, uint32 SoundControls) { +void Client::SendFullPopup( + const char *Title, + const char *Text, + uint32 PopupID, + uint32 NegativeID, + uint32 Buttons, + uint32 Duration, + const char *ButtonName0, + const char *ButtonName1, + uint32 SoundControls +) +{ auto outapp = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); - OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *)outapp->pBuffer; - if((strlen(Text) > (sizeof(olms->Text)-1)) || (strlen(Title) > (sizeof(olms->Title) - 1)) ) { + OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *) outapp->pBuffer; + + if ((strlen(Text) > (sizeof(olms->Text) - 1)) || (strlen(Title) > (sizeof(olms->Title) - 1))) { safe_delete(outapp); return; } - if (ButtonName0 && ButtonName1 && ( (strlen(ButtonName0) > (sizeof(olms->ButtonName0) - 1)) || (strlen(ButtonName1) > (sizeof(olms->ButtonName1) - 1)) ) ) { + if (ButtonName0 && ButtonName1 && ((strlen(ButtonName0) > (sizeof(olms->ButtonName0) - 1)) || + (strlen(ButtonName1) > (sizeof(olms->ButtonName1) - 1)))) { safe_delete(outapp); return; } @@ -4072,31 +4101,47 @@ void Client::SendFullPopup(const char *Title, const char *Text, uint32 PopupID, strcpy(olms->Text, Text); olms->Buttons = Buttons; - - if (ButtonName0 == NULL || ButtonName1 == NULL) { + + if (ButtonName0 == nullptr || ButtonName1 == nullptr) { sprintf(olms->ButtonName0, "%s", "Yes"); sprintf(olms->ButtonName1, "%s", "No"); - } else { + } + else { strcpy(olms->ButtonName0, ButtonName0); strcpy(olms->ButtonName1, ButtonName1); } - if(Duration > 0) + if (Duration > 0) { olms->Duration = Duration * 1000; - else + } + else { olms->Duration = 0xffffffff; + } - olms->PopupID = PopupID; - olms->NegativeID = NegativeID; + olms->PopupID = PopupID; + olms->NegativeID = NegativeID; olms->SoundControls = SoundControls; QueuePacket(outapp); safe_delete(outapp); } -void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const char *ButtonName0, const char *ButtonName1, uint32 Duration, int title_type, Client* target, const char *Title, const char *Text, ...) { +void Client::SendWindow( + uint32 PopupID, + uint32 NegativeID, + uint32 Buttons, + const char *ButtonName0, + const char *ButtonName1, + uint32 Duration, + int title_type, + Client *target, + const char *Title, + const char *Text, + ... +) +{ va_list argptr; - char buffer[4096]; + char buffer[4096]; va_start(argptr, Text); vsnprintf(buffer, sizeof(buffer), Text, argptr); @@ -4104,23 +4149,23 @@ void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const size_t len = strlen(buffer); - auto app = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); - OnLevelMessage_Struct* olms=(OnLevelMessage_Struct*)app->pBuffer; + auto app = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); + OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *) app->pBuffer; - if(strlen(Text) > (sizeof(olms->Text)-1)) { + if (strlen(Text) > (sizeof(olms->Text) - 1)) { safe_delete(app); return; } - if(!target) + if (!target) { title_type = 0; + } - switch (title_type) - { + switch (title_type) { case 1: { char name[64] = ""; strcpy(name, target->GetName()); - if(target->GetLastName()) { + if (target->GetLastName()) { char last_name[64] = ""; strcpy(last_name, target->GetLastName()); strcat(name, " "); @@ -4130,8 +4175,8 @@ void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const break; } case 2: { - if(target->GuildID()) { - char *guild_name = (char*)guild_mgr.GetGuildName(target->GuildID()); + if (target->GuildID()) { + char *guild_name = (char *) guild_mgr.GetGuildName(target->GuildID()); strcpy(olms->Title, guild_name); } else { @@ -4145,19 +4190,21 @@ void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const } } - memcpy(olms->Text, buffer, len+1); + memcpy(olms->Text, buffer, len + 1); olms->Buttons = Buttons; sprintf(olms->ButtonName0, "%s", ButtonName0); sprintf(olms->ButtonName1, "%s", ButtonName1); - if(Duration > 0) + if (Duration > 0) { olms->Duration = Duration * 1000; - else + } + else { olms->Duration = 0xffffffff; + } - olms->PopupID = PopupID; + olms->PopupID = PopupID; olms->NegativeID = NegativeID; FastQueuePacket(&app); @@ -6341,30 +6388,13 @@ void Client::SendStatsWindow(Client* client, bool use_window) std::string class_Name = itoa(GetClass()); std::string class_List[] = { "WAR", "CLR", "PAL", "RNG", "SHD", "DRU", "MNK", "BRD", "ROG", "SHM", "NEC", "WIZ", "MAG", "ENC", "BST", "BER" }; - if(GetClass() < 17 && GetClass() > 0) { class_Name = class_List[GetClass()-1]; } + if (GetClass() < 17 && GetClass() > 0) { + class_Name = class_List[GetClass() - 1]; + } // Race - std::string race_Name = itoa(GetRace()); - switch(GetRace()) - { - case 1: race_Name = "Human"; break; - case 2: race_Name = "Barbarian"; break; - case 3: race_Name = "Erudite"; break; - case 4: race_Name = "Wood Elf"; break; - case 5: race_Name = "High Elf"; break; - case 6: race_Name = "Dark Elf"; break; - case 7: race_Name = "Half Elf"; break; - case 8: race_Name = "Dwarf"; break; - case 9: race_Name = "Troll"; break; - case 10: race_Name = "Ogre"; break; - case 11: race_Name = "Halfing"; break; - case 12: race_Name = "Gnome"; break; - case 128: race_Name = "Iksar"; break; - case 130: race_Name = "Vah Shir"; break; - case 330: race_Name = "Froglok"; break; - case 522: race_Name = "Drakkin"; break; - default: break; - } + std::string race_name = GetRaceIDName(GetRace()); + /*########################################################## ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ H/M/E String @@ -6793,7 +6823,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) std::ostringstream final_string; final_string << - /* C/L/R */ indP << "Class: " << class_Name << indS << "Level: " << static_cast(GetLevel()) << indS << "Race: " << race_Name << "
" << + /* C/L/R */ indP << "Class: " << class_Name << indS << "Level: " << static_cast(GetLevel()) << indS << "Race: " << race_name << "
" << /* Runes */ indP << "Rune: " << rune_number << indL << indS << "Spell Rune: " << magic_rune_number << "
" << /* HP/M/E */ HME_row << /* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")
" << @@ -8977,3 +9007,22 @@ void Client::InitInnates() } } +bool Client::GetDisplayMobInfoWindow() const +{ + return display_mob_info_window; +} + +void Client::SetDisplayMobInfoWindow(bool display_mob_info_window) +{ + Client::display_mob_info_window = display_mob_info_window; +} + +bool Client::IsDevToolsWindowEnabled() const +{ + return dev_tools_window_enabled; +} + +void Client::SetDevToolsWindowEnabled(bool in_dev_tools_window_enabled) +{ + Client::dev_tools_window_enabled = in_dev_tools_window_enabled; +} \ No newline at end of file diff --git a/zone/client.h b/zone/client.h index 1768c9493..4f7b0d9a0 100644 --- a/zone/client.h +++ b/zone/client.h @@ -246,6 +246,12 @@ public: std::unordered_map close_mobs; bool is_client_moving; + void SetDisplayMobInfoWindow(bool display_mob_info_window); + bool GetDisplayMobInfoWindow() const; + + bool IsDevToolsWindowEnabled() const; + void SetDevToolsWindowEnabled(bool dev_tools_window_enabled); + //abstract virtual function implementations required by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); @@ -697,7 +703,9 @@ public: void SendGuildJoin(GuildJoin_Struct* gj); void RefreshGuildInfo(); - + int GetClientMaxLevel() const { return client_max_level; } + void SetClientMaxLevel(int max_level) { client_max_level = max_level; } + void CheckManaEndUpdate(); void SendManaUpdate(); void SendEnduranceUpdate(); @@ -773,8 +781,10 @@ public: void UnscribeSpellAll(bool update_client = true); void UntrainDisc(int slot, bool update_client = true); void UntrainDiscAll(bool update_client = true); - bool SpellGlobalCheck(uint16 Spell_ID, uint32 Char_ID); + bool SpellGlobalCheck(uint16 spell_id, uint32 char_id); + bool SpellBucketCheck(uint16 spell_id, uint32 char_id); uint32 GetCharMaxLevelFromQGlobal(); + uint32 GetCharMaxLevelFromBucket(); inline bool IsSitting() const {return (playeraction == 1);} inline bool IsBecomeNPC() const { return npcflag; } @@ -851,7 +861,6 @@ public: void SetAATitle(const char *Title); void SetTitleSuffix(const char *txt); void MemorizeSpell(uint32 slot, uint32 spellid, uint32 scribing); - int32 acmod(); // Item methods void EVENT_ITEM_ScriptStopReturn(); @@ -1442,6 +1451,9 @@ private: int Haste; //precalced value uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004 + bool display_mob_info_window; + bool dev_tools_window_enabled; + int32 max_end; int32 current_endurance; @@ -1616,6 +1628,8 @@ private: void InterrogateInventory_(bool errorcheck, Client* requester, int16 head, int16 index, const EQEmu::ItemInstance* inst, const EQEmu::ItemInstance* parent, bool log, bool silent, bool &error, int depth); bool InterrogateInventory_error(int16 head, int16 index, const EQEmu::ItemInstance* inst, const EQEmu::ItemInstance* parent, int depth); + int client_max_level; + #ifdef BOTS struct BotOwnerOptions { bool death_marquee; diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 39c37c874..8651a622f 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -275,8 +275,9 @@ int32 Client::CalcHPRegen(bool bCombat) if (!skip_innate && m_pp.InnateSkills[InnateRegen] != InnateDisabled) { if (level >= 50) { ++base; - if (level >= 55) + if (level >= 55) { ++base; + } } base *= 2; } @@ -331,6 +332,7 @@ int32 Client::CalcMaxHP() if (hp_perc_cap) { int curHP_cap = (max_hp * hp_perc_cap) / 100; if (cur_hp > curHP_cap || (spellbonuses.HPPercCap[1] && cur_hp > spellbonuses.HPPercCap[1])) { + cur_hp = curHP_cap; } } @@ -340,136 +342,137 @@ int32 Client::CalcMaxHP() uint32 Mob::GetClassLevelFactor() { uint32 multiplier = 0; - uint8 mlevel = GetLevel(); + uint8 mlevel = GetLevel(); switch (GetClass()) { case WARRIOR: { - if (mlevel < 20) { - multiplier = 220; - } - else if (mlevel < 30) { - multiplier = 230; - } - else if (mlevel < 40) { - multiplier = 250; - } - else if (mlevel < 53) { - multiplier = 270; - } - else if (mlevel < 57) { - multiplier = 280; - } - else if (mlevel < 60) { - multiplier = 290; - } - else if (mlevel < 70) { - multiplier = 300; - } - else { - multiplier = 311; - } - break; + if (mlevel < 20) { + multiplier = 220; } + else if (mlevel < 30) { + multiplier = 230; + } + else if (mlevel < 40) { + multiplier = 250; + } + else if (mlevel < 53) { + multiplier = 270; + } + else if (mlevel < 57) { + multiplier = 280; + } + else if (mlevel < 60) { + multiplier = 290; + } + else if (mlevel < 70) { + multiplier = 300; + } + else { + multiplier = 311; + } + break; + } case DRUID: case CLERIC: case SHAMAN: { - if (mlevel < 70) { - multiplier = 150; - } - else { - multiplier = 157; - } - break; + if (mlevel < 70) { + multiplier = 150; } + else { + multiplier = 157; + } + break; + } case BERSERKER: case PALADIN: case SHADOWKNIGHT: { - if (mlevel < 35) { - multiplier = 210; - } - else if (mlevel < 45) { - multiplier = 220; - } - else if (mlevel < 51) { - multiplier = 230; - } - else if (mlevel < 56) { - multiplier = 240; - } - else if (mlevel < 60) { - multiplier = 250; - } - else if (mlevel < 68) { - multiplier = 260; - } - else { - multiplier = 270; - } - break; + if (mlevel < 35) { + multiplier = 210; } + else if (mlevel < 45) { + multiplier = 220; + } + else if (mlevel < 51) { + multiplier = 230; + } + else if (mlevel < 56) { + multiplier = 240; + } + else if (mlevel < 60) { + multiplier = 250; + } + else if (mlevel < 68) { + multiplier = 260; + } + else { + multiplier = 270; + } + break; + } case MONK: case BARD: case ROGUE: case BEASTLORD: { - if (mlevel < 51) { - multiplier = 180; - } - else if (mlevel < 58) { - multiplier = 190; - } - else if (mlevel < 70) { - multiplier = 200; - } - else { - multiplier = 210; - } - break; + if (mlevel < 51) { + multiplier = 180; } + else if (mlevel < 58) { + multiplier = 190; + } + else if (mlevel < 70) { + multiplier = 200; + } + else { + multiplier = 210; + } + break; + } case RANGER: { - if (mlevel < 58) { - multiplier = 200; - } - else if (mlevel < 70) { - multiplier = 210; - } - else { - multiplier = 220; - } - break; + if (mlevel < 58) { + multiplier = 200; } + else if (mlevel < 70) { + multiplier = 210; + } + else { + multiplier = 220; + } + break; + } case MAGICIAN: case WIZARD: case NECROMANCER: case ENCHANTER: { - if (mlevel < 70) { - multiplier = 120; - } - else { - multiplier = 127; - } - break; + if (mlevel < 70) { + multiplier = 120; } + else { + multiplier = 127; + } + break; + } default: { - if (mlevel < 35) { - multiplier = 210; - } - else if (mlevel < 45) { - multiplier = 220; - } - else if (mlevel < 51) { - multiplier = 230; - } - else if (mlevel < 56) { - multiplier = 240; - } - else if (mlevel < 60) { - multiplier = 250; - } - else { - multiplier = 260; - } - break; + if (mlevel < 35) { + multiplier = 210; } + else if (mlevel < 45) { + multiplier = 220; + } + else if (mlevel < 51) { + multiplier = 230; + } + else if (mlevel < 56) { + multiplier = 240; + } + else if (mlevel < 60) { + multiplier = 250; + } + else { + multiplier = 260; + } + break; + } } + return multiplier; } @@ -560,537 +563,6 @@ int32 Client::GetRawItemAC() return Total; } -int32 Client::acmod() -{ - int agility = GetAGI(); - int level = GetLevel(); - if (agility < 1 || level < 1) { - return (0); - } - if (agility <= 74) { - if (agility == 1) { - return -24; - } - else if (agility <= 3) { - return -23; - } - else if (agility == 4) { - return -22; - } - else if (agility <= 6) { - return -21; - } - else if (agility <= 8) { - return -20; - } - else if (agility == 9) { - return -19; - } - else if (agility <= 11) { - return -18; - } - else if (agility == 12) { - return -17; - } - else if (agility <= 14) { - return -16; - } - else if (agility <= 16) { - return -15; - } - else if (agility == 17) { - return -14; - } - else if (agility <= 19) { - return -13; - } - else if (agility == 20) { - return -12; - } - else if (agility <= 22) { - return -11; - } - else if (agility <= 24) { - return -10; - } - else if (agility == 25) { - return -9; - } - else if (agility <= 27) { - return -8; - } - else if (agility == 28) { - return -7; - } - else if (agility <= 30) { - return -6; - } - else if (agility <= 32) { - return -5; - } - else if (agility == 33) { - return -4; - } - else if (agility <= 35) { - return -3; - } - else if (agility == 36) { - return -2; - } - else if (agility <= 38) { - return -1; - } - else if (agility <= 65) { - return 0; - } - else if (agility <= 70) { - return 1; - } - else if (agility <= 74) { - return 5; - } - } - else if (agility <= 137) { - if (agility == 75) { - if (level <= 6) { - return 9; - } - else if (level <= 19) { - return 23; - } - else if (level <= 39) { - return 33; - } - else { - return 39; - } - } - else if (agility >= 76 && agility <= 79) { - if (level <= 6) { - return 10; - } - else if (level <= 19) { - return 23; - } - else if (level <= 39) { - return 33; - } - else { - return 40; - } - } - else if (agility == 80) { - if (level <= 6) { - return 11; - } - else if (level <= 19) { - return 24; - } - else if (level <= 39) { - return 34; - } - else { - return 41; - } - } - else if (agility >= 81 && agility <= 85) { - if (level <= 6) { - return 12; - } - else if (level <= 19) { - return 25; - } - else if (level <= 39) { - return 35; - } - else { - return 42; - } - } - else if (agility >= 86 && agility <= 90) { - if (level <= 6) { - return 12; - } - else if (level <= 19) { - return 26; - } - else if (level <= 39) { - return 36; - } - else { - return 42; - } - } - else if (agility >= 91 && agility <= 95) { - if (level <= 6) { - return 13; - } - else if (level <= 19) { - return 26; - } - else if (level <= 39) { - return 36; - } - else { - return 43; - } - } - else if (agility >= 96 && agility <= 99) { - if (level <= 6) { - return 14; - } - else if (level <= 19) { - return 27; - } - else if (level <= 39) { - return 37; - } - else { - return 44; - } - } - else if (agility == 100 && level >= 7) { - if (level <= 19) { - return 28; - } - else if (level <= 39) { - return 38; - } - else { - return 45; - } - } - else if (level <= 6) { - return 15; - } - //level is >6 - else if (agility >= 101 && agility <= 105) { - if (level <= 19) { - return 29; - } - else if (level <= 39) { - return 39; // not verified - } - else { - return 45; - } - } - else if (agility >= 106 && agility <= 110) { - if (level <= 19) { - return 29; - } - else if (level <= 39) { - return 39; // not verified - } - else { - return 46; - } - } - else if (agility >= 111 && agility <= 115) { - if (level <= 19) { - return 30; - } - else if (level <= 39) { - return 40; // not verified - } - else { - return 47; - } - } - else if (agility >= 116 && agility <= 119) { - if (level <= 19) { - return 31; - } - else if (level <= 39) { - return 41; - } - else { - return 47; - } - } - else if (level <= 19) { - return 32; - } - //level is > 19 - else if (agility == 120) { - if (level <= 39) { - return 42; - } - else { - return 48; - } - } - else if (agility <= 125) { - if (level <= 39) { - return 42; - } - else { - return 49; - } - } - else if (agility <= 135) { - if (level <= 39) { - return 42; - } - else { - return 50; - } - } - else { - if (level <= 39) { - return 42; - } - else { - return 51; - } - } - } - else if (agility <= 300) { - if (level <= 6) { - if (agility <= 139) { - return (21); - } - else if (agility == 140) { - return (22); - } - else if (agility <= 145) { - return (23); - } - else if (agility <= 150) { - return (23); - } - else if (agility <= 155) { - return (24); - } - else if (agility <= 159) { - return (25); - } - else if (agility == 160) { - return (26); - } - else if (agility <= 165) { - return (26); - } - else if (agility <= 170) { - return (27); - } - else if (agility <= 175) { - return (28); - } - else if (agility <= 179) { - return (28); - } - else if (agility == 180) { - return (29); - } - else if (agility <= 185) { - return (30); - } - else if (agility <= 190) { - return (31); - } - else if (agility <= 195) { - return (31); - } - else if (agility <= 199) { - return (32); - } - else if (agility <= 219) { - return (33); - } - else if (agility <= 239) { - return (34); - } - else { - return (35); - } - } - else if (level <= 19) { - if (agility <= 139) { - return (34); - } - else if (agility == 140) { - return (35); - } - else if (agility <= 145) { - return (36); - } - else if (agility <= 150) { - return (37); - } - else if (agility <= 155) { - return (37); - } - else if (agility <= 159) { - return (38); - } - else if (agility == 160) { - return (39); - } - else if (agility <= 165) { - return (40); - } - else if (agility <= 170) { - return (40); - } - else if (agility <= 175) { - return (41); - } - else if (agility <= 179) { - return (42); - } - else if (agility == 180) { - return (43); - } - else if (agility <= 185) { - return (43); - } - else if (agility <= 190) { - return (44); - } - else if (agility <= 195) { - return (45); - } - else if (agility <= 199) { - return (45); - } - else if (agility <= 219) { - return (46); - } - else if (agility <= 239) { - return (47); - } - else { - return (48); - } - } - else if (level <= 39) { - if (agility <= 139) { - return (44); - } - else if (agility == 140) { - return (45); - } - else if (agility <= 145) { - return (46); - } - else if (agility <= 150) { - return (47); - } - else if (agility <= 155) { - return (47); - } - else if (agility <= 159) { - return (48); - } - else if (agility == 160) { - return (49); - } - else if (agility <= 165) { - return (50); - } - else if (agility <= 170) { - return (50); - } - else if (agility <= 175) { - return (51); - } - else if (agility <= 179) { - return (52); - } - else if (agility == 180) { - return (53); - } - else if (agility <= 185) { - return (53); - } - else if (agility <= 190) { - return (54); - } - else if (agility <= 195) { - return (55); - } - else if (agility <= 199) { - return (55); - } - else if (agility <= 219) { - return (56); - } - else if (agility <= 239) { - return (57); - } - else { - return (58); - } - } - else { //lvl >= 40 - if (agility <= 139) { - return (51); - } - else if (agility == 140) { - return (52); - } - else if (agility <= 145) { - return (53); - } - else if (agility <= 150) { - return (53); - } - else if (agility <= 155) { - return (54); - } - else if (agility <= 159) { - return (55); - } - else if (agility == 160) { - return (56); - } - else if (agility <= 165) { - return (56); - } - else if (agility <= 170) { - return (57); - } - else if (agility <= 175) { - return (58); - } - else if (agility <= 179) { - return (58); - } - else if (agility == 180) { - return (59); - } - else if (agility <= 185) { - return (60); - } - else if (agility <= 190) { - return (61); - } - else if (agility <= 195) { - return (61); - } - else if (agility <= 199) { - return (62); - } - else if (agility <= 219) { - return (63); - } - else if (agility <= 239) { - return (64); - } - else { - return (65); - } - } - } - else { - //seems about 21 agil per extra AC pt over 300... - return (65 + ((agility - 300) / 21)); - } - Log(Logs::Detail, Logs::Error, "Error in Client::acmod(): Agility: %i, Level: %i", agility, level); - return 0; -}; - int32 Client::CalcMaxMana() { switch (GetCasterClass()) { diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 13ab441b0..43034da6b 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -47,6 +47,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/spdat.h" #include "../common/string_util.h" #include "../common/zone_numbers.h" +#include "data_bucket.h" #include "event_codes.h" #include "guild_mgr.h" #include "merc.h" @@ -1316,6 +1317,15 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) drakkin_tattoo = m_pp.drakkin_tattoo; drakkin_details = m_pp.drakkin_details; + // Max Level for Character:PerCharacterQglobalMaxLevel and Character:PerCharacterBucketMaxLevel + int client_max_level = 0; + if (RuleB(Character, PerCharacterQglobalMaxLevel)) { + client_max_level = GetCharMaxLevelFromQGlobal(); + } else if (RuleB(Character, PerCharacterBucketMaxLevel)) { + client_max_level = GetCharMaxLevelFromBucket(); + } + SetClientMaxLevel(client_max_level); + // we know our class now, so we might have to fix our consume timer! if (class_ == MONK) consume_food_timer.SetTimer(CONSUMPTION_MNK_TIMER); @@ -1643,6 +1653,15 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) /* Task Packets */ LoadClientTaskState(); + /** + * DevTools Load Settings + */ + if (Admin() >= 200) { + std::string dev_tools_window_key = StringFormat("%i-dev-tools-window-disabled", AccountID()); + if (DataBucket::GetData(dev_tools_window_key) == "true") { + dev_tools_window_enabled = false; + } + } if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) { outapp = new EQApplicationPacket(OP_XTargetResponse, 8); @@ -10678,33 +10697,46 @@ void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) { if (app->size != sizeof(PopupResponse_Struct)) { - Log(Logs::General, Logs::None, "Size mismatch in OP_PopupResponse expected %i got %i", - sizeof(PopupResponse_Struct), app->size); + Log(Logs::General, + Logs::None, + "Size mismatch in OP_PopupResponse expected %i got %i", + sizeof(PopupResponse_Struct), + app->size); + DumpPacket(app); return; } - PopupResponse_Struct *prs = (PopupResponse_Struct*)app->pBuffer; - // Handle any EQEmu defined popup Ids first - switch (prs->popupid) - { - case POPUPID_UPDATE_SHOWSTATSWINDOW: - if (GetTarget() && GetTarget()->IsClient()) - GetTarget()->CastToClient()->SendStatsWindow(this, true); - else - SendStatsWindow(this, true); - return; + PopupResponse_Struct *popup_response = (PopupResponse_Struct *) app->pBuffer; - default: - break; + /** + * Handle any EQEmu defined popup Ids first + */ + switch (popup_response->popupid) { + case POPUPID_UPDATE_SHOWSTATSWINDOW: + if (GetTarget() && GetTarget()->IsClient()) { + GetTarget()->CastToClient()->SendStatsWindow(this, true); + } + else { + SendStatsWindow(this, true); + } + return; + break; + + case EQEmu::popupresponse::MOB_INFO_DISMISS: + this->SetDisplayMobInfoWindow(false); + this->Message(15, "[DevTools] Window snoozed in this zone..."); + break; + default: + break; } char buf[16]; - sprintf(buf, "%d\0", prs->popupid); + sprintf(buf, "%d\0", popup_response->popupid); parse->EventPlayer(EVENT_POPUP_RESPONSE, this, buf, 0); - Mob* Target = GetTarget(); + Mob *Target = GetTarget(); if (Target && Target->IsNPC()) { parse->EventNPC(EVENT_POPUP_RESPONSE, Target->CastToNPC(), this, buf, 0); } diff --git a/zone/command.cpp b/zone/command.cpp old mode 100644 new mode 100755 index 5c07c4096..2bca4cfcf --- a/zone/command.cpp +++ b/zone/command.cpp @@ -55,7 +55,7 @@ #include "../say_link.h" #include "../common/eqemu_logsys.h" - +#include "data_bucket.h" #include "command.h" #include "guild_mgr.h" #include "map.h" @@ -68,6 +68,7 @@ #include "worldserver.h" #include "fastmath.h" #include "mob_movement_manager.h" +#include "npc_scale_manager.h" extern QueryServ* QServ; extern WorldServer worldserver; @@ -185,6 +186,7 @@ int command_init(void) command_add("delpetition", "[petition number] - Delete a petition", 20, command_delpetition) || command_add("depop", "- Depop your NPC target", 50, command_depop) || command_add("depopzone", "- Depop the zone", 100, command_depopzone) || + command_add("devtools", "- Manages devtools", 200, command_devtools) || command_add("details", "- Change the details of your target (Drakkin Only)", 80, command_details) || command_add("disablerecipe", "[recipe_id] - Disables a recipe using the recipe id.", 80, command_disablerecipe) || command_add("disarmtrap", "Analog for ldon disarm trap for the newer clients since we still don't have it working.", 80, command_disarmtrap) || @@ -250,6 +252,7 @@ int command_init(void) command_add("lastname", "[new lastname] - Set your or your player target's lastname", 50, command_lastname) || command_add("level", "[level] - Set your or your target's level", 10, command_level) || command_add("listnpcs", "[name/range] - Search NPCs", 20, command_listnpcs) || + command_add("list", "[npcs|players|corpses|doors|objects] [search] - Search entities", 20, command_list) || command_add("listpetition", "- List petitions", 50, command_listpetition) || command_add("load_shared_memory", "[shared_memory_name] - Reloads shared memory and uses the input as output", 250, command_load_shared_memory) || command_add("loc", "- Print out your or your target's current location and heading", 0, command_loc) || @@ -336,6 +339,7 @@ int command_init(void) command_add("revoke", "[charname] [1/0] - Makes charname unable to talk on OOC", 200, command_revoke) || command_add("rules", "(subcommand) - Manage server rules", 250, command_rules) || command_add("save", "- Force your player or player corpse target to be saved to the database", 50, command_save) || + command_add("scale", "- Handles npc scaling", 150, command_scale) || command_add("scribespell", "[spellid] - Scribe specified spell in your target's spell book.", 180, command_scribespell) || command_add("scribespells", "[max level] [min level] - Scribe all spells for you or your player target that are usable by them, up to level specified. (may freeze client for a few seconds)", 150, command_scribespells) || command_add("sendzonespawns", "- Refresh spawn list for all clients in zone", 150, command_sendzonespawns) || @@ -1402,16 +1406,266 @@ void command_delpetition(Client *c, const Seperator *sep) void command_listnpcs(Client *c, const Seperator *sep) { - if (strcasecmp(sep->arg[1], "all") == 0) - entity_list.ListNPCs(c,sep->arg[1],sep->arg[2],0); - else if(sep->IsNumber(1) && sep->IsNumber(2)) - entity_list.ListNPCs(c,sep->arg[1],sep->arg[2],2); - else if(sep->arg[1][0] != 0) - entity_list.ListNPCs(c,sep->arg[1],sep->arg[2],1); + c->Message(0, "Deprecated, use the #list command (#list npcs )"); +} + +void command_list(Client *c, const Seperator *sep) +{ + std::string search_type; + if (strcasecmp(sep->arg[1], "npcs") == 0) { + search_type = "npcs"; + } + + if (strcasecmp(sep->arg[1], "players") == 0) { + search_type = "players"; + } + + if (strcasecmp(sep->arg[1], "corpses") == 0) { + search_type = "corpses"; + } + + if (strcasecmp(sep->arg[1], "doors") == 0) { + search_type = "doors"; + } + + if (strcasecmp(sep->arg[1], "objects") == 0) { + search_type = "objects"; + } + + if (search_type.length() > 0) { + + int entity_count = 0; + int found_count = 0; + + std::string search_string; + + if (sep->arg[2]) { + search_string = sep->arg[2]; + } + + /** + * NPC + */ + if (search_type.find("npcs") != std::string::npos) { + auto &entity_list_search = entity_list.GetMobList(); + + for (auto &itr : entity_list_search) { + Mob *entity = itr.second; + if (!entity->IsNPC()) { + continue; + } + + entity_count++; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Client + */ + if (search_type.find("players") != std::string::npos) { + auto &entity_list_search = entity_list.GetClientList(); + + for (auto &itr : entity_list_search) { + Client *entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Corpse + */ + if (search_type.find("corpses") != std::string::npos) { + auto &entity_list_search = entity_list.GetCorpseList(); + + for (auto &itr : entity_list_search) { + Corpse *entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | ID %5d | %s | x %.0f | y %0.f | z %.0f", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Doors + */ + if (search_type.find("doors") != std::string::npos) { + auto &entity_list_search = entity_list.GetDoorsList(); + + for (auto &itr : entity_list_search) { + Doors * entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetDoorName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | Entity ID %5d | Door ID %i | %s | x %.0f | y %0.f | z %.0f", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetDoorID(), + entity->GetDoorName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + /** + * Objects + */ + if (search_type.find("objects") != std::string::npos) { + auto &entity_list_search = entity_list.GetObjectList(); + + for (auto &itr : entity_list_search) { + Object * entity = itr.second; + + entity_count++; + + std::string entity_name = entity->GetModelName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos) { + continue; + } + + std::string saylink = StringFormat( + "#goto %.0f %0.f %.0f", + entity->GetX(), + entity->GetY(), + entity->GetZ()); + + c->Message( + 0, + "| %s | Entity ID %5d | Object DBID %i | %s | x %.0f | y %0.f | z %.0f", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Goto").c_str(), + entity->GetID(), + entity->GetDBID(), + entity->GetModelName(), + entity->GetX(), + entity->GetY(), + entity->GetZ() + ); + + found_count++; + } + } + + if (found_count) { + c->Message( + 0, "Found (%i) of type (%s) in zone (%i) total", + found_count, + search_type.c_str(), + entity_count + ); + } + } else { - c->Message(0, "Usage of #listnpcs:"); - c->Message(0, "#listnpcs [#] [#] (Each number would search by ID, ex. #listnpcs 1 30, searches 1-30)"); - c->Message(0, "#listnpcs [name] (Would search for a npc with [name])"); + c->Message(0, "Usage of #list"); + c->Message(0, "- #list [npcs|players|corpses|doors|objects] [search]"); + c->Message(0, "- Example: #list npc (Blank for all)"); } } @@ -1534,24 +1788,24 @@ void command_npcstats(Client *c, const Seperator *sep) c->Message(0, "ERROR: Target is not a NPC!"); else { auto target_npc = c->GetTarget()->CastToNPC(); - c->Message(0, "NPC Stats:"); - c->Message(0, "Name: %s NpcID: %u", target_npc->GetName(), target_npc->GetNPCTypeID()); - c->Message(0, "Race: %i Level: %i Class: %i Material: %i", target_npc->GetRace(), target_npc->GetLevel(), target_npc->GetClass(), target_npc->GetTexture()); - c->Message(0, "Current HP: %i Max HP: %i", target_npc->GetHP(), target_npc->GetMaxHP()); + c->Message(0, "# NPC Stats"); + c->Message(0, "- Name: %s NpcID: %u", target_npc->GetName(), target_npc->GetNPCTypeID()); + c->Message(0, "- Race: %i Level: %i Class: %i Material: %i", target_npc->GetRace(), target_npc->GetLevel(), target_npc->GetClass(), target_npc->GetTexture()); + c->Message(0, "- Current HP: %i Max HP: %i", target_npc->GetHP(), target_npc->GetMaxHP()); //c->Message(0, "Weapon Item Number: %s", target_npc->GetWeapNo()); - c->Message(0, "Gender: %i Size: %f Bodytype: %d", target_npc->GetGender(), target_npc->GetSize(), target_npc->GetBodyType()); - c->Message(0, "Runspeed: %.3f Walkspeed: %.3f", static_cast(0.025f * target_npc->GetRunspeed()), static_cast(0.025f * target_npc->GetWalkspeed())); - c->Message(0, "Spawn Group: %i Grid: %i", target_npc->GetSp2(), target_npc->GetGrid()); + c->Message(0, "- Gender: %i Size: %f Bodytype: %d", target_npc->GetGender(), target_npc->GetSize(), target_npc->GetBodyType()); + c->Message(0, "- Runspeed: %.3f Walkspeed: %.3f", static_cast(0.025f * target_npc->GetRunspeed()), static_cast(0.025f * target_npc->GetWalkspeed())); + c->Message(0, "- Spawn Group: %i Grid: %i", target_npc->GetSp2(), target_npc->GetGrid()); if (target_npc->proximity) { - c->Message(0, "Proximity: Enabled"); - c->Message(0, "Cur_X: %1.3f, Cur_Y: %1.3f, Cur_Z: %1.3f", target_npc->GetX(), target_npc->GetY(), target_npc->GetZ()); - c->Message(0, "Min_X: %1.3f(%1.3f), Max_X: %1.3f(%1.3f), X_Range: %1.3f", target_npc->proximity->min_x, (target_npc->proximity->min_x - target_npc->GetX()), target_npc->proximity->max_x, (target_npc->proximity->max_x - target_npc->GetX()), (target_npc->proximity->max_x - target_npc->proximity->min_x)); - c->Message(0, "Min_Y: %1.3f(%1.3f), Max_Y: %1.3f(%1.3f), Y_Range: %1.3f", target_npc->proximity->min_y, (target_npc->proximity->min_y - target_npc->GetY()), target_npc->proximity->max_y, (target_npc->proximity->max_y - target_npc->GetY()), (target_npc->proximity->max_y - target_npc->proximity->min_y)); - c->Message(0, "Min_Z: %1.3f(%1.3f), Max_Z: %1.3f(%1.3f), Z_Range: %1.3f", target_npc->proximity->min_z, (target_npc->proximity->min_z - target_npc->GetZ()), target_npc->proximity->max_z, (target_npc->proximity->max_z - target_npc->GetZ()), (target_npc->proximity->max_z - target_npc->proximity->min_z)); - c->Message(0, "Say: %s", (target_npc->proximity->say ? "Enabled" : "Disabled")); + c->Message(0, "- Proximity: Enabled"); + c->Message(0, "-- Cur_X: %1.3f, Cur_Y: %1.3f, Cur_Z: %1.3f", target_npc->GetX(), target_npc->GetY(), target_npc->GetZ()); + c->Message(0, "-- Min_X: %1.3f(%1.3f), Max_X: %1.3f(%1.3f), X_Range: %1.3f", target_npc->proximity->min_x, (target_npc->proximity->min_x - target_npc->GetX()), target_npc->proximity->max_x, (target_npc->proximity->max_x - target_npc->GetX()), (target_npc->proximity->max_x - target_npc->proximity->min_x)); + c->Message(0, "-- Min_Y: %1.3f(%1.3f), Max_Y: %1.3f(%1.3f), Y_Range: %1.3f", target_npc->proximity->min_y, (target_npc->proximity->min_y - target_npc->GetY()), target_npc->proximity->max_y, (target_npc->proximity->max_y - target_npc->GetY()), (target_npc->proximity->max_y - target_npc->proximity->min_y)); + c->Message(0, "-- Min_Z: %1.3f(%1.3f), Max_Z: %1.3f(%1.3f), Z_Range: %1.3f", target_npc->proximity->min_z, (target_npc->proximity->min_z - target_npc->GetZ()), target_npc->proximity->max_z, (target_npc->proximity->max_z - target_npc->GetZ()), (target_npc->proximity->max_z - target_npc->proximity->min_z)); + c->Message(0, "-- Say: %s", (target_npc->proximity->say ? "Enabled" : "Disabled")); } else { - c->Message(0, "Proximity: Disabled"); + c->Message(0, "-Proximity: Disabled"); } c->Message(0, ""); c->Message(0, "EmoteID: %i", target_npc->GetEmoteID()); @@ -1762,7 +2016,7 @@ void command_zheader(Client *c, const Seperator *sep) c->Message(0, "Invalid Zone Name: %s", sep->argplus[1]); else { - if (zone->LoadZoneCFG(sep->argplus[1], true)) + if (zone->LoadZoneCFG(sep->argplus[1], 0)) c->Message(0, "Successfully loaded zone header for %s from database.", sep->argplus[1]); else c->Message(0, "Failed to load zone header %s from database", sep->argplus[1]); @@ -2098,12 +2352,12 @@ void command_grid(Client *c, const Seperator *sep) } std::string query = StringFormat( - "SELECT `x`, `y`, `z`, `heading`, `number`, `pause` " - "FROM `grid_entries` " - "WHERE `zoneid` = %u and `gridid` = %i " - "ORDER BY `number` ", - zone->GetZoneID(), - target->CastToNPC()->GetGrid() + "SELECT `x`, `y`, `z`, `heading`, `number`, `pause` " + "FROM `grid_entries` " + "WHERE `zoneid` = %u and `gridid` = %i " + "ORDER BY `number` ", + zone->GetZoneID(), + target->CastToNPC()->GetGrid() ); auto results = database.QueryDatabase(query); @@ -2120,11 +2374,12 @@ void command_grid(Client *c, const Seperator *sep) /** * Depop any node npc's already spawned */ - auto &mob_list = entity_list.GetMobList(); - for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { + auto &mob_list = entity_list.GetMobList(); + for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { Mob *mob = itr->second; - if (mob->IsNPC() && mob->GetRace() == 2254) + if (mob->IsNPC() && mob->GetRace() == 2254) { mob->Depop(); + } } /** @@ -2134,21 +2389,22 @@ void command_grid(Client *c, const Seperator *sep) auto node_position = glm::vec4(atof(row[0]), atof(row[1]), atof(row[2]), atof(row[3])); NPC *npc = NPC::SpawnGridNodeNPC( - target->GetCleanName(), - node_position, - static_cast(target->CastToNPC()->GetGrid()), - static_cast(atoi(row[4])), - static_cast(atoi(row[5])) + target->GetCleanName(), + node_position, + static_cast(target->CastToNPC()->GetGrid()), + static_cast(atoi(row[4])), + static_cast(atoi(row[5])) ); npc->SetFlyMode(GravityBehavior::Flying); npc->GMMove(node_position.x, node_position.y, node_position.z, node_position.w); } } - else if (strcasecmp("delete", sep->arg[1]) == 0) - database.ModifyGrid(c, true,atoi(sep->arg[2]),0,0,zone->GetZoneID()); + else if (strcasecmp("delete", sep->arg[1]) == 0) { + database.ModifyGrid(c, true, atoi(sep->arg[2]), 0, 0, zone->GetZoneID()); + } else { - c->Message(0,"Usage: #grid add/delete grid_num wandertype pausetype"); - c->Message(0,"Usage: #grid max - displays the highest grid ID used in this zone (for add)"); + c->Message(0, "Usage: #grid add/delete grid_num wandertype pausetype"); + c->Message(0, "Usage: #grid max - displays the highest grid ID used in this zone (for add)"); } } @@ -4436,6 +4692,49 @@ void command_depopzone(Client *c, const Seperator *sep) c->Message(0, "Zone depoped."); } +void command_devtools(Client *c, const Seperator *sep) +{ + std::string menu_commands_search; + std::string window_toggle_command; + + /** + * Search entity commands + */ + menu_commands_search += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#list npcs", false, "NPC") + "] "; + menu_commands_search += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#list players", false, "Players") + "] "; + menu_commands_search += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#list corpses", false, "Corpses") + "] "; + menu_commands_search += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#list doors", false, "Doors") + "] "; + menu_commands_search += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#list objects", false, "Objects") + "] "; + + std::string dev_tools_window_key = StringFormat("%i-dev-tools-window-disabled", c->AccountID()); + + /** + * Handle window toggle + */ + if (strcasecmp(sep->arg[1], "disable_window") == 0) { + DataBucket::SetData(dev_tools_window_key, "true"); + c->SetDevToolsWindowEnabled(false); + } + if (strcasecmp(sep->arg[1], "enable_window") == 0) { + DataBucket::DeleteData(dev_tools_window_key); + c->SetDevToolsWindowEnabled(true); + } + + /** + * Show window status + */ + window_toggle_command = "Disabled [" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#devtools enable_window", false, "Enable") + "] "; + if (c->IsDevToolsWindowEnabled()) { + window_toggle_command = "Enabled [" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#devtools disable_window", false, "Disable") + "] "; + } + + /** + * Print menu + */ + c->Message(0, "| [Devtools] Window %s", window_toggle_command.c_str()); + c->Message(0, "| [Devtools] Search %s", menu_commands_search.c_str()); +} + void command_repop(Client *c, const Seperator *sep) { int timearg = 1; @@ -11234,6 +11533,133 @@ void command_reloadtraps(Client *c, const Seperator *sep) c->Message(CC_Default, "Traps reloaded for %s.", zone->GetShortName()); } +void command_scale(Client *c, const Seperator *sep) +{ + if (sep->argnum == 0) { + c->Message(15, "# Usage # "); + c->Message(15, "#scale [static/dynamic] (With targeted NPC)"); + c->Message(15, "#scale [npc_name_search] [static/dynamic] (To make zone-wide changes)"); + c->Message(15, "#scale all [static/dynamic]"); + return; + } + + /** + * Targeted changes + */ + if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->argnum < 2) { + NPC * npc = c->GetTarget()->CastToNPC(); + + bool apply_status = false; + if (strcasecmp(sep->arg[1], "dynamic") == 0) { + c->Message(15, "Applying global base scaling to npc dynamically (All stats set to zeroes)..."); + apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(npc); + } + else if (strcasecmp(sep->arg[1], "static") == 0) { + c->Message(15, "Applying global base scaling to npc statically (Copying base stats onto NPC)..."); + apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(npc); + } + else { + return; + } + + if (apply_status) { + c->Message(15, "Applied to NPC '%s' successfully!", npc->GetName()); + } + else { + c->Message(15, "Failed to load scaling data from the database " + "for this npc / type, see 'NPCScaling' log for more info"); + } + } + else if (c->GetTarget() && sep->argnum < 2) { + c->Message(15, "Target must be an npc!"); + } + + /** + * Zonewide + */ + if (sep->argnum > 1) { + + std::string scale_type; + if (strcasecmp(sep->arg[2], "dynamic") == 0) { + scale_type = "dynamic"; + } + else if (strcasecmp(sep->arg[2], "static") == 0) { + scale_type = "static"; + } + + if (scale_type.length() <= 0) { + c->Message(15, "You must first set if you intend on using static versus dynamic for these changes"); + c->Message(15, "#scale [npc_name_search] [static/dynamic]"); + c->Message(15, "#scale all [static/dynamic]"); + return; + } + + std::string search_string = sep->arg[1]; + + auto &entity_list_search = entity_list.GetNPCList(); + + int found_count = 0; + for (auto &itr : entity_list_search) { + NPC *entity = itr.second; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos && strcasecmp(sep->arg[1], "all") != 0) { + continue; + } + + std::string status = "(Searching)"; + + if (strcasecmp(sep->arg[3], "apply") == 0) { + status = "(Applying)"; + + if (strcasecmp(sep->arg[2], "dynamic") == 0) { + npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(entity); + } + if (strcasecmp(sep->arg[2], "static") == 0) { + npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(entity); + } + } + + c->Message( + 15, + "| ID %5d | %s | x %.0f | y %0.f | z %.0f | DBID %u %s", + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ(), + entity->GetNPCTypeID(), + status.c_str() + ); + + found_count++; + } + + if (strcasecmp(sep->arg[3], "apply") == 0) { + c->Message(15, "%s scaling applied against (%i) NPC's", sep->arg[2], found_count); + } + else { + + std::string saylink = StringFormat( + "#scale %s %s apply", + sep->arg[1], + sep->arg[2] + ); + + c->Message(15, "Found (%i) NPC's that match this search...", found_count); + c->Message( + 15, "To apply these changes, click <%s> or type %s", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Apply").c_str(), + saylink.c_str() + ); + } + } +} + // All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block. #ifdef BOTS #include "bot_command.h" diff --git a/zone/command.h b/zone/command.h index 95b9e77dd..abac73def 100644 --- a/zone/command.h +++ b/zone/command.h @@ -84,6 +84,7 @@ void command_deletegraveyard(Client *c, const Seperator *sep); void command_delpetition(Client *c, const Seperator *sep); void command_depop(Client *c, const Seperator *sep); void command_depopzone(Client *c, const Seperator *sep); +void command_devtools(Client *c, const Seperator *sep); void command_details(Client *c, const Seperator *sep); void command_disablerecipe(Client *c, const Seperator *sep); void command_disarmtrap(Client *c, const Seperator *sep); @@ -152,6 +153,7 @@ void command_kill(Client *c, const Seperator *sep); void command_lastname(Client *c, const Seperator *sep); void command_level(Client *c, const Seperator *sep); void command_listnpcs(Client *c, const Seperator *sep); +void command_list(Client *c, const Seperator *sep); void command_listpetition(Client *c, const Seperator *sep); void command_load_shared_memory(Client *c, const Seperator *sep); void command_loc(Client *c, const Seperator *sep); @@ -242,6 +244,7 @@ void command_resetaa_timer(Client *c, const Seperator *sep); void command_revoke(Client *c, const Seperator *sep); void command_rules(Client *c, const Seperator *sep); void command_save(Client *c, const Seperator *sep); +void command_scale(Client *c, const Seperator *sep); void command_scribespell(Client *c, const Seperator *sep); void command_scribespells(Client *c, const Seperator *sep); void command_sendop(Client *c, const Seperator *sep); diff --git a/zone/doors.cpp b/zone/doors.cpp index dc963ab2a..899e3f6b5 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -820,7 +820,7 @@ void Doors::SetDisableTimer(bool flag) { void Doors::CreateDatabaseEntry() { - if(database.GetDoorsDBCountPlusOne(zone->GetShortName(), zone->GetInstanceVersion()) - 1 >= 255) { + if (database.GetDoorsDBCountPlusOne(zone->GetShortName(), zone->GetInstanceVersion()) - 1 >= 255) { return; } @@ -828,18 +828,32 @@ void Doors::CreateDatabaseEntry() * Persist */ database.InsertDoor( - GetDoorDBID(), - GetDoorID(), - GetDoorName(), - m_Position, - GetOpenType(), - static_cast(GetGuildID()), - GetLockpick(), - GetKeyItem(), - static_cast(GetDoorParam()), - static_cast(GetInvertState()), - GetIncline(), - GetSize() + GetDoorDBID(), + GetDoorID(), + GetDoorName(), + m_Position, + GetOpenType(), + static_cast(GetGuildID()), + GetLockpick(), + GetKeyItem(), + static_cast(GetDoorParam()), + static_cast(GetInvertState()), + GetIncline(), + GetSize() ); } +float Doors::GetX() +{ + return m_Position.x; +} + +float Doors::GetY() +{ + return m_Position.y; +} + +float Doors::GetZ() +{ + return m_Position.z; +} diff --git a/zone/doors.h b/zone/doors.h index ef83361c4..d7fbfc021 100644 --- a/zone/doors.h +++ b/zone/doors.h @@ -64,6 +64,10 @@ public: void SetSize(uint16 size); void ToggleState(Mob *sender); + float GetX(); + float GetY(); + float GetZ(); + private: uint32 database_id; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 7ec3ecc5a..86b72e1b8 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -2870,6 +2870,20 @@ XS(XS__GetInstanceID) { XSRETURN_UV(id); } +XS(XS__GetInstanceIDByCharID); +XS(XS__GetInstanceIDByCharID) { + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: quest::GetInstanceIDByCharID(string zone_name, uint16 version, uint32 char_id)"); + + char *zone = (char *) SvPV_nolen(ST(0)); + uint16 version = (int) SvUV(ST(1)); + uint32 char_id = (int) SvUV(ST(2)); + uint16 id = quest_manager.GetInstanceIDByCharID(zone, version, char_id); + + XSRETURN_UV(id); +} + XS(XS__GetCharactersInInstance); XS(XS__GetCharactersInInstance) { dXSARGS; @@ -2921,6 +2935,19 @@ XS(XS__AssignToInstance) { XSRETURN_EMPTY; } +XS(XS__AssignToInstanceByCharID); +XS(XS__AssignToInstanceByCharID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::AssignToInstanceByCharID(uint16 instance_id, uint32 char_id)"); + + uint16 instance_id = (int) SvUV(ST(0)); + uint32 char_id = (int) SvUV(ST(1)); + quest_manager.AssignToInstanceByCharID(instance_id, char_id); + + XSRETURN_EMPTY; +} + XS(XS__AssignGroupToInstance); XS(XS__AssignGroupToInstance) { dXSARGS; @@ -2957,6 +2984,19 @@ XS(XS__RemoveFromInstance) { XSRETURN_EMPTY; } +XS(XS__RemoveFromInstanceByCharID); +XS(XS__RemoveFromInstanceByCharID) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id)"); + + uint16 instance_id = (int) SvUV(ST(0)); + uint32 char_id = (int) SvUV(ST(1)); + quest_manager.RemoveFromInstanceByCharID(instance_id, char_id); + + XSRETURN_EMPTY; +} + XS(XS__RemoveAllFromInstance); XS(XS__RemoveAllFromInstance) { dXSARGS; @@ -3025,10 +3065,10 @@ XS(XS__saylink) { Perl_croak(aTHX_ "Usage: quest::saylink(string message, [bool silent = false], [link_name = message])"); dXSTARG; - Const_char *RETVAL; - char message[250]; - char link_name[250]; - bool silent = false; + std::string RETVAL; + char message[250]; + char link_name[250]; + bool silent = false; strcpy(message, (char *) SvPV_nolen(ST(0))); if (items >= 2) { silent = ((int) SvIV(ST(1))) == 0 ? false : true; @@ -3039,7 +3079,8 @@ XS(XS__saylink) { strcpy(link_name, message); RETVAL = quest_manager.saylink(message, silent, link_name); - sv_setpv(TARG, RETVAL); + + sv_setpv(TARG, RETVAL.c_str()); XSprePUSH; PUSHTARG; XSRETURN(1); @@ -3675,6 +3716,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "AssignGroupToInstance"), XS__AssignGroupToInstance, file); newXS(strcpy(buf, "AssignRaidToInstance"), XS__AssignRaidToInstance, file); newXS(strcpy(buf, "AssignToInstance"), XS__AssignToInstance, file); + newXS(strcpy(buf, "AssignToInstanceByCharID"), XS__AssignToInstanceByCharID, file); newXS(strcpy(buf, "ChooseRandom"), XS__ChooseRandom, file); newXS(strcpy(buf, "CreateInstance"), XS__CreateInstance, file); newXS(strcpy(buf, "DestroyInstance"), XS__DestroyInstance, file); @@ -3686,6 +3728,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "FlyMode"), XS__FlyMode, file); newXS(strcpy(buf, "GetCharactersInInstance"), XS__GetCharactersInInstance, file); newXS(strcpy(buf, "GetInstanceID"), XS__GetInstanceID, file); + newXS(strcpy(buf, "GetInstanceIDByCharID"), XS__GetInstanceIDByCharID, file); newXS(strcpy(buf, "GetSpellResistType"), XS__GetSpellResistType, file); newXS(strcpy(buf, "GetSpellTargetType"), XS__GetSpellTargetType, file); newXS(strcpy(buf, "GetTimeSeconds"), XS__GetTimeSeconds, file); @@ -3704,6 +3747,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "MovePCInstance"), XS__MovePCInstance, file); newXS(strcpy(buf, "RemoveAllFromInstance"), XS__RemoveAllFromInstance, file); newXS(strcpy(buf, "RemoveFromInstance"), XS__RemoveFromInstance, file); + newXS(strcpy(buf, "RemoveFromInstanceByCharID"), XS__RemoveFromInstanceByCharID, file); newXS(strcpy(buf, "SendMail"), XS__SendMail, file); newXS(strcpy(buf, "SetRunning"), XS__SetRunning, file); newXS(strcpy(buf, "activespeakactivity"), XS__activespeakactivity, file); diff --git a/zone/entity.cpp b/zone/entity.cpp index f7f5b4594..c2201f97b 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -40,6 +40,8 @@ #include "string_ids.h" #include "worldserver.h" #include "water_map.h" +#include "npc_scale_manager.h" +#include "../common/say_link.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -2703,58 +2705,6 @@ char *EntityList::RemoveNumbers(char *name) return name; } -void EntityList::ListNPCs(Client* client, const char *arg1, const char *arg2, uint8 searchtype) -{ - if (arg1 == 0) - searchtype = 0; - else if (arg2 == 0 && searchtype >= 2) - searchtype = 0; - uint32 x = 0; - uint32 z = 0; - char sName[36]; - - auto it = npc_list.begin(); - client->Message(0, "NPCs in the zone:"); - if (searchtype == 0) { - while (it != npc_list.end()) { - NPC *n = it->second; - - client->Message(0, " %5d: %s (%.0f, %0.f, %.0f)", n->GetID(), n->GetName(), n->GetX(), n->GetY(), n->GetZ()); - x++; - z++; - ++it; - } - } else if (searchtype == 1) { - client->Message(0, "Searching by name method. (%s)",arg1); - auto tmp = new char[strlen(arg1) + 1]; - strcpy(tmp, arg1); - strupr(tmp); - while (it != npc_list.end()) { - z++; - strcpy(sName, it->second->GetName()); - strupr(sName); - if (strstr(sName, tmp)) { - NPC *n = it->second; - client->Message(0, " %5d: %s (%.0f, %.0f, %.0f)", n->GetID(), n->GetName(), n->GetX(), n->GetY(), n->GetZ()); - x++; - } - ++it; - } - safe_delete_array(tmp); - } else if (searchtype == 2) { - client->Message(0, "Searching by number method. (%s %s)",arg1,arg2); - while (it != npc_list.end()) { - z++; - if ((it->second->GetID() >= atoi(arg1)) && (it->second->GetID() <= atoi(arg2)) && (atoi(arg1) <= atoi(arg2))) { - client->Message(0, " %5d: %s", it->second->GetID(), it->second->GetName()); - x++; - } - ++it; - } - } - client->Message(0, "%d npcs listed. There is a total of %d npcs in this zone.", x, z); -} - void EntityList::ListNPCCorpses(Client *client) { uint32 x = 0; diff --git a/zone/entity.h b/zone/entity.h index 7c9c06c31..89f64b798 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -381,7 +381,6 @@ public: void SendPetitionToAdmins(); void AddLootToNPCS(uint32 item_id, uint32 count); - void ListNPCs(Client* client, const char* arg1 = 0, const char* arg2 = 0, uint8 searchtype = 0); void ListNPCCorpses(Client* client); void ListPlayerCorpses(Client* client); int32 DeleteNPCCorpses(); diff --git a/zone/exp.cpp b/zone/exp.cpp index 94f36197d..5ffdd5690 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -684,14 +684,12 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { } } - if(RuleB(Character, PerCharacterQglobalMaxLevel)){ - uint32 MaxLevel = GetCharMaxLevelFromQGlobal(); - if(MaxLevel){ - if(GetLevel() >= MaxLevel){ - uint32 expneeded = GetEXPForLevel(MaxLevel); - if(set_exp > expneeded) { - set_exp = expneeded; - } + if (GetClientMaxLevel() > 0) { + int client_max_level = GetClientMaxLevel(); + if (GetLevel() >= client_max_level) { + uint32 expneeded = GetEXPForLevel(client_max_level); + if(set_exp > expneeded) { + set_exp = expneeded; } } } @@ -1130,7 +1128,23 @@ uint32 Client::GetCharMaxLevelFromQGlobal() { ++gcount; } - return false; + return 0; +} + +uint32 Client::GetCharMaxLevelFromBucket() { + uint32 char_id = this->CharacterID(); + std::string query = StringFormat("SELECT value FROM data_buckets WHERE key = '%i-CharMaxLevel'", char_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Log(Logs::General, Logs::Error, "Data bucket for CharMaxLevel for char ID %i failed.", char_id); + return 0; + } + + if (results.RowCount() > 0) { + auto row = results.begin(); + return atoi(row[0]); + } + return 0; } uint32 Client::GetRequiredAAExperience() { diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 5ee89f210..f94194754 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -789,33 +789,29 @@ int lua_merchant_count_item(uint32 npc_id, uint32 item_id) { std::string lua_item_link(int item_id) { char text[250] = { 0 }; - quest_manager.varlink(text, item_id); - return std::string(text); + return quest_manager.varlink(text, item_id); } std::string lua_say_link(const char *phrase, bool silent, const char *link_name) { char text[256] = { 0 }; strncpy(text, phrase, 255); - quest_manager.saylink(text, silent, link_name); - return std::string(text); + return quest_manager.saylink(text, silent, link_name); } std::string lua_say_link(const char *phrase, bool silent) { char text[256] = { 0 }; strncpy(text, phrase, 255); - quest_manager.saylink(text, silent, text); - return std::string(text); + return quest_manager.saylink(text, silent, text); } std::string lua_say_link(const char *phrase) { char text[256] = { 0 }; strncpy(text, phrase, 255); - quest_manager.saylink(text, false, text); - return std::string(text); + return quest_manager.saylink(text, false, text); } std::string lua_get_data(std::string bucket_key) { @@ -866,10 +862,18 @@ int lua_get_instance_id(const char *zone, uint32 version) { return quest_manager.GetInstanceID(zone, version); } +int lua_get_instance_id_by_char_id(const char *zone, uint32 version, uint32 char_id) { + return quest_manager.GetInstanceIDByCharID(zone, version, char_id); +} + void lua_assign_to_instance(uint32 instance_id) { quest_manager.AssignToInstance(instance_id); } +void lua_assign_to_instance_by_char_id(uint32 instance_id, uint32 char_id) { + quest_manager.AssignToInstanceByCharID(instance_id, char_id); +} + void lua_assign_group_to_instance(uint32 instance_id) { quest_manager.AssignGroupToInstance(instance_id); } @@ -882,6 +886,10 @@ void lua_remove_from_instance(uint32 instance_id) { quest_manager.RemoveFromInstance(instance_id); } +void lua_remove_from_instance_by_char_id(uint32 instance_id, uint32 char_id) { + quest_manager.RemoveFromInstanceByCharID(instance_id, char_id); +} + void lua_remove_all_from_instance(uint32 instance_id) { quest_manager.RemoveAllFromInstance(instance_id); } @@ -1430,19 +1438,19 @@ void lua_create_npc(luabind::adl::object table, float x, float y, float z, float LuaCreateNPCParse(AC, uint32, 0); LuaCreateNPCParse(Mana, uint32, 0); LuaCreateNPCParse(ATK, uint32, 0); - LuaCreateNPCParse(STR, uint32, 75); - LuaCreateNPCParse(STA, uint32, 75); - LuaCreateNPCParse(DEX, uint32, 75); - LuaCreateNPCParse(AGI, uint32, 75); - LuaCreateNPCParse(INT, uint32, 75); - LuaCreateNPCParse(WIS, uint32, 75); - LuaCreateNPCParse(CHA, uint32, 75); - LuaCreateNPCParse(MR, int32, 25); - LuaCreateNPCParse(FR, int32, 25); - LuaCreateNPCParse(CR, int32, 25); - LuaCreateNPCParse(PR, int32, 25); - LuaCreateNPCParse(DR, int32, 25); - LuaCreateNPCParse(Corrup, int32, 25); + LuaCreateNPCParse(STR, uint32, 0); + LuaCreateNPCParse(STA, uint32, 0); + LuaCreateNPCParse(DEX, uint32, 0); + LuaCreateNPCParse(AGI, uint32, 0); + LuaCreateNPCParse(INT, uint32, 0); + LuaCreateNPCParse(WIS, uint32, 0); + LuaCreateNPCParse(CHA, uint32, 0); + LuaCreateNPCParse(MR, int32, 0); + LuaCreateNPCParse(FR, int32, 0); + LuaCreateNPCParse(CR, int32, 0); + LuaCreateNPCParse(PR, int32, 0); + LuaCreateNPCParse(DR, int32, 0); + LuaCreateNPCParse(Corrup, int32, 0); LuaCreateNPCParse(PhR, int32, 0); LuaCreateNPCParse(haircolor, uint8, 0); LuaCreateNPCParse(beardcolor, uint8, 0); @@ -1689,13 +1697,16 @@ luabind::scope lua_register_general() { luabind::def("destroy_instance", &lua_destroy_instance), luabind::def("update_instance_timer", &lua_update_instance_timer), luabind::def("get_instance_id", &lua_get_instance_id), + luabind::def("get_instance_id_by_char_id", &lua_get_instance_id_by_char_id), luabind::def("get_instance_timer", &lua_get_instance_timer), luabind::def("get_instance_timer_by_id", &lua_get_instance_timer_by_id), luabind::def("get_characters_in_instance", &lua_get_characters_in_instance), luabind::def("assign_to_instance", &lua_assign_to_instance), + luabind::def("assign_to_instance_by_char_id", &lua_assign_to_instance_by_char_id), luabind::def("assign_group_to_instance", &lua_assign_group_to_instance), luabind::def("assign_raid_to_instance", &lua_assign_raid_to_instance), luabind::def("remove_from_instance", &lua_remove_from_instance), + luabind::def("remove_from_instance_by_char_id", &lua_remove_from_instance_by_char_id), luabind::def("remove_all_from_instance", &lua_remove_all_from_instance), luabind::def("flag_instance_by_group_leader", &lua_flag_instance_by_group_leader), luabind::def("flag_instance_by_raid_leader", &lua_flag_instance_by_raid_leader), diff --git a/zone/mob.cpp b/zone/mob.cpp index a2e55dd19..9688cca16 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -122,60 +122,65 @@ Mob::Mob(const char* in_name, targeted = 0; currently_fleeing = false; + currently_fleeing = false; + AI_Init(); SetMoving(false); - moved = false; + moved = false; m_RewindLocation = glm::vec3(); name[0] = 0; orig_name[0] = 0; + clean_name[0] = 0; - lastname[0] = 0; + lastname[0] = 0; if (in_name) { strn0cpy(name, in_name, 64); strn0cpy(orig_name, in_name, 64); } - if (in_lastname) + if (in_lastname) { strn0cpy(lastname, in_lastname, 64); - cur_hp = in_cur_hp; - max_hp = in_max_hp; - base_hp = in_max_hp; - gender = in_gender; - race = in_race; - base_gender = in_gender; - base_race = in_race; - class_ = in_class; - bodytype = in_bodytype; + } + cur_hp = in_cur_hp; + max_hp = in_max_hp; + base_hp = in_max_hp; + gender = in_gender; + race = in_race; + base_gender = in_gender; + base_race = in_race; + class_ = in_class; + bodytype = in_bodytype; orig_bodytype = in_bodytype; - deity = in_deity; - level = in_level; - orig_level = in_level; - npctype_id = in_npctype_id; - size = in_size; - base_size = size; - runspeed = in_runspeed; + deity = in_deity; + level = in_level; + orig_level = in_level; + npctype_id = in_npctype_id; + size = in_size; + base_size = size; + runspeed = in_runspeed; // neotokyo: sanity check - if (runspeed < 0 || runspeed > 20) + if (runspeed < 0 || runspeed > 20) { runspeed = 1.25f; - base_runspeed = (int)((float)runspeed * 40.0f); + // clients -- todo movement this doesn't take into account gm speed we need to fix that. + if (runspeed == 0.7f) { - base_runspeed = 28; - walkspeed = 0.3f; + base_runspeed = 28; + walkspeed = 0.3f; base_walkspeed = 12; - fearspeed = 0.625f; + fearspeed = 0.625f; base_fearspeed = 25; // npcs } else { base_walkspeed = base_runspeed * 100 / 265; - walkspeed = ((float)base_walkspeed) * 0.025f; + walkspeed = ((float) base_walkspeed) * 0.025f; base_fearspeed = base_runspeed * 100 / 127; - fearspeed = ((float)base_fearspeed) * 0.025f; + fearspeed = ((float) base_fearspeed) * 0.025f; } last_hp_percent = 0; - last_hp = 0; + last_hp = 0; current_speed = base_runspeed; @@ -183,206 +188,201 @@ Mob::Mob(const char* in_name, // sanity check - if (runspeed < 0 || runspeed > 20) + if (runspeed < 0 || runspeed > 20) { runspeed = 1.25f; + } - m_Light.Type[EQEmu::lightsource::LightInnate] = in_light; + m_Light.Type[EQEmu::lightsource::LightInnate] = in_light; m_Light.Level[EQEmu::lightsource::LightInnate] = EQEmu::lightsource::TypeToLevel(m_Light.Type[EQEmu::lightsource::LightInnate]); - m_Light.Type[EQEmu::lightsource::LightActive] = m_Light.Type[EQEmu::lightsource::LightInnate]; + m_Light.Type[EQEmu::lightsource::LightActive] = m_Light.Type[EQEmu::lightsource::LightInnate]; m_Light.Level[EQEmu::lightsource::LightActive] = m_Light.Level[EQEmu::lightsource::LightInnate]; - texture = in_texture; - helmtexture = in_helmtexture; - armtexture = in_armtexture; + texture = in_texture; + helmtexture = in_helmtexture; + armtexture = in_armtexture; bracertexture = in_bracertexture; - handtexture = in_handtexture; - legtexture = in_legtexture; - feettexture = in_feettexture; - multitexture = (armtexture || bracertexture || handtexture || legtexture || feettexture); + handtexture = in_handtexture; + legtexture = in_legtexture; + feettexture = in_feettexture; + multitexture = (armtexture || bracertexture || handtexture || legtexture || feettexture); - haircolor = in_haircolor; - beardcolor = in_beardcolor; - eyecolor1 = in_eyecolor1; - eyecolor2 = in_eyecolor2; - hairstyle = in_hairstyle; - luclinface = in_luclinface; - beard = in_beard; - drakkin_heritage = in_drakkin_heritage; - drakkin_tattoo = in_drakkin_tattoo; - drakkin_details = in_drakkin_details; - attack_speed = 0; - attack_delay = 0; - slow_mitigation = 0; - findable = false; - trackable = true; - has_shieldequiped = false; + haircolor = in_haircolor; + beardcolor = in_beardcolor; + eyecolor1 = in_eyecolor1; + eyecolor2 = in_eyecolor2; + hairstyle = in_hairstyle; + luclinface = in_luclinface; + beard = in_beard; + drakkin_heritage = in_drakkin_heritage; + drakkin_tattoo = in_drakkin_tattoo; + drakkin_details = in_drakkin_details; + attack_speed = 0; + attack_delay = 0; + slow_mitigation = 0; + findable = false; + trackable = true; + has_shieldequiped = false; has_twohandbluntequiped = false; - has_twohanderequipped = false; - can_facestab = false; - has_numhits = false; - has_MGB = false; - has_ProjectIllusion = false; - SpellPowerDistanceMod = 0; - last_los_check = false; + has_twohanderequipped = false; + can_facestab = false; + has_numhits = false; + has_MGB = false; + has_ProjectIllusion = false; + SpellPowerDistanceMod = 0; + last_los_check = false; - if (in_aa_title > 0) + if (in_aa_title > 0) { aa_title = in_aa_title; - else + } + else { aa_title = 0xFF; - AC = in_ac; - ATK = in_atk; - STR = in_str; - STA = in_sta; - DEX = in_dex; - AGI = in_agi; - INT = in_int; - WIS = in_wis; - CHA = in_cha; - MR = CR = FR = DR = PR = Corrup = 0; + } - ExtraHaste = 0; - bEnraged = false; - - shield_target = nullptr; - current_mana = 0; - max_mana = 0; - hp_regen = in_hp_regen; - mana_regen = in_mana_regen; - ooc_regen = RuleI(NPC, OOCRegen); //default Out of Combat Regen - maxlevel = in_maxlevel; - scalerate = in_scalerate; - invisible = false; - invisible_undead = false; + AC = in_ac; + ATK = in_atk; + STR = in_str; + STA = in_sta; + DEX = in_dex; + AGI = in_agi; + INT = in_int; + WIS = in_wis; + CHA = in_cha; + MR = CR = FR = DR = PR = Corrup = 0; + ExtraHaste = 0; + bEnraged = false; + shield_target = nullptr; + current_mana = 0; + max_mana = 0; + hp_regen = in_hp_regen; + mana_regen = in_mana_regen; + ooc_regen = RuleI(NPC, OOCRegen); //default Out of Combat Regen + maxlevel = in_maxlevel; + scalerate = in_scalerate; + invisible = 0; + invisible_undead = false; invisible_animals = false; - sneaking = false; - hidden = false; - improved_hidden = false; - invulnerable = false; - IsFullHP = (cur_hp == max_hp); - qglobal = 0; - spawned = false; - rare_spawn = false; + sneaking = false; + hidden = false; + improved_hidden = false; + invulnerable = false; + IsFullHP = (cur_hp == max_hp); + qglobal = 0; + spawned = false; + rare_spawn = false; InitializeBuffSlots(); // clear the proc arrays - int i; - int j; - for (j = 0; j < MAX_PROCS; j++) - { - PermaProcs[j].spellID = SPELL_UNKNOWN; - PermaProcs[j].chance = 0; - PermaProcs[j].base_spellID = SPELL_UNKNOWN; - PermaProcs[j].level_override = -1; - SpellProcs[j].spellID = SPELL_UNKNOWN; - SpellProcs[j].chance = 0; - SpellProcs[j].base_spellID = SPELL_UNKNOWN; - SpellProcs[j].level_override = -1; - DefensiveProcs[j].spellID = SPELL_UNKNOWN; - DefensiveProcs[j].chance = 0; - DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; + for (int j = 0; j < MAX_PROCS; j++) { + PermaProcs[j].spellID = SPELL_UNKNOWN; + PermaProcs[j].chance = 0; + PermaProcs[j].base_spellID = SPELL_UNKNOWN; + PermaProcs[j].level_override = -1; + SpellProcs[j].spellID = SPELL_UNKNOWN; + SpellProcs[j].chance = 0; + SpellProcs[j].base_spellID = SPELL_UNKNOWN; + SpellProcs[j].level_override = -1; + DefensiveProcs[j].spellID = SPELL_UNKNOWN; + DefensiveProcs[j].chance = 0; + DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; DefensiveProcs[j].level_override = -1; - RangedProcs[j].spellID = SPELL_UNKNOWN; - RangedProcs[j].chance = 0; - RangedProcs[j].base_spellID = SPELL_UNKNOWN; - RangedProcs[j].level_override = -1; + RangedProcs[j].spellID = SPELL_UNKNOWN; + RangedProcs[j].chance = 0; + RangedProcs[j].base_spellID = SPELL_UNKNOWN; + RangedProcs[j].level_override = -1; } - for (i = EQEmu::textures::textureBegin; i < EQEmu::textures::materialCount; i++) - { + for (int i = EQEmu::textures::textureBegin; i < EQEmu::textures::materialCount; i++) { armor_tint.Slot[i].Color = in_armor_tint.Slot[i].Color; } std::fill(std::begin(m_spellHitsLeft), std::end(m_spellHitsLeft), 0); - m_Delta = glm::vec4(); + m_Delta = glm::vec4(); animation = 0; - logging_enabled = false; - isgrouped = false; + isgrouped = false; israidgrouped = false; IsHorse = false; entity_id_being_looted = 0; - _appearance = eaStanding; - pRunAnimSpeed = 0; + _appearance = eaStanding; + pRunAnimSpeed = 0; spellend_timer.Disable(); bardsong_timer.Disable(); - bardsong = 0; - bardsong_target_id = 0; - casting_spell_id = 0; - casting_spell_timer = 0; + bardsong = 0; + bardsong_target_id = 0; + casting_spell_id = 0; + casting_spell_timer = 0; casting_spell_timer_duration = 0; casting_spell_inventory_slot = 0; - casting_spell_aa_id = 0; - target = 0; + casting_spell_aa_id = 0; + target = 0; ActiveProjectileATK = false; - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) - { - ProjectileAtk[i].increment = 0; + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { + ProjectileAtk[i].increment = 0; ProjectileAtk[i].hit_increment = 0; - ProjectileAtk[i].target_id = 0; - ProjectileAtk[i].wpn_dmg = 0; - ProjectileAtk[i].origin_x = 0.0f; - ProjectileAtk[i].origin_y = 0.0f; - ProjectileAtk[i].origin_z = 0.0f; - ProjectileAtk[i].tlast_x = 0.0f; - ProjectileAtk[i].tlast_y = 0.0f; - ProjectileAtk[i].ranged_id = 0; - ProjectileAtk[i].ammo_id = 0; - ProjectileAtk[i].ammo_slot = 0; - ProjectileAtk[i].skill = 0; - ProjectileAtk[i].speed_mod = 0.0f; + ProjectileAtk[i].target_id = 0; + ProjectileAtk[i].wpn_dmg = 0; + ProjectileAtk[i].origin_x = 0.0f; + ProjectileAtk[i].origin_y = 0.0f; + ProjectileAtk[i].origin_z = 0.0f; + ProjectileAtk[i].tlast_x = 0.0f; + ProjectileAtk[i].tlast_y = 0.0f; + ProjectileAtk[i].ranged_id = 0; + ProjectileAtk[i].ammo_id = 0; + ProjectileAtk[i].ammo_slot = 0; + ProjectileAtk[i].skill = 0; + ProjectileAtk[i].speed_mod = 0.0f; } memset(&itembonuses, 0, sizeof(StatBonuses)); memset(&spellbonuses, 0, sizeof(StatBonuses)); memset(&aabonuses, 0, sizeof(StatBonuses)); - spellbonuses.AggroRange = -1; + spellbonuses.AggroRange = -1; spellbonuses.AssistRange = -1; SetPetID(0); SetOwnerID(0); - typeofpet = petNone; // default to not a pet - petpower = 0; - held = false; - gheld = false; - nocast = false; - focused = false; - pet_stop = false; - pet_regroup = false; - _IsTempPet = false; - pet_owner_client = false; + typeofpet = petNone; // default to not a pet + petpower = 0; + held = false; + gheld = false; + nocast = false; + focused = false; + pet_stop = false; + pet_regroup = false; + _IsTempPet = false; + pet_owner_client = false; pet_targetlock_id = 0; attacked_count = 0; - mezzed = false; - stunned = false; - silenced = false; - amnesiad = false; - inWater = false; + mezzed = false; + stunned = false; + silenced = false; + amnesiad = false; + inWater = false; int m; - for (m = 0; m < MAX_SHIELDERS; m++) - { - shielder[m].shielder_id = 0; + for (m = 0; m < MAX_SHIELDERS; m++) { + shielder[m].shielder_id = 0; shielder[m].shielder_bonus = 0; } destructibleobject = false; - wandertype = 0; - pausetype = 0; - cur_wp = 0; - m_CurrentWayPoint = glm::vec4(); - cur_wp_pause = 0; - patrol = 0; - follow_id = 0; - follow_dist = 100; // Default Distance for Follow - follow_run = true; // We can run if distance great enough - no_target_hotkey = false; - flee_mode = false; - currently_fleeing = false; + wandertype = 0; + pausetype = 0; + cur_wp = 0; + m_CurrentWayPoint = glm::vec4(); + cur_wp_pause = 0; + patrol = 0; + follow_id = 0; + follow_dist = 100; // Default Distance for Follow + follow_run = true; // We can run if distance great enough + no_target_hotkey = false; + flee_mode = false; + currently_fleeing = false; flee_timer.Start(); permarooted = (runspeed > 0) ? false : true; @@ -396,12 +396,13 @@ Mob::Mob(const char* in_name, for (i = 0; i < MAX_SPELL_TRIGGER * 2; i++) { viral_spells[i] = 0; } - pStandingPetOrder = SPO_Follow; - pseudo_rooted = false; - see_invis = GetSeeInvisible(in_see_invis); - see_invis_undead = GetSeeInvisible(in_see_invis_undead); - see_hide = GetSeeInvisible(in_see_hide); + pStandingPetOrder = SPO_Follow; + pseudo_rooted = false; + + see_invis = GetSeeInvisible(in_see_invis); + see_invis_undead = GetSeeInvisible(in_see_invis_undead); + see_hide = GetSeeInvisible(in_see_hide); see_improved_hide = GetSeeInvisible(in_see_improved_hide); qglobal = in_qglobal != 0; @@ -410,12 +411,12 @@ Mob::Mob(const char* in_name, bindwound_timer.Disable(); bindwound_target = 0; - trade = new Trade(this); + trade = new Trade(this); // hp event - nexthpevent = -1; + nexthpevent = -1; nextinchpevent = -1; - hasTempPet = false; + hasTempPet = false; count_TempPet = 0; m_is_running = false; @@ -423,21 +424,28 @@ Mob::Mob(const char* in_name, nimbus_effect1 = 0; nimbus_effect2 = 0; nimbus_effect3 = 0; - m_targetable = true; + m_targetable = true; m_TargetRing = glm::vec3(); flymode = GravityBehavior::Water; + DistractedFromGrid = false; hate_list.SetHateOwner(this); m_AllowBeneficial = false; - m_DisableMelee = false; - for (int i = 0; i < EQEmu::skills::HIGHEST_SKILL + 2; i++) { SkillDmgTaken_Mod[i] = 0; } - for (int i = 0; i < HIGHEST_RESIST + 2; i++) { Vulnerability_Mod[i] = 0; } + m_DisableMelee = false; - emoteid = 0; - endur_upkeep = false; + for (int i = 0; i < EQEmu::skills::HIGHEST_SKILL + 2; i++) { + SkillDmgTaken_Mod[i] = 0; + } + + for (int i = 0; i < HIGHEST_RESIST + 2; i++) { + Vulnerability_Mod[i] = 0; + } + + emoteid = 0; + endur_upkeep = false; degenerating_effects = false; PrimaryAggro = false; AssistAggro = false; @@ -823,6 +831,7 @@ int32 Mob::CalcMaxMana() { int32 Mob::CalcMaxHP() { max_hp = (base_hp + itembonuses.HP + spellbonuses.HP); max_hp += max_hp * ((aabonuses.MaxHPChange + spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f); + return max_hp; } @@ -3183,8 +3192,7 @@ void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str) const char *Mob::GetCleanName() { - if(!strlen(clean_name)) - { + if (!strlen(clean_name)) { CleanMobName(GetName(), clean_name); } @@ -3396,24 +3404,33 @@ int Mob::GetHaste() return 100 + h; } -void Mob::SetTarget(Mob* mob) { - - if (target == mob) +void Mob::SetTarget(Mob *mob) +{ + if (target == mob) { return; + } target = mob; entity_list.UpdateHoTT(this); - - if(IsNPC()) + + if (IsNPC()) { parse->EventNPC(EVENT_TARGET_CHANGE, CastToNPC(), mob, "", 0); - else if (IsClient()) + } + else if (IsClient()) { parse->EventPlayer(EVENT_TARGET_CHANGE, CastToClient(), "", 0); - if(IsPet() && GetOwner() && GetOwner()->IsClient()) - GetOwner()->CastToClient()->UpdateXTargetType(MyPetTarget, mob); + if (this->CastToClient()->admin > 200) { + this->DisplayInfo(mob); + } + } - if (this->IsClient() && this->GetTarget() && this->CastToClient()->hp_other_update_throttle_timer.Check()) + if (IsPet() && GetOwner() && GetOwner()->IsClient()) { + GetOwner()->CastToClient()->UpdateXTargetType(MyPetTarget, mob); + } + + if (this->IsClient() && this->GetTarget() && this->CastToClient()->hp_other_update_throttle_timer.Check()) { this->GetTarget()->SendHPUpdate(false, true); + } } // For when we want a Ground Z at a location we are not at yet @@ -3563,11 +3580,10 @@ void Mob::SetEntityVariable(const char *id, const char *m_var) m_EntityVariables[id] = n_m_var; } -const char* Mob::GetEntityVariable(const char *id) +const char *Mob::GetEntityVariable(const char *id) { auto iter = m_EntityVariables.find(id); - if(iter != m_EntityVariables.end()) - { + if (iter != m_EntityVariables.end()) { return iter->second.c_str(); } return nullptr; @@ -5483,14 +5499,26 @@ bool Mob::HasSpellEffect(int effectid) return(0); } -int Mob::GetSpecialAbility(int ability) { - if(ability >= MAX_SPECIAL_ATTACK || ability < 0) { +int Mob::GetSpecialAbility(int ability) +{ + if (ability >= MAX_SPECIAL_ATTACK || ability < 0) { return 0; } return SpecialAbilities[ability].level; } +bool Mob::HasSpecialAbilities() +{ + for (int i = 0; i < MAX_SPECIAL_ATTACK; ++i) { + if (GetSpecialAbility(i)) { + return true; + } + } + + return false; +} + int Mob::GetSpecialAbilityParam(int ability, int param) { if(param >= MAX_SPECIAL_ATTACK_PARAMS || param < 0 || ability >= MAX_SPECIAL_ATTACK || ability < 0) { return 0; diff --git a/zone/mob.h b/zone/mob.h index ffa471597..0fee32be4 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -165,6 +165,8 @@ public: inline virtual bool IsMob() const { return true; } inline virtual bool InZone() const { return true; } + void DisplayInfo(Mob *mob); + //Somewhat sorted: needs documenting! //Attack @@ -481,6 +483,11 @@ public: inline virtual int32 GetINT() const { return INT + itembonuses.INT + spellbonuses.INT; } inline virtual int32 GetWIS() const { return WIS + itembonuses.WIS + spellbonuses.WIS; } inline virtual int32 GetCHA() const { return CHA + itembonuses.CHA + spellbonuses.CHA; } + inline virtual int32 GetHeroicMR() const { return 0; } + inline virtual int32 GetHeroicFR() const { return 0; } + inline virtual int32 GetHeroicDR() const { return 0; } + inline virtual int32 GetHeroicPR() const { return 0; } + inline virtual int32 GetHeroicCR() const { return 0; } inline virtual int32 GetMR() const { return MR + itembonuses.MR + spellbonuses.MR; } inline virtual int32 GetFR() const { return FR + itembonuses.FR + spellbonuses.FR; } inline virtual int32 GetDR() const { return DR + itembonuses.DR + spellbonuses.DR; } @@ -494,6 +501,13 @@ public: inline StatBonuses* GetItemBonusesPtr() { return &itembonuses; } inline StatBonuses* GetSpellBonusesPtr() { return &spellbonuses; } inline StatBonuses* GetAABonusesPtr() { return &aabonuses; } + inline virtual int32 GetHeroicSTR() const { return 0; } + inline virtual int32 GetHeroicSTA() const { return 0; } + inline virtual int32 GetHeroicDEX() const { return 0; } + inline virtual int32 GetHeroicAGI() const { return 0; } + inline virtual int32 GetHeroicINT() const { return 0; } + inline virtual int32 GetHeroicWIS() const { return 0; } + inline virtual int32 GetHeroicCHA() const { return 0; } inline virtual int32 GetMaxSTR() const { return GetSTR(); } inline virtual int32 GetMaxSTA() const { return GetSTA(); } inline virtual int32 GetMaxDEX() const { return GetDEX(); } @@ -513,6 +527,7 @@ public: inline int32 GetMaxMana() const { return max_mana; } inline int32 GetMana() const { return current_mana; } virtual int32 GetEndurance() const { return 0; } + virtual int32 GetMaxEndurance() const { return 0; } virtual void SetEndurance(int32 newEnd) { return; } int32 GetItemHPBonuses(); int32 GetSpellHPBonuses(); @@ -1042,6 +1057,7 @@ public: inline bool Sanctuary() const { return spellbonuses.Sanctuary; } bool HasNPCSpecialAtk(const char* parse); + bool HasSpecialAbilities(); int GetSpecialAbility(int ability); int GetSpecialAbilityParam(int ability, int param); void SetSpecialAbility(int ability, int level); @@ -1140,6 +1156,10 @@ public: int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); + int32 GetHPRegen() const; + int32 GetManaRegen() const; + + // Bots HealRotation methods #ifdef BOTS bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); } @@ -1461,7 +1481,6 @@ protected: bool pAIControlled; bool roamer; - bool logging_enabled; int wandertype; int pausetype; diff --git a/zone/mob_info.cpp b/zone/mob_info.cpp new file mode 100644 index 000000000..21e7ed5bb --- /dev/null +++ b/zone/mob_info.cpp @@ -0,0 +1,831 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2018 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "client.h" +#include "mob.h" +#include "../common/races.h" +#include "../common/say_link.h" +#include "npc_scale_manager.h" + +std::string commify(const std::string &number) +{ + std::string temp_string; + + auto string_length = static_cast(number.length()); + + int i = 0; + for (i = string_length - 3; i >= 0; i -= 3) { + if (i > 0) { + temp_string = "," + number.substr(static_cast(i), 3) + temp_string; + } + else { + temp_string = number.substr(static_cast(i), 3) + temp_string; + } + } + + if (i < 0) { + temp_string = number.substr(0, static_cast(3 + i)) + temp_string; + } + + return temp_string; +} + +inline std::string GetMobAttributeByString(Mob *mob, const std::string &attribute) +{ + std::string entity_variable = "modify_stat_" + attribute; + std::string scaling_modified; + if (mob->GetEntityVariable(entity_variable.c_str())) { + scaling_modified = " *"; + } + + if (attribute == "ac") { + if (mob->GetEntityVariable(std::string("modify_stat_max_hp").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string(mob->GetAC())) + scaling_modified; + } + + if (attribute == "atk") { + return std::to_string(mob->GetATK()) + scaling_modified; + } + + if (attribute == "end") { + int endurance = 0; + if (mob->IsClient()) { + endurance = mob->CastToClient()->GetEndurance(); + } + + return commify(std::to_string(endurance)); + } + + if (attribute == "hp") { + return commify(std::to_string(mob->GetHP())); + } + + if (attribute == "hp_min_max") { + if (mob->GetEntityVariable(std::string("modify_stat_max_hp").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string(mob->GetHP())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxHP())) + " (" + + std::to_string((int) mob->GetHPRatio()) + "%)"; + } + + if (attribute == "mana") { + return commify(std::to_string(mob->GetMana())); + } + + if (attribute == "mp_min_max") { + return commify(std::to_string(mob->GetMana())) + " / " + commify(std::to_string(mob->GetMaxMana())) + " (" + + std::to_string((int) mob->GetManaPercent()) + "%)"; + } + + if (attribute == "end_min_max") { + return commify(std::to_string(mob->GetEndurance())) + " / " + commify(std::to_string(mob->GetMaxEndurance())) + " (" + + std::to_string((int)mob->GetEndurancePercent()) + "%)"; + } + + if (attribute == "str") { + return commify(std::to_string(mob->GetSTR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxSTR())) + " +" + + commify(std::to_string(mob->GetHeroicSTR())); + } + + if (attribute == "sta") { + return commify(std::to_string(mob->GetSTA())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxSTA())) + " +" + + commify(std::to_string(mob->GetHeroicSTA())); + } + + if (attribute == "dex") { + return commify(std::to_string(mob->GetDEX())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxDEX())) + " +" + + commify(std::to_string(mob->GetHeroicDEX())); + } + + if (attribute == "agi") { + return commify(std::to_string(mob->GetAGI())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxAGI())) + " +" + + commify(std::to_string(mob->GetHeroicAGI())); + } + + if (attribute == "int") { + return commify(std::to_string(mob->GetINT())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxINT())) + " +" + + commify(std::to_string(mob->GetHeroicINT())); + } + + if (attribute == "wis") { + return commify(std::to_string(mob->GetWIS())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxWIS())) + " +" + + commify(std::to_string(mob->GetHeroicWIS())); + } + + if (attribute == "cha") { + return commify(std::to_string(mob->GetCHA())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxCHA())) + " +" + + commify(std::to_string(mob->GetHeroicCHA())); + } + + if (attribute == "mr") { + return commify(std::to_string(mob->GetMR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxMR())) + " +" + + commify(std::to_string(mob->GetHeroicMR())); + } + + if (attribute == "cr") { + return commify(std::to_string(mob->GetCR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxCR())) + " +" + + commify(std::to_string(mob->GetHeroicCR())); + } + + if (attribute == "fr") { + return commify(std::to_string(mob->GetFR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxFR())) + " +" + + commify(std::to_string(mob->GetHeroicFR())); + } + + if (attribute == "pr") { + return commify(std::to_string(mob->GetPR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxPR())) + " +" + + commify(std::to_string(mob->GetHeroicPR())); + } + + if (attribute == "dr") { + return commify(std::to_string(mob->GetDR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxDR())) + " +" + + commify(std::to_string(mob->GetHeroicDR())); + } + + if (attribute == "cr") { + return commify(std::to_string(mob->GetCR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxCR())) + " +" + + commify(std::to_string(mob->GetHeroicCR())); + } + + if (attribute == "pr") { + return commify(std::to_string(mob->GetPR())) + scaling_modified + " / " + + commify(std::to_string(mob->GetMaxPR())) + " +" + + commify(std::to_string(mob->GetHeroicPR())); + } + + if (attribute == "cor") { + return std::to_string(mob->GetCorrup()); + } + + if (attribute == "phy") { + return std::to_string(mob->GetPhR()); + } + + if (attribute == "name") { + return mob->GetCleanName(); + } + + if (attribute == "surname") { + std::string last_name = mob->GetLastName(); + return (last_name.length() > 0 ? mob->GetLastName() : " "); + } + + if (attribute == "race") { + return GetRaceIDName(mob->GetRace()); + } + + if (attribute == "class") { + return GetClassIDName(mob->GetClass(), 0); + } + + if (attribute == "level") { + return std::to_string(mob->GetLevel()); + } + + if (attribute == "flymode") { + return std::to_string(mob->GetFlyMode()); + } + + if (attribute == "maxbuffslots") { + return std::to_string(mob->GetMaxBuffSlots()); + } + + if (attribute == "curbuffslots") { + return std::to_string(mob->GetCurrentBuffSlots()); + } + + if (attribute == "tohit") { + return std::to_string(mob->compute_tohit(EQEmu::skills::SkillHandtoHand)); + } + + if (attribute == "total_to_hit") { + return std::to_string(mob->GetTotalToHit(EQEmu::skills::SkillHandtoHand, 0)); + } + + if (attribute == "defense") { + return std::to_string(mob->compute_defense()); + } + + if (attribute == "total_defense") { + return std::to_string(mob->GetTotalDefense()); + } + + if (attribute == "offense") { + return std::to_string(mob->offense(EQEmu::skills::SkillHandtoHand)); + } + + if (attribute == "mitigation_ac") { + return std::to_string(mob->GetMitigationAC()); + } + + if (mob->IsNPC()) { + NPC *npc = mob->CastToNPC(); + + if (attribute == "npcid") { + return std::to_string(npc->GetNPCTypeID()); + } + if (attribute == "texture") { + return std::to_string(npc->GetTexture()); + } + if (attribute == "bodytype") { + return std::to_string(npc->GetBodyType()); + } + if (attribute == "gender") { + return std::to_string(npc->GetGender()); + } + if (attribute == "size") { + return std::to_string((int)npc->GetSize()); + } + if (attribute == "runspeed") { + return std::to_string((int)npc->GetRunspeed()); + } + if (attribute == "walkspeed") { + return std::to_string((int)npc->GetWalkspeed()); + } + if (attribute == "spawngroup") { + return std::to_string(npc->GetSp2()); + } + if (attribute == "grid") { + return std::to_string(npc->GetGrid()); + } + if (attribute == "emote") { + return std::to_string(npc->GetEmoteID()); + } + if (attribute == "see_invis") { + return std::to_string(npc->SeeInvisible()); + } + if (attribute == "see_invis_undead") { + return std::to_string(npc->SeeInvisibleUndead()); + } + if (attribute == "faction") { + return std::to_string(npc->GetNPCFactionID()); + } + if (attribute == "loottable") { + return std::to_string(npc->GetLoottableID()); + } + if (attribute == "prim_skill") { + return std::to_string(npc->GetPrimSkill()); + } + if (attribute == "sec_skill") { + return std::to_string(npc->GetSecSkill()); + } + if (attribute == "melee_texture_1") { + return std::to_string(npc->GetMeleeTexture1()); + } + if (attribute == "melee_texture_2") { + return std::to_string(npc->GetMeleeTexture2()); + } + if (attribute == "aggrorange") { + return std::to_string((int)npc->GetAggroRange()); + } + if (attribute == "assistrange") { + return std::to_string((int)npc->GetAssistRange()); + } + if (attribute == "findable") { + return std::to_string(npc->IsFindable()); + } + if (attribute == "trackable") { + return std::to_string(npc->IsTrackable()); + } + if (attribute == "spells_id") { + return std::to_string(npc->GetNPCSpellsID()); + } + if (attribute == "roambox_min_x") { + return std::to_string((int)npc->GetRoamboxMinX()); + } + if (attribute == "roambox_max_x") { + return std::to_string((int)npc->GetRoamboxMaxX()); + } + if (attribute == "roambox_min_y") { + return std::to_string((int)npc->GetRoamboxMinY()); + } + if (attribute == "roambox_max_y") { + return std::to_string((int)npc->GetRoamboxMaxY()); + } + if (attribute == "roambox_min_delay") { + return std::to_string((int)npc->GetRoamboxMinDelay()); + } + if (attribute == "roambox_delay") { + return std::to_string((int)npc->GetRoamboxDelay()); + } + if (attribute == "roambox_distance") { + return std::to_string((int)npc->GetRoamboxDistance()); + } + if (attribute == "proximity_min_x") { + return std::to_string((int)npc->GetProximityMinX()); + } + if (attribute == "proximity_max_x") { + return std::to_string((int)npc->GetProximityMaxX()); + } + if (attribute == "proximity_min_y") { + return std::to_string((int)npc->GetProximityMinY()); + } + if (attribute == "proximity_max_y") { + return std::to_string((int)npc->GetProximityMaxY()); + } + if (attribute == "proximity_min_z") { + return std::to_string((int)npc->GetProximityMinZ()); + } + if (attribute == "proximity_max_z") { + return std::to_string((int)npc->GetProximityMaxZ()); + } + if (attribute == "accuracy") { + return std::to_string((int)npc->GetAccuracyRating()) + scaling_modified; + } + if (attribute == "slow_mitigation") { + if (mob->GetEntityVariable(std::string("modify_stat_slow_mitigation").c_str())) { + scaling_modified = " *"; + } + + return std::to_string((int)npc->GetSlowMitigation()) + scaling_modified; + } + if (attribute == "min_hit") { + if (mob->GetEntityVariable(std::string("modify_stat_min_hit").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string((int)npc->GetMinDMG())) + scaling_modified; + } + if (attribute == "max_hit") { + if (mob->GetEntityVariable(std::string("modify_stat_max_hit").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string((int)npc->GetMaxDMG())) + scaling_modified; + } + if (attribute == "hp_regen") { + if (mob->GetEntityVariable(std::string("modify_stat_hp_regen").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string((int)npc->GetHPRegen())) + scaling_modified; + } + if (attribute == "attack_delay") { + if (mob->GetEntityVariable(std::string("modify_stat_attack_delay").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string(npc->GetAttackDelay())) + scaling_modified; + } + if (attribute == "spell_scale") { + if (mob->GetEntityVariable(std::string("modify_stat_spell_scale").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string((int)npc->GetSpellScale())) + scaling_modified; + } + if (attribute == "heal_scale") { + if (mob->GetEntityVariable(std::string("modify_stat_heal_scale").c_str())) { + scaling_modified = " *"; + } + + return commify(std::to_string((int)npc->GetHealScale())) + scaling_modified; + } + if (attribute == "avoidance") { + return commify(std::to_string((int)npc->GetAvoidanceRating())) + scaling_modified; + } + + npc->GetNPCEmote(npc->GetEmoteID(), 0); + } + + if (mob->IsClient()) { + Client *client = mob->CastToClient(); + + if (attribute == "shielding") { + return commify(std::to_string((int)client->GetShielding())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemShieldingCap))); + } + if (attribute == "spell_shielding") { + return commify(std::to_string((int)client->GetSpellShield())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemSpellShieldingCap))); + } + if (attribute == "dot_shielding") { + return commify(std::to_string((int)client->GetDoTShield())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemDoTShieldingCap))); + } + if (attribute == "stun_resist") { + return commify(std::to_string((int)client->GetStunResist())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemStunResistCap))); + } + if (attribute == "damage_shield") { + return commify(std::to_string((int)client->GetDS())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemDamageShieldCap))); + } + if (attribute == "avoidance") { + return commify(std::to_string((int) client->GetAvoidance())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemAvoidanceCap))); + } + if (attribute == "strikethrough") { + return commify(std::to_string((int) client->GetStrikeThrough())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemStrikethroughCap))); + } + if (attribute == "accuracy") { + return commify(std::to_string((int) client->GetAccuracy())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemAccuracyCap))); + } + if (attribute == "combat_effects") { + return commify(std::to_string((int) client->GetCombatEffects())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemCombatEffectsCap))); + } + if (attribute == "heal_amount") { + return commify(std::to_string((int) client->GetHealAmt())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemHealAmtCap))); + } + if (attribute == "spell_dmg") { + return commify(std::to_string((int) client->GetSpellDmg())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemSpellDmgCap))); + } + if (attribute == "clairvoyance") { + return commify(std::to_string((int) client->GetClair())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemClairvoyanceCap))); + } + if (attribute == "ds_mitigation") { + return commify(std::to_string((int) client->GetDSMit())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemDSMitigationCap))); + } + if (attribute == "hp_regen") { + return commify(std::to_string((int) client->GetHPRegen())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemHealthRegenCap))); + } + if (attribute == "mana_regen") { + return commify(std::to_string((int) client->GetManaRegen())) + " / " + + commify(std::to_string((int) RuleI(Character, ItemManaRegenCap))); + } + if (attribute == "end_regen") { + return commify(std::to_string((int) client->CalcEnduranceRegen())) + " / " + + commify(std::to_string((int) client->CalcEnduranceRegenCap())); + } + } + + if (attribute == "type") { + std::string entity_type = "Mob"; + + if (mob->IsCorpse()) { + entity_type = "Corpse"; + } + + if (mob->IsNPC()) { + entity_type = "NPC"; + } + + if (mob->IsClient()) { + entity_type = "Client"; + } + + return entity_type; + } + + return "null"; +} + +inline std::string WriteDisplayInfoSection( + Mob *mob, + const std::string §ion_name, + std::vector attributes_list, + int column_count = 3, + bool display_section_name = false +) +{ + std::string text; + + if (display_section_name) { + text += "" + section_name + "
"; + } + + text += ""; + + int index = 0; + bool first_row = true; + + for (const auto &attribute : attributes_list) { + if (index == 0) { + if (first_row) { + text += "\n"; + first_row = false; + } + else { + text += "\n"; + } + + } + + std::string attribute_name = attribute; + + find_replace(attribute_name, "_min_max", std::string("")); + + /** + * Translate attribute names with underscores + * "total_to_hit" = "Total To Hit" + */ + if (attribute_name.find('_') != std::string::npos) { + std::vector split_string = split(attribute_name, '_'); + std::string new_attribute_name; + for (std::string &string_value : split_string) { + new_attribute_name += ucfirst(string_value) + " "; + } + attribute_name = new_attribute_name; + } + + /** + * Attribute names less than 4 characters get capitalized + * "hp" = "HP" + */ + if (attribute_name.length() <= 3) { + attribute_name = str_toupper(attribute_name); + } + + /** + * Attribute names larger than 3 characters get capitalized first letter + * "avoidance" = "Avoidance" + */ + if (attribute_name.length() > 3) { + attribute_name = ucfirst(attribute_name); + } + + find_replace(attribute_name, "Proximity", std::string("")); + find_replace(attribute_name, "Roambox", std::string("")); + + std::string attribute_value = GetMobAttributeByString(mob, attribute); + + if (attribute_value.length() <= 0) { + continue; + } + + text += ""; + + if (index == column_count) { + index = 0; + continue; + } + + index++; + } + + text += "
" + attribute_name + "" + GetMobAttributeByString(mob, attribute) + "
"; + + return text; +} + +inline void NPCCommandsMenu(Client* client, NPC* npc) +{ + std::string menu_commands; + + if (npc->GetGrid() > 0) { + menu_commands += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#grid show", false, "Grid Points") + "] "; + } + + if (npc->GetEmoteID() > 0) { + std::string saylink = StringFormat("#emotesearch %u", npc->GetEmoteID()); + menu_commands += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Emotes") + "] "; + } + + if (npc->GetLoottableID() > 0) { + menu_commands += "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#npcloot show", false, "Loot") + "] "; + } + + if (menu_commands.length() > 0) { + std::string dev_menu = "[" + EQEmu::SayLinkEngine::GenerateQuestSaylink("#devtools", false, "DevTools") + "] ";; + client->Message(0, "| %s [Show Commands] %s", dev_menu.c_str(), menu_commands.c_str()); + } +} + +void Mob::DisplayInfo(Mob *mob) +{ + if (!mob) { + return; + } + + if (this->IsClient()) { + + std::string window_text = "*Drag window open vertically to see all
"; + + Client *client = this->CastToClient(); + + if (!client->IsDevToolsWindowEnabled()) { + return; + } + + std::vector info_attributes = { + "name", + "race", + "surname", + "class", + }; + window_text += WriteDisplayInfoSection(mob, "Info", info_attributes, 1, false); + + std::vector basic_attributes = { + "type", + "level", + "hp_min_max", + "ac", + "mp_min_max", + "atk", + "end_min_max", + }; + window_text += WriteDisplayInfoSection(mob, "Main", basic_attributes, 1, false); + + std::vector stat_attributes = { + "str", + "sta", + "agi", + "dex", + "wis", + "int", + "cha", + }; + window_text += WriteDisplayInfoSection(mob, "Statistics", stat_attributes, 1, false); + + std::vector resist_attributes = { + "pr", + "mr", + "dr", + "fr", + "cr", + "cor", + "phy", + }; + window_text += WriteDisplayInfoSection(mob, "Resists", resist_attributes, 1, false); + + std::vector calculations = { + "tohit", + "total_to_hit", + "defense", + "total_defense", + "offense", + "mitigation_ac", + }; + window_text += WriteDisplayInfoSection(mob, "Calculations", calculations, 1, true); + + if (mob->IsClient()) { + std::vector mods = { + "hp_regen", + "mana_regen", + "end_regen", + "heal_amount", + "spell_dmg", + "clairvoyance", + }; + window_text += WriteDisplayInfoSection(mob, "Mods", mods, 1, true); + + std::vector mod_defensive = { + "shielding", + "spell_shielding", + "dot_shielding", + "stun_resist", + "damage_shield", + "ds_mitigation", + "avoidance", + }; + + window_text += WriteDisplayInfoSection(mob, "Mod Defensive", mod_defensive, 1, true); + + std::vector mod_offensive = { + "strikethrough", + "accuracy", + "combat_effects", + }; + window_text += WriteDisplayInfoSection(mob, "Mod Offensive", mod_offensive, 1, true); + } + + if (mob->IsNPC()) { + NPC *npc = mob->CastToNPC(); + + std::vector npc_stats = { + "accuracy", + "slow_mitigation", + "min_hit", + "max_hit", + "hp_regen", + "attack_delay", + "spell_scale", + "heal_scale", + "avoidance", + }; + + window_text += WriteDisplayInfoSection(mob, "NPC Stats", npc_stats, 1, true); + + std::vector npc_attributes = { + "npcid", + "texture", + "bodytype", + "gender", + "size", + "runspeed", + "walkspeed", + "spawngroup", + "grid", + "emote", + "see_invis", + "see_invis_undead", + "faction", + "loottable", + "prim_skill", + "sec_skill", + "melee_texture_1", + "melee_texture_2", + "aggrorange", + "assistrange", + "findable", + "trackable", + "flymode", + "spells_id", + "curbuffslots", + "maxbuffslots", + }; + + window_text += WriteDisplayInfoSection(mob, "NPC Attributes", npc_attributes, 1, true); + + /** + * Print Roambox + */ + if (npc->GetRoamboxMaxX() != 0 && npc->GetRoamboxMinX() != 0) { + std::vector npc_roambox = { + "roambox_min_x", + "roambox_max_x", + "roambox_min_y", + "roambox_max_y", + "roambox_min_delay", + "roambox_delay", + "roambox_distance", + }; + + window_text += WriteDisplayInfoSection(mob, "Roambox", npc_roambox, 1, true); + } + + if (npc->proximity != nullptr) { + std::vector npc_proximity = { + "proximity_min_x", + "proximity_max_x", + "proximity_min_y", + "proximity_max_y", + "proximity_min_z", + "proximity_max_z", + }; + + window_text += WriteDisplayInfoSection(mob, "Proximity", npc_proximity, 1, true); + } + + int8 npc_type = npc_scale_manager->GetNPCScalingType(npc); + std::string npc_type_string = npc_scale_manager->GetNPCScalingTypeName(npc); + + client->Message( + 0, + "| # Target: %s Type: %i (%s)", + npc->GetCleanName(), + npc_type, + npc_type_string.c_str()); + + NPCCommandsMenu(client, npc); + } + + std::cout << "Window Length: " << window_text.length() << std::endl; + + if (client->GetDisplayMobInfoWindow()) { + client->SendFullPopup( + "GM: Entity Info", + window_text.c_str(), + EQEmu::popupresponse::MOB_INFO_DISMISS, + 0, + 100, + 0, + "Snooze", + "OK" + ); + } + } +} diff --git a/zone/net.cpp b/zone/net.cpp index 1dd1c9eb3..641c69248 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -42,7 +42,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/spdat.h" #include "../common/eqemu_logsys.h" - #include "zone_config.h" #include "masterentity.h" #include "worldserver.h" @@ -62,6 +61,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "embparser.h" #include "lua_parser.h" #include "questmgr.h" +#include "npc_scale_manager.h" #include "../common/event/event_loop.h" #include "../common/event/timer.h" @@ -104,6 +104,7 @@ npcDecayTimes_Struct npcCorpseDecayTimes[100]; TitleManager title_manager; QueryServ *QServ = 0; TaskManager *taskmanager = 0; +NpcScaleManager *npc_scale_manager; QuestParserCollection *parse = 0; EQEmuLogSys LogSys; const SPDat_Spell_Struct* spells; @@ -222,7 +223,6 @@ int main(int argc, char** argv) { worldserver.SetLauncherName("NONE"); } - Log(Logs::General, Logs::Zone_Server, "Connecting to MySQL..."); if (!database.Connect( Config->DatabaseHost.c_str(), @@ -255,6 +255,12 @@ int main(int argc, char** argv) { guild_mgr.SetDatabase(&database); GuildBanks = nullptr; + /** + * NPC Scale Manager + */ + npc_scale_manager = new NpcScaleManager; + npc_scale_manager->LoadScaleData(); + #ifdef _EQDEBUG _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif diff --git a/zone/npc.cpp b/zone/npc.cpp index dc96ff2ba..365fc7ff9 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -40,6 +40,7 @@ #include "zone.h" #include "quest_parser_collection.h" #include "water_map.h" +#include "npc_scale_manager.h" #include #include @@ -58,250 +59,237 @@ extern Zone* zone; extern volatile bool is_zone_loaded; extern EntityList entity_list; -NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, GravityBehavior iflymode, bool IsCorpse) -: Mob(d->name, - d->lastname, - d->max_hp, - d->max_hp, - d->gender, - d->race, - d->class_, - (bodyType)d->bodytype, - d->deity, - d->level, - d->npc_id, - d->size, - d->runspeed, - position, - d->light, // innate_light - d->texture, - d->helmtexture, - d->AC, - d->ATK, - d->STR, - d->STA, - d->DEX, - d->AGI, - d->INT, - d->WIS, - d->CHA, - d->haircolor, - d->beardcolor, - d->eyecolor1, - d->eyecolor2, - d->hairstyle, - d->luclinface, - d->beard, - d->drakkin_heritage, - d->drakkin_tattoo, - d->drakkin_details, - d->armor_tint, - 0, - d->see_invis, // pass see_invis/see_ivu flags to mob constructor - d->see_invis_undead, - d->see_hide, - d->see_improved_hide, - d->hp_regen, - d->mana_regen, - d->qglobal, - d->maxlevel, - d->scalerate, - d->armtexture, - d->bracertexture, - d->handtexture, - d->legtexture, - d->feettexture), - attacked_timer(CombatEventTimer_expire), - swarm_timer(100), - classattack_timer(1000), - knightattack_timer(1000), - assist_timer(AIassistcheck_delay), - qglobal_purge_timer(30000), - send_hp_update_timer(2000), - enraged_timer(1000), - taunt_timer(TauntReuseTime * 1000), - m_SpawnPoint(position), - m_GuardPoint(-1,-1,-1,0), - m_GuardPointSaved(0,0,0,0) +NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &position, GravityBehavior iflymode, bool IsCorpse) + : Mob( + npc_type_data->name, + npc_type_data->lastname, + npc_type_data->max_hp, + npc_type_data->max_hp, + npc_type_data->gender, + npc_type_data->race, + npc_type_data->class_, + (bodyType) npc_type_data->bodytype, + npc_type_data->deity, + npc_type_data->level, + npc_type_data->npc_id, + npc_type_data->size, + npc_type_data->runspeed, + position, + npc_type_data->light, // innate_light + npc_type_data->texture, + npc_type_data->helmtexture, + npc_type_data->AC, + npc_type_data->ATK, + npc_type_data->STR, + npc_type_data->STA, + npc_type_data->DEX, + npc_type_data->AGI, + npc_type_data->INT, + npc_type_data->WIS, + npc_type_data->CHA, + npc_type_data->haircolor, + npc_type_data->beardcolor, + npc_type_data->eyecolor1, + npc_type_data->eyecolor2, + npc_type_data->hairstyle, + npc_type_data->luclinface, + npc_type_data->beard, + npc_type_data->drakkin_heritage, + npc_type_data->drakkin_tattoo, + npc_type_data->drakkin_details, + npc_type_data->armor_tint, + 0, + npc_type_data->see_invis, // pass see_invis/see_ivu flags to mob constructor + npc_type_data->see_invis_undead, + npc_type_data->see_hide, + npc_type_data->see_improved_hide, + npc_type_data->hp_regen, + npc_type_data->mana_regen, + npc_type_data->qglobal, + npc_type_data->maxlevel, + npc_type_data->scalerate, + npc_type_data->armtexture, + npc_type_data->bracertexture, + npc_type_data->handtexture, + npc_type_data->legtexture, + npc_type_data->feettexture +), + attacked_timer(CombatEventTimer_expire), + swarm_timer(100), + classattack_timer(1000), + knightattack_timer(1000), + assist_timer(AIassistcheck_delay), + qglobal_purge_timer(30000), + send_hp_update_timer(2000), + enraged_timer(1000), + taunt_timer(TauntReuseTime * 1000), + m_SpawnPoint(position), + m_GuardPoint(-1, -1, -1, 0), + m_GuardPointSaved(0, 0, 0, 0) { //What is the point of this, since the names get mangled.. - Mob* mob = entity_list.GetMob(name); - if(mob != 0) + Mob *mob = entity_list.GetMob(name); + if (mob != nullptr) { entity_list.RemoveEntity(mob->GetID()); + } - int moblevel=GetLevel(); + int moblevel = GetLevel(); - NPCTypedata = d; + NPCTypedata = npc_type_data; NPCTypedata_ours = nullptr; - respawn2 = in_respawn; + respawn2 = in_respawn; + swarm_timer.Disable(); - if (size <= 0.0f) + if (size <= 0.0f) { size = GetRaceGenderDefaultHeight(race, gender); + } - taunting = false; - proximity = nullptr; - copper = 0; - silver = 0; - gold = 0; - platinum = 0; - max_dmg = d->max_dmg; - min_dmg = d->min_dmg; - attack_count = d->attack_count; - grid = 0; - wp_m = 0; - max_wp=0; - save_wp = 0; - spawn_group = 0; + taunting = false; + proximity = nullptr; + copper = 0; + silver = 0; + gold = 0; + platinum = 0; + max_dmg = npc_type_data->max_dmg; + min_dmg = npc_type_data->min_dmg; + attack_count = npc_type_data->attack_count; + grid = 0; + wp_m = 0; + max_wp = 0; + save_wp = 0; + spawn_group = 0; swarmInfoPtr = nullptr; - spellscale = d->spellscale; - healscale = d->healscale; - - logging_enabled = NPC_DEFAULT_LOGGING_ENABLED; - - pAggroRange = d->aggroradius; - pAssistRange = d->assistradius; - findable = d->findable; - trackable = d->trackable; - - MR = d->MR; - CR = d->CR; - DR = d->DR; - FR = d->FR; - PR = d->PR; - Corrup = d->Corrup; - PhR = d->PhR; - - STR = d->STR; - STA = d->STA; - AGI = d->AGI; - DEX = d->DEX; - INT = d->INT; - WIS = d->WIS; - CHA = d->CHA; - npc_mana = d->Mana; + spellscale = npc_type_data->spellscale; + healscale = npc_type_data->healscale; + pAggroRange = npc_type_data->aggroradius; + pAssistRange = npc_type_data->assistradius; + findable = npc_type_data->findable; + trackable = npc_type_data->trackable; + MR = npc_type_data->MR; + CR = npc_type_data->CR; + DR = npc_type_data->DR; + FR = npc_type_data->FR; + PR = npc_type_data->PR; + Corrup = npc_type_data->Corrup; + PhR = npc_type_data->PhR; + STR = npc_type_data->STR; + STA = npc_type_data->STA; + AGI = npc_type_data->AGI; + DEX = npc_type_data->DEX; + INT = npc_type_data->INT; + WIS = npc_type_data->WIS; + CHA = npc_type_data->CHA; + npc_mana = npc_type_data->Mana; //quick fix of ordering if they screwed it up in the DB - if(max_dmg < min_dmg) { + if (max_dmg < min_dmg) { int tmp = min_dmg; min_dmg = max_dmg; max_dmg = tmp; } // Max Level and Stat Scaling if maxlevel is set - if(maxlevel > level) - { + if (maxlevel > level) { LevelScale(); } - // Set Resists if they are 0 in the DB - CalcNPCResists(); - - // Set Mana and HP Regen Rates if they are 0 in the DB - CalcNPCRegen(); - - // Set Min and Max Damage if they are 0 in the DB - if(max_dmg == 0){ - CalcNPCDamage(); - } - - base_damage = round((max_dmg - min_dmg) / 1.9); - min_damage = min_dmg - round(base_damage / 10.0); - - accuracy_rating = d->accuracy_rating; - avoidance_rating = d->avoidance_rating; - ATK = d->ATK; + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + accuracy_rating = npc_type_data->accuracy_rating; + avoidance_rating = npc_type_data->avoidance_rating; + ATK = npc_type_data->ATK; // used for when switch back to charm - default_ac = d->AC; - default_min_dmg = min_dmg; - default_max_dmg = max_dmg; - default_attack_delay = d->attack_delay; - default_accuracy_rating = d->accuracy_rating; - default_avoidance_rating = d->avoidance_rating; - default_atk = d->ATK; + default_ac = npc_type_data->AC; + default_min_dmg = min_dmg; + default_max_dmg = max_dmg; + default_attack_delay = npc_type_data->attack_delay; + default_accuracy_rating = npc_type_data->accuracy_rating; + default_avoidance_rating = npc_type_data->avoidance_rating; + default_atk = npc_type_data->ATK; // used for when getting charmed, if 0, doesn't swap - charm_ac = d->charm_ac; - charm_min_dmg = d->charm_min_dmg; - charm_max_dmg = d->charm_max_dmg; - charm_attack_delay = d->charm_attack_delay; - charm_accuracy_rating = d->charm_accuracy_rating; - charm_avoidance_rating = d->charm_avoidance_rating; - charm_atk = d->charm_atk; + charm_ac = npc_type_data->charm_ac; + charm_min_dmg = npc_type_data->charm_min_dmg; + charm_max_dmg = npc_type_data->charm_max_dmg; + charm_attack_delay = npc_type_data->charm_attack_delay; + charm_accuracy_rating = npc_type_data->charm_accuracy_rating; + charm_avoidance_rating = npc_type_data->charm_avoidance_rating; + charm_atk = npc_type_data->charm_atk; CalcMaxMana(); SetMana(GetMaxMana()); - MerchantType = d->merchanttype; - merchant_open = GetClass() == MERCHANT; - adventure_template_id = d->adventure_template; - flymode = iflymode; - guard_anim = eaStanding; - roambox_distance = 0; - roambox_max_x = -2; - roambox_max_y = -2; - roambox_min_x = -2; - roambox_min_y = -2; + MerchantType = npc_type_data->merchanttype; + merchant_open = GetClass() == MERCHANT; + adventure_template_id = npc_type_data->adventure_template; + flymode = iflymode; + guard_anim = eaStanding; + roambox_distance = 0; + roambox_max_x = -2; + roambox_max_y = -2; + roambox_min_x = -2; + roambox_min_y = -2; roambox_destination_x = -2; roambox_destination_y = -2; - roambox_min_delay = 1000; - roambox_delay = 1000; - p_depop = false; - loottable_id = d->loottable_id; - skip_global_loot = d->skip_global_loot; - rare_spawn = d->rare_spawn; + roambox_min_delay = 1000; + roambox_delay = 1000; + p_depop = false; + loottable_id = npc_type_data->loottable_id; + skip_global_loot = npc_type_data->skip_global_loot; + rare_spawn = npc_type_data->rare_spawn; + no_target_hotkey = npc_type_data->no_target_hotkey; + primary_faction = 0; - no_target_hotkey = d->no_target_hotkey; + SetNPCFactionID(npc_type_data->npc_faction_id); - primary_faction = 0; - SetNPCFactionID(d->npc_faction_id); - - npc_spells_id = 0; - HasAISpell = false; - HasAISpellEffects = false; + npc_spells_id = 0; + HasAISpell = false; + HasAISpellEffects = false; innate_proc_spell_id = 0; - if(GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs)) - { + if (GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs)) { LoadMercTypes(); LoadMercs(); } - SpellFocusDMG = 0; + SpellFocusDMG = 0; SpellFocusHeal = 0; pet_spell_id = 0; - delaytimer = false; - combat_event = false; - attack_speed = d->attack_speed; - attack_delay = d->attack_delay; - slow_mitigation = d->slow_mitigation; + delaytimer = false; + combat_event = false; + attack_speed = npc_type_data->attack_speed; + attack_delay = npc_type_data->attack_delay; + slow_mitigation = npc_type_data->slow_mitigation; EntityList::RemoveNumbers(name); entity_list.MakeNameUnique(name); - npc_aggro = d->npc_aggro; + npc_aggro = npc_type_data->npc_aggro; AI_Init(); AI_Start(); - d_melee_texture1 = d->d_melee_texture1; - d_melee_texture2 = d->d_melee_texture2; - herosforgemodel = d->herosforgemodel; + d_melee_texture1 = npc_type_data->d_melee_texture1; + d_melee_texture2 = npc_type_data->d_melee_texture2; + herosforgemodel = npc_type_data->herosforgemodel; - ammo_idfile = d->ammo_idfile; + ammo_idfile = npc_type_data->ammo_idfile; memset(equipment, 0, sizeof(equipment)); - prim_melee_type = d->prim_melee_type; - sec_melee_type = d->sec_melee_type; - ranged_type = d->ranged_type; + prim_melee_type = npc_type_data->prim_melee_type; + sec_melee_type = npc_type_data->sec_melee_type; + ranged_type = npc_type_data->ranged_type; // If Melee Textures are not set, set attack type to Hand to Hand as default - if(!d_melee_texture1) + if (!d_melee_texture1) { prim_melee_type = 28; - if(!d_melee_texture2) + } + if (!d_melee_texture2) { sec_melee_type = 28; + } //give NPCs skill values... int r; @@ -313,106 +301,139 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, Gravit // the theory of needing to be able to set skills for each mob separately if (moblevel > 50) { skills[EQEmu::skills::SkillDoubleAttack] = 250; - skills[EQEmu::skills::SkillDualWield] = 250; - } else if (moblevel > 3) { + skills[EQEmu::skills::SkillDualWield] = 250; + } + else if (moblevel > 3) { skills[EQEmu::skills::SkillDoubleAttack] = moblevel * 5; - skills[EQEmu::skills::SkillDualWield] = skills[EQEmu::skills::SkillDoubleAttack]; - } else { + skills[EQEmu::skills::SkillDualWield] = skills[EQEmu::skills::SkillDoubleAttack]; + } + else { skills[EQEmu::skills::SkillDoubleAttack] = moblevel * 5; } - if(d->trap_template > 0) - { - std::map >::iterator trap_ent_iter; - std::list trap_list; + ldon_trapped = false; + ldon_trap_type = 0; + ldon_spell_id = 0; + ldon_locked = false; + ldon_locked_skill = 0; + ldon_trap_detected = false; - trap_ent_iter = zone->ldon_trap_entry_list.find(d->trap_template); - if(trap_ent_iter != zone->ldon_trap_entry_list.end()) - { + if (npc_type_data->trap_template > 0) { + std::map >::iterator trap_ent_iter; + std::list trap_list; + + trap_ent_iter = zone->ldon_trap_entry_list.find(npc_type_data->trap_template); + if (trap_ent_iter != zone->ldon_trap_entry_list.end()) { trap_list = trap_ent_iter->second; - if(trap_list.size() > 0) - { + if (trap_list.size() > 0) { auto trap_list_iter = trap_list.begin(); std::advance(trap_list_iter, zone->random.Int(0, trap_list.size() - 1)); - LDoNTrapTemplate* tt = (*trap_list_iter); - if(tt) - { - if((uint8)tt->spell_id > 0) - { - ldon_trapped = true; - ldon_spell_id = tt->spell_id; + LDoNTrapTemplate *trap_template = (*trap_list_iter); + if (trap_template) { + if ((uint8) trap_template->spell_id > 0) { + ldon_trapped = true; + ldon_spell_id = trap_template->spell_id; } - else - { - ldon_trapped = false; + else { + ldon_trapped = false; ldon_spell_id = 0; } - ldon_trap_type = (uint8)tt->type; - if(tt->locked > 0) - { - ldon_locked = true; - ldon_locked_skill = tt->skill; + ldon_trap_type = (uint8) trap_template->type; + if (trap_template->locked > 0) { + ldon_locked = true; + ldon_locked_skill = trap_template->skill; } - else - { - ldon_locked = false; + else { + ldon_locked = false; ldon_locked_skill = 0; } + ldon_trap_detected = 0; } } - else - { - ldon_trapped = false; - ldon_trap_type = 0; - ldon_spell_id = 0; - ldon_locked = false; - ldon_locked_skill = 0; - ldon_trap_detected = 0; - } - } - else - { - ldon_trapped = false; - ldon_trap_type = 0; - ldon_spell_id = 0; - ldon_locked = false; - ldon_locked_skill = 0; - ldon_trap_detected = 0; } } - else - { - ldon_trapped = false; - ldon_trap_type = 0; - ldon_spell_id = 0; - ldon_locked = false; - ldon_locked_skill = 0; - ldon_trap_detected = 0; - } + reface_timer = new Timer(15000); reface_timer->Disable(); + qGlobals = nullptr; - SetEmoteID(d->emoteid); + + SetEmoteID(static_cast(npc_type_data->emoteid)); InitializeBuffSlots(); CalcBonuses(); - raid_target = d->raid_target; - ignore_despawn = d->ignore_despawn; - m_targetable = !d->untargetable; - AISpellVar.fail_recast = RuleI(Spells, AI_SpellCastFinishedFailRecast); - AISpellVar.engaged_no_sp_recast_min = RuleI(Spells, AI_EngagedNoSpellMinRecast); - AISpellVar.engaged_no_sp_recast_max = RuleI(Spells, AI_EngagedNoSpellMaxRecast); - AISpellVar.engaged_beneficial_self_chance = RuleI(Spells, AI_EngagedBeneficialSelfChance); - AISpellVar.engaged_beneficial_other_chance = RuleI(Spells, AI_EngagedBeneficialOtherChance); - AISpellVar.engaged_detrimental_chance = RuleI(Spells, AI_EngagedDetrimentalChance); - AISpellVar.pursue_no_sp_recast_min = RuleI(Spells, AI_PursueNoSpellMinRecast); - AISpellVar.pursue_no_sp_recast_max = RuleI(Spells, AI_PursueNoSpellMaxRecast); - AISpellVar.pursue_detrimental_chance = RuleI(Spells, AI_PursueDetrimentalChance); - AISpellVar.idle_no_sp_recast_min = RuleI(Spells, AI_IdleNoSpellMinRecast); - AISpellVar.idle_no_sp_recast_max = RuleI(Spells, AI_IdleNoSpellMaxRecast); - AISpellVar.idle_beneficial_chance = RuleI(Spells, AI_IdleBeneficialChance); + raid_target = npc_type_data->raid_target; + ignore_despawn = npc_type_data->ignore_despawn; + m_targetable = !npc_type_data->untargetable; + + npc_scale_manager->ScaleNPC(this); + + SetMana(GetMaxMana()); + + AISpellVar.fail_recast = static_cast(RuleI(Spells, AI_SpellCastFinishedFailRecast)); + AISpellVar.engaged_no_sp_recast_min = static_cast(RuleI(Spells, AI_EngagedNoSpellMinRecast)); + AISpellVar.engaged_no_sp_recast_max = static_cast(RuleI(Spells, AI_EngagedNoSpellMaxRecast)); + AISpellVar.engaged_beneficial_self_chance = static_cast (RuleI(Spells, AI_EngagedBeneficialSelfChance)); + AISpellVar.engaged_beneficial_other_chance = static_cast (RuleI(Spells, AI_EngagedBeneficialOtherChance)); + AISpellVar.engaged_detrimental_chance = static_cast (RuleI(Spells, AI_EngagedDetrimentalChance)); + AISpellVar.pursue_no_sp_recast_min = static_cast(RuleI(Spells, AI_PursueNoSpellMinRecast)); + AISpellVar.pursue_no_sp_recast_max = static_cast(RuleI(Spells, AI_PursueNoSpellMaxRecast)); + AISpellVar.pursue_detrimental_chance = static_cast (RuleI(Spells, AI_PursueDetrimentalChance)); + AISpellVar.idle_no_sp_recast_min = static_cast(RuleI(Spells, AI_IdleNoSpellMinRecast)); + AISpellVar.idle_no_sp_recast_max = static_cast(RuleI(Spells, AI_IdleNoSpellMaxRecast)); + AISpellVar.idle_beneficial_chance = static_cast (RuleI(Spells, AI_IdleBeneficialChance)); +} + +float NPC::GetRoamboxMaxX() const +{ + return roambox_max_x; +} + +float NPC::GetRoamboxMaxY() const +{ + return roambox_max_y; +} + +float NPC::GetRoamboxMinX() const +{ + return roambox_min_x; +} + +float NPC::GetRoamboxMinY() const +{ + return roambox_min_y; +} + +float NPC::GetRoamboxDistance() const +{ + return roambox_distance; +} + +float NPC::GetRoamboxDestinationX() const +{ + return roambox_destination_x; +} + +float NPC::GetRoamboxDestinationY() const +{ + return roambox_destination_y; +} + +float NPC::GetRoamboxDestinationZ() const +{ + return roambox_destination_z; +} + +uint32 NPC::GetRoamboxDelay() const +{ + return roambox_delay; +} + +uint32 NPC::GetRoamboxMinDelay() const +{ + return roambox_min_delay; } NPC::~NPC() @@ -583,10 +604,10 @@ void NPC::ClearItemList() { void NPC::QueryLoot(Client* to) { - to->Message(0, "Coin: %ip %ig %is %ic", platinum, gold, silver, copper); + to->Message(0, "| # Current Loot (%s) LootTableID: %i", GetName(), GetLoottableID()); - int x = 0; - for (auto cur = itemlist.begin(); cur != itemlist.end(); ++cur, ++x) { + int item_count = 0; + for (auto cur = itemlist.begin(); cur != itemlist.end(); ++cur, ++item_count) { if (!(*cur)) { Log(Logs::General, Logs::Error, "NPC::QueryLoot() - ItemList error, null item"); continue; @@ -600,10 +621,18 @@ void NPC::QueryLoot(Client* to) linker.SetLinkType(EQEmu::saylink::SayLinkLootItem); linker.SetLootData(*cur); - to->Message(0, "%s, ID: %u, Level: (min: %u, max: %u)", linker.GenerateLink().c_str(), (*cur)->item_id, (*cur)->min_level, (*cur)->max_level); + to->Message( + 0, + "| -- Item %i: %s ID: %u min_level: %u max_level: %u", + item_count, + linker.GenerateLink().c_str(), + (*cur)->item_id, + (*cur)->min_level, + (*cur)->max_level + ); } - to->Message(0, "%i items on %s.", x, GetName()); + to->Message(0, "| %i Platinum %i Gold %i Silver %i Copper", platinum, gold, silver, copper); } void NPC::AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_platinum) { @@ -628,10 +657,11 @@ void NPC::AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_ platinum = 0; } -void NPC::AddCash() { - copper = zone->random.Int(1, 100); - silver = zone->random.Int(1, 50); - gold = zone->random.Int(1, 10); +void NPC::AddCash() +{ + copper = zone->random.Int(1, 100); + silver = zone->random.Int(1, 50); + gold = zone->random.Int(1, 10); platinum = zone->random.Int(1, 5); } @@ -933,7 +963,8 @@ bool NPC::DatabaseCastAccepted(int spell_id) { return false; } -bool NPC::SpawnZoneController(){ +bool NPC::SpawnZoneController() +{ if (!RuleB(Zone, UseZoneController)) return false; @@ -942,23 +973,23 @@ bool NPC::SpawnZoneController(){ memset(npc_type, 0, sizeof(NPCType)); strncpy(npc_type->name, "zone_controller", 60); - npc_type->cur_hp = 2000000000; - npc_type->max_hp = 2000000000; - npc_type->hp_regen = 100000000; - npc_type->race = 240; - npc_type->size = .1; - npc_type->gender = 2; - npc_type->class_ = 1; - npc_type->deity = 1; - npc_type->level = 200; - npc_type->npc_id = ZONE_CONTROLLER_NPC_ID; - npc_type->loottable_id = 0; - npc_type->texture = 3; - npc_type->runspeed = 0; + npc_type->cur_hp = 2000000000; + npc_type->max_hp = 2000000000; + npc_type->hp_regen = 100000000; + npc_type->race = 240; + npc_type->size = .1; + npc_type->gender = 2; + npc_type->class_ = 1; + npc_type->deity = 1; + npc_type->level = 200; + npc_type->npc_id = ZONE_CONTROLLER_NPC_ID; + npc_type->loottable_id = 0; + npc_type->texture = 3; + npc_type->runspeed = 0; npc_type->d_melee_texture1 = 0; npc_type->d_melee_texture2 = 0; - npc_type->merchanttype = 0; - npc_type->bodytype = 11; + npc_type->merchanttype = 0; + npc_type->bodytype = 11; npc_type->skip_global_loot = true; if (RuleB(Zone, EnableZoneControllerGlobals)) { @@ -966,9 +997,9 @@ bool NPC::SpawnZoneController(){ } npc_type->prim_melee_type = 28; - npc_type->sec_melee_type = 28; + npc_type->sec_melee_type = 28; - npc_type->findable = 0; + npc_type->findable = 0; npc_type->trackable = 0; strcpy(npc_type->special_abilities, "12,1^13,1^14,1^15,1^16,1^17,1^19,1^22,1^24,1^25,1^28,1^31,1^35,1^39,1^42,1"); @@ -1004,7 +1035,7 @@ NPC * NPC::SpawnGridNodeNPC(std::string name, const glm::vec4 &position, uint32 npc_type->loottable_id = 0; npc_type->texture = 1; npc_type->light = 1; - npc_type->size = 3; + npc_type->size = 1; npc_type->runspeed = 0; npc_type->d_melee_texture1 = 1; npc_type->d_melee_texture2 = 1; @@ -1062,90 +1093,7 @@ NPC* NPC::SpawnNPC(const char* spawncommand, const glm::vec4& position, Client* sprintf(sep.arg[10], "0"); //Calc MaxHP if client neglected to enter it... if (!sep.IsNumber(4)) { - //Stolen from Client::GetMaxHP... - uint8 multiplier = 0; - int tmplevel = atoi(sep.arg[2]); - switch(atoi(sep.arg[5])) - { - case WARRIOR: - if (tmplevel < 20) - multiplier = 22; - else if (tmplevel < 30) - multiplier = 23; - else if (tmplevel < 40) - multiplier = 25; - else if (tmplevel < 53) - multiplier = 27; - else if (tmplevel < 57) - multiplier = 28; - else - multiplier = 30; - break; - - case DRUID: - case CLERIC: - case SHAMAN: - multiplier = 15; - break; - - case PALADIN: - case SHADOWKNIGHT: - if (tmplevel < 35) - multiplier = 21; - else if (tmplevel < 45) - multiplier = 22; - else if (tmplevel < 51) - multiplier = 23; - else if (tmplevel < 56) - multiplier = 24; - else if (tmplevel < 60) - multiplier = 25; - else - multiplier = 26; - break; - - case MONK: - case BARD: - case ROGUE: - //case BEASTLORD: - if (tmplevel < 51) - multiplier = 18; - else if (tmplevel < 58) - multiplier = 19; - else - multiplier = 20; - break; - - case RANGER: - if (tmplevel < 58) - multiplier = 20; - else - multiplier = 21; - break; - - case MAGICIAN: - case WIZARD: - case NECROMANCER: - case ENCHANTER: - multiplier = 12; - break; - - default: - if (tmplevel < 35) - multiplier = 21; - else if (tmplevel < 45) - multiplier = 22; - else if (tmplevel < 51) - multiplier = 23; - else if (tmplevel < 56) - multiplier = 24; - else if (tmplevel < 60) - multiplier = 25; - else - multiplier = 26; - break; - } - sprintf(sep.arg[4],"%i",5+multiplier*atoi(sep.arg[2])+multiplier*atoi(sep.arg[2])*75/300); + sep.arg[4] = 0; } // Autoselect NPC Gender @@ -1158,30 +1106,30 @@ NPC* NPC::SpawnNPC(const char* spawncommand, const glm::vec4& position, Client* memset(npc_type, 0, sizeof(NPCType)); strncpy(npc_type->name, sep.arg[0], 60); - npc_type->cur_hp = atoi(sep.arg[4]); - npc_type->max_hp = atoi(sep.arg[4]); - npc_type->race = atoi(sep.arg[1]); - npc_type->gender = atoi(sep.arg[5]); - npc_type->class_ = atoi(sep.arg[6]); - npc_type->deity = 1; - npc_type->level = atoi(sep.arg[2]); - npc_type->npc_id = 0; - npc_type->loottable_id = 0; - npc_type->texture = atoi(sep.arg[3]); - npc_type->light = 0; // spawncommand needs update - npc_type->runspeed = 1.25; + npc_type->cur_hp = atoi(sep.arg[4]); + npc_type->max_hp = atoi(sep.arg[4]); + npc_type->race = atoi(sep.arg[1]); + npc_type->gender = atoi(sep.arg[5]); + npc_type->class_ = atoi(sep.arg[6]); + npc_type->deity = 1; + npc_type->level = atoi(sep.arg[2]); + npc_type->npc_id = 0; + npc_type->loottable_id = 0; + npc_type->texture = atoi(sep.arg[3]); + npc_type->light = 0; // spawncommand needs update + npc_type->runspeed = 1.25; npc_type->d_melee_texture1 = atoi(sep.arg[7]); npc_type->d_melee_texture2 = atoi(sep.arg[8]); - npc_type->merchanttype = atoi(sep.arg[9]); - npc_type->bodytype = atoi(sep.arg[10]); + npc_type->merchanttype = atoi(sep.arg[9]); + npc_type->bodytype = atoi(sep.arg[10]); - npc_type->STR = 150; - npc_type->STA = 150; - npc_type->DEX = 150; - npc_type->AGI = 150; - npc_type->INT = 150; - npc_type->WIS = 150; - npc_type->CHA = 150; + npc_type->STR = 0; + npc_type->STA = 0; + npc_type->DEX = 0; + npc_type->AGI = 0; + npc_type->INT = 0; + npc_type->WIS = 0; + npc_type->CHA = 0; npc_type->attack_delay = 3000; @@ -2138,77 +2086,218 @@ void NPC::SetLevel(uint8 in_level, bool command) SendAppearancePacket(AT_WhoLevel, in_level); } -void NPC::ModifyNPCStat(const char *identifier, const char *newValue) +void NPC::ModifyNPCStat(const char *identifier, const char *new_value) { - std::string id = identifier; - std::string val = newValue; - for(int i = 0; i < id.length(); ++i) { - id[i] = std::tolower(id[i]); - } + std::string id = str_tolower(identifier); + std::string val = new_value; - if(id == "ac") { AC = atoi(val.c_str()); CalcAC(); return; } - else if(id == "str") { STR = atoi(val.c_str()); return; } - else if(id == "sta") { STA = atoi(val.c_str()); return; } - else if(id == "agi") { AGI = atoi(val.c_str()); CalcAC(); return; } - else if(id == "dex") { DEX = atoi(val.c_str()); return; } - else if(id == "wis") { WIS = atoi(val.c_str()); CalcMaxMana(); return; } - else if(id == "int" || id == "_int") { INT = atoi(val.c_str()); CalcMaxMana(); return; } - else if(id == "cha") { CHA = atoi(val.c_str()); return; } - else if(id == "max_hp") { base_hp = atoi(val.c_str()); CalcMaxHP(); if (cur_hp > max_hp) { cur_hp = max_hp; } return; } - else if(id == "max_mana") { npc_mana = atoi(val.c_str()); CalcMaxMana(); if (current_mana > max_mana){ current_mana = max_mana; } return; } - else if(id == "mr") { MR = atoi(val.c_str()); return; } - else if(id == "fr") { FR = atoi(val.c_str()); return; } - else if(id == "cr") { CR = atoi(val.c_str()); return; } - else if(id == "pr") { PR = atoi(val.c_str()); return; } - else if(id == "dr") { DR = atoi(val.c_str()); return; } - else if(id == "phr") { PhR = atoi(val.c_str()); return; } - else if(id == "runspeed") { - runspeed = (float)atof(val.c_str()); - base_runspeed = (int)((float)runspeed * 40.0f); + std::string variable_key = StringFormat("modify_stat_%s", id.c_str()); + SetEntityVariable(variable_key.c_str(), new_value); + + Log(Logs::Detail, Logs::NPCScaling, "NPC::ModifyNPCStat key: %s val: %s ", variable_key.c_str(), new_value); + + if (id == "ac") { + AC = atoi(val.c_str()); + CalcAC(); + return; + } + else if (id == "str") { + STR = atoi(val.c_str()); + return; + } + else if (id == "sta") { + STA = atoi(val.c_str()); + return; + } + else if (id == "agi") { + AGI = atoi(val.c_str()); + CalcAC(); + return; + } + else if (id == "dex") { + DEX = atoi(val.c_str()); + return; + } + else if (id == "wis") { + WIS = atoi(val.c_str()); + CalcMaxMana(); + return; + } + else if (id == "int" || id == "_int") { + INT = atoi(val.c_str()); + CalcMaxMana(); + return; + } + else if (id == "cha") { + CHA = atoi(val.c_str()); + return; + } + else if (id == "max_hp") { + base_hp = atoi(val.c_str()); + + CalcMaxHP(); + if (cur_hp > max_hp) { + cur_hp = max_hp; + } + + return; + } + else if (id == "max_mana") { + npc_mana = atoi(val.c_str()); + CalcMaxMana(); + if (current_mana > max_mana) { + current_mana = max_mana; + } + return; + } + else if (id == "mr") { + MR = atoi(val.c_str()); + return; + } + else if (id == "fr") { + FR = atoi(val.c_str()); + return; + } + else if (id == "cr") { + CR = atoi(val.c_str()); + return; + } + else if (id == "pr") { + PR = atoi(val.c_str()); + return; + } + else if (id == "dr") { + DR = atoi(val.c_str()); + return; + } + else if (id == "phr") { + PhR = atoi(val.c_str()); + return; + } + else if (id == "runspeed") { + runspeed = (float) atof(val.c_str()); + base_runspeed = (int) ((float) runspeed * 40.0f); base_walkspeed = base_runspeed * 100 / 265; - walkspeed = ((float)base_walkspeed) * 0.025f; + walkspeed = ((float) base_walkspeed) * 0.025f; base_fearspeed = base_runspeed * 100 / 127; - fearspeed = ((float)base_fearspeed) * 0.025f; - CalcBonuses(); return; - } - else if(id == "special_attacks") { NPCSpecialAttacks(val.c_str(), 0, 1); return; } - else if(id == "special_abilities") { ProcessSpecialAbilities(val.c_str()); return; } - else if(id == "attack_speed") { attack_speed = (float)atof(val.c_str()); CalcBonuses(); return; } - else if(id == "attack_delay") { /* TODO: fix DB */attack_delay = atoi(val.c_str()) * 100; CalcBonuses(); return; } - else if(id == "atk") { ATK = atoi(val.c_str()); return; } - else if(id == "accuracy") { accuracy_rating = atoi(val.c_str()); return; } - else if(id == "avoidance") { avoidance_rating = atoi(val.c_str()); return; } - else if(id == "trackable") { trackable = atoi(val.c_str()); return; } - else if(id == "min_hit") { - min_dmg = atoi(val.c_str()); - // TODO: fix DB - base_damage = round((max_dmg - min_dmg) / 1.9); - min_damage = min_dmg - round(base_damage / 10.0); + fearspeed = ((float) base_fearspeed) * 0.025f; + CalcBonuses(); return; } - else if(id == "max_hit") { - max_dmg = atoi(val.c_str()); - // TODO: fix DB - base_damage = round((max_dmg - min_dmg) / 1.9); - min_damage = min_dmg - round(base_damage / 10.0); + else if (id == "special_attacks") { + NPCSpecialAttacks(val.c_str(), 0, 1); + return; + } + else if (id == "special_abilities") { + ProcessSpecialAbilities(val.c_str()); + return; + } + else if (id == "attack_speed") { + attack_speed = (float) atof(val.c_str()); + CalcBonuses(); + return; + } + else if (id == "attack_delay") { + /* TODO: fix DB */ + attack_delay = atoi(val.c_str()) * 100; + CalcBonuses(); + return; + } + else if (id == "atk") { + ATK = atoi(val.c_str()); + return; + } + else if (id == "accuracy") { + accuracy_rating = atoi(val.c_str()); + return; + } + else if (id == "avoidance") { + avoidance_rating = atoi(val.c_str()); + return; + } + else if (id == "trackable") { + trackable = atoi(val.c_str()); + return; + } + else if (id == "min_hit") { + min_dmg = atoi(val.c_str()); + // TODO: fix DB + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + return; + } + else if (id == "max_hit") { + max_dmg = atoi(val.c_str()); + // TODO: fix DB + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + return; + } + else if (id == "attack_count") { + attack_count = atoi(val.c_str()); + return; + } + else if (id == "see_invis") { + see_invis = atoi(val.c_str()); + return; + } + else if (id == "see_invis_undead") { + see_invis_undead = atoi(val.c_str()); + return; + } + else if (id == "see_hide") { + see_hide = atoi(val.c_str()); + return; + } + else if (id == "see_improved_hide") { + see_improved_hide = atoi(val.c_str()); + return; + } + else if (id == "hp_regen") { + hp_regen = atoi(val.c_str()); + return; + } + else if (id == "mana_regen") { + mana_regen = atoi(val.c_str()); + return; + } + else if (id == "level") { + SetLevel(atoi(val.c_str())); + return; + } + else if (id == "aggro") { + pAggroRange = atof(val.c_str()); + return; + } + else if (id == "assist") { + pAssistRange = atof(val.c_str()); + return; + } + else if (id == "slow_mitigation") { + slow_mitigation = atoi(val.c_str()); + return; + } + else if (id == "loottable_id") { + loottable_id = atof(val.c_str()); + return; + } + else if (id == "healscale") { + healscale = atof(val.c_str()); + return; + } + else if (id == "spellscale") { + spellscale = atof(val.c_str()); + return; + } + else if (id == "npc_spells_id") { + AI_AddNPCSpells(atoi(val.c_str())); + return; + } + else if (id == "npc_spells_effects_id") { + AI_AddNPCSpellsEffects(atoi(val.c_str())); + CalcBonuses(); return; } - else if(id == "attack_count") { attack_count = atoi(val.c_str()); return; } - else if(id == "see_invis") { see_invis = atoi(val.c_str()); return; } - else if(id == "see_invis_undead") { see_invis_undead = atoi(val.c_str()); return; } - else if(id == "see_hide") { see_hide = atoi(val.c_str()); return; } - else if(id == "see_improved_hide") { see_improved_hide = atoi(val.c_str()); return; } - else if(id == "hp_regen") { hp_regen = atoi(val.c_str()); return; } - else if(id == "mana_regen") { mana_regen = atoi(val.c_str()); return; } - else if(id == "level") { SetLevel(atoi(val.c_str())); return; } - else if(id == "aggro") { pAggroRange = atof(val.c_str()); return; } - else if(id == "assist") { pAssistRange = atof(val.c_str()); return; } - else if(id == "slow_mitigation") { slow_mitigation = atoi(val.c_str()); return; } - else if(id == "loottable_id") { loottable_id = atof(val.c_str()); return; } - else if(id == "healscale") { healscale = atof(val.c_str()); return; } - else if(id == "spellscale") { spellscale = atof(val.c_str()); return; } - else if(id == "npc_spells_id") { AI_AddNPCSpells(atoi(val.c_str())); return; } - else if(id == "npc_spells_effects_id") { AI_AddNPCSpellsEffects(atoi(val.c_str())); CalcBonuses(); return; } } void NPC::LevelScale() { @@ -2308,120 +2397,9 @@ void NPC::LevelScale() { return; } -void NPC::CalcNPCResists() { - - if (!MR) - MR = (GetLevel() * 11)/10; - if (!CR) - CR = (GetLevel() * 11)/10; - if (!DR) - DR = (GetLevel() * 11)/10; - if (!FR) - FR = (GetLevel() * 11)/10; - if (!PR) - PR = (GetLevel() * 11)/10; - if (!Corrup) - Corrup = 15; - if (!PhR) - PhR = 10; - return; -} - -void NPC::CalcNPCRegen() { - - // Fix for lazy db-updaters (regen values left at 0) - if (GetCasterClass() != 'N' && mana_regen == 0) - mana_regen = (GetLevel() / 10) + 4; - else if(mana_regen < 0) - mana_regen = 0; - else - mana_regen = mana_regen; - - // Gives low end monsters no regen if set to 0 in database. Should make low end monsters killable - // Might want to lower this to /5 rather than 10. - if(hp_regen == 0) - { - if(GetLevel() <= 6) - hp_regen = 1; - else if(GetLevel() > 6 && GetLevel() <= 10) - hp_regen = 2; - else if(GetLevel() > 10 && GetLevel() <= 15) - hp_regen = 3; - else if(GetLevel() > 15 && GetLevel() <= 20) - hp_regen = 5; - else if(GetLevel() > 20 && GetLevel() <= 30) - hp_regen = 7; - else if(GetLevel() > 30 && GetLevel() <= 35) - hp_regen = 9; - else if(GetLevel() > 35 && GetLevel() <= 40) - hp_regen = 12; - else if(GetLevel() > 40 && GetLevel() <= 45) - hp_regen = 18; - else if(GetLevel() > 45 && GetLevel() <= 50) - hp_regen = 21; - else - hp_regen = 30; - } else if(hp_regen < 0) { - hp_regen = 0; - } else - hp_regen = hp_regen; - - return; -} - -void NPC::CalcNPCDamage() { - - int AC_adjust=12; - - if (GetLevel() >= 66) { - if (min_dmg==0) - min_dmg = 220; - if (max_dmg==0) - max_dmg = ((((99000)*(GetLevel()-64))/400)*AC_adjust/10); - } - else if (GetLevel() >= 60 && GetLevel() <= 65){ - if(min_dmg==0) - min_dmg = (GetLevel()+(GetLevel()/3)); - if(max_dmg==0) - max_dmg = (GetLevel()*3)*AC_adjust/10; - } - else if (GetLevel() >= 51 && GetLevel() <= 59){ - if(min_dmg==0) - min_dmg = (GetLevel()+(GetLevel()/3)); - if(max_dmg==0) - max_dmg = (GetLevel()*3)*AC_adjust/10; - } - else if (GetLevel() >= 40 && GetLevel() <= 50) { - if (min_dmg==0) - min_dmg = GetLevel(); - if(max_dmg==0) - max_dmg = (GetLevel()*3)*AC_adjust/10; - } - else if (GetLevel() >= 28 && GetLevel() <= 39) { - if (min_dmg==0) - min_dmg = GetLevel() / 2; - if (max_dmg==0) - max_dmg = ((GetLevel()*2)+2)*AC_adjust/10; - } - else if (GetLevel() <= 27) { - if (min_dmg==0) - min_dmg=1; - if (max_dmg==0) - max_dmg = (GetLevel()*2)*AC_adjust/10; - } - - int32 clfact = GetClassLevelFactor(); - min_dmg = (min_dmg * clfact) / 220; - max_dmg = (max_dmg * clfact) / 220; - - return; -} - - uint32 NPC::GetSpawnPointID() const { - if(respawn2) - { + if (respawn2) { return respawn2->GetID(); } return 0; @@ -2438,7 +2416,6 @@ void NPC::NPCSlotTexture(uint8 slot, uint16 texture) else if (slot < 6) { // Reserved for texturing individual armor slots } - return; } uint32 NPC::GetSwarmOwner() @@ -2468,14 +2445,15 @@ void NPC::SetSwarmTarget(int target_id) return; } -int32 NPC::CalcMaxMana() { - if(npc_mana == 0) { +int32 NPC::CalcMaxMana() +{ + if (npc_mana == 0) { switch (GetCasterClass()) { case 'I': - max_mana = (((GetINT()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + max_mana = (((GetINT() / 2) + 1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; break; case 'W': - max_mana = (((GetWIS()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; + max_mana = (((GetWIS() / 2) + 1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana; break; case 'N': default: @@ -2487,7 +2465,8 @@ int32 NPC::CalcMaxMana() { } return max_mana; - } else { + } + else { switch (GetCasterClass()) { case 'I': max_mana = npc_mana + spellbonuses.Mana + itembonuses.Mana; @@ -2632,7 +2611,16 @@ FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) { return FACTION_INDIFFERENT; } } - return FACTION_INDIFFERENT; + + // I believe that the assumption is, barring no entry in npc_faction_entries + // that two npcs on like faction con ally to each other. This catches cases + // where an npc is on a faction but has no hits (hence no entry in + // npc_faction_entries). + + if (GetPrimaryFaction() == other_faction) + return FACTION_ALLY; + else + return FACTION_INDIFFERENT; } bool NPC::IsFactionListAlly(uint32 other_faction) { @@ -2821,3 +2809,43 @@ void NPC::ModifyStatsOnCharm(bool bRemoved) SetAttackTimer(); CalcAC(); } + +uint16 NPC::GetMeleeTexture1() const +{ + return d_melee_texture1; +} + +uint16 NPC::GetMeleeTexture2() const +{ + return d_melee_texture2; +} + +float NPC::GetProximityMinX() +{ + return proximity->min_x; +} + +float NPC::GetProximityMaxX() +{ + return proximity->max_x; +} + +float NPC::GetProximityMinY() +{ + return proximity->min_y; +} + +float NPC::GetProximityMaxY() +{ + return proximity->max_y; +} + +float NPC::GetProximityMinZ() +{ + return proximity->min_z; +} + +float NPC::GetProximityMaxZ() +{ + return proximity->max_z; +} diff --git a/zone/npc.h b/zone/npc.h index 6917b0ec4..0ad772754 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -106,7 +106,7 @@ public: static bool SpawnZoneController(); static int8 GetAILevel(bool iForceReRead = false); - NPC(const NPCType* data, Spawn2* respawn, const glm::vec4& position, GravityBehavior iflymode, bool IsCorpse = false); + NPC(const NPCType* npc_type_data, Spawn2* respawn, const glm::vec4& position, GravityBehavior iflymode, bool IsCorpse = false); virtual ~NPC(); @@ -142,9 +142,6 @@ public: virtual void AI_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); void LevelScale(); - void CalcNPCResists(); - void CalcNPCRegen(); - void CalcNPCDamage(); virtual void SetTarget(Mob* mob); virtual uint16 GetSkill(EQEmu::skills::SkillType skill_num) const { if (skill_num <= EQEmu::skills::HIGHEST_SKILL) { return skills[skill_num]; } return 0; } @@ -252,12 +249,23 @@ public: void SignalNPC(int _signal_id); - inline int32 GetNPCFactionID() const { return npc_faction_id; } - inline int32 GetPrimaryFaction() const { return primary_faction; } - int32 GetNPCHate(Mob* in_ent) {return hate_list.GetEntHateAmount(in_ent);} - bool IsOnHatelist(Mob*p) { return hate_list.IsEntOnHateList(p);} + inline int32 GetNPCFactionID() const + { return npc_faction_id; } - void SetNPCFactionID(int32 in) { npc_faction_id = in; database.GetFactionIdsForNPC(npc_faction_id, &faction_list, &primary_faction); } + inline int32 GetPrimaryFaction() const + { return primary_faction; } + + int32 GetNPCHate(Mob *in_ent) + { return hate_list.GetEntHateAmount(in_ent); } + + bool IsOnHatelist(Mob *p) + { return hate_list.IsEntOnHateList(p); } + + void SetNPCFactionID(int32 in) + { + npc_faction_id = in; + database.GetFactionIdsForNPC(npc_faction_id, &faction_list, &primary_faction); + } glm::vec4 m_SpawnPoint; @@ -309,6 +317,9 @@ public: inline bool IsGuarding() const { return(m_GuardPoint.w != 0); } void SaveGuardSpotCharm(); + uint16 GetMeleeTexture1() const; + uint16 GetMeleeTexture2() const; + void RestoreGuardSpotCharm(); void AI_SetRoambox( @@ -338,6 +349,13 @@ public: inline const uint32 GetNPCSpellsID() const { return npc_spells_id; } inline const uint32 GetNPCSpellsEffectsID() const { return npc_spells_effects_id; } + float GetProximityMinX(); + float GetProximityMaxX(); + float GetProximityMinY(); + float GetProximityMaxY(); + float GetProximityMinZ(); + float GetProximityMaxZ(); + ItemList itemlist; //kathgar - why is this public? Doing other things or I would check the code NPCProximity* proximity; @@ -354,7 +372,7 @@ public: void SetAvoidanceRating(int32 d) { avoidance_rating = d;} int32 GetRawAC() const { return AC; } - void ModifyNPCStat(const char *identifier, const char *newValue); + void ModifyNPCStat(const char *identifier, const char *new_value); virtual void SetLevel(uint8 in_level, bool command = false); bool IsLDoNTrapped() const { return (ldon_trapped); } @@ -432,6 +450,17 @@ public: bool IgnoreDespawn() { return ignore_despawn; } + float GetRoamboxMaxX() const; + float GetRoamboxMaxY() const; + float GetRoamboxMinX() const; + float GetRoamboxMinY() const; + float GetRoamboxDistance() const; + float GetRoamboxDestinationX() const; + float GetRoamboxDestinationY() const; + float GetRoamboxDestinationZ() const; + uint32 GetRoamboxDelay() const; + uint32 GetRoamboxMinDelay() const; + std::unique_ptr AIautocastspell_timer; virtual int GetStuckBehavior() const { return NPCTypedata_ours ? NPCTypedata_ours->stuck_behavior : NPCTypedata->stuck_behavior; } @@ -548,7 +577,8 @@ protected: uint32 equipment[EQEmu::invslot::EQUIPMENT_COUNT]; //this is an array of item IDs uint32 herosforgemodel; //this is the Hero Forge Armor Model (i.e 63 or 84 or 203) - uint16 d_melee_texture1; //this is an item Material value + uint16 d_melee_texture1; + //this is an item Material value uint16 d_melee_texture2; //this is an item Material value (offhand) const char* ammo_idfile; //this determines projectile graphic "IT###" (see item field 'idfile') uint8 prim_melee_type; //Sets the Primary Weapon attack message and animation diff --git a/zone/npc_scale_manager.cpp b/zone/npc_scale_manager.cpp new file mode 100644 index 000000000..130f8c601 --- /dev/null +++ b/zone/npc_scale_manager.cpp @@ -0,0 +1,624 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2018 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "npc_scale_manager.h" +#include "../common/string_util.h" + +/** + * @param npc + */ +void NpcScaleManager::ScaleNPC(NPC * npc) +{ + int8 npc_type = GetNPCScalingType(npc); + int npc_level = npc->GetLevel(); + bool is_auto_scaled = IsAutoScaled(npc); + + global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); + + if (!scale_data.level) { + Log(Logs::General, Logs::NPCScaling, "NPC: %s - scaling data not found for type: %i level: %i", + npc->GetCleanName(), + npc_type, + npc_level + ); + + return; + } + + if (npc->GetAC() == 0) { + npc->ModifyNPCStat("ac", std::to_string(scale_data.ac).c_str()); + } + if (npc->GetMaxHP() == 0) { + npc->ModifyNPCStat("max_hp", std::to_string(scale_data.hp).c_str()); + npc->Heal(); + } + if (npc->GetAccuracyRating() == 0) { + npc->ModifyNPCStat("accuracy", std::to_string(scale_data.accuracy).c_str()); + } + if (npc->GetSlowMitigation() == 0) { + npc->ModifyNPCStat("slow_mitigation", std::to_string(scale_data.slow_mitigation).c_str()); + } + if (npc->GetATK() == 0) { + npc->ModifyNPCStat("atk", std::to_string(scale_data.attack).c_str()); + } + if (npc->GetSTR() == 0) { + npc->ModifyNPCStat("str", std::to_string(scale_data.strength).c_str()); + } + if (npc->GetSTA() == 0) { + npc->ModifyNPCStat("sta", std::to_string(scale_data.stamina).c_str()); + } + if (npc->GetDEX() == 0) { + npc->ModifyNPCStat("dex", std::to_string(scale_data.dexterity).c_str()); + } + if (npc->GetAGI() == 0) { + npc->ModifyNPCStat("agi", std::to_string(scale_data.agility).c_str()); + } + if (npc->GetINT() == 0) { + npc->ModifyNPCStat("int", std::to_string(scale_data.intelligence).c_str()); + } + if (npc->GetWIS() == 0) { + npc->ModifyNPCStat("wis", std::to_string(scale_data.wisdom).c_str()); + } + if (npc->GetCHA() == 0) { + npc->ModifyNPCStat("cha", std::to_string(scale_data.charisma).c_str()); + } + if (npc->GetMR() == 0) { + npc->ModifyNPCStat("mr", std::to_string(scale_data.magic_resist).c_str()); + } + if (npc->GetCR() == 0) { + npc->ModifyNPCStat("cr", std::to_string(scale_data.cold_resist).c_str()); + } + if (npc->GetFR() == 0) { + npc->ModifyNPCStat("fr", std::to_string(scale_data.fire_resist).c_str()); + } + if (npc->GetPR() == 0) { + npc->ModifyNPCStat("pr", std::to_string(scale_data.poison_resist).c_str()); + } + if (npc->GetDR() == 0) { + npc->ModifyNPCStat("dr", std::to_string(scale_data.disease_resist).c_str()); + } + if (npc->GetCorrup() == 0) { + npc->ModifyNPCStat("cr", std::to_string(scale_data.corruption_resist).c_str()); + } + if (npc->GetPhR() == 0) { + npc->ModifyNPCStat("pr", std::to_string(scale_data.physical_resist).c_str()); + } + if (npc->GetMinDMG() == 0) { + int min_dmg = scale_data.min_dmg; + if (RuleB(Combat, UseNPCDamageClassLevelMods)) { + int32 class_level_damage_mod = GetClassLevelDamageMod(npc->GetLevel(), npc->GetClass()); + min_dmg = (min_dmg * class_level_damage_mod) / 220; + + Log(Logs::Moderate, + Logs::NPCScaling, + "ClassLevelDamageMod::min_dmg base: %i calc: %i", + scale_data.min_dmg, + min_dmg); + } + + npc->ModifyNPCStat("min_hit", std::to_string(min_dmg).c_str()); + } + if (npc->GetMaxDMG() == 0) { + int max_dmg = scale_data.max_dmg; + if (RuleB(Combat, UseNPCDamageClassLevelMods)) { + int32 class_level_damage_mod = GetClassLevelDamageMod(npc->GetLevel(), npc->GetClass()); + max_dmg = (scale_data.max_dmg * class_level_damage_mod) / 220; + + Log(Logs::Moderate, + Logs::NPCScaling, + "ClassLevelDamageMod::max_dmg base: %i calc: %i", + scale_data.max_dmg, + max_dmg + ); + } + + npc->ModifyNPCStat("max_hit", std::to_string(max_dmg).c_str()); + } + if (npc->GetHPRegen() == 0) { + npc->ModifyNPCStat("hp_regen", std::to_string(scale_data.hp_regen_rate).c_str()); + } + if (npc->GetAttackDelay() == 0) { + npc->ModifyNPCStat("attack_delay", std::to_string(scale_data.attack_delay).c_str()); + } + if (npc->GetSpellScale() == 0) { + npc->ModifyNPCStat("spell_scale", std::to_string(scale_data.spell_scale).c_str()); + } + if (npc->GetHealScale() == 0) { + npc->ModifyNPCStat("heal_scale", std::to_string(scale_data.heal_scale).c_str()); + } + if (!npc->HasSpecialAbilities() && is_auto_scaled) { + npc->ModifyNPCStat("special_abilities", scale_data.special_abilities.c_str()); + } + + if (LogSys.log_settings[Logs::NPCScaling].is_category_enabled == 1) { + std::string scale_log; + + for (const auto &stat : scaling_stats) { + std::string variable = StringFormat("modify_stat_%s", stat.c_str()); + if (npc->EntityVariableExists(variable.c_str())) { + scale_log += stat + ": " + npc->GetEntityVariable(variable.c_str()) + " "; + } + } + + Log(Logs::General, + Logs::NPCScaling, + "(%s) level: %i type: %i Auto: %s Setting: %s", + npc->GetCleanName(), + npc_level, + npc_type, + (is_auto_scaled ? "true" : "false"), + scale_log.c_str()); + } +} + +bool NpcScaleManager::LoadScaleData() +{ + auto results = database.QueryDatabase( + "SELECT " + "type," + "level," + "ac," + "hp," + "accuracy," + "slow_mitigation," + "attack," + "strength," + "stamina," + "dexterity," + "agility," + "intelligence," + "wisdom," + "charisma," + "magic_resist," + "cold_resist," + "fire_resist," + "poison_resist," + "disease_resist," + "corruption_resist," + "physical_resist," + "min_dmg," + "max_dmg," + "hp_regen_rate," + "attack_delay," + "spell_scale," + "heal_scale," + "special_abilities" + " FROM `npc_scale_global_base`" + ); + + for (auto row = results.begin(); row != results.end(); ++row) { + global_npc_scale scale_data; + + scale_data.type = atoi(row[0]); + scale_data.level = atoi(row[1]); + scale_data.ac = atoi(row[2]); + scale_data.hp = atoi(row[3]); + scale_data.accuracy = atoi(row[4]); + scale_data.slow_mitigation = atoi(row[5]); + scale_data.attack = atoi(row[6]); + scale_data.strength = atoi(row[7]); + scale_data.stamina = atoi(row[8]); + scale_data.dexterity = atoi(row[9]); + scale_data.agility = atoi(row[10]); + scale_data.intelligence = atoi(row[11]); + scale_data.wisdom = atoi(row[12]); + scale_data.charisma = atoi(row[13]); + scale_data.magic_resist = atoi(row[14]); + scale_data.cold_resist = atoi(row[15]); + scale_data.fire_resist = atoi(row[16]); + scale_data.poison_resist = atoi(row[17]); + scale_data.disease_resist = atoi(row[18]); + scale_data.corruption_resist = atoi(row[19]); + scale_data.physical_resist = atoi(row[20]); + scale_data.min_dmg = atoi(row[21]); + scale_data.max_dmg = atoi(row[22]); + scale_data.hp_regen_rate = atoi(row[23]); + scale_data.attack_delay = atoi(row[24]); + scale_data.spell_scale = atoi(row[25]); + scale_data.heal_scale = atoi(row[26]); + + if (row[25]) { + scale_data.special_abilities = row[27]; + } + + npc_global_base_scaling_data.insert( + std::make_pair( + std::make_pair(scale_data.type, scale_data.level), + scale_data + ) + ); + } + + Log(Logs::General, Logs::NPCScaling, "Global Base Scaling Data Loaded..."); + + return true; +} + +/** + * @param npc_type + * @param npc_level + * @return NpcScaleManager::global_npc_scale + */ +NpcScaleManager::global_npc_scale NpcScaleManager::GetGlobalScaleDataForTypeLevel(int8 npc_type, int npc_level) +{ + auto iter = npc_global_base_scaling_data.find(std::make_pair(npc_type, npc_level)); + if (iter != npc_global_base_scaling_data.end()) { + return iter->second; + } + + return {}; +} + +/** + * @param level + * @param npc_class + * @return + */ +uint32 NpcScaleManager::GetClassLevelDamageMod(uint32 level, uint32 npc_class) +{ + uint32 multiplier = 0; + + switch (npc_class) { + case WARRIOR: { + if (level < 20) { + multiplier = 220; + } + else if (level < 30) { + multiplier = 230; + } + else if (level < 40) { + multiplier = 250; + } + else if (level < 53) { + multiplier = 270; + } + else if (level < 57) { + multiplier = 280; + } + else if (level < 60) { + multiplier = 290; + } + else if (level < 70) { + multiplier = 300; + } + else { + multiplier = 311; + } + break; + } + case DRUID: + case CLERIC: + case SHAMAN: { + if (level < 70) { + multiplier = 150; + } + else { + multiplier = 157; + } + break; + } + case BERSERKER: + case PALADIN: + case SHADOWKNIGHT: { + if (level < 35) { + multiplier = 210; + } + else if (level < 45) { + multiplier = 220; + } + else if (level < 51) { + multiplier = 230; + } + else if (level < 56) { + multiplier = 240; + } + else if (level < 60) { + multiplier = 250; + } + else if (level < 68) { + multiplier = 260; + } + else { + multiplier = 270; + } + break; + } + case MONK: + case BARD: + case ROGUE: + case BEASTLORD: { + if (level < 51) { + multiplier = 180; + } + else if (level < 58) { + multiplier = 190; + } + else if (level < 70) { + multiplier = 200; + } + else { + multiplier = 210; + } + break; + } + case RANGER: { + if (level < 58) { + multiplier = 200; + } + else if (level < 70) { + multiplier = 210; + } + else { + multiplier = 220; + } + break; + } + case MAGICIAN: + case WIZARD: + case NECROMANCER: + case ENCHANTER: { + if (level < 70) { + multiplier = 120; + } + else { + multiplier = 127; + } + break; + } + default: { + if (level < 35) { + multiplier = 210; + } + else if (level < 45) { + multiplier = 220; + } + else if (level < 51) { + multiplier = 230; + } + else if (level < 56) { + multiplier = 240; + } + else if (level < 60) { + multiplier = 250; + } + else { + multiplier = 260; + } + break; + } + } + + return multiplier; +} + +/** + * @param npc + * @return int8 + */ +int8 NpcScaleManager::GetNPCScalingType(NPC *&npc) +{ + std::string npc_name = npc->GetName(); + + if (npc->IsRareSpawn() || npc_name.find('#') != std::string::npos || isupper(npc_name[0])) { + return 1; + } + + if (npc->IsRaidTarget()) { + return 2; + } + + return 0; +} + +/** + * @param npc + * @return std::string + */ +std::string NpcScaleManager::GetNPCScalingTypeName(NPC *&npc) +{ + int8 scaling_type = GetNPCScalingType(npc); + + if (scaling_type == 1) { + return "Named"; + } + + if (npc->IsRaidTarget()) { + return "Raid"; + } + + return "Trash"; +} + +/** + * Determines based on minimum criteria if NPC is auto scaled for certain things to be scaled like + * special abilities. We use this so we don't blindly assume we want things to be applied + * + * @param npc + * @return + */ +bool NpcScaleManager::IsAutoScaled(NPC *npc) +{ + return + (npc->GetHP() == 0 && + npc->GetMaxDMG() == 0 && + npc->GetMinDMG() == 0 && + npc->GetSTR() == 0 && + npc->GetSTA() == 0 && + npc->GetDEX() == 0 && + npc->GetAGI() == 0 && + npc->GetINT() == 0 && + npc->GetWIS() == 0 && + npc->GetCHA() == 0 && + npc->GetMR() == 0 && + npc->GetFR() == 0 && + npc->GetCR() == 0 && + npc->GetPR() == 0 && + npc->GetDR() == 0); +} + +/** + * Returns false if scaling data not found + * @param npc + * @return + */ +bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc) +{ + int8 npc_type = GetNPCScalingType(npc); + int npc_level = npc->GetLevel(); + + global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); + + if (!scale_data.level) { + Log( + Logs::General, + Logs::NPCScaling, + "NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically NPC: %s - scaling data not found for type: %i level: %i", + npc->GetCleanName(), + npc_type, + npc_level + ); + + return false; + } + + std::string query = StringFormat( + "UPDATE `npc_types` SET " + "AC = %i, " + "hp = %i, " + "Accuracy = %i, " + "slow_mitigation = %i, " + "ATK = %i, " + "STR = %i, " + "STA = %i, " + "DEX = %i, " + "AGI = %i, " + "_INT = %i, " + "WIS = %i, " + "CHA = %i, " + "MR = %i, " + "CR = %i, " + "FR = %i, " + "PR = %i, " + "DR = %i, " + "Corrup = %i, " + "PhR = %i, " + "mindmg = %i, " + "maxdmg = %i, " + "hp_regen_rate = %i, " + "attack_delay = %i, " + "spellscale = %i, " + "healscale = %i, " + "special_abilities = '%s' " + "WHERE `id` = %i", + scale_data.ac, + scale_data.hp, + scale_data.accuracy, + scale_data.slow_mitigation, + scale_data.attack, + scale_data.strength, + scale_data.stamina, + scale_data.dexterity, + scale_data.agility, + scale_data.intelligence, + scale_data.wisdom, + scale_data.charisma, + scale_data.magic_resist, + scale_data.cold_resist, + scale_data.fire_resist, + scale_data.poison_resist, + scale_data.disease_resist, + scale_data.corruption_resist, + scale_data.physical_resist, + scale_data.min_dmg, + scale_data.max_dmg, + scale_data.hp_regen_rate, + scale_data.attack_delay, + scale_data.spell_scale, + scale_data.heal_scale, + EscapeString(scale_data.special_abilities).c_str(), + npc->GetNPCTypeID() + ); + + auto results = database.QueryDatabase(query); + + return results.Success(); +} + +/** + * Returns false if scaling data not found + * @param npc + * @return + */ +bool NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically(NPC *&npc) +{ + int8 npc_type = GetNPCScalingType(npc); + int npc_level = npc->GetLevel(); + + global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); + + if (!scale_data.level) { + Log( + Logs::General, + Logs::NPCScaling, + "NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically NPC: %s - scaling data not found for type: %i level: %i", + npc->GetCleanName(), + npc_type, + npc_level + ); + + return false; + } + + std::string query = StringFormat( + "UPDATE `npc_types` SET " + "AC = 0, " + "hp = 0, " + "Accuracy = 0, " + "slow_mitigation = 0, " + "ATK = 0, " + "STR = 0, " + "STA = 0, " + "DEX = 0, " + "AGI = 0, " + "_INT = 0, " + "WIS = 0, " + "CHA = 0, " + "MR = 0, " + "CR = 0, " + "FR = 0, " + "PR = 0, " + "DR = 0, " + "Corrup = 0, " + "PhR = 0, " + "mindmg = 0, " + "maxdmg = 0, " + "hp_regen_rate = 0, " + "attack_delay = 0, " + "spellscale = 0, " + "healscale = 0, " + "special_abilities = '' " + "WHERE `id` = %i", + npc->GetNPCTypeID() + ); + + auto results = database.QueryDatabase(query); + + return results.Success(); +} \ No newline at end of file diff --git a/zone/npc_scale_manager.h b/zone/npc_scale_manager.h new file mode 100644 index 000000000..1135ee8a2 --- /dev/null +++ b/zone/npc_scale_manager.h @@ -0,0 +1,107 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2018 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef EQEMU_NPC_SCALE_MANAGER_H +#define EQEMU_NPC_SCALE_MANAGER_H + +#include "npc.h" + +class NpcScaleManager { +public: + struct global_npc_scale { + int type; + int level; + int ac; + int hp; + int accuracy; + int slow_mitigation; + int attack; + int strength; + int stamina; + int dexterity; + int agility; + int intelligence; + int wisdom; + int charisma; + int magic_resist; + int cold_resist; + int fire_resist; + int poison_resist; + int disease_resist; + int corruption_resist; + int physical_resist; + int min_dmg; + int max_dmg; + int hp_regen_rate; + int attack_delay; + int spell_scale; + int heal_scale; + + std::string special_abilities; + }; + + std::vector scaling_stats = { + "ac", + "max_hp", + "accuracy", + "slow_mitigation", + "atk", + "str", + "sta", + "dex", + "agi", + "int", + "wis", + "cha", + "mr", + "cr", + "fr", + "pr", + "dr", + "cr", + "pr", + "min_hit", + "max_hit", + "hp_regen", + "attack_delay", + "spell_scale", + "heal_scale", + "special_abilities" + }; + + void ScaleNPC(NPC * npc); + bool IsAutoScaled(NPC * npc); + bool LoadScaleData(); + + global_npc_scale GetGlobalScaleDataForTypeLevel(int8 npc_type, int npc_level); + + std::map, global_npc_scale> npc_global_base_scaling_data; + + int8 GetNPCScalingType(NPC * &npc); + std::string GetNPCScalingTypeName(NPC * &npc); + bool ApplyGlobalBaseScalingToNPCStatically(NPC * &npc); + bool ApplyGlobalBaseScalingToNPCDynamically(NPC * &npc); + + uint32 GetClassLevelDamageMod(uint32 level, uint32 npc_class); +}; + +extern NpcScaleManager *npc_scale_manager; + +#endif //EQEMU_NPC_SCALE_MANAGER_H diff --git a/zone/object.h b/zone/object.h index 1afe574ea..ce64ee47e 100644 --- a/zone/object.h +++ b/zone/object.h @@ -179,20 +179,20 @@ protected: void ResetState(); // Set state back to original void RandomSpawn(bool send_packet = false); //spawn this ground spawn at a random place - Object_Struct m_data; // Packet data - EQEmu::ItemInstance* m_inst; // Item representing object - bool m_inuse; // Currently in use by a client? - uint32 m_id; // Database key, different than drop_id - uint32 m_type; // Object Type, ie, forge, oven, dropped item, etc (ref: ContainerUseTypes) - uint32 m_icon; // Icon to use for forge, oven, etc - float m_max_x; - float m_max_y; - float m_min_x; - float m_min_y; - float m_z; - float m_heading; - bool m_ground_spawn; - char m_display_name[64]; + Object_Struct m_data; // Packet data + EQEmu::ItemInstance *m_inst; // Item representing object + bool m_inuse; // Currently in use by a client? + uint32 m_id; // Database key, different than drop_id + uint32 m_type; // Object Type, ie, forge, oven, dropped item, etc (ref: ContainerUseTypes) + uint32 m_icon; // Icon to use for forge, oven, etc + float m_max_x; + float m_max_y; + float m_min_x; + float m_min_y; + float m_z; + float m_heading; + bool m_ground_spawn; + char m_display_name[64]; std::map o_EntityVariables; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 2b6c5d4a0..3905c6abc 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -976,37 +976,44 @@ void QuestManager::permagender(int gender_id) { uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); uint16 book_slot, count; - uint16 curspell; + uint16 spell_id; - uint32 Char_ID = initiator->CharacterID(); + uint32 char_id = initiator->CharacterID(); bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); + bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); bool SpellGlobalCheckResult = 0; + bool SpellBucketCheckResult = 0; - for(curspell = 0, book_slot = initiator->GetNextAvailableSpellBookSlot(), count = 0; curspell < SPDAT_RECORDS && book_slot < MAX_PP_SPELLBOOK; curspell++, book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot)) + for(spell_id = 0, book_slot = initiator->GetNextAvailableSpellBookSlot(), count = 0; spell_id < SPDAT_RECORDS && book_slot < MAX_PP_SPELLBOOK; spell_id++, book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot)) { if ( - spells[curspell].classes[WARRIOR] != 0 && //check if spell exists - spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level - spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level - spells[curspell].skill != 52 && - spells[curspell].effectid[EFFECT_COUNT - 1] != 10 + spells[spell_id].classes[WARRIOR] != 0 && //check if spell exists + spells[spell_id].classes[initiator->GetPP().class_-1] <= max_level && //maximum level + spells[spell_id].classes[initiator->GetPP().class_-1] >= min_level && //minimum level + spells[spell_id].skill != 52 && + spells[spell_id].effectid[EFFECT_COUNT - 1] != 10 ) { if (book_slot == -1) //no more book slots break; - if(!IsDiscipline(curspell) && !initiator->HasSpellScribed(curspell)) { //isn't a discipline & we don't already have it scribed + if(!IsDiscipline(spell_id) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed if (SpellGlobalRule) { // Bool to see if the character has the required QGlobal to scribe it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(curspell, Char_ID); + SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id, char_id); if (SpellGlobalCheckResult) { - initiator->ScribeSpell(curspell, book_slot); + initiator->ScribeSpell(spell_id, book_slot); count++; } - } - else { - initiator->ScribeSpell(curspell, book_slot); + } else if (SpellBucketRule) { + SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id); + if (SpellBucketCheckResult) { + initiator->ScribeSpell(spell_id, book_slot); + count++; + } + } else { + initiator->ScribeSpell(spell_id, book_slot); count++; } } @@ -1018,46 +1025,59 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { uint16 QuestManager::traindiscs(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); uint16 count; - uint16 curspell; + uint16 spell_id; - uint32 Char_ID = initiator->CharacterID(); + uint32 char_id = initiator->CharacterID(); bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); + bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); bool SpellGlobalCheckResult = 0; + bool SpellBucketCheckResult = 0; - for(curspell = 0, count = 0; curspell < SPDAT_RECORDS; curspell++) + for(spell_id = 0, count = 0; spell_id < SPDAT_RECORDS; spell_id++) { if ( - spells[curspell].classes[WARRIOR] != 0 && //check if spell exists - spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level - spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level - spells[curspell].skill != 52 && - ( !RuleB(Spells, UseCHAScribeHack) || spells[curspell].effectid[EFFECT_COUNT - 1] != 10 ) + spells[spell_id].classes[WARRIOR] != 0 && //check if spell exists + spells[spell_id].classes[initiator->GetPP().class_-1] <= max_level && //maximum level + spells[spell_id].classes[initiator->GetPP().class_-1] >= min_level && //minimum level + spells[spell_id].skill != 52 && + ( !RuleB(Spells, UseCHAScribeHack) || spells[spell_id].effectid[EFFECT_COUNT - 1] != 10 ) ) { - if(IsDiscipline(curspell)){ + if(IsDiscipline(spell_id)){ //we may want to come up with a function like Client::GetNextAvailableSpellBookSlot() to help speed this up a little for(uint32 r = 0; r < MAX_PP_DISCIPLINES; r++) { - if(initiator->GetPP().disciplines.values[r] == curspell) { + if(initiator->GetPP().disciplines.values[r] == spell_id) { initiator->Message(13, "You already know this discipline."); break; //continue the 1st loop } else if(initiator->GetPP().disciplines.values[r] == 0) { if (SpellGlobalRule) { // Bool to see if the character has the required QGlobal to train it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(curspell, Char_ID); + SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id, char_id); if (SpellGlobalCheckResult) { - initiator->GetPP().disciplines.values[r] = curspell; - database.SaveCharacterDisc(Char_ID, r, curspell); + initiator->GetPP().disciplines.values[r] = spell_id; + database.SaveCharacterDisc(char_id, r, spell_id); initiator->SendDisciplineUpdate(); initiator->Message(0, "You have learned a new discipline!"); count++; //success counter } break; //continue the 1st loop + } else if (SpellBucketRule) { + // Bool to see if the character has the required bucket to train it if one exists in the spell_buckets table + SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id); + if (SpellBucketCheckResult) { + initiator->GetPP().disciplines.values[r] = spell_id; + database.SaveCharacterDisc(char_id, r, spell_id); + initiator->SendDisciplineUpdate(); + initiator->Message(0, "You have learned a new discipline!"); + count++; + } + break; } else { - initiator->GetPP().disciplines.values[r] = curspell; - database.SaveCharacterDisc(Char_ID, r, curspell); + initiator->GetPP().disciplines.values[r] = spell_id; + database.SaveCharacterDisc(char_id, r, spell_id); initiator->SendDisciplineUpdate(); initiator->Message(0, "You have learned a new discipline!"); count++; //success counter @@ -2659,6 +2679,10 @@ uint16 QuestManager::GetInstanceID(const char *zone, int16 version) return 0; } +uint16 QuestManager::GetInstanceIDByCharID(const char *zone, int16 version, uint32 char_id) { + return database.GetInstanceID(zone, char_id, version); +} + void QuestManager::AssignToInstance(uint16 instance_id) { QuestManagerCurrentQuestVars(); @@ -2668,6 +2692,10 @@ void QuestManager::AssignToInstance(uint16 instance_id) } } +void QuestManager::AssignToInstanceByCharID(uint16 instance_id, uint32 char_id) { + database.AddClientToInstance(instance_id, char_id); +} + void QuestManager::AssignGroupToInstance(uint16 instance_id) { QuestManagerCurrentQuestVars(); @@ -2708,6 +2736,10 @@ void QuestManager::RemoveFromInstance(uint16 instance_id) } } +void QuestManager::RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id) { + database.RemoveClientFromInstance(instance_id, char_id); +} + void QuestManager::RemoveAllFromInstance(uint16 instance_id) { QuestManagerCurrentQuestVars(); @@ -2759,47 +2791,11 @@ void QuestManager::FlagInstanceByRaidLeader(uint32 zone, int16 version) } } -const char* QuestManager::saylink(char* Phrase, bool silent, const char* LinkName) { +std::string QuestManager::saylink(char *saylink_text, bool silent, const char *link_name) +{ QuestManagerCurrentQuestVars(); - int sayid = 0; - - int sz = strlen(Phrase); - auto escaped_string = new char[sz * 2]; - database.DoEscapeString(escaped_string, Phrase, sz); - - // Query for an existing phrase and id in the saylink table - std::string query = StringFormat("SELECT `id` FROM `saylink` WHERE `phrase` = '%s'", escaped_string); - auto results = database.QueryDatabase(query); - if (results.Success()) { - if (results.RowCount() >= 1) { - for (auto row = results.begin();row != results.end(); ++row) - sayid = atoi(row[0]); - } else { - std::string insert_query = StringFormat("INSERT INTO `saylink` (`phrase`) VALUES ('%s')", escaped_string); - results = database.QueryDatabase(insert_query); - if (!results.Success()) { - Log(Logs::General, Logs::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str()); - } - else { - sayid = results.LastInsertedID(); - } - } - } - safe_delete_array(escaped_string); - - //Create the say link as an item link hash - EQEmu::SayLinkEngine linker; - linker.SetProxyItemID(SAYLINK_ITEM_ID); - if (silent) - linker.SetProxyAugment2ID(sayid); - else - linker.SetProxyAugment1ID(sayid); - linker.SetProxyText(LinkName); - - strcpy(Phrase, linker.GenerateLink().c_str()); - - return Phrase; + return EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink_text, silent, link_name); } const char* QuestManager::getguildnamebyid(int guild_id) { diff --git a/zone/questmgr.h b/zone/questmgr.h index d227b27d1..6517872d8 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -234,10 +234,13 @@ public: uint32 GetInstanceTimerByID(uint16 instance_id = 0); void DestroyInstance(uint16 instance_id); uint16 GetInstanceID(const char *zone, int16 version); + uint16 GetInstanceIDByCharID(const char *zone, int16 version, uint32 char_id); void AssignToInstance(uint16 instance_id); + void AssignToInstanceByCharID(uint16 instance_id, uint32 char_id); void AssignGroupToInstance(uint16 instance_id); void AssignRaidToInstance(uint16 instance_id); void RemoveFromInstance(uint16 instance_id); + void RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id); //void RemoveGroupFromInstance(uint16 instance_id); //potentially useful but not implmented at this time. //void RemoveRaidFromInstance(uint16 instance_id); //potentially useful but not implmented at this time. void RemoveAllFromInstance(uint16 instance_id); @@ -245,7 +248,7 @@ public: void FlagInstanceByGroupLeader(uint32 zone, int16 version); void FlagInstanceByRaidLeader(uint32 zone, int16 version); const char* varlink(char* perltext, int item_id); - const char* saylink(char* Phrase, bool silent, const char* LinkName); + std::string saylink(char *saylink_text, bool silent, const char *link_name); const char* getguildnamebyid(int guild_id); void SetRunning(bool val); bool IsRunning(); diff --git a/zone/spells.cpp b/zone/spells.cpp index d4c42d624..42a64d6f0 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3005,6 +3005,10 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if(effect1 != effect2) continue; + if (IsBardOnlyStackEffect(effect1) && GetSpellLevel(spellid1, BARD) != 255 && + GetSpellLevel(spellid2, BARD) != 255) + continue; + // big ol' list according to the client, wasn't that nice! if (IsEffectIgnoredInStacking(effect1)) continue; @@ -5115,14 +5119,11 @@ int Client::FindSpellBookSlotBySpellID(uint16 spellid) { return -1; //default } -bool Client::SpellGlobalCheck(uint16 spell_ID, uint32 char_ID) { - - std::string spell_Global_Name; - int spell_Global_Value; - int global_Value; - - std::string query = StringFormat("SELECT qglobal, value FROM spell_globals " - "WHERE spellid = %i", spell_ID); +bool Client::SpellGlobalCheck(uint16 spell_id, uint32 char_id) { + std::string spell_global_name; + int spell_global_value; + int global_value; + std::string query = StringFormat("SELECT qglobal, value FROM spell_globals WHERE spellid = %i", spell_id); auto results = database.QueryDatabase(query); if (!results.Success()) { return false; // Query failed, so prevent spell from scribing just in case @@ -5132,37 +5133,79 @@ bool Client::SpellGlobalCheck(uint16 spell_ID, uint32 char_ID) { return true; // Spell ID isn't listed in the spells_global table, so it is not restricted from scribing auto row = results.begin(); - spell_Global_Name = row[0]; - spell_Global_Value = atoi(row[1]); + spell_global_name = row[0]; + spell_global_value = atoi(row[1]); - if (spell_Global_Name.empty()) + if (spell_global_name.empty()) return true; // If the entry in the spell_globals table has nothing set for the qglobal name query = StringFormat("SELECT value FROM quest_globals " "WHERE charid = %i AND name = '%s'", - char_ID, spell_Global_Name.c_str()); + char_id, spell_global_name.c_str()); results = database.QueryDatabase(query); if (!results.Success()) { - Log(Logs::General, Logs::Error, "Spell ID %i query of spell_globals with Name: '%s' Value: '%i' failed", spell_ID, spell_Global_Name.c_str(), spell_Global_Value); + Log(Logs::General, Logs::Error, "Spell ID %i query of spell_globals with Name: '%s' Value: '%i' failed", spell_id, spell_global_name.c_str(), spell_global_value); return false; } if (results.RowCount() != 1) { - Log(Logs::General, Logs::Error, "Char ID: %i does not have the Qglobal Name: '%s' for Spell ID %i", char_ID, spell_Global_Name.c_str(), spell_ID); + Log(Logs::General, Logs::Error, "Char ID: %i does not have the Qglobal Name: '%s' for Spell ID %i", char_id, spell_global_name.c_str(), spell_id); + return false; + } + + row = results.begin(); + global_value = atoi(row[0]); + if (global_value == spell_global_value) + return true; // If the values match from both tables, allow the spell to be scribed + else if (global_value > spell_global_value) + return true; // Check if the qglobal value is greater than the require spellglobal value + + // If no matching result found in qglobals, don't scribe this spell + Log(Logs::General, Logs::Error, "Char ID: %i Spell_globals Name: '%s' Value: '%i' did not match QGlobal Value: '%i' for Spell ID %i", char_id, spell_global_name.c_str(), spell_global_value, global_value, spell_id); + return false; +} + +bool Client::SpellBucketCheck(uint16 spell_id, uint32 char_id) { + std::string spell_bucket_name; + int spell_bucket_value; + int bucket_value; + std::string query = StringFormat("SELECT key, value FROM spell_buckets WHERE spellid = %i", spell_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return false; + + if (results.RowCount() != 1) + return true; + + auto row = results.begin(); + spell_bucket_name = row[0]; + spell_bucket_value = atoi(row[1]); + if (spell_bucket_name.empty()) + return true; + + query = StringFormat("SELECT value FROM data_buckets WHERE key = '%i-%s'", char_id, spell_bucket_name.c_str()); + results = database.QueryDatabase(query); + if (!results.Success()) { + Log(Logs::General, Logs::Error, "Spell bucket %s for spell ID %i for char ID %i failed.", spell_bucket_name.c_str(), spell_id, char_id); + return false; + } + + if (results.RowCount() != 1) { + Log(Logs::General, Logs::Error, "Spell bucket %s does not exist for spell ID %i for char ID %i.", spell_bucket_name.c_str(), spell_id, char_id); return false; } row = results.begin(); - global_Value = atoi(row[0]); + bucket_value = atoi(row[0]); - if (global_Value == spell_Global_Value) + if (bucket_value == spell_bucket_value) return true; // If the values match from both tables, allow the spell to be scribed - else if (global_Value > spell_Global_Value) - return true; // Check if the qglobal value is greater than the require spellglobal value + else if (bucket_value > spell_bucket_value) + return true; // Check if the data bucket value is greater than the required spell bucket value - // If no matching result found in qglobals, don't scribe this spell - Log(Logs::General, Logs::Error, "Char ID: %i Spell_globals Name: '%s' Value: '%i' did not match QGlobal Value: '%i' for Spell ID %i", char_ID, spell_Global_Name.c_str(), spell_Global_Value, global_Value, spell_ID); + // If no matching result found in spell buckets, don't scribe this spell + Log(Logs::General, Logs::Error, "Spell bucket %s for spell ID %i for char ID %i did not match value %i.", spell_bucket_name.c_str(), spell_id, char_id, spell_bucket_value); return false; } @@ -5605,9 +5648,8 @@ void NPC::InitializeBuffSlots() { int max_slots = GetMaxTotalSlots(); buffs = new Buffs_Struct[max_slots]; - for(int x = 0; x < max_slots; ++x) - { - buffs[x].spellid = SPELL_UNKNOWN; + for (int x = 0; x < max_slots; ++x) { + buffs[x].spellid = SPELL_UNKNOWN; buffs[x].UpdateClient = false; } current_buff_count = 0; diff --git a/zone/zone.cpp b/zone/zone.cpp old mode 100644 new mode 100755 index a2ab032c3..c6f5c8ac5 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -896,7 +896,7 @@ bool Zone::Init(bool iStaticZone) { SetStaticZone(iStaticZone); //load the zone config file. - if (!LoadZoneCFG(zone->GetShortName(), zone->GetInstanceVersion(), true)) // try loading the zone name... + if (!LoadZoneCFG(zone->GetShortName(), zone->GetInstanceVersion())) // try loading the zone name... LoadZoneCFG(zone->GetFileName(), zone->GetInstanceVersion()); // if that fails, try the file name, then load defaults if(RuleManager::Instance()->GetActiveRulesetID() != default_ruleset) @@ -1057,39 +1057,31 @@ void Zone::ReloadStaticData() { zone->LoadNPCEmotes(&NPCEmoteList); //load the zone config file. - if (!LoadZoneCFG(zone->GetShortName(), zone->GetInstanceVersion(), true)) // try loading the zone name... + if (!LoadZoneCFG(zone->GetShortName(), zone->GetInstanceVersion())) // try loading the zone name... LoadZoneCFG(zone->GetFileName(), zone->GetInstanceVersion()); // if that fails, try the file name, then load defaults Log(Logs::General, Logs::Status, "Zone Static Data Reloaded."); } -bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id, bool DontLoadDefault) +bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id) { + memset(&newzone_data, 0, sizeof(NewZone_Struct)); - if(instance_id == 0) + map_name = nullptr; + + if(!database.GetZoneCFG(database.GetZoneID(filename), instance_id, &newzone_data, can_bind, + can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) { - map_name = nullptr; - if(!database.GetZoneCFG(database.GetZoneID(filename), 0, &newzone_data, can_bind, - can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) - { - Log(Logs::General, Logs::Error, "Error loading the Zone Config."); - return false; - } - } - else - { - //Fall back to base zone if we don't find the instance version. - map_name = nullptr; - if(!database.GetZoneCFG(database.GetZoneID(filename), instance_id, &newzone_data, can_bind, - can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) + // If loading a non-zero instance failed, try loading the default + if (instance_id != 0) { safe_delete_array(map_name); - if(!database.GetZoneCFG(database.GetZoneID(filename), 0, &newzone_data, can_bind, - can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) - { + if(!database.GetZoneCFG(database.GetZoneID(filename), 0, &newzone_data, can_bind, can_combat, can_levitate, + can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) + { Log(Logs::General, Logs::Error, "Error loading the Zone Config."); return false; - } + } } } @@ -1277,7 +1269,8 @@ bool Zone::Process() { } } - if(Weather_Timer->Check()){ + if(Weather_Timer->Check()) + { Weather_Timer->Disable(); this->ChangeWeather(); } diff --git a/zone/zone.h b/zone/zone.h old mode 100644 new mode 100755 index 1e3d345fb..4eaabfb8a --- a/zone/zone.h +++ b/zone/zone.h @@ -94,7 +94,7 @@ public: bool is_zone_time_localized; bool Init(bool iStaticZone); - bool LoadZoneCFG(const char* filename, uint16 instance_id, bool DontLoadDefault = false); + bool LoadZoneCFG(const char* filename, uint16 instance_id); bool SaveZoneCFG(); bool IsLoaded(); bool IsPVPZone() { return pvpzone; } diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 52f726c36..d721edba4 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -2638,7 +2638,7 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->spellscale = atoi(row[86]); temp_npctype_data->healscale = atoi(row[87]); temp_npctype_data->no_target_hotkey = atoi(row[88]) == 1 ? true: false; - temp_npctype_data->raid_target = atoi(row[89]) == 0 ? false: true; + temp_npctype_data->raid_target = atoi(row[89]) == 0 ? false : true; temp_npctype_data->attack_delay = atoi(row[90]) * 100; // TODO: fix DB temp_npctype_data->light = (atoi(row[91]) & 0x0F);