diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 486b3a9524ec5d..72cc5b3a746097 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -1,6 +1,10 @@ name: Codestyle on: pull_request: + types: + - opened + - reopened + - synchronize paths: - src/** - "!README.md" @@ -18,7 +22,7 @@ jobs: with: python-version: '3.10' - name: AzerothCore codestyle - run: python ./apps/codestyle/codestyle.py + run: python ./apps/codestyle/codestyle-cpp.py - name: C++ Advanced run: | sudo apt update -y diff --git a/.github/workflows/core-build-pch.yml b/.github/workflows/core-build-pch.yml index 2f71753ce85d6c..58411a5995c5fa 100644 --- a/.github/workflows/core-build-pch.yml +++ b/.github/workflows/core-build-pch.yml @@ -4,7 +4,10 @@ on: branches: - 'master' pull_request: - types: ['opened', 'synchronize', 'reopened'] + types: + - opened + - reopened + - synchronize concurrency: group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) diff --git a/.github/workflows/core_modules_build.yml b/.github/workflows/core_modules_build.yml index a5ef03cd5d6a1c..8b30ae67eea95c 100644 --- a/.github/workflows/core_modules_build.yml +++ b/.github/workflows/core_modules_build.yml @@ -5,10 +5,19 @@ on: - 'master' pull_request: types: - - labeled - opened - reopened - synchronize + paths: + - 'src/*' + - 'src/common/**/*' + - 'src/genrev/**/*' + - 'src/server/*' + - 'src/server/apps/**/*' + - 'src/server/database/**/*' + - 'src/server/game/**/*' + - 'src/server/shared/**/*' + - 'src/tools/**/*' concurrency: group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 9d2d0192e51687..1442ffb86ac888 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -6,9 +6,7 @@ on: pull_request: types: - labeled - - opened - synchronize - - reopened concurrency: group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) diff --git a/.github/workflows/pr_labeler.yml b/.github/workflows/pr_labeler.yml index 21b73fc842f51f..1c3632972a79f3 100644 --- a/.github/workflows/pr_labeler.yml +++ b/.github/workflows/pr_labeler.yml @@ -6,7 +6,7 @@ jobs: triage: runs-on: ubuntu-24.04 permissions: write-all - if: github.repository == 'azerothcore/azerothcore-wotlk' + if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/sql-codestyle.yml b/.github/workflows/sql-codestyle.yml index cfd83fe073e9b3..f60b95cd7c5205 100644 --- a/.github/workflows/sql-codestyle.yml +++ b/.github/workflows/sql-codestyle.yml @@ -1,6 +1,10 @@ name: Codestyle on: pull_request: + types: + - opened + - reopened + - synchronize paths: - data/** - "!README.md" @@ -10,8 +14,12 @@ jobs: triage: runs-on: ubuntu-latest name: SQL - if: github.repository == 'azerothcore/azerothcore-wotlk' + if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft steps: - uses: actions/checkout@v4 - - name: Check pending SQL - run: source ./apps/ci/ci-pending.sh + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: AzerothCore codestyle + run: python ./apps/codestyle/codestyle-sql.py diff --git a/.github/workflows/tools_build.yml b/.github/workflows/tools_build.yml index 12122e183aa974..22c212c6ba23f1 100644 --- a/.github/workflows/tools_build.yml +++ b/.github/workflows/tools_build.yml @@ -6,8 +6,6 @@ on: pull_request: types: - labeled - - opened - - reopened - synchronize concurrency: diff --git a/apps/ci/ci-pending.sh b/apps/ci/ci-pending.sh deleted file mode 100644 index 87858b33c6fc5b..00000000000000 --- a/apps/ci/ci-pending.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -set -e - -echo "Pending SQL check script:" -echo - -# We want to ensure the end of file has a semicolon and doesn't have extra -# newlines -find data/sql/updates/pending* -name "*.sql" -type f | while read -r file; do - # The first sed script collapses all strings into an empty string. The - # contents of strings aren't necessary for this check and its still valid - # sql. - # - # The second rule removes sql comments. - ERR_AT_EOF="$(sed -e "s/'.*'/''/g" -e 's/ --([^-])*$//' "$file" | tr -d '\n ' | tail -c 1)" - if [[ "$ERR_AT_EOF" != ";" ]]; then - echo "Missing Semicolon (;) or multiple newlines at the end of the file." - exit 1 - else - echo "> Semicolon check - OK" - fi -done - -find data/sql/updates/pending* -name "*.sql" -type f | while read -r file; do - if sed "s/'.*'\(.*\)/\1/g" "$file" | grep -q -i -E "broadcast_text"; then - echo "> broadcast_text check - Failed" - echo " - DON'T EDIT broadcast_text TABLE UNLESS YOU KNOW WHAT YOU ARE DOING!" - echo " - This error can safely be ignored if the changes are approved to be sniffed." - exit 1 - else - echo "> broadcast_text check - OK" - fi -done - -echo -echo "Everything looks good" diff --git a/apps/codestyle/codestyle.py b/apps/codestyle/codestyle-cpp.py similarity index 95% rename from apps/codestyle/codestyle.py rename to apps/codestyle/codestyle-cpp.py index 5cbf1691ab768c..77ff3f37645c98 100644 --- a/apps/codestyle/codestyle.py +++ b/apps/codestyle/codestyle-cpp.py @@ -222,6 +222,11 @@ def misc_codestyle_check(file: io, file_path: str) -> None: # used to check for "if/else (...) {" "} else" ignores "if/else (...) {...}" "#define ... if/else (...) {" ifelse_curlyregex = r"^[^#define].*\s+(if|else)(\s*\(.*\))?\s*{[^}]*$|}\s*else(\s*{[^}]*$)" + # used to catch double semicolons ";;" ignores "(;;)" + double_semiregex = r"[^(];;[^)]" + # used to catch tabs + tab_regex = r"\t" + # Parse all the file for line_number, line in enumerate(file, start = 1): if 'const auto&' in line: @@ -240,6 +245,15 @@ def misc_codestyle_check(file: io, file_path: str) -> None: print( f"Curly brackets are not allowed to be leading or trailing if/else statements. Place it on a new line: {file_path} at line {line_number}") check_failed = True + if re.match(double_semiregex, line): + print( + f"Double semicolon (;;) found in {file_path} at line {line_number}") + check_failed = True + if re.match(tab_regex, line): + print( + f"Tab found! Replace it to 4 spaces: {file_path} at line {line_number}") + check_failed = True + # Handle the script error and update the result output if check_failed: error_handler = True diff --git a/apps/codestyle/codestyle-sql.py b/apps/codestyle/codestyle-sql.py new file mode 100644 index 00000000000000..840be4904588d7 --- /dev/null +++ b/apps/codestyle/codestyle-sql.py @@ -0,0 +1,141 @@ +import io +import os +import sys +import re +import glob + +# Get the pending directory of the project +base_dir = os.getcwd() +pattern = os.path.join(base_dir, 'data/sql/updates/pending_db_*') +src_directory = glob.glob(pattern) + +# Global variables +error_handler = False +results = { + "Multiple blank lines check": "Passed", + "Trailing whitespace check": "Passed", + "SQL codestyle check": "Passed", +} + +# Collect all files in all directories +def collect_files_from_directories(directories: list) -> list: + all_files = [] + for directory in directories: + for root, _, files in os.walk(directory): + for file in files: + if not file.endswith('.sh'): # Skip .sh files + all_files.append(os.path.join(root, file)) + return all_files + +# Main function to parse all the files of the project +def parsing_file(files: list) -> None: + print("Starting AzerothCore SQL Codestyle check...") + print(" ") + print("Please read the SQL Standards for AzerothCore:") + print("https://www.azerothcore.org/wiki/sql-standards") + print(" ") + + # Iterate over all files + for file_path in files: + try: + with open(file_path, 'r', encoding='utf-8') as file: + multiple_blank_lines_check(file, file_path) + trailing_whitespace_check(file, file_path) + sql_check(file, file_path) + except UnicodeDecodeError: + print(f"\nCould not decode file {file_path}") + sys.exit(1) + + # Output the results + print("") + for check, result in results.items(): + print(f"{check} : {result}") + if error_handler: + print("\nPlease fix the codestyle issues above.") + sys.exit(1) + else: + print(f"\nEverything looks good") + +# Codestyle patterns checking for multiple blank lines +def multiple_blank_lines_check(file: io, file_path: str) -> None: + global error_handler, results + file.seek(0) # Reset file pointer to the beginning + check_failed = False + consecutive_blank_lines = 0 + # Parse all the file + for line_number, line in enumerate(file, start = 1): + if line.strip() == '': + consecutive_blank_lines += 1 + if consecutive_blank_lines > 1: + print(f"Multiple blank lines found in {file_path} at line {line_number - 1}") + check_failed = True + else: + consecutive_blank_lines = 0 + # Additional check for the end of the file + if consecutive_blank_lines >= 1: + print(f"Multiple blank lines found at the end of: {file_path}") + check_failed = True + # Handle the script error and update the result output + if check_failed: + error_handler = True + results["Multiple blank lines check"] = "Failed" + +# Codestyle patterns checking for whitespace at the end of the lines +def trailing_whitespace_check(file: io, file_path: str) -> None: + global error_handler, results + file.seek(0) # Reset file pointer to the beginning + check_failed = False + # Parse all the file + for line_number, line in enumerate(file, start = 1): + if line.endswith(' \n'): + print(f"Trailing whitespace found: {file_path} at line {line_number}") + check_failed = True + if check_failed: + error_handler = True + results["Trailing whitespace check"] = "Failed" + +# Codestyle patterns checking for various codestyle issues +def sql_check(file: io, file_path: str) -> None: + global error_handler, results + file.seek(0) # Reset file pointer to the beginning + check_failed = False + + # Parse all the file + for line_number, line in enumerate(file, start = 1): + if [match for match in ['broadcast_text'] if match in line]: + print( + f"DON'T EDIT broadcast_text TABLE UNLESS YOU KNOW WHAT YOU ARE DOING!\nThis error can safely be ignored if the changes are approved to be sniffed: {file_path} at line {line_number}") + check_failed = True + if [match for match in [';;'] if match in line]: + print( + f"Double semicolon (;;) found in {file_path} at line {line_number}") + check_failed = True + if re.match(r"\t", line): + print( + f"Tab found! Replace it to 4 spaces: {file_path} at line {line_number}") + check_failed = True + + # Ignore comments (remove content after --) + line_without_comment = re.sub(r'--.*', '', line).strip() + # Check if the last non-empty line ends with a semicolon + if not line_without_comment.endswith(';'): + print( + f"The last non-empty line does not end with a semicolon: {file_path}") + check_failed = True + + last_line = line[-1].strip() + if last_line: + print( + f"The last line is not a newline. Please add a newline: {file_path}") + check_failed = True + + # Handle the script error and update the result output + if check_failed: + error_handler = True + results["SQL codestyle check"] = "Failed" + +# Collect all files from matching directories +all_files = collect_files_from_directories(src_directory) + +# Main function +parsing_file(all_files) diff --git a/data/sql/updates/db_world/2024_12_22_00.sql b/data/sql/updates/db_world/2024_12_22_00.sql new file mode 100644 index 00000000000000..4d1c8d5f1ee732 --- /dev/null +++ b/data/sql/updates/db_world/2024_12_22_00.sql @@ -0,0 +1,5 @@ +-- DB update 2024_12_20_02 -> 2024_12_22_00 + +-- Remove Black Pearl Item from all creature loot tables. + +DELETE FROM `creature_loot_template` WHERE (`Entry` IN (6347, 6348, 6349, 6350, 6351, 6352, 6369, 6370, 6371, 6372, 6649, 7246, 7864, 7885, 7886, 7977, 8136, 8408, 8716, 8761, 8905, 11741, 11783, 11793, 12207, 12397, 13599, 13896, 14123, 14446, 14638, 14639, 15206, 504, 595, 747, 750, 751, 752, 922, 950, 979, 1729, 1791, 1809, 1907, 2043, 2255, 2408, 2505, 2544, 2574, 2583, 2659, 2680, 2701, 2715, 2718, 2791, 2793, 2817, 2926, 3715, 4096, 4374, 4467, 4648, 4663, 4687, 5232, 5238, 5268, 5269, 5286, 5307, 5308, 5328, 5331, 5332, 5333, 5334, 5335, 5336, 5337, 5343, 5423, 5431, 5432, 5461, 5462, 5466, 5615, 5616, 5618, 5623, 5843, 5856, 5974, 6116, 6117, 6135, 6136, 6137, 6138, 6140, 6143, 6144, 6147, 6190, 6193, 6194, 6195, 6196, 6199, 6202)) AND (`Item` IN (7971)); diff --git a/data/sql/updates/db_world/2024_12_22_01.sql b/data/sql/updates/db_world/2024_12_22_01.sql new file mode 100644 index 00000000000000..236f140adfc012 --- /dev/null +++ b/data/sql/updates/db_world/2024_12_22_01.sql @@ -0,0 +1,4 @@ +-- DB update 2024_12_22_00 -> 2024_12_22_01 +-- +DELETE FROM `command` where `name` = 'reload game_event_npc_vendor'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES ('reload game_event_npc_vendor', 3, 'Syntax: .reload game_event_npc_vendor\r Reload game_event_npc_vendor table.'); diff --git a/data/sql/updates/db_world/2024_12_22_02.sql b/data/sql/updates/db_world/2024_12_22_02.sql new file mode 100644 index 00000000000000..34c109f5aa3227 --- /dev/null +++ b/data/sql/updates/db_world/2024_12_22_02.sql @@ -0,0 +1,5 @@ +-- DB update 2024_12_22_01 -> 2024_12_22_02 +-- +DELETE FROM `quest_request_items_locale` WHERE `CompletionText` = ''; +DELETE FROM `quest_request_items_locale` WHERE `CompletionText` = 'NULL'; +DELETE FROM `quest_request_items_locale` WHERE `CompletionText` IS NULL; diff --git a/src/cmake/macros/FindMySQL.cmake b/src/cmake/macros/FindMySQL.cmake index b78dfc2d87fdc2..2a8de7e7a8c8d5 100644 --- a/src/cmake/macros/FindMySQL.cmake +++ b/src/cmake/macros/FindMySQL.cmake @@ -161,7 +161,7 @@ if(WIN32) set(_MYSQL_ROOT_PATHS ${_MYSQL_ROOT_PATHS} - ${_MYSQL_ROOT_PATHS_VERSION_SUBDIRECTORIES} + ${_MYSQL_ROOT_PATHS_VERSION_SUBDIRECTORIES} "${PROGRAM_FILES_64}/MySQL" "${PROGRAM_FILES_32}/MySQL" "$ENV{SystemDrive}/MySQL" @@ -180,7 +180,7 @@ find_path(MYSQL_INCLUDE_DIR /usr/local/include /usr/local/include/mysql /usr/local/mysql/include - ${_MYSQL_ROOT_PATHS} + ${_MYSQL_ROOT_PATHS} PATH_SUFFIXES include include/mysql @@ -270,10 +270,10 @@ set(MYSQL_REQUIRED_VARS "") foreach(_comp IN LISTS MySQL_FIND_COMPONENTS) if(_comp STREQUAL "lib") set(MySQL_${_comp}_WANTED TRUE) - if(MySQL_FIND_REQUIRED_${_comp}) - list(APPEND MYSQL_REQUIRED_VARS "MYSQL_LIBRARY") - list(APPEND MYSQL_REQUIRED_VARS "MYSQL_INCLUDE_DIR") - endif() + if(MySQL_FIND_REQUIRED_${_comp}) + list(APPEND MYSQL_REQUIRED_VARS "MYSQL_LIBRARY") + list(APPEND MYSQL_REQUIRED_VARS "MYSQL_INCLUDE_DIR") + endif() if(EXISTS "${MYSQL_LIBRARY}" AND EXISTS "${MYSQL_INCLUDE_DIR}") set(MySQL_${_comp}_FOUND TRUE) else() @@ -281,9 +281,9 @@ foreach(_comp IN LISTS MySQL_FIND_COMPONENTS) endif() elseif(_comp STREQUAL "binary") set(MySQL_${_comp}_WANTED TRUE) - if(MySQL_FIND_REQUIRED_${_comp}) - list(APPEND MYSQL_REQUIRED_VARS "MYSQL_EXECUTABLE") - endif() + if(MySQL_FIND_REQUIRED_${_comp}) + list(APPEND MYSQL_REQUIRED_VARS "MYSQL_EXECUTABLE") + endif() if(EXISTS "${MYSQL_EXECUTABLE}" ) set(MySQL_${_comp}_FOUND TRUE) else() diff --git a/src/common/Asio/SteadyTimer.h b/src/common/Asio/SteadyTimer.h new file mode 100644 index 00000000000000..2c5a6fd7fad5b6 --- /dev/null +++ b/src/common/Asio/SteadyTimer.h @@ -0,0 +1,31 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _STEADYTIMER_H +#define _STEADYTIMER_H + +#include + +namespace Acore::Asio::SteadyTimer +{ + inline auto GetExpirationTime(int32 seconds) + { + return std::chrono::steady_clock::now() + std::chrono::seconds(seconds); + } +} + +#endif // _STEADYTIMER_H diff --git a/src/common/Metric/Metric.cpp b/src/common/Metric/Metric.cpp index a6cd4b1f276ea8..7c4771dcad2db5 100644 --- a/src/common/Metric/Metric.cpp +++ b/src/common/Metric/Metric.cpp @@ -18,6 +18,7 @@ #include "Metric.h" #include "Config.h" #include "Log.h" +#include "SteadyTimer.h" #include "Strand.h" #include "Tokenize.h" #include @@ -246,9 +247,7 @@ void Metric::ScheduleSend() { if (_enabled) { - // Calculate the expiration time - auto expirationTime = std::chrono::steady_clock::now() + std::chrono::seconds(_updateInterval); - _batchTimer->expires_at(expirationTime); + _batchTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(_updateInterval)); _batchTimer->async_wait(std::bind(&Metric::SendBatch, this)); } else @@ -281,9 +280,7 @@ void Metric::ScheduleOverallStatusLog() { if (_enabled) { - // Calculate the expiration time _overallStatusTimerInterval from now - auto expirationTime = std::chrono::steady_clock::now() + std::chrono::seconds(_overallStatusTimerInterval); - _overallStatusTimer->expires_at(expirationTime); + _overallStatusTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(_overallStatusTimerInterval)); _overallStatusTimer->async_wait([this](const boost::system::error_code&) { _overallStatusTimerTriggered = true; diff --git a/src/server/apps/authserver/Main.cpp b/src/server/apps/authserver/Main.cpp index 6030d9114220ae..903a12134d0c64 100644 --- a/src/server/apps/authserver/Main.cpp +++ b/src/server/apps/authserver/Main.cpp @@ -38,6 +38,7 @@ #include "RealmList.h" #include "SecretMgr.h" #include "SharedDefines.h" +#include "SteadyTimer.h" #include "Util.h" #include #include @@ -179,17 +180,13 @@ int main(int argc, char** argv) int32 dbPingInterval = sConfigMgr->GetOption("MaxPingTime", 30); std::shared_ptr dbPingTimer = std::make_shared(*ioContext); - // Calculate the expiration time - auto expirationTime = std::chrono::steady_clock::now() + std::chrono::seconds(dbPingInterval); - dbPingTimer->expires_at(expirationTime); + dbPingTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(dbPingInterval * MINUTE)); dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, std::weak_ptr(dbPingTimer), dbPingInterval, std::placeholders::_1)); int32 banExpiryCheckInterval = sConfigMgr->GetOption("BanExpiryCheckInterval", 60); std::shared_ptr banExpiryCheckTimer = std::make_shared(*ioContext); - // Calculate the expiration time - auto expirationTimeBanExpiry = std::chrono::steady_clock::now() + std::chrono::seconds(banExpiryCheckInterval); - banExpiryCheckTimer->expires_at(expirationTimeBanExpiry); + banExpiryCheckTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(banExpiryCheckInterval)); banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, std::weak_ptr(banExpiryCheckTimer), banExpiryCheckInterval, std::placeholders::_1)); // Start the io service worker loop @@ -252,9 +249,7 @@ void KeepDatabaseAliveHandler(std::weak_ptr dbPingTim LOG_INFO("server.authserver", "Ping MySQL to keep connection alive"); LoginDatabase.KeepAlive(); - // Calculate the expiration time - auto expirationTime = std::chrono::steady_clock::now() + std::chrono::seconds(dbPingInterval); - dbPingTimer->expires_at(expirationTime); + dbPingTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(dbPingInterval)); dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, dbPingTimerRef, dbPingInterval, std::placeholders::_1)); } } @@ -269,9 +264,7 @@ void BanExpiryHandler(std::weak_ptr banExpiryCheckTim LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); - // Calculate the expiration time - auto expirationTime = std::chrono::steady_clock::now() + std::chrono::seconds(banExpiryCheckInterval); - banExpiryCheckTimer->expires_at(expirationTime); + banExpiryCheckTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(banExpiryCheckInterval)); banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, banExpiryCheckTimerRef, banExpiryCheckInterval, std::placeholders::_1)); } } diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp index 6f4dc6d4d73120..1a3842d07c4ffb 100644 --- a/src/server/apps/worldserver/Main.cpp +++ b/src/server/apps/worldserver/Main.cpp @@ -47,6 +47,7 @@ #include "ScriptMgr.h" #include "SecretMgr.h" #include "SharedDefines.h" +#include "SteadyTimer.h" #include "World.h" #include "WorldSocket.h" #include "WorldSocketMgr.h" @@ -90,9 +91,7 @@ class FreezeDetector static void Start(std::shared_ptr const& freezeDetector) { - // Calculate the expiration time 5seconds from now - auto expirationTime = std::chrono::steady_clock::now() + std::chrono::seconds(5); - freezeDetector->_timer.expires_at(expirationTime); + freezeDetector->_timer.expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(5)); freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, std::weak_ptr(freezeDetector), std::placeholders::_1)); } @@ -644,9 +643,7 @@ void FreezeDetector::Handler(std::weak_ptr freezeDetectorRef, bo } } - // Calculate the expiration time - auto expirationTime = std::chrono::steady_clock::now() + std::chrono::seconds(1); - freezeDetector->_timer.expires_at(expirationTime); + freezeDetector->_timer.expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(1)); freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, freezeDetectorRef, std::placeholders::_1)); } } diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index a88c6deae79c12..a6dd8742da418e 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -2047,11 +2047,18 @@ WaterBreath.Timer = 180000 EnableLowLevelRegenBoost = 1 # -# Rate.MoveSpeed -# Description: Movement speed rate. +# Rate.MoveSpeed.Player +# Description: Movement speed rate for players. # Default: 1 -Rate.MoveSpeed = 1 +Rate.MoveSpeed.Player = 1 + +# +# Rate.MoveSpeed.NPC +# Description: Movement speed rate for NPCs. +# Default: 1 + +Rate.MoveSpeed.NPC = 1 # # Rate.Damage.Fall diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 323f4ccbd8d204..d6081c983e8864 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -86,6 +86,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_INS_CREATURE, "INSERT INTO creature (guid, id1, id2, id3, map, spawnMask, phaseMask, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, currentwaypoint, curhealth, curmana, MovementType, npcflag, unit_flags, dynamicflags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(WORLD_DEL_GAME_EVENT_CREATURE, "DELETE FROM game_event_creature WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_DEL_GAME_EVENT_MODEL_EQUIP, "DELETE FROM game_event_model_equip WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(WORLD_SEL_GAME_EVENT_NPC_VENDOR, "SELECT eventEntry, guid, item, maxcount, incrtime, ExtendedCost FROM game_event_npc_vendor ORDER BY guid, slot ASC", CONNECTION_SYNCH); PrepareStatement(WORLD_INS_GAMEOBJECT, "INSERT INTO gameobject (guid, id, map, spawnMask, phaseMask, position_x, position_y, position_z, orientation, rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(WORLD_INS_DISABLES, "INSERT INTO disables (entry, sourceType, flags, comment) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_DISABLES, "SELECT entry FROM disables WHERE entry = ? AND sourceType = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/WorldDatabase.h b/src/server/database/Database/Implementation/WorldDatabase.h index ebfdcfe0705bf2..04976672579512 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.h +++ b/src/server/database/Database/Implementation/WorldDatabase.h @@ -92,6 +92,7 @@ enum WorldDatabaseStatements : uint32 WORLD_INS_CREATURE, WORLD_DEL_GAME_EVENT_CREATURE, WORLD_DEL_GAME_EVENT_MODEL_EQUIP, + WORLD_SEL_GAME_EVENT_NPC_VENDOR, WORLD_INS_GAMEOBJECT, WORLD_SEL_DISABLES, WORLD_INS_DISABLES, diff --git a/src/server/game/AuctionHouse/AuctionHouseSearcher.cpp b/src/server/game/AuctionHouse/AuctionHouseSearcher.cpp index f1cda1fb14f833..0d50d7eafa9010 100644 --- a/src/server/game/AuctionHouse/AuctionHouseSearcher.cpp +++ b/src/server/game/AuctionHouse/AuctionHouseSearcher.cpp @@ -156,7 +156,7 @@ void AuctionHouseWorkerThread::SearchListRequest(AuctionSearchListRequest const& if (!searchListRequest.searchInfo.sorting.empty() && auctionEntries.size() > MAX_AUCTIONS_PER_PAGE) { - AuctionSorter sorter(&searchListRequest.searchInfo.sorting, searchListRequest.playerInfo.locdbc_idx); + AuctionSorter sorter(&searchListRequest.searchInfo.sorting, searchListRequest.playerInfo.loc_idx); std::sort(auctionEntries.begin(), auctionEntries.end(), sorter); } @@ -326,7 +326,7 @@ void AuctionHouseWorkerThread::BuildListAuctionItems(AuctionSearchListRequest co // No need to do any of this if no search term was entered if (!searchRequest.searchInfo.wsearchedname.empty()) { - if (Aitem.itemName[searchRequest.playerInfo.locdbc_idx].find(searchRequest.searchInfo.wsearchedname) == std::wstring::npos) + if (Aitem.itemName[searchRequest.playerInfo.loc_idx].find(searchRequest.searchInfo.wsearchedname) == std::wstring::npos) continue; } diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index 9513d1fc9877c7..28b923d1e04057 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -274,6 +274,11 @@ void TempSummon::InitSummon() } } +void TempSummon::UpdateObjectVisibilityOnCreate() +{ + WorldObject::UpdateObjectVisibility(true); +} + void TempSummon::SetTempSummonType(TempSummonType type) { m_type = type; diff --git a/src/server/game/Entities/Creature/TemporarySummon.h b/src/server/game/Entities/Creature/TemporarySummon.h index e5bc369bd3d1a1..18cc729794a4fe 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.h +++ b/src/server/game/Entities/Creature/TemporarySummon.h @@ -45,6 +45,7 @@ class TempSummon : public Creature virtual void InitStats(uint32 lifetime); virtual void InitSummon(); virtual void UnSummon(uint32 msTime = 0); + void UpdateObjectVisibilityOnCreate() override; void RemoveFromWorld() override; void SetTempSummonType(TempSummonType type); void SaveToDB(uint32 /*mapid*/, uint8 /*spawnMask*/, uint32 /*phaseMask*/) override {} diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 317b3441cec90f..fe37ea46c92f95 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2274,7 +2274,9 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert summon->InitSummon(); - //ObjectAccessor::UpdateObjectVisibility(summon); + // call MoveInLineOfSight for nearby creatures + Acore::AIRelocationNotifier notifier(*summon); + Cell::VisitAllObjects(summon, notifier, GetVisibilityRange()); return summon; } diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 2fe73c1aeccef4..043a34d6d6b0fd 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -558,6 +558,7 @@ class WorldObject : public Object, public WorldLocation void DestroyForNearbyPlayers(); virtual void UpdateObjectVisibility(bool forced = true, bool fromUpdate = false); + virtual void UpdateObjectVisibilityOnCreate() { UpdateObjectVisibility(true); } void BuildUpdate(UpdateDataMapType& data_map, UpdatePlayerSet& player_set) override; void GetCreaturesWithEntryInRange(std::list& creatureList, float radius, uint32 entry); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index e4d46d1a21d23b..cca7f3fb299a72 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -444,7 +444,7 @@ struct Runes struct EnchantDuration { - EnchantDuration() = default;; + EnchantDuration() = default; EnchantDuration(Item* _item, EnchantmentSlot _slot, uint32 _leftduration) : item(_item), slot(_slot), leftduration(_leftduration) { ASSERT(item); }; diff --git a/src/server/game/Entities/Player/PlayerSettings.cpp b/src/server/game/Entities/Player/PlayerSettings.cpp index 7e45d03db76e3f..0c25361dd2383f 100644 --- a/src/server/game/Entities/Player/PlayerSettings.cpp +++ b/src/server/game/Entities/Player/PlayerSettings.cpp @@ -38,7 +38,7 @@ void Player::_LoadCharacterSettings(PreparedQueryResult result) { Field* fields = result->Fetch(); - std::string source = fields[0].Get();; + std::string source = fields[0].Get(); std::string data = fields[1].Get(); std::vector tokens = Acore::Tokenize(data, ' ', false); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index c9ee07985b9916..f42d52f8cc75b5 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -21307,7 +21307,7 @@ bool Unit::IsInDisallowedMountForm() const return true; } - if (!(shapeshift->flags1 & 0x1)) + if (!(shapeshift->flags1 & SHAPESHIFT_FLAG_STANCE)) { return true; } diff --git a/src/server/game/Entities/Unit/UnitDefines.h b/src/server/game/Entities/Unit/UnitDefines.h index b694bbcd9b5926..040e2c89f16a05 100644 --- a/src/server/game/Entities/Unit/UnitDefines.h +++ b/src/server/game/Entities/Unit/UnitDefines.h @@ -99,6 +99,27 @@ enum ShapeshiftForm FORM_SPIRITOFREDEMPTION = 0x20 }; +enum ShapeshiftFlags +{ + SHAPESHIFT_FLAG_STANCE = 0x00000001, // Form allows various player activities, which normally cause "You can't X while shapeshifted." errors (npc/go interaction, item use, etc) + SHAPESHIFT_FLAG_NOT_TOGGLEABLE = 0x00000002, // NYI + SHAPESHIFT_FLAG_PERSIST_ON_DEATH = 0x00000004, // NYI + SHAPESHIFT_FLAG_CAN_NPC_INTERACT = 0x00000008, // Form unconditionally allows talking to NPCs while shapeshifted (even if other activities are disabled) + SHAPESHIFT_FLAG_DONT_USE_WEAPON = 0x00000010, // NYI + SHAPESHIFT_FLAG_AGILITY_ATTACK_BONUS = 0x00000020, // Druid Cat form + SHAPESHIFT_FLAG_CAN_USE_EQUIPPED_ITEMS = 0x00000040, // NYI + SHAPESHIFT_FLAG_CAN_USE_ITEMS = 0x00000080, // NYI + SHAPESHIFT_FLAG_DONT_AUTO_UNSHIFT = 0x00000100, // Handled at client side + SHAPESHIFT_FLAG_CONSIDERED_DEAD = 0x00000200, // NYI + SHAPESHIFT_FLAG_CAN_ONLY_CAST_SHAPESHIFT_SPELLS = 0x00000400, // NYI + SHAPESHIFT_FLAG_STANCE_CANCEL_AT_FLIGHTMASTER = 0x00000800, // NYI + SHAPESHIFT_FLAG_NO_EMOTE_SOUNDS = 0x00001000, // NYI + SHAPESHIFT_FLAG_NO_TRIGGER_TELEPORT = 0x00002000, // NYI + SHAPESHIFT_FLAG_CANNOT_CHANGE_EQUIPPED_ITEMS = 0x00004000, // NYI + SHAPESHIFT_FLAG_RESUMMON_PETS_ON_UNSHIFT = 0x00008000, // NYI + SHAPESHIFT_FLAG_CANNOT_USE_GAME_OBJECTS = 0x00010000, // NYI +}; + // low byte (0 from 0..3) of UNIT_FIELD_BYTES_2 enum SheathState { diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp index a914182c893d09..0909ae7dbd4e99 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -236,6 +236,92 @@ void GameEventMgr::StopEvent(uint16 event_id, bool overwrite) sScriptMgr->OnGameEventStop(event_id); } +void GameEventMgr::LoadEventVendors() +{ + uint32 oldMSTime = getMSTime(); + WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_GAME_EVENT_NPC_VENDOR); + PreparedQueryResult result = WorldDatabase.Query(stmt); + + if (!result) + { + LOG_WARN("server.loading", ">> Loaded 0 Vendor Additions In Game Events. DB Table `game_event_npc_vendor` Is Empty."); + LOG_INFO("server.loading", " "); + return; + } + + uint32 count = 0; + std::unordered_set processedEvents; + + do + { + Field* fields = result->Fetch(); + uint8 eventId = fields[0].Get(); + ObjectGuid::LowType guid = fields[1].Get(); + + if (eventId >= mGameEventVendors.size()) + { + LOG_ERROR("sql.sql", "Table `game_event_npc_vendor` has invalid eventEntry ({}) for GUID ({}), skipped.", eventId, guid); + continue; + } + + // Clear existing vendors for this event only once + if (processedEvents.find(eventId) == processedEvents.end()) + { + // Remove vendor items from in-memory data + for (auto& entry : mGameEventVendors[eventId]) + { + sObjectMgr->RemoveVendorItem(entry.entry, entry.item, false); + } + mGameEventVendors[eventId].clear(); + processedEvents.insert(eventId); + } + + NPCVendorList& vendors = mGameEventVendors[eventId]; + NPCVendorEntry newEntry; + newEntry.item = fields[2].Get(); + newEntry.maxcount = fields[3].Get(); + newEntry.incrtime = fields[4].Get(); + newEntry.ExtendedCost = fields[5].Get(); + + // Get the event NPC flag for validity check + uint32 event_npc_flag = 0; + NPCFlagList& flist = mGameEventNPCFlags[eventId]; + for (NPCFlagList::const_iterator itr = flist.begin(); itr != flist.end(); ++itr) + { + if (itr->first == guid) + { + event_npc_flag = itr->second; + break; + } + } + + // Get creature entry + newEntry.entry = 0; + if (CreatureData const* data = sObjectMgr->GetCreatureData(guid)) + newEntry.entry = data->id1; + + // Validate vendor item + if (!sObjectMgr->IsVendorItemValid(newEntry.entry, newEntry.item, newEntry.maxcount, newEntry.incrtime, newEntry.ExtendedCost, nullptr, nullptr, event_npc_flag)) + { + LOG_ERROR("sql.sql", "Table `game_event_npc_vendor` has invalid item ({}) for guid ({}) for event ({}), skipped.", + newEntry.item, newEntry.entry, eventId); + continue; + } + + // Add the item to the vendor if event is active + if (IsEventActive(eventId)) + sObjectMgr->AddVendorItem(newEntry.entry, newEntry.item, newEntry.maxcount, newEntry.incrtime, newEntry.ExtendedCost, false); + + vendors.push_back(newEntry); + + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} Vendor Additions In Game Events in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); + +} + void GameEventMgr::LoadFromDB() { { @@ -852,69 +938,7 @@ void GameEventMgr::LoadFromDB() } LOG_INFO("server.loading", "Loading Game Event Vendor Additions Data..."); - { - uint32 oldMSTime = getMSTime(); - - // 0 1 2 3 4 5 - QueryResult result = WorldDatabase.Query("SELECT eventEntry, guid, item, maxcount, incrtime, ExtendedCost FROM game_event_npc_vendor ORDER BY guid, slot ASC"); - - if (!result) - { - LOG_WARN("server.loading", ">> Loaded 0 Vendor Additions In Game Events. DB Table `game_event_npc_vendor` Is Empty."); - LOG_INFO("server.loading", " "); - } - else - { - uint32 count = 0; - do - { - Field* fields = result->Fetch(); - - uint8 event_id = fields[0].Get(); - - if (event_id >= mGameEventVendors.size()) - { - LOG_ERROR("sql.sql", "`game_event_npc_vendor` game event id ({}) is out of range compared to max event id in `game_event`", event_id); - continue; - } - - NPCVendorList& vendors = mGameEventVendors[event_id]; - NPCVendorEntry newEntry; - ObjectGuid::LowType guid = fields[1].Get(); - newEntry.item = fields[2].Get(); - newEntry.maxcount = fields[3].Get(); - newEntry.incrtime = fields[4].Get(); - newEntry.ExtendedCost = fields[5].Get(); - // get the event npc flag for checking if the npc will be vendor during the event or not - uint32 event_npc_flag = 0; - NPCFlagList& flist = mGameEventNPCFlags[event_id]; - for (NPCFlagList::const_iterator itr = flist.begin(); itr != flist.end(); ++itr) - { - if (itr->first == guid) - { - event_npc_flag = itr->second; - break; - } - } - // get creature entry - newEntry.entry = 0; - - if (CreatureData const* data = sObjectMgr->GetCreatureData(guid)) - newEntry.entry = data->id1; - - // check validity with event's npcflag - if (!sObjectMgr->IsVendorItemValid(newEntry.entry, newEntry.item, newEntry.maxcount, newEntry.incrtime, newEntry.ExtendedCost, nullptr, nullptr, event_npc_flag)) - continue; - - vendors.push_back(newEntry); - - ++count; - } while (result->NextRow()); - - LOG_INFO("server.loading", ">> Loaded {} Vendor Additions In Game Events in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); - LOG_INFO("server.loading", " "); - } - } + LoadEventVendors(); LOG_INFO("server.loading", "Loading Game Event Battleground Data..."); { diff --git a/src/server/game/Events/GameEventMgr.h b/src/server/game/Events/GameEventMgr.h index c251cf8038729b..b997efe06b6782 100644 --- a/src/server/game/Events/GameEventMgr.h +++ b/src/server/game/Events/GameEventMgr.h @@ -121,6 +121,8 @@ class GameEventMgr void StopEvent(uint16 event_id, bool overwrite = false); void HandleQuestComplete(uint32 quest_id); // called on world event type quest completions uint32 GetNPCFlag(Creature* cr); + // Load the game event npc vendor table from the DB + void LoadEventVendors(); [[nodiscard]] uint32 GetHolidayEventId(uint32 holidayId) const; private: void SendWorldStateUpdate(Player* player, uint16 event_id); diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index e248575a676a7f..6fe94f32143ff7 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -268,7 +268,7 @@ void WorldSession::HandleWhoOpcode(WorldPacket& recvData) return; wstrToLower(wpacketPlayerName); - wstrToLower(wpacketGuildName);; + wstrToLower(wpacketGuildName); // client send in case not set max level value 100 but Acore supports 255 max level, // update it to show GMs with characters after 100 level diff --git a/src/server/game/Instances/InstanceSaveMgr.h b/src/server/game/Instances/InstanceSaveMgr.h index 7c6f18371e739d..686a6fe729a5f4 100644 --- a/src/server/game/Instances/InstanceSaveMgr.h +++ b/src/server/game/Instances/InstanceSaveMgr.h @@ -108,7 +108,7 @@ class InstanceSaveMgr friend class InstanceSave; private: - InstanceSaveMgr() = default;; + InstanceSaveMgr() = default; ~InstanceSaveMgr(); public: diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index ab9df20b02216c..87ca2d8d57ddbf 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -561,7 +561,7 @@ bool Map::AddToMap(T* obj, bool checkTransport) if (obj->IsInWorld()) { ASSERT(obj->IsInGrid()); - obj->UpdateObjectVisibility(true); + obj->UpdateObjectVisibilityOnCreate(); return true; } diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 5f2141b91af446..7f05148cc1bf82 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -5832,7 +5832,7 @@ SpellCastResult Spell::CheckCast(bool strict) if (effInfo->ApplyAuraName == SPELL_AURA_MOD_SHAPESHIFT) { SpellShapeshiftFormEntry const* shapeShiftEntry = sSpellShapeshiftFormStore.LookupEntry(effInfo->MiscValue); - if (shapeShiftEntry && (shapeShiftEntry->flags1 & 1) == 0) // unk flag + if (shapeShiftEntry && (shapeShiftEntry->flags1 & SHAPESHIFT_FLAG_STANCE) == 0) checkMask |= VEHICLE_SEAT_FLAG_UNCONTROLLED; break; } diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 9716102a5cdf9c..421f96a5d0dc5e 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1457,7 +1457,7 @@ SpellCastResult SpellInfo::CheckShapeshift(uint32 form) const LOG_ERROR("spells", "GetErrorAtShapeshiftedCast: unknown shapeshift {}", form); return SPELL_CAST_OK; } - actAsShifted = !(shapeInfo->flags1 & 1); // shapeshift acts as normal form for spells + actAsShifted = !(shapeInfo->flags1 & SHAPESHIFT_FLAG_STANCE); // shapeshift acts as normal form for spells } if (actAsShifted) @@ -1477,7 +1477,7 @@ SpellCastResult SpellInfo::CheckShapeshift(uint32 form) const // Check if stance disables cast of not-stance spells // Example: cannot cast any other spells in zombie or ghoul form /// @todo: Find a way to disable use of these spells clientside - if (shapeInfo && shapeInfo->flags1 & 0x400) + if (shapeInfo && (shapeInfo->flags1 & SHAPESHIFT_FLAG_CAN_ONLY_CAST_SHAPESHIFT_SPELLS)) { if (!(stanceMask & Stances)) return SPELL_FAILED_ONLY_SHAPESHIFT; diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index e32eadda6fdcc7..393db767e852ef 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -518,7 +518,8 @@ enum Rates RATE_DURABILITY_LOSS_PARRY, RATE_DURABILITY_LOSS_ABSORB, RATE_DURABILITY_LOSS_BLOCK, - RATE_MOVESPEED, + RATE_MOVESPEED_PLAYER, + RATE_MOVESPEED_NPC, RATE_MISS_CHANCE_MULTIPLIER_TARGET_CREATURE, RATE_MISS_CHANCE_MULTIPLIER_TARGET_PLAYER, MAX_RATES diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index f2fd3246a59064..ddcc393fd805b1 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -576,13 +576,24 @@ void World::LoadConfigSettings(bool reload) LOG_ERROR("server.loading", "Rate.Talent.Pet ({}) must be > 0. Using 1 instead.", _rate_values[RATE_TALENT_PET]); _rate_values[RATE_TALENT_PET] = 1.0f; } - _rate_values[RATE_MOVESPEED] = sConfigMgr->GetOption("Rate.MoveSpeed", 1.0f); - if (_rate_values[RATE_MOVESPEED] < 0) + // Controls Player movespeed rate. + _rate_values[RATE_MOVESPEED_PLAYER] = sConfigMgr->GetOption("Rate.MoveSpeed.Player", 1.0f); + if (_rate_values[RATE_MOVESPEED_PLAYER] < 0) { - LOG_ERROR("server.loading", "Rate.MoveSpeed ({}) must be > 0. Using 1 instead.", _rate_values[RATE_MOVESPEED]); - _rate_values[RATE_MOVESPEED] = 1.0f; + LOG_ERROR("server.loading", "Rate.MoveSpeed.Player ({}) must be > 0. Using 1 instead.", _rate_values[RATE_MOVESPEED_PLAYER]); + _rate_values[RATE_MOVESPEED_PLAYER] = 1.0f; } - for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) playerBaseMoveSpeed[i] = baseMoveSpeed[i] * _rate_values[RATE_MOVESPEED]; + for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) playerBaseMoveSpeed[i] = baseMoveSpeed[i] * _rate_values[RATE_MOVESPEED_PLAYER]; + + // Controls all npc movespeed rate. + _rate_values[RATE_MOVESPEED_NPC] = sConfigMgr->GetOption("Rate.MoveSpeed.NPC", 1.0f); + if (_rate_values[RATE_MOVESPEED_NPC] < 0) + { + LOG_ERROR("server.loading", "Rate.MoveSpeed.NPC ({}) must be > 0. Using 1 instead.", _rate_values[RATE_MOVESPEED_NPC]); + _rate_values[RATE_MOVESPEED_NPC] = 1.0f; + } + for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) baseMoveSpeed[i] *= _rate_values[RATE_MOVESPEED_NPC]; + _rate_values[RATE_CORPSE_DECAY_LOOTED] = sConfigMgr->GetOption("Rate.Corpse.Decay.Looted", 0.5f); _rate_values[RATE_DURABILITY_LOSS_ON_DEATH] = sConfigMgr->GetOption("DurabilityLoss.OnDeath", 10.0f); diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 19ed065e8eee7e..7a5005e5daae29 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -374,20 +374,24 @@ class npc_commandscript : public CommandScript return true; } - static bool HandleNpcDeleteCommand(ChatHandler* handler) + static bool HandleNpcDeleteCommand(ChatHandler* handler, Optional lowGuid) { - Creature* unit = handler->getSelectedCreature(); + Creature* creature; + if (lowGuid.has_value()) + creature = handler->GetCreatureFromPlayerMapByDbGuid(*lowGuid); + else + creature = handler->getSelectedCreature(); - if (!unit || unit->IsPet() || unit->IsTotem()) + if (!creature || creature->IsPet() || creature->IsTotem()) { handler->SendErrorMessage(LANG_SELECT_CREATURE); return false; } // Delete the creature - unit->CombatStop(); - unit->DeleteFromDB(); - unit->AddObjectToRemoveList(); + creature->CombatStop(); + creature->DeleteFromDB(); + creature->AddObjectToRemoveList(); handler->SendSysMessage(LANG_COMMAND_DELCREATMESSAGE); @@ -712,25 +716,33 @@ class npc_commandscript : public CommandScript } //move selected creature - static bool HandleNpcMoveCommand(ChatHandler* handler) + static bool HandleNpcMoveCommand(ChatHandler* handler, Optional guid) { - Creature* creature = handler->getSelectedCreature(); + Creature* creature; + if (guid.has_value()) + creature = handler->GetCreatureFromPlayerMapByDbGuid(*guid); + else + creature = handler->getSelectedCreature(); if (!creature) return false; - ObjectGuid::LowType lowguid = creature->GetSpawnId(); + ObjectGuid::LowType lowGuid; + if (guid.has_value()) + lowGuid = *guid; + else + lowGuid = creature->GetSpawnId(); - CreatureData const* data = sObjectMgr->GetCreatureData(lowguid); + CreatureData const* data = sObjectMgr->GetCreatureData(lowGuid); if (!data) { - handler->SendErrorMessage(LANG_COMMAND_CREATGUIDNOTFOUND, lowguid); + handler->SendErrorMessage(LANG_COMMAND_CREATGUIDNOTFOUND, lowGuid); return false; } if (handler->GetSession()->GetPlayer()->GetMapId() != data->mapid) { - handler->SendErrorMessage(LANG_COMMAND_CREATUREATSAMEMAP, lowguid); + handler->SendErrorMessage(LANG_COMMAND_CREATUREATSAMEMAP, lowGuid); return false; } @@ -764,7 +776,7 @@ class npc_commandscript : public CommandScript stmt->SetData(1, y); stmt->SetData(2, z); stmt->SetData(3, o); - stmt->SetData(4, lowguid); + stmt->SetData(4, lowGuid); WorldDatabase.Execute(stmt); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index a8544c2180f50f..b11eb2c1e2a046 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -135,6 +135,7 @@ class reload_commandscript : public CommandScript { "npc_spellclick_spells", HandleReloadSpellClickSpellsCommand, SEC_ADMINISTRATOR, Console::Yes }, { "npc_trainer", HandleReloadNpcTrainerCommand, SEC_ADMINISTRATOR, Console::Yes }, { "npc_vendor", HandleReloadNpcVendorCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "game_event_npc_vendor", HandleReloadGameEventNPCVendorCommand, SEC_ADMINISTRATOR, Console::Yes }, { "page_text", HandleReloadPageTextsCommand, SEC_ADMINISTRATOR, Console::Yes }, { "pickpocketing_loot_template", HandleReloadLootTemplatesPickpocketingCommand, SEC_ADMINISTRATOR, Console::Yes }, { "points_of_interest", HandleReloadPointsOfInterestCommand, SEC_ADMINISTRATOR, Console::Yes }, @@ -765,6 +766,14 @@ class reload_commandscript : public CommandScript return true; } + static bool HandleReloadGameEventNPCVendorCommand(ChatHandler* handler) + { + LOG_INFO("server.loading", "Reloading `game_event_npc_vendor` Table!"); + sGameEventMgr->LoadEventVendors(); + handler->SendGlobalGMSysMessage("DB table `game_event_npc_vendor` reloaded."); + return true; + } + static bool HandleReloadPointsOfInterestCommand(ChatHandler* handler) { LOG_INFO("server.loading", "Reloading `points_of_interest` Table!"); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/instance_hyjal.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/instance_hyjal.cpp index 4a11a8d1325fc7..c0c95f6c780bb1 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/instance_hyjal.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/instance_hyjal.cpp @@ -190,6 +190,8 @@ class instance_hyjal : public InstanceMapScript if (creature->IsSummon() && _bossWave != TO_BE_DECIDED) { + if (_currentWave == 0) + creature->SetDisableReputationGain(true); DoUpdateWorldState(WORLD_STATE_ENEMYCOUNT, ++trash); // Update the instance wave count on new trash spawn _encounterNPCs.insert(creature->GetGUID()); // Used for despawning on wipe } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp index 57239edd3c442f..4b7ab2e131f1cb 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp @@ -332,7 +332,7 @@ class boss_blood_queen_lana_thel : public CreatureScript const Map::PlayerList& pl = me->GetMap()->GetPlayers(); for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) if (Player* p = itr->GetSource()) - if (p->IsAlive() && p != me->GetVictim() && p->GetGUID() != _offtankGUID && !p->IsGameMaster() && p->GetDistance(me) < 70.0f) + if (p->IsAlive() && p->GetDistance(me) < 70.0f) { float th = me->GetThreatMgr().getThreatWithoutTemp(p); if (!target || th > maxThreat) diff --git a/src/server/shared/Network/NetworkThread.h b/src/server/shared/Network/NetworkThread.h index b280c16d4f0f9d..0f65a97b63f72d 100644 --- a/src/server/shared/Network/NetworkThread.h +++ b/src/server/shared/Network/NetworkThread.h @@ -179,7 +179,7 @@ class NetworkThread { LOG_DEBUG("misc", "Network Thread Starting"); - _updateTimer.expires_at(std::chrono::steady_clock::now()); + _updateTimer.expires_at(std::chrono::steady_clock::now() + std::chrono::milliseconds(1)); _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); }); _ioContext.run(); @@ -193,7 +193,7 @@ class NetworkThread if (_stopped) return; - _updateTimer.expires_at(std::chrono::steady_clock::now()); + _updateTimer.expires_at(std::chrono::steady_clock::now() + std::chrono::milliseconds(1)); _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); }); AddNewSockets(); diff --git a/src/server/shared/Realms/RealmList.cpp b/src/server/shared/Realms/RealmList.cpp index a1c258edd3d3e1..b3b3f7656b1215 100644 --- a/src/server/shared/Realms/RealmList.cpp +++ b/src/server/shared/Realms/RealmList.cpp @@ -18,8 +18,9 @@ #include "RealmList.h" #include "DatabaseEnv.h" #include "Log.h" -#include "Resolver.h" #include "QueryResult.h" +#include "Resolver.h" +#include "SteadyTimer.h" #include "Util.h" #include #include @@ -227,9 +228,7 @@ void RealmList::UpdateRealms(boost::system::error_code const& error) if (_updateInterval) { - // Calculate the expiration time _updateInterval from now - auto expiration_time = std::chrono::steady_clock::now() + std::chrono::seconds(_updateInterval); - _updateTimer->expires_at(expiration_time); + _updateTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(_updateInterval)); _updateTimer->async_wait([this](boost::system::error_code const& errorCode){ UpdateRealms(errorCode); }); } }