diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..03f782494 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +[*.cpp] +indent_style = tab +[*.h] +indent_style = tab + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 817af542c..9f3694698 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,9 @@ #EQEMU_MAP_DIR CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +IF(POLICY CMP0074) + cmake_policy(SET CMP0074 NEW) +ENDIF() #FindMySQL is located here so lets make it so CMake can find it SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/" ${CMAKE_MODULE_PATH}) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index a35c264e0..bde77822b 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -376,7 +376,11 @@ struct NewZone_Struct { /*0692*/ uint8 unknown692[8]; /*0700*/ float fog_density; /*0704*/ uint32 SuspendBuffs; -/*0704*/ +/*0708*/ uint32 FastRegenHP; +/*0712*/ uint32 FastRegenMana; +/*0716*/ uint32 FastRegenEndurance; +/*0720*/ uint32 NPCAggroMaxDist; +/*0724*/ }; /* diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index a54edc234..b0afa72f2 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -90,6 +90,7 @@ enum LogCategory { FixZ, Food, Traps, + NPCRoamBox, MaxCategoryID /* Don't Remove this*/ }; @@ -144,7 +145,8 @@ static const char* LogCategoryName[LogCategory::MaxCategoryID] = { "HP Update", "FixZ", "Food", - "Traps" + "Traps", + "NPC Roam Box" }; } diff --git a/common/memory_buffer.h b/common/memory_buffer.h index 880b860ae..474953f91 100644 --- a/common/memory_buffer.h +++ b/common/memory_buffer.h @@ -124,7 +124,7 @@ namespace EQEmu class OutBuffer : public std::stringstream { public: - inline size_t size() { return tellp(); } + inline size_t size() { return static_cast(tellp()); } void overwrite(OutBuffer::pos_type position, const char *_Str, std::streamsize _Count); uchar* detach(); }; diff --git a/common/misc_functions.h b/common/misc_functions.h index 2ead697e7..8a7ffd59a 100644 --- a/common/misc_functions.h +++ b/common/misc_functions.h @@ -73,7 +73,7 @@ uint32 SwapBits21And22(uint32 mask); uint32 Catch22(uint32 mask); // macro to catch fp errors (provided by noudness) -#define FCMP(a,b) (fabs(a-b) < FLT_EPSILON) +#define FCMP(a,b) (std::abs(a-b) < FLT_EPSILON) #define _ITOA_BUFLEN 25 const char *itoa(int num); //not thread safe diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 388bfddef..da453bffe 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1823,6 +1823,9 @@ namespace RoF OUT(zone_id); OUT(zone_instance); OUT(SuspendBuffs); + OUT(FastRegenHP); + OUT(FastRegenMana); + OUT(FastRegenEndurance); eq->FogDensity = emu->fog_density; @@ -1839,9 +1842,6 @@ namespace RoF eq->unknown893 = 0; eq->fall_damage = 0; // 0 = Fall Damage on, 1 = Fall Damage off eq->unknown895 = 0; - eq->unknown896 = 180; - eq->unknown900 = 180; - eq->unknown904 = 180; eq->unknown908 = 2; eq->unknown912 = 2; eq->unknown932 = -1; // Set from PoK Example @@ -2807,7 +2807,7 @@ namespace RoF std::vector skill; std::vector points; - for(auto i = 0; i < emu->total_prereqs; ++i) { + for(auto i = 0u; i < emu->total_prereqs; ++i) { skill.push_back(inapp->ReadUInt32()); points.push_back(inapp->ReadUInt32()); } @@ -2861,7 +2861,7 @@ namespace RoF outapp->WriteUInt32(emu->total_effects); inapp->SetReadPosition(sizeof(AARankInfo_Struct)); - for(auto i = 0; i < emu->total_effects; ++i) { + for(auto i = 0u; i < emu->total_effects; ++i) { outapp->WriteUInt32(inapp->ReadUInt32()); // skill_id outapp->WriteUInt32(inapp->ReadUInt32()); // base1 outapp->WriteUInt32(inapp->ReadUInt32()); // base2 @@ -2917,7 +2917,7 @@ namespace RoF unsigned char *eq_ptr = __packet->pBuffer; eq_ptr += sizeof(structs::CharacterSelect_Struct); - for (int counter = 0; counter < character_count; ++counter) { + for (auto counter = 0u; counter < character_count; ++counter) { emu_cse = (CharacterSelectEntry_Struct *)emu_ptr; eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // base address @@ -3259,7 +3259,7 @@ namespace RoF InBuffer += title_size; TaskDescriptionData1_Struct *emu_tdd1 = (TaskDescriptionData1_Struct *)InBuffer; - emu_tdd1->StartTime = (time(nullptr) - emu_tdd1->StartTime); // RoF has elapsed time here rather than start time + emu_tdd1->StartTime = (static_cast(time(nullptr)) - emu_tdd1->StartTime); // RoF has elapsed time here rather than start time InBuffer += sizeof(TaskDescriptionData1_Struct); uint32 description_size = strlen(InBuffer) + 1; @@ -3610,10 +3610,10 @@ namespace RoF // calculate size of names, note the packet DOES NOT have null termed c-strings std::vector name_lengths; - for (int i = 0; i < count; ++i) { + for (auto i = 0u; i < count; ++i) { InternalVeteranReward *ivr = (InternalVeteranReward *)__emu_buffer; - for (int i = 0; i < ivr->claim_count; i++) { + for (auto i = 0u; i < ivr->claim_count; i++) { uint32 length = strnlen(ivr->items[i].item_name, 63); if (length) name_lengths.push_back(length); @@ -3633,7 +3633,7 @@ namespace RoF outapp->WriteUInt32(count); auto name_itr = name_lengths.begin(); - for (int i = 0; i < count; i++) { + for (auto i = 0u; i < count; i++) { InternalVeteranReward *ivr = (InternalVeteranReward *)__emu_buffer; outapp->WriteUInt32(ivr->claim_id); @@ -3641,7 +3641,7 @@ namespace RoF outapp->WriteUInt32(ivr->claim_count); outapp->WriteUInt8(1); // enabled - for (int j = 0; j < ivr->claim_count; j++) { + for (auto j = 0u; j < ivr->claim_count; j++) { assert(name_itr != name_lengths.end()); // the way it's written, it should never happen, so just assert outapp->WriteUInt32(*name_itr); outapp->WriteData(ivr->items[j].item_name, *name_itr); @@ -3869,7 +3869,7 @@ namespace RoF VARSTRUCT_ENCODE_STRING(Buffer, emu->name); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->spawnId); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->level); - VARSTRUCT_ENCODE_TYPE(float, Buffer, SpawnSize - 0.7); // Eye Height? + VARSTRUCT_ENCODE_TYPE(float, Buffer, SpawnSize - 0.7f); // Eye Height? VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->NPC); structs::Spawn_Struct_Bitfields *Bitfields = (structs::Spawn_Struct_Bitfields*)Buffer; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index aa942f98c..1632daf3b 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1891,6 +1891,9 @@ namespace RoF2 OUT(zone_id); OUT(zone_instance); OUT(SuspendBuffs); + OUT(FastRegenHP); + OUT(FastRegenMana); + OUT(FastRegenEndurance); eq->FogDensity = emu->fog_density; @@ -1914,9 +1917,6 @@ namespace RoF2 eq->bNoFear = 0; eq->fall_damage = 0; // 0 = Fall Damage on, 1 = Fall Damage off eq->unknown895 = 0; - eq->FastRegenHP = 180; - eq->FastRegenMana = 180; - eq->FastRegenEndurance = 180; eq->CanPlaceCampsite = 2; eq->CanPlaceGuildBanner = 2; eq->FishingRelated = -1; // Set from PoK Example diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index dcc5e4ccc..885a9aa80 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -588,9 +588,9 @@ struct NewZone_Struct { /*0893*/ uint8 unknown893; // Seen 0 - 00 /*0894*/ uint8 fall_damage; // 0 = Fall Damage on, 1 = Fall Damage off /*0895*/ uint8 unknown895; // Seen 0 - 00 -/*0896*/ uint32 unknown896; // Seen 180 -/*0900*/ uint32 unknown900; // Seen 180 -/*0904*/ uint32 unknown904; // Seen 180 +/*0896*/ uint32 FastRegenHP; // Seen 180 +/*0900*/ uint32 FastRegenMana; // Seen 180 +/*0904*/ uint32 FastRegenEndurance; // Seen 180 /*0908*/ uint32 unknown908; // Seen 2 /*0912*/ uint32 unknown912; // Seen 2 /*0916*/ float FogDensity; // Most zones have this set to 0.33 Blightfire had 0.16 diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 067b2e571..4abf01ea3 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1350,6 +1350,10 @@ namespace SoD OUT(zone_id); OUT(zone_instance); OUT(SuspendBuffs); + OUT(FastRegenHP); + OUT(FastRegenMana); + OUT(FastRegenEndurance); + /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown800 = -1; eq->unknown844 = 600; @@ -1363,9 +1367,6 @@ namespace SoD eq->unknown893 = 0; eq->fall_damage = 0; // 0 = Fall Damage on, 1 = Fall Damage off eq->unknown895 = 0; - eq->unknown896 = 180; - eq->unknown900 = 180; - eq->unknown904 = 180; eq->unknown908 = 2; eq->unknown912 = 2; eq->FogDensity = emu->fog_density; diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 80d0faae9..57e37c957 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -456,9 +456,9 @@ struct NewZone_Struct { /*0893*/ uint8 unknown893; //seen 0 - 00 /*0894*/ uint8 fall_damage; // 0 = Fall Damage on, 1 = Fall Damage off /*0895*/ uint8 unknown895; //seen 0 - 00 -/*0896*/ uint32 unknown896; //seen 180 -/*0900*/ uint32 unknown900; //seen 180 -/*0904*/ uint32 unknown904; //seen 180 +/*0896*/ uint32 FastRegenHP; //seen 180 +/*0900*/ uint32 FastRegenMana; //seen 180 +/*0904*/ uint32 FastRegenEndurance; //seen 180 /*0908*/ uint32 unknown908; //seen 2 /*0912*/ uint32 unknown912; //seen 2 /*0916*/ float FogDensity; //Of about 10 or so zones tested, all but one have this set to 0.33 Blightfire had 0.16 diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 02ae774bc..7497df94c 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -1027,6 +1027,9 @@ namespace SoF OUT(zone_id); OUT(zone_instance); OUT(SuspendBuffs); + OUT(FastRegenHP); + OUT(FastRegenMana); + OUT(FastRegenEndurance); /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown796 = -1; @@ -1041,9 +1044,6 @@ namespace SoF eq->unknown889 = 0; eq->fall_damage = 0; // 0 = Fall Damage on, 1 = Fall Damage off eq->unknown891 = 0; - eq->unknown892 = 180; - eq->unknown896 = 180; - eq->unknown900 = 180; eq->unknown904 = 2; eq->unknown908 = 2; diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index a312b48a8..527341c93 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -460,9 +460,9 @@ struct NewZone_Struct { /*0893*/ uint8 unknown889; //seen 0 - 00 /*0894*/ uint8 fall_damage; // 0 = Fall Damage on, 1 = Fall Damage off /*0895*/ uint8 unknown891; //seen 0 - 00 -/*0892*/ uint32 unknown892; //seen 180 -/*0896*/ uint32 unknown896; //seen 180 -/*0900*/ uint32 unknown900; //seen 180 +/*0892*/ uint32 FastRegenHP; //seen 180 +/*0896*/ uint32 FastRegenMana; //seen 180 +/*0900*/ uint32 FastRegenEndurance; //seen 180 /*0904*/ uint32 unknown904; //seen 2 /*0908*/ uint32 unknown908; //seen 2 /*0912*/ diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 437cf8cce..d2a50541f 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1574,6 +1574,9 @@ namespace UF OUT(zone_id); OUT(zone_instance); OUT(SuspendBuffs); + OUT(FastRegenHP); + OUT(FastRegenMana); + OUT(FastRegenEndurance); eq->FogDensity = emu->fog_density; @@ -1590,9 +1593,6 @@ namespace UF eq->unknown893 = 0; eq->fall_damage = 0; // 0 = Fall Damage on, 1 = Fall Damage off eq->unknown895 = 0; - eq->unknown896 = 180; - eq->unknown900 = 180; - eq->unknown904 = 180; eq->unknown908 = 2; eq->unknown912 = 2; diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 02361cf1f..fe58b73b2 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -456,9 +456,9 @@ struct NewZone_Struct { /*0893*/ uint8 unknown893; //seen 0 - 00 /*0894*/ uint8 fall_damage; // 0 = Fall Damage on, 1 = Fall Damage off /*0895*/ uint8 unknown895; //seen 0 - 00 -/*0896*/ uint32 unknown896; //seen 180 -/*0900*/ uint32 unknown900; //seen 180 -/*0904*/ uint32 unknown904; //seen 180 +/*0896*/ uint32 FastRegenHP; //seen 180 +/*0900*/ uint32 FastRegenMana; //seen 180 +/*0904*/ uint32 FastRegenEndurance; //seen 180 /*0908*/ uint32 unknown908; //seen 2 /*0912*/ uint32 unknown912; //seen 2 /*0916*/ float FogDensity; //Of about 10 or so zones tested, all but one have this set to 0.33 Blightfire had 0.16 diff --git a/common/ruletypes.h b/common/ruletypes.h index 71af49010..c2f11c1c3 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -94,9 +94,6 @@ RULE_INT(Character, SkillUpModifier, 100) //skill ups are at 100% RULE_BOOL(Character, SharedBankPlat, false) //off by default to prevent duping for now RULE_BOOL(Character, BindAnywhere, false) RULE_BOOL(Character, RestRegenEnabled, true) // Enable OOC Regen -RULE_INT(Character, RestRegenHP, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 -RULE_INT(Character, RestRegenMana, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 -RULE_INT(Character, RestRegenEnd, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 RULE_INT(Character, RestRegenTimeToActivate, 30) // Time in seconds for rest state regen to kick in. RULE_INT(Character, RestRegenRaidTimeToActivate, 300) // Time in seconds for rest state regen to kick in with a raid target. RULE_INT(Character, KillsPerGroupLeadershipAA, 250) // Number of dark blues or above per Group Leadership AA @@ -419,6 +416,8 @@ RULE_BOOL(Combat, UseIntervalAC, true) RULE_INT(Combat, PetAttackMagicLevel, 30) RULE_BOOL(Combat, EnableFearPathing, true) RULE_REAL(Combat, FleeMultiplier, 2.0) // Determines how quickly a NPC will slow down while fleeing. Decrease multiplier to slow NPC down quicker. +RULE_BOOL(Combat, FleeGray, true) // If true FleeGrayHPRatio will be used. +RULE_INT(Combat, FleeGrayHPRatio, 50) //HP % when a Gray NPC begins to flee. RULE_INT(Combat, FleeHPRatio, 25) //HP % when a NPC begins to flee. RULE_BOOL(Combat, FleeIfNotAlone, false) // If false, mobs won't flee if other mobs are in combat with it. RULE_BOOL(Combat, AdjustProcPerMinute, true) @@ -532,6 +531,11 @@ RULE_INT(NPC, NPCToNPCAggroTimerMin, 500) RULE_INT(NPC, NPCToNPCAggroTimerMax, 6000) RULE_BOOL(NPC, UseClassAsLastName, true) // Uses class archetype as LastName for npcs with none RULE_BOOL(NPC, NewLevelScaling, true) // Better level scaling, use old if new formulas would break your server +RULE_INT(NPC, NPCGatePercent, 5) // % at which the NPC Will attempt to gate at. +RULE_BOOL(NPC, NPCGateNearBind, false) // Will NPC attempt to gate when near bind location? +RULE_INT(NPC, NPCGateDistanceBind, 75) // Distance from bind before NPC will attempt to gate +RULE_BOOL(NPC, NPCHealOnGate, true) // Will the NPC Heal on Gate. +RULE_REAL(NPC, NPCHealOnGateAmount, 25) // How much the npc will heal on gate if enabled. RULE_CATEGORY_END() RULE_CATEGORY(Aggro) @@ -695,6 +699,7 @@ RULE_CATEGORY_END() RULE_CATEGORY(QueryServ) RULE_BOOL(QueryServ, PlayerLogChat, false) // Logs Player Chat RULE_BOOL(QueryServ, PlayerLogTrades, false) // Logs Player Trades +RULE_BOOL(QueryServ, PlayerDropItems, false) // Logs Player dropping items RULE_BOOL(QueryServ, PlayerLogHandins, false) // Logs Player Handins RULE_BOOL(QueryServ, PlayerLogNPCKills, false) // Logs Player NPC Kills RULE_BOOL(QueryServ, PlayerLogDeletes, false) // Logs Player Deletes diff --git a/common/servertalk.h b/common/servertalk.h index e1d019fc4..c5bee9ef8 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -203,6 +203,7 @@ #define ServerOP_CZSignalNPC 0x5017 #define ServerOP_CZSetEntityVariableByNPCTypeID 0x5018 #define ServerOP_WWMarquee 0x5019 +#define ServerOP_QSPlayerDropItem 0x5020 /* Query Serv Generic Packet Flag/Type Enumeration */ enum { QSG_LFGuild = 0 }; @@ -1140,6 +1141,27 @@ struct QSPlayerLogTrade_Struct { QSTradeItems_Struct items[0]; }; +struct QSDropItems_Struct { + uint32 item_id; + uint16 charges; + uint32 aug_1; + uint32 aug_2; + uint32 aug_3; + uint32 aug_4; + uint32 aug_5; +}; + +struct QSPlayerDropItem_Struct { + uint32 char_id; + bool pickup; // 0 drop, 1 pickup + uint32 zone_id; + int x; + int y; + int z; + uint16 _detail_count; + QSDropItems_Struct items[0]; +}; + struct QSHandinItems_Struct { char action_type[7]; // handin, return or reward uint16 char_slot; diff --git a/common/version.h b/common/version.h index 8653e2e9d..849b3d9ac 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 9127 +#define CURRENT_BINARY_DATABASE_VERSION 9129 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9020 #else diff --git a/queryserv/database.cpp b/queryserv/database.cpp index c4e185360..fcda884e4 100644 --- a/queryserv/database.cpp +++ b/queryserv/database.cpp @@ -123,6 +123,39 @@ void Database::AddSpeech(const char* from, const char* to, const char* message, } +void Database::LogPlayerDropItem(QSPlayerDropItem_Struct* QS) { + + std::string query = StringFormat("INSERT INTO `qs_player_drop_record` SET `time` = NOW(), " + "`char_id` = '%i', `pickup` = '%i', " + "`zone_id` = '%i', `x` = '%i', `y` = '%i', `z` = '%i' ", + QS->char_id, QS->pickup, QS->zone_id, QS->x, QS->y, QS->z); + + auto results = QueryDatabase(query); + if (!results.Success()) { + Log(Logs::Detail, Logs::QS_Server, "Failed Drop Record Insert: %s", results.ErrorMessage().c_str()); + Log(Logs::Detail, Logs::QS_Server, "%s", query.c_str()); + } + + if (QS->_detail_count == 0) + return; + + int lastIndex = results.LastInsertedID(); + + for (int i = 0; i < QS->_detail_count; i++) { + query = StringFormat("INSERT INTO `qs_player_drop_record_entries` SET `event_id` = '%i', " + "`item_id` = '%i', `charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', " + "`aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'", + lastIndex, QS->items[i].item_id, + QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, + QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5); + results = QueryDatabase(query); + if (!results.Success()) { + Log(Logs::Detail, Logs::QS_Server, "Failed Drop Record Entry Insert: %s", results.ErrorMessage().c_str()); + Log(Logs::Detail, Logs::QS_Server, "%s", query.c_str()); + } + } +} + void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 detailCount) { std::string query = StringFormat("INSERT INTO `qs_player_trade_record` SET `time` = NOW(), " diff --git a/queryserv/database.h b/queryserv/database.h index b2d32341b..13815c575 100644 --- a/queryserv/database.h +++ b/queryserv/database.h @@ -45,6 +45,7 @@ public: void AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type); void LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount); + void LogPlayerDropItem(QSPlayerDropItem_Struct* QS); void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount); void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members); void LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items); diff --git a/queryserv/worldserver.cpp b/queryserv/worldserver.cpp index 686c92326..a03d23b1e 100644 --- a/queryserv/worldserver.cpp +++ b/queryserv/worldserver.cpp @@ -98,6 +98,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) database.LogPlayerTrade(QS, QS->_detail_count); break; } + case ServerOP_QSPlayerDropItem: { + QSPlayerDropItem_Struct *QS = (QSPlayerDropItem_Struct *) p.Data(); + database.LogPlayerDropItem(QS); + break; + } case ServerOP_QSPlayerLogHandins: { QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct*)p.Data(); database.LogPlayerHandin(QS, QS->_detail_count); diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index 9201a9e27..29a8142a6 100644 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -40,7 +40,12 @@ if($Config{osname}=~/freebsd|linux/i){ if($Config{osname}=~/Win|MS/i){ $OS = "Windows"; } + $has_internet_connection = check_internet_connection(); +if(-e "skip_internet_connection_check.txt"){ + $has_internet_connection = 1; +} + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(); if(-e "eqemu_server_skip_update.txt"){ @@ -167,19 +172,19 @@ sub show_install_summary_info { if (-e "install_variables.txt") { $file_to_open = "install_variables.txt"; } - elsif(-e "../install_variables.txt"){ + elsif (-e "../install_variables.txt") { $file_to_open = "../install_variables.txt"; } - open (INSTALL_VARS, $file_to_open); - while (){ + open(INSTALL_VARS, $file_to_open); + while () { chomp; $o = $_; @data = split(":", $o); print " - " . $data[0] . "\t" . $data[1] . "\n"; } - close (INSTALL_VARS); - - if($OS eq "Windows"){ + close(INSTALL_VARS); + + if ($OS eq "Windows") { print "[Install] Windows Utility Scripts:\n"; print " - t_start_server.bat Starts EQEmu server with 30 dynamic zones, UCS & Queryserv, dynamic zones\n"; print " - t_start_server_with_loginserver.bat Starts EQEmu server with 30 zones with loginserver\n"; @@ -187,16 +192,16 @@ sub show_install_summary_info { print " - t_database_backup.bat Backs up the Database to backups/ folder - do not run during server is online\n"; print " - t_server_crash_report.pl Will parse any zone crashes for reporting to developers\n"; } - if($OS eq "Linux"){ + if ($OS eq "Linux") { print "[Install] Linux Utility Scripts:\n"; print " - server_start.sh Starts EQEmu server (Quiet) with 30 dynamic zones, UCS & Queryserv, dynamic zones\n"; print " - server_start_dev.sh Starts EQEmu server with 10 dynamic zones, UCS & Queryserv, dynamic zones all verbose\n"; print " - server_stop.sh Stops EQEmu Server (No warning)\n"; print " - server_status.sh Prints the status of the EQEmu Server processes\n"; } - + print "[Configure] eqemu_config.xml Edit to change server settings and name\n"; - + analytics_insertion("install_complete", "null"); } @@ -826,6 +831,7 @@ sub show_menu_prompt { elsif($input eq "setup_bots"){ setup_bots(); $dc = 1; } elsif($input eq "linux_login_server_setup"){ do_linux_login_server_setup(); $dc = 1; } elsif($input eq "quest_heading_convert"){ quest_heading_convert(); $dc = 1; } + elsif($input eq "source_peq_db"){ fetch_peq_db_full(); $dc = 1; } elsif($input eq "exit"){ exit; } diff --git a/utils/scripts/linux_installer/install.sh b/utils/scripts/linux_installer/install.sh index 8f6f2f4d4..386503119 100644 --- a/utils/scripts/linux_installer/install.sh +++ b/utils/scripts/linux_installer/install.sh @@ -54,36 +54,39 @@ export apt_options="-y -qq" # Set autoconfirm and silent install ################################################################ -read -n1 -r -p "Press any key to continue..." key +if [ ! -f ./install_variables.txt ]; then -#::: Setting up user environment (eqemu) -echo "First, we need to set your passwords..." -echo "Make sure that you remember these and keep them somewhere" -echo "" -echo "" -groupadd eqemu -useradd -g eqemu -d $eqemu_server_directory eqemu -passwd eqemu + read -n1 -r -p "Press any key to continue..." key + + #::: Setting up user environment (eqemu) + echo "First, we need to set your passwords..." + echo "Make sure that you remember these and keep them somewhere" + echo "" + echo "" + groupadd eqemu + useradd -g eqemu -d $eqemu_server_directory eqemu + passwd eqemu -#::: Make server directory and go to it -mkdir $eqemu_server_directory -cd $eqemu_server_directory + #::: Make server directory and go to it + mkdir $eqemu_server_directory + cd $eqemu_server_directory -#::: Setup MySQL root user PW -read -p "Enter MySQL root (Database) password: " eqemu_db_root_password + #::: Setup MySQL root user PW + read -p "Enter MySQL root (Database) password: " eqemu_db_root_password -#::: Write install variables (later use) -echo "mysql_root:$eqemu_db_root_password" > install_variables.txt + #::: Write install variables (later use) + echo "mysql_root:$eqemu_db_root_password" > install_variables.txt -#::: Setup MySQL server -read -p "Enter Database Name (single word, no special characters, lower case):" eqemu_db_name -read -p "Enter (Database) MySQL EQEmu Server username: " eqemu_db_username -read -p "Enter (Database) MySQL EQEmu Server password: " eqemu_db_password + #::: Setup MySQL server + read -p "Enter Database Name (single word, no special characters, lower case):" eqemu_db_name + read -p "Enter (Database) MySQL EQEmu Server username: " eqemu_db_username + read -p "Enter (Database) MySQL EQEmu Server password: " eqemu_db_password -#::: Write install variables (later use) -echo "mysql_eqemu_db_name:$eqemu_db_name" >> install_variables.txt -echo "mysql_eqemu_user:$eqemu_db_username" >> install_variables.txt -echo "mysql_eqemu_password:$eqemu_db_password" >> install_variables.txt + #::: Write install variables (later use) + echo "mysql_eqemu_db_name:$eqemu_db_name" >> install_variables.txt + echo "mysql_eqemu_user:$eqemu_db_username" >> install_variables.txt + echo "mysql_eqemu_password:$eqemu_db_password" >> install_variables.txt +fi if [[ "$OS" == "Debian" ]]; then # Install pre-req packages diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 916833777..dae319641 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -379,8 +379,10 @@ 9123|2018_07_07_data_buckets.sql|SHOW TABLES LIKE 'data_buckets'|empty| 9124|2018_07_09_tasks.sql|SHOW COLUMNS FROM `tasks` LIKE 'type'|empty| 9125|2018_07_20_task_emote.sql|SHOW COLUMNS FROM `tasks` LIKE 'completion_emote'|empty| -9126|2018_08_13_inventory_version_update.sql|SHOW TABLES LIKE 'inventory_versions'|empty| -9127|2018_08_13_inventory_update.sql|SELECT * FROM `inventory_versions` WHERE `version` = 2 and `step` = 0|not_empty| +9126|2018_09_07_FastRegen.sql|SHOW COLUMNS FROM `zone` LIKE 'fast_regen_hp'|empty| +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_versions'|empty| +9129|2018_08_13_inventory_update.sql|SELECT * FROM `inventory_versions` WHERE `version` = 2 and `step` = 0|not_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_09_07_FastRegen.sql b/utils/sql/git/required/2018_09_07_FastRegen.sql new file mode 100644 index 000000000..d9c52acf7 --- /dev/null +++ b/utils/sql/git/required/2018_09_07_FastRegen.sql @@ -0,0 +1,3 @@ +ALTER TABLE `zone` ADD `fast_regen_hp` INT NOT NULL DEFAULT '180'; +ALTER TABLE `zone` ADD `fast_regen_mana` INT NOT NULL DEFAULT '180'; +ALTER TABLE `zone` ADD `fast_regen_endurance` INT NOT NULL DEFAULT '180'; diff --git a/utils/sql/git/required/2018_09_07_NPCMaxAggroDist.sql b/utils/sql/git/required/2018_09_07_NPCMaxAggroDist.sql new file mode 100644 index 000000000..239a483a5 --- /dev/null +++ b/utils/sql/git/required/2018_09_07_NPCMaxAggroDist.sql @@ -0,0 +1 @@ +ALTER TABLE `zone` ADD `npc_max_aggro_dist` INT NOT NULL DEFAULT '600'; diff --git a/utils/sql/system_tables.txt b/utils/sql/system_tables.txt index 7816fea21..a5692dabd 100644 --- a/utils/sql/system_tables.txt +++ b/utils/sql/system_tables.txt @@ -1,11 +1,10 @@ aa_ability aa_actions aa_effects -aa_required_level_cost -aa_ranks aa_rank_effects aa_rank_prereqs -task_activities +aa_ranks +aa_required_level_cost adventure_template adventure_template_entry adventure_template_entry_flavor @@ -18,6 +17,7 @@ char_create_combinations char_create_point_allocations class_skill damageshieldtypes +data_buckets doors faction_list faction_list_mod @@ -33,38 +33,39 @@ ground_spawns horses instance_list items -ldon_trap_templates ldon_trap_entries +ldon_trap_templates level_exp_mods +logsys_categories lootdrop lootdrop_entries loottable loottable_entries merc_armorinfo -merc_weaponinfo -merc_stats +merc_buffs +merc_inventory merc_merchant_entries merc_merchant_template_entries merc_merchant_templates -merc_stance_entries -merc_templates -merc_npc_types merc_name_types -merc_subtypes -merc_types +merc_npc_types merc_spell_list_entries merc_spell_lists -merc_buffs -mercs -merc_inventory +merc_stance_entries +merc_stats +merc_subtypes +merc_templates +merc_types +merc_weaponinfo merchantlist +mercs npc_emotes npc_faction npc_faction_entries npc_spells -npc_spells_entries npc_spells_effects npc_spells_effects_entries +npc_spells_entries npc_types npc_types_metadata npc_types_tint @@ -77,14 +78,15 @@ races saylink skill_caps spawn2 -spawn_conditions spawn_condition_values +spawn_conditions spawn_events spawnentry spawngroup spells_new start_zones starting_items +task_activities tasks tasksets titles diff --git a/world/clientlist.cpp b/world/clientlist.cpp index 3f1c3f50c..eb821b22f 100644 --- a/world/clientlist.cpp +++ b/world/clientlist.cpp @@ -530,6 +530,28 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S //uint32 x = 0; int whomlen = 0; if (whom) { + // fixes for client converting some queries into a race query instead of zone + if (whom->wrace == 221) { + whom->wrace = 0xFFFF; + strcpy(whom->whom, "scarlet"); + } + if (whom->wrace == 327) { + whom->wrace = 0xFFFF; + strcpy(whom->whom, "crystal"); + } + if (whom->wrace == 103) { + whom->wrace = 0xFFFF; + strcpy(whom->whom, "kedge"); + } + if (whom->wrace == 230) { + whom->wrace = 0xFFFF; + strcpy(whom->whom, "akheva"); + } + if (whom->wrace == 229) { + whom->wrace = 0xFFFF; + strcpy(whom->whom, "netherbian"); + } + whomlen = strlen(whom->whom); if(whom->wrace == 0x001A) // 0x001A is the old Froglok race number and is sent by the client for /who all froglok whom->wrace = FROGLOK; // This is what EQEmu uses for the Froglok Race number. diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 263aeee2f..7d638d100 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1295,6 +1295,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_QSPlayerLogDeletes: case ServerOP_QSPlayerLogMoves: case ServerOP_QSPlayerLogMerchantTransactions: + case ServerOP_QSPlayerDropItem: { QSLink.SendPacket(pack); break; diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 91d14498f..ec7f61f1a 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -107,16 +107,10 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) { float iAggroRange = GetAggroRange(); float t1, t2, t3; - t1 = mob->GetX() - GetX(); - t2 = mob->GetY() - GetY(); - t3 = mob->GetZ() - GetZ(); - //Cheap ABS() - if(t1 < 0) - t1 = 0 - t1; - if(t2 < 0) - t2 = 0 - t2; - if(t3 < 0) - t3 = 0 - t3; + t1 = std::abs(mob->GetX() - GetX()); + t2 = std::abs(mob->GetY() - GetY()); + t3 = std::abs(mob->GetZ() - GetZ()); + if(( t1 > iAggroRange) || ( t2 > iAggroRange) || ( t3 > iAggroRange) ) { @@ -271,16 +265,10 @@ bool Mob::CheckWillAggro(Mob *mob) { // Image: I moved this up by itself above faction and distance checks because if one of these return true, theres no reason to go through the other information float t1, t2, t3; - t1 = mob->GetX() - GetX(); - t2 = mob->GetY() - GetY(); - t3 = mob->GetZ() - GetZ(); - //Cheap ABS() - if(t1 < 0) - t1 = 0 - t1; - if(t2 < 0) - t2 = 0 - t2; - if(t3 < 0) - t3 = 0 - t3; + t1 = std::abs(mob->GetX() - GetX()); + t2 = std::abs(mob->GetY() - GetY()); + t3 = std::abs(mob->GetZ() - GetZ()); + if(( t1 > iAggroRange) || ( t2 > iAggroRange) || ( t3 > iAggroRange) @@ -424,7 +412,7 @@ Mob* EntityList::AICheckNPCtoNPCAggro(Mob* sender, float iAggroRange, float iAss return nullptr; } -int EntityList::GetHatedCount(Mob *attacker, Mob *exclude) +int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) { // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker if (!attacker) @@ -434,20 +422,25 @@ int EntityList::GetHatedCount(Mob *attacker, Mob *exclude) for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; - if (!mob || (mob == exclude)) + if (!mob || (mob == exclude)) { continue; + } - if (!mob->IsEngaged()) + if (!mob->IsEngaged()) { continue; + } - if (mob->IsFeared() || mob->IsMezzed()) + if (mob->IsFeared() || mob->IsMezzed()) { continue; + } - if (attacker->GetLevelCon(mob->GetLevel()) == CON_GRAY) + if (!inc_gray_con && attacker->GetLevelCon(mob->GetLevel()) == CON_GRAY) { continue; + } - if (!mob->CheckAggro(attacker)) + if (!mob->CheckAggro(attacker)) { continue; + } float AggroRange = mob->GetAggroRange(); @@ -455,14 +448,12 @@ int EntityList::GetHatedCount(Mob *attacker, Mob *exclude) AggroRange *= AggroRange; - if (DistanceSquared(mob->GetPosition(), attacker->GetPosition()) > AggroRange) + if (DistanceSquared(mob->GetPosition(), attacker->GetPosition()) > AggroRange) { continue; - + } Count++; } - return Count; - } void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { @@ -523,7 +514,7 @@ void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { Log(Logs::General, Logs::None, "AIYellForHelp(\"%s\",\"%s\") %s attacking %s Dist %f Z %f", sender->GetName(), attacker->GetName(), mob->GetName(), attacker->GetName(), DistanceSquared(mob->GetPosition(), - sender->GetPosition()), fabs(sender->GetZ()+mob->GetZ())); + sender->GetPosition()), std::abs(sender->GetZ()+mob->GetZ())); #endif mob->AddToHateList(attacker, 25, 0, false); sender->AddAssistCap(); diff --git a/zone/attack.cpp b/zone/attack.cpp index a48d68934..d6cf4227f 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -5455,3 +5455,11 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) } } } + +bool Mob::GetWasSpawnedInWater() const { + return spawned_in_water; +} + +void Mob::SetSpawnedInWater(bool spawned_in_water) { + Mob::spawned_in_water = spawned_in_water; +} diff --git a/zone/bot.cpp b/zone/bot.cpp index f0b28f12e..aafea1e6c 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7014,9 +7014,9 @@ void Bot::CalcRestState() { } } - RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP)); - RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana)); - RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd)); + RestRegenHP = 6 * (GetMaxHP() / zone->newzone_data.FastRegenHP); + RestRegenMana = 6 * (GetMaxMana() / zone->newzone_data.FastRegenMana); + RestRegenEndurance = 6 * (GetMaxEndurance() / zone->newzone_data.FastRegenEndurance); } int32 Bot::LevelRegen() { diff --git a/zone/client.cpp b/zone/client.cpp index cd5dbbea1..b251a72d4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -8081,9 +8081,9 @@ void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, ui { //The ole switcheroo if (npc_value[i] > 0) - npc_value[i] = -abs(npc_value[i]); + npc_value[i] = -std::abs(npc_value[i]); else if (npc_value[i] < 0) - npc_value[i] = abs(npc_value[i]); + npc_value[i] = std::abs(npc_value[i]); } // Adjust the amount you can go up or down so the resulting range diff --git a/zone/client.h b/zone/client.h index ead552f73..393ede129 100644 --- a/zone/client.h +++ b/zone/client.h @@ -876,6 +876,7 @@ public: void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); void DropItem(int16 slot_id, bool recurse = true); + void DropItemQS(EQEmu::ItemInstance* inst, bool pickup); int GetItemLinkHash(const EQEmu::ItemInstance* inst); // move to ItemData..or make use of the pre-calculated database field diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index f342fff9f..39c37c874 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -290,9 +290,8 @@ int32 Client::CalcHPRegen(bool bCombat) // another check for IsClient && !(base + item_regen) && Cur_HP <= 0 do --base; do later if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { - auto fast_mod = RuleI(Character, RestRegenHP); // TODO: this is actually zone based auto max_hp = GetMaxHP(); - int fast_regen = 6 * (max_hp / fast_mod); + int fast_regen = 6 * (max_hp / zone->newzone_data.FastRegenHP); if (base < fast_regen) // weird, but what the client is doing base = fast_regen; } @@ -1296,9 +1295,8 @@ int32 Client::CalcManaRegen(bool bCombat) regen = regen * 100.0f * AreaManaRegen * 0.01f + 0.5f; if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { - auto fast_mod = RuleI(Character, RestRegenMana); // TODO: this is actually zone based auto max_mana = GetMaxMana(); - int fast_regen = 6 * (max_mana / fast_mod); + int fast_regen = 6 * (max_mana / zone->newzone_data.FastRegenMana); if (regen < fast_regen) // weird, but what the client is doing regen = fast_regen; } @@ -2264,9 +2262,8 @@ int32 Client::CalcEnduranceRegen(bool bCombat) int regen = base; if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { - auto fast_mod = RuleI(Character, RestRegenEnd); // TODO: this is actually zone based auto max_end = GetMaxEndurance(); - int fast_regen = 6 * (max_end / fast_mod); + int fast_regen = 6 * (max_end / zone->newzone_data.FastRegenEndurance); if (aa_regen < fast_regen) // weird, but what the client is doing aa_regen = fast_regen; } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index f9e4afefd..97db51286 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -2950,7 +2950,11 @@ void Client::Handle_OP_Assist(const EQApplicationPacket *app) Distance(m_Position, assistee->GetPosition()) <= TARGETING_RANGE)) { SetAssistExemption(true); eid->entity_id = new_target->GetID(); + } else { + eid->entity_id = 0; } + } else { + eid->entity_id = 0; } } diff --git a/zone/entity.cpp b/zone/entity.cpp index 264b8cfc9..2523c7d96 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -39,6 +39,7 @@ #include "raids.h" #include "string_ids.h" #include "worldserver.h" +#include "water_map.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -686,6 +687,15 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) } } + /** + * Set whether NPC was spawned in or out of water + */ + if (zone->HasMap() && zone->HasWaterMap()) { + npc->SetSpawnedInWater(false); + if (zone->watermap->InLiquid(npc->GetPosition())) { + npc->SetSpawnedInWater(true); + } + } } void EntityList::AddMerc(Merc *merc, bool SendSpawnPacket, bool dontqueue) @@ -3425,52 +3435,54 @@ void EntityList::ProcessMove(Client *c, const glm::vec3& location) } } -void EntityList::ProcessMove(NPC *n, float x, float y, float z) -{ +void EntityList::ProcessMove(NPC *n, float x, float y, float z) { float last_x = n->GetX(); float last_y = n->GetY(); float last_z = n->GetZ(); std::list events; + for (auto iter = area_list.begin(); iter != area_list.end(); ++iter) { - Area& a = (*iter); + + Area &a = (*iter); bool old_in = true; bool new_in = true; if (last_x < a.min_x || last_x > a.max_x || - last_y < a.min_y || last_y > a.max_y || - last_z < a.min_z || last_z > a.max_z) { + last_y < a.min_y || last_y > a.max_y || + last_z < a.min_z || last_z > a.max_z) { old_in = false; } if (x < a.min_x || x > a.max_x || - y < a.min_y || y > a.max_y || - z < a.min_z || z > a.max_z) { + y < a.min_y || y > a.max_y || + z < a.min_z || z > a.max_z) { new_in = false; } if (old_in && !new_in) { //were in but are no longer. quest_proximity_event evt; - evt.event_id = EVENT_LEAVE_AREA; - evt.client = nullptr; - evt.npc = n; - evt.area_id = a.id; + evt.event_id = EVENT_LEAVE_AREA; + evt.client = nullptr; + evt.npc = n; + evt.area_id = a.id; evt.area_type = a.type; events.push_back(evt); - } else if (!old_in && new_in) { + } + else if (!old_in && new_in) { //were not in but now are quest_proximity_event evt; - evt.event_id = EVENT_ENTER_AREA; - evt.client = nullptr; - evt.npc = n; - evt.area_id = a.id; + evt.event_id = EVENT_ENTER_AREA; + evt.client = nullptr; + evt.npc = n; + evt.area_id = a.id; evt.area_type = a.type; events.push_back(evt); } } for (auto iter = events.begin(); iter != events.end(); ++iter) { - quest_proximity_event& evt = (*iter); + quest_proximity_event &evt = (*iter); std::vector args; args.push_back(&evt.area_id); args.push_back(&evt.area_type); diff --git a/zone/entity.h b/zone/entity.h index f296a8cb4..f32532392 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -416,7 +416,7 @@ public: void CheckClientAggro(Client *around); Mob* AICheckNPCtoNPCAggro(Mob* sender, float iAggroRange, float iAssistRange); - int GetHatedCount(Mob *attacker, Mob *exclude); + int GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con); void AIYellForHelp(Mob* sender, Mob* attacker); bool AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint32 iSpellTypes); bool Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes); diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index 0593cbee2..c73ec448e 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -31,87 +31,117 @@ extern Zone* zone; //this is called whenever we are damaged to process possible fleeing void Mob::CheckFlee() { - //if were allready fleeing, dont need to check more... - if(flee_mode && currently_fleeing) + + // if mob is dead why would you run? + if(GetHP() == 0) { return; + } + + // if were already fleeing, don't need to check more... + if(flee_mode && currently_fleeing) { + return; + } //dont bother if we are immune to fleeing - if(GetSpecialAbility(IMMUNE_FLEEING) || spellbonuses.ImmuneToFlee) + if(GetSpecialAbility(IMMUNE_FLEEING) || spellbonuses.ImmuneToFlee) { return; + } - if(!flee_timer.Check()) - return; //only do all this stuff every little while, since - //its not essential that we start running RIGHT away - - //see if were possibly hurt enough - float ratio = GetHPRatio(); - float fleeratio = GetSpecialAbility(FLEE_PERCENT); - fleeratio = fleeratio > 0 ? fleeratio : RuleI(Combat, FleeHPRatio); - - if(ratio >= fleeratio) + // Check if Flee Timer is cleared + if(!flee_timer.Check()) { return; + } - //we might be hurt enough, check con now.. + int hpratio = GetIntHPRatio(); + int fleeratio = GetSpecialAbility(FLEE_PERCENT); // if a special flee_percent exists Mob *hate_top = GetHateTop(); + + // Sanity Check for race conditions + if(hate_top == nullptr) { + return; + } + + // If no special flee_percent check for Gray or Other con rates + if(GetLevelCon(hate_top->GetLevel(), GetLevel()) == CON_GRAY && fleeratio == 0 && RuleB(Combat, FleeGray)) { + fleeratio = RuleI(Combat, FleeGrayHPRatio); + } else if(fleeratio == 0) { + fleeratio = RuleI(Combat, FleeHPRatio ); + } + + // Mob does not have low enough health to flee + if(hpratio >= fleeratio) { + return; + } + + // Sanity Check this should never happen... if(!hate_top) { - //this should never happen... StartFleeing(); return; } - float other_ratio = hate_top->GetHPRatio(); + int other_ratio = hate_top->GetIntHPRatio(); + // If the Client is nearing death the NPC will not flee and instead try to kill the client. if(other_ratio < 20) { - //our hate top is almost dead too... stay and fight return; } - //base our flee ratio on our con. this is how the - //attacker sees the mob, since this is all we can observe + // Flee Chance checking based on con. uint32 con = GetLevelCon(hate_top->GetLevel(), GetLevel()); - float run_ratio; + int flee_chance; switch(con) { //these values are not 100% researched case CON_GRAY: - run_ratio = fleeratio; + flee_chance = 100; break; case CON_GREEN: - run_ratio = fleeratio * 9 / 10; + flee_chance = 90; break; case CON_LIGHTBLUE: - run_ratio = fleeratio * 9 / 10; + flee_chance = 90; break; case CON_BLUE: - run_ratio = fleeratio * 8 / 10; + flee_chance = 80; break; default: - run_ratio = fleeratio * 7 / 10; + flee_chance = 70; break; } - if(ratio < run_ratio) - { - if (RuleB(Combat, FleeIfNotAlone) || - GetSpecialAbility(ALWAYS_FLEE) || - (!RuleB(Combat, FleeIfNotAlone) && (entity_list.GetHatedCount(hate_top, this) == 0))) - StartFleeing(); + + // If we got here we are allowed to roll on flee chance if there is not other hated NPC's in the area. + + if(RuleB(Combat, FleeIfNotAlone) || GetSpecialAbility(ALWAYS_FLEE) || zone->random.Roll(flee_chance) && entity_list.GetHatedCount(hate_top, this, true) == 0) { + currently_fleeing = true; + StartFleeing(); } } + void Mob::ProcessFlee() { //Stop fleeing if effect is applied after they start to run. //When ImmuneToFlee effect fades it will turn fear back on and check if it can still flee. if (flee_mode && (GetSpecialAbility(IMMUNE_FLEEING) || spellbonuses.ImmuneToFlee) && - !spellbonuses.IsFeared && !spellbonuses.IsBlind) { + !spellbonuses.IsFeared && !spellbonuses.IsBlind) { currently_fleeing = false; return; } - //see if we are still dying, if so, do nothing - float fleeratio = GetSpecialAbility(FLEE_PERCENT); - fleeratio = fleeratio > 0 ? fleeratio : RuleI(Combat, FleeHPRatio); - if (GetHPRatio() < fleeratio) + int hpratio = GetIntHPRatio(); + int fleeratio = GetSpecialAbility(FLEE_PERCENT); // if a special flee_percent exists + Mob *hate_top = GetHateTop(); + + // If no special flee_percent check for Gray or Other con rates + if(hate_top != nullptr && GetLevelCon(hate_top->GetLevel(), GetLevel()) == CON_GRAY && fleeratio == 0 && RuleB(Combat, FleeGray)) { + fleeratio = RuleI(Combat, FleeGrayHPRatio); + } else if(fleeratio == 0) { + fleeratio = RuleI(Combat, FleeHPRatio ); + } + + // Mob is still too low. Keep Running + if(hpratio < fleeratio) { return; + } //we are not dying anymore... see what we do next @@ -125,42 +155,43 @@ void Mob::ProcessFlee() } } -void Mob::CalculateNewFearpoint() -{ - if(RuleB(Pathing, Fear) && zone->pathing) - { +void Mob::CalculateNewFearpoint() { + if (RuleB(Pathing, Fear) && zone->pathing) { auto Node = zone->pathing->GetRandomLocation(); if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) { ++Node.z; - m_FearWalkTarget = Node; + m_FearWalkTarget = Node; + currently_fleeing = true; + return; } - Log(Logs::Detail, Logs::None, "No path found to selected node. Falling through to old fear point selection."); + Log(Logs::Detail, + Logs::Pathing, + "No path found to selected node. Falling through to old fear point selection."); } - int loop = 0; + int loop = 0; float ranx, rany, ranz; currently_fleeing = true; while (loop < 100) //Max 100 tries { - int ran = 250 - (loop*2); + int ran = 250 - (loop * 2); loop++; - ranx = GetX()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1); - rany = GetY()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1); - ranz = FindGroundZ(ranx,rany); + ranx = GetX() + zone->random.Int(0, ran - 1) - zone->random.Int(0, ran - 1); + rany = GetY() + zone->random.Int(0, ran - 1) - zone->random.Int(0, ran - 1); + ranz = FindGroundZ(ranx, rany); if (ranz == BEST_Z_INVALID) continue; float fdist = ranz - GetZ(); - if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(),GetY(),GetZ(),ranx,rany,ranz)) - { + if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(), GetY(), GetZ(), ranx, rany, ranz)) { break; } } if (currently_fleeing) - m_FearWalkTarget = glm::vec3(ranx, rany, ranz); + m_FearWalkTarget = glm::vec3(ranx, rany, ranz); } diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index f02cbe6b6..5649c0da4 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -575,12 +575,19 @@ int HateList::AreaRampage(Mob *caster, Mob *target, int count, ExtraAttackOption if (!target || !caster) return 0; + // tank will be hit ONLY if they are the only target on the hate list + // if there is anyone else on the hate list, the tank will not be hit, even if those others aren't hit either + if (list.size() == 1) { + caster->ProcessAttackRounds(target, opts); + return 1; + } + int hit_count = 0; // This should prevent crashes if something dies (or mainly more than 1 thing goes away) // This is a temp solution until the hate lists can be rewritten to not have that issue std::vector id_list; for (auto &h : list) { - if (h->entity_on_hatelist && h->entity_on_hatelist != caster && + if (h->entity_on_hatelist && h->entity_on_hatelist != caster && h->entity_on_hatelist != target && caster->CombatRange(h->entity_on_hatelist)) id_list.push_back(h->entity_on_hatelist->GetID()); if (count != -1 && id_list.size() > count) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index fb88fe03e..24fa86b30 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -596,15 +596,15 @@ void Client::DropItem(int16 slot_id, bool recurse) if (LogSys.log_settings[Logs::Inventory].is_category_enabled) { Log(Logs::General, Logs::Inventory, "DropItem() Hack detected - full item parse:"); Log(Logs::General, Logs::Inventory, "depth: 0, Item: '%s' (id: %u), IsDroppable: %s", - (invalid_drop->GetItem() ? invalid_drop->GetItem()->Name : "null data"), invalid_drop->GetID(), invalid_drop->IsDroppable(false)); + (invalid_drop->GetItem() ? invalid_drop->GetItem()->Name : "null data"), invalid_drop->GetID(), (invalid_drop->IsDroppable(false) ? "true" : "false")); for (auto iter1 : *invalid_drop->GetContents()) { // depth 1 Log(Logs::General, Logs::Inventory, "-depth: 1, Item: '%s' (id: %u), IsDroppable: %s", - (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), iter1.second->IsDroppable(false)); + (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), (iter1.second->IsDroppable(false) ? "true" : "false")); for (auto iter2 : *iter1.second->GetContents()) { // depth 2 Log(Logs::General, Logs::Inventory, "--depth: 2, Item: '%s' (id: %u), IsDroppable: %s", - (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), iter2.second->IsDroppable(false)); + (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), (iter2.second->IsDroppable(false) ? "true" : "false")); } } } @@ -622,21 +622,21 @@ void Client::DropItem(int16 slot_id, bool recurse) if (LogSys.log_settings[Logs::Inventory].is_category_enabled) { Log(Logs::General, Logs::Inventory, "DropItem() Processing - full item parse:"); Log(Logs::General, Logs::Inventory, "depth: 0, Item: '%s' (id: %u), IsDroppable: %s", - (inst->GetItem() ? inst->GetItem()->Name : "null data"), inst->GetID(), inst->IsDroppable(false)); + (inst->GetItem() ? inst->GetItem()->Name : "null data"), inst->GetID(), (inst->IsDroppable(false) ? "true" : "false")); if (!inst->IsDroppable(false)) Log(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); for (auto iter1 : *inst->GetContents()) { // depth 1 Log(Logs::General, Logs::Inventory, "-depth: 1, Item: '%s' (id: %u), IsDroppable: %s", - (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), iter1.second->IsDroppable(false)); + (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), (iter1.second->IsDroppable(false) ? "true" : "false")); if (!iter1.second->IsDroppable(false)) Log(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); for (auto iter2 : *iter1.second->GetContents()) { // depth 2 Log(Logs::General, Logs::Inventory, "--depth: 2, Item: '%s' (id: %u), IsDroppable: %s", - (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), iter2.second->IsDroppable(false)); + (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), (iter2.second->IsDroppable(false) ? "true" : "false")); if (!iter2.second->IsDroppable(false)) Log(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); @@ -674,10 +674,79 @@ void Client::DropItem(int16 slot_id, bool recurse) object->StartDecay(); Log(Logs::General, Logs::Inventory, "Item drop handled ut assolet"); + DropItemQS(inst, false); safe_delete(inst); } +void Client::DropItemQS(EQEmu::ItemInstance* inst, bool pickup) { + if (RuleB(QueryServ, PlayerDropItems)) { + QSPlayerDropItem_Struct qs_audit; + std::list event_details; + memset(&qs_audit, 0, sizeof(QSPlayerDropItem_Struct)); + + qs_audit.char_id = this->character_id; + qs_audit.pickup = pickup; + qs_audit.zone_id = this->GetZoneID(); + qs_audit.x = (int) this->GetX(); + qs_audit.y = (int) this->GetY(); + qs_audit.z = (int) this->GetZ(); + + if (inst) { + auto detail = new QSDropItems_Struct; + detail->item_id = inst->GetID(); + detail->charges = inst->IsClassBag() ? 1 : inst->GetCharges(); + detail->aug_1 = inst->GetAugmentItemID(1); + detail->aug_2 = inst->GetAugmentItemID(2); + detail->aug_3 = inst->GetAugmentItemID(3); + detail->aug_4 = inst->GetAugmentItemID(4); + detail->aug_5 = inst->GetAugmentItemID(5); + event_details.push_back(detail); + + if (inst->IsClassBag()) { + for (uint8 sub_slot = EQEmu::invbag::SLOT_BEGIN; (sub_slot <= EQEmu::invbag::SLOT_END); ++sub_slot) { // this is to catch ALL items + const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot); + if (bag_inst) { + detail = new QSDropItems_Struct; + detail->item_id = bag_inst->GetID(); + detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); + detail->aug_1 = bag_inst->GetAugmentItemID(1); + detail->aug_2 = bag_inst->GetAugmentItemID(2); + detail->aug_3 = bag_inst->GetAugmentItemID(3); + detail->aug_4 = bag_inst->GetAugmentItemID(4); + detail->aug_5 = bag_inst->GetAugmentItemID(5); + event_details.push_back(detail); + } + } + } + } + qs_audit._detail_count = event_details.size(); + + auto qs_pack = new ServerPacket( + ServerOP_QSPlayerDropItem, + sizeof(QSPlayerDropItem_Struct) + + (sizeof(QSDropItems_Struct) * qs_audit._detail_count)); + QSPlayerDropItem_Struct* qs_buf = (QSPlayerDropItem_Struct*) qs_pack->pBuffer; + + memcpy(qs_buf, &qs_audit, sizeof(QSPlayerDropItem_Struct)); + + int offset = 0; + + for (auto iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSDropItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); + } + + event_details.clear(); + + if (worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); + } +} + // Drop inst void Client::DropInst(const EQEmu::ItemInstance* inst) { diff --git a/zone/lua_raid.cpp b/zone/lua_raid.cpp index 295fe0220..93c1f4c28 100644 --- a/zone/lua_raid.cpp +++ b/zone/lua_raid.cpp @@ -32,12 +32,12 @@ int Lua_Raid::RaidCount() { return self->RaidCount(); } -uint32 Lua_Raid::GetGroup(const char *c) { +int Lua_Raid::GetGroup(const char *c) { Lua_Safe_Call_Int(); return self->GetGroup(c); } -uint32 Lua_Raid::GetGroup(Lua_Client c) { +int Lua_Raid::GetGroup(Lua_Client c) { Lua_Safe_Call_Int(); return self->GetGroup(c); } @@ -122,6 +122,16 @@ Lua_Client Lua_Raid::GetMember(int index) { return self->members[index].member; } +int Lua_Raid::GetGroupNumber(int index) { + Lua_Safe_Call_Int(); + + if(index >= 72 || index < 0 || self->members[index].GroupNumber == RAID_GROUPLESS) { + return -1; + } + + return self->members[index].GroupNumber; +} + luabind::scope lua_register_raid() { return luabind::class_("Raid") @@ -132,7 +142,8 @@ luabind::scope lua_register_raid() { .def("CastGroupSpell", (void(Lua_Raid::*)(Lua_Mob,int,uint32))&Lua_Raid::CastGroupSpell) .def("GroupCount", (int(Lua_Raid::*)(uint32))&Lua_Raid::GroupCount) .def("RaidCount", (int(Lua_Raid::*)(void))&Lua_Raid::RaidCount) - .def("GetGroup", (uint32(Lua_Raid::*)(const char*))&Lua_Raid::GetGroup) + .def("GetGroup", (int(Lua_Raid::*)(const char*))&Lua_Raid::GetGroup) + .def("GetGroup", (int(Lua_Raid::*)(Lua_Client))&Lua_Raid::GetGroup) .def("SplitExp", (void(Lua_Raid::*)(uint32,Lua_Mob))&Lua_Raid::SplitExp) .def("GetTotalRaidDamage", (uint32(Lua_Raid::*)(Lua_Mob))&Lua_Raid::GetTotalRaidDamage) .def("SplitMoney", (void(Lua_Raid::*)(uint32,uint32,uint32,uint32))&Lua_Raid::SplitMoney) @@ -146,7 +157,8 @@ luabind::scope lua_register_raid() { .def("TeleportGroup", (int(Lua_Raid::*)(Lua_Mob,uint32,uint32,float,float,float,float,uint32))&Lua_Raid::TeleportGroup) .def("TeleportRaid", (int(Lua_Raid::*)(Lua_Mob,uint32,uint32,float,float,float,float))&Lua_Raid::TeleportRaid) .def("GetID", (int(Lua_Raid::*)(void))&Lua_Raid::GetID) - .def("GetMember", (Lua_Client(Lua_Raid::*)(int))&Lua_Raid::GetMember); + .def("GetMember", (Lua_Client(Lua_Raid::*)(int))&Lua_Raid::GetMember) + .def("GetGroupNumber", (int(Lua_Raid::*)(int))&Lua_Raid::GetGroupNumber); } #endif diff --git a/zone/lua_raid.h b/zone/lua_raid.h index cc88a1c3d..626b98bb2 100644 --- a/zone/lua_raid.h +++ b/zone/lua_raid.h @@ -30,8 +30,8 @@ public: void CastGroupSpell(Lua_Mob caster, int spell_id, uint32 group_id); int GroupCount(uint32 group_id); int RaidCount(); - uint32 GetGroup(const char *c); - uint32 GetGroup(Lua_Client c); + int GetGroup(const char *c); + int GetGroup(Lua_Client c); void SplitExp(uint32 exp, Lua_Mob other); uint32 GetTotalRaidDamage(Lua_Mob other); void SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum); @@ -47,6 +47,7 @@ public: void TeleportRaid(Lua_Mob sender, uint32 zone_id, uint32 instance_id, float x, float y, float z, float h); int GetID(); Lua_Client GetMember(int index); + int GetGroupNumber(int index); }; #endif diff --git a/zone/merc.cpp b/zone/merc.cpp index 7e660f82b..355a2c525 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1168,11 +1168,11 @@ void Merc::CalcRestState() { } } - RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP)); + RestRegenHP = 6 * (GetMaxHP() / zone->newzone_data.FastRegenHP); - RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana)); + RestRegenMana = 6 * (GetMaxMana() / zone->newzone_data.FastRegenMana); - RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd)); + RestRegenEndurance = 6 * (GetMaxEndurance() / zone->newzone_data.FastRegenEndurance); } bool Merc::HasSkill(EQEmu::skills::SkillType skill_id) const { @@ -1408,7 +1408,7 @@ void Merc::AI_Process() { if(DivineAura()) return; - int hateCount = entity_list.GetHatedCount(this, nullptr); + int hateCount = entity_list.GetHatedCount(this, nullptr, false); if(GetHatedCount() < hateCount) { SetHatedCount(hateCount); @@ -1475,8 +1475,14 @@ void Merc::AI_Process() { if (RuleB(Mercs, MercsUsePathing) && zone->pathing) { bool WaypointChanged, NodeReached; - glm::vec3 Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), - GetRunspeed(), WaypointChanged, NodeReached); + glm::vec3 Goal = UpdatePath( + GetTarget()->GetX(), + GetTarget()->GetY(), + GetTarget()->GetZ(), + GetRunspeed(), + WaypointChanged, + NodeReached + ); if (WaypointChanged) tar_ndx = 20; @@ -1582,7 +1588,7 @@ void Merc::AI_Process() { } } - if(IsMoving()) + if (IsMoving()) SendPositionUpdate(); else SendPosition(); diff --git a/zone/mob.h b/zone/mob.h index 422c76cd3..4dafe2bf9 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -981,15 +981,15 @@ public: inline bool CheckAggro(Mob* other) {return hate_list.IsEntOnHateList(other);} float CalculateHeadingToTarget(float in_x, float in_y) { return HeadingAngleToMob(in_x, in_y); } - virtual bool CalculateNewPosition(float x, float y, float z, int speed, bool checkZ = true, bool calcheading = true); + virtual bool CalculateNewPosition(float x, float y, float z, float speed, bool check_z = true, bool calculate_heading = true); float CalculateDistance(float x, float y, float z); float GetGroundZ(float new_x, float new_y, float z_offset=0.0); void SendTo(float new_x, float new_y, float new_z); void SendToFixZ(float new_x, float new_y, float new_z); float GetZOffset() const; float GetDefaultRaceSize() const; - void FixZ(int32 z_find_offset = 5); - float GetFixedZ(glm::vec3 position, int32 z_find_offset = 5); + void FixZ(int32 z_find_offset = 5, bool fix_client_z = false); + float GetFixedZ(glm::vec3 destination, int32 z_find_offset = 5); void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } @@ -1165,7 +1165,7 @@ protected: int _GetWalkSpeed() const; int _GetRunSpeed() const; int _GetFearSpeed() const; - virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ = true, bool calcHeading = true); + virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, bool check_z = true, bool calculate_heading = true); virtual bool AI_EngagedCastCheck() { return(false); } virtual bool AI_PursueCastCheck() { return(false); } @@ -1414,6 +1414,13 @@ protected: bool pseudo_rooted; bool endur_upkeep; bool degenerating_effects; // true if we have a buff that needs to be recalced every tick + bool spawned_in_water; +public: + bool GetWasSpawnedInWater() const; + + void SetSpawnedInWater(bool spawned_in_water); + +protected: // Bind wound Timer bindwound_timer; @@ -1443,7 +1450,7 @@ protected: std::unique_ptr AI_feign_remember_timer; std::unique_ptr AI_check_signal_timer; std::unique_ptr AI_scan_door_open_timer; - uint32 pLastFightingDelayMoving; + uint32 time_until_can_move; HateList hate_list; std::set feign_memory_list; // This is to keep track of mobs we cast faction mod spells on diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 154575c40..766929c97 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -30,6 +30,7 @@ #include "string_ids.h" #include "water_map.h" #include "fastmath.h" +#include "../common/data_verification.h" #include #include @@ -199,11 +200,15 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates case SpellType_Escape: { // If min_hp !=0 then the spell list has specified // custom range and we're inside that range if we - // made it here. The hard coded <=5 is for unspecified. - if (AIspells[i].min_hp != 0 || GetHPRatio() <= 5) - { - AIDoSpellCast(i, tar, mana_cost); - return true; + // made it here. + if (AIspells[i].min_hp != 0 || GetHPRatio() <= (RuleI(NPC, NPCGatePercent))) { + auto npcSpawnPoint = CastToNPC()->GetSpawnPoint(); + if (!RuleB(NPC, NPCGateNearBind) && DistanceNoZ(m_Position, npcSpawnPoint) < RuleI(NPC, NPCGateDistanceBind)) { + break; + } else { + AIDoSpellCast(i, tar, mana_cost); + return true; + } } break; } @@ -459,8 +464,9 @@ void NPC::AI_Init() roambox_min_x = 0; roambox_min_y = 0; roambox_distance = 0; - roambox_movingto_x = 0; - roambox_movingto_y = 0; + roambox_destination_x = 0; + roambox_destination_y = 0; + roambox_destination_z = 0; roambox_min_delay = 2500; roambox_delay = 2500; } @@ -476,9 +482,9 @@ void Mob::AI_Start(uint32 iMoveDelay) { return; if (iMoveDelay) - pLastFightingDelayMoving = Timer::GetCurrentTime() + iMoveDelay; + time_until_can_move = Timer::GetCurrentTime() + iMoveDelay; else - pLastFightingDelayMoving = 0; + time_until_can_move = 0; pAIControlled = true; AI_think_timer = std::unique_ptr(new Timer(AIthink_duration)); @@ -774,44 +780,50 @@ void Client::AI_Process() } } - if(RuleB(Combat, EnableFearPathing)){ - if(currently_fleeing) { + if (RuleB(Combat, EnableFearPathing)) { + if (currently_fleeing) { - if (fix_z_timer_engaged.Check()) - this->FixZ(); + if (fix_z_timer.Check()) + this->FixZ(5, true); - if(IsRooted()) { + if (IsRooted()) { //make sure everybody knows were not moving, for appearance sake - if(IsMoving()) - { - if(GetTarget()) + if (IsMoving()) { + if (GetTarget()) SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); SetCurrentSpeed(0); } //continue on to attack code, ensuring that we execute the engaged code engaged = true; - } else { - if(AI_movement_timer->Check()) { + } + else { + if (AI_movement_timer->Check()) { int speed = GetFearSpeed(); animation = speed; speed *= 2; SetCurrentSpeed(speed); // Check if we have reached the last fear point if ((std::abs(GetX() - m_FearWalkTarget.x) < 0.1) && - (std::abs(GetY() - m_FearWalkTarget.y) < 0.1)) { + (std::abs(GetY() - m_FearWalkTarget.y) < 0.1)) { // Calculate a new point to run to CalculateNewFearpoint(); } - if(!RuleB(Pathing, Fear) || !zone->pathing) + + if (!RuleB(Pathing, Fear) || !zone->pathing) CalculateNewPosition(m_FearWalkTarget.x, m_FearWalkTarget.y, m_FearWalkTarget.z, speed, true); - else - { - bool WaypointChanged, NodeReached; + else { + bool waypoint_changed, node_reached; - glm::vec3 Goal = UpdatePath(m_FearWalkTarget.x, m_FearWalkTarget.y, m_FearWalkTarget.z, - speed, WaypointChanged, NodeReached); + glm::vec3 Goal = UpdatePath( + m_FearWalkTarget.x, + m_FearWalkTarget.y, + m_FearWalkTarget.z, + speed, + waypoint_changed, + node_reached + ); - if(WaypointChanged) + if (waypoint_changed) tar_ndx = 20; CalculateNewPosition(Goal.x, Goal.y, Goal.z, speed); @@ -1053,7 +1065,7 @@ void Mob::AI_Process() { if (IsCasting()) return; - bool engaged = IsEngaged(); + bool engaged = IsEngaged(); bool doranged = false; if (!zone->CanDoCombat() || IsPetStop() || IsPetRegroup()) { @@ -1063,7 +1075,7 @@ void Mob::AI_Process() { if (moving) { if (AI_scan_door_open_timer->Check()) { - auto &door_list = entity_list.GetDoorsList(); + auto &door_list = entity_list.GetDoorsList(); for (auto itr : door_list) { Doors *door = itr.second; @@ -1096,39 +1108,50 @@ void Mob::AI_Process() { // Begin: Additions for Wiz Fear Code // - if(RuleB(Combat, EnableFearPathing)){ - if(currently_fleeing) { - if((IsRooted() || (IsBlind() && CombatRange(hate_list.GetClosestEntOnHateList(this)))) && !IsPetStop() && !IsPetRegroup()) { + if (RuleB(Combat, EnableFearPathing)) { + if (currently_fleeing) { + if ((IsRooted() || (IsBlind() && CombatRange(hate_list.GetClosestEntOnHateList(this)))) && !IsPetStop() && + !IsPetRegroup()) { //make sure everybody knows were not moving, for appearance sake - if(IsMoving()) - { - if(target) + if (IsMoving()) { + if (target) SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY())); SetCurrentSpeed(0); - moved=false; + moved = false; } //continue on to attack code, ensuring that we execute the engaged code engaged = true; - } else { - if(AI_movement_timer->Check()) { + } + else { + if (AI_movement_timer->Check()) { // Check if we have reached the last fear point if ((std::abs(GetX() - m_FearWalkTarget.x) < 0.1) && - (std::abs(GetY() - m_FearWalkTarget.y) < 0.1)) { + (std::abs(GetY() - m_FearWalkTarget.y) < 0.1)) { // Calculate a new point to run to CalculateNewFearpoint(); } - if(!RuleB(Pathing, Fear) || !zone->pathing) - { - CalculateNewPosition(m_FearWalkTarget.x, m_FearWalkTarget.y, m_FearWalkTarget.z, GetFearSpeed(), true); + if (!RuleB(Pathing, Fear) || !zone->pathing) { + CalculateNewPosition( + m_FearWalkTarget.x, + m_FearWalkTarget.y, + m_FearWalkTarget.z, + GetFearSpeed(), + true + ); } - else - { + else { bool WaypointChanged, NodeReached; - glm::vec3 Goal = UpdatePath(m_FearWalkTarget.x, m_FearWalkTarget.y, m_FearWalkTarget.z, - GetFearSpeed(), WaypointChanged, NodeReached); + glm::vec3 Goal = UpdatePath( + m_FearWalkTarget.x, + m_FearWalkTarget.y, + m_FearWalkTarget.z, + GetFearSpeed(), + WaypointChanged, + NodeReached + ); - if(WaypointChanged) + if (WaypointChanged) tar_ndx = 20; CalculateNewPosition(Goal.x, Goal.y, Goal.z, GetFearSpeed()); @@ -1154,7 +1177,7 @@ void Mob::AI_Process() { this->FixZ(); if (target_distance <= 15 && !this->CheckLosFN(this->GetTarget())) { - Mob* target = this->GetTarget(); + Mob *target = this->GetTarget(); m_Position.x = target->GetX(); m_Position.y = target->GetY(); @@ -1171,7 +1194,7 @@ void Mob::AI_Process() { // NPCs will forget people after 10 mins of not interacting with them or out of range // both of these maybe zone specific, hardcoded for now if (hate_list_cleanup_timer.Check()) { - hate_list.RemoveStaleEntries(600000, 600.0f); + hate_list.RemoveStaleEntries(600000, static_cast(zone->newzone_data.NPCAggroMaxDist)); if (hate_list.IsHateListEmpty()) { AI_Event_NoLongerEngaged(); zone->DelAggroMob(); @@ -1183,10 +1206,8 @@ void Mob::AI_Process() { // from above, so no extra blind checks needed if ((IsRooted() && !GetSpecialAbility(IGNORE_ROOT_AGGRO_RULES)) || IsBlind()) SetTarget(hate_list.GetClosestEntOnHateList(this)); - else - { - if (AI_target_check_timer->Check()) - { + else { + if (AI_target_check_timer->Check()) { if (IsFocused()) { if (!target) { SetTarget(hate_list.GetEntWithMostHateOnList(this)); @@ -1248,21 +1269,16 @@ void Mob::AI_Process() { bool is_combat_range = CombatRange(target); - if (is_combat_range) - { - if (AI_movement_timer->Check()) - { - if (CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()) != m_Position.w) - { + if (is_combat_range) { + if (AI_movement_timer->Check()) { + if (CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()) != m_Position.w) { SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); SendPosition(); } SetCurrentSpeed(0); } - if (IsMoving()) - { - if (CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()) != m_Position.w) - { + if (IsMoving()) { + if (CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()) != m_Position.w) { SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); SendPosition(); } @@ -1287,7 +1303,7 @@ void Mob::AI_Process() { if (zone->random.Roll(flurry_chance)) { ExtraAttackOptions opts; - int cur = GetSpecialAbilityParam(SPECATK_FLURRY, 2); + int cur = GetSpecialAbilityParam(SPECATK_FLURRY, 2); if (cur > 0) opts.damage_percent = cur / 100.0f; @@ -1322,7 +1338,7 @@ void Mob::AI_Process() { if (owner) { int16 flurry_chance = owner->aabonuses.PetFlurry + - owner->spellbonuses.PetFlurry + owner->itembonuses.PetFlurry; + owner->spellbonuses.PetFlurry + owner->itembonuses.PetFlurry; if (flurry_chance && zone->random.Roll(flurry_chance)) Flurry(nullptr); @@ -1330,21 +1346,22 @@ void Mob::AI_Process() { } if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { - if (spellbonuses.PC_Pet_Rampage[0] || itembonuses.PC_Pet_Rampage[0] || aabonuses.PC_Pet_Rampage[0]) { - int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] + aabonuses.PC_Pet_Rampage[0]; + if (spellbonuses.PC_Pet_Rampage[0] || itembonuses.PC_Pet_Rampage[0] || + aabonuses.PC_Pet_Rampage[0]) { + int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] + + aabonuses.PC_Pet_Rampage[0]; if (zone->random.Roll(chance)) { Rampage(nullptr); } } } - if (GetSpecialAbility(SPECATK_RAMPAGE) && !specialed) - { + if (GetSpecialAbility(SPECATK_RAMPAGE) && !specialed) { int rampage_chance = GetSpecialAbilityParam(SPECATK_RAMPAGE, 0); rampage_chance = rampage_chance > 0 ? rampage_chance : 20; if (zone->random.Roll(rampage_chance)) { ExtraAttackOptions opts; - int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); + int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); if (cur > 0) { opts.damage_flat = cur; } @@ -1373,13 +1390,12 @@ void Mob::AI_Process() { } } - if (GetSpecialAbility(SPECATK_AREA_RAMPAGE) && !specialed) - { + if (GetSpecialAbility(SPECATK_AREA_RAMPAGE) && !specialed) { int rampage_chance = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 0); rampage_chance = rampage_chance > 0 ? rampage_chance : 20; if (zone->random.Roll(rampage_chance)) { ExtraAttackOptions opts; - int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3); + int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3); if (cur > 0) { opts.damage_flat = cur; } @@ -1421,7 +1437,7 @@ void Mob::AI_Process() { } AI_EngagedCastCheck(); - } //end is within combat rangepet + } //end is within combat rangepet else { //we cannot reach our target... //underwater stuff only works with water maps in the zone! @@ -1434,7 +1450,7 @@ void Mob::AI_Process() { Heal(); BuffFadeAll(); AI_walking_timer->Start(100); - pLastFightingDelayMoving = Timer::GetCurrentTime(); + time_until_can_move = Timer::GetCurrentTime(); return; } else if (tar != nullptr) { @@ -1445,8 +1461,7 @@ void Mob::AI_Process() { } // See if we can summon the mob to us - if (!HateSummon()) - { + if (!HateSummon()) { //could not summon them, check ranged... if (GetSpecialAbility(SPECATK_RANGED_ATK)) doranged = true; @@ -1456,18 +1471,18 @@ void Mob::AI_Process() { if (AI_PursueCastCheck()) { //we did something, so do not process movement. } - else if (AI_movement_timer->Check() && target) - { + else if (AI_movement_timer->Check() && target) { if (!IsRooted()) { Log(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", target->GetName()); if (!RuleB(Pathing, Aggro) || !zone->pathing) CalculateNewPosition(target->GetX(), target->GetY(), target->GetZ(), GetRunspeed()); - else - { + else { bool WaypointChanged, NodeReached; - glm::vec3 Goal = UpdatePath(target->GetX(), target->GetY(), target->GetZ(), - GetRunspeed(), WaypointChanged, NodeReached); + glm::vec3 Goal = UpdatePath( + target->GetX(), target->GetY(), target->GetZ(), + GetRunspeed(), WaypointChanged, NodeReached + ); if (WaypointChanged) tar_ndx = 20; @@ -1492,146 +1507,159 @@ void Mob::AI_Process() { if (IsPetStop()) // pet stop won't be engaged, so we will always get here and we want the above branch to execute return; - if(zone->CanDoCombat() && AI_feign_remember_timer->Check()) { + if (zone->CanDoCombat() && AI_feign_remember_timer->Check()) { // 6/14/06 // Improved Feign Death Memory // check to see if any of our previous feigned targets have gotten up. std::set::iterator RememberedCharID; RememberedCharID = feign_memory_list.begin(); while (RememberedCharID != feign_memory_list.end()) { - Client* remember_client = entity_list.GetClientByCharID(*RememberedCharID); + Client *remember_client = entity_list.GetClientByCharID(*RememberedCharID); if (remember_client == nullptr) { //they are gone now... RememberedCharID = feign_memory_list.erase(RememberedCharID); - } else if (!remember_client->GetFeigned()) { - AddToHateList(remember_client->CastToMob(),1); + } + else if (!remember_client->GetFeigned()) { + AddToHateList(remember_client->CastToMob(), 1); RememberedCharID = feign_memory_list.erase(RememberedCharID); break; - } else { + } + else { //they are still feigned, carry on... ++RememberedCharID; } } } - if (AI_IdleCastCheck()) - { + if (AI_IdleCastCheck()) { //we processed a spell action, so do nothing else. } - else if (zone->CanDoCombat() && CastToNPC()->WillAggroNPCs() && AI_scan_area_timer->Check()) - { + else if (zone->CanDoCombat() && CastToNPC()->WillAggroNPCs() && AI_scan_area_timer->Check()) { + /* * NPC to NPC aggro checking, npc needs npc_aggro flag */ - - Mob* temp_target = entity_list.AICheckNPCtoNPCAggro(this, GetAggroRange(), GetAssistRange()); - if (temp_target){ + Mob *temp_target = entity_list.AICheckNPCtoNPCAggro(this, GetAggroRange(), GetAssistRange()); + if (temp_target) { AddToHateList(temp_target); } AI_scan_area_timer->Disable(); - AI_scan_area_timer->Start(RandomTimer(RuleI(NPC, NPCToNPCAggroTimerMin), RuleI(NPC, NPCToNPCAggroTimerMax)), false); + AI_scan_area_timer->Start( + RandomTimer(RuleI(NPC, NPCToNPCAggroTimerMin), RuleI(NPC, NPCToNPCAggroTimerMax)), + false + ); } - else if (AI_movement_timer->Check() && !IsRooted()) - { - if (IsPet()) - { + else if (AI_movement_timer->Check() && !IsRooted()) { + if (IsPet()) { // we're a pet, do as we're told - switch (pStandingPetOrder) - { - case SPO_Follow: - { + switch (pStandingPetOrder) { + case SPO_Follow: { - Mob* owner = GetOwner(); - if(owner == nullptr) + Mob *owner = GetOwner(); + if (owner == nullptr) { break; + } - //if(owner->IsClient()) - // printf("Pet start pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ()); + glm::vec4 pet_owner_position = owner->GetPosition(); + float distance_to_owner = DistanceSquared(m_Position, pet_owner_position); + float z_distance = pet_owner_position.z - m_Position.z; - glm::vec4 ownerPos = owner->GetPosition(); - float dist = DistanceSquared(m_Position, ownerPos); - float distz = ownerPos.z - m_Position.z; + if (distance_to_owner >= 400 || z_distance > 100) { - if (dist >= 400 || distz > 100) - { - int speed = GetWalkspeed(); - if (dist >= 1225) // 35 - speed = GetRunspeed(); + int pet_speed = GetWalkspeed(); - if (dist >= 202500 || distz > 100) // dist >= 450 - { - m_Position = ownerPos; + /** + * Distance: >= 35 (Run if far away) + */ + if (distance_to_owner >= 1225) { + pet_speed = GetRunspeed(); + } + + /** + * Distance: >= 450 (Snap to owner) + */ + if (distance_to_owner >= 202500 || z_distance > 100) { + m_Position = pet_owner_position; SendPositionUpdate(); moved = true; } - else - { - CalculateNewPosition(owner->GetX(), owner->GetY(), owner->GetZ(), speed); + else { + + bool waypoint_changed, node_reached; + glm::vec3 Goal = UpdatePath( + owner->GetX(), + owner->GetY(), + owner->GetZ(), + pet_speed, + waypoint_changed, + node_reached + ); + + CalculateNewPosition(Goal.x, Goal.y, Goal.z, pet_speed, true); } } - else - { - if(moved) - { + else { + if (moved) { this->FixZ(); SetCurrentSpeed(0); moved = false; } } - /* - //fix up Z - float zdiff = GetZ() - owner->GetZ(); - if(zdiff < 0) - zdiff = 0 - zdiff; - if(zdiff > 2.0f) { - SendTo(GetX(), GetY(), owner->GetZ()); - SendPosition(); - } - - if(owner->IsClient()) - printf("Pet pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ()); - */ - break; } - case SPO_Sit: - { + case SPO_Sit: { SetAppearance(eaSitting, false); break; } - case SPO_Guard: - { + case SPO_Guard: { //only NPCs can guard stuff. (forced by where the guard movement code is in the AI) - if(IsNPC()) { + if (IsNPC()) { CastToNPC()->NextGuardPosition(); } break; } } - if (IsPetRegroup()) + if (IsPetRegroup()) { return; + } } - /* Entity has been assigned another entity to follow */ - else if (GetFollowID()) - { - Mob* follow = entity_list.GetMob(GetFollowID()); - if (!follow) SetFollowID(0); - else - { - float dist2 = DistanceSquared(m_Position, follow->GetPosition()); - int followdist = GetFollowDistance(); + /* Entity has been assigned another entity to follow */ + else if (GetFollowID()) { + Mob *follow = entity_list.GetMob(static_cast(GetFollowID())); + if (!follow) { + SetFollowID(0); + } + else { - if (dist2 >= followdist) // Default follow distance is 100 - { + float distance = DistanceSquared(m_Position, follow->GetPosition()); + int follow_distance = GetFollowDistance(); + + /** + * Default follow distance is 100 + */ + if (distance >= follow_distance) { int speed = GetWalkspeed(); - if (dist2 >= followdist + 150) + + if (distance >= follow_distance + 150) { speed = GetRunspeed(); - CalculateNewPosition(follow->GetX(), follow->GetY(), follow->GetZ(), speed); + } + + bool waypoint_changed, node_reached; + + glm::vec3 Goal = UpdatePath( + follow->GetX(), + follow->GetY(), + follow->GetZ(), + speed, + waypoint_changed, + node_reached + ); + + CalculateNewPosition(Goal.x, Goal.y, Goal.z, speed, true); } - else - { + else { moved = false; SetCurrentSpeed(0); } @@ -1640,20 +1668,21 @@ void Mob::AI_Process() { else //not a pet, and not following somebody... { // dont move till a bit after you last fought - if (pLastFightingDelayMoving < Timer::GetCurrentTime()) - { - if (this->IsClient()) - { - // LD timer expired, drop out of world - if (this->CastToClient()->IsLD()) + if (time_until_can_move < Timer::GetCurrentTime()) { + if (this->IsClient()) { + + /** + * LD timer expired, drop out of world + */ + if (this->CastToClient()->IsLD()) { this->CastToClient()->Disconnect(); + } + return; } - if(IsNPC()) - { - if(RuleB(NPC, SmartLastFightingDelayMoving) && !feign_memory_list.empty()) - { + if (IsNPC()) { + if (RuleB(NPC, SmartLastFightingDelayMoving) && !feign_memory_list.empty()) { minLastFightingDelayMoving = 0; maxLastFightingDelayMoving = 0; } @@ -1661,93 +1690,145 @@ void Mob::AI_Process() { CastToNPC()->AI_DoMovement(); } } - } - } // else if (AImovement_timer->Check()) + } } //Do Ranged attack here - if(doranged) - { + if (doranged) { RangedAttack(target); } } void NPC::AI_DoMovement() { - float walksp = GetMovespeed(); - if(walksp <= 0.0f) - return; //this is idle movement at walk speed, and we are unable to walk right now. - if (roambox_distance > 0) { - if ( - roambox_movingto_x > roambox_max_x - || roambox_movingto_x < roambox_min_x - || roambox_movingto_y > roambox_max_y - || roambox_movingto_y < roambox_min_y - ) - { - float movedist = roambox_distance*roambox_distance; - float movex = zone->random.Real(0, movedist); - float movey = movedist - movex; - movex = sqrtf(movex); - movey = sqrtf(movey); - movex *= zone->random.Int(0, 1) ? 1 : -1; - movey *= zone->random.Int(0, 1) ? 1 : -1; - roambox_movingto_x = GetX() + movex; - roambox_movingto_y = GetY() + movey; - //Try to calculate new coord using distance. - if (roambox_movingto_x > roambox_max_x || roambox_movingto_x < roambox_min_x) - roambox_movingto_x -= movex * 2; - if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y) - roambox_movingto_y -= movey * 2; - //New coord is still invalid, ignore distance and just pick a new random coord. - //If we're here we may have a roambox where one side is shorter than the specified distance. Commons, Wkarana, etc. - if (roambox_movingto_x > roambox_max_x || roambox_movingto_x < roambox_min_x) - roambox_movingto_x = zone->random.Real(roambox_min_x+1,roambox_max_x-1); - if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y) - roambox_movingto_y = zone->random.Real(roambox_min_y+1,roambox_max_y-1); - Log(Logs::Detail, Logs::AI, - "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", - roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, - roambox_max_y, roambox_movingto_x, roambox_movingto_y); - } - - Log(Logs::Detail, Logs::AI, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", - roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y); + float move_speed = GetMovespeed(); - float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5) + GetZOffset(); - - if (!CalculateNewPosition(roambox_movingto_x, roambox_movingto_y, new_z, walksp, true)) - { - this->FixZ(); // FixZ on final arrival point. - roambox_movingto_x = roambox_max_x + 1; // force update - pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay); - SetMoving(false); - SendPosition(); // makes mobs stop clientside - } + if (move_speed <= 0.0f) { + return; } - else if (roamer) - { - if (AI_walking_timer->Check()) - { - movetimercompleted=true; + + /** + * Roambox logic sets precedence + */ + if (roambox_distance > 0) { + + if (!IsMoving()) { + auto move_x = static_cast(zone->random.Real(-roambox_distance, roambox_distance)); + auto move_y = static_cast(zone->random.Real(-roambox_distance, roambox_distance)); + + roambox_destination_x = EQEmu::Clamp((GetX() + move_x), roambox_min_x, roambox_max_x); + roambox_destination_y = EQEmu::Clamp((GetY() + move_y), roambox_min_y, roambox_max_y); + + /** + * If our roambox was configured with large distances, chances of hitting the min or max end of + * the clamp is high, this causes NPC's to gather on the border of a box, to reduce clustering + * either lower the roambox distance or the code will do a simple random between min - max when it + * hits the min or max of the clamp + */ + if (roambox_destination_x == roambox_min_x || roambox_destination_x == roambox_max_x) { + roambox_destination_x = static_cast(zone->random.Real(roambox_min_x, roambox_max_x)); + } + + if (roambox_destination_y == roambox_min_y || roambox_destination_y == roambox_max_y) { + roambox_destination_y = static_cast(zone->random.Real(roambox_min_y, roambox_max_y)); + } + + /** + * If mob was not spawned in water, let's not randomly roam them into water + * if the roam box was sloppily configured + */ + if (!this->GetWasSpawnedInWater()) { + if (zone->HasMap() && zone->HasWaterMap()) { + auto position = glm::vec3( + roambox_destination_x, + roambox_destination_y, + (m_Position.z - 15) + ); + + /** + * If someone brought us into water when we naturally wouldn't path there, return to spawn + */ + if (zone->watermap->InLiquid(position) && zone->watermap->InLiquid(m_Position)) { + roambox_destination_x = m_SpawnPoint.x; + roambox_destination_y = m_SpawnPoint.y; + } + + if (zone->watermap->InLiquid(position)) { + Log(Logs::Detail, + Logs::NPCRoamBox, "%s | My destination is in water and I don't belong there!", + this->GetCleanName()); + + return; + } + } + } + + glm::vec3 destination; + destination.x = roambox_destination_x; + destination.y = roambox_destination_y; + destination.z = m_Position.z; + roambox_destination_z = GetFixedZ(destination) + this->GetZOffset(); + + Log(Logs::Detail, + Logs::NPCRoamBox, + "Calculate | NPC: %s distance %.3f | min_x %.3f | max_x %.3f | final_x %.3f | min_y %.3f | max_y %.3f | final_y %.3f", + this->GetCleanName(), + roambox_distance, + roambox_min_x, + roambox_max_x, + roambox_destination_x, + roambox_min_y, + roambox_max_y, + roambox_destination_y); + } + + bool waypoint_changed, node_reached; + + glm::vec3 Goal = UpdatePath( + roambox_destination_x, + roambox_destination_y, + roambox_destination_z, + move_speed, + waypoint_changed, + node_reached + ); + + CalculateNewPosition(Goal.x, Goal.y, Goal.z, move_speed, true); + + if (m_Position.x == roambox_destination_x && m_Position.y == roambox_destination_y) { + time_until_can_move = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay); + SetMoving(false); + this->FixZ(); + SendPosition(); + } + + return; + } + else if (roamer) { + if (AI_walking_timer->Check()) { + movetimercompleted = true; AI_walking_timer->Disable(); } - int32 gridno = CastToNPC()->GetGrid(); - if (gridno > 0 || cur_wp==-2) { - if (movetimercompleted==true) { // time to pause at wp is over + if (gridno > 0 || cur_wp == -2) { + if (movetimercompleted == true) { // time to pause at wp is over AI_SetupNextWaypoint(); - } // endif (movetimercompleted==true) - else if (!(AI_walking_timer->Enabled())) - { // currently moving + } // endif (movetimercompleted==true) + else if (!(AI_walking_timer->Enabled())) { // currently moving bool doMove = true; - if (m_CurrentWayPoint.x == GetX() && m_CurrentWayPoint.y == GetY()) - { // are we there yet? then stop - Log(Logs::Detail, Logs::AI, "We have reached waypoint %d (%.3f,%.3f,%.3f) on grid %d", cur_wp, GetX(), GetY(), GetZ(), GetGrid()); - + if (m_CurrentWayPoint.x == GetX() && m_CurrentWayPoint.y == GetY()) { // are we there yet? then stop + Log(Logs::Detail, + Logs::AI, + "We have reached waypoint %d (%.3f,%.3f,%.3f) on grid %d", + cur_wp, + GetX(), + GetY(), + GetZ(), + GetGrid()); + SetWaypointPause(); SetAppearance(eaStanding, false); SetMoving(false); @@ -1773,36 +1854,45 @@ void NPC::AI_DoMovement() { } // wipe feign memory since we reached our first waypoint - if(cur_wp == 1) + if (cur_wp == 1) ClearFeignMemory(); } - if (doMove) - { // not at waypoint yet or at 0 pause WP, so keep moving - if(!RuleB(Pathing, AggroReturnToGrid) || !zone->pathing || (DistractedFromGrid == 0)) - CalculateNewPosition(m_CurrentWayPoint.x, m_CurrentWayPoint.y, m_CurrentWayPoint.z, walksp, true); - else - { - bool WaypointChanged; - bool NodeReached; - glm::vec3 Goal = UpdatePath(m_CurrentWayPoint.x, m_CurrentWayPoint.y, m_CurrentWayPoint.z, walksp, WaypointChanged, NodeReached); - if(WaypointChanged) + if (doMove) { // not at waypoint yet or at 0 pause WP, so keep moving + if (!RuleB(Pathing, AggroReturnToGrid) || !zone->pathing || (DistractedFromGrid == 0)) + CalculateNewPosition( + m_CurrentWayPoint.x, + m_CurrentWayPoint.y, + m_CurrentWayPoint.z, + move_speed, + true + ); + else { + bool WaypointChanged; + bool NodeReached; + glm::vec3 Goal = UpdatePath( + m_CurrentWayPoint.x, + m_CurrentWayPoint.y, + m_CurrentWayPoint.z, + move_speed, + WaypointChanged, + NodeReached + ); + if (WaypointChanged) tar_ndx = 20; - if(NodeReached) + if (NodeReached) entity_list.OpenDoorsNear(CastToNPC()); - CalculateNewPosition(Goal.x, Goal.y, Goal.z, walksp, true); + CalculateNewPosition(Goal.x, Goal.y, Goal.z, move_speed, true); } } } - } // endif (gridno > 0) - // handle new quest grid command processing - else if (gridno < 0) - { // this mob is under quest control - if (movetimercompleted==true) - { // time to pause has ended - SetGrid( 0 - GetGrid()); // revert to AI control + } // endif (gridno > 0) + // handle new quest grid command processing + else if (gridno < 0) { // this mob is under quest control + if (movetimercompleted == true) { // time to pause has ended + SetGrid(0 - GetGrid()); // revert to AI control Log(Logs::Detail, Logs::Pathing, "Quest pathing is finished. Resuming on grid %d", GetGrid()); SetAppearance(eaStanding, false); @@ -1812,39 +1902,55 @@ void NPC::AI_DoMovement() { } } - else if (IsGuarding()) - { + else if (IsGuarding()) { bool CP2Moved; - if(!RuleB(Pathing, Guard) || !zone->pathing) - CP2Moved = CalculateNewPosition(m_GuardPoint.x, m_GuardPoint.y, m_GuardPoint.z, walksp); - else - { - if(!((m_Position.x == m_GuardPoint.x) && (m_Position.y == m_GuardPoint.y) && (m_Position.z == m_GuardPoint.z))) - { + if (!RuleB(Pathing, Guard) || !zone->pathing) { + CP2Moved = CalculateNewPosition(m_GuardPoint.x, m_GuardPoint.y, m_GuardPoint.z, move_speed); + } + else { + if (!((m_Position.x == m_GuardPoint.x) && (m_Position.y == m_GuardPoint.y) && + (m_Position.z == m_GuardPoint.z))) { + bool WaypointChanged, NodeReached; - glm::vec3 Goal = UpdatePath(m_GuardPoint.x, m_GuardPoint.y, m_GuardPoint.z, walksp, WaypointChanged, NodeReached); - if(WaypointChanged) + + glm::vec3 Goal = UpdatePath( + m_GuardPoint.x, + m_GuardPoint.y, + m_GuardPoint.z, + move_speed, + WaypointChanged, + NodeReached + ); + if (WaypointChanged) { tar_ndx = 20; + } - if(NodeReached) + if (NodeReached) { entity_list.OpenDoorsNear(CastToNPC()); + } - CP2Moved = CalculateNewPosition(Goal.x, Goal.y, Goal.z, walksp); + CP2Moved = CalculateNewPosition(Goal.x, Goal.y, Goal.z, move_speed); } - else + else { CP2Moved = false; + } } - if (!CP2Moved) - { - if(moved) { - Log(Logs::Detail, Logs::AI, "Reached guard point (%.3f,%.3f,%.3f)", m_GuardPoint.x, m_GuardPoint.y, m_GuardPoint.z); + if (!CP2Moved) { + if (moved) { + Log(Logs::Detail, + Logs::AI, + "Reached guard point (%.3f,%.3f,%.3f)", + m_GuardPoint.x, + m_GuardPoint.y, + m_GuardPoint.z); + ClearFeignMemory(); - moved=false; - if (GetTarget() == nullptr || DistanceSquared(m_Position, GetTarget()->GetPosition()) >= 5*5 ) - { + moved = false; + if (GetTarget() == nullptr || DistanceSquared(m_Position, GetTarget()->GetPosition()) >= 5 * 5) { SetHeading(m_GuardPoint.w); - } else { + } + else { FaceTarget(GetTarget()); } SetCurrentSpeed(0); @@ -1968,11 +2074,11 @@ void Mob::AI_Event_NoLongerEngaged() { if (!IsAIControlled()) return; this->AI_walking_timer->Start(RandomTimer(3000,20000)); - pLastFightingDelayMoving = Timer::GetCurrentTime(); + time_until_can_move = Timer::GetCurrentTime(); if (minLastFightingDelayMoving == maxLastFightingDelayMoving) - pLastFightingDelayMoving += minLastFightingDelayMoving; + time_until_can_move += minLastFightingDelayMoving; else - pLastFightingDelayMoving += zone->random.Int(minLastFightingDelayMoving, maxLastFightingDelayMoving); + time_until_can_move += zone->random.Int(minLastFightingDelayMoving, maxLastFightingDelayMoving); // So mobs don't keep running as a ghost until AIwalking_timer fires // if they were moving prior to losing all hate // except if we're a pet, then we might run into some issues with pets backing off when they should immediately be moving @@ -2255,8 +2361,6 @@ void Mob::AreaRampage(ExtraAttackOptions *opts) m_specialattacks = eSpecialAttacks::AERampage; index_hit = hate_list.AreaRampage(this, GetTarget(), rampage_targets, opts); - if(index_hit == 0) - ProcessAttackRounds(GetTarget(), opts); m_specialattacks = eSpecialAttacks::None; } diff --git a/zone/npc.cpp b/zone/npc.cpp index 7cf31b617..bf1508858 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -242,8 +242,8 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if roambox_max_y = -2; roambox_min_x = -2; roambox_min_y = -2; - roambox_movingto_x = -2; - roambox_movingto_y = -2; + roambox_destination_x = -2; + roambox_destination_y = -2; roambox_min_delay = 1000; roambox_delay = 1000; p_depop = false; diff --git a/zone/npc.h b/zone/npc.h index 471b47557..ec2676f60 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -311,9 +311,17 @@ public: void SaveGuardSpot(bool iClearGuardSpot = false); inline bool IsGuarding() const { return(m_GuardPoint.w != 0); } void SaveGuardSpotCharm(); - void RestoreGuardSpotCharm(); - void AI_SetRoambox(float iDist, float iRoamDist, uint32 iDelay = 2500, uint32 iMinDelay = 2500); - void AI_SetRoambox(float iDist, float iMaxX, float iMinX, float iMaxY, float iMinY, uint32 iDelay = 2500, uint32 iMinDelay = 2500); + + void RestoreGuardSpotCharm(); + + void AI_SetRoambox( + float max_distance, + float roam_distance_variance, + uint32 delay = 2500, + uint32 min_delay = 2500 + ); + + void AI_SetRoambox(float distance, float max_x, float min_x, float max_y, float min_y, uint32 delay = 2500, uint32 min_delay = 2500); //mercenary stuff void LoadMercTypes(); @@ -530,8 +538,9 @@ protected: float roambox_min_x; float roambox_min_y; float roambox_distance; - float roambox_movingto_x; - float roambox_movingto_y; + float roambox_destination_x; + float roambox_destination_y; + float roambox_destination_z; uint32 roambox_delay; uint32 roambox_min_delay; diff --git a/zone/object.cpp b/zone/object.cpp index 3ad4a6a9c..4306c6642 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -528,6 +528,8 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) if(cursordelete) // delete the item if it's a duplicate lore. We have to do this because the client expects the item packet sender->DeleteItemInInventory(EQEmu::invslot::slotCursor); + sender->DropItemQS(m_inst, true); + if(!m_ground_spawn) safe_delete(m_inst); diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index bfc474721..71101dff8 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -141,118 +141,181 @@ uint32 Spawn2::despawnTimer(uint32 despawn_timer) bool Spawn2::Process() { IsDespawned = false; - if(!Enabled()) + if (!Enabled()) return true; //grab our spawn group - SpawnGroup* sg = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); + SpawnGroup *spawn_group = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); - if(NPCPointerValid() && (sg->despawn == 0 || condition_id != 0)) + if (NPCPointerValid() && (spawn_group->despawn == 0 || condition_id != 0)) { return true; + } - if (timer.Check()) { + if (timer.Check()) { timer.Disable(); Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Timer has triggered", spawn2_id); //first check our spawn condition, if this isnt active //then we reset the timer and try again next time. - if(condition_id != SC_AlwaysEnabled + if (condition_id != SC_AlwaysEnabled && !zone->spawn_conditions.Check(condition_id, condition_min_value)) { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: spawning prevented by spawn condition %d", spawn2_id, condition_id); + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: spawning prevented by spawn condition %d", + spawn2_id, + condition_id); Reset(); - return(true); + return (true); } - if (sg == nullptr) { - database.LoadSpawnGroupsByID(spawngroup_id_,&zone->spawn_group_list); - sg = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); + if (spawn_group == nullptr) { + database.LoadSpawnGroupsByID(spawngroup_id_, &zone->spawn_group_list); + spawn_group = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); } - if (sg == nullptr) { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Unable to locate spawn group %d. Disabling.", spawn2_id, spawngroup_id_); + if (spawn_group == nullptr) { + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: Unable to locate spawn group %d. Disabling.", + spawn2_id, + spawngroup_id_); + return false; } //have the spawn group pick an NPC for us - uint32 npcid = sg->GetNPCType(); + uint32 npcid = spawn_group->GetNPCType(); if (npcid == 0) { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d did not yeild an NPC! not spawning.", spawn2_id, spawngroup_id_); - Reset(); //try again later (why?) - return(true); + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: Spawn group %d did not yeild an NPC! not spawning.", + spawn2_id, + spawngroup_id_); + + Reset(); //try again later (why?) + return (true); } //try to find our NPC type. - const NPCType* tmp = database.LoadNPCTypesData(npcid); + const NPCType *tmp = database.LoadNPCTypesData(npcid); if (tmp == nullptr) { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d yeilded an invalid NPC type %d", spawn2_id, spawngroup_id_, npcid); - Reset(); //try again later - return(true); + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: Spawn group %d yeilded an invalid NPC type %d", + spawn2_id, + spawngroup_id_, + npcid); + Reset(); //try again later + return (true); } - if(tmp->unique_spawn_by_name) - { - if(!entity_list.LimitCheckName(tmp->name)) - { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is unique and one already exists.", spawn2_id, spawngroup_id_, npcid); - timer.Start(5000); //try again in five seconds. - return(true); + if (tmp->unique_spawn_by_name) { + if (!entity_list.LimitCheckName(tmp->name)) { + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is unique and one already exists.", + spawn2_id, + spawngroup_id_, + npcid); + timer.Start(5000); //try again in five seconds. + return (true); } } - if(tmp->spawn_limit > 0) { - if(!entity_list.LimitCheckType(npcid, tmp->spawn_limit)) { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is over its spawn limit (%d)", spawn2_id, spawngroup_id_, npcid, tmp->spawn_limit); - timer.Start(5000); //try again in five seconds. - return(true); + if (tmp->spawn_limit > 0) { + if (!entity_list.LimitCheckType(npcid, tmp->spawn_limit)) { + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is over its spawn limit (%d)", + spawn2_id, + spawngroup_id_, + npcid, + tmp->spawn_limit); + timer.Start(5000); //try again in five seconds. + return (true); } } bool ignore_despawn = false; - if (npcthis) - { + if (npcthis) { ignore_despawn = npcthis->IgnoreDespawn(); } - - if (ignore_despawn) - { + + if (ignore_despawn) { return true; } - - if (sg->despawn != 0 && condition_id == 0 && !ignore_despawn) - { + + if (spawn_group->despawn != 0 && condition_id == 0 && !ignore_despawn) { zone->Despawn(spawn2_id); } - if (IsDespawned) - { + if (IsDespawned) { return true; } currentnpcid = npcid; - NPC* npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), FlyMode3); + NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), FlyMode3); npc->mod_prespawn(this); npcthis = npc; npc->AddLootTable(); - if (npc->DropsGlobalLoot()) + if (npc->DropsGlobalLoot()) { npc->CheckGlobalLootTables(); + } npc->SetSp2(spawngroup_id_); npc->SaveGuardPointAnim(anim); - npc->SetAppearance((EmuAppearance)anim); + npc->SetAppearance((EmuAppearance) anim); entity_list.AddNPC(npc); //this limit add must be done after the AddNPC since we need the entity ID. entity_list.LimitAddNPC(npc); - if(sg->roamdist && sg->roambox[0] && sg->roambox[1] && sg->roambox[2] && sg->roambox[3] && sg->delay && sg->min_delay) - npc->AI_SetRoambox(sg->roamdist,sg->roambox[0],sg->roambox[1],sg->roambox[2],sg->roambox[3],sg->delay,sg->min_delay); - if(zone->InstantGrids()) { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f).", spawn2_id, spawngroup_id_, npc->GetName(), npcid, x, y, z); + + /** + * Roambox init + */ + if (spawn_group->roamdist && spawn_group->roambox[0] && spawn_group->roambox[1] && spawn_group->roambox[2] && + spawn_group->roambox[3] && spawn_group->delay && spawn_group->min_delay) { + + npc->AI_SetRoambox( + spawn_group->roamdist, + spawn_group->roambox[0], + spawn_group->roambox[1], + spawn_group->roambox[2], + spawn_group->roambox[3], + spawn_group->delay, + spawn_group->min_delay + ); + } + + if (zone->InstantGrids()) { + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f).", + spawn2_id, + spawngroup_id_, + npc->GetName(), + npcid, + x, + y, + z); + LoadGrid(); - } else { - Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f). Grid loading delayed.", spawn2_id, spawngroup_id_, tmp->name, npcid, x, y, z); + } + else { + Log(Logs::Detail, + Logs::Spawns, + "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f). Grid loading delayed.", + spawn2_id, + spawngroup_id_, + tmp->name, + npcid, + x, + y, + z); } } + return true; } @@ -266,11 +329,11 @@ void Spawn2::Disable() } void Spawn2::LoadGrid() { - if(!npcthis) + if (!npcthis) return; - if(grid_ < 1) + if (grid_ < 1) return; - if(!entity_list.IsMobInZone(npcthis)) + if (!entity_list.IsMobInZone(npcthis)) return; //dont set an NPC's grid until its loaded for them. npcthis->SetGrid(grid_); @@ -278,7 +341,6 @@ void Spawn2::LoadGrid() { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Loading grid %d for %s", spawn2_id, grid_, npcthis->GetName()); } - /* All three of these actions basically say that the mob which was associated with this spawn point is no longer relavent. diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index e7d182105..7e8db6930 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -3928,6 +3928,12 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) Mob* tempmob = GetOwner(); SetOwnerID(0); SetPetType(petNone); + SetHeld(false); + SetGHeld(false); + SetNoCast(false); + SetFocused(false); + SetPetStop(false); + SetPetRegroup(false); if(tempmob) { tempmob->SetPet(0); diff --git a/zone/spells.cpp b/zone/spells.cpp index 574056679..0fcb7fe2b 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -4247,6 +4247,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) { Log(Logs::Detail, Logs::Spells, "Our level (%d) is higher than the limit of this Mez spell (%d)", GetLevel(), spells[spell_id].max[effect_index]); caster->Message_StringID(MT_Shout, CANNOT_MEZ_WITH_SPELL); + AddToHateList(caster, 1,0,true,false,false,spell_id); return true; } } @@ -4338,6 +4339,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) { Log(Logs::Detail, Logs::Spells, "Our level (%d) is higher than the limit of this Charm spell (%d)", GetLevel(), spells[spell_id].max[effect_index]); caster->Message_StringID(MT_Shout, CANNOT_CHARM_YET); + AddToHateList(caster, 1,0,true,false,false,spell_id); return true; } } diff --git a/zone/water_map_v2.cpp b/zone/water_map_v2.cpp index 939b9be97..03ae76fa4 100644 --- a/zone/water_map_v2.cpp +++ b/zone/water_map_v2.cpp @@ -30,7 +30,7 @@ bool WaterMapV2::InLava(const glm::vec3& location) const { } bool WaterMapV2::InLiquid(const glm::vec3& location) const { - return InWater(location) || InLava(location); + return InWater(location) || InLava(location) || InVWater(location); } bool WaterMapV2::InPvP(const glm::vec3& location) const { diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 2351e2686..36c996ce8 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -42,19 +42,27 @@ struct wp_distance int index; }; -void NPC::AI_SetRoambox(float iDist, float iRoamDist, uint32 iDelay, uint32 iMinDelay) { - AI_SetRoambox(iDist, GetX() + iRoamDist, GetX() - iRoamDist, GetY() + iRoamDist, GetY() - iRoamDist, iDelay, iMinDelay); +void NPC::AI_SetRoambox(float max_distance, float roam_distance_variance, uint32 delay, uint32 min_delay) { + AI_SetRoambox( + max_distance, + GetX() + roam_distance_variance, + GetX() - roam_distance_variance, + GetY() + roam_distance_variance, + GetY() - roam_distance_variance, + delay, + min_delay + ); } -void NPC::AI_SetRoambox(float iDist, float iMaxX, float iMinX, float iMaxY, float iMinY, uint32 iDelay, uint32 iMinDelay) { - roambox_distance = iDist; - roambox_max_x = iMaxX; - roambox_min_x = iMinX; - roambox_max_y = iMaxY; - roambox_min_y = iMinY; - roambox_movingto_x = roambox_max_x + 1; // this will trigger a recalc - roambox_delay = iDelay; - roambox_min_delay = iMinDelay; +void NPC::AI_SetRoambox(float distance, float max_x, float min_x, float max_y, float min_y, uint32 delay, uint32 min_delay) { + roambox_distance = distance; + roambox_max_x = max_x; + roambox_min_x = min_x; + roambox_max_y = max_y; + roambox_min_y = min_y; + roambox_destination_x = roambox_max_x + 1; // this will trigger a recalc + roambox_delay = delay; + roambox_min_delay = min_delay; } void NPC::DisplayWaypointInfo(Client *c) { @@ -199,7 +207,7 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot) } cur_wp_pause = 0; - pLastFightingDelayMoving = 0; + time_until_can_move = 0; if (AI_walking_timer->Enabled()) AI_walking_timer->Start(100); } @@ -438,22 +446,16 @@ float Mob::CalculateDistance(float x, float y, float z) { return (float)sqrtf(((m_Position.x - x)*(m_Position.x - x)) + ((m_Position.y - y)*(m_Position.y - y)) + ((m_Position.z - z)*(m_Position.z - z))); } -bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ, bool calcHeading) { +bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, bool check_z, bool calculate_heading) { if (GetID() == 0) return true; - if (speed <= 0) - { + if (speed <= 0) { SetCurrentSpeed(0); return true; } - if ((m_Position.x - x == 0) && (m_Position.y - y == 0)) {//spawn is at target coords - if (m_Position.z - z != 0) { - m_Position.z = z; - Log(Logs::Detail, Logs::AI, "Calc Position2 (%.3f, %.3f, %.3f): Jumping pure Z.", x, y, z); - return true; - } + if ((m_Position.x - x == 0) && (m_Position.y - y == 0)) { //spawn is at target coords return false; } else if ((std::abs(m_Position.x - x) < 0.1) && (std::abs(m_Position.y - y) < 0.1)) { @@ -464,16 +466,17 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo m_Position.x = x; m_Position.y = y; m_Position.z = z; + return true; } - bool send_update = false; int compare_steps = 20; if (tar_ndx < compare_steps && m_TargetLocation.x == x && m_TargetLocation.y == y) { - float new_x = m_Position.x + m_TargetV.x*tar_vector; - float new_y = m_Position.y + m_TargetV.y*tar_vector; - float new_z = m_Position.z + m_TargetV.z*tar_vector; + float new_x = m_Position.x + m_TargetV.x * tar_vector; + float new_y = m_Position.y + m_TargetV.y * tar_vector; + float new_z = m_Position.z + m_TargetV.z * tar_vector; + if (IsNPC()) { entity_list.ProcessMove(CastToNPC(), new_x, new_y, new_z); } @@ -482,21 +485,22 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo m_Position.y = new_y; m_Position.z = new_z; - if(checkZ && fix_z_timer.Check() && - (!this->IsEngaged() || flee_mode || currently_fleeing)) + if (check_z && fix_z_timer.Check() && (!this->IsEngaged() || flee_mode || currently_fleeing)) { this->FixZ(); + } tar_ndx++; + return true; } - - if (tar_ndx>50) { + if (tar_ndx > 50) { tar_ndx--; } else { tar_ndx = 0; } + m_TargetLocation = glm::vec3(x, y, z); float nx = this->m_Position.x; @@ -530,14 +534,12 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo // mob move fix - if (numsteps<20) - { - if (numsteps>1) - { + if (numsteps < 20) { + if (numsteps > 1) { tar_vector = 1.0f; - m_TargetV.x = m_TargetV.x / (float)numsteps; - m_TargetV.y = m_TargetV.y / (float)numsteps; - m_TargetV.z = m_TargetV.z / (float)numsteps; + m_TargetV.x = m_TargetV.x / (float) numsteps; + m_TargetV.y = m_TargetV.y / (float) numsteps; + m_TargetV.z = m_TargetV.z / (float) numsteps; float new_x = m_Position.x + m_TargetV.x; float new_y = m_Position.y + m_TargetV.y; @@ -549,12 +551,12 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo m_Position.x = new_x; m_Position.y = new_y; m_Position.z = new_z; - if (calcHeading) + if (calculate_heading) { m_Position.w = CalculateHeadingToTarget(x, y); + } tar_ndx = 20 - numsteps; } - else - { + else { if (IsNPC()) { entity_list.ProcessMove(CastToNPC(), x, y, z); } @@ -578,9 +580,10 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo tar_vector *= (dur / 100.0f); - float new_x = m_Position.x + m_TargetV.x*tar_vector; - float new_y = m_Position.y + m_TargetV.y*tar_vector; - float new_z = m_Position.z + m_TargetV.z*tar_vector; + float new_x = m_Position.x + m_TargetV.x * tar_vector; + float new_y = m_Position.y + m_TargetV.y * tar_vector; + float new_z = m_Position.z + m_TargetV.z * tar_vector; + if (IsNPC()) { entity_list.ProcessMove(CastToNPC(), new_x, new_y, new_z); } @@ -588,11 +591,12 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo m_Position.x = new_x; m_Position.y = new_y; m_Position.z = new_z; - if (calcHeading) + if (calculate_heading) { m_Position.w = CalculateHeadingToTarget(x, y); + } } - if (checkZ && fix_z_timer.Check() && !this->IsEngaged()) + if (check_z && fix_z_timer.Check() && !this->IsEngaged()) this->FixZ(); SetMoving(true); @@ -600,13 +604,11 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo m_Delta = glm::vec4(m_Position.x - nx, m_Position.y - ny, m_Position.z - nz, 0.0f); - if (IsClient()) - { + if (IsClient()) { SendPositionUpdate(1); CastToClient()->ResetPositionTimer(); } - else - { + else { SendPositionUpdate(); SetAppearance(eaStanding, false); } @@ -615,8 +617,8 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo return true; } -bool Mob::CalculateNewPosition(float x, float y, float z, int speed, bool checkZ, bool calcHeading) { - return MakeNewPositionAndSendUpdate(x, y, z, speed); +bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool check_z, bool calculate_heading) { + return MakeNewPositionAndSendUpdate(x, y, z, speed, check_z); } void NPC::AssignWaypoints(int32 grid) @@ -746,10 +748,11 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { } } -float Mob::GetFixedZ(glm::vec3 dest, int32 z_find_offset) { +float Mob::GetFixedZ(glm::vec3 destination, int32 z_find_offset) { BenchTimer timer; timer.reset(); - float new_z = dest.z; + + float new_z = destination.z; if (zone->HasMap() && RuleB(Map, FixZWhenMoving)) { @@ -765,7 +768,7 @@ float Mob::GetFixedZ(glm::vec3 dest, int32 z_find_offset) { /* * Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors */ - new_z = this->FindDestGroundZ(dest, z_find_offset); + new_z = this->FindDestGroundZ(destination, z_find_offset); if (new_z != BEST_Z_INVALID) { new_z += this->GetZOffset(); @@ -776,31 +779,48 @@ float Mob::GetFixedZ(glm::vec3 dest, int32 z_find_offset) { auto duration = timer.elapsed(); - Log(Logs::Moderate, Logs::FixZ, - "Mob::GetFixedZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", - this->GetCleanName(), new_z, dest.x, dest.y, dest.z, duration); + Log(Logs::Moderate, + Logs::FixZ, + "Mob::GetFixedZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", + this->GetCleanName(), + new_z, + destination.x, + destination.y, + destination.z, + duration); } return new_z; } -void Mob::FixZ(int32 z_find_offset /*= 5*/) { +void Mob::FixZ(int32 z_find_offset /*= 5*/, bool fix_client_z /*= false*/) { glm::vec3 current_loc(m_Position); - float new_z = GetFixedZ(current_loc, z_find_offset); - if (!IsClient() && new_z != m_Position.z) { - if ((new_z > -2000) && new_z != BEST_Z_INVALID) { - if (RuleB(Map, MobZVisualDebug)) - this->SendAppearanceEffect(78, 0, 0, 0, 0); + if (IsClient() && !fix_client_z) + return; - m_Position.z = new_z; - } else { - if (RuleB(Map, MobZVisualDebug)) - this->SendAppearanceEffect(103, 0, 0, 0, 0); + float new_z = GetFixedZ(current_loc, z_find_offset); - Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", - this->GetCleanName(), std::abs(m_Position.z - new_z)); + if (new_z == m_Position.z) + return; + + if ((new_z > -2000) && new_z != BEST_Z_INVALID) { + if (RuleB(Map, MobZVisualDebug)) { + this->SendAppearanceEffect(78, 0, 0, 0, 0); } + + m_Position.z = new_z; + } + else { + if (RuleB(Map, MobZVisualDebug)) { + this->SendAppearanceEffect(103, 0, 0, 0, 0); + } + + Log(Logs::General, + Logs::FixZ, + "%s is failing to find Z %f", + this->GetCleanName(), + std::abs(m_Position.z - new_z)); } } diff --git a/zone/zone.cpp b/zone/zone.cpp index d43527c4f..3b9e5e513 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -904,10 +904,10 @@ bool Zone::Init(bool iStaticZone) { RuleManager::Instance()->LoadRules(&database, r_name.c_str()); } } - - zone->zonemap = Map::LoadMapFile(zone->map_name); + + zone->zonemap = Map::LoadMapFile(zone->map_name); zone->watermap = WaterMap::LoadWaterMapfile(zone->map_name); - zone->pathing = IPathfinder::Load(zone->map_name); + zone->pathing = IPathfinder::Load(zone->map_name); Log(Logs::General, Logs::Status, "Loading spawn conditions..."); if(!spawn_conditions.LoadSpawnConditions(short_name, instanceid)) { diff --git a/zone/zone.h b/zone/zone.h index c3d5fe8a6..5f8651d8b 100644 --- a/zone/zone.h +++ b/zone/zone.h @@ -212,10 +212,10 @@ public: void ReloadWorld(uint32 Option); void ReloadMerchants(); - Map* zonemap; - WaterMap* watermap; - IPathfinder *pathing; - NewZone_Struct newzone_data; + Map *zonemap; + WaterMap *watermap; + IPathfinder *pathing; + NewZone_Struct newzone_data; SpawnConditionManager spawn_conditions; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index dc940b552..027225243 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -143,7 +143,11 @@ bool ZoneDatabase::GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct "snow_duration2, " // 53 "snow_duration3, " // 54 "snow_duration4, " // 55 - "gravity " // 56 + "gravity, " // 56 + "fast_regen_hp, " // 57 + "fast_regen_mana, " // 58 + "fast_regen_endurance, " // 59 + "npc_max_aggro_dist " // 60 "FROM zone WHERE zoneidnumber = %i AND version = %i", zoneid, instance_id); auto results = QueryDatabase(query); @@ -188,6 +192,11 @@ bool ZoneDatabase::GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct Log(Logs::General, Logs::Debug, "Zone Gravity is %f", zone_data->gravity); allow_mercs = true; + zone_data->FastRegenHP = atoi(row[57]); + zone_data->FastRegenMana = atoi(row[58]); + zone_data->FastRegenEndurance = atoi(row[59]); + zone_data->NPCAggroMaxDist = atoi(row[60]); + int bindable = 0; bindable = atoi(row[31]); diff --git a/zone/zoning.cpp b/zone/zoning.cpp index cbd928f21..9dcf0a774 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -717,6 +717,10 @@ void Client::GoToSafeCoords(uint16 zone_id, uint16 instance_id) { void Mob::Gate(uint8 bindnum) { GoToBind(bindnum); + if (RuleB(NPC, NPCHealOnGate) && this->IsNPC() && this->GetHPRatio() <= RuleR(NPC, NPCHealOnGateAmount)) { + auto HealAmount = (RuleR(NPC, NPCHealOnGateAmount) / 100); + SetHP(int(this->GetMaxHP() * HealAmount)); + } } void Client::Gate(uint8 bindnum) {