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); });
}
}