Local snapshot for Docker build (includes mod-ale)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Docker Build
2026-05-06 21:18:20 -04:00
commit 72dd540b67
9823 changed files with 7356554 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
add_subdirectory(apps)
if ((APPS_BUILD AND NOT APPS_BUILD STREQUAL "none") OR BUILD_TOOLS_DB_IMPORT)
add_subdirectory(database)
endif()
if (BUILD_APPLICATION_AUTHSERVER OR BUILD_APPLICATION_WORLDSERVER)
add_subdirectory(shared)
endif()
if (BUILD_APPLICATION_WORLDSERVER)
add_subdirectory(game)
add_subdirectory(scripts)
endif()
+212
View File
@@ -0,0 +1,212 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# Make the script module list available in the current scope
GetApplicationsList(APPLICATIONS_BUILD_LIST)
if (APPS_BUILD STREQUAL "none")
set(APPS_DEFAULT_BUILD "disabled")
else()
set(APPS_DEFAULT_BUILD "enabled")
endif()
# Sets BUILD_APPS_USE_WHITELIST
# Sets BUILD_APPS_WHITELIST
if (APPS_BUILD MATCHES "-only")
set(BUILD_APPS_USE_WHITELIST ON)
if (APPS_BUILD STREQUAL "auth-only")
list(APPEND BUILD_APPS_WHITELIST authserver)
endif()
if (APPS_BUILD STREQUAL "world-only")
list(APPEND BUILD_APPS_WHITELIST worldserver)
endif()
endif()
# Set the SCRIPTS_${BUILD_APP} variables from the
# variables set above
foreach(BUILD_APP ${APPLICATIONS_BUILD_LIST})
ApplicationNameToVariable(${BUILD_APP} BUILD_APP_VARIABLE)
if(${BUILD_APP_VARIABLE} STREQUAL "default")
if(BUILD_APPS_USE_WHITELIST)
list(FIND BUILD_APPS_WHITELIST "${BUILD_APP}" INDEX)
if(${INDEX} GREATER -1)
set(${BUILD_APP_VARIABLE} ${APPS_DEFAULT_BUILD})
else()
set(${BUILD_APP_VARIABLE} "disabled")
endif()
else()
set(${BUILD_APP_VARIABLE} ${APPS_DEFAULT_BUILD})
endif()
endif()
# Build the Graph values
if(${BUILD_APP_VARIABLE} MATCHES "enabled")
list(APPEND BUILD_APP_GRAPH_KEYS apps)
set(BUILD_APP_VALUE_DISPLAY_apps apps)
list(APPEND BUILD_APP_VALUE_CONTAINS_apps ${BUILD_APP})
else()
list(APPEND BUILD_APP_GRAPH_KEYS disabled)
set(BUILD_APP_VALUE_DISPLAY_disabled disabled)
list(APPEND BUILD_APP_VALUE_CONTAINS_disabled ${BUILD_APP})
endif()
endforeach()
list(SORT BUILD_APP_GRAPH_KEYS)
list(REMOVE_DUPLICATES BUILD_APP_GRAPH_KEYS)
# Display the graphs
message("")
message("* Apps build list (${APPS_BUILD}):")
message(" |")
foreach(BUILD_APP_GRAPH_KEY ${BUILD_APP_GRAPH_KEYS})
if(NOT BUILD_APP_GRAPH_KEY STREQUAL "disabled")
message(" +- ${BUILD_APP_VALUE_DISPLAY_${BUILD_APP_GRAPH_KEY}}")
else()
message(" | ${BUILD_APP_VALUE_DISPLAY_${BUILD_APP_GRAPH_KEY}}")
endif()
foreach(BUILD_APP_GRAPH_ENTRY ${BUILD_APP_VALUE_CONTAINS_${BUILD_APP_GRAPH_KEY}})
message(" | +- ${BUILD_APP_GRAPH_ENTRY}")
endforeach()
message(" |")
endforeach()
message("")
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
# Generates the actual apps projects
foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST})
GetPathToApplication(${APPLICATION_NAME} SOURCE_APP_PATH)
ApplicationNameToVariable(${APPLICATION_NAME} BUILD_APP_VARIABLE)
if (${BUILD_APP_VARIABLE} STREQUAL "disabled")
continue()
endif()
unset(APP_PRIVATE_SOURCES)
CollectSourceFiles(
${SOURCE_APP_PATH}
APP_PRIVATE_SOURCES
# Exclude
${SOURCE_APP_PATH}/PrecompiledHeaders)
if (WIN32)
list(APPEND APP_PRIVATE_SOURCES ${winDebugging})
if (${APPLICATION_NAME} MATCHES "worldserver")
list(APPEND APP_PRIVATE_SOURCES ${winService})
endif()
if (MSVC)
list(APPEND APP_PRIVATE_SOURCES ${SOURCE_APP_PATH}/${APPLICATION_NAME}.rc)
endif()
endif()
GetProjectNameOfApplicationName(${APPLICATION_NAME} APP_PROJECT_NAME)
# Create the application project
add_executable(${APP_PROJECT_NAME}
${APP_PRIVATE_SOURCES})
add_dependencies(${APP_PROJECT_NAME} revision.h)
target_link_libraries(${APP_PROJECT_NAME}
PRIVATE
acore-core-interface)
if (${APP_PROJECT_NAME} MATCHES "authserver")
target_link_libraries(${APP_PROJECT_NAME}
PUBLIC
shared)
elseif(${APP_PROJECT_NAME} MATCHES "worldserver")
target_link_libraries(${APP_PROJECT_NAME}
PUBLIC
modules
scripts
game
gsoap
readline
gperftools)
if (UNIX AND NOT NOJEM)
set(${APP_PROJECT_NAME}_LINK_FLAGS "-pthread -lncurses ${${APP_PROJECT_NAME}_LINK_FLAGS}")
endif()
set_target_properties(${APP_PROJECT_NAME} PROPERTIES LINK_FLAGS "${${APP_PROJECT_NAME}_LINK_FLAGS}")
# Add all dynamic projects as dependency to the worldserver
if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES)
add_dependencies(${APP_PROJECT_NAME} ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES})
endif()
endif()
unset(APP_PUBLIC_INCLUDES)
CollectIncludeDirectories(
${SOURCE_APP_PATH}
APP_PUBLIC_INCLUDES
# Exclude
${SOURCE_APP_PATH}/PrecompiledHeaders)
target_include_directories(${APP_PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(${APP_PROJECT_NAME}
PUBLIC
${APP_PUBLIC_INCLUDES}
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_NAME})
set_target_properties(${APP_PROJECT_NAME}
PROPERTIES
FOLDER
"server")
# Install config
CopyApplicationConfig(${APP_PROJECT_NAME} ${APPLICATION_NAME})
# Copy config merger tool (only once, after all app targets, if enabled)
if(TOOL_CONFIG_MERGER)
if(WIN32)
if("${CMAKE_MAKE_PROGRAM}" MATCHES "MSBuild")
foreach(cfg IN ITEMS Debug Release RelWithDebInfo MinSizeRel)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${cfg}/configs")
file(COPY "${CMAKE_SOURCE_DIR}/apps/config-merger/python/" DESTINATION "${CMAKE_BINARY_DIR}/bin/${cfg}/configs")
endforeach()
elseif(MINGW)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/configs")
file(COPY "${CMAKE_SOURCE_DIR}/apps/config-merger/python/" DESTINATION "${CMAKE_BINARY_DIR}/bin/configs")
endif()
elseif(UNIX)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/configs")
file(COPY "${CMAKE_SOURCE_DIR}/apps/config-merger/python/" DESTINATION "${CMAKE_BINARY_DIR}/bin/configs")
endif()
endif()
if (UNIX)
install(TARGETS ${APP_PROJECT_NAME} DESTINATION bin)
elseif (WIN32)
install(TARGETS ${APP_PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_PREFIX}")
endif()
set(PATH_TO_PCH ${SOURCE_APP_PATH}/PrecompiledHeaders/${APPLICATION_NAME}PCH.h)
# Generate precompiled header
if (USE_COREPCH AND EXISTS ${PATH_TO_PCH})
add_cxx_pch(${APP_PROJECT_NAME} ${PATH_TO_PCH})
endif()
endforeach()
@@ -0,0 +1,39 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "AuthCodes.h"
#include "RealmList.h"
namespace AuthHelper
{
constexpr static uint32 MAX_PRE_BC_CLIENT_BUILD = 6141;
bool IsPreBCAcceptedClientBuild(uint32 build)
{
return build <= MAX_PRE_BC_CLIENT_BUILD && sRealmList->GetBuildInfo(build);
}
bool IsPostBCAcceptedClientBuild(uint32 build)
{
return build > MAX_PRE_BC_CLIENT_BUILD && sRealmList->GetBuildInfo(build);
}
bool IsAcceptedClientBuild(uint32 build)
{
return sRealmList->GetBuildInfo(build) != nullptr;
}
};
@@ -0,0 +1,90 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUTHCODES_H
#define _AUTHCODES_H
#include "Define.h"
enum AuthResult
{
WOW_SUCCESS = 0x00,
WOW_FAIL_BANNED = 0x03,
WOW_FAIL_UNKNOWN_ACCOUNT = 0x04,
WOW_FAIL_INCORRECT_PASSWORD = 0x05,
WOW_FAIL_ALREADY_ONLINE = 0x06,
WOW_FAIL_NO_TIME = 0x07,
WOW_FAIL_DB_BUSY = 0x08,
WOW_FAIL_VERSION_INVALID = 0x09,
WOW_FAIL_VERSION_UPDATE = 0x0A,
WOW_FAIL_INVALID_SERVER = 0x0B,
WOW_FAIL_SUSPENDED = 0x0C,
WOW_FAIL_FAIL_NOACCESS = 0x0D,
WOW_SUCCESS_SURVEY = 0x0E,
WOW_FAIL_PARENTCONTROL = 0x0F,
WOW_FAIL_LOCKED_ENFORCED = 0x10,
WOW_FAIL_TRIAL_ENDED = 0x11,
WOW_FAIL_USE_BATTLENET = 0x12,
WOW_FAIL_ANTI_INDULGENCE = 0x13,
WOW_FAIL_EXPIRED = 0x14,
WOW_FAIL_NO_GAME_ACCOUNT = 0x15,
WOW_FAIL_CHARGEBACK = 0x16,
WOW_FAIL_INTERNET_GAME_ROOM_WITHOUT_BNET = 0x17,
WOW_FAIL_GAME_ACCOUNT_LOCKED = 0x18,
WOW_FAIL_UNLOCKABLE_LOCK = 0x19,
WOW_FAIL_CONVERSION_REQUIRED = 0x20,
WOW_FAIL_DISCONNECTED = 0xFF
};
enum LoginResult
{
LOGIN_OK = 0x00,
LOGIN_FAILED = 0x01,
LOGIN_FAILED2 = 0x02,
LOGIN_BANNED = 0x03,
LOGIN_UNKNOWN_ACCOUNT = 0x04,
LOGIN_UNKNOWN_ACCOUNT3 = 0x05,
LOGIN_ALREADYONLINE = 0x06,
LOGIN_NOTIME = 0x07,
LOGIN_DBBUSY = 0x08,
LOGIN_BADVERSION = 0x09,
LOGIN_DOWNLOAD_FILE = 0x0A,
LOGIN_FAILED3 = 0x0B,
LOGIN_SUSPENDED = 0x0C,
LOGIN_FAILED4 = 0x0D,
LOGIN_CONNECTED = 0x0E,
LOGIN_PARENTALCONTROL = 0x0F,
LOGIN_LOCKED_ENFORCED = 0x10
};
enum ExpansionFlags
{
POST_BC_EXP_FLAG = 0x2,
PRE_BC_EXP_FLAG = 0x1,
NO_VALID_EXP_FLAG = 0x0
};
struct RealmBuildInfo;
namespace AuthHelper
{
bool IsAcceptedClientBuild(uint32 build);
bool IsPostBCAcceptedClientBuild(uint32 build);
bool IsPreBCAcceptedClientBuild(uint32 build);
};
#endif
+304
View File
@@ -0,0 +1,304 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
/**
* @file main.cpp
* @brief Authentication Server main program
*
* This file contains the main program for the
* authentication server
*/
#include "AppenderDB.h"
#include "AuthSocketMgr.h"
#include "Banner.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "GitRevision.h"
#include "IPLocation.h"
#include "IoContext.h"
#include "Log.h"
#include "MySQLThreading.h"
#include "OpenSSLCrypto.h"
#include "ProcessPriority.h"
#include "RealmList.h"
#include "SecretMgr.h"
#include "SharedDefines.h"
#include "SteadyTimer.h"
#include "Util.h"
#include <boost/asio/signal_set.hpp>
#include <boost/program_options.hpp>
#include <boost/version.hpp>
#include <csignal>
#include <filesystem>
#include <iostream>
#include <openssl/crypto.h>
#include <openssl/opensslv.h>
#ifndef _ACORE_REALM_CONFIG
#define _ACORE_REALM_CONFIG "authserver.conf"
#endif
using boost::asio::ip::tcp;
using namespace boost::program_options;
namespace fs = std::filesystem;
bool StartDB();
void StopDB();
void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int signalNumber);
void KeepDatabaseAliveHandler(std::weak_ptr<boost::asio::steady_timer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error);
void BanExpiryHandler(std::weak_ptr<boost::asio::steady_timer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error);
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile);
/// Launch the auth server
int main(int argc, char** argv)
{
Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_AUTHSERVER;
signal(SIGABRT, &Acore::AbortHandler);
// Command line parsing
auto configFile = fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG));
auto vm = GetConsoleArguments(argc, argv, configFile);
// exit if help or version is enabled
if (vm.count("help") || vm.count("version"))
return 0;
// Add file and args in config
sConfigMgr->Configure(configFile.generic_string(), std::vector<std::string>(argv, argv + argc));
if (!sConfigMgr->LoadAppConfigs())
return 1;
// Init logging
sLog->RegisterAppender<AppenderDB>();
sLog->Initialize(nullptr);
Acore::Banner::Show("authserver",
[](std::string_view text)
{
LOG_INFO("server.authserver", text);
},
[]()
{
LOG_INFO("server.authserver", "> Using configuration file {}", sConfigMgr->GetFilename());
LOG_INFO("server.authserver", "> Using SSL version: {} (library: {})", OPENSSL_VERSION_TEXT, OpenSSL_version(OPENSSL_VERSION));
LOG_INFO("server.authserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
});
OpenSSLCrypto::threadsSetup();
std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); });
// authserver PID file creation
std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", "");
if (!pidFile.empty())
{
if (uint32 pid = CreatePIDFile(pidFile))
LOG_INFO("server.authserver", "Daemon PID: {}\n", pid); // outError for red color in console
else
{
LOG_ERROR("server.authserver", "Cannot create PID file {} (possible error: permission)\n", pidFile);
return 1;
}
}
// Initialize the database connection
if (!StartDB())
return 1;
sSecretMgr->Initialize();
// Load IP Location Database
sIPLocation->Load();
std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); });
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
// Get the list of realms for the server
sRealmList->Initialize(*ioContext, sConfigMgr->GetOption<int32>("RealmsStateUpdateDelay", 20));
std::shared_ptr<void> sRealmListHandle(nullptr, [](void*) { sRealmList->Close(); });
if (sRealmList->GetRealms().empty())
{
LOG_ERROR("server.authserver", "No valid realms specified.");
return 1;
}
// Stop auth server if dry run
if (sConfigMgr->isDryRun())
{
LOG_INFO("server.authserver", "Dry run completed, terminating.");
return 0;
}
// Start the listening port (acceptor) for auth connections
int32 port = sConfigMgr->GetOption<int32>("RealmServerPort", 3724);
if (port < 0 || port > 0xFFFF)
{
LOG_ERROR("server.authserver", "Specified port out of allowed range (1-65535)");
return 1;
}
std::string bindIp = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
if (!sAuthSocketMgr.StartNetwork(*ioContext, bindIp, port))
{
LOG_ERROR("server.authserver", "Failed to initialize network");
return 1;
}
std::shared_ptr<void> sAuthSocketMgrHandle(nullptr, [](void*) { sAuthSocketMgr.StopNetwork(); });
// Set signal handlers
boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM);
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
signals.add(SIGBREAK);
#endif
signals.async_wait(std::bind(&SignalHandler, std::weak_ptr<Acore::Asio::IoContext>(ioContext), std::placeholders::_1, std::placeholders::_2));
// Set process priority according to configuration settings
SetProcessPriority("server.authserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false));
// Enabled a timed callback for handling the database keep alive ping
int32 dbPingInterval = sConfigMgr->GetOption<int32>("MaxPingTime", 30);
std::shared_ptr<boost::asio::steady_timer> dbPingTimer = std::make_shared<boost::asio::steady_timer>(*ioContext);
dbPingTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(dbPingInterval * MINUTE));
dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, std::weak_ptr<boost::asio::steady_timer>(dbPingTimer), dbPingInterval, std::placeholders::_1));
int32 banExpiryCheckInterval = sConfigMgr->GetOption<int32>("BanExpiryCheckInterval", 60);
std::shared_ptr<boost::asio::steady_timer> banExpiryCheckTimer = std::make_shared<boost::asio::steady_timer>(*ioContext);
banExpiryCheckTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(banExpiryCheckInterval));
banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, std::weak_ptr<boost::asio::steady_timer>(banExpiryCheckTimer), banExpiryCheckInterval, std::placeholders::_1));
// Start the io service worker loop
ioContext->run();
banExpiryCheckTimer->cancel();
dbPingTimer->cancel();
LOG_INFO("server.authserver", "Halting process...");
signals.cancel();
return 0;
}
/// Initialize connection to the database
bool StartDB()
{
MySQL::Library_Init();
// Load databases
// NOTE: While authserver is singlethreaded you should keep synch_threads == 1.
// Increasing it is just silly since only 1 will be used ever.
DatabaseLoader loader("server.authserver");
loader
.AddDatabase(LoginDatabase, "Login");
if (!loader.Load())
return false;
LOG_INFO("server.authserver", "Started auth database connection pool.");
sLog->SetRealmId(0); // Enables DB appenders when realm is set.
return true;
}
/// Close the connection to the database
void StopDB()
{
LoginDatabase.Close();
MySQL::Library_End();
}
void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int /*signalNumber*/)
{
if (!error)
{
if (std::shared_ptr<Acore::Asio::IoContext> ioContext = ioContextRef.lock())
{
ioContext->stop();
}
}
}
void KeepDatabaseAliveHandler(std::weak_ptr<boost::asio::steady_timer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error)
{
if (!error)
{
if (std::shared_ptr<boost::asio::steady_timer> dbPingTimer = dbPingTimerRef.lock())
{
LOG_DEBUG("sql.driver", "Ping MySQL to keep connection alive");
LoginDatabase.KeepAlive();
dbPingTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(dbPingInterval));
dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, dbPingTimerRef, dbPingInterval, std::placeholders::_1));
}
}
}
void BanExpiryHandler(std::weak_ptr<boost::asio::steady_timer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error)
{
if (!error)
{
if (std::shared_ptr<boost::asio::steady_timer> banExpiryCheckTimer = banExpiryCheckTimerRef.lock())
{
LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS));
banExpiryCheckTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(banExpiryCheckInterval));
banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, banExpiryCheckTimerRef, banExpiryCheckInterval, std::placeholders::_1));
}
}
}
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile)
{
options_description all("Allowed options");
all.add_options()
("help,h", "print usage message")
("version,v", "print version build info")
("dry-run,d", "Dry run")
("config,c", value<fs::path>(&configFile)->default_value(fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG))), "use <arg> as configuration file")
("config-policy", value<std::string>()->value_name("policy"), "override config severity policy (e.g. default=skip,critical_option=fatal)");
variables_map variablesMap;
try
{
store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), variablesMap);
notify(variablesMap);
}
catch (std::exception const& e)
{
std::cerr << e.what() << "\n";
}
if (variablesMap.count("help"))
std::cout << all << "\n";
else if (variablesMap.count("version"))
std::cout << GitRevision::GetFullVersion() << "\n";
else if (variablesMap.count("dry-run"))
sConfigMgr->setDryRun(true);
return variablesMap;
}
@@ -0,0 +1,22 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "Common.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "RealmList.h"
@@ -0,0 +1,875 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "AuthSession.h"
#include "AES.h"
#include "AuthCodes.h"
#include "Config.h"
#include "CryptoGenerics.h"
#include "CryptoHash.h"
#include "CryptoRandom.h"
#include "DatabaseEnv.h"
#include "IPLocation.h"
#include "Log.h"
#include "RealmList.h"
#include "SecretMgr.h"
#include "StringConvert.h"
#include "TOTP.h"
#include "Util.h"
#include <boost/lexical_cast.hpp>
using boost::asio::ip::tcp;
enum eAuthCmd
{
AUTH_LOGON_CHALLENGE = 0x00,
AUTH_LOGON_PROOF = 0x01,
AUTH_RECONNECT_CHALLENGE = 0x02,
AUTH_RECONNECT_PROOF = 0x03,
REALM_LIST = 0x10,
XFER_INITIATE = 0x30,
XFER_DATA = 0x31,
XFER_ACCEPT = 0x32,
XFER_RESUME = 0x33,
XFER_CANCEL = 0x34
};
#pragma pack(push, 1)
typedef struct AUTH_LOGON_CHALLENGE_C
{
uint8 cmd;
uint8 error;
uint16 size;
uint8 gamename[4];
uint8 version1;
uint8 version2;
uint8 version3;
uint16 build;
uint8 platform[4];
uint8 os[4];
uint8 country[4];
uint32 timezone_bias;
uint32 ip;
uint8 I_len;
uint8 I[1];
} sAuthLogonChallenge_C;
static_assert(sizeof(sAuthLogonChallenge_C) == (1 + 1 + 2 + 4 + 1 + 1 + 1 + 2 + 4 + 4 + 4 + 4 + 4 + 1 + 1));
typedef struct AUTH_LOGON_PROOF_C
{
uint8 cmd;
Acore::Crypto::SRP6::EphemeralKey A;
Acore::Crypto::SHA1::Digest clientM;
Acore::Crypto::SHA1::Digest crc_hash;
uint8 number_of_keys;
uint8 securityFlags;
} sAuthLogonProof_C;
static_assert(sizeof(sAuthLogonProof_C) == (1 + 32 + 20 + 20 + 1 + 1));
typedef struct AUTH_LOGON_PROOF_S
{
uint8 cmd;
uint8 error;
Acore::Crypto::SHA1::Digest M2;
uint32 AccountFlags;
uint32 SurveyId;
uint16 LoginFlags;
} sAuthLogonProof_S;
static_assert(sizeof(sAuthLogonProof_S) == (1 + 1 + 20 + 4 + 4 + 2));
typedef struct AUTH_LOGON_PROOF_S_OLD
{
uint8 cmd;
uint8 error;
Acore::Crypto::SHA1::Digest M2;
uint32 unk2;
} sAuthLogonProof_S_Old;
static_assert(sizeof(sAuthLogonProof_S_Old) == (1 + 1 + 20 + 4));
typedef struct AUTH_RECONNECT_PROOF_C
{
uint8 cmd;
uint8 R1[16];
Acore::Crypto::SHA1::Digest R2, R3;
uint8 number_of_keys;
} sAuthReconnectProof_C;
static_assert(sizeof(sAuthReconnectProof_C) == (1 + 16 + 20 + 20 + 1));
#pragma pack(pop)
std::array<uint8, 16> VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, 0x21, 0x57, 0xFC, 0x37, 0x3F, 0xB3, 0x69, 0xCD, 0xD2, 0xF1 } };
#define MAX_ACCEPTED_CHALLENGE_SIZE (sizeof(AUTH_LOGON_CHALLENGE_C) + 16)
#define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4
#define REALM_LIST_PACKET_SIZE 5
std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers()
{
std::unordered_map<uint8, AuthHandler> handlers;
handlers[AUTH_LOGON_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleLogonChallenge };
handlers[AUTH_LOGON_PROOF] = { STATUS_LOGON_PROOF, sizeof(AUTH_LOGON_PROOF_C), &AuthSession::HandleLogonProof };
handlers[AUTH_RECONNECT_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleReconnectChallenge };
handlers[AUTH_RECONNECT_PROOF] = { STATUS_RECONNECT_PROOF, sizeof(AUTH_RECONNECT_PROOF_C), &AuthSession::HandleReconnectProof };
handlers[REALM_LIST] = { STATUS_AUTHED, REALM_LIST_PACKET_SIZE, &AuthSession::HandleRealmList };
return handlers;
}
std::unordered_map<uint8, AuthHandler> const Handlers = AuthSession::InitHandlers();
void AccountInfo::LoadResult(Field* fields)
{
// 0 1 2 3 4 5 6
// SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.Flags, a.failed_logins,
// 7 8
// ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate,
// 9 10
// ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate,
// 11
// aa.gmlevel (, more query-specific fields)
// FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 LEFT JOIN ip_banned ipb ON ipb.ip = ? WHERE a.username = ?
Id = fields[0].Get<uint32>();
Login = fields[1].Get<std::string>();
IsLockedToIP = fields[2].Get<bool>();
LockCountry = fields[3].Get<std::string>();
LastIP = fields[4].Get<std::string>();
Flags = fields[5].Get<uint32>();
FailedLogins = fields[6].Get<uint32>();
IsBanned = fields[7].Get<bool>() || fields[9].Get<bool>();
IsPermanentlyBanned = fields[8].Get<bool>() || fields[10].Get<bool>();
SecurityLevel = static_cast<AccountTypes>(fields[11].Get<uint8>()) > SEC_CONSOLE ? SEC_CONSOLE : static_cast<AccountTypes>(fields[11].Get<uint8>());
// Use our own uppercasing of the account name instead of using UPPER() in mysql query
// This is how the account was created in the first place and changing it now would result in breaking
// login for all accounts having accented characters in their name
Utf8ToUpperOnlyLatin(Login);
}
AuthSession::AuthSession(IoContextTcpSocket&& socket) :
Socket(std::move(socket)), _status(STATUS_CHALLENGE), _build(0), _expversion(0) { }
void AuthSession::Start()
{
std::string ip_address = GetRemoteIpAddress().to_string();
LOG_TRACE("session", "Accepted connection from {}", ip_address);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
stmt->SetData(0, ip_address);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::CheckIpCallback, this, std::placeholders::_1)));
}
bool AuthSession::Update()
{
if (!AuthSocket::Update())
return false;
_queryProcessor.ProcessReadyCallbacks();
return true;
}
void AuthSession::CheckIpCallback(PreparedQueryResult result)
{
if (result)
{
bool banned = false;
for (auto const& fields : *result)
{
if (fields[0].Get<uint64>() != 0)
{
banned = true;
break;
}
}
if (banned)
{
ByteBuffer pkt;
pkt << uint8(AUTH_LOGON_CHALLENGE);
pkt << uint8(0x00);
pkt << uint8(WOW_FAIL_BANNED);
SendPacket(pkt);
LOG_DEBUG("session", "[AuthSession::CheckIpCallback] Banned ip '{}:{}' tries to login!", GetRemoteIpAddress().to_string(), GetRemotePort());
return;
}
}
AsyncRead();
}
SocketReadCallbackResult AuthSession::ReadHandler()
{
MessageBuffer& packet = GetReadBuffer();
while (packet.GetActiveSize())
{
uint8 cmd = packet.GetReadPointer()[0];
auto itr = Handlers.find(cmd);
if (itr == Handlers.end())
{
// well we dont handle this, lets just ignore it
packet.Reset();
break;
}
if (_status != itr->second.status)
{
CloseSocket();
return SocketReadCallbackResult::Stop;
}
uint16 size = uint16(itr->second.packetSize);
if (packet.GetActiveSize() < size)
break;
if (cmd == AUTH_LOGON_CHALLENGE || cmd == AUTH_RECONNECT_CHALLENGE)
{
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(packet.GetReadPointer());
size += challenge->size;
if (size > MAX_ACCEPTED_CHALLENGE_SIZE)
{
CloseSocket();
return SocketReadCallbackResult::Stop;
}
}
if (packet.GetActiveSize() < size)
break;
if (!(*this.*itr->second.handler)())
{
CloseSocket();
return SocketReadCallbackResult::Stop;
}
packet.ReadCompleted(size);
}
return SocketReadCallbackResult::KeepReading;
}
void AuthSession::SendPacket(ByteBuffer& packet)
{
if (!IsOpen())
return;
if (!packet.empty())
{
MessageBuffer buffer(packet.size());
buffer.Write(packet.contents(), packet.size());
QueuePacket(std::move(buffer));
}
}
bool AuthSession::HandleLogonChallenge()
{
_status = STATUS_CLOSED;
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer());
if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
return false;
std::string login((char const*)challenge->I, challenge->I_len);
LOG_DEBUG("server.authserver", "[AuthChallenge] '{}'", login);
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
std::array<char, 5> os;
os.fill('\0');
memcpy(os.data(), challenge->os, sizeof(challenge->os));
_os = os.data();
// Restore string order as its byte order is reversed
std::reverse(_os.begin(), _os.end());
_localizationName.resize(4);
for (int i = 0; i < 4; ++i)
_localizationName[i] = challenge->country[4 - i - 1];
// Get the account details from the account table
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE);
stmt->SetData(0, GetRemoteIpAddress().to_string());
stmt->SetData(1, login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::LogonChallengeCallback, this, std::placeholders::_1)));
return true;
}
void AuthSession::LogonChallengeCallback(PreparedQueryResult result)
{
ByteBuffer pkt;
pkt << uint8(AUTH_LOGON_CHALLENGE);
pkt << uint8(0x00);
if (!result)
{
pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
SendPacket(pkt);
return;
}
Field* fields = result->Fetch();
_accountInfo.LoadResult(fields);
std::string ipAddress = GetRemoteIpAddress().to_string();
uint16 port = GetRemotePort();
// If the IP is 'locked', check that the player comes indeed from the correct IP address
if (_accountInfo.IsLockedToIP)
{
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is locked to IP - '{}' is logging in from '{}'", _accountInfo.Login, _accountInfo.LastIP, ipAddress);
if (_accountInfo.LastIP != ipAddress)
{
pkt << uint8(WOW_FAIL_LOCKED_ENFORCED);
SendPacket(pkt);
return;
}
}
else
{
if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ipAddress))
_ipCountry = location->CountryCode;
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is not locked to ip", _accountInfo.Login);
if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00")
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is not locked to country", _accountInfo.Login);
else if (!_ipCountry.empty())
{
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is locked to country: '{}' Player country is '{}'", _accountInfo.Login, _accountInfo.LockCountry, _ipCountry);
if (_ipCountry != _accountInfo.LockCountry)
{
pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK);
SendPacket(pkt);
return;
}
}
}
// If the account is banned, reject the logon attempt
if (_accountInfo.IsBanned)
{
if (_accountInfo.IsPermanentlyBanned)
{
pkt << uint8(WOW_FAIL_BANNED);
SendPacket(pkt);
LOG_INFO("server.authserver.banned", "'{}:{}' [AuthChallenge] Banned account {} tried to login!", ipAddress, port, _accountInfo.Login);
return;
}
else
{
pkt << uint8(WOW_FAIL_SUSPENDED);
SendPacket(pkt);
LOG_INFO("server.authserver.banned", "'{}:{}' [AuthChallenge] Temporarily banned account {} tried to login!", ipAddress, port, _accountInfo.Login);
return;
}
}
uint8 securityFlags = 0;
// Check if a TOTP token is needed
if (!fields[12].IsNull())
{
securityFlags = 4;
_totpSecret = fields[12].Get<Binary>();
if (auto const& secret = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY))
{
bool success = Acore::Crypto::AEDecrypt<Acore::Crypto::AES>(*_totpSecret, *secret);
if (!success)
{
pkt << uint8(WOW_FAIL_DB_BUSY);
LOG_ERROR("server.authserver", "[AuthChallenge] Account '{}' has invalid ciphertext for TOTP token key stored", _accountInfo.Login);
SendPacket(pkt);
return;
}
}
}
_srp6.emplace(_accountInfo.Login,
fields[13].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>(),
fields[14].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>());
// Fill the response packet with the result
if (AuthHelper::IsAcceptedClientBuild(_build))
{
pkt << uint8(WOW_SUCCESS);
pkt.append(_srp6->B);
pkt << uint8(1);
pkt.append(_srp6->g);
pkt << uint8(32);
pkt.append(_srp6->N);
pkt.append(_srp6->s);
pkt.append(VersionChallenge.data(), VersionChallenge.size());
pkt << uint8(securityFlags); // security flags (0x0...0x04)
if (securityFlags & 0x01) // PIN input
{
pkt << uint32(0);
pkt << uint64(0) << uint64(0); // 16 bytes hash?
}
if (securityFlags & 0x02) // Matrix input
{
pkt << uint8(0);
pkt << uint8(0);
pkt << uint8(0);
pkt << uint8(0);
pkt << uint64(0);
}
if (securityFlags & 0x04) // Security token input
pkt << uint8(1);
LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] account {} is using '{}' locale ({})",
ipAddress, port, _accountInfo.Login, _localizationName, GetLocaleByName(_localizationName));
_status = STATUS_LOGON_PROOF;
}
else
pkt << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(pkt);
}
// Logon Proof command handler
bool AuthSession::HandleLogonProof()
{
LOG_DEBUG("server.authserver", "Entering _HandleLogonProof");
_status = STATUS_CLOSED;
// Read the packet
sAuthLogonProof_C* logonProof = reinterpret_cast<sAuthLogonProof_C*>(GetReadBuffer().GetReadPointer());
// If the client has no valid version
if (_expversion == NO_VALID_EXP_FLAG)
{
// Check if we have the appropriate patch on the disk
LOG_DEBUG("network", "Client with invalid version, patching is not implemented");
return false;
}
// Check if SRP6 results match (password is correct), else send an error
if (Optional<SessionKey> K = _srp6->VerifyChallengeResponse(logonProof->A, logonProof->clientM))
{
_sessionKey = *K;
// Check auth token
bool tokenSuccess = false;
bool sentToken = (logonProof->securityFlags & 0x04);
if (sentToken && _totpSecret)
{
uint8 size = *(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C));
std::string token(reinterpret_cast<char*>(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size);
GetReadBuffer().ReadCompleted(sizeof(size) + size);
uint32 incomingToken = *Acore::StringTo<uint32>(token);
tokenSuccess = Acore::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken);
memset(_totpSecret->data(), 0, _totpSecret->size());
}
else if (!sentToken && !_totpSecret)
tokenSuccess = true;
if (!tokenSuccess)
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
packet << uint16(0); // LoginFlags, 1 has account message
SendPacket(packet);
return true;
}
if (!VerifyVersion(logonProof->A.data(), logonProof->A.size(), logonProof->crc_hash, false))
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(packet);
return true;
}
LOG_DEBUG("server.authserver", "'{}:{}' User '{}' successfully authenticated", GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login);
// Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account
// No SQL injection (escaped user name) and IP address as received by socket
std::string address = sConfigMgr->GetOption<bool>("AllowLoggingIPAddressesInDatabase", true, true) ? GetRemoteIpAddress().to_string() : "0.0.0.0";
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF);
stmt->SetData(0, _sessionKey);
stmt->SetData(1, address);
stmt->SetData(2, GetLocaleByName(_localizationName));
stmt->SetData(3, _os);
stmt->SetData(4, _accountInfo.Login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
.WithPreparedCallback([this, M2 = Acore::Crypto::SRP6::GetSessionVerifier(logonProof->A, logonProof->clientM, _sessionKey)](PreparedQueryResult const&)
{
// Finish SRP6 and send the final result to the client
ByteBuffer packet;
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
{
sAuthLogonProof_S proof;
proof.M2 = M2;
proof.cmd = AUTH_LOGON_PROOF;
proof.error = 0;
proof.AccountFlags = _accountInfo.Flags;
proof.SurveyId = 0;
proof.LoginFlags = 0; // 0x1 = has account message
packet.resize(sizeof(proof));
std::memcpy(packet.contents(), &proof, sizeof(proof));
}
else
{
sAuthLogonProof_S_Old proof;
proof.M2 = M2;
proof.cmd = AUTH_LOGON_PROOF;
proof.error = 0;
proof.unk2 = 0x00;
packet.resize(sizeof(proof));
std::memcpy(packet.contents(), &proof, sizeof(proof));
}
SendPacket(packet);
_status = STATUS_AUTHED;
}));
}
else
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
packet << uint16(0); // LoginFlags, 1 has account message
SendPacket(packet);
LOG_INFO("server.authserver.hack", "'{}:{}' [AuthChallenge] account {} tried to login with invalid password!",
GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login);
uint32 MaxWrongPassCount = sConfigMgr->GetOption<int32>("WrongPass.MaxCount", 0);
// We can not include the failed account login hook. However, this is a workaround to still log this.
if (sConfigMgr->GetOption<bool>("WrongPass.Logging", false))
{
LoginDatabasePreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING);
logstmt->SetData(0, _accountInfo.Id);
logstmt->SetData(1, GetRemoteIpAddress().to_string());
logstmt->SetData(2, "Login to WoW Failed - Incorrect Password");
LoginDatabase.Execute(logstmt);
}
if (MaxWrongPassCount > 0)
{
//Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS);
stmt->SetData(0, _accountInfo.Login);
LoginDatabase.Execute(stmt);
if (++_accountInfo.FailedLogins >= MaxWrongPassCount)
{
uint32 WrongPassBanTime = sConfigMgr->GetOption<int32>("WrongPass.BanTime", 600);
bool WrongPassBanType = sConfigMgr->GetOption<bool>("WrongPass.BanType", false);
if (WrongPassBanType)
{
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED);
stmt->SetData(0, _accountInfo.Id);
stmt->SetData(1, WrongPassBanTime);
LoginDatabase.Execute(stmt);
LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] account {} got banned for '{}' seconds because it failed to authenticate '{}' times",
GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login, WrongPassBanTime, _accountInfo.FailedLogins);
}
else
{
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
stmt->SetData(0, GetRemoteIpAddress().to_string());
stmt->SetData(1, WrongPassBanTime);
LoginDatabase.Execute(stmt);
LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] IP got banned for '{}' seconds because account {} failed to authenticate '{}' times",
GetRemoteIpAddress().to_string(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login, _accountInfo.FailedLogins);
}
}
}
}
return true;
}
bool AuthSession::HandleReconnectChallenge()
{
_status = STATUS_CLOSED;
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer());
if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
return false;
std::string login((char const*)challenge->I, challenge->I_len);
LOG_DEBUG("server.authserver", "[ReconnectChallenge] '{}'", login);
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
std::array<char, 5> os;
os.fill('\0');
memcpy(os.data(), challenge->os, sizeof(challenge->os));
_os = os.data();
// Restore string order as its byte order is reversed
std::reverse(_os.begin(), _os.end());
_localizationName.resize(4);
for (int i = 0; i < 4; ++i)
_localizationName[i] = challenge->country[4 - i - 1];
// Get the account details from the account table
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE);
stmt->SetData(0, GetRemoteIpAddress().to_string());
stmt->SetData(1, login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::ReconnectChallengeCallback, this, std::placeholders::_1)));
return true;
}
void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result)
{
ByteBuffer pkt;
pkt << uint8(AUTH_RECONNECT_CHALLENGE);
if (!result)
{
pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
SendPacket(pkt);
return;
}
Field* fields = result->Fetch();
_accountInfo.LoadResult(fields);
_sessionKey = fields[12].Get<Binary, SESSION_KEY_LENGTH>();
Acore::Crypto::GetRandomBytes(_reconnectProof);
_status = STATUS_RECONNECT_PROOF;
pkt << uint8(WOW_SUCCESS);
pkt.append(_reconnectProof);
pkt.append(VersionChallenge.data(), VersionChallenge.size());
SendPacket(pkt);
}
bool AuthSession::HandleReconnectProof()
{
LOG_DEBUG("server.authserver", "Entering _HandleReconnectProof");
_status = STATUS_CLOSED;
sAuthReconnectProof_C* reconnectProof = reinterpret_cast<sAuthReconnectProof_C*>(GetReadBuffer().GetReadPointer());
if (_accountInfo.Login.empty())
return false;
Acore::Crypto::SHA1 sha;
sha.UpdateData(_accountInfo.Login);
sha.UpdateData(reconnectProof->R1, 16);
sha.UpdateData(_reconnectProof);
sha.UpdateData(_sessionKey);
sha.Finalize();
if (sha.GetDigest() == reconnectProof->R2)
{
if (!VerifyVersion(reconnectProof->R1, sizeof(reconnectProof->R1), reconnectProof->R3, true))
{
ByteBuffer packet;
packet << uint8(AUTH_RECONNECT_PROOF);
packet << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(packet);
return true;
}
// Sending response
ByteBuffer pkt;
pkt << uint8(AUTH_RECONNECT_PROOF);
pkt << uint8(WOW_SUCCESS);
pkt << uint16(0); // LoginFlags, 1 has account message
SendPacket(pkt);
_status = STATUS_AUTHED;
return true;
}
else
{
LOG_ERROR("server.authserver.hack", "'{}:{}' [ERROR] user {} tried to login, but session is invalid.", GetRemoteIpAddress().to_string(),
GetRemotePort(), _accountInfo.Login);
return false;
}
}
bool AuthSession::HandleRealmList()
{
LOG_DEBUG("server.authserver", "Entering _HandleRealmList");
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS);
stmt->SetData(0, _accountInfo.Id);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::RealmListCallback, this, std::placeholders::_1)));
_status = STATUS_WAITING_FOR_REALM_LIST;
return true;
}
void AuthSession::RealmListCallback(PreparedQueryResult result)
{
std::map<uint32, uint8> characterCounts;
if (result)
{
do
{
Field* fields = result->Fetch();
characterCounts[fields[0].Get<uint32>()] = fields[1].Get<uint8>();
} while (result->NextRow());
}
// Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm)
ByteBuffer pkt;
std::size_t RealmListSize = 0;
for (auto const& [realmHandle, realm] : sRealmList->GetRealms())
{
// don't work with realms which not compatible with the client
bool okBuild = ((_expversion & POST_BC_EXP_FLAG) && realm.Build == _build) || ((_expversion & PRE_BC_EXP_FLAG) && !AuthHelper::IsPreBCAcceptedClientBuild(realm.Build));
// No SQL injection. id of realm is controlled by the database.
uint32 flag = realm.Flags;
RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(realm.Build);
if (!okBuild)
{
if (!buildInfo)
continue;
flag |= REALM_FLAG_OFFLINE | REALM_FLAG_SPECIFYBUILD; // tell the client what build the realm is for
}
if (!buildInfo)
flag &= ~REALM_FLAG_SPECIFYBUILD;
std::string name = realm.Name;
if (_expversion & PRE_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD)
{
std::ostringstream ss;
ss << name << " (" << buildInfo->MajorVersion << '.' << buildInfo->MinorVersion << '.' << buildInfo->BugfixVersion << ')';
name = ss.str();
}
uint8 lock = (realm.AllowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0;
pkt << uint8(realm.Type); // realm type
if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
pkt << uint8(lock); // if 1, then realm locked
pkt << uint8(flag); // RealmFlags
pkt << name;
pkt << boost::lexical_cast<std::string>(realm.GetAddressForClient(GetRemoteIpAddress()));
pkt << float(realm.PopulationLevel);
pkt << uint8(characterCounts[realm.Id.Realm]);
pkt << uint8(realm.Timezone); // realm category
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
pkt << uint8(realm.Id.Realm);
else
pkt << uint8(0x0); // 1.12.1 and 1.12.2 clients
if (_expversion & POST_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD)
{
pkt << uint8(buildInfo->MajorVersion);
pkt << uint8(buildInfo->MinorVersion);
pkt << uint8(buildInfo->BugfixVersion);
pkt << uint16(buildInfo->Build);
}
++RealmListSize;
}
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
{
pkt << uint8(0x10);
pkt << uint8(0x00);
}
else // 1.12.1 and 1.12.2 clients
{
pkt << uint8(0x00);
pkt << uint8(0x02);
}
// make a ByteBuffer which stores the RealmList's size
ByteBuffer RealmListSizeBuffer;
RealmListSizeBuffer << uint32(0);
if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
RealmListSizeBuffer << uint16(RealmListSize);
else
RealmListSizeBuffer << uint32(RealmListSize);
ByteBuffer hdr;
hdr << uint8(REALM_LIST);
hdr << uint16(pkt.size() + RealmListSizeBuffer.size());
hdr.append(RealmListSizeBuffer); // append RealmList's size buffer
hdr.append(pkt); // append realms in the realmlist
SendPacket(hdr);
_status = STATUS_AUTHED;
}
bool AuthSession::VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect)
{
if (!sConfigMgr->GetOption<bool>("StrictVersionCheck", false))
return true;
Acore::Crypto::SHA1::Digest zeros{};
Acore::Crypto::SHA1::Digest const* versionHash{ nullptr };
if (!isReconnect)
{
RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(_build);
if (!buildInfo)
return false;
if (_os == "Win")
versionHash = &buildInfo->WindowsHash;
else if (_os == "OSX")
versionHash = &buildInfo->MacHash;
if (!versionHash)
return false;
if (zeros == *versionHash)
return true; // not filled serverside
}
else
versionHash = &zeros;
Acore::Crypto::SHA1 version;
version.UpdateData(a, aLength);
version.UpdateData(*versionHash);
version.Finalize();
return (versionProof == version.GetDigest());
}
@@ -0,0 +1,122 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __AUTHSESSION_H__
#define __AUTHSESSION_H__
#include "AsyncCallbackProcessor.h"
#include "BigNumber.h"
#include "ByteBuffer.h"
#include "Common.h"
#include "CryptoHash.h"
#include "Optional.h"
#include "QueryResult.h"
#include "SRP6.h"
#include "Socket.h"
#include <boost/asio/ip/tcp.hpp>
using boost::asio::ip::tcp;
class Field;
struct AuthHandler;
enum AuthStatus
{
STATUS_CHALLENGE = 0,
STATUS_LOGON_PROOF,
STATUS_RECONNECT_PROOF,
STATUS_AUTHED,
STATUS_WAITING_FOR_REALM_LIST,
STATUS_CLOSED
};
// cppcheck-suppress ctuOneDefinitionRuleViolation
struct AccountInfo
{
void LoadResult(Field* fields);
uint32 Id = 0;
std::string Login;
bool IsLockedToIP = false;
std::string LockCountry;
std::string LastIP;
uint32 Flags;
uint32 FailedLogins = 0;
bool IsBanned = false;
bool IsPermanentlyBanned = false;
AccountTypes SecurityLevel = SEC_PLAYER;
};
class AuthSession final : public Socket<AuthSession>
{
typedef Socket<AuthSession> AuthSocket;
public:
static std::unordered_map<uint8, AuthHandler> InitHandlers();
AuthSession(IoContextTcpSocket&& socket);
void Start() override;
bool Update() final;
void SendPacket(ByteBuffer& packet);
protected:
SocketReadCallbackResult ReadHandler() final;
private:
bool HandleLogonChallenge();
bool HandleLogonProof();
bool HandleReconnectChallenge();
bool HandleReconnectProof();
bool HandleRealmList();
void CheckIpCallback(PreparedQueryResult result);
void LogonChallengeCallback(PreparedQueryResult result);
void ReconnectChallengeCallback(PreparedQueryResult result);
void RealmListCallback(PreparedQueryResult result);
bool VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect);
Optional<Acore::Crypto::SRP6> _srp6;
SessionKey _sessionKey = {};
std::array<uint8, 16> _reconnectProof = {};
AuthStatus _status;
AccountInfo _accountInfo;
Optional<std::vector<uint8>> _totpSecret;
std::string _localizationName;
std::string _os;
std::string _ipCountry;
uint16 _build;
uint8 _expversion;
QueryCallbackProcessor _queryProcessor;
};
#pragma pack(push, 1)
struct AuthHandler
{
AuthStatus status;
std::size_t packetSize;
bool (AuthSession::* handler)();
};
#pragma pack(pop)
#endif
@@ -0,0 +1,65 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef AuthSocketMgr_h__
#define AuthSocketMgr_h__
#include "AuthSession.h"
#include "Config.h"
#include "SocketMgr.h"
class AuthSocketMgr : public SocketMgr<AuthSession>
{
typedef SocketMgr<AuthSession> BaseSocketMgr;
public:
static AuthSocketMgr& Instance()
{
static AuthSocketMgr instance;
return instance;
}
bool StartNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount = 1) override
{
if (!BaseSocketMgr::StartNetwork(ioContext, bindIp, port, threadCount))
return false;
_acceptor->AsyncAcceptWithCallback<&AuthSocketMgr::OnSocketAccept>();
return true;
}
protected:
NetworkThread<AuthSession>* CreateThreads() const override
{
NetworkThread<AuthSession>* threads = new NetworkThread<AuthSession>[1];
bool proxyProtocolEnabled = sConfigMgr->GetOption<bool>("EnableProxyProtocol", false, true);
if (proxyProtocolEnabled)
threads[0].EnableProxyProtocol();
return threads;
}
static void OnSocketAccept(IoContextTcpSocket&& sock, uint32 threadIndex)
{
Instance().OnSocketOpen(std::move(sock), threadIndex);
}
};
#define sAuthSocketMgr AuthSocketMgr::Instance()
#endif // AuthSocketMgr_h__
@@ -0,0 +1,460 @@
###############################################
# AzerothCore Auth Server configuration file #
###############################################
[authserver]
###################################################################################################
# SECTION INDEX
#
# EXAMPLE CONFIG
# AUTH SERVER SETTINGS
# MYSQL SETTINGS
# CRYPTOGRAPHY
# UPDATE SETTINGS
# LOGGING SYSTEM SETTINGS
# NETWORK
#
###################################################################################################
###################################################################################################
# EXAMPLE CONFIG
#
# Variable
# Description: Brief description what the variable is doing.
# Important: Annotation for important things about this variable.
# Example: "Example, i.e. if the value is a string"
# Default: 10 - (Enabled|Comment|Variable name in case of grouped config options)
# 0 - (Disabled|Comment|Variable name in case of grouped config options)
#
# Note to developers:
# - Copy this example to keep the formatting.
# - Line breaks should be at column 100.
###################################################################################################
###################################################################################################
# AUTH SERVER SETTINGS
#
# LogsDir
# Description: Logs directory setting.
# Important: LogsDir needs to be quoted, as the string might contain space characters.
# Logs directory must exists, or log file creation will be disabled.
# Example: "/home/youruser/azerothcore/logs"
# Default: "" - (Log files will be stored in the current path)
LogsDir = ""
#
# MaxPingTime
# Description: Time (in minutes) between database pings.
# Default: 30
MaxPingTime = 30
#
# RealmServerPort
# Description: TCP port to reach the auth server.
# Default: 3724
RealmServerPort = 3724
#
#
# BindIP
# Description: Bind auth server to IP/hostname
# Default: "0.0.0.0" - (Bind to all IPs on the system)
BindIP = "0.0.0.0"
#
# EnableProxyProtocol
# Description: Enables Proxy Protocol v2. When your server is behind a proxy,
# load balancer, or similar component, you need to enable Proxy Protocol v2 on both
# this server and the proxy/load balancer to track the real IP address of players.
# Example: 1 - (Enabled)
# Default: 0 - (Disabled)
EnableProxyProtocol = 0
#
# PidFile
# Description: Auth server PID file.
# Example: "./authserver.pid" - (Enabled)
# Default: "" - (Disabled)
PidFile = ""
#
# UseProcessors
# Description: Processors mask for Windows and Linux based multi-processor systems.
# Example: For a computer with 3 CPUs:
# 1 - 1st CPU only
# 2 - 2nd CPU only
# 4 - 3rd CPU only
# 6 - 2nd + 3rd CPUs, because "2 | 4" -> 6
# Default: 0 - (Selected by OS)
# 1+ - (Bit mask value of selected processors)
UseProcessors = 0
#
# ProcessPriority
# Description: Process priority setting for Windows and Linux based systems.
# Details: On Linux, a nice value of -15 is used. (requires superuser). On Windows, process is set to HIGH class.
# Default: 0 - (Normal)
# 1 - (High)
ProcessPriority = 0
#
# RealmsStateUpdateDelay
# Description: Time (in seconds) between realm list updates.
# Default: 20 - (Enabled)
# 0 - (Disabled)
RealmsStateUpdateDelay = 20
#
# WrongPass.MaxCount
# Description: Number of login attempts with wrong password before the account or IP will be
# banned.
# Default: 0 - (Disabled)
# 1+ - (Enabled)
WrongPass.MaxCount = 0
#
# WrongPass.BanTime
# Description: Time (in seconds) for banning account or IP for invalid login attempts.
# Default: 600 - (10 minutes)
# 0 - (Permanent ban)
WrongPass.BanTime = 600
#
# WrongPass.BanType
# Description: Ban type for invalid login attempts.
# Default: 0 - (Ban IP)
# 1 - (Ban Account)
WrongPass.BanType = 0
#
# WrongPass.Logging
# Description: Additionally log attempted wrong password logging
# Default: 0 - (Disabled)
# 1 - (Enabled)
WrongPass.Logging = 0
#
# BanExpiryCheckInterval
# Description: Time (in seconds) between checks for expired bans
# Default: 60
#
BanExpiryCheckInterval = 60
#
# StrictVersionCheck
# Description: Prevent modified clients from connecting
# Default: 0 - (Disabled)
# 1 - (Enabled)
#
StrictVersionCheck = 0
#
# SourceDirectory
# Description: The path to your AzerothCore source directory.
# If the path is left empty, the built-in CMAKE_SOURCE_DIR is used.
# Example: "../AzerothCore"
# Default: ""
#
SourceDirectory = ""
#
# MySQLExecutable
# Description: The path to your MySQL CLI binary.
# If the path is left empty, built-in path from cmake is used.
# Example: "C:/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe"
# "mysql.exe"
# "/usr/bin/mysql"
# Default: ""
#
MySQLExecutable = ""
#
# TempDir
# Description: Temp directory setting.
# Important: TempDir needs to be quoted, as the string might contain space characters.
# TempDir directory must exists, or the server can't work properly
# Example: "/home/youruser/azerothcore/temp"
# Default: "" - (Temp files will be stored in the current path)
TempDir = ""
#
# IPLocationFile
# Description: The path to your IP2Location database CSV file.
# Example: "C:/acore/IP2LOCATION-LITE-DB1.CSV"
# "/home/acore/IP2LOCATION-LITE-DB1.CSV"
# Default: "" - (Disabled)
IPLocationFile = ""
#
# AllowLoggingIPAddressesInDatabase
# Description: Specifies if IP addresses can be logged to the database
# Default: 1 - (Enabled)
# 0 - (Disabled)
#
AllowLoggingIPAddressesInDatabase = 1
#
###################################################################################################
###################################################################################################
# MYSQL SETTINGS
#
# LoginDatabaseInfo
# Description: Database connection settings for the realm server.
# Example: "hostname;port;username;password;database"
# ".;somenumber;username;password;database" - (Use named pipes on Windows
# "enable-named-pipe" to [mysqld]
# section my.ini)
# ".;/path/to/unix_socket;username;password;database" - (use Unix sockets on
# Unix/Linux)
# Default: "127.0.0.1;3306;acore;acore;acore_auth"
LoginDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_auth"
#
# Database.Reconnect.Seconds
# Database.Reconnect.Attempts
#
# Description: How many seconds between every reconnection attempt
# and how many attempts will be performed in total
# Default: 20 attempts every 15 seconds
#
Database.Reconnect.Seconds = 15
Database.Reconnect.Attempts = 20
#
# LoginDatabase.WorkerThreads
# Description: The amount of worker threads spawned to handle asynchronous (delayed) MySQL
# statements. Each worker thread is mirrored with its own connection to the
# Default: 1
LoginDatabase.WorkerThreads = 1
#
# LoginDatabase.SynchThreads
# Description: The amount of MySQL connections spawned to handle.
# Default: 1 - (LoginDatabase.WorkerThreads)
#
LoginDatabase.SynchThreads = 1
#
###################################################################################################
###################################################################################################
# CRYPTOGRAPHY
#
# EnableTOTP
# Description: Check if a TOTP token is needed on account login
#
# Default: 0 - (Disabled)
# 1 - (Enabled)
EnableTOTP = 0
# TOTPMasterSecret
# Description: The master key used to encrypt TOTP secrets for database storage.
# If you want to change this, uncomment TOTPOldMasterSecret, then copy
# your old secret there and startup authserver once. Afterwards, you can re-
# comment that line and get rid of your old secret.
#
# Default: <blank> - (Store TOTP secrets unencrypted)
# Example: 000102030405060708090A0B0C0D0E0F
TOTPMasterSecret =
# TOTPOldMasterSecret =
#
###################################################################################################
###################################################################################################
# UPDATE SETTINGS
#
# Updates.EnableDatabases
# Description: A mask that describes which databases shall be updated.
#
# Following flags are available
# DATABASE_LOGIN = 1, // Auth database
#
# Default: 0 - (All Disabled)
# 1 - (All Enabled)
Updates.EnableDatabases = 1
#
# Updates.AutoSetup
# Description: Auto populate empty databases.
# Default: 1 - (Enabled)
# 0 - (Disabled)
Updates.AutoSetup = 1
#
# Updates.Redundancy
# Description: Perform data redundancy checks through hashing
# to detect changes on sql updates and reapply it.
# Default: 1 - (Enabled)
# 0 - (Disabled)
Updates.Redundancy = 1
#
# Updates.ArchivedRedundancy
# Description: Check hashes of archived updates (slows down startup).
# Default: 0 - (Disabled)
# 1 - (Enabled)
Updates.ArchivedRedundancy = 0
#
# Updates.AllowRehash
# Description: Inserts the current file hash in the database if it is left empty.
# Useful if you want to mark a file as applied but you don't know its hash.
# Default: 1 - (Enabled)
# 0 - (Disabled)
Updates.AllowRehash = 1
#
# Updates.CleanDeadRefMaxCount
# Description: Cleans dead/ orphaned references that occur if an update was removed or renamed and edited in one step.
# It only starts the clean up if the count of the missing updates is below or equal the Updates.CleanDeadRefMaxCount value.
# This way prevents erasing of the update history due to wrong source directory state (maybe wrong branch or bad revision).
# Disable this if you want to know if the database is in a possible "dirty state".
# Default: 3 - (Enabled)
# 0 - (Disabled)
# -1 - (Enabled - unlimited)
Updates.CleanDeadRefMaxCount = 3
###################################################################################################
###################################################################################################
#
# LOGGING SYSTEM SETTINGS
#
# Appender config values: Given an appender "name"
# Appender.name
# Description: Defines 'where to log'
# Format: Type,LogLevel,Flags,optional1,optional2,optional3
#
# Type
# 0 - (None)
# 1 - (Console)
# 2 - (File)
# 3 - (DB)
#
# LogLevel
# 0 - (Disabled)
# 1 - (Fatal)
# 2 - (Error)
# 3 - (Warning)
# 4 - (Info)
# 5 - (Debug)
# 6 - (Trace)
#
# Flags:
# 0 - None
# 1 - Prefix Timestamp to the text
# 2 - Prefix Log Level to the text
# 4 - Prefix Log Filter type to the text
# 8 - Append timestamp to the log file name. Format: YYYY-MM-DD_HH-MM-SS (Only used with Type = 2)
# 16 - Make a backup of existing file before overwrite (Only used with Mode = w)
#
# Colors (read as optional1 if Type = Console)
# Format: "fatal error warn info debug trace"
# 0 - BLACK
# 1 - RED
# 2 - GREEN
# 3 - BROWN
# 4 - BLUE
# 5 - MAGENTA
# 6 - CYAN
# 7 - GREY
# 8 - YELLOW
# 9 - LRED
# 10 - LGREEN
# 11 - LBLUE
# 12 - LMAGENTA
# 13 - LCYAN
# 14 - WHITE
# Example: "1 9 3 6 5 8"
#
# File: Name of the file (read as optional1 if Type = File)
# Allows to use one "%s" to create dynamic files
#
# Mode: Mode to open the file (read as optional2 if Type = File)
# a - (Append)
# w - (Overwrite)
#
# MaxFileSize: Maximum file size of the log file before creating a new log file
# (read as optional3 if Type = File)
# Size is measured in bytes expressed in a 64-bit unsigned integer.
# Maximum value is 4294967295 (4 GB). Leave blank for no limit.
# NOTE: Does not work with dynamic filenames.
# Example: 536870912 (512 MB)
#
Appender.Console=1,5,0,"1 9 3 6 5 8"
Appender.Auth=2,5,0,Auth.log,w
# Logger config values: Given a logger "name"
# Logger.name
# Description: Defines 'What to log'
# Format: LogLevel,AppenderList
#
# LogLevel
# 0 - (Disabled)
# 1 - (Fatal)
# 2 - (Error)
# 3 - (Warning)
# 4 - (Info)
# 5 - (Debug)
# 6 - (Trace)
#
# AppenderList: List of appenders linked to logger
# (Using spaces as separator).
#
Logger.root=4,Console Auth
#
###################################################################################################
###################################################################################################
# NETWORK
#
# Network.UseSocketActivation
# Description: [LINUX ONLY FEATURE] Enable systemd socket activation support for the authserver.
# When enabled and the process is started by systemd socket activation,
# the server will use the socket passed by systemd instead of
# creating and binding its own listening socket. Disabled by default.
#
# Example: 1 - (Enabled)
# Default: 0 - (Disabled)
Network.UseSocketActivation = 0
#
###################################################################################################
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

+90
View File
@@ -0,0 +1,90 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "resource.h"
#include "revision.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "windows.h" //"afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APPICON ICON "authserver.ico"
/////////////////////////////////////////////////////////////////////////////
// Neutre (Par défaut système) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD)
#ifdef _WIN32
LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT
#pragma code_page(1252)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#if AC_BUILD_HAS_DEBUG_INFO == 1
#define AC_DEBUG VS_FF_DEBUG
#else
#define AC_DEBUG 0
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION AC_FILEVERSION
PRODUCTVERSION AC_PRODUCTVERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS (VS_FF_PRERELEASE | AC_DEBUG)
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080004b0"
BEGIN
VALUE "CompanyName", AC_COMPANYNAME_STR
VALUE "FileDescription", "Authentication Server Daemon"
VALUE "FileVersion", AC_FILEVERSION_STR
VALUE "InternalName", "authserver"
VALUE "LegalCopyright", AC_LEGALCOPYRIGHT_STR
VALUE "OriginalFilename", "authserver.exe"
VALUE "ProductName", "Authentication Server"
VALUE "ProductVersion", AC_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x800, 1200
END
END
#endif
+15
View File
@@ -0,0 +1,15 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by AzerothCore.rc
//
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
@@ -0,0 +1,149 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "ACSoap.h"
#include "AccountMgr.h"
#include "Log.h"
#include "World.h"
#include "soapStub.h"
void ACSoapThread(const std::string& host, uint16 port)
{
struct soap soap;
soap_init(&soap);
soap_set_imode(&soap, SOAP_C_UTFSTRING);
soap_set_omode(&soap, SOAP_C_UTFSTRING);
// check every 3 seconds if world ended
soap.accept_timeout = 3;
soap.recv_timeout = 5;
soap.send_timeout = 5;
if (!soap_valid_socket(soap_bind(&soap, host.c_str(), port, 100)))
{
LOG_ERROR("network.soap", "ACSoap: couldn't bind to {}:{}", host, port);
exit(-1);
}
LOG_INFO("network.soap", "ACSoap: bound to http://{}:{}", host, port);
while (!World::IsStopped())
{
if (!soap_valid_socket(soap_accept(&soap)))
continue; // ran into an accept timeout
LOG_DEBUG("network.soap", "ACSoap: accepted connection from IP={}.{}.{}.{}", (int)(soap.ip >> 24) & 0xFF, (int)(soap.ip >> 16) & 0xFF, (int)(soap.ip >> 8) & 0xFF, (int)soap.ip & 0xFF);
struct soap* thread_soap = soap_copy(&soap);// make a safe copy
process_message(thread_soap);
}
soap_destroy(&soap);
soap_end(&soap);
soap_done(&soap);
}
void process_message(struct soap* soap_message)
{
LOG_TRACE("network.soap", "SOAPWorkingThread::process_message");
soap_serve(soap_message);
soap_destroy(soap_message); // dealloc C++ data
soap_end(soap_message); // dealloc data and clean up
soap_free(soap_message); // detach soap struct and fre up the memory
}
/*
Code used for generating stubs:
int ns1__executeCommand(char* command, char** result);
*/
int ns1__executeCommand(soap* soap, char* command, char** result)
{
// security check
if (!soap->userid || !soap->passwd)
{
LOG_DEBUG("network.soap", "ACSoap: Client didn't provide login information");
return 401;
}
uint32 accountId = AccountMgr::GetId(soap->userid);
if (!accountId)
{
LOG_DEBUG("network", "ACSoap: Client used invalid username '{}'", soap->userid);
return 401;
}
if (!AccountMgr::CheckPassword(accountId, soap->passwd))
{
LOG_DEBUG("network.soap", "ACSoap: invalid password for account '{}'", soap->userid);
return 401;
}
if (AccountMgr::GetSecurity(accountId) < SEC_ADMINISTRATOR)
{
LOG_DEBUG("network.soap", "ACSoap: {}'s gmlevel is too low", soap->userid);
return 403;
}
if (!command || !*command)
return soap_sender_fault(soap, "Command can not be empty", "The supplied command was an empty string");
LOG_DEBUG("network.soap", "ACSoap: got command '{}'", command);
SOAPCommand connection;
// commands are executed in the world thread. We have to wait for them to be completed
{
// CliCommandHolder will be deleted from world, accessing after queueing is NOT save
CliCommandHolder* cmd = new CliCommandHolder(&connection, command, &SOAPCommand::print, &SOAPCommand::commandFinished);
sWorld->QueueCliCommand(cmd);
}
// Wait until the command has finished executing
connection.finishedPromise.get_future().wait();
// The command has finished executing already
char* printBuffer = soap_strdup(soap, connection.m_printBuffer.c_str());
if (connection.hasCommandSucceeded())
{
*result = printBuffer;
return SOAP_OK;
}
else
return soap_sender_fault(soap, printBuffer, printBuffer);
}
void SOAPCommand::commandFinished(void* soapconnection, bool success)
{
SOAPCommand* con = (SOAPCommand*)soapconnection;
con->setCommandSuccess(success);
}
////////////////////////////////////////////////////////////////////////////////
//
// Namespace Definition Table
//
////////////////////////////////////////////////////////////////////////////////
struct Namespace namespaces[] =
{
{ "SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", nullptr, nullptr }, // must be first
{ "SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", nullptr, nullptr }, // must be second
{ "xsi", "http://www.w3.org/1999/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", nullptr },
{ "xsd", "http://www.w3.org/1999/XMLSchema", "http://www.w3.org/*/XMLSchema", nullptr },
{ "ns1", "urn:AC", nullptr, nullptr }, // "ns1" namespace prefix
{ nullptr, nullptr, nullptr, nullptr }
};
@@ -0,0 +1,63 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _ACSOAP_H
#define _ACSOAP_H
#include "Define.h"
#include <future>
void process_message(struct soap* soap_message);
void ACSoapThread(const std::string& host, uint16 port);
class SOAPCommand
{
public:
SOAPCommand() :
m_success(false) { }
~SOAPCommand() { }
void appendToPrintBuffer(std::string_view msg)
{
m_printBuffer += msg;
}
void setCommandSuccess(bool val)
{
m_success = val;
finishedPromise.set_value();
}
bool hasCommandSucceeded() const
{
return m_success;
}
static void print(void* callbackArg, std::string_view msg)
{
((SOAPCommand*)callbackArg)->appendToPrintBuffer(msg);
}
static void commandFinished(void* callbackArg, bool success);
bool m_success;
std::string m_printBuffer;
std::promise<void> finishedPromise;
};
#endif
@@ -0,0 +1,241 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
/// \addtogroup Acored
/// @{
/// \file
#include "CliRunnable.h"
#include "Config.h"
#include "ObjectMgr.h"
#include "World.h"
#include <fmt/core.h>
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
#include <windows.h>
#include <iostream>
#else
#include "Chat.h"
#include "ChatCommand.h"
#include <cstring>
#include <readline/history.h>
#include <readline/readline.h>
#endif
static constexpr char CLI_PREFIX[] = "AC> ";
static inline void PrintCliPrefix()
{
fmt::print(CLI_PREFIX);
}
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
namespace Acore::Impl::Readline
{
static std::vector<std::string> vec;
char* cli_unpack_vector(char const*, int state)
{
static std::size_t i=0;
if (!state)
i = 0;
if (i < vec.size())
return strdup(vec[i++].c_str());
else
return nullptr;
}
char** cli_completion(char const* text, int /*start*/, int /*end*/)
{
::rl_attempted_completion_over = 1;
vec = Acore::ChatCommands::GetAutoCompletionsFor(CliHandler(nullptr,nullptr), text);
return ::rl_completion_matches(text, &cli_unpack_vector);
}
int cli_hook_func()
{
if (World::IsStopped())
::rl_done = 1;
return 0;
}
}
#endif
void utf8print(void* /*arg*/, std::string_view str)
{
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
fmt::print("{}", str);
#else
{
fmt::print("{}", str);
fflush(stdout);
}
#endif
}
void commandFinished(void*, bool /*success*/)
{
PrintCliPrefix();
fflush(stdout);
}
#ifdef linux
// Non-blocking keypress detector, when return pressed, return 1, else always return 0
int kb_hit_return()
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, nullptr, nullptr, &tv);
return FD_ISSET(STDIN_FILENO, &fds);
}
#endif
/// %Thread start
void CliThread()
{
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
// Set console code pages to UTF-8
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
// print this here the first time
// later it will be printed after command queue updates
PrintCliPrefix();
#else
::rl_attempted_completion_function = &Acore::Impl::Readline::cli_completion;
{
static char BLANK = '\0';
::rl_completer_word_break_characters = &BLANK;
}
::rl_event_hook = &Acore::Impl::Readline::cli_hook_func;
#endif
if (sConfigMgr->GetOption<bool>("BeepAtStart", true))
printf("\a"); // \a = Alert
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
if (sConfigMgr->GetOption<bool>("FlashAtStart", true))
{
FLASHWINFO fInfo;
fInfo.cbSize = sizeof(FLASHWINFO);
fInfo.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG;
fInfo.hwnd = GetConsoleWindow();
fInfo.uCount = 0;
fInfo.dwTimeout = 0;
FlashWindowEx(&fInfo);
}
// Get console input handle once for reading commands
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
if (hStdIn == INVALID_HANDLE_VALUE)
{
LOG_ERROR("server.worldserver", "Failed to get console input handle");
return;
}
#endif
///- As long as the World is running (no World::m_stopEvent), get the command line and handle it
while (!World::IsStopped())
{
fflush(stdout);
std::string command;
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
static bool checkedConsole = false;
static bool isRealConsole = false;
if (!checkedConsole)
{
DWORD mode = 0;
isRealConsole = GetConsoleMode(hStdIn, &mode);
checkedConsole = true;
}
if (isRealConsole)
{
// ===== Real Windows Console =====
wchar_t commandbuf[256];
DWORD charsRead = 0;
if (ReadConsoleW(hStdIn, commandbuf,
sizeof(commandbuf) / sizeof(wchar_t) - 1,
&charsRead, nullptr))
{
if (charsRead > 0)
{
commandbuf[charsRead] = L'\0';
if (!WStrToUtf8(commandbuf, charsRead, command))
{
PrintCliPrefix();
continue;
}
}
}
}
else
{
// ===== Redirected input (pipe) =====
if (!std::getline(std::cin, command))
{
World::StopNow(SHUTDOWN_EXIT_CODE);
break;
}
}
#else
char* command_str = readline(CLI_PREFIX);
::rl_bind_key('\t', ::rl_complete);
if (command_str != nullptr)
{
command = command_str;
free(command_str);
}
#endif
if (!command.empty())
{
std::size_t nextLineIndex = command.find_first_of("\r\n");
if (nextLineIndex != std::string::npos)
{
if (nextLineIndex == 0)
{
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
PrintCliPrefix();
#endif
continue;
}
command.erase(nextLineIndex);
}
fflush(stdout);
sWorld->QueueCliCommand(new CliCommandHolder(nullptr, command.c_str(), &utf8print, &commandFinished));
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
add_history(command.c_str());
#endif
}
else if (feof(stdin))
{
World::StopNow(SHUTDOWN_EXIT_CODE);
}
}
}
@@ -0,0 +1,30 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
/// \addtogroup Acored
/// @{
/// \file
#ifndef __CLIRUNNABLE_H
#define __CLIRUNNABLE_H
/// Command Line Interface handling thread
void CliThread();
#endif
/// @}
+746
View File
@@ -0,0 +1,746 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
/// \addtogroup Acored Acore Daemon
/// @{
/// \file
#include "ACSoap.h"
#include "AppenderDB.h"
#include "AsyncAcceptor.h"
#include "Banner.h"
#include "BattlegroundMgr.h"
#include "BigNumber.h"
#include "CliRunnable.h"
#include "Common.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "GitRevision.h"
#include "IoContext.h"
#include "MapMgr.h"
#include "Metric.h"
#include "ModuleMgr.h"
#include "ModulesScriptLoader.h"
#include "MySQLThreading.h"
#include "OpenSSLCrypto.h"
#include "OutdoorPvPMgr.h"
#include "ProcessPriority.h"
#include "RASession.h"
#include "RealmList.h"
#include "Resolver.h"
#include "ScriptLoader.h"
#include "ScriptMgr.h"
#include "SecretMgr.h"
#include "SharedDefines.h"
#include "SteadyTimer.h"
#include "Systemd.h"
#include "World.h"
#include "WorldSessionMgr.h"
#include "WorldSocket.h"
#include "WorldSocketMgr.h"
#include <boost/asio/signal_set.hpp>
#include <boost/program_options.hpp>
#include <csignal>
#include <filesystem>
#include <iostream>
#include <openssl/crypto.h>
#include <openssl/opensslv.h>
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
#include "ServiceWin32.h"
char serviceName[] = "worldserver";
char serviceLongName[] = "AzerothCore world service";
char serviceDescription[] = "AzerothCore World of Warcraft emulator world service";
/*
* -1 - not in service mode
* 0 - stopped
* 1 - running
* 2 - paused
*/
int m_ServiceStatus = -1;
#include <boost/dll/shared_library.hpp>
#include <timeapi.h>
#endif
#ifndef _ACORE_CORE_CONFIG
#define _ACORE_CORE_CONFIG "worldserver.conf"
#endif
using namespace boost::program_options;
namespace fs = std::filesystem;
class FreezeDetector
{
public:
FreezeDetector(Acore::Asio::IoContext& ioContext, uint32 maxCoreStuckTime)
: _timer(ioContext), _worldLoopCounter(0), _lastChangeMsTime(getMSTime()), _maxCoreStuckTimeInMs(maxCoreStuckTime) { }
static void Start(std::shared_ptr<FreezeDetector> const& freezeDetector)
{
freezeDetector->_timer.expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(5));
freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, std::weak_ptr<FreezeDetector>(freezeDetector), std::placeholders::_1));
}
static void Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error);
private:
boost::asio::steady_timer _timer;
uint32 _worldLoopCounter;
uint32 _lastChangeMsTime;
uint32 _maxCoreStuckTimeInMs;
};
void SignalHandler(boost::system::error_code const& error, int signalNumber);
void ClearOnlineAccounts();
bool StartDB();
void StopDB();
bool LoadRealmInfo(Acore::Asio::IoContext& ioContext);
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext);
void ShutdownCLIThread(std::thread* cliThread);
void WorldUpdateLoop();
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& cfg_service);
/// Launch the Azeroth server
int main(int argc, char** argv)
{
Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_WORLDSERVER;
signal(SIGABRT, &Acore::AbortHandler);
// Command line parsing
auto configFile = fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_CORE_CONFIG));
std::string configService;
auto vm = GetConsoleArguments(argc, argv, configFile, configService);
// exit if help or version is enabled
if (vm.count("help") || vm.count("version"))
return 0;
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
if (configService.compare("install") == 0)
return WinServiceInstall() == true ? 0 : 1;
else if (configService.compare("uninstall") == 0)
return WinServiceUninstall() == true ? 0 : 1;
else if (configService.compare("run") == 0)
WinServiceRun();
Optional<UINT> newTimerResolution;
boost::system::error_code dllError;
std::shared_ptr<boost::dll::shared_library> winmm(new boost::dll::shared_library("winmm.dll", dllError, boost::dll::load_mode::search_system_folders), [&](boost::dll::shared_library* lib)
{
try
{
if (newTimerResolution)
lib->get<decltype(timeEndPeriod)>("timeEndPeriod")(*newTimerResolution);
}
catch (std::exception const&)
{
// ignore
}
delete lib;
});
if (winmm->is_loaded())
{
try
{
auto timeGetDevCapsPtr = winmm->get<decltype(timeGetDevCaps)>("timeGetDevCaps");
// setup timer resolution
TIMECAPS timeResolutionLimits;
if (timeGetDevCapsPtr(&timeResolutionLimits, sizeof(TIMECAPS)) == TIMERR_NOERROR)
{
auto timeBeginPeriodPtr = winmm->get<decltype(timeBeginPeriod)>("timeBeginPeriod");
newTimerResolution = std::min(std::max(timeResolutionLimits.wPeriodMin, 1u), timeResolutionLimits.wPeriodMax);
timeBeginPeriodPtr(*newTimerResolution);
}
}
catch (std::exception const& e)
{
printf("Failed to initialize timer resolution: %s\n", e.what());
}
}
#endif
// Add file and args in config
sConfigMgr->Configure(configFile.generic_string(), {argv, argv + argc}, CONFIG_FILE_LIST);
if (!sConfigMgr->LoadAppConfigs())
return 1;
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
// Init all logs
sLog->RegisterAppender<AppenderDB>();
// If logs are supposed to be handled async then we need to pass the IoContext into the Log singleton
sLog->Initialize(sConfigMgr->GetOption<bool>("Log.Async.Enable", false) ? ioContext.get() : nullptr);
Acore::Banner::Show("worldserver-daemon",
[](std::string_view text)
{
LOG_INFO("server.worldserver", text);
},
[]()
{
LOG_INFO("server.worldserver", "> Using configuration file {}", sConfigMgr->GetFilename());
LOG_INFO("server.worldserver", "> Using SSL version: {} (library: {})", OPENSSL_VERSION_TEXT, OpenSSL_version(OPENSSL_VERSION));
LOG_INFO("server.worldserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
});
OpenSSLCrypto::threadsSetup();
std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); });
// Seed the OpenSSL's PRNG here.
// That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login
BigNumber seed;
seed.SetRand(16 * 8);
/// worldserver PID file creation
std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", "");
if (!pidFile.empty())
{
if (uint32 pid = CreatePIDFile(pidFile))
LOG_ERROR("server", "Daemon PID: {}\n", pid); // outError for red color in console
else
{
LOG_ERROR("server", "Cannot create PID file {} (possible error: permission)\n", pidFile);
return 1;
}
}
// Set signal handlers (this must be done before starting IoContext threads, because otherwise they would unblock and exit)
boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM);
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
signals.add(SIGBREAK);
#endif
signals.async_wait(SignalHandler);
// Start the Boost based thread pool
int numThreads = sConfigMgr->GetOption<int32>("ThreadPool", 2);
std::shared_ptr<std::vector<std::thread>> threadPool(new std::vector<std::thread>(), [ioContext](std::vector<std::thread>* del)
{
ioContext->stop();
for (std::thread& thr : *del)
thr.join();
delete del;
});
if (numThreads < 1)
{
numThreads = 1;
}
for (int i = 0; i < numThreads; ++i)
{
threadPool->push_back(std::thread([ioContext]()
{
ioContext->run();
}));
}
// Set process priority according to configuration settings
SetProcessPriority("server.worldserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, true));
// Loading modules configs before scripts
sConfigMgr->LoadModulesConfigs();
sScriptMgr->SetScriptLoader(AddScripts);
sScriptMgr->SetModulesLoader(AddModulesScripts);
std::shared_ptr<void> sScriptMgrHandle(nullptr, [](void*)
{
sScriptMgr->Unload();
//sScriptReloadMgr->Unload();
});
LOG_INFO("server.loading", "Initializing Scripts...");
sScriptMgr->Initialize();
// Start the databases
if (!StartDB())
return 1;
std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); });
// set server offline (not connectable)
LoginDatabase.DirectExecute("UPDATE realmlist SET flag = (flag & ~{}) | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
LoadRealmInfo(*ioContext);
sMetric->Initialize(realm.Name, *ioContext, []()
{
METRIC_VALUE("online_players", sWorldSessionMgr->GetPlayerCount());
METRIC_VALUE("db_queue_login", uint64(LoginDatabase.QueueSize()));
METRIC_VALUE("db_queue_character", uint64(CharacterDatabase.QueueSize()));
METRIC_VALUE("db_queue_world", uint64(WorldDatabase.QueueSize()));
});
METRIC_EVENT("events", "Worldserver started", "");
std::shared_ptr<void> sMetricHandle(nullptr, [](void*)
{
METRIC_EVENT("events", "Worldserver shutdown", "");
sMetric->Unload();
});
Acore::Module::SetEnableModulesList(AC_MODULES_LIST);
///- Initialize the World
sSecretMgr->Initialize();
sWorld->SetInitialWorldSettings();
std::shared_ptr<void> mapManagementHandle(nullptr, [](void*)
{
// unload battleground templates before different singletons destroyed
sBattlegroundMgr->DeleteAllBattlegrounds();
sOutdoorPvPMgr->Die(); // unload it before MapMgr
sMapMgr->UnloadAll(); // unload all grids (including locked in memory)
sScriptMgr->OnAfterUnloadAllMaps();
});
// Start the Remote Access port (acceptor) if enabled
std::unique_ptr<AsyncAcceptor> raAcceptor;
if (sConfigMgr->GetOption<bool>("Ra.Enable", false))
{
raAcceptor.reset(StartRaSocketAcceptor(*ioContext));
}
// Start soap serving thread if enabled
std::shared_ptr<std::thread> soapThread;
if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false))
{
soapThread.reset(new std::thread(ACSoapThread, sConfigMgr->GetOption<std::string>("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetOption<int32>("SOAP.Port", 7878))),
[](std::thread* thr)
{
thr->join();
delete thr;
});
}
// Launch the worldserver listener socket
uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
std::string worldListener = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
int networkThreads = sConfigMgr->GetOption<int32>("Network.Threads", 1);
if (networkThreads <= 0)
{
LOG_ERROR("server.worldserver", "Network.Threads must be greater than 0");
World::StopNow(ERROR_EXIT_CODE);
return 1;
}
if (!sWorldSocketMgr.StartWorldNetwork(*ioContext, worldListener, worldPort, networkThreads))
{
LOG_ERROR("server.worldserver", "Failed to initialize network");
World::StopNow(ERROR_EXIT_CODE);
return 1;
}
std::shared_ptr<void> sWorldSocketMgrHandle(nullptr, [](void*)
{
sWorldSessionMgr->KickAll(); // save and kick all players
sWorldSessionMgr->UpdateSessions(1); // real players unload required UpdateSessions call
sWorldSocketMgr.StopNetwork();
///- Clean database before leaving
ClearOnlineAccounts();
});
// Set server online (allow connecting now)
LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag & ~{}, population = 0 WHERE id = '{}'", REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
realm.PopulationLevel = 0.0f;
realm.Flags = RealmFlags(realm.Flags & ~uint32(REALM_FLAG_VERSION_MISMATCH));
// Start the freeze check callback cycle in 5 seconds (cycle itself is 1 sec)
std::shared_ptr<FreezeDetector> freezeDetector;
if (int32 coreStuckTime = sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 60))
{
freezeDetector = std::make_shared<FreezeDetector>(*ioContext, coreStuckTime * 1000);
FreezeDetector::Start(freezeDetector);
LOG_INFO("server.worldserver", "Starting up anti-freeze thread ({} seconds max stuck time)...", coreStuckTime);
}
LOG_INFO("server.worldserver", "{} (worldserver-daemon) ready...", GitRevision::GetFullVersion());
sScriptMgr->OnStartup();
// Launch CliRunnable thread
std::shared_ptr<std::thread> cliThread;
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
if (sConfigMgr->GetOption<bool>("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
#else
if (sConfigMgr->GetOption<bool>("Console.Enable", true))
#endif
{
cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread);
}
WorldUpdateLoop();
// Shutdown starts here
threadPool.reset();
sLog->SetSynchronous();
sScriptMgr->OnShutdown();
// set server offline
if (!sConfigMgr->GetOption<bool>("Network.UseSocketActivation", false))
LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, realm.Id.Realm);
LOG_INFO("server.worldserver", "Halting process...");
// 0 - normal shutdown
// 1 - shutdown at error
// 2 - restart command used, this code can be used by restarter for restart AzerothCore
return World::GetExitCode();
}
/// Initialize connection to the databases
bool StartDB()
{
MySQL::Library_Init();
// Load databases
DatabaseLoader loader("server.worldserver", DatabaseLoader::DATABASE_MASK_ALL, AC_MODULES_LIST);
loader
.AddDatabase(LoginDatabase, "Login")
.AddDatabase(CharacterDatabase, "Character")
.AddDatabase(WorldDatabase, "World");
if (!loader.Load())
return false;
///- Get the realm Id from the configuration file
realm.Id.Realm = sConfigMgr->GetOption<uint32>("RealmID", 1);
if (!realm.Id.Realm)
{
LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file");
return false;
}
else if (realm.Id.Realm > 255)
{
/*
* Due to the client only being able to read a realm.Id.Realm
* with a size of uint8 we can "only" store up to 255 realms
* anything further the client will behave anormaly
*/
LOG_ERROR("server.worldserver", "Realm ID must range from 1 to 255");
return false;
}
LOG_INFO("server.loading", "Loading World Information...");
LOG_INFO("server.loading", "> RealmID: {}", realm.Id.Realm);
///- Clean the database before starting
ClearOnlineAccounts();
///- Insert version info into DB
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_VERSION);
stmt->SetData(0, GitRevision::GetFullVersion());
stmt->SetData(1, GitRevision::GetHash());
WorldDatabase.Execute(stmt);
sWorld->LoadDBVersion();
LOG_INFO("server.loading", "> Version DB world: {}", sWorld->GetDBVersion());
sScriptMgr->OnAfterDatabasesLoaded(loader.GetUpdateFlags());
return true;
}
void StopDB()
{
CharacterDatabase.Close();
WorldDatabase.Close();
LoginDatabase.Close();
MySQL::Library_End();
}
/// Clear 'online' status for all accounts with characters in this realm
void ClearOnlineAccounts()
{
// Reset online status for all accounts with characters on the current realm
// pussywizard: tc query would set online=0 even if logged in on another realm >_>
LoginDatabase.DirectExecute("UPDATE account SET online = 0 WHERE online = {}", realm.Id.Realm);
// Reset online status for all characters
CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0");
}
void ShutdownCLIThread(std::thread* cliThread)
{
if (cliThread)
{
#ifdef _WIN32
// First try to cancel any I/O in the CLI thread
if (!CancelSynchronousIo(cliThread->native_handle()))
{
// if CancelSynchronousIo() fails, print the error and try with old way
DWORD errorCode = GetLastError();
LPCSTR errorBuffer;
DWORD formatReturnCode = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, errorCode, 0, (LPTSTR)&errorBuffer, 0, nullptr);
if (!formatReturnCode)
errorBuffer = "Unknown error";
LOG_DEBUG("server.worldserver", "Error cancelling I/O of CliThread, error code {}, detail: {}", uint32(errorCode), errorBuffer);
if (!formatReturnCode)
LocalFree((LPSTR)errorBuffer);
// send keyboard input to safely unblock the CLI thread
INPUT_RECORD b[4];
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
b[0].EventType = KEY_EVENT;
b[0].Event.KeyEvent.bKeyDown = TRUE;
b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
b[0].Event.KeyEvent.wRepeatCount = 1;
b[1].EventType = KEY_EVENT;
b[1].Event.KeyEvent.bKeyDown = FALSE;
b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
b[1].Event.KeyEvent.wRepeatCount = 1;
b[2].EventType = KEY_EVENT;
b[2].Event.KeyEvent.bKeyDown = TRUE;
b[2].Event.KeyEvent.dwControlKeyState = 0;
b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[2].Event.KeyEvent.wRepeatCount = 1;
b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].EventType = KEY_EVENT;
b[3].Event.KeyEvent.bKeyDown = FALSE;
b[3].Event.KeyEvent.dwControlKeyState = 0;
b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].Event.KeyEvent.wRepeatCount = 1;
DWORD numb;
WriteConsoleInput(hStdIn, b, 4, &numb);
}
#endif
cliThread->join();
delete cliThread;
}
}
void WorldUpdateLoop()
{
uint32 minUpdateDiff = uint32(sConfigMgr->GetOption<int32>("MinWorldUpdateTime", 1));
uint32 realCurrTime = 0;
uint32 realPrevTime = getMSTime();
uint32 maxCoreStuckTime = uint32(sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 60)) * 1000;
uint32 halfMaxCoreStuckTime = maxCoreStuckTime / 2;
if (!halfMaxCoreStuckTime)
halfMaxCoreStuckTime = std::numeric_limits<uint32>::max();
LoginDatabase.WarnAboutSyncQueries(true);
CharacterDatabase.WarnAboutSyncQueries(true);
WorldDatabase.WarnAboutSyncQueries(true);
///- While we have not World::m_stopEvent, update the world
while (!World::IsStopped())
{
++World::m_worldLoopCounter;
realCurrTime = getMSTime();
uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime);
if (diff < minUpdateDiff)
{
uint32 sleepTime = minUpdateDiff - diff;
if (sleepTime >= halfMaxCoreStuckTime)
LOG_ERROR("server.worldserver", "WorldUpdateLoop() waiting for {} ms with MaxCoreStuckTime set to {} ms", sleepTime, maxCoreStuckTime);
// sleep until enough time passes that we can update all timers
std::this_thread::sleep_for(Milliseconds(sleepTime));
continue;
}
sWorld->Update(diff);
realPrevTime = realCurrTime;
#ifdef _WIN32
if (m_ServiceStatus == 0)
World::StopNow(SHUTDOWN_EXIT_CODE);
while (m_ServiceStatus == 2)
Sleep(1000);
#endif
}
LoginDatabase.WarnAboutSyncQueries(false);
CharacterDatabase.WarnAboutSyncQueries(false);
WorldDatabase.WarnAboutSyncQueries(false);
}
void SignalHandler(boost::system::error_code const& error, int /*signalNumber*/)
{
if (!error)
World::StopNow(SHUTDOWN_EXIT_CODE);
}
void FreezeDetector::Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error)
{
if (!error)
{
if (std::shared_ptr<FreezeDetector> freezeDetector = freezeDetectorRef.lock())
{
uint32 curtime = getMSTime();
uint32 worldLoopCounter = World::m_worldLoopCounter;
if (freezeDetector->_worldLoopCounter != worldLoopCounter)
{
freezeDetector->_lastChangeMsTime = curtime;
freezeDetector->_worldLoopCounter = worldLoopCounter;
}
// possible freeze
else
{
uint32 msTimeDiff = getMSTimeDiff(freezeDetector->_lastChangeMsTime, curtime);
if (msTimeDiff > freezeDetector->_maxCoreStuckTimeInMs)
{
LOG_ERROR("server.worldserver", "World Thread hangs for {} ms, forcing a crash!", msTimeDiff);
ABORT("World Thread hangs for {} ms, forcing a crash!", msTimeDiff);
}
}
freezeDetector->_timer.expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(1));
freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, freezeDetectorRef, std::placeholders::_1));
}
}
}
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext)
{
uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443));
std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0");
AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort);
if (!acceptor->Bind())
{
LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor");
delete acceptor;
return nullptr;
}
acceptor->AsyncAccept<RASession>();
return acceptor;
}
bool LoadRealmInfo(Acore::Asio::IoContext& ioContext)
{
QueryResult result = LoginDatabase.Query("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE id = {}", realm.Id.Realm);
if (!result)
return false;
Acore::Asio::Resolver resolver(ioContext);
Field* fields = result->Fetch();
realm.Name = fields[1].Get<std::string>();
Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[2].Get<std::string>(), "");
if (!externalAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[2].Get<std::string>());
return false;
}
realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(externalAddress->address());
Optional<boost::asio::ip::tcp::endpoint> localAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[3].Get<std::string>(), "");
if (!localAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[3].Get<std::string>());
return false;
}
realm.LocalAddress = std::make_unique<boost::asio::ip::address>(localAddress->address());
Optional<boost::asio::ip::tcp::endpoint> localSubmask = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[4].Get<std::string>(), "");
if (!localSubmask)
{
LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[4].Get<std::string>());
return false;
}
realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(localSubmask->address());
realm.Port = fields[5].Get<uint16>();
realm.Type = fields[6].Get<uint8>();
realm.Flags = RealmFlags(fields[7].Get<uint8>());
realm.Timezone = fields[8].Get<uint8>();
realm.AllowedSecurityLevel = AccountTypes(fields[9].Get<uint8>());
realm.PopulationLevel = fields[10].Get<float>();
realm.Build = fields[11].Get<uint32>();
return true;
}
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& configService)
{
options_description all("Allowed options");
all.add_options()
("help,h", "print usage message")
("version,v", "print version build info")
("dry-run,d", "Dry run")
("config,c", value<fs::path>(&configFile)->default_value(fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_CORE_CONFIG))), "use <arg> as configuration file")
("config-policy", value<std::string>()->value_name("policy"), "override config severity policy (e.g. default=skip,critical_option=fatal)");
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
options_description win("Windows platform specific options");
win.add_options()
("service,s", value<std::string>(&configService)->default_value(""), "Windows service options: [install | uninstall]");
all.add(win);
#endif
variables_map vm;
try
{
store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), vm);
notify(vm);
}
catch (std::exception const& e)
{
std::cerr << e.what() << "\n";
}
if (vm.count("help"))
std::cout << all << "\n";
else if (vm.count("version"))
std::cout << GitRevision::GetFullVersion() << "\n";
else if (vm.count("dry-run"))
sConfigMgr->setDryRun(true);
return vm;
}
@@ -0,0 +1,24 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "Common.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "Util.h"
#include "World.h"
#include "WorldSocket.h"
@@ -0,0 +1,224 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "RASession.h"
#include "AccountMgr.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "Duration.h"
#include "Log.h"
#include "MotdMgr.h"
#include "QueryResult.h"
#include "SRP6.h"
#include "Util.h"
#include "World.h"
#include <boost/asio/buffer.hpp>
#include <boost/asio/read_until.hpp>
#include <thread>
using boost::asio::ip::tcp;
void RASession::Start()
{
// wait 1 second for active connections to send negotiation request
for (int counter = 0; counter < 10 && _socket.available() == 0; counter++)
std::this_thread::sleep_for(100ms);
// Check if there are bytes available, if they are, then the client is requesting the negotiation
if (_socket.available() > 0)
{
// Handle subnegotiation
char buf[1024] = { };
_socket.read_some(boost::asio::buffer(buf));
// Send the end-of-negotiation packet
uint8 const reply[2] = { 0xFF, 0xF0 };
_socket.write_some(boost::asio::buffer(reply));
}
Send("Authentication Required\r\n");
Send("Username: ");
std::string username = ReadString();
if (username.empty())
return;
LOG_INFO("commands.ra", "Accepting RA connection from user {} (IP: {})", username, GetRemoteIpAddress());
Send("Password: ");
std::string password = ReadString();
if (password.empty())
return;
if (!CheckAccessLevel(username) || !CheckPassword(username, password))
{
Send("Authentication failed\r\n");
_socket.close();
return;
}
LOG_INFO("commands.ra", "User {} (IP: {}) authenticated correctly to RA", username, GetRemoteIpAddress());
// Authentication successful, send the motd
Send(std::string(std::string(sMotdMgr->GetMotd(DEFAULT_LOCALE)) + "\r\n").c_str());
// Read commands
for (;;)
{
Send("AC>");
std::string command = ReadString();
if (ProcessCommand(command))
break;
}
_socket.close();
}
int RASession::Send(std::string_view data)
{
std::ostream os(&_writeBuffer);
os << data;
std::size_t written = _socket.send(_writeBuffer.data());
_writeBuffer.consume(written);
return written;
}
std::string RASession::ReadString()
{
boost::system::error_code error;
std::size_t read = boost::asio::read_until(_socket, _readBuffer, "\r\n", error);
if (!read)
{
_socket.close();
return "";
}
std::string line;
std::istream is(&_readBuffer);
std::getline(is, line);
if (*line.rbegin() == '\r')
line.erase(line.length() - 1);
return line;
}
bool RASession::CheckAccessLevel(const std::string& user)
{
std::string safeUser = user;
Utf8ToUpperOnlyLatin(safeUser);
auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ACCESS);
stmt->SetData(0, safeUser);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (!result)
{
LOG_INFO("commands.ra", "User {} does not exist in database", user);
return false;
}
Field* fields = result->Fetch();
if (fields[1].Get<uint8>() < sConfigMgr->GetOption<int32>("Ra.MinLevel", 3))
{
LOG_INFO("commands.ra", "User {} has no privilege to login", user);
return false;
}
else if (fields[2].Get<int32>() != -1)
{
LOG_INFO("commands.ra", "User {} has to be assigned on all realms (with RealmID = '-1')", user);
return false;
}
return true;
}
bool RASession::CheckPassword(const std::string& user, const std::string& pass)
{
std::string safe_user = user;
std::transform(safe_user.begin(), safe_user.end(), safe_user.begin(), ::toupper);
Utf8ToUpperOnlyLatin(safe_user);
std::string safe_pass = pass;
Utf8ToUpperOnlyLatin(safe_pass);
std::transform(safe_pass.begin(), safe_pass.end(), safe_pass.begin(), ::toupper);
auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME);
stmt->SetData(0, safe_user);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
{
Acore::Crypto::SRP6::Salt salt = (*result)[0].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>();
Acore::Crypto::SRP6::Verifier verifier = (*result)[1].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>();
if (Acore::Crypto::SRP6::CheckLogin(safe_user, safe_pass, salt, verifier))
return true;
}
LOG_INFO("commands.ra", "Wrong password for user: {}", user);
return false;
}
bool RASession::ProcessCommand(std::string& command)
{
if (command.length() == 0)
return true;
LOG_INFO("commands.ra", "Received command: {}", command);
// handle quit, exit and logout commands to terminate connection
if (command == "quit" || command == "exit" || command == "logout")
{
Send("Bye\r\n");
return true;
}
// Obtain a new promise per command
delete _commandExecuting;
_commandExecuting = new std::promise<void>();
CliCommandHolder* cmd = new CliCommandHolder(this, command.c_str(), &RASession::CommandPrint, &RASession::CommandFinished);
sWorld->QueueCliCommand(cmd);
// Wait for the command to finish
_commandExecuting->get_future().wait();
return false;
}
void RASession::CommandPrint(void* callbackArg, std::string_view text)
{
if (text.empty())
{
return;
}
RASession* session = static_cast<RASession*>(callbackArg);
session->Send(text);
}
void RASession::CommandFinished(void* callbackArg, bool /*success*/)
{
RASession* session = static_cast<RASession*>(callbackArg);
session->_commandExecuting->set_value();
}
@@ -0,0 +1,54 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __RASESSION_H__
#define __RASESSION_H__
#include "Socket.h"
#include <boost/asio/streambuf.hpp>
#include <future>
const std::size_t bufferSize = 4096;
class RASession : public std::enable_shared_from_this<RASession>
{
public:
RASession(IoContextTcpSocket&& socket) :
_socket(std::move(socket)), _commandExecuting(nullptr) { }
void Start();
const std::string GetRemoteIpAddress() const { return _socket.remote_endpoint().address().to_string(); }
unsigned short GetRemotePort() const { return _socket.remote_endpoint().port(); }
private:
int Send(std::string_view data);
std::string ReadString();
bool CheckAccessLevel(const std::string& user);
bool CheckPassword(const std::string& user, const std::string& pass);
bool ProcessCommand(std::string& command);
static void CommandPrint(void* callbackArg, std::string_view text);
static void CommandFinished(void* callbackArg, bool);
IoContextTcpSocket _socket;
boost::asio::streambuf _readBuffer;
boost::asio::streambuf _writeBuffer;
std::promise<void>* _commandExecuting;
};
#endif
+15
View File
@@ -0,0 +1,15 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by AzerothCore.rc
//
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

@@ -0,0 +1,90 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "resource.h"
#include "revision.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "windows.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APPICON ICON "worldserver.ico"
/////////////////////////////////////////////////////////////////////////////
// Neutre (Par défaut système) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD)
#ifdef _WIN32
LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT
#pragma code_page(1252)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#if AC_BUILD_HAS_DEBUG_INFO == 1
#define AC_DEBUG VS_FF_DEBUG
#else
#define AC_DEBUG 0
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION AC_FILEVERSION
PRODUCTVERSION AC_PRODUCTVERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS (VS_FF_PRERELEASE | AC_DEBUG)
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080004b0"
BEGIN
VALUE "CompanyName", AC_COMPANYNAME_STR
VALUE "FileDescription", "World Server Daemon"
VALUE "FileVersion", AC_FILEVERSION_STR
VALUE "InternalName", "worldserver"
VALUE "LegalCopyright", AC_LEGALCOPYRIGHT_STR
VALUE "OriginalFilename", "worldserver.exe"
VALUE "ProductName", "World Server"
VALUE "ProductVersion", AC_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x800, 1200
END
END
#endif
+65
View File
@@ -0,0 +1,65 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
CollectSourceFiles(
${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE_SOURCES
# Exclude
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
if(USE_COREPCH)
set(PRIVATE_PCH_HEADER PrecompiledHeaders/databasePCH.h)
endif()
# Group sources
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
add_library(database
${PRIVATE_SOURCES})
# Do NOT add any extra include directory unless it does not create unneeded extra dependencies,
# and specially, not add any dependency to neither of these: shared, game, scripts
# This way we ensure that if either a PR does that without modifying this file,
# a compile error will be generated, either this file will be modified so it
# is detected more easily.
# While it is OK to include files from other libs as long as they don't require
# linkage (enums, defines...) it is discouraged to do so unless necessary, as it will pullute
# include_directories leading to further unnoticed dependency aditions
# Linker Depencency requirements: common
CollectIncludeDirectories(
${CMAKE_CURRENT_SOURCE_DIR}
PUBLIC_INCLUDES
# Exclude
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
target_include_directories(database
PUBLIC
${PUBLIC_INCLUDES}
PRIVATE
${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(database
PRIVATE
acore-core-interface
mysql
PUBLIC
common)
set_target_properties(database
PROPERTIES
FOLDER
"server")
# Generate precompiled header
if(USE_COREPCH)
add_cxx_pch(database ${PRIVATE_PCH_HEADER})
endif()
@@ -0,0 +1,56 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "AdhocStatement.h"
#include "MySQLConnection.h"
#include "QueryResult.h"
/*! Basic, ad-hoc queries. */
BasicStatementTask::BasicStatementTask(std::string_view sql, bool async) : m_result(nullptr)
{
m_sql = std::string(sql);
m_has_result = async; // If the operation is async, then there's a result
if (async)
m_result = new QueryResultPromise();
}
BasicStatementTask::~BasicStatementTask()
{
m_sql.clear();
if (m_has_result && m_result)
delete m_result;
}
bool BasicStatementTask::Execute()
{
if (m_has_result)
{
ResultSet* result = m_conn->Query(m_sql);
if (!result || !result->GetRowCount() || !result->NextRow())
{
delete result;
m_result->set_value(QueryResult(nullptr));
return false;
}
m_result->set_value(QueryResult(result));
return true;
}
return m_conn->Execute(m_sql);
}
@@ -0,0 +1,41 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _ADHOCSTATEMENT_H
#define _ADHOCSTATEMENT_H
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include "SQLOperation.h"
/*! Raw, ad-hoc query. */
class AC_DATABASE_API BasicStatementTask : public SQLOperation
{
public:
BasicStatementTask(std::string_view sql, bool async = false);
~BasicStatementTask();
bool Execute() override;
QueryResultFuture GetFuture() const { return m_result->get_future(); }
private:
std::string m_sql; //- Raw query to be executed
bool m_has_result;
QueryResultPromise* m_result;
};
#endif
@@ -0,0 +1,22 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "DatabaseEnv.h"
DatabaseWorkerPool<WorldDatabaseConnection> WorldDatabase;
DatabaseWorkerPool<CharacterDatabaseConnection> CharacterDatabase;
DatabaseWorkerPool<LoginDatabaseConnection> LoginDatabase;
@@ -0,0 +1,39 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef DATABASEENV_H
#define DATABASEENV_H
#include "DatabaseWorkerPool.h"
#include "Define.h"
#include "Implementation/CharacterDatabase.h"
#include "Implementation/LoginDatabase.h"
#include "Implementation/WorldDatabase.h"
#include "PreparedStatement.h"
#include "QueryCallback.h"
#include "Transaction.h"
/// Accessor to the world database
AC_DATABASE_API extern DatabaseWorkerPool<WorldDatabaseConnection> WorldDatabase;
/// Accessor to the character database
AC_DATABASE_API extern DatabaseWorkerPool<CharacterDatabaseConnection> CharacterDatabase;
/// Accessor to the realm/login database
AC_DATABASE_API extern DatabaseWorkerPool<LoginDatabaseConnection> LoginDatabase;
#endif
@@ -0,0 +1,93 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef DatabaseEnvFwd_h__
#define DatabaseEnvFwd_h__
#include <future>
struct QueryResultFieldMetadata;
class Field;
class ResultSet;
using QueryResult = std::shared_ptr<ResultSet>;
using QueryResultFuture = std::future<QueryResult>;
using QueryResultPromise = std::promise<QueryResult>;
class CharacterDatabaseConnection;
class LoginDatabaseConnection;
class WorldDatabaseConnection;
class PreparedStatementBase;
template<typename T>
class PreparedStatement;
using CharacterDatabasePreparedStatement = PreparedStatement<CharacterDatabaseConnection>;
using LoginDatabasePreparedStatement = PreparedStatement<LoginDatabaseConnection>;
using WorldDatabasePreparedStatement = PreparedStatement<WorldDatabaseConnection>;
class PreparedResultSet;
using PreparedQueryResult = std::shared_ptr<PreparedResultSet>;
using PreparedQueryResultFuture = std::future<PreparedQueryResult>;
using PreparedQueryResultPromise = std::promise<PreparedQueryResult>;
class QueryCallback;
template<typename T>
class AsyncCallbackProcessor;
using QueryCallbackProcessor = AsyncCallbackProcessor<QueryCallback>;
class TransactionBase;
using TransactionFuture = std::future<bool>;
using TransactionPromise = std::promise<bool>;
template<typename T>
class Transaction;
class TransactionCallback;
template<typename T>
using SQLTransaction = std::shared_ptr<Transaction<T>>;
using CharacterDatabaseTransaction = SQLTransaction<CharacterDatabaseConnection>;
using LoginDatabaseTransaction = SQLTransaction<LoginDatabaseConnection>;
using WorldDatabaseTransaction = SQLTransaction<WorldDatabaseConnection>;
class SQLQueryHolderBase;
using QueryResultHolderFuture = std::future<void>;
using QueryResultHolderPromise = std::promise<void>;
template<typename T>
class SQLQueryHolder;
using CharacterDatabaseQueryHolder = SQLQueryHolder<CharacterDatabaseConnection>;
using LoginDatabaseQueryHolder = SQLQueryHolder<LoginDatabaseConnection>;
using WorldDatabaseQueryHolder = SQLQueryHolder<WorldDatabaseConnection>;
class SQLQueryHolderCallback;
// mysql
struct MySQLHandle;
struct MySQLResult;
struct MySQLField;
struct MySQLBind;
struct MySQLStmt;
#endif // DatabaseEnvFwd_h__
@@ -0,0 +1,240 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "DatabaseLoader.h"
#include "Config.h"
#include "DBUpdater.h"
#include "DatabaseEnv.h"
#include "Duration.h"
#include "Log.h"
#include <errmsg.h>
#include <mysqld_error.h>
#include <thread>
#include <string_view>
namespace
{
std::string const EMPTY_DATABASE_INFO;
std::string const LOGIN_DATABASE_INFO_DEFAULT = "127.0.0.1;3306;acore;acore;acore_auth";
std::string const WORLD_DATABASE_INFO_DEFAULT = "127.0.0.1;3306;acore;acore;acore_world";
std::string const CHARACTER_DATABASE_INFO_DEFAULT = "127.0.0.1;3306;acore;acore;acore_characters";
std::string const& GetDefaultDatabaseInfo(std::string_view name)
{
if (name == "Login")
return LOGIN_DATABASE_INFO_DEFAULT;
if (name == "World")
return WORLD_DATABASE_INFO_DEFAULT;
if (name == "Character")
return CHARACTER_DATABASE_INFO_DEFAULT;
return EMPTY_DATABASE_INFO;
}
}
DatabaseLoader::DatabaseLoader(std::string const& logger, uint32 const defaultUpdateMask, std::string_view modulesList)
: _logger(logger),
_modulesList(modulesList),
_autoSetup(sConfigMgr->GetOption<bool>("Updates.AutoSetup", true)),
_updateFlags(sConfigMgr->GetOption<uint32>("Updates.EnableDatabases", defaultUpdateMask)) { }
template <class T>
DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool<T>& pool, std::string const& name)
{
bool const updatesEnabledForThis = DBUpdater<T>::IsEnabled(_updateFlags);
_open.push([this, name, updatesEnabledForThis, &pool]() -> bool
{
std::string const& defaultDatabaseInfo = GetDefaultDatabaseInfo(name);
std::string const dbString = sConfigMgr->GetOption<std::string>(name + "DatabaseInfo", defaultDatabaseInfo);
if (dbString.empty())
{
LOG_ERROR(_logger, "Database {} not specified in configuration file!", name);
return false;
}
uint8 const asyncThreads = sConfigMgr->GetOption<uint8>(name + "Database.WorkerThreads", 1);
if (asyncThreads < 1 || asyncThreads > 32)
{
LOG_ERROR(_logger, "{} database: invalid number of worker threads specified. "
"Please pick a value between 1 and 32.", name);
return false;
}
uint8 const synchThreads = sConfigMgr->GetOption<uint8>(name + "Database.SynchThreads", 1);
pool.SetConnectionInfo(dbString, asyncThreads, synchThreads);
if (uint32 error = pool.Open())
{
// Try reconnect
if (error == CR_CONNECTION_ERROR)
{
uint8 const attempts = sConfigMgr->GetOption<uint8>("Database.Reconnect.Attempts", 20);
Seconds reconnectSeconds = Seconds(sConfigMgr->GetOption<uint8>("Database.Reconnect.Seconds", 15));
uint8 reconnectCount = 0;
while (reconnectCount < attempts)
{
LOG_WARN(_logger, "> Retrying after {} seconds", static_cast<uint32>(reconnectSeconds.count()));
std::this_thread::sleep_for(reconnectSeconds);
error = pool.Open();
if (error == CR_CONNECTION_ERROR)
{
reconnectCount++;
}
else
{
break;
}
}
}
// Database does not exist
if ((error == ER_BAD_DB_ERROR) && updatesEnabledForThis && _autoSetup)
{
// Try to create the database and connect again if auto setup is enabled
if (DBUpdater<T>::Create(pool) && (!pool.Open()))
{
error = 0;
}
}
// If the error wasn't handled quit
if (error)
{
LOG_ERROR(_logger, "DatabasePool {} NOT opened. There were errors opening the MySQL connections. "
"Check your log file for specific errors", name);
return false;
}
}
// Add the close operation
_close.push([&pool]
{
pool.Close();
});
return true;
});
// Populate and update only if updates are enabled for this pool
if (updatesEnabledForThis)
{
_populate.push([this, name, &pool]() -> bool
{
if (!DBUpdater<T>::Populate(pool))
{
LOG_ERROR(_logger, "Could not populate the {} database, see log for details.", name);
return false;
}
return true;
});
_update.push([this, name, &pool]() -> bool
{
if (!DBUpdater<T>::Update(pool, _modulesList))
{
LOG_ERROR(_logger, "Could not update the {} database, see log for details.", name);
return false;
}
return true;
});
}
_prepare.push([this, name, &pool]() -> bool
{
if (!pool.PrepareStatements())
{
LOG_ERROR(_logger, "Could not prepare statements of the {} database, see log for details.", name);
return false;
}
return true;
});
return *this;
}
bool DatabaseLoader::Load()
{
if (!_updateFlags)
LOG_WARN("sql.updates", "> AUTOUPDATER: Automatic database updates are disabled for all databases in the config! This is not recommended!");
if (!OpenDatabases())
return false;
if (!PopulateDatabases())
return false;
if (!UpdateDatabases())
return false;
if (!PrepareStatements())
return false;
return true;
}
bool DatabaseLoader::OpenDatabases()
{
return Process(_open);
}
bool DatabaseLoader::PopulateDatabases()
{
return Process(_populate);
}
bool DatabaseLoader::UpdateDatabases()
{
return Process(_update);
}
bool DatabaseLoader::PrepareStatements()
{
return Process(_prepare);
}
bool DatabaseLoader::Process(std::queue<Predicate>& queue)
{
while (!queue.empty())
{
if (!queue.front()())
{
// Close all open databases which have a registered close operation
while (!_close.empty())
{
_close.top()();
_close.pop();
}
return false;
}
queue.pop();
}
return true;
}
template AC_DATABASE_API
DatabaseLoader& DatabaseLoader::AddDatabase<LoginDatabaseConnection>(DatabaseWorkerPool<LoginDatabaseConnection>&, std::string const&);
template AC_DATABASE_API
DatabaseLoader& DatabaseLoader::AddDatabase<CharacterDatabaseConnection>(DatabaseWorkerPool<CharacterDatabaseConnection>&, std::string const&);
template AC_DATABASE_API
DatabaseLoader& DatabaseLoader::AddDatabase<WorldDatabaseConnection>(DatabaseWorkerPool<WorldDatabaseConnection>&, std::string const&);
@@ -0,0 +1,82 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef DatabaseLoader_h__
#define DatabaseLoader_h__
#include "Define.h"
#include <functional>
#include <queue>
#include <stack>
#include <string>
template <class T>
class DatabaseWorkerPool;
// A helper class to initiate all database worker pools,
// handles updating, delays preparing of statements and cleans up on failure.
class AC_DATABASE_API DatabaseLoader
{
public:
DatabaseLoader(std::string const& logger, uint32 const defaultUpdateMask = 7, std::string_view modulesList = {});
// Register a database to the loader (lazy implemented)
template <class T>
DatabaseLoader& AddDatabase(DatabaseWorkerPool<T>& pool, std::string const& name);
// Load all databases
bool Load();
enum DatabaseTypeFlags
{
DATABASE_NONE = 0,
DATABASE_LOGIN = 1,
DATABASE_CHARACTER = 2,
DATABASE_WORLD = 4,
DATABASE_MASK_ALL = DATABASE_LOGIN | DATABASE_CHARACTER | DATABASE_WORLD
};
[[nodiscard]] uint32 GetUpdateFlags() const
{
return _updateFlags;
}
private:
bool OpenDatabases();
bool PopulateDatabases();
bool UpdateDatabases();
bool PrepareStatements();
using Predicate = std::function<bool()>;
using Closer = std::function<void()>;
// Invokes all functions in the given queue and closes the databases on errors.
// Returns false when there was an error.
bool Process(std::queue<Predicate>& queue);
std::string const _logger;
std::string_view _modulesList;
bool const _autoSetup;
uint32 const _updateFlags;
std::queue<Predicate> _open, _populate, _update, _prepare;
std::stack<Closer> _close;
};
#endif // DatabaseLoader_h__
@@ -0,0 +1,53 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "DatabaseWorker.h"
#include "PCQueue.h"
#include "SQLOperation.h"
DatabaseWorker::DatabaseWorker(ProducerConsumerQueue<SQLOperation*>* newQueue, MySQLConnection* connection)
{
_connection = connection;
_queue = newQueue;
_workerThread = std::thread(&DatabaseWorker::WorkerThread, this);
}
DatabaseWorker::~DatabaseWorker()
{
_workerThread.join();
}
void DatabaseWorker::WorkerThread()
{
if (!_queue)
return;
for (;;)
{
SQLOperation* operation = nullptr;
_queue->WaitAndPop(operation);
if (!operation)
return;
operation->SetConnection(_connection);
operation->call();
delete operation;
}
}
@@ -0,0 +1,48 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _WORKERTHREAD_H
#define _WORKERTHREAD_H
#include "Define.h"
#include <atomic>
#include <thread>
template <typename T>
class ProducerConsumerQueue;
class MySQLConnection;
class SQLOperation;
class AC_DATABASE_API DatabaseWorker
{
public:
DatabaseWorker(ProducerConsumerQueue<SQLOperation*>* newQueue, MySQLConnection* connection);
~DatabaseWorker();
private:
ProducerConsumerQueue<SQLOperation*>* _queue;
MySQLConnection* _connection;
void WorkerThread();
std::thread _workerThread;
DatabaseWorker(DatabaseWorker const& right) = delete;
DatabaseWorker& operator=(DatabaseWorker const& right) = delete;
};
#endif
@@ -0,0 +1,573 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "DatabaseWorkerPool.h"
#include "AdhocStatement.h"
#include "CharacterDatabase.h"
#include "Errors.h"
#include "Log.h"
#include "LoginDatabase.h"
#include "MySQLPreparedStatement.h"
#include "MySQLWorkaround.h"
#include "PCQueue.h"
#include "PreparedStatement.h"
#include "QueryCallback.h"
#include "QueryHolder.h"
#include "QueryResult.h"
#include "SQLOperation.h"
#include "Transaction.h"
#include "WorldDatabase.h"
#include <limits>
#include <mysqld_error.h>
#include <sstream>
#include <vector>
#ifdef ACORE_DEBUG
#include <boost/stacktrace.hpp>
#include <sstream>
#endif
class PingOperation : public SQLOperation
{
//! Operation for idle delaythreads
bool Execute() override
{
m_conn->Ping();
return true;
}
};
template <class T>
DatabaseWorkerPool<T>::DatabaseWorkerPool() :
_queue(new ProducerConsumerQueue<SQLOperation*>()),
_async_threads(0),
_synch_threads(0)
{
WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe.");
bool isSupportClientDB = mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION;
bool isSameClientDB = mysql_get_client_version() == MYSQL_VERSION_ID;
WPFatal(isSupportClientDB, "AzerothCore does not support MySQL versions below 8.0\n\nFound version: {} / {}. Server compiled with: {}.\nSearch the wiki for ACE00043 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00043).",
mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID);
WPFatal(isSameClientDB, "Used MySQL library version ({} id {}) does not match the version id used to compile AzerothCore (id {}).\nSearch the wiki for ACE00046 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00046).",
mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID);
}
template <class T>
DatabaseWorkerPool<T>::~DatabaseWorkerPool()
{
_queue->Cancel();
}
template <class T>
void DatabaseWorkerPool<T>::SetConnectionInfo(std::string_view infoString, uint8 const asyncThreads, uint8 const synchThreads)
{
_connectionInfo = std::make_unique<MySQLConnectionInfo>(infoString);
_async_threads = asyncThreads;
_synch_threads = synchThreads;
}
template <class T>
uint32 DatabaseWorkerPool<T>::Open()
{
WPFatal(_connectionInfo.get(), "Connection info was not set!");
LOG_INFO("sql.driver", "Opening DatabasePool '{}'. Asynchronous connections: {}, synchronous connections: {}.",
GetDatabaseName(), _async_threads, _synch_threads);
uint32 error = OpenConnections(IDX_ASYNC, _async_threads);
if (error)
return error;
error = OpenConnections(IDX_SYNCH, _synch_threads);
if (!error)
{
LOG_INFO("sql.driver", "DatabasePool '{}' opened successfully. {} total connections running.",
GetDatabaseName(), (_connections[IDX_SYNCH].size() + _connections[IDX_ASYNC].size()));
}
LOG_INFO("sql.driver", " ");
return error;
}
template <class T>
void DatabaseWorkerPool<T>::Close()
{
LOG_INFO("sql.driver", "Closing down DatabasePool '{}'. Waiting for {} queries to finish...", GetDatabaseName(), _queue->Size());
// Gracefully close async query queue, worker threads will block when the destructor
// is called from the .clear() functions below until the queue is empty
_queue->Shutdown();
//! Closes the actualy MySQL connection.
_connections[IDX_ASYNC].clear();
LOG_INFO("sql.driver", "Asynchronous connections on DatabasePool '{}' terminated. Proceeding with synchronous connections.",
GetDatabaseName());
//! Shut down the synchronous connections
//! There's no need for locking the connection, because DatabaseWorkerPool<>::Close
//! should only be called after any other thread tasks in the core have exited,
//! meaning there can be no concurrent access at this point.
_connections[IDX_SYNCH].clear();
LOG_INFO("sql.driver", "All connections on DatabasePool '{}' closed.", GetDatabaseName());
}
template <class T>
bool DatabaseWorkerPool<T>::PrepareStatements()
{
for (auto const& connections : _connections)
{
for (auto const& connection : connections)
{
connection->LockIfReady();
if (!connection->PrepareStatements())
{
connection->Unlock();
Close();
return false;
}
else
connection->Unlock();
std::size_t const preparedSize = connection->m_stmts.size();
if (_preparedStatementSize.size() < preparedSize)
_preparedStatementSize.resize(preparedSize);
for (std::size_t i = 0; i < preparedSize; ++i)
{
// already set by another connection
// (each connection only has prepared statements of it's own type sync/async)
if (_preparedStatementSize[i] > 0)
continue;
if (MySQLPreparedStatement* stmt = connection->m_stmts[i].get())
{
uint32 const paramCount = stmt->GetParameterCount();
// WH only supports uint8 indices.
ASSERT(paramCount < std::numeric_limits<uint8>::max());
_preparedStatementSize[i] = static_cast<uint8>(paramCount);
}
}
}
}
return true;
}
template <class T>
QueryResult DatabaseWorkerPool<T>::Query(std::string_view sql)
{
auto connection = GetFreeConnection();
ResultSet* result = connection->Query(sql);
connection->Unlock();
if (!result || !result->GetRowCount() || !result->NextRow())
{
delete result;
return QueryResult(nullptr);
}
return QueryResult(result);
}
template <class T>
PreparedQueryResult DatabaseWorkerPool<T>::Query(PreparedStatement<T>* stmt)
{
auto connection = GetFreeConnection();
PreparedResultSet* ret = connection->Query(stmt);
connection->Unlock();
//! Delete proxy-class. Not needed anymore
delete stmt;
if (!ret || !ret->GetRowCount())
{
delete ret;
return PreparedQueryResult(nullptr);
}
return PreparedQueryResult(ret);
}
template <class T>
QueryCallback DatabaseWorkerPool<T>::AsyncQuery(std::string_view sql)
{
BasicStatementTask* task = new BasicStatementTask(sql, true);
// Store future result before enqueueing - task might get already processed and deleted before returning from this method
QueryResultFuture result = task->GetFuture();
Enqueue(task);
return QueryCallback(std::move(result));
}
template <class T>
QueryCallback DatabaseWorkerPool<T>::AsyncQuery(PreparedStatement<T>* stmt)
{
PreparedStatementTask* task = new PreparedStatementTask(stmt, true);
// Store future result before enqueueing - task might get already processed and deleted before returning from this method
PreparedQueryResultFuture result = task->GetFuture();
Enqueue(task);
return QueryCallback(std::move(result));
}
template <class T>
SQLQueryHolderCallback DatabaseWorkerPool<T>::DelayQueryHolder(std::shared_ptr<SQLQueryHolder<T>> holder)
{
SQLQueryHolderTask* task = new SQLQueryHolderTask(holder);
// Store future result before enqueueing - task might get already processed and deleted before returning from this method
QueryResultHolderFuture result = task->GetFuture();
Enqueue(task);
return { std::move(holder), std::move(result) };
}
template <class T>
SQLTransaction<T> DatabaseWorkerPool<T>::BeginTransaction()
{
return std::make_shared<Transaction<T>>();
}
template <class T>
void DatabaseWorkerPool<T>::CommitTransaction(SQLTransaction<T> transaction)
{
#ifdef ACORE_DEBUG
//! Only analyze transaction weaknesses in Debug mode.
//! Ideally we catch the faults in Debug mode and then correct them,
//! so there's no need to waste these CPU cycles in Release mode.
switch (transaction->GetSize())
{
case 0:
LOG_DEBUG("sql.driver", "Transaction contains 0 queries. Not executing.");
return;
case 1:
LOG_DEBUG("sql.driver", "Warning: Transaction only holds 1 query, consider removing Transaction context in code.");
break;
default:
break;
}
#endif // ACORE_DEBUG
Enqueue(new TransactionTask(transaction));
}
template <class T>
TransactionCallback DatabaseWorkerPool<T>::AsyncCommitTransaction(SQLTransaction<T> transaction)
{
#ifdef ACORE_DEBUG
//! Only analyze transaction weaknesses in Debug mode.
//! Ideally we catch the faults in Debug mode and then correct them,
//! so there's no need to waste these CPU cycles in Release mode.
switch (transaction->GetSize())
{
case 0:
LOG_DEBUG("sql.driver", "Transaction contains 0 queries. Not executing.");
break;
case 1:
LOG_DEBUG("sql.driver", "Warning: Transaction only holds 1 query, consider removing Transaction context in code.");
break;
default:
break;
}
#endif // ACORE_DEBUG
TransactionWithResultTask* task = new TransactionWithResultTask(transaction);
TransactionFuture result = task->GetFuture();
Enqueue(task);
return TransactionCallback(std::move(result));
}
template <class T>
void DatabaseWorkerPool<T>::DirectCommitTransaction(SQLTransaction<T>& transaction)
{
T* connection = GetFreeConnection();
int errorCode = connection->ExecuteTransaction(transaction);
if (!errorCode)
{
connection->Unlock(); // OK, operation succesful
return;
}
//! Handle MySQL Errno 1213 without extending deadlock to the core itself
/// @todo More elegant way
if (errorCode == ER_LOCK_DEADLOCK)
{
//todo: handle multiple sync threads deadlocking in a similar way as async threads
uint8 loopBreaker = 5;
for (uint8 i = 0; i < loopBreaker; ++i)
{
if (!connection->ExecuteTransaction(transaction))
break;
}
}
//! Clean up now.
transaction->Cleanup();
connection->Unlock();
}
template <class T>
PreparedStatement<T>* DatabaseWorkerPool<T>::GetPreparedStatement(PreparedStatementIndex index)
{
return new PreparedStatement<T>(index, _preparedStatementSize[index]);
}
template <class T>
void DatabaseWorkerPool<T>::EscapeString(std::string& str)
{
if (str.empty())
return;
char* buf = new char[str.size() * 2 + 1];
EscapeString(buf, str.c_str(), uint32(str.size()));
str = buf;
delete[] buf;
}
template <class T>
void DatabaseWorkerPool<T>::KeepAlive()
{
//! Ping synchronous connections
for (auto& connection : _connections[IDX_SYNCH])
{
if (connection->LockIfReady())
{
connection->Ping();
connection->Unlock();
}
}
//! Assuming all worker threads are free, every worker thread will receive 1 ping operation request
//! If one or more worker threads are busy, the ping operations will not be split evenly, but this doesn't matter
//! as the sole purpose is to prevent connections from idling.
auto const count = _connections[IDX_ASYNC].size();
for (uint8 i = 0; i < count; ++i)
Enqueue(new PingOperation);
}
/**
* @brief Returns true if the version string given is incompatible
*
* Intended to be used with mysql_get_server_info()'s output as the source
*
* DatabaseIncompatibleVersion("8.0.35") => false
* DatabaseIncompatibleVersion("5.6.6") => true
*
* Adapted from stackoverflow response
* https://stackoverflow.com/a/2941508
*
* @param mysqlVersion The output from GetServerInfo()/mysql_get_server_info()
* @return Returns true if the Server version is incompatible
*/
bool DatabaseIncompatibleVersion(std::string const mysqlVersion)
{
// anon func to turn a version string into an array of uint8
// "1.2.3" => [1, 2, 3]
auto parse = [](std::string const& input)
{
std::vector<uint8> result;
std::istringstream parser(input);
result.push_back(parser.get());
for (int i = 1; i < 3; i++)
{
// Skip period
parser.get();
// Append int from parser to output
result.push_back(parser.get());
}
return result;
};
// default to values for MySQL
uint8 offset = 0;
std::string minVersion = MIN_MYSQL_SERVER_VERSION;
auto parsedMySQLVersion = parse(mysqlVersion.substr(offset));
auto parsedMinVersion = parse(minVersion);
return std::lexicographical_compare(parsedMySQLVersion.begin(), parsedMySQLVersion.end(),
parsedMinVersion.begin(), parsedMinVersion.end());
}
template <class T>
uint32 DatabaseWorkerPool<T>::OpenConnections(InternalIndex type, uint8 numConnections)
{
for (uint8 i = 0; i < numConnections; ++i)
{
// Create the connection
auto connection = [&]
{
switch (type)
{
case IDX_ASYNC:
return std::make_unique<T>(_queue.get(), *_connectionInfo);
case IDX_SYNCH:
return std::make_unique<T>(*_connectionInfo);
default:
ABORT();
}
}();
if (uint32 error = connection->Open())
{
// Failed to open a connection or invalid version, abort and cleanup
_queue->Cancel();
_connections[type].clear();
return error;
}
else if (DatabaseIncompatibleVersion(connection->GetServerInfo()))
{
LOG_ERROR("sql.driver", "AzerothCore does not support MySQL versions below 8.0\n\nFound server version: {}. Server compiled with: {}.",
connection->GetServerInfo(), MYSQL_VERSION_ID);
return 1;
}
else
{
_connections[type].push_back(std::move(connection));
}
}
// Everything is fine
return 0;
}
template <class T>
unsigned long DatabaseWorkerPool<T>::EscapeString(char* to, char const* from, unsigned long length)
{
if (!to || !from || !length)
return 0;
return _connections[IDX_SYNCH].front()->EscapeString(to, from, length);
}
template <class T>
void DatabaseWorkerPool<T>::Enqueue(SQLOperation* op)
{
_queue->Push(op);
}
template <class T>
std::size_t DatabaseWorkerPool<T>::QueueSize() const
{
return _queue->Size();
}
template <class T>
T* DatabaseWorkerPool<T>::GetFreeConnection()
{
#ifdef ACORE_DEBUG
if (_warnSyncQueries)
{
std::ostringstream ss;
ss << boost::stacktrace::stacktrace();
LOG_WARN("sql.performances", "Sync query at:\n{}", ss.str());
}
#endif
uint8 i = 0;
auto const num_cons = _connections[IDX_SYNCH].size();
T* connection = nullptr;
//! Block forever until a connection is free
for (;;)
{
connection = _connections[IDX_SYNCH][++i % num_cons].get();
//! Must be matched with t->Unlock() or you will get deadlocks
if (connection->LockIfReady())
break;
}
return connection;
}
template <class T>
std::string_view DatabaseWorkerPool<T>::GetDatabaseName() const
{
return std::string_view{ _connectionInfo->database };
}
template <class T>
void DatabaseWorkerPool<T>::Execute(std::string_view sql)
{
if (sql.empty())
return;
BasicStatementTask* task = new BasicStatementTask(sql);
Enqueue(task);
}
template <class T>
void DatabaseWorkerPool<T>::Execute(PreparedStatement<T>* stmt)
{
PreparedStatementTask* task = new PreparedStatementTask(stmt);
Enqueue(task);
}
template <class T>
void DatabaseWorkerPool<T>::DirectExecute(std::string_view sql)
{
if (sql.empty())
return;
T* connection = GetFreeConnection();
connection->Execute(sql);
connection->Unlock();
}
template <class T>
void DatabaseWorkerPool<T>::DirectExecute(PreparedStatement<T>* stmt)
{
T* connection = GetFreeConnection();
connection->Execute(stmt);
connection->Unlock();
//! Delete proxy-class. Not needed anymore
delete stmt;
}
template <class T>
void DatabaseWorkerPool<T>::ExecuteOrAppend(SQLTransaction<T>& trans, std::string_view sql)
{
if (!trans)
Execute(sql);
else
trans->Append(sql);
}
template <class T>
void DatabaseWorkerPool<T>::ExecuteOrAppend(SQLTransaction<T>& trans, PreparedStatement<T>* stmt)
{
if (!trans)
Execute(stmt);
else
trans->Append(stmt);
}
template class AC_DATABASE_API DatabaseWorkerPool<LoginDatabaseConnection>;
template class AC_DATABASE_API DatabaseWorkerPool<WorldDatabaseConnection>;
template class AC_DATABASE_API DatabaseWorkerPool<CharacterDatabaseConnection>;
@@ -0,0 +1,242 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _DATABASEWORKERPOOL_H
#define _DATABASEWORKERPOOL_H
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include "StringFormat.h"
#include <array>
#include <vector>
/** @file DatabaseWorkerPool.h */
/**
* @def MIN_MYSQL_CLIENT_VERSION
* The minimum MySQL Client Version
*/
#define MIN_MYSQL_CLIENT_VERSION 80000u
/**
* @def MIN_MYSQL_SERVER_VERSION
* The minimum MySQL Server Version
*/
#define MIN_MYSQL_SERVER_VERSION "8.0.0"
template <typename T>
class ProducerConsumerQueue;
class SQLOperation;
struct MySQLConnectionInfo;
template <class T>
class DatabaseWorkerPool
{
private:
enum InternalIndex
{
IDX_ASYNC,
IDX_SYNCH,
IDX_SIZE
};
public:
/* Activity state */
DatabaseWorkerPool();
~DatabaseWorkerPool();
void SetConnectionInfo(std::string_view infoString, uint8 const asyncThreads, uint8 const synchThreads);
uint32 Open();
void Close();
//! Prepares all prepared statements
bool PrepareStatements();
[[nodiscard]] inline MySQLConnectionInfo const* GetConnectionInfo() const
{
return _connectionInfo.get();
}
/**
Delayed one-way statement methods.
*/
//! Enqueues a one-way SQL operation in string format that will be executed asynchronously.
//! This method should only be used for queries that are only executed once, e.g during startup.
void Execute(std::string_view sql);
//! Enqueues a one-way SQL operation in string format -with variable args- that will be executed asynchronously.
//! This method should only be used for queries that are only executed once, e.g during startup.
template<typename... Args>
void Execute(std::string_view sql, Args&&... args)
{
if (sql.empty())
return;
Execute(Acore::StringFormat(sql, std::forward<Args>(args)...));
}
//! Enqueues a one-way SQL operation in prepared statement format that will be executed asynchronously.
//! Statement must be prepared with CONNECTION_ASYNC flag.
void Execute(PreparedStatement<T>* stmt);
/**
Direct synchronous one-way statement methods.
*/
//! Directly executes a one-way SQL operation in string format, that will block the calling thread until finished.
//! This method should only be used for queries that are only executed once, e.g during startup.
void DirectExecute(std::string_view sql);
//! Directly executes a one-way SQL operation in string format -with variable args-, that will block the calling thread until finished.
//! This method should only be used for queries that are only executed once, e.g during startup.
template<typename... Args>
void DirectExecute(std::string_view sql, Args&&... args)
{
if (sql.empty())
return;
DirectExecute(Acore::StringFormat(sql, std::forward<Args>(args)...));
}
//! Directly executes a one-way SQL operation in prepared statement format, that will block the calling thread until finished.
//! Statement must be prepared with the CONNECTION_SYNCH flag.
void DirectExecute(PreparedStatement<T>* stmt);
/**
Synchronous query (with resultset) methods.
*/
//! Directly executes an SQL query in string format that will block the calling thread until finished.
//! Returns reference counted auto pointer, no need for manual memory management in upper level code.
QueryResult Query(std::string_view sql);
//! Directly executes an SQL query in string format -with variable args- that will block the calling thread until finished.
//! Returns reference counted auto pointer, no need for manual memory management in upper level code.
template<typename... Args>
QueryResult Query(std::string_view sql, Args&&... args)
{
if (sql.empty())
return QueryResult(nullptr);
return Query(Acore::StringFormat(sql, std::forward<Args>(args)...));
}
//! Directly executes an SQL query in prepared format that will block the calling thread until finished.
//! Returns reference counted auto pointer, no need for manual memory management in upper level code.
//! Statement must be prepared with CONNECTION_SYNCH flag.
PreparedQueryResult Query(PreparedStatement<T>* stmt);
/**
Asynchronous query (with resultset) methods.
*/
//! Enqueues a query in string format that will set the value of the QueryResultFuture return object as soon as the query is executed.
//! The return value is then processed in ProcessQueryCallback methods.
QueryCallback AsyncQuery(std::string_view sql);
//! Enqueues a query in prepared format that will set the value of the PreparedQueryResultFuture return object as soon as the query is executed.
//! The return value is then processed in ProcessQueryCallback methods.
//! Statement must be prepared with CONNECTION_ASYNC flag.
QueryCallback AsyncQuery(PreparedStatement<T>* stmt);
//! Enqueues a vector of SQL operations (can be both adhoc and prepared) that will set the value of the QueryResultHolderFuture
//! return object as soon as the query is executed.
//! The return value is then processed in ProcessQueryCallback methods.
//! Any prepared statements added to this holder need to be prepared with the CONNECTION_ASYNC flag.
SQLQueryHolderCallback DelayQueryHolder(std::shared_ptr<SQLQueryHolder<T>> holder);
/**
Transaction context methods.
*/
//! Begins an automanaged transaction pointer that will automatically rollback if not commited. (Autocommit=0)
SQLTransaction<T> BeginTransaction();
//! Enqueues a collection of one-way SQL operations (can be both adhoc and prepared). The order in which these operations
//! were appended to the transaction will be respected during execution.
void CommitTransaction(SQLTransaction<T> transaction);
//! Enqueues a collection of one-way SQL operations (can be both adhoc and prepared). The order in which these operations
//! were appended to the transaction will be respected during execution.
TransactionCallback AsyncCommitTransaction(SQLTransaction<T> transaction);
//! Directly executes a collection of one-way SQL operations (can be both adhoc and prepared). The order in which these operations
//! were appended to the transaction will be respected during execution.
void DirectCommitTransaction(SQLTransaction<T>& transaction);
//! Method used to execute ad-hoc statements in a diverse context.
//! Will be wrapped in a transaction if valid object is present, otherwise executed standalone.
void ExecuteOrAppend(SQLTransaction<T>& trans, std::string_view sql);
//! Method used to execute prepared statements in a diverse context.
//! Will be wrapped in a transaction if valid object is present, otherwise executed standalone.
void ExecuteOrAppend(SQLTransaction<T>& trans, PreparedStatement<T>* stmt);
/**
Other
*/
typedef typename T::Statements PreparedStatementIndex;
//! Automanaged (internally) pointer to a prepared statement object for usage in upper level code.
//! Pointer is deleted in this->DirectExecute(PreparedStatement*), this->Query(PreparedStatement*) or PreparedStatementTask::~PreparedStatementTask.
//! This object is not tied to the prepared statement on the MySQL context yet until execution.
PreparedStatement<T>* GetPreparedStatement(PreparedStatementIndex index);
//! Apply escape string'ing for current collation. (utf8)
void EscapeString(std::string& str);
//! Keeps all our MySQL connections alive, prevent the server from disconnecting us.
void KeepAlive();
void WarnAboutSyncQueries([[maybe_unused]] bool warn)
{
#ifdef ACORE_DEBUG
_warnSyncQueries = warn;
#endif
}
[[nodiscard]] std::size_t QueueSize() const;
private:
uint32 OpenConnections(InternalIndex type, uint8 numConnections);
unsigned long EscapeString(char* to, char const* from, unsigned long length);
void Enqueue(SQLOperation* op);
//! Gets a free connection in the synchronous connection pool.
//! Caller MUST call t->Unlock() after touching the MySQL context to prevent deadlocks.
T* GetFreeConnection();
[[nodiscard]] std::string_view GetDatabaseName() const;
//! Queue shared by async worker threads.
std::unique_ptr<ProducerConsumerQueue<SQLOperation*>> _queue;
std::array<std::vector<std::unique_ptr<T>>, IDX_SIZE> _connections;
std::unique_ptr<MySQLConnectionInfo> _connectionInfo;
std::vector<uint8> _preparedStatementSize;
uint8 _async_threads, _synch_threads;
#ifdef ACORE_DEBUG
static inline thread_local bool _warnSyncQueries = false;
#endif
};
#endif // _DATABASEWORKERPOOL_H
+338
View File
@@ -0,0 +1,338 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "Field.h"
#include "Errors.h"
#include "Log.h"
#include "MySQLHacks.h"
#include "StringConvert.h"
Field::Field()
{
data.value = nullptr;
data.length = 0;
data.raw = false;
meta = nullptr;
}
namespace
{
template<typename T>
constexpr T GetDefaultValue()
{
if constexpr (std::is_same_v<T, bool>)
return false;
else if constexpr (std::is_integral_v<T>)
return 0;
else if constexpr (std::is_floating_point_v<T>)
return 1.0f;
else if constexpr (std::is_same_v<T, std::vector<uint8>> || std::is_same_v<std::string_view, T>)
return {};
else
return "";
}
template<typename T>
inline bool IsCorrectFieldType(DatabaseFieldTypes type)
{
// Int8
if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, int8> || std::is_same_v<T, uint8>)
{
if (type == DatabaseFieldTypes::Int8)
return true;
}
// In16
if constexpr (std::is_same_v<T, uint16> || std::is_same_v<T, int16>)
{
if (type == DatabaseFieldTypes::Int16)
return true;
}
// Int32
if constexpr (std::is_same_v<T, uint32> || std::is_same_v<T, int32>)
{
if (type == DatabaseFieldTypes::Int32)
return true;
}
// Int64
if constexpr (std::is_same_v<T, uint64> || std::is_same_v<T, int64>)
{
if (type == DatabaseFieldTypes::Int64)
return true;
}
// float
if constexpr (std::is_same_v<T, float>)
{
if (type == DatabaseFieldTypes::Float)
return true;
}
// dobule
if constexpr (std::is_same_v<T, double>)
{
if (type == DatabaseFieldTypes::Double || type == DatabaseFieldTypes::Decimal)
return true;
}
// Binary
if constexpr (std::is_same_v<T, Binary>)
{
if (type == DatabaseFieldTypes::Binary)
return true;
}
return false;
}
inline Optional<std::string_view> GetCleanAliasName(std::string_view alias)
{
if (alias.empty())
return {};
auto pos = alias.find_first_of('(');
if (pos == std::string_view::npos)
return {};
alias.remove_suffix(alias.length() - pos);
return { alias };
}
template<typename T>
inline bool IsCorrectAlias(DatabaseFieldTypes type, std::string_view alias)
{
if constexpr (std::is_same_v<T, double>)
{
if ((StringEqualI(alias, "sum") || StringEqualI(alias, "avg")) && type == DatabaseFieldTypes::Decimal)
return true;
return false;
}
if constexpr (std::is_same_v<T, uint64>)
{
if (StringEqualI(alias, "count") && type == DatabaseFieldTypes::Int64)
return true;
return false;
}
if ((StringEqualI(alias, "min") || StringEqualI(alias, "max")) && IsCorrectFieldType<T>(type))
{
return true;
}
return false;
}
}
void Field::GetBinarySizeChecked(uint8* buf, std::size_t length) const
{
ASSERT(data.value && (data.length == length), "Expected {}-byte binary blob, got {}data ({} bytes) instead", length, data.value ? "" : "no ", data.length);
memcpy(buf, data.value, length);
}
void Field::SetByteValue(char const* newValue, uint32 length)
{
// This value stores raw bytes that have to be explicitly cast later
data.value = newValue;
data.length = length;
data.raw = true;
}
void Field::SetStructuredValue(char const* newValue, uint32 length)
{
// This value stores somewhat structured data that needs function style casting
data.value = newValue;
data.length = length;
data.raw = false;
}
bool Field::IsType(DatabaseFieldTypes type) const
{
return meta->Type == type;
}
bool Field::IsNumeric() const
{
return (meta->Type == DatabaseFieldTypes::Int8 ||
meta->Type == DatabaseFieldTypes::Int16 ||
meta->Type == DatabaseFieldTypes::Int32 ||
meta->Type == DatabaseFieldTypes::Int64 ||
meta->Type == DatabaseFieldTypes::Float ||
meta->Type == DatabaseFieldTypes::Double);
}
void Field::LogWrongType(std::string_view getter, std::string_view typeName) const
{
LOG_WARN("sql.sql", "Warning: {}<{}> on {} field {}.{} ({}.{}) at index {}.",
getter, typeName, meta->TypeName, meta->TableAlias, meta->Alias, meta->TableName, meta->Name, meta->Index);
}
void Field::SetMetadata(QueryResultFieldMetadata const* fieldMeta)
{
meta = fieldMeta;
}
template<typename T>
T Field::GetData() const
{
static_assert(std::is_arithmetic_v<T>, "Unsurropt type for Field::GetData()");
if (!data.value)
return GetDefaultValue<T>();
#ifdef ACORE_STRICT_DATABASE_TYPE_CHECKS
if (!IsCorrectFieldType<T>(meta->Type))
{
LogWrongType(__FUNCTION__, typeid(T).name());
//return GetDefaultValue<T>();
}
#endif
Optional<T> result = {};
if (data.raw)
result = *reinterpret_cast<T const*>(data.value);
else
result = Acore::StringTo<T>(std::string_view(data.value, data.length));
// Correct double fields... this undefined behavior :/
if constexpr (std::is_same_v<T, double>)
{
if (data.raw && !IsType(DatabaseFieldTypes::Decimal))
result = *reinterpret_cast<double const*>(data.value);
else
result = Acore::StringTo<float>(std::string_view(data.value, data.length));
}
// Check -1 for *_dbc db tables
if constexpr (std::is_same_v<T, uint32>)
{
std::string_view tableName{ meta->TableName };
if (!tableName.empty() && tableName.size() > 4)
{
auto signedResult = Acore::StringTo<int32>(std::string_view(data.value, data.length));
if (signedResult && !result && tableName.substr(tableName.length() - 4) == "_dbc")
{
LOG_DEBUG("sql.sql", "> Found incorrect value '{}' for type '{}' in _dbc table.", data.value, typeid(T).name());
LOG_DEBUG("sql.sql", "> Table name '{}'. Field name '{}'. Try return int32 value", meta->TableName, meta->Name);
return GetData<int32>();
}
}
}
if (auto alias = GetCleanAliasName(meta->Alias))
{
if ((StringEqualI(*alias, "min") || StringEqualI(*alias, "max")) && !IsCorrectAlias<T>(meta->Type, *alias))
{
LogWrongType(__FUNCTION__, typeid(T).name());
}
if ((StringEqualI(*alias, "sum") || StringEqualI(*alias, "avg")) && !IsCorrectAlias<T>(meta->Type, *alias))
{
LogWrongType(__FUNCTION__, typeid(T).name());
LOG_WARN("sql.sql", "> Please use GetData<double>()");
return GetData<double>();
}
if (StringEqualI(*alias, "count") && !IsCorrectAlias<T>(meta->Type, *alias))
{
LogWrongType(__FUNCTION__, typeid(T).name());
LOG_WARN("sql.sql", "> Please use GetData<uint64>()");
return GetData<uint64>();
}
}
if (!result)
{
LOG_FATAL("sql.sql", "> Incorrect value '{}' for type '{}'. Value is raw ? '{}'", data.value, typeid(T).name(), data.raw);
LOG_FATAL("sql.sql", "> Table name '{}'. Field name '{}'", meta->TableName, meta->Name);
//ABORT();
return GetDefaultValue<T>();
}
return *result;
}
template bool Field::GetData() const;
template uint8 Field::GetData() const;
template uint16 Field::GetData() const;
template uint32 Field::GetData() const;
template uint64 Field::GetData() const;
template int8 Field::GetData() const;
template int16 Field::GetData() const;
template int32 Field::GetData() const;
template int64 Field::GetData() const;
template float Field::GetData() const;
template double Field::GetData() const;
std::string Field::GetDataString() const
{
if (!data.value)
return "";
#ifdef ACORE_STRICT_DATABASE_TYPE_CHECKS
if (IsNumeric() && data.raw)
{
LogWrongType(__FUNCTION__, "std::string");
return "";
}
#endif
return { data.value, data.length };
}
std::string_view Field::GetDataStringView() const
{
if (!data.value)
return {};
#ifdef ACORE_STRICT_DATABASE_TYPE_CHECKS
if (IsNumeric() && data.raw)
{
LogWrongType(__FUNCTION__, "std::string_view");
return {};
}
#endif
return { data.value, data.length };
}
Binary Field::GetDataBinary() const
{
Binary result = {};
if (!data.value || !data.length)
return result;
#ifdef ACORE_STRICT_DATABASE_TYPE_CHECKS
if (!IsCorrectFieldType<Binary>(meta->Type))
{
LogWrongType(__FUNCTION__, "Binary");
return {};
}
#endif
result.resize(data.length);
memcpy(result.data(), data.value, data.length);
return result;
}
+178
View File
@@ -0,0 +1,178 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _FIELD_H
#define _FIELD_H
#include "Define.h"
#include "Duration.h"
#include <array>
#include <string_view>
#include <vector>
namespace Acore::Types
{
template <typename T>
using is_chrono_v = std::enable_if_t<std::is_same_v<Milliseconds, T>
|| std::is_same_v<Seconds, T>
|| std::is_same_v<Minutes, T>
|| std::is_same_v<Hours, T>
|| std::is_same_v<Days, T>
|| std::is_same_v<Weeks, T>
|| std::is_same_v<Years, T>
|| std::is_same_v<Months, T>, T>;
}
using Binary = std::vector<uint8>;
enum class DatabaseFieldTypes : uint8
{
Null,
Int8,
Int16,
Int32,
Int64,
Float,
Double,
Decimal,
Date,
Binary
};
struct QueryResultFieldMetadata
{
std::string TableName{};
std::string TableAlias{};
std::string Name{};
std::string Alias{};
std::string TypeName{};
uint32 Index = 0;
DatabaseFieldTypes Type = DatabaseFieldTypes::Null;
};
/**
@class Field
@brief Class used to access individual fields of database query result
Guideline on field type matching:
| MySQL type | method to use |
|------------------------|-----------------------------------------|
| TINYINT | Get<bool>, Get<int8>, Get<uint8> |
| SMALLINT | Get<int16>, Get<uint16> |
| MEDIUMINT, INT | Get<int32>, Get<uint32> |
| BIGINT | Get<int64>, Get<uint64> |
| FLOAT | Get<float> |
| DOUBLE, DECIMAL | Get<double> |
| CHAR, VARCHAR, | Get<std::string>, Get<std::string_view> |
| TINYTEXT, MEDIUMTEXT, | Get<std::string>, Get<std::string_view> |
| TEXT, LONGTEXT | Get<std::string>, Get<std::string_view> |
| TINYBLOB, MEDIUMBLOB, | Get<Binary>, Get<std::string> |
| BLOB, LONGBLOB | Get<Binary>, Get<std::string> |
| BINARY, VARBINARY | Get<Binary> |
Return types of aggregate functions:
| Function | Type |
|----------|-------------------|
| MIN, MAX | Same as the field |
| SUM, AVG | DECIMAL |
| COUNT | BIGINT |
*/
class AC_DATABASE_API Field
{
friend class ResultSet;
friend class PreparedResultSet;
public:
Field();
~Field() = default;
[[nodiscard]] inline bool IsNull() const
{
return data.value == nullptr;
}
template<typename T>
inline std::enable_if_t<std::is_arithmetic_v<T>, T> Get() const
{
return GetData<T>();
}
template<typename T>
inline std::enable_if_t<std::is_same_v<std::string, T>, T> Get() const
{
return GetDataString();
}
template<typename T>
inline std::enable_if_t<std::is_same_v<std::string_view, T>, T> Get() const
{
return GetDataStringView();
}
template<typename T>
inline std::enable_if_t<std::is_same_v<Binary, T>, T> Get() const
{
return GetDataBinary();
}
template <typename T, std::size_t S>
inline std::enable_if_t<std::is_same_v<Binary, T>, std::array<uint8, S>> Get() const
{
std::array<uint8, S> buf = {};
GetBinarySizeChecked(buf.data(), S);
return buf;
}
template<typename T>
inline Acore::Types::is_chrono_v<T> Get(bool convertToUin32 = true) const
{
return convertToUin32 ? T(GetData<uint32>()) : T(GetData<uint64>());
}
DatabaseFieldTypes GetType() { return meta->Type; }
protected:
struct
{
char const* value; // Actual data in memory
uint32 length; // Length
bool raw; // Raw bytes? (Prepared statement or ad hoc)
} data;
void SetByteValue(char const* newValue, uint32 length);
void SetStructuredValue(char const* newValue, uint32 length);
[[nodiscard]] bool IsType(DatabaseFieldTypes type) const;
[[nodiscard]] bool IsNumeric() const;
private:
template<typename T>
T GetData() const;
std::string GetDataString() const;
std::string_view GetDataStringView() const;
Binary GetDataBinary() const;
QueryResultFieldMetadata const* meta;
void LogWrongType(std::string_view getter, std::string_view typeName) const;
void SetMetadata(QueryResultFieldMetadata const* fieldMeta);
void GetBinarySizeChecked(uint8* buf, std::size_t size) const;
};
#endif
@@ -0,0 +1,638 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "CharacterDatabase.h"
#include "MySQLPreparedStatement.h"
void CharacterDatabaseConnection::DoPrepareStatements()
{
if (!m_reconnecting)
m_stmts.resize(MAX_CHARACTERDATABASE_STATEMENTS);
PrepareStatement(CHAR_DEL_QUEST_POOL_SAVE, "DELETE FROM pool_quest_save WHERE pool_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_QUEST_POOL_SAVE, "INSERT INTO pool_quest_save (pool_id, quest_id) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_NONEXISTENT_GUILD_BANK_ITEM, "DELETE FROM guild_bank_item WHERE guildid = ? AND TabId = ? AND SlotId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_EXPIRED_BANS, "UPDATE character_banned SET active = 0 WHERE unbandate <= UNIX_TIMESTAMP() AND unbandate <> bandate", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_DATA_BY_NAME, "SELECT guid, account, name, gender, race, class, level FROM characters WHERE deleteDate IS NULL AND name = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_SEL_DATA_BY_GUID, "SELECT guid, account, name, gender, race, class, level FROM characters WHERE deleteDate IS NULL AND guid = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_SEL_CHECK_NAME, "SELECT 1 FROM characters WHERE name = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_SEL_CHECK_GUID, "SELECT 1 FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_SUM_CHARS, "SELECT COUNT(guid) FROM characters WHERE account = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_SEL_CHAR_CREATE_INFO, "SELECT level, race, class FROM characters WHERE account = ? LIMIT 0, ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHARACTER_BAN, "INSERT INTO character_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?, 1)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHARACTER_BAN, "UPDATE character_banned SET active = 0 WHERE guid = ? AND active != 0", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHARACTER_BAN, "DELETE cb FROM character_banned cb INNER JOIN characters c ON c.guid = cb.guid WHERE c.account = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_BANINFO, "SELECT FROM_UNIXTIME(bandate, '%Y-%m-%d %H:%i:%s'), unbandate-bandate, active, unbandate, banreason, bannedby FROM character_banned WHERE guid = ? ORDER BY bandate ASC", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_GUID_BY_NAME_FILTER, "SELECT guid, name FROM characters WHERE name LIKE CONCAT('%%', ?, '%%')", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_BANINFO_LIST, "SELECT bandate, unbandate, bannedby, banreason FROM character_banned WHERE guid = ? ORDER BY unbandate", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_BANNED_NAME, "SELECT characters.name FROM characters, character_banned WHERE character_banned.guid = ? AND character_banned.guid = characters.guid", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, "
"gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.extra_flags "
"FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL ORDER BY COALESCE(c.order, c.guid)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, "
"c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, "
"cb.guid, c.extra_flags, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? "
"LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL ORDER BY COALESCE(c.order, c.guid)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_FREE_NAME, "SELECT guid, name, at_login FROM characters WHERE guid = ? AND account = ? AND NOT EXISTS (SELECT NULL FROM characters WHERE name = ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_ZONE, "SELECT zone FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHARACTER_NAME_DATA, "SELECT race, class, gender, level FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_POSITION_XYZ, "SELECT map, position_x, position_y, position_z FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_POSITION, "SELECT position_x, position_y, position_z, orientation, map, taxi_path FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_QUEST_STATUS_DAILY, "DELETE FROM character_queststatus_daily", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_QUEST_STATUS_WEEKLY, "DELETE FROM character_queststatus_weekly", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_QUEST_STATUS_MONTHLY, "DELETE FROM character_queststatus_monthly", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_QUEST_STATUS_SEASONAL, "DELETE FROM character_queststatus_seasonal WHERE event = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_QUEST_STATUS_DAILY_CHAR, "DELETE FROM character_queststatus_daily WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_QUEST_STATUS_WEEKLY_CHAR, "DELETE FROM character_queststatus_weekly WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_QUEST_STATUS_MONTHLY_CHAR, "DELETE FROM character_queststatus_monthly WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_QUEST_STATUS_SEASONAL_CHAR, "DELETE FROM character_queststatus_seasonal WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_BATTLEGROUND_RANDOM, "DELETE FROM character_battleground_random", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_BATTLEGROUND_RANDOM, "INSERT INTO character_battleground_random (guid) VALUES (?)", CONNECTION_ASYNC);
// Start LoginQueryHolder content
PrepareStatement(CHAR_SEL_CHARACTER, "SELECT guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, bankSlots, restState, playerFlags, "
"position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, "
"resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, instance_mode_mask, "
"arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk, "
"health, power1, power2, power3, power4, power5, power6, power7, instance_id, talentGroupsCount, activeTalentGroup, exploredZones, equipmentCache, ammoId, "
"knownTitles, actionBars, grantableLevels, innTriggerId, extraBonusTalentCount, UNIX_TIMESTAMP(creation_date) FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_AURAS, "SELECT casterGuid, itemGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, "
"base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges FROM character_aura WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_SPELL, "SELECT spell, specMask FROM character_spell WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS, "SELECT quest, status, explored, timer, mobcount1, mobcount2, mobcount3, mobcount4, "
"itemcount1, itemcount2, itemcount3, itemcount4, itemcount5, itemcount6, playercount FROM character_queststatus WHERE guid = ? AND status <> 0", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_DAILYQUESTSTATUS, "SELECT quest, time FROM character_queststatus_daily WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_WEEKLYQUESTSTATUS, "SELECT quest FROM character_queststatus_weekly WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_MONTHLYQUESTSTATUS, "SELECT quest FROM character_queststatus_monthly WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_SEASONALQUESTSTATUS, "SELECT quest, event FROM character_queststatus_seasonal WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHARACTER_DAILYQUESTSTATUS, "INSERT INTO character_queststatus_daily (guid, quest, time) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHARACTER_WEEKLYQUESTSTATUS, "INSERT INTO character_queststatus_weekly (guid, quest) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHARACTER_MONTHLYQUESTSTATUS, "INSERT INTO character_queststatus_monthly (guid, quest) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHARACTER_SEASONALQUESTSTATUS, "INSERT IGNORE INTO character_queststatus_seasonal (guid, quest, event) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_REPUTATION, "SELECT faction, standing, flags FROM character_reputation WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_INVENTORY, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, bag, slot, "
"item, itemEntry FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid WHERE ci.guid = ? ORDER BY bag, slot", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS, "SELECT a.button, a.action, a.type FROM character_action as a, characters as c WHERE a.guid = c.guid AND a.spec = c.activeTalentGroup AND a.guid = ? ORDER BY button", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD, "SELECT COUNT(id) FROM mail WHERE receiver = ? AND (checked & 1) = 0 AND deliver_time <= ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD_SYNCH, "SELECT COUNT(id) FROM mail WHERE receiver = ? AND (checked & 1) = 0 AND deliver_time <= ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_MAIL_SERVER_CHARACTER, "SELECT mailId from mail_server_character WHERE guid = ? and mailId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_MAIL_SERVER_CHARACTER, "REPLACE INTO mail_server_character (guid, mailId) values (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_SOCIALLIST, "SELECT friend, flags, note FROM character_social JOIN characters ON characters.guid = character_social.friend WHERE character_social.guid = ? AND deleteinfos_name IS NULL LIMIT 255", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ FROM character_homebind WHERE guid = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_SEL_CHARACTER_SPELLCOOLDOWNS, "SELECT spell, category, item, time, needSend FROM character_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_DECLINEDNAMES, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_declinedname WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_ACHIEVEMENTS, "SELECT achievement, date FROM character_achievement WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_CRITERIAPROGRESS, "SELECT criteria, counter, date FROM character_achievement_progress WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_EQUIPMENTSETS, "SELECT setguid, setindex, name, iconname, ignore_mask, item0, item1, item2, item3, item4, item5, item6, item7, item8, "
"item9, item10, item11, item12, item13, item14, item15, item16, item17, item18 FROM character_equipmentsets WHERE guid = ? ORDER BY setindex", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_ENTRY_POINT, "SELECT joinX, joinY, joinZ, joinO, joinMapId, taxiPath0, taxiPath1, mountSpell FROM character_entry_point WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6 FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT spell, specMask FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_SKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUSREW, "SELECT quest FROM character_queststatus_rewarded WHERE guid = ? AND active = 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES, "SELECT instanceId, releaseTime FROM account_instance_times WHERE accountId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_BREW_OF_THE_MONTH, "SELECT lastEventId FROM character_brew_of_the_month WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_BREW_OF_THE_MONTH, "REPLACE INTO character_brew_of_the_month (guid, lastEventId) VALUES (?, ?)", CONNECTION_ASYNC);
// End LoginQueryHolder content
PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_MAILITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, item_guid, itemEntry, ii.owner_guid, m.id FROM mail_items mi INNER JOIN mail m ON mi.mail_id = m.id LEFT JOIN item_instance ii ON mi.item_guid = ii.guid WHERE m.receiver = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_SEL_AUCTION_ITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, itemguid, itemEntry FROM auctionhouse ah JOIN item_instance ii ON ah.itemguid = ii.guid", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_AUCTIONS, "SELECT id, houseid, itemguid, itemEntry, count, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid", CONNECTION_SYNCH);
PrepareStatement(CHAR_INS_AUCTION, "INSERT INTO auctionhouse (id, houseid, itemguid, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_AUCTION, "DELETE FROM auctionhouse WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_AUCTION_BID, "UPDATE auctionhouse SET buyguid = ?, lastbid = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_MAIL, "INSERT INTO mail(id, messageType, stationery, mailTemplateId, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_MAIL_BY_ID, "DELETE FROM mail WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_MAIL_ITEM, "INSERT INTO mail_items(mail_id, item_guid, receiver) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_MAIL_ITEM, "DELETE FROM mail_items WHERE item_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INVALID_MAIL_ITEM, "DELETE FROM mail_items WHERE item_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_EXPIRED_MAIL, "SELECT id, messageType, sender, receiver, has_items, expire_time, stationery, checked, mailTemplateId FROM mail WHERE expire_time < ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_EXPIRED_MAIL_ITEMS, "SELECT item_guid, itemEntry, mail_id FROM mail_items mi INNER JOIN item_instance ii ON ii.guid = mi.item_guid LEFT JOIN mail mm ON mi.mail_id = mm.id WHERE mm.id IS NOT NULL AND mm.expire_time < ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_UPD_MAIL_RETURNED, "UPDATE mail SET sender = ?, receiver = ?, expire_time = ?, deliver_time = ?, cod = 0, checked = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_MAIL_ITEM_RECEIVER, "UPDATE mail_items SET receiver = ? WHERE item_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ITEM_OWNER, "UPDATE item_instance SET owner_guid = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_ITEM_REFUNDS, "SELECT player_guid, paidMoney, paidExtendedCost FROM item_refund_instance WHERE item_guid = ? AND player_guid = ? LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_ITEM_BOP_TRADE, "SELECT allowedPlayers FROM item_soulbound_trade_data WHERE itemGuid = ? LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_ITEM_BOP_TRADE, "DELETE FROM item_soulbound_trade_data WHERE itemGuid = ? LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ITEM_BOP_TRADE, "INSERT INTO item_soulbound_trade_data VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_INVENTORY_ITEM, "REPLACE INTO character_inventory (guid, bag, slot, item) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_ITEM_INSTANCE, "REPLACE INTO item_instance (itemEntry, owner_guid, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ITEM_INSTANCE, "UPDATE item_instance SET itemEntry = ?, owner_guid = ?, creatorGuid = ?, giftCreatorGuid = ?, count = ?, duration = ?, charges = ?, flags = ?, enchantments = ?, randomPropertyId = ?, durability = ?, playedTime = ?, text = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ITEM_INSTANCE_ON_LOAD, "UPDATE item_instance SET duration = ?, flags = ?, durability = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ITEM_INSTANCE, "DELETE FROM item_instance WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER, "DELETE FROM item_instance WHERE owner_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GIFT_OWNER, "UPDATE character_gifts SET guid = ? WHERE item_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GIFT, "DELETE FROM character_gifts WHERE item_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_GIFT_BY_ITEM, "SELECT entry, flags FROM character_gifts WHERE item_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_ACCOUNT_BY_NAME, "SELECT account FROM characters WHERE name = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_ACCOUNT_INSTANCE_LOCK_TIMES, "DELETE FROM account_instance_times WHERE accountId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ACCOUNT_INSTANCE_LOCK_TIMES, "INSERT INTO account_instance_times (accountId, instanceId, releaseTime) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_MATCH_MAKER_RATING, "SELECT matchMakerRating, maxMMR FROM character_arena_stats WHERE guid = ? AND slot = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHARACTER_COUNT, "SELECT account, COUNT(guid) FROM characters WHERE account = ? GROUP BY account", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_NAME_BY_GUID, "UPDATE characters SET name = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_DECLINED_NAME, "DELETE FROM character_declinedname WHERE guid = ?", CONNECTION_ASYNC);
// Guild handling
// 0: uint32, 1: string, 2: uint32, 3: string, 4: string, 5: uint64, 6-10: uint32, 11: uint64
PrepareStatement(CHAR_INS_GUILD, "INSERT INTO guild (guildid, name, leaderguid, info, motd, createdate, EmblemStyle, EmblemColor, BorderStyle, BorderColor, BackgroundColor, BankMoney) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD, "DELETE FROM guild WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
// 0: string, 1: uint32
PrepareStatement(CHAR_UPD_GUILD_NAME, "UPDATE guild SET name = ? WHERE guildid = ?", CONNECTION_ASYNC);
// 0: uint32, 1: uint32, 2: uint8, 4: string, 5: string
PrepareStatement(CHAR_INS_GUILD_MEMBER, "INSERT INTO guild_member (guildid, guid, `rank`, pnote, offnote) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_MEMBER, "DELETE FROM guild_member WHERE guid = ?", CONNECTION_ASYNC); // 0: uint32
PrepareStatement(CHAR_DEL_GUILD_MEMBERS, "DELETE FROM guild_member WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
PrepareStatement(CHAR_SEL_GUILD_MEMBER_EXTENDED, "SELECT g.guildid, g.name, gr.rname, gm.pnote, gm.offnote "
"FROM guild g JOIN guild_member gm ON g.guildid = gm.guildid "
"JOIN guild_rank gr ON g.guildid = gr.guildid AND gm.`rank` = gr.rid WHERE gm.guid = ?", CONNECTION_BOTH);
// 0: uint32, 1: uint8, 3: string, 4: uint32, 5: uint32
PrepareStatement(CHAR_INS_GUILD_RANK, "INSERT INTO guild_rank (guildid, rid, rname, rights, BankMoneyPerDay) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_RANKS, "DELETE FROM guild_rank WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
PrepareStatement(CHAR_DEL_GUILD_LOWEST_RANK, "DELETE FROM guild_rank WHERE guildid = ? AND rid >= ?", CONNECTION_ASYNC); // 0: uint32, 1: uint8
PrepareStatement(CHAR_INS_GUILD_BANK_TAB, "INSERT INTO guild_bank_tab (guildid, TabId) VALUES (?, ?)", CONNECTION_ASYNC); // 0: uint32, 1: uint8
PrepareStatement(CHAR_DEL_GUILD_BANK_TAB, "DELETE FROM guild_bank_tab WHERE guildid = ? AND TabId = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint8
PrepareStatement(CHAR_DEL_GUILD_BANK_TABS, "DELETE FROM guild_bank_tab WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
// 0: uint32, 1: uint8, 2: uint8, 3: uint32, 4: uint32
PrepareStatement(CHAR_INS_GUILD_BANK_ITEM, "INSERT INTO guild_bank_item (guildid, TabId, SlotId, item_guid) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_BANK_ITEM, "DELETE FROM guild_bank_item WHERE guildid = ? AND TabId = ? AND SlotId = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint8, 2: uint8
PrepareStatement(CHAR_DEL_GUILD_BANK_ITEMS, "DELETE FROM guild_bank_item WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
// 0: uint32, 1: uint8, 2: uint8, 3: uint8, 4: uint32
PrepareStatement(CHAR_INS_GUILD_BANK_RIGHT, "INSERT INTO guild_bank_right (guildid, TabId, rid, gbright, SlotPerDay) VALUES (?, ?, ?, ?, ?) "
"ON DUPLICATE KEY UPDATE gbright = VALUES(gbright), SlotPerDay = VALUES(SlotPerDay)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_BANK_RIGHTS, "DELETE FROM guild_bank_right WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
PrepareStatement(CHAR_DEL_GUILD_BANK_RIGHTS_FOR_RANK, "DELETE FROM guild_bank_right WHERE guildid = ? AND rid = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint8
// 0-1: uint32, 2-3: uint8, 4-5: uint32, 6: uint16, 7: uint8, 8: uint64
PrepareStatement(CHAR_INS_GUILD_BANK_EVENTLOG, "INSERT INTO guild_bank_eventlog (guildid, LogGuid, TabId, EventType, PlayerGuid, ItemOrMoney, ItemStackCount, DestTabId, TimeStamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_BANK_EVENTLOG, "DELETE FROM guild_bank_eventlog WHERE guildid = ? AND LogGuid = ? AND TabId = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint32, 2: uint8
PrepareStatement(CHAR_DEL_GUILD_BANK_EVENTLOGS, "DELETE FROM guild_bank_eventlog WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
// 0-1: uint32, 2: uint8, 3-4: uint32, 5: uint8, 6: uint64
PrepareStatement(CHAR_INS_GUILD_EVENTLOG, "INSERT INTO guild_eventlog (guildid, LogGuid, EventType, PlayerGuid1, PlayerGuid2, NewRank, TimeStamp) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_EVENTLOG, "DELETE FROM guild_eventlog WHERE guildid = ? AND LogGuid = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint32
PrepareStatement(CHAR_DEL_GUILD_EVENTLOGS, "DELETE FROM guild_eventlog WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32
PrepareStatement(CHAR_UPD_GUILD_MEMBER_PNOTE, "UPDATE guild_member SET pnote = ? WHERE guid = ?", CONNECTION_ASYNC); // 0: string, 1: uint32
PrepareStatement(CHAR_UPD_GUILD_MEMBER_OFFNOTE, "UPDATE guild_member SET offnote = ? WHERE guid = ?", CONNECTION_ASYNC); // 0: string, 1: uint32
PrepareStatement(CHAR_UPD_GUILD_MEMBER_RANK, "UPDATE guild_member SET `rank` = ? WHERE guid = ?", CONNECTION_ASYNC); // 0: uint8, 1: uint32
PrepareStatement(CHAR_UPD_GUILD_MOTD, "UPDATE guild SET motd = ? WHERE guildid = ?", CONNECTION_ASYNC); // 0: string, 1: uint32
PrepareStatement(CHAR_UPD_GUILD_INFO, "UPDATE guild SET info = ? WHERE guildid = ?", CONNECTION_ASYNC); // 0: string, 1: uint32
PrepareStatement(CHAR_UPD_GUILD_LEADER, "UPDATE guild SET leaderguid = ? WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint32
PrepareStatement(CHAR_UPD_GUILD_RANK_NAME, "UPDATE guild_rank SET rname = ? WHERE rid = ? AND guildid = ?", CONNECTION_ASYNC); // 0: string, 1: uint8, 2: uint32
PrepareStatement(CHAR_UPD_GUILD_RANK_RIGHTS, "UPDATE guild_rank SET rights = ? WHERE rid = ? AND guildid = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint8, 2: uint32
// 0-5: uint32
PrepareStatement(CHAR_UPD_GUILD_EMBLEM_INFO, "UPDATE guild SET EmblemStyle = ?, EmblemColor = ?, BorderStyle = ?, BorderColor = ?, BackgroundColor = ? WHERE guildid = ?", CONNECTION_ASYNC);
// 0: string, 1: string, 2: uint32, 3: uint8
PrepareStatement(CHAR_UPD_GUILD_BANK_TAB_INFO, "UPDATE guild_bank_tab SET TabName = ?, TabIcon = ? WHERE guildid = ? AND TabId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GUILD_BANK_MONEY, "UPDATE guild SET BankMoney = ? WHERE guildid = ?", CONNECTION_ASYNC); // 0: uint64, 1: uint32
// 0: uint8, 1: uint32, 2: uint8, 3: uint32
PrepareStatement(CHAR_UPD_GUILD_BANK_EVENTLOG_TAB, "UPDATE guild_bank_eventlog SET TabId = ? WHERE guildid = ? AND TabId = ? AND LogGuid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GUILD_RANK_BANK_MONEY, "UPDATE guild_rank SET BankMoneyPerDay = ? WHERE rid = ? AND guildid = ?", CONNECTION_ASYNC); // 0: uint32, 1: uint8, 2: uint32
PrepareStatement(CHAR_UPD_GUILD_BANK_TAB_TEXT, "UPDATE guild_bank_tab SET TabText = ? WHERE guildid = ? AND TabId = ?", CONNECTION_ASYNC); // 0: string, 1: uint32, 2: uint8
PrepareStatement(CHAR_INS_GUILD_MEMBER_WITHDRAW,
"INSERT INTO guild_member_withdraw (guid, tab0, tab1, tab2, tab3, tab4, tab5, money) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "
"ON DUPLICATE KEY UPDATE tab0 = VALUES (tab0), tab1 = VALUES (tab1), tab2 = VALUES (tab2), tab3 = VALUES (tab3), tab4 = VALUES (tab4), tab5 = VALUES (tab5)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_MEMBER_WITHDRAW, "TRUNCATE guild_member_withdraw", CONNECTION_ASYNC);
// 0: uint32, 1: uint32, 2: uint32
PrepareStatement(CHAR_SEL_CHAR_DATA_FOR_GUILD, "SELECT name, level, class, gender, zone, account FROM characters WHERE guid = ?", CONNECTION_SYNCH);
// Chat channel handling
PrepareStatement(CHAR_INS_CHANNEL, "INSERT INTO channels(channelId, name, team, announce, lastUsed) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP())", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHANNEL, "UPDATE channels SET announce = ?, password = ?, lastUsed = UNIX_TIMESTAMP() WHERE channelId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHANNEL, "DELETE FROM channels WHERE name = ? AND team = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHANNEL_USAGE, "UPDATE channels SET lastUsed = UNIX_TIMESTAMP() WHERE channelId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_OLD_CHANNELS, "DELETE FROM channels WHERE lastUsed + ? < UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_OLD_CHANNELS_BANS, "DELETE cb.* FROM channels_bans cb LEFT JOIN channels cn ON cb.channelId=cn.channelId WHERE cn.channelId IS NULL OR cb.banTime <= UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHANNEL_BAN, "REPLACE INTO channels_bans VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHANNEL_BAN, "DELETE FROM channels_bans WHERE channelId = ? AND playerGUID = ?", CONNECTION_ASYNC);
// Equipmentsets
PrepareStatement(CHAR_UPD_EQUIP_SET, "UPDATE character_equipmentsets SET name=?, iconname=?, ignore_mask=?, item0=?, item1=?, item2=?, item3=?, "
"item4=?, item5=?, item6=?, item7=?, item8=?, item9=?, item10=?, item11=?, item12=?, item13=?, item14=?, item15=?, item16=?, "
"item17=?, item18=? WHERE guid=? AND setguid=? AND setindex=?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_EQUIP_SET, "INSERT INTO character_equipmentsets (guid, setguid, setindex, name, iconname, ignore_mask, item0, item1, item2, item3, "
"item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, item16, item17, item18) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_EQUIP_SET, "DELETE FROM character_equipmentsets WHERE setguid=?", CONNECTION_ASYNC);
// Auras
PrepareStatement(CHAR_INS_AURA, "INSERT INTO character_aura (guid, casterGuid, itemGuid, spell, effectMask, recalculateMask, stackcount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
// Account data
PrepareStatement(CHAR_SEL_ACCOUNT_DATA, "SELECT type, time, data FROM account_data WHERE accountId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_ACCOUNT_DATA, "REPLACE INTO account_data (accountId, type, time, data) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ACCOUNT_DATA, "DELETE FROM account_data WHERE accountId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PLAYER_ACCOUNT_DATA, "SELECT type, time, data FROM character_account_data WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_PLAYER_ACCOUNT_DATA, "REPLACE INTO character_account_data(guid, type, time, data) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PLAYER_ACCOUNT_DATA, "DELETE FROM character_account_data WHERE guid = ?", CONNECTION_ASYNC);
// Tutorials
PrepareStatement(CHAR_SEL_TUTORIALS, "SELECT tut0, tut1, tut2, tut3, tut4, tut5, tut6, tut7 FROM account_tutorial WHERE accountId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_HAS_TUTORIALS, "SELECT 1 FROM account_tutorial WHERE accountId = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_INS_TUTORIALS, "INSERT INTO account_tutorial(tut0, tut1, tut2, tut3, tut4, tut5, tut6, tut7, accountId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_TUTORIALS, "UPDATE account_tutorial SET tut0 = ?, tut1 = ?, tut2 = ?, tut3 = ?, tut4 = ?, tut5 = ?, tut6 = ?, tut7 = ? WHERE accountId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_TUTORIALS, "DELETE FROM account_tutorial WHERE accountId = ?", CONNECTION_ASYNC);
// Instance saves
PrepareStatement(CHAR_INS_INSTANCE_SAVE, "INSERT INTO instance (id, map, resettime, difficulty, completedEncounters, data) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_INSTANCE_SAVE_DATA, "UPDATE instance SET data=? WHERE id=?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_INSTANCE_SAVE_ENCOUNTERMASK, "UPDATE instance SET completedEncounters=? WHERE id=?", CONNECTION_ASYNC);
// Game event saves
PrepareStatement(CHAR_DEL_GAME_EVENT_SAVE, "DELETE FROM game_event_save WHERE eventEntry = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_GAME_EVENT_SAVE, "INSERT INTO game_event_save (eventEntry, state, next_start) VALUES (?, ?, ?)", CONNECTION_ASYNC);
// Game event condition saves
PrepareStatement(CHAR_DEL_ALL_GAME_EVENT_CONDITION_SAVE, "DELETE FROM game_event_condition_save WHERE eventEntry = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GAME_EVENT_CONDITION_SAVE, "DELETE FROM game_event_condition_save WHERE eventEntry = ? AND condition_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_GAME_EVENT_CONDITION_SAVE, "INSERT INTO game_event_condition_save (eventEntry, condition_id, done) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_GAME_EVENT_CONDITION_SAVE_DATA, "SELECT eventEntry, condition_id, done FROM game_event_condition_save", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_GAME_EVENT_SAVE_DATA, "SELECT eventEntry, state, next_start FROM game_event_save", CONNECTION_SYNCH);
// Petitions
PrepareStatement(CHAR_DEL_ALL_PETITION_SIGNATURES, "DELETE FROM petition_sign WHERE playerguid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_SIGNATURE, "DELETE FROM petition_sign WHERE playerguid = ? AND type = ?", CONNECTION_ASYNC);
// Arena teams
PrepareStatement(CHAR_INS_ARENA_TEAM, "INSERT INTO arena_team (arenaTeamId, name, captainGuid, type, rating, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ARENA_TEAM_MEMBER, "INSERT INTO arena_team_member (arenaTeamId, guid, personalRating) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ARENA_TEAM, "DELETE FROM arena_team WHERE arenaTeamId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ARENA_TEAM_MEMBERS, "DELETE FROM arena_team_member WHERE arenaTeamId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ARENA_TEAM_CAPTAIN, "UPDATE arena_team SET captainGuid = ? WHERE arenaTeamId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ARENA_TEAM_MEMBER, "DELETE FROM arena_team_member WHERE arenaTeamId = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ARENA_TEAM_STATS, "UPDATE arena_team SET rating = ?, weekGames = ?, weekWins = ?, seasonGames = ?, seasonWins = ?, `rank` = ? WHERE arenaTeamId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ARENA_TEAM_MEMBER, "UPDATE arena_team_member SET personalRating = ?, weekGames = ?, weekWins = ?, seasonGames = ?, seasonWins = ? WHERE arenaTeamId = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_CHARACTER_ARENA_STATS, "REPLACE INTO character_arena_stats (guid, slot, matchMakerRating, maxMMR) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PLAYER_ARENA_TEAMS, "SELECT arena_team_member.arenaTeamId FROM arena_team_member JOIN arena_team ON arena_team_member.arenaTeamId = arena_team.arenaTeamId WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_UPD_ARENA_TEAM_NAME, "UPDATE arena_team SET name = ? WHERE arenaTeamId = ?", CONNECTION_ASYNC);
// Character battleground data
PrepareStatement(CHAR_INS_PLAYER_ENTRY_POINT, "INSERT INTO character_entry_point (guid, joinX, joinY, joinZ, joinO, joinMapId, taxiPath0, taxiPath1, mountSpell) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PLAYER_ENTRY_POINT, "DELETE FROM character_entry_point WHERE guid = ?", CONNECTION_ASYNC);
// Character homebind
PrepareStatement(CHAR_INS_PLAYER_HOMEBIND, "INSERT INTO character_homebind (guid, mapId, zoneId, posX, posY, posZ) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_PLAYER_HOMEBIND, "UPDATE character_homebind SET mapId = ?, zoneId = ?, posX = ?, posY = ?, posZ = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PLAYER_HOMEBIND, "DELETE FROM character_homebind WHERE guid = ?", CONNECTION_ASYNC);
// Corpse
PrepareStatement(CHAR_SEL_CORPSES, "SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, guildId, flags, dynFlags, time, corpseType, instanceId, phaseMask, guid FROM corpse WHERE mapId = ? AND instanceId = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_INS_CORPSE, "INSERT INTO corpse (guid, posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, guildId, flags, dynFlags, time, corpseType, instanceId, phaseMask) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CORPSE, "DELETE FROM corpse WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CORPSES_FROM_MAP, "DELETE FROM corpse WHERE mapId = ? AND instanceId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CORPSE_LOCATION, "SELECT mapId, posX, posY, posZ, orientation FROM corpse WHERE guid = ?", CONNECTION_ASYNC);
// Creature respawn
PrepareStatement(CHAR_SEL_CREATURE_RESPAWNS, "SELECT guid, respawnTime FROM creature_respawn WHERE mapId = ? AND instanceId = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_REP_CREATURE_RESPAWN, "REPLACE INTO creature_respawn (guid, respawnTime, mapId, instanceId) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CREATURE_RESPAWN, "DELETE FROM creature_respawn WHERE guid = ? AND mapId = ? AND instanceId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CREATURE_RESPAWN_BY_INSTANCE, "DELETE FROM creature_respawn WHERE mapId = ? AND instanceId = ?", CONNECTION_ASYNC);
// Gameobject respawn
PrepareStatement(CHAR_SEL_GO_RESPAWNS, "SELECT guid, respawnTime FROM gameobject_respawn WHERE mapId = ? AND instanceId = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_REP_GO_RESPAWN, "REPLACE INTO gameobject_respawn (guid, respawnTime, mapId, instanceId) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GO_RESPAWN, "DELETE FROM gameobject_respawn WHERE guid = ? AND mapId = ? AND instanceId = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GO_RESPAWN_BY_INSTANCE, "DELETE FROM gameobject_respawn WHERE mapId = ? AND instanceId = ?", CONNECTION_ASYNC);
// GM Tickets
PrepareStatement(CHAR_SEL_GM_TICKETS, "SELECT id, type, playerGuid, name, description, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, needMoreHelp, resolvedBy FROM gm_ticket", CONNECTION_SYNCH);
PrepareStatement(CHAR_REP_GM_TICKET, "REPLACE INTO gm_ticket (id, type, playerGuid, name, description, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, needMoreHelp, resolvedBy) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GM_TICKET, "DELETE FROM gm_ticket WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PLAYER_GM_TICKETS, "DELETE FROM gm_ticket WHERE playerGuid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_PLAYER_GM_TICKETS_ON_CHAR_DELETION, "UPDATE gm_ticket SET type = 2 WHERE playerGuid = ?", CONNECTION_ASYNC);
// GM Survey/subsurvey/lag report
PrepareStatement(CHAR_INS_GM_SURVEY, "INSERT INTO gm_survey (guid, surveyId, mainSurvey, comment, createTime) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(NOW()))", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_GM_SUBSURVEY, "INSERT INTO gm_subsurvey (surveyId, questionId, answer, answerComment) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_LAG_REPORT, "INSERT INTO lag_reports (guid, lagType, mapId, posX, posY, posZ, latency, createTime) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
// LFG Data
PrepareStatement(CHAR_REP_LFG_DATA, "REPLACE INTO lfg_data (guid, dungeon, state) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_LFG_DATA, "DELETE FROM lfg_data WHERE guid = ?", CONNECTION_ASYNC);
// Player saving
PrepareStatement(CHAR_INS_CHARACTER, "INSERT INTO characters (guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, bankSlots, restState, playerFlags, "
"map, instance_id, instance_mode_mask, position_x, position_y, position_z, orientation, trans_x, trans_y, trans_z, trans_o, transguid, "
"taximask, cinematic, "
"totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, "
"extra_flags, stable_slots, at_login, zone, "
"death_expire_time, taxi_path, arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, "
"todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk, health, power1, power2, power3, "
"power4, power5, power6, power7, latency, talentGroupsCount, activeTalentGroup, exploredZones, equipmentCache, "
"ammoId, knownTitles, actionBars, grantableLevels, innTriggerId, extraBonusTalentCount) VALUES "
"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHARACTER, "UPDATE characters SET name=?,race=?,class=?,gender=?,level=?,xp=?,money=?,skin=?,face=?,hairStyle=?,hairColor=?,facialStyle=?,bankSlots=?,restState=?,playerFlags=?,"
"map=?,instance_id=?,instance_mode_mask=?,position_x=?,position_y=?,position_z=?,orientation=?,trans_x=?,trans_y=?,trans_z=?,trans_o=?,transguid=?,taximask=?,cinematic=?,totaltime=?,leveltime=?,rest_bonus=?,"
"logout_time=?,is_logout_resting=?,resettalents_cost=?,resettalents_time=?,extra_flags=?,stable_slots=?,at_login=?,zone=?,death_expire_time=?,taxi_path=?,"
"arenaPoints=?,totalHonorPoints=?,todayHonorPoints=?,yesterdayHonorPoints=?,totalKills=?,todayKills=?,yesterdayKills=?,chosenTitle=?,knownCurrencies=?,"
"watchedFaction=?,drunk=?,health=?,power1=?,power2=?,power3=?,power4=?,power5=?,power6=?,power7=?,latency=?,talentGroupsCount=?,activeTalentGroup=?,exploredZones=?,"
"equipmentCache=?,ammoId=?,knownTitles=?,actionBars=?,grantableLevels=?,innTriggerId=?,extraBonusTalentCount=?,online=? WHERE guid=?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG, "UPDATE characters SET at_login = at_login | ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_REM_AT_LOGIN_FLAG, "UPDATE characters set at_login = at_login & ~ ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ALL_AT_LOGIN_FLAGS, "UPDATE characters SET at_login = at_login | ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_BUG_REPORT, "INSERT INTO bugreport (type, content) VALUES(?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_SPAM_REPORT, "INSERT INTO spam_reports (SpamType, SpammerGuid, Unk1, MailIdOrMessageType, ChannelId, SecondsSinceMessage, Description, Time) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_PETITION_NAME, "UPDATE petition SET name = ? WHERE petition_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PETITION_SIGNATURE, "INSERT INTO petition_sign (ownerguid, petition_id, playerguid, player_account) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ACCOUNT_ONLINE, "UPDATE characters SET online = 0 WHERE account = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_GROUP, "INSERT INTO `groups` (guid, leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raidDifficulty, masterLooterGuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_GROUP_MEMBER, "REPLACE INTO group_member (guid, memberGuid, memberFlags, subgroup, roles) VALUES(?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GROUP_MEMBER, "DELETE FROM group_member WHERE memberGuid = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GROUP_LEADER, "UPDATE `groups` SET leaderGuid = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GROUP_TYPE, "UPDATE `groups` SET groupType = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GROUP_MEMBER_SUBGROUP, "UPDATE group_member SET subgroup = ? WHERE memberGuid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GROUP_MEMBER_FLAG, "UPDATE group_member SET memberFlags = ? WHERE memberGuid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GROUP_DIFFICULTY, "UPDATE `groups` SET difficulty = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GROUP_RAID_DIFFICULTY, "UPDATE `groups` SET raidDifficulty = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ALL_GM_TICKETS, "TRUNCATE TABLE gm_ticket", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INVALID_SPELL_TALENTS, "DELETE FROM character_talent WHERE spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INVALID_SPELL_SPELLS, "DELETE FROM character_spell WHERE spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_DELETE_INFO, "UPDATE characters SET deleteInfos_Name = name, deleteInfos_Account = account, deleteDate = UNIX_TIMESTAMP(), name = '', account = 0 WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UDP_RESTORE_DELETE_INFO, "UPDATE characters SET name = ?, account = ?, deleteDate = NULL, deleteInfos_Name = NULL, deleteInfos_Account = NULL WHERE deleteDate IS NOT NULL AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ZONE, "UPDATE characters SET zone = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_LEVEL, "UPDATE characters SET level = ?, xp = 0 WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_XP_ACCUMULATIVE, "UPDATE characters SET xp = xp + ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA, "DELETE FROM character_achievement_progress WHERE criteria = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INVALID_ACHIEVMENT, "DELETE FROM character_achievement WHERE achievement = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ADDON, "INSERT INTO addons (name, crc) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INVALID_PET_SPELL, "DELETE FROM pet_spell WHERE spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GLOBAL_INSTANCE_RESETTIME, "UPDATE instance_reset SET resettime = ? WHERE mapid = ? AND difficulty = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_ONLINE, "UPDATE characters SET online = 1 WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_NAME_AT_LOGIN, "UPDATE characters set name = ?, at_login = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_WORLDSTATE, "UPDATE worldstates SET value = ? WHERE entry = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_WORLDSTATE, "INSERT INTO worldstates (entry, value) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE, "DELETE FROM character_instance WHERE instance = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_NOT_EXTENDED, "DELETE FROM character_instance WHERE instance = ? AND extended = 0", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_INSTANCE_SET_NOT_EXTENDED, "UPDATE character_instance SET extended = 0 WHERE instance = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID, "DELETE FROM character_instance WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extended = 0 WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_INSTANCE_EXTENDED, "UPDATE character_instance SET extended = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extended) VALUES (?, ?, ?, 0)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ARENA_LOG_FIGHT, "INSERT INTO log_arena_fights VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ARENA_LOG_MEMBERSTATS, "INSERT INTO log_arena_memberstats VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GENDER_AND_APPEARANCE, "UPDATE characters SET gender = ?, skin = ?, face = ?, hairStyle = ?, hairColor = ?, facialStyle = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHARACTER_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags | ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_REM_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags & ~ ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHARACTER_SOCIAL, "REPLACE INTO character_social (guid, friend, flags) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHARACTER_SOCIAL, "DELETE FROM character_social WHERE guid = ? AND friend = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHARACTER_SOCIAL_NOTE, "UPDATE character_social SET note = ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHARACTER_POSITION, "UPDATE characters SET position_x = ?, position_y = ?, position_z = ?, orientation = ?, map = ?, zone = ?, trans_x = 0, trans_y = 0, trans_z = 0, transguid = 0, taxi_path = '', cinematic = 1 WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_AURA_FROZEN, "SELECT characters.name FROM characters LEFT JOIN character_aura ON (characters.guid = character_aura.guid) WHERE character_aura.spell = 9454", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_ONLINE, "SELECT name, account, map, zone FROM characters WHERE online > 0", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_GUID, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_NAME, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%')", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_DEL_INFO, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID, "SELECT guid FROM characters WHERE account = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_PINFO, "SELECT totaltime, level, money, account, race, class, map, zone, gender, health, playerFlags FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM character_banned WHERE guid = ? AND active ORDER BY bandate ASC LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_PINFO_MAILS, "SELECT SUM(CASE WHEN (checked & 1) THEN 1 ELSE 0 END) AS 'readmail', COUNT(*) AS 'totalmail' FROM mail WHERE `receiver` = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_PINFO_XP, "SELECT a.xp, b.guid FROM characters a LEFT JOIN guild_member b ON a.guid = b.guid WHERE a.guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ FROM character_homebind WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_GUID_NAME_BY_ACC, "SELECT guid, name FROM characters WHERE account = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_POOL_QUEST_SAVE, "SELECT quest_id FROM pool_quest_save WHERE pool_id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHARACTER_AT_LOGIN, "SELECT at_login FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN, "SELECT class, level, at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_CUSTOMIZE_INFO, "SELECT name, race, class, gender, at_login FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, "SELECT at_login, knownTitles, money FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_AT_LOGIN_TITLES_MONEY, "SELECT at_login, knownTitles, money FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL, "SELECT id, messageType, mailTemplateId, sender, subject, body, money, has_items FROM mail WHERE receiver = ? AND has_items <> 0 AND cod <> 0", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_SOCIAL, "SELECT DISTINCT guid FROM character_social WHERE friend = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_ARENA_TEAM_ID_BY_PLAYER_GUID, "SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid = ? AND type = ? LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId FROM mail WHERE receiver = ? AND deliver_time <= ? ORDER BY id DESC", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_NEXT_MAIL_DELIVERYTIME, "SELECT MIN(deliver_time) FROM mail WHERE receiver = ? AND deliver_time > ? AND (checked & 1) = 0 LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_AURA_FROZEN, "DELETE FROM character_aura WHERE spell = 9454 AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_INVENTORY_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE itemEntry = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_MAIL_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM mail_items mi INNER JOIN item_instance ii ON ii.guid = mi.item_guid WHERE itemEntry = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_AUCTIONHOUSE_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid WHERE itemEntry = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_GUILD_BANK_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM guild_bank_item gbi INNER JOIN item_instance ii ON ii.guid = gbi.item_guid WHERE itemEntry = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY, "SELECT ci.item, cb.slot AS bag, ci.slot, ci.guid, c.account, c.name FROM characters c "
"INNER JOIN character_inventory ci ON ci.guid = c.guid "
"INNER JOIN item_instance ii ON ii.guid = ci.item "
"LEFT JOIN character_inventory cb ON cb.item = ci.bag WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER, "SELECT ci.item FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE ii.itemEntry = ? AND ii.owner_guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_MAIL_ITEMS_BY_ENTRY, "SELECT mi.item_guid, m.sender, m.receiver, cs.account, cs.name, cr.account, cr.name "
"FROM mail m INNER JOIN mail_items mi ON mi.mail_id = m.id INNER JOIN item_instance ii ON ii.guid = mi.item_guid "
"INNER JOIN characters cs ON cs.guid = m.sender INNER JOIN characters cr ON cr.guid = m.receiver WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_AUCTIONHOUSE_ITEM_BY_ENTRY, "SELECT ah.itemguid, ah.itemowner, c.account, c.name FROM auctionhouse ah INNER JOIN characters c ON c.guid = ah.itemowner INNER JOIN item_instance ii ON ii.guid = ah.itemguid WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_GUILD_BANK_ITEM_BY_ENTRY, "SELECT gi.item_guid, gi.guildid, g.name FROM guild_bank_item gi INNER JOIN guild g ON g.guildid = gi.guildid INNER JOIN item_instance ii ON ii.guid = gi.item_guid WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT, "DELETE FROM character_achievement WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS, "DELETE FROM character_achievement_progress WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_ACHIEVEMENT, "INSERT INTO character_achievement (guid, achievement, date) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS_BY_CRITERIA, "DELETE FROM character_achievement_progress WHERE guid = ? AND criteria = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_ACHIEVEMENT_PROGRESS, "INSERT INTO character_achievement_progress (guid, criteria, counter, date) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_ACHIEVEMENT_OFFLINE_UPDATES, "INSERT INTO character_achievement_offline_updates (guid, update_type, arg1, arg2, arg3) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_ACHIEVEMENT_OFFLINE_UPDATES, "SELECT update_type, arg1, arg2, arg3 FROM character_achievement_offline_updates WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT_OFFLINE_UPDATES, "DELETE FROM character_achievement_offline_updates WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_REPUTATION_BY_FACTION, "DELETE FROM character_reputation WHERE guid = ? AND faction = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_REPUTATION_BY_FACTION, "INSERT INTO character_reputation (guid, faction, standing, flags) VALUES (?, ?, ? , ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_ARENA_POINTS, "UPDATE characters SET arenaPoints = (arenaPoints + ?) WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ITEM_REFUND_INSTANCE, "DELETE FROM item_refund_instance WHERE item_guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ITEM_REFUND_INSTANCE, "INSERT INTO item_refund_instance (item_guid, player_guid, paidMoney, paidExtendedCost) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GROUP, "DELETE FROM `groups` WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GROUP_MEMBER_ALL, "DELETE FROM group_member WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_GIFT, "INSERT INTO character_gifts (guid, item_guid, entry, flags) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INSTANCE_BY_INSTANCE, "DELETE FROM instance WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_MAIL_ITEM_BY_ID, "DELETE FROM mail_items WHERE mail_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (petition_id, ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_BY_ID, "DELETE FROM petition WHERE petition_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_ID, "DELETE FROM petition_sign WHERE petition_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_DECLINED_NAME, "DELETE FROM character_declinedname WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_DECLINED_NAME, "INSERT INTO character_declinedname (guid, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_RACE, "UPDATE characters SET race = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SKILL_LANGUAGES, "DELETE FROM character_skills WHERE skill IN (98, 113, 759, 111, 313, 109, 115, 315, 673, 137) AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_SKILL_LANGUAGE, "INSERT INTO `character_skills` (guid, skill, value, max) VALUES (?, ?, 300, 300)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_TAXI_PATH, "UPDATE characters SET taxi_path = '' WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_TAXIMASK, "UPDATE characters SET taximask = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS, "DELETE FROM character_queststatus WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SOCIAL_BY_GUID, "DELETE FROM character_social WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SOCIAL_BY_FRIEND, "DELETE FROM character_social WHERE friend = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT, "DELETE FROM character_achievement WHERE achievement = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_ACHIEVEMENT, "UPDATE character_achievement SET achievement = ? WHERE achievement = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_INVENTORY_FACTION_CHANGE, "UPDATE item_instance ii, character_inventory ci SET ii.itemEntry = ? WHERE ii.itemEntry = ? AND ci.guid = ? AND ci.item = ii.guid", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SPELL_BY_SPELL, "DELETE FROM character_spell WHERE guid = ? AND spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_SPELL_FACTION_CHANGE, "UPDATE character_spell SET spell = ? WHERE spell = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_REP_BY_FACTION, "SELECT standing FROM character_reputation WHERE faction = ? AND guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_REP_BY_FACTION, "DELETE FROM character_reputation WHERE faction = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_REP_FACTION_CHANGE, "UPDATE character_reputation SET faction = ?, standing = ? WHERE faction = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_TITLES_FACTION_CHANGE, "UPDATE characters SET knownTitles = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_RES_CHAR_TITLES_FACTION_CHANGE, "UPDATE characters SET chosenTitle = 0 WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN, "DELETE FROM character_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHARACTER, "DELETE FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACTION, "DELETE FROM character_action WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_AURA, "DELETE FROM character_aura WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_GIFT, "DELETE FROM character_gifts WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INSTANCE, "DELETE FROM character_instance WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INVENTORY, "DELETE FROM character_inventory WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED, "DELETE FROM character_queststatus_rewarded WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_REPUTATION, "DELETE FROM character_reputation WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SPELL, "DELETE FROM character_spell WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_MAIL, "DELETE FROM mail WHERE receiver = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_MAIL_ITEMS, "DELETE FROM mail_items WHERE receiver = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENTS, "DELETE FROM character_achievement WHERE guid = ? AND achievement NOT BETWEEN '456' AND '467' AND achievement NOT BETWEEN '1400' AND '1427' AND achievement NOT IN(1463, 3117, 3259)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_EQUIPMENTSETS, "DELETE FROM character_equipmentsets WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER, "DELETE FROM guild_eventlog WHERE PlayerGuid1 = ? OR PlayerGuid2 = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, "DELETE FROM guild_bank_eventlog WHERE PlayerGuid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_GLYPHS, "DELETE FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_TALENT, "DELETE FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SKILLS, "DELETE FROM character_skills WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UDP_CHAR_HONOR_POINTS, "UPDATE characters SET totalHonorPoints = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UDP_CHAR_HONOR_POINTS_ACCUMULATIVE, "UPDATE characters SET totalHonorPoints = totalHonorPoints + ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UDP_CHAR_ARENA_POINTS, "UPDATE characters SET arenaPoints = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UDP_CHAR_ARENA_POINTS_ACCUMULATIVE, "UPDATE characters SET arenaPoints = arenaPoints + ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ALL_HONOR_POINTS, "UPDATE characters SET totalHonorPoints = 0", CONNECTION_SYNCH);
PrepareStatement(CHAR_UPD_ALL_ARENA_POINTS, "UPDATE characters SET arenaPoints = 0", CONNECTION_SYNCH);
PrepareStatement(CHAR_UDP_CHAR_MONEY, "UPDATE characters SET money = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UDP_CHAR_MONEY_ACCUMULATIVE, "UPDATE characters SET money = money + ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_REMOVE_GHOST, "UPDATE characters SET playerFlags = (playerFlags & (~16)) WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, button, action, type) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_ACTION, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC, "DELETE FROM character_action WHERE guid = ? AND button = ? AND spec = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM, "DELETE FROM character_inventory WHERE item = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT, "DELETE FROM character_inventory WHERE bag = ? AND slot = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_MAIL, "UPDATE mail SET has_items = ?, expire_time = ?, deliver_time = ?, money = ?, cod = ?, checked = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_CHAR_QUESTSTATUS, "REPLACE INTO character_queststatus (guid, quest, status, explored, timer, mobcount1, mobcount2, mobcount3, mobcount4, itemcount1, itemcount2, itemcount3, itemcount4, itemcount5, itemcount6, playercount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST, "DELETE FROM character_queststatus WHERE guid = ? AND quest = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_QUESTSTATUS_REWARDED, "INSERT IGNORE INTO character_queststatus_rewarded (guid, quest, active) VALUES (?, ?, 1)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST, "DELETE FROM character_queststatus_rewarded WHERE guid = ? AND quest = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE, "UPDATE character_queststatus_rewarded SET quest = ? WHERE quest = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE, "UPDATE character_queststatus_rewarded SET active = 1 WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE_BY_QUEST, "UPDATE character_queststatus_rewarded SET active = 0 WHERE quest = ? AND guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SKILL_BY_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_SKILLS, "INSERT INTO character_skills (guid, skill, value, max) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UDP_CHAR_SKILLS, "UPDATE character_skills SET value = ?, max = ? WHERE guid = ? AND skill = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_SPELL, "INSERT INTO character_spell (guid, spell, specMask) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_STATS, "DELETE FROM character_stats WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_STATS, "INSERT INTO character_stats (guid, maxhealth, maxpower1, maxpower2, maxpower3, maxpower4, maxpower5, maxpower6, maxpower7, strength, agility, stamina, intellect, spirit, "
"armor, resHoly, resFire, resNature, resFrost, resShadow, resArcane, blockPct, dodgePct, parryPct, critPct, rangedCritPct, spellCritPct, attackPower, rangedAttackPower, "
"spellPower, resilience) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_STATS, "SELECT maxhealth, strength, agility, stamina, intellect, spirit, armor, attackPower, spellPower, resilience FROM character_stats WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_PETITION_BY_OWNER, "DELETE FROM petition WHERE ownerguid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER, "DELETE FROM petition_sign WHERE ownerguid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_BY_OWNER_AND_TYPE, "DELETE FROM petition WHERE ownerguid = ? AND type = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER_AND_TYPE, "DELETE FROM petition_sign WHERE ownerguid = ? AND type = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_GLYPHS, "INSERT INTO character_glyphs VALUES(?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_TALENT_BY_SPELL, "DELETE FROM character_talent WHERE guid = ? AND spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, spell, specMask) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACTION_EXCEPT_SPEC, "DELETE FROM character_action WHERE spec<>? AND guid = ?", CONNECTION_ASYNC);
// Items that hold loot or money
PrepareStatement(CHAR_SEL_ITEMCONTAINER_ITEMS, "SELECT containerGUID, itemid, item_index, count, randomPropertyId, randomSuffix, follow_loot_rules, freeforall, is_blocked, is_counted, is_underthreshold, needs_quest, conditionLootId FROM item_loot_storage", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_ITEMCONTAINER_SINGLE_ITEM, "DELETE FROM item_loot_storage WHERE containerGUID = ? AND itemid = ? AND count = ? AND item_index = ? LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ITEMCONTAINER_SINGLE_ITEM, "INSERT INTO item_loot_storage (containerGUID, itemid, item_index, count, randomPropertyId, randomSuffix, follow_loot_rules, freeforall, is_blocked, is_counted, is_underthreshold, needs_quest, conditionLootId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ITEMCONTAINER_CONTAINER, "DELETE FROM item_loot_storage WHERE containerGUID = ?", CONNECTION_ASYNC);
// Calendar
PrepareStatement(CHAR_REP_CALENDAR_EVENT, "REPLACE INTO calendar_events (id, creator, title, description, type, dungeon, eventtime, flags, time2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CALENDAR_EVENT, "DELETE FROM calendar_events WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_CALENDAR_INVITE, "REPLACE INTO calendar_invites (id, event, invitee, sender, status, statustime, `rank`, text) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE id = ?", CONNECTION_ASYNC);
// Pet
PrepareStatement(CHAR_SEL_CHAR_PET_IDS, "SELECT id FROM character_pet WHERE owner = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_ADD_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges FROM pet_aura WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, category, time FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_AURAS, "DELETE FROM pet_aura WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_SPELLS, "DELETE FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_SPELL_COOLDOWNS, "DELETE FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PET_SPELL_COOLDOWN, "INSERT INTO pet_spell_cooldown (guid, spell, category, time) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_SPELL_BY_SPELL, "DELETE FROM pet_spell WHERE guid = ? AND spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, "
"base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_PETS, "SELECT id, entry, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_REP_CHAR_PET, "REPLACE INTO character_pet (id, entry, owner, modelid, CreatedBySpell, PetType, level, exp, Reactstate, name, renamed, slot, curhealth, curmana, curhappiness, savetime, abdata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
// PvPstats
PrepareStatement(CHAR_SEL_PVPSTATS_MAXID, "SELECT MAX(id) FROM pvpstats_battlegrounds", CONNECTION_SYNCH);
PrepareStatement(CHAR_INS_PVPSTATS_BATTLEGROUND, "INSERT INTO pvpstats_battlegrounds (id, winner_faction, bracket_id, type, date) VALUES (?, ?, ?, ?, NOW())", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PVPSTATS_PLAYER, "INSERT INTO pvpstats_players (battleground_id, character_guid, winner, score_killing_blows, score_deaths, score_honorable_kills, score_bonus_honor, score_damage_done, score_healing_done, attr_1, attr_2, attr_3, attr_4, attr_5) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PVPSTATS_FACTIONS_OVERALL, "SELECT winner_faction, COUNT(*) AS count FROM pvpstats_battlegrounds WHERE DATEDIFF(NOW(), date) < 7 GROUP BY winner_faction ORDER BY winner_faction ASC", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_PVPSTATS_BRACKET_MONTH, "SELECT character_guid, COUNT(character_guid) AS count, characters.name as character_name FROM pvpstats_players INNER JOIN pvpstats_battlegrounds ON pvpstats_players.battleground_id = pvpstats_battlegrounds.id AND bracket_id = ? AND MONTH(date) = MONTH(NOW()) AND YEAR(date) = YEAR(NOW()) INNER JOIN characters ON pvpstats_players.character_guid = characters.guid AND characters.deleteDate IS NULL WHERE pvpstats_players.winner = 1 GROUP BY character_guid ORDER BY count(character_guid) DESC LIMIT 0, ?", CONNECTION_SYNCH);
// Deserter tracker
PrepareStatement(CHAR_INS_DESERTER_TRACK, "INSERT INTO battleground_deserters (guid, type, datetime) VALUES (?, ?, NOW())", CONNECTION_ASYNC);
// QuestTracker
PrepareStatement(CHAR_INS_QUEST_TRACK, "INSERT INTO quest_tracker (id, character_guid, quest_accept_time, core_hash, core_revision) VALUES (?, ?, NOW(), ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_QUEST_TRACK_GM_COMPLETE, "UPDATE quest_tracker SET completed_by_gm = 1 WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_QUEST_TRACK_COMPLETE_TIME, "UPDATE quest_tracker SET quest_complete_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_QUEST_TRACK_ABANDON_TIME, "UPDATE quest_tracker SET quest_abandon_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC);
// Recovery Item
PrepareStatement(CHAR_INS_RECOVERY_ITEM, "INSERT INTO recovery_item (Guid, ItemEntry, Count, DeleteDate) VALUES (?, ?, ?, UNIX_TIMESTAMP())", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_RECOVERY_ITEM, "SELECT id, itemEntry, Count, Guid FROM recovery_item WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_RECOVERY_ITEM_LIST, "SELECT id, itemEntry, Count FROM recovery_item WHERE Guid = ? ORDER BY id DESC", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_RECOVERY_ITEM, "DELETE FROM recovery_item WHERE Guid = ? AND ItemEntry = ? AND Count = ? ORDER BY Id DESC LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_RECOVERY_ITEM_BY_RECOVERY_ID, "DELETE FROM recovery_item WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_RECOVERY_ITEM_OLD_ITEMS, "SELECT Guid, ItemEntry FROM recovery_item WHERE DeleteDate IS NOT NULL AND DeleteDate < ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_RECOVERY_ITEM_BY_GUID, "DELETE FROM recovery_item WHERE Guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_HONORPOINTS, "SELECT totalHonorPoints FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_ARENAPOINTS, "SELECT arenaPoints FROM characters WHERE guid = ?", CONNECTION_SYNCH);
// Character names
PrepareStatement(CHAR_INS_RESERVED_PLAYER_NAME, "INSERT IGNORE INTO reserved_name (name) VALUES (?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PROFANITY_PLAYER_NAME, "INSERT IGNORE INTO profanity_name (name) VALUES (?)", CONNECTION_ASYNC);
// Character settings
PrepareStatement(CHAR_SEL_CHAR_SETTINGS, "SELECT source, data FROM character_settings WHERE guid = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_REP_CHAR_SETTINGS, "REPLACE INTO character_settings (guid, source, data) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SETTINGS, "DELETE FROM character_settings WHERE guid = ?", CONNECTION_ASYNC);
// Instance saved data. Stores the states of gameobjects in instances to be loaded on server start
PrepareStatement(CHAR_SELECT_INSTANCE_SAVED_DATA, "SELECT guid, state FROM instance_saved_go_state_data WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_INSERT_INSTANCE_SAVED_DATA, "INSERT INTO instance_saved_go_state_data (id, guid, state) VALUES (?, ?, ?)"
"ON DUPLICATE KEY UPDATE state = VALUES(state)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DELETE_INSTANCE_SAVED_DATA, "DELETE FROM instance_saved_go_state_data WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SANITIZE_INSTANCE_SAVED_DATA, "DELETE FROM instance_saved_go_state_data WHERE id NOT IN (SELECT instance.id FROM instance)", CONNECTION_ASYNC);
// world_state
PrepareStatement(CHAR_SEL_WORLD_STATE, "SELECT Id, Data FROM world_state", CONNECTION_SYNCH);
PrepareStatement(CHAR_REP_WORLD_STATE, "REPLACE INTO world_state (Id, Data) VALUES(?, ?)", CONNECTION_ASYNC);
}
CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo)
{
}
CharacterDatabaseConnection::CharacterDatabaseConnection(ProducerConsumerQueue<SQLOperation*>* q, MySQLConnectionInfo& connInfo) : MySQLConnection(q, connInfo)
{
}
CharacterDatabaseConnection::~CharacterDatabaseConnection()
{
}
@@ -0,0 +1,554 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _CHARACTERDATABASE_H
#define _CHARACTERDATABASE_H
#include "MySQLConnection.h"
enum CharacterDatabaseStatements : uint32
{
/* Naming standard for defines:
{DB}_{SEL/INS/UPD/DEL/REP}_{Summary of data changed}
When updating more than one field, consider looking at the calling function
name for a suiting suffix.
*/
CHAR_DEL_QUEST_POOL_SAVE,
CHAR_INS_QUEST_POOL_SAVE,
CHAR_DEL_NONEXISTENT_GUILD_BANK_ITEM,
CHAR_DEL_EXPIRED_BANS,
CHAR_SEL_DATA_BY_NAME,
CHAR_SEL_DATA_BY_GUID,
CHAR_SEL_CHECK_NAME,
CHAR_SEL_CHECK_GUID,
CHAR_SEL_SUM_CHARS,
CHAR_SEL_CHAR_CREATE_INFO,
CHAR_INS_CHARACTER_BAN,
CHAR_UPD_CHARACTER_BAN,
CHAR_DEL_CHARACTER_BAN,
CHAR_SEL_BANINFO,
CHAR_SEL_GUID_BY_NAME_FILTER,
CHAR_SEL_BANINFO_LIST,
CHAR_SEL_BANNED_NAME,
CHAR_SEL_ENUM,
CHAR_SEL_ENUM_DECLINED_NAME,
CHAR_SEL_FREE_NAME,
CHAR_SEL_CHAR_ZONE,
CHAR_SEL_CHARACTER_NAME_DATA,
CHAR_SEL_CHAR_POSITION_XYZ,
CHAR_SEL_CHAR_POSITION,
CHAR_DEL_QUEST_STATUS_DAILY,
CHAR_DEL_QUEST_STATUS_WEEKLY,
CHAR_DEL_QUEST_STATUS_MONTHLY,
CHAR_DEL_QUEST_STATUS_SEASONAL,
CHAR_DEL_QUEST_STATUS_DAILY_CHAR,
CHAR_DEL_QUEST_STATUS_WEEKLY_CHAR,
CHAR_DEL_QUEST_STATUS_MONTHLY_CHAR,
CHAR_DEL_QUEST_STATUS_SEASONAL_CHAR,
CHAR_DEL_BATTLEGROUND_RANDOM,
CHAR_INS_BATTLEGROUND_RANDOM,
CHAR_SEL_CHARACTER,
CHAR_SEL_CHARACTER_AURAS,
CHAR_SEL_CHARACTER_SPELL,
CHAR_SEL_CHARACTER_QUESTSTATUS,
CHAR_SEL_CHARACTER_DAILYQUESTSTATUS,
CHAR_SEL_CHARACTER_WEEKLYQUESTSTATUS,
CHAR_SEL_CHARACTER_MONTHLYQUESTSTATUS,
CHAR_SEL_CHARACTER_SEASONALQUESTSTATUS,
CHAR_INS_CHARACTER_DAILYQUESTSTATUS,
CHAR_INS_CHARACTER_WEEKLYQUESTSTATUS,
CHAR_INS_CHARACTER_MONTHLYQUESTSTATUS,
CHAR_INS_CHARACTER_SEASONALQUESTSTATUS,
CHAR_SEL_CHARACTER_REPUTATION,
CHAR_SEL_CHARACTER_INVENTORY,
CHAR_SEL_CHARACTER_ACTIONS,
CHAR_SEL_CHARACTER_ACTIONS_SPEC,
CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD,
CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD_SYNCH,
CHAR_SEL_MAIL_SERVER_CHARACTER,
CHAR_REP_MAIL_SERVER_CHARACTER,
CHAR_SEL_CHARACTER_SOCIALLIST,
CHAR_SEL_CHARACTER_HOMEBIND,
CHAR_SEL_CHARACTER_SPELLCOOLDOWNS,
CHAR_SEL_CHARACTER_DECLINEDNAMES,
CHAR_SEL_CHARACTER_ACHIEVEMENTS,
CHAR_SEL_CHARACTER_CRITERIAPROGRESS,
CHAR_SEL_CHARACTER_EQUIPMENTSETS,
CHAR_SEL_CHARACTER_ENTRY_POINT,
CHAR_SEL_CHARACTER_GLYPHS,
CHAR_SEL_CHARACTER_TALENTS,
CHAR_SEL_CHARACTER_SKILLS,
CHAR_SEL_CHARACTER_RANDOMBG,
CHAR_SEL_CHARACTER_BANNED,
CHAR_SEL_CHARACTER_QUESTSTATUSREW,
CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES,
CHAR_SEL_MAILITEMS,
CHAR_SEL_BREW_OF_THE_MONTH,
CHAR_REP_BREW_OF_THE_MONTH,
CHAR_SEL_AUCTION_ITEMS,
CHAR_INS_AUCTION,
CHAR_DEL_AUCTION,
CHAR_UPD_AUCTION_BID,
CHAR_SEL_AUCTIONS,
CHAR_INS_MAIL,
CHAR_DEL_MAIL_BY_ID,
CHAR_INS_MAIL_ITEM,
CHAR_DEL_MAIL_ITEM,
CHAR_DEL_INVALID_MAIL_ITEM,
CHAR_SEL_EXPIRED_MAIL,
CHAR_SEL_EXPIRED_MAIL_ITEMS,
CHAR_UPD_MAIL_RETURNED,
CHAR_UPD_MAIL_ITEM_RECEIVER,
CHAR_UPD_ITEM_OWNER,
CHAR_SEL_ITEM_REFUNDS,
CHAR_SEL_ITEM_BOP_TRADE,
CHAR_DEL_ITEM_BOP_TRADE,
CHAR_INS_ITEM_BOP_TRADE,
CHAR_REP_INVENTORY_ITEM,
CHAR_REP_ITEM_INSTANCE,
CHAR_UPD_ITEM_INSTANCE,
CHAR_UPD_ITEM_INSTANCE_ON_LOAD,
CHAR_DEL_ITEM_INSTANCE,
CHAR_DEL_ITEM_INSTANCE_BY_OWNER,
CHAR_UPD_GIFT_OWNER,
CHAR_DEL_GIFT,
CHAR_SEL_CHARACTER_GIFT_BY_ITEM,
CHAR_SEL_ACCOUNT_BY_NAME,
CHAR_DEL_ACCOUNT_INSTANCE_LOCK_TIMES,
CHAR_INS_ACCOUNT_INSTANCE_LOCK_TIMES,
CHAR_SEL_MATCH_MAKER_RATING,
CHAR_SEL_CHARACTER_COUNT,
CHAR_UPD_NAME_BY_GUID,
CHAR_DEL_DECLINED_NAME,
CHAR_INS_GUILD,
CHAR_DEL_GUILD,
CHAR_UPD_GUILD_NAME,
CHAR_INS_GUILD_MEMBER,
CHAR_DEL_GUILD_MEMBER,
CHAR_DEL_GUILD_MEMBERS,
CHAR_SEL_GUILD_MEMBER_EXTENDED,
CHAR_INS_GUILD_RANK,
CHAR_DEL_GUILD_RANKS,
CHAR_DEL_GUILD_LOWEST_RANK,
CHAR_INS_GUILD_BANK_TAB,
CHAR_DEL_GUILD_BANK_TAB,
CHAR_DEL_GUILD_BANK_TABS,
CHAR_INS_GUILD_BANK_ITEM,
CHAR_DEL_GUILD_BANK_ITEM,
CHAR_DEL_GUILD_BANK_ITEMS,
CHAR_INS_GUILD_BANK_RIGHT,
CHAR_DEL_GUILD_BANK_RIGHTS,
CHAR_DEL_GUILD_BANK_RIGHTS_FOR_RANK,
CHAR_INS_GUILD_BANK_EVENTLOG,
CHAR_DEL_GUILD_BANK_EVENTLOG,
CHAR_DEL_GUILD_BANK_EVENTLOGS,
CHAR_INS_GUILD_EVENTLOG,
CHAR_DEL_GUILD_EVENTLOG,
CHAR_DEL_GUILD_EVENTLOGS,
CHAR_UPD_GUILD_MEMBER_PNOTE,
CHAR_UPD_GUILD_MEMBER_OFFNOTE,
CHAR_UPD_GUILD_MEMBER_RANK,
CHAR_UPD_GUILD_MOTD,
CHAR_UPD_GUILD_INFO,
CHAR_UPD_GUILD_LEADER,
CHAR_UPD_GUILD_RANK_NAME,
CHAR_UPD_GUILD_RANK_RIGHTS,
CHAR_UPD_GUILD_EMBLEM_INFO,
CHAR_UPD_GUILD_BANK_TAB_INFO,
CHAR_UPD_GUILD_BANK_MONEY,
CHAR_UPD_GUILD_BANK_EVENTLOG_TAB,
CHAR_UPD_GUILD_RANK_BANK_MONEY,
CHAR_UPD_GUILD_BANK_TAB_TEXT,
CHAR_INS_GUILD_MEMBER_WITHDRAW,
CHAR_DEL_GUILD_MEMBER_WITHDRAW,
CHAR_SEL_CHAR_DATA_FOR_GUILD,
CHAR_INS_CHANNEL,
CHAR_UPD_CHANNEL,
CHAR_DEL_CHANNEL,
CHAR_UPD_CHANNEL_USAGE,
CHAR_DEL_OLD_CHANNELS,
CHAR_DEL_OLD_CHANNELS_BANS,
CHAR_INS_CHANNEL_BAN,
CHAR_DEL_CHANNEL_BAN,
CHAR_UPD_EQUIP_SET,
CHAR_INS_EQUIP_SET,
CHAR_DEL_EQUIP_SET,
CHAR_INS_AURA,
CHAR_SEL_ACCOUNT_DATA,
CHAR_REP_ACCOUNT_DATA,
CHAR_DEL_ACCOUNT_DATA,
CHAR_SEL_PLAYER_ACCOUNT_DATA,
CHAR_REP_PLAYER_ACCOUNT_DATA,
CHAR_DEL_PLAYER_ACCOUNT_DATA,
CHAR_SEL_TUTORIALS,
CHAR_SEL_HAS_TUTORIALS,
CHAR_INS_TUTORIALS,
CHAR_UPD_TUTORIALS,
CHAR_DEL_TUTORIALS,
CHAR_INS_INSTANCE_SAVE,
CHAR_UPD_INSTANCE_SAVE_DATA,
CHAR_UPD_INSTANCE_SAVE_ENCOUNTERMASK,
CHAR_DEL_GAME_EVENT_SAVE,
CHAR_INS_GAME_EVENT_SAVE,
CHAR_DEL_ALL_GAME_EVENT_CONDITION_SAVE,
CHAR_DEL_GAME_EVENT_CONDITION_SAVE,
CHAR_INS_GAME_EVENT_CONDITION_SAVE,
CHAR_SEL_GAME_EVENT_CONDITION_SAVE_DATA,
CHAR_SEL_GAME_EVENT_SAVE_DATA,
CHAR_INS_ARENA_TEAM,
CHAR_INS_ARENA_TEAM_MEMBER,
CHAR_DEL_ARENA_TEAM,
CHAR_DEL_ARENA_TEAM_MEMBERS,
CHAR_UPD_ARENA_TEAM_CAPTAIN,
CHAR_DEL_ARENA_TEAM_MEMBER,
CHAR_UPD_ARENA_TEAM_STATS,
CHAR_UPD_ARENA_TEAM_MEMBER,
CHAR_REP_CHARACTER_ARENA_STATS,
CHAR_SEL_PLAYER_ARENA_TEAMS,
CHAR_UPD_ARENA_TEAM_NAME,
CHAR_DEL_ALL_PETITION_SIGNATURES,
CHAR_DEL_PETITION_SIGNATURE,
CHAR_INS_PLAYER_ENTRY_POINT,
CHAR_DEL_PLAYER_ENTRY_POINT,
CHAR_INS_PLAYER_HOMEBIND,
CHAR_UPD_PLAYER_HOMEBIND,
CHAR_DEL_PLAYER_HOMEBIND,
CHAR_SEL_CORPSES,
CHAR_INS_CORPSE,
CHAR_DEL_CORPSE,
CHAR_DEL_CORPSES_FROM_MAP,
CHAR_SEL_CORPSE_LOCATION,
CHAR_SEL_CREATURE_RESPAWNS,
CHAR_REP_CREATURE_RESPAWN,
CHAR_DEL_CREATURE_RESPAWN,
CHAR_DEL_CREATURE_RESPAWN_BY_INSTANCE,
CHAR_SEL_GO_RESPAWNS,
CHAR_REP_GO_RESPAWN,
CHAR_DEL_GO_RESPAWN,
CHAR_DEL_GO_RESPAWN_BY_INSTANCE,
CHAR_SEL_GM_TICKETS,
CHAR_REP_GM_TICKET,
CHAR_DEL_GM_TICKET,
CHAR_DEL_ALL_GM_TICKETS,
CHAR_DEL_PLAYER_GM_TICKETS,
CHAR_UPD_PLAYER_GM_TICKETS_ON_CHAR_DELETION,
CHAR_INS_GM_SURVEY,
CHAR_INS_GM_SUBSURVEY,
CHAR_INS_LAG_REPORT,
CHAR_INS_CHARACTER,
CHAR_UPD_CHARACTER,
CHAR_UPD_ADD_AT_LOGIN_FLAG,
CHAR_UPD_REM_AT_LOGIN_FLAG,
CHAR_UPD_ALL_AT_LOGIN_FLAGS,
CHAR_INS_BUG_REPORT,
CHAR_INS_SPAM_REPORT,
CHAR_UPD_PETITION_NAME,
CHAR_INS_PETITION_SIGNATURE,
CHAR_UPD_ACCOUNT_ONLINE,
CHAR_INS_GROUP,
CHAR_REP_GROUP_MEMBER,
CHAR_DEL_GROUP_MEMBER,
CHAR_UPD_GROUP_LEADER,
CHAR_UPD_GROUP_TYPE,
CHAR_UPD_GROUP_MEMBER_SUBGROUP,
CHAR_UPD_GROUP_MEMBER_FLAG,
CHAR_UPD_GROUP_DIFFICULTY,
CHAR_UPD_GROUP_RAID_DIFFICULTY,
CHAR_DEL_INVALID_SPELL_SPELLS,
CHAR_DEL_INVALID_SPELL_TALENTS,
CHAR_UPD_DELETE_INFO,
CHAR_UDP_RESTORE_DELETE_INFO,
CHAR_UPD_ZONE,
CHAR_UPD_LEVEL,
CHAR_UPD_XP_ACCUMULATIVE,
CHAR_DEL_INVALID_ACHIEV_PROGRESS_CRITERIA,
CHAR_DEL_INVALID_ACHIEVMENT,
CHAR_INS_ADDON,
CHAR_DEL_INVALID_PET_SPELL,
CHAR_UPD_GLOBAL_INSTANCE_RESETTIME,
CHAR_UPD_CHAR_ONLINE,
CHAR_UPD_CHAR_NAME_AT_LOGIN,
CHAR_UPD_WORLDSTATE,
CHAR_INS_WORLDSTATE,
CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE,
CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_NOT_EXTENDED,
CHAR_UPD_CHAR_INSTANCE_SET_NOT_EXTENDED,
CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID,
CHAR_UPD_CHAR_INSTANCE,
CHAR_UPD_CHAR_INSTANCE_EXTENDED,
CHAR_INS_CHAR_INSTANCE,
CHAR_INS_ARENA_LOG_FIGHT,
CHAR_INS_ARENA_LOG_MEMBERSTATS,
CHAR_UPD_GENDER_AND_APPEARANCE,
CHAR_DEL_CHARACTER_SKILL,
CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS,
CHAR_UPD_REM_CHARACTER_SOCIAL_FLAGS,
CHAR_INS_CHARACTER_SOCIAL,
CHAR_DEL_CHARACTER_SOCIAL,
CHAR_UPD_CHARACTER_SOCIAL_NOTE,
CHAR_UPD_CHARACTER_POSITION,
CHAR_REP_LFG_DATA,
CHAR_DEL_LFG_DATA,
CHAR_SEL_CHARACTER_AURA_FROZEN,
CHAR_SEL_CHARACTER_ONLINE,
CHAR_SEL_CHAR_DEL_INFO_BY_GUID,
CHAR_SEL_CHAR_DEL_INFO_BY_NAME,
CHAR_SEL_CHAR_DEL_INFO,
CHAR_SEL_CHARS_BY_ACCOUNT_ID,
CHAR_SEL_CHAR_PINFO,
CHAR_SEL_PINFO_XP,
CHAR_SEL_PINFO_MAILS,
CHAR_SEL_PINFO_BANS,
CHAR_SEL_CHAR_HOMEBIND,
CHAR_SEL_CHAR_GUID_NAME_BY_ACC,
CHAR_SEL_POOL_QUEST_SAVE,
CHAR_SEL_CHARACTER_AT_LOGIN,
CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN,
CHAR_SEL_CHAR_CUSTOMIZE_INFO,
CHAR_SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS,
CHAR_SEL_CHAR_AT_LOGIN_TITLES_MONEY,
CHAR_SEL_CHAR_COD_ITEM_MAIL,
CHAR_SEL_CHAR_SOCIAL,
CHAR_SEL_CHAR_OLD_CHARS,
CHAR_SEL_ARENA_TEAM_ID_BY_PLAYER_GUID,
CHAR_SEL_MAIL,
CHAR_SEL_NEXT_MAIL_DELIVERYTIME,
CHAR_DEL_CHAR_AURA_FROZEN,
CHAR_SEL_CHAR_INVENTORY_COUNT_ITEM,
CHAR_SEL_MAIL_COUNT_ITEM,
CHAR_SEL_AUCTIONHOUSE_COUNT_ITEM,
CHAR_SEL_GUILD_BANK_COUNT_ITEM,
CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY,
CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER,
CHAR_SEL_MAIL_ITEMS_BY_ENTRY,
CHAR_SEL_AUCTIONHOUSE_ITEM_BY_ENTRY,
CHAR_SEL_GUILD_BANK_ITEM_BY_ENTRY,
CHAR_DEL_CHAR_ACHIEVEMENT,
CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS,
CHAR_INS_CHAR_ACHIEVEMENT,
CHAR_DEL_CHAR_ACHIEVEMENT_PROGRESS_BY_CRITERIA,
CHAR_INS_CHAR_ACHIEVEMENT_PROGRESS,
CHAR_INS_CHAR_ACHIEVEMENT_OFFLINE_UPDATES,
CHAR_SEL_CHAR_ACHIEVEMENT_OFFLINE_UPDATES,
CHAR_DEL_CHAR_ACHIEVEMENT_OFFLINE_UPDATES,
CHAR_DEL_CHAR_REPUTATION_BY_FACTION,
CHAR_INS_CHAR_REPUTATION_BY_FACTION,
CHAR_UPD_CHAR_ARENA_POINTS,
CHAR_DEL_ITEM_REFUND_INSTANCE,
CHAR_INS_ITEM_REFUND_INSTANCE,
CHAR_DEL_GROUP,
CHAR_DEL_GROUP_MEMBER_ALL,
CHAR_INS_CHAR_GIFT,
CHAR_DEL_INSTANCE_BY_INSTANCE,
CHAR_DEL_MAIL_ITEM_BY_ID,
CHAR_INS_PETITION,
CHAR_DEL_PETITION_BY_ID,
CHAR_DEL_PETITION_SIGNATURE_BY_ID,
CHAR_DEL_CHAR_DECLINED_NAME,
CHAR_INS_CHAR_DECLINED_NAME,
CHAR_UPD_CHAR_RACE,
CHAR_DEL_CHAR_SKILL_LANGUAGES,
CHAR_INS_CHAR_SKILL_LANGUAGE,
CHAR_UPD_CHAR_TAXI_PATH,
CHAR_UPD_CHAR_TAXIMASK,
CHAR_DEL_CHAR_QUESTSTATUS,
CHAR_DEL_CHAR_SOCIAL_BY_GUID,
CHAR_DEL_CHAR_SOCIAL_BY_FRIEND,
CHAR_DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT,
CHAR_UPD_CHAR_ACHIEVEMENT,
CHAR_UPD_CHAR_INVENTORY_FACTION_CHANGE,
CHAR_DEL_CHAR_SPELL_BY_SPELL,
CHAR_UPD_CHAR_SPELL_FACTION_CHANGE,
CHAR_SEL_CHAR_REP_BY_FACTION,
CHAR_DEL_CHAR_REP_BY_FACTION,
CHAR_UPD_CHAR_REP_FACTION_CHANGE,
CHAR_UPD_CHAR_TITLES_FACTION_CHANGE,
CHAR_RES_CHAR_TITLES_FACTION_CHANGE,
CHAR_DEL_CHAR_SPELL_COOLDOWN,
CHAR_DEL_CHARACTER,
CHAR_DEL_CHAR_ACTION,
CHAR_DEL_CHAR_AURA,
CHAR_DEL_CHAR_GIFT,
CHAR_DEL_CHAR_INSTANCE,
CHAR_DEL_CHAR_INVENTORY,
CHAR_DEL_CHAR_QUESTSTATUS_REWARDED,
CHAR_DEL_CHAR_REPUTATION,
CHAR_DEL_CHAR_SPELL,
CHAR_DEL_MAIL,
CHAR_DEL_MAIL_ITEMS,
CHAR_DEL_CHAR_ACHIEVEMENTS,
CHAR_DEL_CHAR_EQUIPMENTSETS,
CHAR_DEL_GUILD_EVENTLOG_BY_PLAYER,
CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER,
CHAR_DEL_CHAR_GLYPHS,
CHAR_DEL_CHAR_TALENT,
CHAR_DEL_CHAR_SKILLS,
CHAR_UDP_CHAR_HONOR_POINTS,
CHAR_UDP_CHAR_HONOR_POINTS_ACCUMULATIVE,
CHAR_UDP_CHAR_ARENA_POINTS,
CHAR_UDP_CHAR_ARENA_POINTS_ACCUMULATIVE,
CHAR_UPD_ALL_HONOR_POINTS,
CHAR_UPD_ALL_ARENA_POINTS,
CHAR_UDP_CHAR_MONEY,
CHAR_UDP_CHAR_MONEY_ACCUMULATIVE,
CHAR_UPD_CHAR_REMOVE_GHOST, // pussywizard
CHAR_INS_CHAR_ACTION,
CHAR_UPD_CHAR_ACTION,
CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC,
CHAR_DEL_CHAR_INVENTORY_BY_ITEM,
CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT,
CHAR_UPD_MAIL,
CHAR_REP_CHAR_QUESTSTATUS,
CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST,
CHAR_INS_CHAR_QUESTSTATUS_REWARDED,
CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST,
CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE,
CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE,
CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_ACTIVE_BY_QUEST,
CHAR_DEL_CHAR_SKILL_BY_SKILL,
CHAR_INS_CHAR_SKILLS,
CHAR_UDP_CHAR_SKILLS,
CHAR_INS_CHAR_SPELL,
CHAR_DEL_CHAR_STATS,
CHAR_INS_CHAR_STATS,
CHAR_SEL_CHAR_STATS,
CHAR_DEL_PETITION_BY_OWNER,
CHAR_DEL_PETITION_SIGNATURE_BY_OWNER,
CHAR_DEL_PETITION_BY_OWNER_AND_TYPE,
CHAR_DEL_PETITION_SIGNATURE_BY_OWNER_AND_TYPE,
CHAR_INS_CHAR_GLYPHS,
CHAR_DEL_CHAR_TALENT_BY_SPELL,
CHAR_INS_CHAR_TALENT,
CHAR_DEL_CHAR_ACTION_EXCEPT_SPEC,
CHAR_REP_CALENDAR_EVENT,
CHAR_DEL_CALENDAR_EVENT,
CHAR_REP_CALENDAR_INVITE,
CHAR_DEL_CALENDAR_INVITE,
CHAR_SEL_PET_AURA,
CHAR_SEL_PET_SPELL,
CHAR_SEL_PET_SPELL_COOLDOWN,
CHAR_DEL_PET_AURAS,
CHAR_DEL_PET_SPELL_COOLDOWNS,
CHAR_INS_PET_SPELL_COOLDOWN,
CHAR_DEL_PET_SPELL_BY_SPELL,
CHAR_INS_PET_SPELL,
CHAR_INS_PET_AURA,
CHAR_DEL_PET_SPELLS,
CHAR_DEL_CHAR_PET_BY_OWNER,
CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER,
CHAR_SEL_CHAR_PETS,
CHAR_SEL_CHAR_PET_IDS,
CHAR_DEL_CHAR_PET_DECLINEDNAME,
CHAR_ADD_CHAR_PET_DECLINEDNAME,
CHAR_SEL_PET_DECLINED_NAME,
CHAR_UPD_CHAR_PET_NAME,
CHAR_UPD_CHAR_PET_SLOT_BY_ID,
CHAR_DEL_CHAR_PET_BY_ID,
CHAR_DEL_CHAR_PET_BY_SLOT,
CHAR_REP_CHAR_PET,
CHAR_SEL_ITEMCONTAINER_ITEMS,
CHAR_DEL_ITEMCONTAINER_SINGLE_ITEM,
CHAR_INS_ITEMCONTAINER_SINGLE_ITEM,
CHAR_DEL_ITEMCONTAINER_CONTAINER,
CHAR_SEL_PVPSTATS_MAXID,
CHAR_INS_PVPSTATS_BATTLEGROUND,
CHAR_INS_PVPSTATS_PLAYER,
CHAR_SEL_PVPSTATS_FACTIONS_OVERALL,
CHAR_SEL_PVPSTATS_BRACKET_MONTH,
CHAR_INS_DESERTER_TRACK,
CHAR_INS_QUEST_TRACK,
CHAR_UPD_QUEST_TRACK_GM_COMPLETE,
CHAR_UPD_QUEST_TRACK_COMPLETE_TIME,
CHAR_UPD_QUEST_TRACK_ABANDON_TIME,
CHAR_INS_RECOVERY_ITEM,
CHAR_SEL_RECOVERY_ITEM,
CHAR_SEL_RECOVERY_ITEM_LIST,
CHAR_DEL_RECOVERY_ITEM,
CHAR_DEL_RECOVERY_ITEM_BY_RECOVERY_ID,
CHAR_SEL_RECOVERY_ITEM_OLD_ITEMS,
CHAR_DEL_RECOVERY_ITEM_BY_GUID,
CHAR_SEL_HONORPOINTS,
CHAR_SEL_ARENAPOINTS,
CHAR_INS_RESERVED_PLAYER_NAME,
CHAR_INS_PROFANITY_PLAYER_NAME,
CHAR_SEL_CHAR_SETTINGS,
CHAR_REP_CHAR_SETTINGS,
CHAR_DEL_CHAR_SETTINGS,
CHAR_SELECT_INSTANCE_SAVED_DATA,
CHAR_INSERT_INSTANCE_SAVED_DATA,
CHAR_DELETE_INSTANCE_SAVED_DATA,
CHAR_SANITIZE_INSTANCE_SAVED_DATA,
CHAR_SEL_WORLD_STATE,
CHAR_REP_WORLD_STATE,
MAX_CHARACTERDATABASE_STATEMENTS
};
class AC_DATABASE_API CharacterDatabaseConnection : public MySQLConnection
{
public:
typedef CharacterDatabaseStatements Statements;
//- Constructors for sync and async connections
CharacterDatabaseConnection(MySQLConnectionInfo& connInfo);
CharacterDatabaseConnection(ProducerConsumerQueue<SQLOperation*>* q, MySQLConnectionInfo& connInfo);
~CharacterDatabaseConnection() override;
//- Loads database type specific prepared statements
void DoPrepareStatements() override;
};
#endif
@@ -0,0 +1,176 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "LoginDatabase.h"
#include "MySQLPreparedStatement.h"
void LoginDatabaseConnection::DoPrepareStatements()
{
if (!m_reconnecting)
m_stmts.resize(MAX_LOGINDATABASE_STATEMENTS);
PrepareStatement(LOGIN_SEL_LOGONCHALLENGE,
"SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.Flags, a.failed_logins, "
"ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, "
"ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate, "
"aa.gmlevel, a.totp_secret, a.salt, a.verifier "
"FROM account a "
"LEFT JOIN account_access aa ON a.id = aa.id "
"LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 "
"LEFT JOIN ip_banned ipb ON ipb.ip = ? "
"WHERE a.username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE,
"SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.Flags, a.failed_logins, "
"ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, "
"ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate, "
"aa.gmlevel, a.session_key "
"FROM account a "
"LEFT JOIN account_access aa ON a.id = aa.id "
"LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 "
"LEFT JOIN ip_banned ipb ON ipb.ip = ? "
"WHERE a.username = ? AND a.session_key IS NOT NULL", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, a.last_ip, a.locked, a.lock_country, a.expansion, a.Flags, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, "
"aa.gmlevel, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) "
"LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? "
"AND a.session_key IS NOT NULL ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_INFO, "SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_REALMLIST, "SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE flag <> 3 ORDER BY name", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_BANNED, "SELECT * FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'realmd', 'Failed login autoban')", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_IP_BANNED_BY_IP, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) AND ip LIKE CONCAT('%%', ?, '%%') ORDER BY unbandate", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED, "SELECT bandate, unbandate FROM account_banned WHERE id = ? AND active = 1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username LIKE CONCAT('%%', ?, '%%') GROUP BY account.id", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'realmd', 'Failed login autoban', 1)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LOGON, "UPDATE account SET salt = ?, verifier = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET session_key = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_FAILEDLOGINS, "SELECT id, failed_logins FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_NUM_CHARS_ON_REALM, "SELECT numchars FROM realmcharacters WHERE realmid = ? AND acctid= ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS, "SELECT realmid, numchars FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_IP_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_IP_NOT_BANNED, "DELETE FROM ip_banned WHERE ip = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?, 1)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED, "UPDATE account_banned SET active = 0 WHERE id = ? AND active != 0", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_REP_REALM_CHARACTERS, "REPLACE INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, expansion, reg_mail, email, joindate) VALUES(?, ?, ?, ?, ?, ?, NOW())", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid=account.id WHERE acctid IS NULL", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK_COUNTRY, "UPDATE account SET lock_country = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EMAIL, "UPDATE account SET email = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_USERNAME, "UPDATE account SET username = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_MUTE_TIME, "UPDATE account SET mutetime = ? , mutereason = ? , muteby = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_MUTE_TIME_LOGIN, "UPDATE account SET mutetime = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LAST_IP, "UPDATE account SET last_ip = ? WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LAST_ATTEMPT_IP, "UPDATE account SET last_attempt_ip = ? WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_ONLINE, "UPDATE account SET online = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_UPTIME_PLAYERS, "UPDATE uptime SET uptime = ?, maxplayers = ? WHERE realmid = ? AND starttime = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_OLD_LOGS, "DELETE FROM logs WHERE (time + ?) < ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_ACCOUNT_ACCESS, "DELETE FROM account_access WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_ACCOUNT_ACCESS_BY_REALM, "DELETE FROM account_access WHERE id = ? AND (RealmID = ? OR RealmID = -1)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT_ACCESS, "INSERT INTO account_access (id,gmlevel,RealmID) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_GET_ACCOUNT_ID_BY_USERNAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_GET_ACCOUNT_ACCESS_GMLEVEL, "SELECT gmlevel FROM account_access WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_GET_GMLEVEL_BY_REALMID, "SELECT gmlevel FROM account_access WHERE id = ? AND (RealmID = ? OR RealmID = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_GET_USERNAME_BY_ID, "SELECT username FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD, "SELECT salt, verifier FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME, "SELECT salt, verifier FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_FLAG, "SELECT Flags FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_SET_ACCOUNT_FLAG, "UPDATE account SET Flags = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_PINFO, "SELECT a.username, aa.gmlevel, a.email, a.reg_mail, a.last_ip, DATE_FORMAT(a.last_login, '%Y-%m-%d %T'), a.mutetime, a.mutereason, a.muteby, a.failed_logins, a.locked, a.OS FROM account a LEFT JOIN account_access aa ON (a.id = aa.id AND (aa.RealmID = ? OR aa.RealmID = -1)) WHERE a.id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM account_banned WHERE id = ? AND active ORDER BY bandate ASC LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_GM_ACCOUNTS, "SELECT a.username, aa.gmlevel FROM account a, account_access aa WHERE a.id=aa.id AND aa.gmlevel >= ? AND (aa.realmid = -1 OR aa.realmid = ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO, "SELECT a.username, a.last_ip, aa.gmlevel, a.expansion FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.id = ? ORDER BY a.last_ip", CONNECTION_SYNCH); // Only used in ".account onlinelist" command
PrepareStatement(LOGIN_SEL_ACCOUNT_ACCESS_GMLEVEL_TEST, "SELECT 1 FROM account_access WHERE id = ? AND gmlevel > ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_ACCESS, "SELECT a.id, aa.gmlevel, aa.RealmID FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_RECRUITER, "SELECT 1 FROM account WHERE recruiter = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_BANS, "SELECT 1 FROM account_banned WHERE id = ? AND active = 1 UNION SELECT 1 FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_WHOIS, "SELECT username, email, last_ip FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_LAST_ATTEMPT_IP, "SELECT last_attempt_ip FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_LAST_IP, "SELECT last_ip FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_REALMLIST_SECURITY_LEVEL, "SELECT allowedSecurityLevel from realmlist WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_ACCOUNT, "DELETE FROM account WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_LOCALIZED, "SELECT id, locale, text FROM autobroadcast_locale WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_AUTOBROADCAST, "INSERT INTO autobroadcast (realmid, weight, text) VALUES (?, ?, ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_AUTOBROADCAST, "DELETE FROM autobroadcast WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_AUTOBROADCAST_LOCALE, "REPLACE INTO autobroadcast_locale (realmid, id, locale, text) VALUES (?, ?, ?, ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_AUTOBROADCAST_LOCALE, "DELETE FROM autobroadcast_locale WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_BY_ID, "SELECT 1 FROM autobroadcast WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_LOCALE_BY_ID, "SELECT locale, text FROM autobroadcast_locale WHERE (realmid = ? OR realmid = -1) AND id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_MAX_ID, "SELECT MAX(id) FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_MOTD, "SELECT text FROM motd WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_MOTD_LOCALE, "SELECT locale, text FROM motd_localized WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_REP_MOTD, "REPLACE INTO motd (realmid, text) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_REP_MOTD_LOCALE, "REPLACE INTO motd_localized (realmid, locale, text) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT_MUTE, "INSERT INTO account_muted VALUES (?, UNIX_TIMESTAMP(), ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_ACCOUNT_MUTED, "DELETE FROM account_muted WHERE guid = ?", CONNECTION_ASYNC);
// 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_AccountLoginDeLete_IP_Logging"
PrepareStatement(LOGIN_INS_ALDL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC);
// 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_FailedAccountLogin_IP_Logging"
PrepareStatement(LOGIN_INS_FACL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_attempt_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC);
// 0: uint32, 1: uint32, 2: uint8, 3: string, 4: string // Complete name: "Login_Insert_CharacterDelete_IP_Logging"
PrepareStatement(LOGIN_INS_CHAR_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, ?, ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC);
// 0: string, 1: string, 2: string // Complete name: "Login_Insert_Failed_Account_Login_due_password_IP_Logging"
PrepareStatement(LOGIN_INS_FALP_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, 0, 1, ?, ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC);
// DB logging
PrepareStatement(LOGIN_INS_LOG, "INSERT INTO logs (time, realm, type, level, string) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
// TOTP
PrepareStatement(LOGIN_SEL_SECRET_DIGEST, "SELECT digest FROM secret_digest WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_SECRET_DIGEST, "INSERT INTO secret_digest (id, digest) VALUES (?,?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_SECRET_DIGEST, "DELETE FROM secret_digest WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_TOTP_SECRET, "SELECT totp_secret FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET, "UPDATE account SET totp_secret = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_UPTIME, "INSERT INTO uptime (realmid, starttime, uptime, revision) VALUES (?, ?, 0, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_GET_EMAIL_BY_ID, "SELECT email FROM account WHERE id = ?", CONNECTION_SYNCH);
// RBAC
PrepareStatement(LOGIN_SEL_RBAC_ACCOUNT_PERMISSIONS, "SELECT permissionId, granted FROM rbac_account_permissions WHERE accountId = ? AND (realmId = ? OR realmId = -1) ORDER BY permissionId, realmId", CONNECTION_BOTH);
PrepareStatement(LOGIN_INS_RBAC_ACCOUNT_PERMISSION, "INSERT INTO rbac_account_permissions (accountId, permissionId, granted, realmId) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE granted = VALUES(granted)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_RBAC_ACCOUNT_PERMISSION, "DELETE FROM rbac_account_permissions WHERE accountId = ? AND permissionId = ? AND (realmId = ? OR realmId = -1)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_RBAC_DEFAULT_PERMISSIONS, "SELECT secId, permissionId FROM rbac_default_permissions WHERE (realmId = ? OR realmId = -1) ORDER BY secId ASC", CONNECTION_SYNCH);
}
LoginDatabaseConnection::LoginDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo)
{
}
LoginDatabaseConnection::LoginDatabaseConnection(ProducerConsumerQueue<SQLOperation*>* q, MySQLConnectionInfo& connInfo) : MySQLConnection(q, connInfo)
{
}
LoginDatabaseConnection::~LoginDatabaseConnection()
{
}
@@ -0,0 +1,161 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _LOGINDATABASE_H
#define _LOGINDATABASE_H
#include "MySQLConnection.h"
enum LoginDatabaseStatements : uint32
{
/* Naming standard for defines:
{DB}_{SEL/INS/UPD/DEL/REP}_{Summary of data changed}
When updating more than one field, consider looking at the calling function
name for a suiting suffix.
*/
LOGIN_SEL_REALMLIST,
LOGIN_DEL_EXPIRED_IP_BANS,
LOGIN_UPD_EXPIRED_ACCOUNT_BANS,
LOGIN_SEL_IP_INFO,
LOGIN_SEL_IP_BANNED,
LOGIN_INS_IP_AUTO_BANNED,
LOGIN_SEL_ACCOUNT_BANNED,
LOGIN_SEL_ACCOUNT_BANNED_ALL,
LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME,
LOGIN_INS_ACCOUNT_AUTO_BANNED,
LOGIN_DEL_ACCOUNT_BANNED,
LOGIN_UPD_LOGON,
LOGIN_UPD_LOGONPROOF,
LOGIN_SEL_LOGONCHALLENGE,
LOGIN_SEL_RECONNECTCHALLENGE,
LOGIN_UPD_FAILEDLOGINS,
LOGIN_SEL_FAILEDLOGINS,
LOGIN_SEL_ACCOUNT_ID_BY_NAME,
LOGIN_SEL_ACCOUNT_LIST_BY_NAME,
LOGIN_SEL_ACCOUNT_INFO_BY_NAME,
LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL,
LOGIN_SEL_NUM_CHARS_ON_REALM,
LOGIN_SEL_REALM_CHARACTER_COUNTS,
LOGIN_SEL_ACCOUNT_BY_IP,
LOGIN_INS_IP_BANNED,
LOGIN_DEL_IP_NOT_BANNED,
LOGIN_SEL_IP_BANNED_ALL,
LOGIN_SEL_IP_BANNED_BY_IP,
LOGIN_SEL_ACCOUNT_BY_ID,
LOGIN_INS_ACCOUNT_BANNED,
LOGIN_UPD_ACCOUNT_NOT_BANNED,
LOGIN_DEL_REALM_CHARACTERS,
LOGIN_REP_REALM_CHARACTERS,
LOGIN_SEL_SUM_REALM_CHARACTERS,
LOGIN_INS_ACCOUNT,
LOGIN_INS_REALM_CHARACTERS_INIT,
LOGIN_UPD_EXPANSION,
LOGIN_UPD_ACCOUNT_LOCK,
LOGIN_UPD_ACCOUNT_LOCK_COUNTRY,
LOGIN_UPD_EMAIL,
LOGIN_UPD_USERNAME,
LOGIN_UPD_MUTE_TIME,
LOGIN_UPD_MUTE_TIME_LOGIN,
LOGIN_UPD_LAST_IP,
LOGIN_UPD_LAST_ATTEMPT_IP,
LOGIN_UPD_ACCOUNT_ONLINE,
LOGIN_UPD_UPTIME_PLAYERS,
LOGIN_DEL_OLD_LOGS,
LOGIN_DEL_ACCOUNT_ACCESS,
LOGIN_DEL_ACCOUNT_ACCESS_BY_REALM,
LOGIN_INS_ACCOUNT_ACCESS,
LOGIN_GET_ACCOUNT_ID_BY_USERNAME,
LOGIN_GET_ACCOUNT_ACCESS_GMLEVEL,
LOGIN_GET_GMLEVEL_BY_REALMID,
LOGIN_GET_USERNAME_BY_ID,
LOGIN_SEL_CHECK_PASSWORD,
LOGIN_SEL_CHECK_PASSWORD_BY_NAME,
LOGIN_SEL_ACCOUNT_FLAG,
LOGIN_UPD_SET_ACCOUNT_FLAG,
LOGIN_SEL_PINFO,
LOGIN_SEL_PINFO_BANS,
LOGIN_SEL_GM_ACCOUNTS,
LOGIN_SEL_ACCOUNT_INFO,
LOGIN_SEL_ACCOUNT_ACCESS_GMLEVEL_TEST,
LOGIN_SEL_ACCOUNT_ACCESS,
LOGIN_SEL_ACCOUNT_RECRUITER,
LOGIN_SEL_BANS,
LOGIN_SEL_ACCOUNT_WHOIS,
LOGIN_SEL_REALMLIST_SECURITY_LEVEL,
LOGIN_DEL_ACCOUNT,
LOGIN_SEL_AUTOBROADCAST,
LOGIN_SEL_AUTOBROADCAST_LOCALIZED,
LOGIN_INS_AUTOBROADCAST,
LOGIN_DEL_AUTOBROADCAST,
LOGIN_INS_AUTOBROADCAST_LOCALE,
LOGIN_DEL_AUTOBROADCAST_LOCALE,
LOGIN_SEL_AUTOBROADCAST_BY_ID,
LOGIN_SEL_AUTOBROADCAST_LOCALE_BY_ID,
LOGIN_SEL_AUTOBROADCAST_MAX_ID,
LOGIN_SEL_MOTD,
LOGIN_SEL_MOTD_LOCALE,
LOGIN_REP_MOTD,
LOGIN_REP_MOTD_LOCALE,
LOGIN_SEL_LAST_ATTEMPT_IP,
LOGIN_SEL_LAST_IP,
LOGIN_INS_ALDL_IP_LOGGING,
LOGIN_INS_FACL_IP_LOGGING,
LOGIN_INS_CHAR_IP_LOGGING,
LOGIN_INS_FALP_IP_LOGGING,
LOGIN_INS_ACCOUNT_MUTE,
LOGIN_SEL_ACCOUNT_MUTE_INFO,
LOGIN_DEL_ACCOUNT_MUTED,
LOGIN_INS_LOG,
LOGIN_SEL_SECRET_DIGEST,
LOGIN_INS_SECRET_DIGEST,
LOGIN_DEL_SECRET_DIGEST,
LOGIN_SEL_ACCOUNT_TOTP_SECRET,
LOGIN_UPD_ACCOUNT_TOTP_SECRET,
LOGIN_INS_UPTIME,
LOGIN_GET_EMAIL_BY_ID,
// RBAC
LOGIN_SEL_RBAC_ACCOUNT_PERMISSIONS,
LOGIN_INS_RBAC_ACCOUNT_PERMISSION,
LOGIN_DEL_RBAC_ACCOUNT_PERMISSION,
LOGIN_SEL_RBAC_DEFAULT_PERMISSIONS,
MAX_LOGINDATABASE_STATEMENTS
};
class AC_DATABASE_API LoginDatabaseConnection : public MySQLConnection
{
public:
typedef LoginDatabaseStatements Statements;
//- Constructors for sync and async connections
LoginDatabaseConnection(MySQLConnectionInfo& connInfo);
LoginDatabaseConnection(ProducerConsumerQueue<SQLOperation*>* q, MySQLConnectionInfo& connInfo);
~LoginDatabaseConnection() override;
//- Loads database type specific prepared statements
void DoPrepareStatements() override;
};
#endif
@@ -0,0 +1,127 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "WorldDatabase.h"
#include "MySQLPreparedStatement.h"
void WorldDatabaseConnection::DoPrepareStatements()
{
if (!m_reconnecting)
m_stmts.resize(MAX_WORLDDATABASE_STATEMENTS);
PrepareStatement(WORLD_SEL_QUEST_POOLS, "SELECT entry, pool_entry FROM pool_quest", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_CRELINKED_RESPAWN, "DELETE FROM linked_respawn WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_REP_CREATURE_LINKED_RESPAWN, "REPLACE INTO linked_respawn (guid, linkedGuid) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_CREATURE_TEXT, "SELECT CreatureID, GroupID, ID, Text, Type, Language, Probability, Emote, Duration, Sound, BroadcastTextId, TextRange FROM creature_text", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_SMART_SCRIPTS, "SELECT entryorguid, source_type, id, link, event_type, event_phase_mask, event_chance, event_flags, event_param1, event_param2, event_param3, event_param4, event_param5, event_param6, action_type, action_param1, action_param2, action_param3, action_param4, action_param5, action_param6, target_type, target_param1, target_param2, target_param3, target_param4, target_x, target_y, target_z, target_o FROM smart_scripts ORDER BY entryorguid, source_type, id, link", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_SMARTAI_WP, "SELECT entry, pointid, position_x, position_y, position_z, orientation, delay FROM waypoints ORDER BY entry, pointid", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_GAMEOBJECT, "DELETE FROM gameobject WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_EVENT_GAMEOBJECT, "DELETE FROM game_event_gameobject WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_INS_GRAVEYARD_ZONE, "INSERT INTO graveyard_zone (ID, GhostZone, Faction) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_GRAVEYARD_ZONE, "DELETE FROM graveyard_zone WHERE ID = ? AND GhostZone = ? AND Faction = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_INS_GAME_TELE, "INSERT INTO game_tele (id, position_x, position_y, position_z, orientation, map, name) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_GAME_TELE, "DELETE FROM game_tele WHERE name = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_INS_NPC_VENDOR, "INSERT INTO npc_vendor (entry, item, maxcount, incrtime, extendedcost) VALUES(?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_NPC_VENDOR, "DELETE FROM npc_vendor WHERE entry = ? AND item = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_NPC_VENDOR_REF, "SELECT item, maxcount, incrtime, ExtendedCost FROM npc_vendor WHERE entry = ? ORDER BY slot ASC", CONNECTION_SYNCH);
PrepareStatement(WORLD_UPD_CREATURE_MOVEMENT_TYPE, "UPDATE creature SET MovementType = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_CREATURE_FACTION, "UPDATE creature_template SET faction = ? WHERE entry = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_CREATURE_NPCFLAG, "UPDATE creature_template SET npcflag = ? WHERE entry = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_CREATURE_POSITION, "UPDATE creature SET position_x = ?, position_y = ?, position_z = ?, orientation = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_CREATURE_WANDER_DISTANCE, "UPDATE creature SET wander_distance = ?, MovementType = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_CREATURE_SPAWN_TIME_SECS, "UPDATE creature SET spawntimesecs = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_INS_CREATURE_FORMATION, "INSERT INTO creature_formations (leaderGUID, memberGUID, dist, angle, groupAI) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_INS_WAYPOINT_DATA, "INSERT INTO waypoint_data (id, point, position_x, position_y, position_z) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_WAYPOINT_DATA, "DELETE FROM waypoint_data WHERE id = ? AND point = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_DATA_POINT, "UPDATE waypoint_data SET point = point - 1 WHERE id = ? AND point > ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_DATA_POSITION, "UPDATE waypoint_data SET position_x = ?, position_y = ?, position_z = ? where id = ? AND point = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_DATA_WPGUID, "UPDATE waypoint_data SET wpguid = ? WHERE id = ? and point = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_MAX_ID, "SELECT MAX(id) FROM waypoint_data", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_MAX_POINT, "SELECT MAX(point) FROM waypoint_data WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID, "SELECT point, position_x, position_y, position_z, orientation, velocity, delay, smoothTransition, move_type, action, action_chance FROM waypoint_data WHERE id = ? ORDER BY point", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_BY_ID, "SELECT point, position_x, position_y, position_z FROM waypoint_data WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_FIRST_BY_ID, "SELECT position_x, position_y, position_z FROM waypoint_data WHERE point = 1 AND id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_LAST_BY_ID, "SELECT position_x, position_y, position_z, orientation FROM waypoint_data WHERE id = ? ORDER BY point DESC LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_BY_WPGUID, "SELECT id, point FROM waypoint_data WHERE wpguid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_ALL_BY_WPGUID, "SELECT id, point, delay, move_type, action, action_chance FROM waypoint_data WHERE wpguid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_UPD_WAYPOINT_DATA_ALL_WPGUID, "UPDATE waypoint_data SET wpguid = 0", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_BY_POS, "SELECT id, point FROM waypoint_data WHERE (abs(position_x - ?) <= ?) and (abs(position_y - ?) <= ?) and (abs(position_z - ?) <= ?)", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_WPGUID_BY_ID, "SELECT wpguid FROM waypoint_data WHERE id = ? and wpguid <> 0", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_ACTION, "SELECT DISTINCT action FROM waypoint_data", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPTS_MAX_ID, "SELECT MAX(guid) FROM waypoint_scripts", CONNECTION_SYNCH);
PrepareStatement(WORLD_INS_CREATURE_ADDON, "INSERT INTO creature_addon(guid, path_id) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_CREATURE_ADDON_PATH, "UPDATE creature_addon SET path_id = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_CREATURE_ADDON, "DELETE FROM creature_addon WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_CREATURE_ADDON_BY_GUID, "SELECT guid FROM creature_addon WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_INS_WAYPOINT_SCRIPT, "INSERT INTO waypoint_scripts (guid) VALUES (?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_WAYPOINT_SCRIPT, "DELETE FROM waypoint_scripts WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_SCRIPT_ID, "UPDATE waypoint_scripts SET id = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_SCRIPT_X, "UPDATE waypoint_scripts SET x = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_SCRIPT_Y, "UPDATE waypoint_scripts SET y = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_SCRIPT_Z, "UPDATE waypoint_scripts SET z = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_WAYPOINT_SCRIPT_O, "UPDATE waypoint_scripts SET o = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_NEAREST, "SELECT guid, id1, id2, id3, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH);
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_SEL_GAME_EVENTS, "SELECT eventEntry, UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), occurence, length, holiday, holidayStage, description, world_event, announce FROM game_event", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_PREREQUISITE_DATA, "SELECT eventEntry, prerequisite_event FROM game_event_prerequisite", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_CREATURE_DATA, "SELECT guid, eventEntry FROM game_event_creature", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_GAMEOBJECT_DATA, "SELECT guid, eventEntry FROM game_event_gameobject", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_MODEL_EQUIPMENT_DATA, "SELECT creature.guid, creature.id1, creature.id2, creature.id3, game_event_model_equip.eventEntry, game_event_model_equip.modelid, game_event_model_equip.equipment_id FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_QUEST_DATA, "SELECT id, quest, eventEntry FROM game_event_creature_quest", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_GAMEOBJECT_QUEST_DATA, "SELECT id, quest, eventEntry FROM game_event_gameobject_quest", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_QUEST_CONDITION_DATA, "SELECT quest, eventEntry, condition_id, num FROM game_event_quest_condition", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_CONDITION_DATA, "SELECT eventEntry, condition_id, req_num, max_world_state_field, done_world_state_field FROM game_event_condition", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_NPC_FLAGS, "SELECT guid, eventEntry, npcflag FROM game_event_npcflag", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_QUEST_SEASONAL_RELATIONS, "SELECT questId, eventEntry FROM game_event_seasonal_questrelation", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_BATTLEGROUND_DATA, "SELECT eventEntry, bgflag FROM game_event_battleground_holiday", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_POOL_DATA, "SELECT pool_template.entry, game_event_pool.eventEntry FROM pool_template JOIN game_event_pool ON pool_template.entry = game_event_pool.pool_entry", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_ARENA_SEASON, "SELECT eventEntry FROM game_event_arena_seasons WHERE season = ?", CONNECTION_SYNCH);
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);
PrepareStatement(WORLD_DEL_DISABLES, "DELETE FROM disables WHERE entry = ? AND sourceType = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA, "UPDATE creature SET zoneId = ?, areaId = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA, "UPDATE gameobject SET zoneId = ?, areaId = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_INS_GAMEOBJECT_ADDON, "INSERT INTO gameobject_addon (guid, invisibilityType, invisibilityValue) VALUES (?, 0, 0)", CONNECTION_ASYNC);
// 0: uint8
PrepareStatement(WORLD_SEL_REQ_XP, "SELECT Experience FROM player_xp_for_level WHERE Level = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_UPD_VERSION, "UPDATE version SET core_version = ?, core_revision = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_SPAWNGROUP_MEMBER, "DELETE FROM spawn_group WHERE spawnType = ? AND spawnId = ?", CONNECTION_ASYNC);
}
WorldDatabaseConnection::WorldDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo)
{
}
WorldDatabaseConnection::WorldDatabaseConnection(ProducerConsumerQueue<SQLOperation*>* q, MySQLConnectionInfo& connInfo) : MySQLConnection(q, connInfo)
{
}
WorldDatabaseConnection::~WorldDatabaseConnection()
{
}
@@ -0,0 +1,138 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _WORLDDATABASE_H
#define _WORLDDATABASE_H
#include "MySQLConnection.h"
enum WorldDatabaseStatements : uint32
{
/* Naming standard for defines:
{DB}_{SEL/INS/UPD/DEL/REP}_{Summary of data changed}
When updating more than one field, consider looking at the calling function
name for a suiting suffix.
*/
WORLD_SEL_QUEST_POOLS,
WORLD_DEL_CRELINKED_RESPAWN,
WORLD_REP_CREATURE_LINKED_RESPAWN,
WORLD_SEL_CREATURE_TEXT,
WORLD_SEL_SMART_SCRIPTS,
WORLD_SEL_SMARTAI_WP,
WORLD_DEL_GAMEOBJECT,
WORLD_DEL_EVENT_GAMEOBJECT,
WORLD_INS_GRAVEYARD_ZONE,
WORLD_DEL_GRAVEYARD_ZONE,
WORLD_INS_GAME_TELE,
WORLD_DEL_GAME_TELE,
WORLD_INS_NPC_VENDOR,
WORLD_DEL_NPC_VENDOR,
WORLD_SEL_NPC_VENDOR_REF,
WORLD_UPD_CREATURE_MOVEMENT_TYPE,
WORLD_UPD_CREATURE_FACTION,
WORLD_UPD_CREATURE_NPCFLAG,
WORLD_UPD_CREATURE_POSITION,
WORLD_UPD_CREATURE_WANDER_DISTANCE,
WORLD_UPD_CREATURE_SPAWN_TIME_SECS,
WORLD_INS_CREATURE_FORMATION,
WORLD_INS_WAYPOINT_DATA,
WORLD_DEL_WAYPOINT_DATA,
WORLD_UPD_WAYPOINT_DATA_POINT,
WORLD_UPD_WAYPOINT_DATA_POSITION,
WORLD_UPD_WAYPOINT_DATA_WPGUID,
WORLD_UPD_WAYPOINT_DATA_ALL_WPGUID,
WORLD_SEL_WAYPOINT_DATA_MAX_ID,
WORLD_SEL_WAYPOINT_DATA_BY_ID,
WORLD_SEL_WAYPOINT_DATA_POS_BY_ID,
WORLD_SEL_WAYPOINT_DATA_POS_FIRST_BY_ID,
WORLD_SEL_WAYPOINT_DATA_POS_LAST_BY_ID,
WORLD_SEL_WAYPOINT_DATA_BY_WPGUID,
WORLD_SEL_WAYPOINT_DATA_ALL_BY_WPGUID,
WORLD_SEL_WAYPOINT_DATA_MAX_POINT,
WORLD_SEL_WAYPOINT_DATA_BY_POS,
WORLD_SEL_WAYPOINT_DATA_WPGUID_BY_ID,
WORLD_SEL_WAYPOINT_DATA_ACTION,
WORLD_SEL_WAYPOINT_SCRIPTS_MAX_ID,
WORLD_UPD_CREATURE_ADDON_PATH,
WORLD_INS_CREATURE_ADDON,
WORLD_DEL_CREATURE_ADDON,
WORLD_SEL_CREATURE_ADDON_BY_GUID,
WORLD_INS_WAYPOINT_SCRIPT,
WORLD_DEL_WAYPOINT_SCRIPT,
WORLD_UPD_WAYPOINT_SCRIPT_ID,
WORLD_UPD_WAYPOINT_SCRIPT_X,
WORLD_UPD_WAYPOINT_SCRIPT_Y,
WORLD_UPD_WAYPOINT_SCRIPT_Z,
WORLD_UPD_WAYPOINT_SCRIPT_O,
WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID,
WORLD_DEL_CREATURE,
WORLD_SEL_COMMANDS,
WORLD_SEL_CREATURE_TEMPLATE,
WORLD_SEL_WAYPOINT_SCRIPT_BY_ID,
WORLD_SEL_ITEM_TEMPLATE_BY_NAME,
WORLD_SEL_CREATURE_BY_ID,
WORLD_SEL_GAMEOBJECT_NEAREST,
WORLD_SEL_CREATURE_NEAREST,
WORLD_SEL_GAMEOBJECT_TARGET,
WORLD_INS_CREATURE,
WORLD_SEL_GAME_EVENTS,
WORLD_SEL_GAME_EVENT_PREREQUISITE_DATA,
WORLD_SEL_GAME_EVENT_CREATURE_DATA,
WORLD_SEL_GAME_EVENT_GAMEOBJECT_DATA,
WORLD_SEL_GAME_EVENT_MODEL_EQUIPMENT_DATA,
WORLD_SEL_GAME_EVENT_QUEST_DATA,
WORLD_SEL_GAME_EVENT_GAMEOBJECT_QUEST_DATA,
WORLD_SEL_GAME_EVENT_QUEST_CONDITION_DATA,
WORLD_SEL_GAME_EVENT_CONDITION_DATA,
WORLD_SEL_GAME_EVENT_NPC_FLAGS,
WORLD_SEL_GAME_EVENT_QUEST_SEASONAL_RELATIONS,
WORLD_SEL_GAME_EVENT_BATTLEGROUND_DATA,
WORLD_SEL_GAME_EVENT_POOL_DATA,
WORLD_SEL_GAME_EVENT_ARENA_SEASON,
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,
WORLD_DEL_DISABLES,
WORLD_UPD_CREATURE_ZONE_AREA_DATA,
WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA,
WORLD_SEL_REQ_XP,
WORLD_INS_GAMEOBJECT_ADDON,
WORLD_UPD_VERSION,
WORLD_DEL_SPAWNGROUP_MEMBER,
MAX_WORLDDATABASE_STATEMENTS
};
class AC_DATABASE_API WorldDatabaseConnection : public MySQLConnection
{
public:
typedef WorldDatabaseStatements Statements;
//- Constructors for sync and async connections
WorldDatabaseConnection(MySQLConnectionInfo& connInfo);
WorldDatabaseConnection(ProducerConsumerQueue<SQLOperation*>* q, MySQLConnectionInfo& connInfo);
~WorldDatabaseConnection() override;
//- Loads database type specific prepared statements
void DoPrepareStatements() override;
};
#endif
@@ -0,0 +1,646 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "MySQLConnection.h"
#include "DatabaseWorker.h"
#include "Log.h"
#include "MySQLHacks.h"
#include "MySQLPreparedStatement.h"
#include "PreparedStatement.h"
#include "QueryResult.h"
#include "StringConvert.h"
#include "Timer.h"
#include "Tokenize.h"
#include "Transaction.h"
#include <errmsg.h>
#include <mysql.h>
#include <mysqld_error.h>
MySQLConnectionInfo::MySQLConnectionInfo(std::string_view infoString)
{
std::vector<std::string_view> tokens = Acore::Tokenize(infoString, ';', true);
if (tokens.size() != 5 && tokens.size() != 6)
return;
host.assign(tokens.at(0));
port_or_socket.assign(tokens.at(1));
user.assign(tokens.at(2));
password.assign(tokens.at(3));
database.assign(tokens.at(4));
if (tokens.size() == 6)
ssl.assign(tokens.at(5));
}
MySQLConnection::MySQLConnection(MySQLConnectionInfo& connInfo) :
m_reconnecting(false),
m_prepareError(false),
m_Mysql(nullptr),
m_queue(nullptr),
m_connectionInfo(connInfo),
m_connectionFlags(CONNECTION_SYNCH) { }
MySQLConnection::MySQLConnection(ProducerConsumerQueue<SQLOperation*>* queue, MySQLConnectionInfo& connInfo) :
m_reconnecting(false),
m_prepareError(false),
m_Mysql(nullptr),
m_queue(queue),
m_connectionInfo(connInfo),
m_connectionFlags(CONNECTION_ASYNC)
{
m_worker = std::make_unique<DatabaseWorker>(m_queue, this);
}
MySQLConnection::~MySQLConnection()
{
Close();
}
void MySQLConnection::Close()
{
// Stop the worker thread before the statements are cleared
m_worker.reset();
m_stmts.clear();
if (m_Mysql)
{
mysql_close(m_Mysql);
m_Mysql = nullptr;
}
}
uint32 MySQLConnection::Open()
{
MYSQL* mysqlInit = mysql_init(nullptr);
if (!mysqlInit)
{
LOG_ERROR("sql.driver", "Could not initialize Mysql connection to database `{}`", m_connectionInfo.database);
return CR_UNKNOWN_ERROR;
}
uint32 port;
char const* unix_socket;
mysql_options(mysqlInit, MYSQL_SET_CHARSET_NAME, "utf8");
#ifdef _WIN32
if (m_connectionInfo.host == ".") // named pipe use option (Windows)
{
unsigned int opt = MYSQL_PROTOCOL_PIPE;
mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, (char const*)&opt);
port = 0;
unix_socket = 0;
}
else // generic case
{
port = *Acore::StringTo<uint32>(m_connectionInfo.port_or_socket);
unix_socket = 0;
}
#else
if (m_connectionInfo.host == ".") // socket use option (Unix/Linux)
{
unsigned int opt = MYSQL_PROTOCOL_SOCKET;
mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, (char const*)&opt);
m_connectionInfo.host = "localhost";
port = 0;
unix_socket = m_connectionInfo.port_or_socket.c_str();
}
else // generic case
{
port = *Acore::StringTo<uint32>(m_connectionInfo.port_or_socket);
unix_socket = nullptr;
}
#endif
if (m_connectionInfo.ssl != "")
{
mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED;
if (m_connectionInfo.ssl == "ssl")
{
opt_use_ssl = SSL_MODE_REQUIRED;
}
mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl);
}
m_Mysql = reinterpret_cast<MySQLHandle*>(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(),
m_connectionInfo.password.c_str(), m_connectionInfo.database.c_str(), port, unix_socket, 0));
if (m_Mysql)
{
if (!m_reconnecting)
{
LOG_INFO("sql.sql", "MySQL client library: {}", mysql_get_client_info());
LOG_INFO("sql.sql", "MySQL server ver: {} ", mysql_get_server_info(m_Mysql));
}
LOG_INFO("sql.sql", "Connected to MySQL database at {}", m_connectionInfo.host);
mysql_autocommit(m_Mysql, 1);
// set connection properties to UTF8 to properly handle locales for different
// server configs - core sends data in UTF8, so MySQL must expect UTF8 too
mysql_set_character_set(m_Mysql, "utf8mb4");
return 0;
}
else
{
LOG_ERROR("sql.driver", "Could not connect to MySQL database at {}: {}", m_connectionInfo.host, mysql_error(mysqlInit));
uint32 errorCode = mysql_errno(mysqlInit);
mysql_close(mysqlInit);
return errorCode;
}
}
bool MySQLConnection::PrepareStatements()
{
DoPrepareStatements();
return !m_prepareError;
}
bool MySQLConnection::Execute(std::string_view sql)
{
if (!m_Mysql)
return false;
{
uint32 _s = getMSTime();
if (mysql_query(m_Mysql, std::string(sql).c_str()))
{
uint32 lErrno = mysql_errno(m_Mysql);
LOG_INFO("sql.sql", "SQL: {}", sql);
LOG_ERROR("sql.sql", "[{}] {}", lErrno, mysql_error(m_Mysql));
if (_HandleMySQLErrno(lErrno, mysql_error(m_Mysql))) // If it returns true, an error was handled successfully (i.e. reconnection)
return Execute(sql); // Try again
return false;
}
else
LOG_DEBUG("sql.sql", "[{} ms] SQL: {}", getMSTimeDiff(_s, getMSTime()), sql);
}
return true;
}
bool MySQLConnection::Execute(PreparedStatementBase* stmt)
{
if (!m_Mysql)
return false;
uint32 index = stmt->GetIndex();
MySQLPreparedStatement* m_mStmt = GetPreparedStatement(index);
ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query
m_mStmt->BindParameters(stmt);
MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT();
MYSQL_BIND* msql_BIND = m_mStmt->GetBind();
uint32 _s = getMSTime();
#if MYSQL_VERSION_ID >= 80300
if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr))
#else
if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
#endif
{
uint32 lErrno = mysql_errno(m_Mysql);
LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
if (_HandleMySQLErrno(lErrno, mysql_stmt_error(msql_STMT))) // If it returns true, an error was handled successfully (i.e. reconnection)
return Execute(stmt); // Try again
m_mStmt->ClearParameters();
return false;
}
if (mysql_stmt_execute(msql_STMT))
{
uint32 lErrno = mysql_errno(m_Mysql);
LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
if (_HandleMySQLErrno(lErrno, mysql_stmt_error(msql_STMT))) // If it returns true, an error was handled successfully (i.e. reconnection)
return Execute(stmt); // Try again
m_mStmt->ClearParameters();
return false;
}
LOG_DEBUG("sql.sql", "[{} ms] SQL(p): {}", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString());
m_mStmt->ClearParameters();
return true;
}
bool MySQLConnection::_Query(PreparedStatementBase* stmt, MySQLPreparedStatement** mysqlStmt, MySQLResult** pResult, uint64* pRowCount, uint32* pFieldCount)
{
if (!m_Mysql)
return false;
uint32 index = stmt->GetIndex();
MySQLPreparedStatement* m_mStmt = GetPreparedStatement(index);
ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query
m_mStmt->BindParameters(stmt);
*mysqlStmt = m_mStmt;
MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT();
MYSQL_BIND* msql_BIND = m_mStmt->GetBind();
uint32 _s = getMSTime();
#if MYSQL_VERSION_ID >= 80300
if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr))
#else
if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
#endif
{
uint32 lErrno = mysql_errno(m_Mysql);
LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
if (_HandleMySQLErrno(lErrno, mysql_stmt_error(msql_STMT))) // If it returns true, an error was handled successfully (i.e. reconnection)
return _Query(stmt, mysqlStmt, pResult, pRowCount, pFieldCount); // Try again
m_mStmt->ClearParameters();
return false;
}
if (mysql_stmt_execute(msql_STMT))
{
uint32 lErrno = mysql_errno(m_Mysql);
LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
if (_HandleMySQLErrno(lErrno, mysql_stmt_error(msql_STMT))) // If it returns true, an error was handled successfully (i.e. reconnection)
return _Query(stmt, mysqlStmt, pResult, pRowCount, pFieldCount); // Try again
m_mStmt->ClearParameters();
return false;
}
LOG_DEBUG("sql.sql", "[{} ms] SQL(p): {}", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString());
m_mStmt->ClearParameters();
*pResult = reinterpret_cast<MySQLResult*>(mysql_stmt_result_metadata(msql_STMT));
*pRowCount = mysql_stmt_affected_rows(msql_STMT);
*pFieldCount = mysql_stmt_field_count(msql_STMT);
return true;
}
ResultSet* MySQLConnection::Query(std::string_view sql)
{
if (sql.empty())
return nullptr;
MySQLResult* result = nullptr;
MySQLField* fields = nullptr;
uint64 rowCount = 0;
uint32 fieldCount = 0;
if (!_Query(sql, &result, &fields, &rowCount, &fieldCount))
return nullptr;
return new ResultSet(result, fields, rowCount, fieldCount);
}
bool MySQLConnection::_Query(std::string_view sql, MySQLResult** pResult, MySQLField** pFields, uint64* pRowCount, uint32* pFieldCount)
{
if (!m_Mysql)
return false;
{
uint32 _s = getMSTime();
if (mysql_query(m_Mysql, std::string(sql).c_str()))
{
uint32 lErrno = mysql_errno(m_Mysql);
LOG_INFO("sql.sql", "SQL: {}", sql);
LOG_ERROR("sql.sql", "[{}] {}", lErrno, mysql_error(m_Mysql));
if (_HandleMySQLErrno(lErrno, mysql_error(m_Mysql))) // If it returns true, an error was handled successfully (i.e. reconnection)
return _Query(sql, pResult, pFields, pRowCount, pFieldCount); // We try again
return false;
}
else
LOG_DEBUG("sql.sql", "[{} ms] SQL: {}", getMSTimeDiff(_s, getMSTime()), sql);
*pResult = reinterpret_cast<MySQLResult*>(mysql_store_result(m_Mysql));
*pRowCount = mysql_affected_rows(m_Mysql);
*pFieldCount = mysql_field_count(m_Mysql);
}
if (!*pResult)
return false;
if (!*pRowCount)
{
mysql_free_result(*pResult);
return false;
}
*pFields = reinterpret_cast<MySQLField*>(mysql_fetch_fields(*pResult));
return true;
}
void MySQLConnection::BeginTransaction()
{
Execute("START TRANSACTION");
}
void MySQLConnection::RollbackTransaction()
{
Execute("ROLLBACK");
}
void MySQLConnection::CommitTransaction()
{
Execute("COMMIT");
}
int MySQLConnection::ExecuteTransaction(std::shared_ptr<TransactionBase> transaction)
{
std::vector<SQLElementData> const& queries = transaction->m_queries;
if (queries.empty())
return -1;
BeginTransaction();
for (auto const& data : queries)
{
switch (data.type)
{
case SQL_ELEMENT_PREPARED:
{
PreparedStatementBase* stmt = nullptr;
try
{
stmt = std::get<PreparedStatementBase*>(data.element);
}
catch (const std::bad_variant_access& ex)
{
LOG_FATAL("sql.sql", "> PreparedStatementBase not found in SQLElementData. {}", ex.what());
ABORT();
}
ASSERT(stmt);
if (!Execute(stmt))
{
LOG_WARN("sql.sql", "Transaction aborted. {} queries not executed.", queries.size());
int errorCode = GetLastError();
RollbackTransaction();
return errorCode;
}
}
break;
case SQL_ELEMENT_RAW:
{
std::string sql{};
try
{
sql = std::get<std::string>(data.element);
}
catch (const std::bad_variant_access& ex)
{
LOG_FATAL("sql.sql", "> std::string not found in SQLElementData. {}", ex.what());
ABORT();
}
ASSERT(!sql.empty());
if (!Execute(sql))
{
LOG_WARN("sql.sql", "Transaction aborted. {} queries not executed.", queries.size());
uint32 errorCode = GetLastError();
RollbackTransaction();
return errorCode;
}
}
break;
}
}
// we might encounter errors during certain queries, and depending on the kind of error
// we might want to restart the transaction. So to prevent data loss, we only clean up when it's all done.
// This is done in calling functions DatabaseWorkerPool<T>::DirectCommitTransaction and TransactionTask::Execute,
// and not while iterating over every element.
CommitTransaction();
return 0;
}
std::size_t MySQLConnection::EscapeString(char* to, const char* from, std::size_t length)
{
return mysql_real_escape_string(m_Mysql, to, from, length);
}
void MySQLConnection::Ping()
{
mysql_ping(m_Mysql);
}
uint32 MySQLConnection::GetLastError()
{
return mysql_errno(m_Mysql);
}
bool MySQLConnection::LockIfReady()
{
return m_Mutex.try_lock();
}
void MySQLConnection::Unlock()
{
m_Mutex.unlock();
}
uint32 MySQLConnection::GetServerVersion() const
{
return mysql_get_server_version(m_Mysql);
}
std::string MySQLConnection::GetServerInfo() const
{
return mysql_get_server_info(m_Mysql);
}
MySQLPreparedStatement* MySQLConnection::GetPreparedStatement(uint32 index)
{
ASSERT(index < m_stmts.size(), "Tried to access invalid prepared statement index {} (max index {}) on database `{}`, connection type: {}",
index, m_stmts.size(), m_connectionInfo.database, (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
MySQLPreparedStatement* ret = m_stmts[index].get();
if (!ret)
LOG_ERROR("sql.sql", "Could not fetch prepared statement {} on database `{}`, connection type: {}.",
index, m_connectionInfo.database, (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
return ret;
}
void MySQLConnection::PrepareStatement(uint32 index, std::string_view sql, ConnectionFlags flags)
{
// Check if specified query should be prepared on this connection
// i.e. don't prepare async statements on synchronous connections
// to save memory that will not be used.
if (!(m_connectionFlags & flags))
{
m_stmts[index].reset();
return;
}
MYSQL_STMT* stmt = mysql_stmt_init(m_Mysql);
if (!stmt)
{
LOG_ERROR("sql.sql", "In mysql_stmt_init() id: {}, sql: \"{}\"", index, sql);
LOG_ERROR("sql.sql", "{}", mysql_error(m_Mysql));
m_prepareError = true;
}
else
{
if (mysql_stmt_prepare(stmt, std::string(sql).c_str(), static_cast<unsigned long>(sql.size())))
{
LOG_ERROR("sql.sql", "In mysql_stmt_prepare() id: {}, sql: \"{}\"", index, sql);
LOG_ERROR("sql.sql", "{}", mysql_stmt_error(stmt));
mysql_stmt_close(stmt);
m_prepareError = true;
}
else
m_stmts[index] = std::make_unique<MySQLPreparedStatement>(reinterpret_cast<MySQLStmt*>(stmt), sql);
}
}
PreparedResultSet* MySQLConnection::Query(PreparedStatementBase* stmt)
{
MySQLPreparedStatement* mysqlStmt = nullptr;
MySQLResult* result = nullptr;
uint64 rowCount = 0;
uint32 fieldCount = 0;
if (!_Query(stmt, &mysqlStmt, &result, &rowCount, &fieldCount))
return nullptr;
if (mysql_more_results(m_Mysql))
{
mysql_next_result(m_Mysql);
}
return new PreparedResultSet(mysqlStmt->GetSTMT(), result, rowCount, fieldCount);
}
bool MySQLConnection::_HandleMySQLErrno(uint32 errNo, char const* err, uint8 attempts /*= 5*/)
{
std::string str = "";
switch (errNo)
{
case CR_SERVER_GONE_ERROR:
case CR_SERVER_LOST:
case CR_SERVER_LOST_EXTENDED:
{
if (m_Mysql)
{
LOG_ERROR("sql.sql", "Lost the connection to the MySQL server!");
mysql_close(m_Mysql);
m_Mysql = nullptr;
}
[[fallthrough]];
}
case CR_CONN_HOST_ERROR:
{
LOG_INFO("sql.sql", "Attempting to reconnect to the MySQL server...");
m_reconnecting = true;
uint32 const lErrno = Open();
if (!lErrno)
{
// Don't remove 'this' pointer unless you want to skip loading all prepared statements...
if (!this->PrepareStatements())
{
str = "Could not re-prepare statements!";
LOG_FATAL("sql.sql", "{}", str);
std::this_thread::sleep_for(10s);
ABORT("{}\n\n[{}] {}", str, errNo, err);
}
LOG_INFO("sql.sql", "Successfully reconnected to {} @{}:{} ({}).",
m_connectionInfo.database, m_connectionInfo.host, m_connectionInfo.port_or_socket,
(m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
m_reconnecting = false;
return true;
}
if ((--attempts) == 0)
{
// Shut down the server when the mysql server isn't
// reachable for some time
str = "Failed to reconnect to the MySQL server, terminating the server to prevent data corruption!";
LOG_FATAL("sql.sql", "{}", str);
// We could also initiate a shutdown through using std::raise(SIGTERM)
std::this_thread::sleep_for(10s);
ABORT("{}\n\n[{}] {}", str, errNo, err);
}
else
{
// It's possible this attempted reconnect throws 2006 at us.
// To prevent crazy recursive calls, sleep here.
std::this_thread::sleep_for(3s); // Sleep 3 seconds
return _HandleMySQLErrno(lErrno, mysql_error(m_Mysql), attempts); // Call self (recursive)
}
[[fallthrough]];
}
case ER_LOCK_DEADLOCK:
return false; // Implemented in TransactionTask::Execute and DatabaseWorkerPool<T>::DirectCommitTransaction
// Query related errors - skip query
case ER_WRONG_VALUE_COUNT:
case ER_DUP_ENTRY:
return false;
// Outdated table or database structure - terminate core
case ER_BAD_FIELD_ERROR:
case ER_NO_SUCH_TABLE:
str = "Your database structure is not up to date. Please make sure you've executed all queries in the sql/updates folders.";
LOG_FATAL("sql.sql", "{}", str);
std::this_thread::sleep_for(10s);
ABORT("{}\n\n[{}] {}", str, errNo, err);
return false;
case ER_PARSE_ERROR:
str = "Error while parsing SQL. Core fix required.";
LOG_FATAL("sql.sql", "{}", str);
std::this_thread::sleep_for(10s);
ABORT("{}\n\n[{}] {}", str, errNo, err);
return false;
default:
LOG_ERROR("sql.sql", "Unhandled MySQL errno {}. Unexpected behaviour possible.", errNo);
return false;
}
}
@@ -0,0 +1,121 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _MYSQLCONNECTION_H
#define _MYSQLCONNECTION_H
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include <map>
#include <mutex>
#include <string>
#include <vector>
template <typename T>
class ProducerConsumerQueue;
class DatabaseWorker;
class MySQLPreparedStatement;
class SQLOperation;
enum ConnectionFlags
{
CONNECTION_ASYNC = 0x1,
CONNECTION_SYNCH = 0x2,
CONNECTION_BOTH = CONNECTION_ASYNC | CONNECTION_SYNCH
};
struct AC_DATABASE_API MySQLConnectionInfo
{
explicit MySQLConnectionInfo(std::string_view infoString);
std::string user;
std::string password;
std::string database;
std::string host;
std::string port_or_socket;
std::string ssl;
};
class AC_DATABASE_API MySQLConnection
{
template <class T>
friend class DatabaseWorkerPool;
friend class PingOperation;
public:
MySQLConnection(MySQLConnectionInfo& connInfo); //! Constructor for synchronous connections.
MySQLConnection(ProducerConsumerQueue<SQLOperation*>* queue, MySQLConnectionInfo& connInfo); //! Constructor for asynchronous connections.
virtual ~MySQLConnection();
virtual uint32 Open();
void Close();
bool PrepareStatements();
bool Execute(std::string_view sql);
bool Execute(PreparedStatementBase* stmt);
ResultSet* Query(std::string_view sql);
PreparedResultSet* Query(PreparedStatementBase* stmt);
bool _Query(std::string_view sql, MySQLResult** pResult, MySQLField** pFields, uint64* pRowCount, uint32* pFieldCount);
bool _Query(PreparedStatementBase* stmt, MySQLPreparedStatement** mysqlStmt, MySQLResult** pResult, uint64* pRowCount, uint32* pFieldCount);
void BeginTransaction();
void RollbackTransaction();
void CommitTransaction();
int ExecuteTransaction(std::shared_ptr<TransactionBase> transaction);
std::size_t EscapeString(char* to, const char* from, std::size_t length);
void Ping();
uint32 GetLastError();
protected:
/// Tries to acquire lock. If lock is acquired by another thread
/// the calling parent will just try another connection
bool LockIfReady();
/// Called by parent databasepool. Will let other threads access this connection
void Unlock();
[[nodiscard]] uint32 GetServerVersion() const;
[[nodiscard]] std::string GetServerInfo() const;
MySQLPreparedStatement* GetPreparedStatement(uint32 index);
void PrepareStatement(uint32 index, std::string_view sql, ConnectionFlags flags);
virtual void DoPrepareStatements() = 0;
virtual bool _HandleMySQLErrno(uint32 errNo, char const* err = "", uint8 attempts = 5);
typedef std::vector<std::unique_ptr<MySQLPreparedStatement>> PreparedStatementContainer;
PreparedStatementContainer m_stmts; //! PreparedStatements storage
bool m_reconnecting; //! Are we reconnecting?
bool m_prepareError; //! Was there any error while preparing statements?
MySQLHandle* m_Mysql; //! MySQL Handle.
private:
ProducerConsumerQueue<SQLOperation*>* m_queue; //! Queue shared with other asynchronous connections.
std::unique_ptr<DatabaseWorker> m_worker; //! Core worker task.
MySQLConnectionInfo& m_connectionInfo; //! Connection info (used for logging)
ConnectionFlags m_connectionFlags; //! Connection flags (for preparing relevant statements)
std::mutex m_Mutex;
MySQLConnection(MySQLConnection const& right) = delete;
MySQLConnection& operator=(MySQLConnection const& right) = delete;
};
#endif
+34
View File
@@ -0,0 +1,34 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MySQLHacks_h__
#define MySQLHacks_h__
#include "MySQLWorkaround.h"
#include <type_traits>
struct MySQLHandle : MYSQL { };
struct MySQLResult : MYSQL_RES { };
struct MySQLField : MYSQL_FIELD { };
struct MySQLBind : MYSQL_BIND { };
struct MySQLStmt : MYSQL_STMT { };
// mysql 8 removed my_bool typedef (it was char) and started using bools directly
// to maintain compatibility we use this trick to retrieve which type is being used
using MySQLBool = std::remove_pointer_t<decltype(std::declval<MYSQL_BIND>().is_null)>;
#endif // MySQLHacks_h__
@@ -0,0 +1,209 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "MySQLPreparedStatement.h"
#include "Errors.h"
#include "Log.h"
#include "MySQLHacks.h"
#include "PreparedStatement.h"
template<typename T>
struct MySQLType { };
template<> struct MySQLType<uint8> : std::integral_constant<enum_field_types, MYSQL_TYPE_TINY> { };
template<> struct MySQLType<uint16> : std::integral_constant<enum_field_types, MYSQL_TYPE_SHORT> { };
template<> struct MySQLType<uint32> : std::integral_constant<enum_field_types, MYSQL_TYPE_LONG> { };
template<> struct MySQLType<uint64> : std::integral_constant<enum_field_types, MYSQL_TYPE_LONGLONG> { };
template<> struct MySQLType<int8> : std::integral_constant<enum_field_types, MYSQL_TYPE_TINY> { };
template<> struct MySQLType<int16> : std::integral_constant<enum_field_types, MYSQL_TYPE_SHORT> { };
template<> struct MySQLType<int32> : std::integral_constant<enum_field_types, MYSQL_TYPE_LONG> { };
template<> struct MySQLType<int64> : std::integral_constant<enum_field_types, MYSQL_TYPE_LONGLONG> { };
template<> struct MySQLType<float> : std::integral_constant<enum_field_types, MYSQL_TYPE_FLOAT> { };
template<> struct MySQLType<double> : std::integral_constant<enum_field_types, MYSQL_TYPE_DOUBLE> { };
MySQLPreparedStatement::MySQLPreparedStatement(MySQLStmt* stmt, std::string_view queryString) :
m_stmt(nullptr),
m_Mstmt(stmt),
m_bind(nullptr),
m_queryString(std::string(queryString))
{
/// Initialize variable parameters
m_paramCount = mysql_stmt_param_count(stmt);
m_paramsSet.assign(m_paramCount, false);
m_bind = new MySQLBind[m_paramCount];
memset(m_bind, 0, sizeof(MySQLBind) * m_paramCount);
/// "If set to 1, causes mysql_stmt_store_result() to update the metadata MYSQL_FIELD->max_length value."
MySQLBool bool_tmp = MySQLBool(1);
mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &bool_tmp);
}
MySQLPreparedStatement::~MySQLPreparedStatement()
{
ClearParameters();
if (m_Mstmt->bind_result_done)
{
delete[] m_Mstmt->bind->length;
delete[] m_Mstmt->bind->is_null;
}
mysql_stmt_close(m_Mstmt);
delete[] m_bind;
}
void MySQLPreparedStatement::BindParameters(PreparedStatementBase* stmt)
{
m_stmt = stmt; // Cross reference them for debug output
uint8 pos = 0;
for (PreparedStatementData const& data : stmt->GetParameters())
{
std::visit([&](auto&& param)
{
SetParameter(pos, param);
}, data.data);
++pos;
}
#ifdef _DEBUG
if (pos < m_paramCount)
LOG_WARN("sql.sql", "[WARNING]: BindParameters() for statement {} did not bind all allocated parameters", stmt->GetIndex());
#endif
}
void MySQLPreparedStatement::ClearParameters()
{
for (uint32 i=0; i < m_paramCount; ++i)
{
delete m_bind[i].length;
m_bind[i].length = nullptr;
delete[] (char*)m_bind[i].buffer;
m_bind[i].buffer = nullptr;
m_paramsSet[i] = false;
}
}
static bool ParamenterIndexAssertFail(uint32 stmtIndex, uint8 index, uint32 paramCount)
{
LOG_ERROR("sql.driver", "Attempted to bind parameter {}{} on a PreparedStatement {} (statement has only {} parameters)",
uint32(index) + 1, (index == 1 ? "st" : (index == 2 ? "nd" : (index == 3 ? "rd" : "nd"))), stmtIndex, paramCount);
return false;
}
//- Bind on mysql level
void MySQLPreparedStatement::AssertValidIndex(uint8 index)
{
ASSERT(index < m_paramCount || ParamenterIndexAssertFail(m_stmt->GetIndex(), index, m_paramCount));
if (m_paramsSet[index])
LOG_ERROR("sql.sql", "[ERROR] Prepared Statement (id: {}) trying to bind value on already bound index ({}).", m_stmt->GetIndex(), index);
}
template<typename T>
void MySQLPreparedStatement::SetParameter(const uint8 index, T value)
{
AssertValidIndex(index);
m_paramsSet[index] = true;
MYSQL_BIND* param = &m_bind[index];
uint32 len = uint32(sizeof(T));
param->buffer_type = MySQLType<T>::value;
delete[] static_cast<char*>(param->buffer);
param->buffer = new char[len];
param->buffer_length = 0;
param->is_null_value = 0;
param->length = nullptr; // Only != NULL for strings
param->is_unsigned = std::is_unsigned_v<T>;
memcpy(param->buffer, &value, len);
}
void MySQLPreparedStatement::SetParameter(const uint8 index, bool value)
{
SetParameter(index, uint8(value ? 1 : 0));
}
void MySQLPreparedStatement::SetParameter(const uint8 index, std::nullptr_t /*value*/)
{
AssertValidIndex(index);
m_paramsSet[index] = true;
MYSQL_BIND* param = &m_bind[index];
param->buffer_type = MYSQL_TYPE_NULL;
delete[] static_cast<char*>(param->buffer);
param->buffer = nullptr;
param->buffer_length = 0;
param->is_null_value = 1;
delete param->length;
param->length = nullptr;
}
void MySQLPreparedStatement::SetParameter(uint8 index, std::string const& value)
{
AssertValidIndex(index);
m_paramsSet[index] = true;
MYSQL_BIND* param = &m_bind[index];
uint32 len = uint32(value.size());
param->buffer_type = MYSQL_TYPE_VAR_STRING;
delete[] static_cast<char*>(param->buffer);
param->buffer = new char[len];
param->buffer_length = len;
param->is_null_value = 0;
delete param->length;
param->length = new unsigned long(len);
memcpy(param->buffer, value.c_str(), len);
}
void MySQLPreparedStatement::SetParameter(uint8 index, std::vector<uint8> const& value)
{
AssertValidIndex(index);
m_paramsSet[index] = true;
MYSQL_BIND* param = &m_bind[index];
uint32 len = uint32(value.size());
param->buffer_type = MYSQL_TYPE_BLOB;
delete[] static_cast<char*>(param->buffer);
param->buffer = new char[len];
param->buffer_length = len;
param->is_null_value = 0;
delete param->length;
param->length = new unsigned long(len);
memcpy(param->buffer, value.data(), len);
}
std::string MySQLPreparedStatement::getQueryString() const
{
std::string queryString(m_queryString);
std::size_t pos = 0;
for (PreparedStatementData const& data : m_stmt->GetParameters())
{
pos = queryString.find('?', pos);
std::string replaceStr = std::visit([&](auto&& data)
{
return PreparedStatementData::ToString(data);
}, data.data);
queryString.replace(pos, 1, replaceStr);
pos += replaceStr.length();
}
return queryString;
}
@@ -0,0 +1,73 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MySQLPreparedStatement_h__
#define MySQLPreparedStatement_h__
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include "MySQLWorkaround.h"
#include <string>
#include <vector>
class MySQLConnection;
class PreparedStatementBase;
//- Class of which the instances are unique per MySQLConnection
//- access to these class objects is only done when a prepared statement task
//- is executed.
class AC_DATABASE_API MySQLPreparedStatement
{
friend class MySQLConnection;
friend class PreparedStatementBase;
public:
MySQLPreparedStatement(MySQLStmt* stmt, std::string_view queryString);
~MySQLPreparedStatement();
void BindParameters(PreparedStatementBase* stmt);
uint32 GetParameterCount() const { return m_paramCount; }
protected:
void SetParameter(const uint8 index, bool value);
void SetParameter(const uint8 index, std::nullptr_t /*value*/);
void SetParameter(const uint8 index, std::string const& value);
void SetParameter(const uint8 index, std::vector<uint8> const& value);
template<typename T>
void SetParameter(const uint8 index, T value);
MySQLStmt* GetSTMT() { return m_Mstmt; }
MySQLBind* GetBind() { return m_bind; }
PreparedStatementBase* m_stmt;
void ClearParameters();
void AssertValidIndex(const uint8 index);
std::string getQueryString() const;
private:
MySQLStmt* m_Mstmt;
uint32 m_paramCount;
std::vector<bool> m_paramsSet;
MySQLBind* m_bind;
std::string m_queryString{};
MySQLPreparedStatement(MySQLPreparedStatement const& right) = delete;
MySQLPreparedStatement& operator=(MySQLPreparedStatement const& right) = delete;
};
#endif // MySQLPreparedStatement_h__
@@ -0,0 +1,34 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "MySQLThreading.h"
#include "MySQLWorkaround.h"
void MySQL::Library_Init()
{
mysql_library_init(-1, nullptr, nullptr);
}
void MySQL::Library_End()
{
mysql_library_end();
}
uint32 MySQL::GetLibraryVersion()
{
return MYSQL_VERSION_ID;
}
@@ -0,0 +1,30 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _MYSQLTHREADING_H
#define _MYSQLTHREADING_H
#include "Define.h"
namespace MySQL
{
AC_DATABASE_API void Library_Init();
AC_DATABASE_API void Library_End();
AC_DATABASE_API uint32 GetLibraryVersion();
}
#endif
@@ -0,0 +1,21 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifdef _WIN32 // hack for broken mysql.h not including the correct winsock header for SOCKET definition, fixed in 5.7
#include <winsock2.h>
#endif
#include <mysql.h>
@@ -0,0 +1,131 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "PreparedStatement.h"
#include "Errors.h"
#include "Log.h"
#include "MySQLConnection.h"
#include "MySQLWorkaround.h"
#include "QueryResult.h"
PreparedStatementBase::PreparedStatementBase(uint32 index, uint8 capacity) :
m_index(index),
statement_data(capacity) { }
PreparedStatementBase::~PreparedStatementBase() { }
//- Bind to buffer
template<typename T>
Acore::Types::is_non_string_view_v<T> PreparedStatementBase::SetValidData(const uint8 index, T const& value)
{
ASSERT(index < statement_data.size());
statement_data[index].data.emplace<T>(value);
}
// Non template functions
void PreparedStatementBase::SetValidData(const uint8 index)
{
ASSERT(index < statement_data.size());
statement_data[index].data.emplace<std::nullptr_t>(nullptr);
}
void PreparedStatementBase::SetValidData(const uint8 index, std::string_view value)
{
ASSERT(index < statement_data.size());
statement_data[index].data.emplace<std::string>(value);
}
template void PreparedStatementBase::SetValidData(const uint8 index, uint8 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, int8 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, uint16 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, int16 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, uint32 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, int32 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, uint64 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, int64 const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, bool const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, float const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, std::string const& value);
template void PreparedStatementBase::SetValidData(const uint8 index, std::vector<uint8> const& value);
//- Execution
PreparedStatementTask::PreparedStatementTask(PreparedStatementBase* stmt, bool async) :
m_stmt(stmt),
m_result(nullptr)
{
m_has_result = async; // If it's async, then there's a result
if (async)
m_result = new PreparedQueryResultPromise();
}
PreparedStatementTask::~PreparedStatementTask()
{
delete m_stmt;
if (m_has_result && m_result)
delete m_result;
}
bool PreparedStatementTask::Execute()
{
if (m_has_result)
{
PreparedResultSet* result = m_conn->Query(m_stmt);
if (!result || !result->GetRowCount())
{
delete result;
m_result->set_value(PreparedQueryResult(nullptr));
return false;
}
m_result->set_value(PreparedQueryResult(result));
return true;
}
return m_conn->Execute(m_stmt);
}
template<typename T>
std::string PreparedStatementData::ToString(T value)
{
return Acore::StringFormat("{}", value);
}
template<>
std::string PreparedStatementData::ToString(std::vector<uint8> /*value*/)
{
return "BINARY";
}
template std::string PreparedStatementData::ToString(uint8);
template std::string PreparedStatementData::ToString(uint16);
template std::string PreparedStatementData::ToString(uint32);
template std::string PreparedStatementData::ToString(uint64);
template std::string PreparedStatementData::ToString(int8);
template std::string PreparedStatementData::ToString(int16);
template std::string PreparedStatementData::ToString(int32);
template std::string PreparedStatementData::ToString(int64);
template std::string PreparedStatementData::ToString(std::string);
template std::string PreparedStatementData::ToString(float);
template std::string PreparedStatementData::ToString(double);
template std::string PreparedStatementData::ToString(bool);
std::string PreparedStatementData::ToString(std::nullptr_t /*value*/)
{
return "NULL";
}
@@ -0,0 +1,184 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _PREPAREDSTATEMENT_H
#define _PREPAREDSTATEMENT_H
#include "Define.h"
#include "Duration.h"
#include "SQLOperation.h"
#include <future>
#include <tuple>
#include <variant>
#include <vector>
namespace Acore::Types
{
template <typename T>
using is_default = std::enable_if_t<std::is_arithmetic_v<T> || std::is_same_v<std::vector<uint8>, T>>;
template <typename T>
using is_enum_v = std::enable_if_t<std::is_enum_v<T>>;
template <typename T>
using is_non_string_view_v = std::enable_if_t<!std::is_base_of_v<std::string_view, T>>;
}
struct PreparedStatementData
{
std::variant<
bool,
uint8,
uint16,
uint32,
uint64,
int8,
int16,
int32,
int64,
float,
double,
std::string,
std::vector<uint8>,
std::nullptr_t
> data;
template<typename T>
static std::string ToString(T value);
static std::string ToString(std::nullptr_t /*value*/);
};
//- Upper-level class that is used in code
class AC_DATABASE_API PreparedStatementBase
{
friend class PreparedStatementTask;
public:
explicit PreparedStatementBase(uint32 index, uint8 capacity);
virtual ~PreparedStatementBase();
// Set numerlic and default binary
template<typename T>
inline Acore::Types::is_default<T> SetData(const uint8 index, T value)
{
SetValidData(index, value);
}
// Set enums
template<typename T>
inline Acore::Types::is_enum_v<T> SetData(const uint8 index, T value)
{
SetValidData(index, std::underlying_type_t<T>(value));
}
// Set string_view
inline void SetData(const uint8 index, std::string_view value)
{
SetValidData(index, value);
}
// Set nullptr
inline void SetData(const uint8 index, std::nullptr_t = nullptr)
{
SetValidData(index);
}
// Set non default binary
template<std::size_t Size>
inline void SetData(const uint8 index, std::array<uint8, Size> const& value)
{
std::vector<uint8> vec(value.begin(), value.end());
SetValidData(index, vec);
}
// Set duration
template<class _Rep, class _Period>
inline void SetData(const uint8 index, std::chrono::duration<_Rep, _Period> const& value, bool convertToUin32 = true)
{
SetValidData(index, convertToUin32 ? static_cast<uint32>(value.count()) : value.count());
}
// Set all
template <typename... Args>
inline void SetArguments(Args&&... args)
{
SetDataTuple(std::make_tuple(std::forward<Args>(args)...));
}
[[nodiscard]] uint32 GetIndex() const { return m_index; }
[[nodiscard]] std::vector<PreparedStatementData> const& GetParameters() const { return statement_data; }
protected:
template<typename T>
Acore::Types::is_non_string_view_v<T> SetValidData(const uint8 index, T const& value);
void SetValidData(const uint8 index);
void SetValidData(const uint8 index, std::string_view value);
template<typename... Ts>
inline void SetDataTuple(std::tuple<Ts...> const& argsList)
{
std::apply
(
[this](Ts const&... arguments)
{
uint8 index{ 0 };
((SetData(index, arguments), index++), ...);
}, argsList
);
}
uint32 m_index;
//- Buffer of parameters, not tied to MySQL in any way yet
std::vector<PreparedStatementData> statement_data;
PreparedStatementBase(PreparedStatementBase const& right) = delete;
PreparedStatementBase& operator=(PreparedStatementBase const& right) = delete;
};
template<typename T>
class PreparedStatement : public PreparedStatementBase
{
public:
explicit PreparedStatement(uint32 index, uint8 capacity) : PreparedStatementBase(index, capacity)
{
}
private:
PreparedStatement(PreparedStatement const& right) = delete;
PreparedStatement& operator=(PreparedStatement const& right) = delete;
};
//- Lower-level class, enqueuable operation
class AC_DATABASE_API PreparedStatementTask : public SQLOperation
{
public:
PreparedStatementTask(PreparedStatementBase* stmt, bool async = false);
~PreparedStatementTask() override;
bool Execute() override;
PreparedQueryResultFuture GetFuture() { return m_result->get_future(); }
protected:
PreparedStatementBase* m_stmt;
bool m_has_result;
PreparedQueryResultPromise* m_result;
};
#endif
@@ -0,0 +1,229 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "QueryCallback.h"
#include "Duration.h"
#include "Errors.h"
template<typename T, typename... Args>
inline void Construct(T& t, Args&&... args)
{
new (&t) T(std::forward<Args>(args)...);
}
template<typename T>
inline void Destroy(T& t)
{
t.~T();
}
template<typename T>
inline void ConstructActiveMember(T* obj)
{
if (!obj->_isPrepared)
Construct(obj->_string);
else
Construct(obj->_prepared);
}
template<typename T>
inline void DestroyActiveMember(T* obj)
{
if (!obj->_isPrepared)
Destroy(obj->_string);
else
Destroy(obj->_prepared);
}
template<typename T>
inline void MoveFrom(T* to, T&& from)
{
ASSERT(to->_isPrepared == from._isPrepared);
if (!to->_isPrepared)
to->_string = std::move(from._string);
else
to->_prepared = std::move(from._prepared);
}
struct QueryCallback::QueryCallbackData
{
public:
friend class QueryCallback;
QueryCallbackData(std::function<void(QueryCallback&, QueryResult)>&& callback) : _string(std::move(callback)), _isPrepared(false) { }
QueryCallbackData(std::function<void(QueryCallback&, PreparedQueryResult)>&& callback) : _prepared(std::move(callback)), _isPrepared(true) { }
QueryCallbackData(QueryCallbackData&& right) noexcept
{
_isPrepared = right._isPrepared;
ConstructActiveMember(this);
MoveFrom(this, std::move(right));
}
QueryCallbackData& operator=(QueryCallbackData&& right) noexcept
{
if (this != &right)
{
if (_isPrepared != right._isPrepared)
{
DestroyActiveMember(this);
_isPrepared = right._isPrepared;
ConstructActiveMember(this);
}
MoveFrom(this, std::move(right));
}
return *this;
}
~QueryCallbackData() { DestroyActiveMember(this); }
private:
QueryCallbackData(QueryCallbackData const&) = delete;
QueryCallbackData& operator=(QueryCallbackData const&) = delete;
template<typename T> friend void ConstructActiveMember(T* obj);
template<typename T> friend void DestroyActiveMember(T* obj);
template<typename T> friend void MoveFrom(T* to, T&& from);
union
{
std::function<void(QueryCallback&, QueryResult)> _string;
std::function<void(QueryCallback&, PreparedQueryResult)> _prepared;
};
bool _isPrepared;
};
// Not using initialization lists to work around segmentation faults when compiling with clang without precompiled headers
QueryCallback::QueryCallback(QueryResultFuture&& result)
{
_isPrepared = false;
Construct(_string, std::move(result));
}
QueryCallback::QueryCallback(PreparedQueryResultFuture&& result)
{
_isPrepared = true;
Construct(_prepared, std::move(result));
}
QueryCallback::QueryCallback(QueryCallback&& right) noexcept
{
_isPrepared = right._isPrepared;
ConstructActiveMember(this);
MoveFrom(this, std::move(right));
_callbacks = std::move(right._callbacks);
}
QueryCallback& QueryCallback::operator=(QueryCallback&& right) noexcept
{
if (this != &right)
{
if (_isPrepared != right._isPrepared)
{
DestroyActiveMember(this);
_isPrepared = right._isPrepared;
ConstructActiveMember(this);
}
MoveFrom(this, std::move(right));
_callbacks = std::move(right._callbacks);
}
return *this;
}
QueryCallback::~QueryCallback()
{
DestroyActiveMember(this);
}
QueryCallback&& QueryCallback::WithCallback(std::function<void(QueryResult)>&& callback)
{
return WithChainingCallback([callback](QueryCallback& /*this*/, QueryResult result) { callback(std::move(result)); });
}
QueryCallback&& QueryCallback::WithPreparedCallback(std::function<void(PreparedQueryResult)>&& callback)
{
return WithChainingPreparedCallback([callback](QueryCallback& /*this*/, PreparedQueryResult result) { callback(std::move(result)); });
}
QueryCallback&& QueryCallback::WithChainingCallback(std::function<void(QueryCallback&, QueryResult)>&& callback)
{
ASSERT(!_callbacks.empty() || !_isPrepared, "Attempted to set callback function for string query on a prepared async query");
_callbacks.emplace(std::move(callback));
return std::move(*this);
}
QueryCallback&& QueryCallback::WithChainingPreparedCallback(std::function<void(QueryCallback&, PreparedQueryResult)>&& callback)
{
ASSERT(!_callbacks.empty() || _isPrepared, "Attempted to set callback function for prepared query on a string async query");
_callbacks.emplace(std::move(callback));
return std::move(*this);
}
void QueryCallback::SetNextQuery(QueryCallback&& next)
{
MoveFrom(this, std::move(next));
}
bool QueryCallback::InvokeIfReady()
{
QueryCallbackData& callback = _callbacks.front();
auto checkStateAndReturnCompletion = [this]()
{
_callbacks.pop();
bool hasNext = !_isPrepared ? _string.valid() : _prepared.valid();
if (_callbacks.empty())
{
ASSERT(!hasNext);
return true;
}
// abort chain
if (!hasNext)
return true;
ASSERT(_isPrepared == _callbacks.front()._isPrepared);
return false;
};
if (!_isPrepared)
{
if (_string.valid() && _string.wait_for(0s) == std::future_status::ready)
{
QueryResultFuture f(std::move(_string));
std::function<void(QueryCallback&, QueryResult)> cb(std::move(callback._string));
cb(*this, f.get());
return checkStateAndReturnCompletion();
}
}
else
{
if (_prepared.valid() && _prepared.wait_for(0s) == std::future_status::ready)
{
PreparedQueryResultFuture f(std::move(_prepared));
std::function<void(QueryCallback&, PreparedQueryResult)> cb(std::move(callback._prepared));
cb(*this, f.get());
return checkStateAndReturnCompletion();
}
}
return false;
}
@@ -0,0 +1,70 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _QUERY_CALLBACK_H
#define _QUERY_CALLBACK_H
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include <functional>
#include <future>
#include <list>
#include <queue>
class AC_DATABASE_API QueryCallback
{
public:
explicit QueryCallback(QueryResultFuture&& result);
explicit QueryCallback(PreparedQueryResultFuture&& result);
QueryCallback(QueryCallback&& right) noexcept;
QueryCallback& operator=(QueryCallback&& right) noexcept;
~QueryCallback();
QueryCallback&& WithCallback(std::function<void(QueryResult)>&& callback);
QueryCallback&& WithPreparedCallback(std::function<void(PreparedQueryResult)>&& callback);
QueryCallback&& WithChainingCallback(std::function<void(QueryCallback&, QueryResult)>&& callback);
QueryCallback&& WithChainingPreparedCallback(std::function<void(QueryCallback&, PreparedQueryResult)>&& callback);
// Moves std::future from next to this object
void SetNextQuery(QueryCallback&& next);
// returns true when completed
bool InvokeIfReady();
private:
QueryCallback(QueryCallback const& right) = delete;
QueryCallback& operator=(QueryCallback const& right) = delete;
template<typename T> friend void ConstructActiveMember(T* obj);
template<typename T> friend void DestroyActiveMember(T* obj);
template<typename T> friend void MoveFrom(T* to, T&& from);
union
{
QueryResultFuture _string;
PreparedQueryResultFuture _prepared;
};
bool _isPrepared;
struct QueryCallbackData;
std::queue<QueryCallbackData, std::list<QueryCallbackData>> _callbacks;
};
#endif // _QUERY_CALLBACK_H
@@ -0,0 +1,97 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "QueryHolder.h"
#include "Errors.h"
#include "Log.h"
#include "MySQLConnection.h"
#include "PreparedStatement.h"
#include "QueryResult.h"
bool SQLQueryHolderBase::SetPreparedQueryImpl(std::size_t index, PreparedStatementBase* stmt)
{
if (m_queries.size() <= index)
{
LOG_ERROR("sql.sql", "Query index ({}) out of range (size: {}) for prepared statement", uint32(index), (uint32)m_queries.size());
return false;
}
m_queries[index].first = stmt;
return true;
}
PreparedQueryResult SQLQueryHolderBase::GetPreparedResult(std::size_t index) const
{
// Don't call to this function if the index is of a prepared statement
ASSERT(index < m_queries.size(), "Query holder result index out of range, tried to access index {} but there are only {} results",
index, m_queries.size());
return m_queries[index].second;
}
void SQLQueryHolderBase::SetPreparedResult(std::size_t index, PreparedResultSet* result)
{
if (result && !result->GetRowCount())
{
delete result;
result = nullptr;
}
/// store the result in the holder
if (index < m_queries.size())
m_queries[index].second = PreparedQueryResult(result);
}
SQLQueryHolderBase::~SQLQueryHolderBase()
{
for (std::pair<PreparedStatementBase*, PreparedQueryResult>& query : m_queries)
{
/// if the result was never used, free the resources
/// results used already (getresult called) are expected to be deleted
delete query.first;
}
}
void SQLQueryHolderBase::SetSize(std::size_t size)
{
/// to optimize push_back, reserve the number of queries about to be executed
m_queries.resize(size);
}
SQLQueryHolderTask::~SQLQueryHolderTask() = default;
bool SQLQueryHolderTask::Execute()
{
/// execute all queries in the holder and pass the results
for (std::size_t i = 0; i < m_holder->m_queries.size(); ++i)
if (PreparedStatementBase* stmt = m_holder->m_queries[i].first)
m_holder->SetPreparedResult(i, m_conn->Query(stmt));
m_result.set_value();
return true;
}
bool SQLQueryHolderCallback::InvokeIfReady()
{
if (m_future.valid() && m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
m_callback(*m_holder);
return true;
}
return false;
}
@@ -0,0 +1,89 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _QUERYHOLDER_H
#define _QUERYHOLDER_H
#include "SQLOperation.h"
#include <vector>
class AC_DATABASE_API SQLQueryHolderBase
{
friend class SQLQueryHolderTask;
public:
SQLQueryHolderBase() = default;
virtual ~SQLQueryHolderBase();
void SetSize(std::size_t size);
PreparedQueryResult GetPreparedResult(std::size_t index) const;
void SetPreparedResult(std::size_t index, PreparedResultSet* result);
protected:
bool SetPreparedQueryImpl(std::size_t index, PreparedStatementBase* stmt);
private:
std::vector<std::pair<PreparedStatementBase*, PreparedQueryResult>> m_queries;
};
template<typename T>
class SQLQueryHolder : public SQLQueryHolderBase
{
public:
bool SetPreparedQuery(std::size_t index, PreparedStatement<T>* stmt)
{
return SetPreparedQueryImpl(index, stmt);
}
};
class AC_DATABASE_API SQLQueryHolderTask : public SQLOperation
{
public:
explicit SQLQueryHolderTask(std::shared_ptr<SQLQueryHolderBase> holder)
: m_holder(std::move(holder)) { }
~SQLQueryHolderTask();
bool Execute() override;
QueryResultHolderFuture GetFuture() { return m_result.get_future(); }
private:
std::shared_ptr<SQLQueryHolderBase> m_holder;
QueryResultHolderPromise m_result;
};
class AC_DATABASE_API SQLQueryHolderCallback
{
public:
SQLQueryHolderCallback(std::shared_ptr<SQLQueryHolderBase>&& holder, QueryResultHolderFuture&& future)
: m_holder(std::move(holder)), m_future(std::move(future)) { }
SQLQueryHolderCallback(SQLQueryHolderCallback&&) = default;
SQLQueryHolderCallback& operator=(SQLQueryHolderCallback&&) = default;
void AfterComplete(std::function<void(SQLQueryHolderBase const&)> callback) &
{
m_callback = std::move(callback);
}
bool InvokeIfReady();
std::shared_ptr<SQLQueryHolderBase> m_holder;
QueryResultHolderFuture m_future;
std::function<void(SQLQueryHolderBase const&)> m_callback;
};
#endif
@@ -0,0 +1,436 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "QueryResult.h"
#include "Errors.h"
#include "Field.h"
#include "Log.h"
#include "MySQLHacks.h"
#include "MySQLWorkaround.h"
namespace
{
static uint32 SizeForType(MYSQL_FIELD* field)
{
switch (field->type)
{
case MYSQL_TYPE_NULL:
return 0;
case MYSQL_TYPE_TINY:
return 1;
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_SHORT:
return 2;
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_FLOAT:
return 4;
case MYSQL_TYPE_DOUBLE:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_BIT:
return 8;
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATETIME:
return sizeof(MYSQL_TIME);
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_VAR_STRING:
return field->max_length + 1;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return 64;
case MYSQL_TYPE_GEOMETRY:
/*
Following types are not sent over the wire:
MYSQL_TYPE_ENUM:
MYSQL_TYPE_SET:
*/
default:
LOG_WARN("sql.sql", "SQL::SizeForType(): invalid field type {}", uint32(field->type));
return 0;
}
}
DatabaseFieldTypes MysqlTypeToFieldType(enum_field_types type)
{
switch (type)
{
case MYSQL_TYPE_NULL:
return DatabaseFieldTypes::Null;
case MYSQL_TYPE_TINY:
return DatabaseFieldTypes::Int8;
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_SHORT:
return DatabaseFieldTypes::Int16;
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
return DatabaseFieldTypes::Int32;
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_BIT:
return DatabaseFieldTypes::Int64;
case MYSQL_TYPE_FLOAT:
return DatabaseFieldTypes::Float;
case MYSQL_TYPE_DOUBLE:
return DatabaseFieldTypes::Double;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return DatabaseFieldTypes::Decimal;
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATETIME:
return DatabaseFieldTypes::Date;
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_VAR_STRING:
return DatabaseFieldTypes::Binary;
default:
LOG_WARN("sql.sql", "MysqlTypeToFieldType(): invalid field type {}", uint32(type));
break;
}
return DatabaseFieldTypes::Null;
}
static std::string FieldTypeToString(enum_field_types type)
{
switch (type)
{
case MYSQL_TYPE_BIT: return "BIT";
case MYSQL_TYPE_BLOB: return "BLOB";
case MYSQL_TYPE_DATE: return "DATE";
case MYSQL_TYPE_DATETIME: return "DATETIME";
case MYSQL_TYPE_NEWDECIMAL: return "NEWDECIMAL";
case MYSQL_TYPE_DECIMAL: return "DECIMAL";
case MYSQL_TYPE_DOUBLE: return "DOUBLE";
case MYSQL_TYPE_ENUM: return "ENUM";
case MYSQL_TYPE_FLOAT: return "FLOAT";
case MYSQL_TYPE_GEOMETRY: return "GEOMETRY";
case MYSQL_TYPE_INT24: return "INT24";
case MYSQL_TYPE_LONG: return "LONG";
case MYSQL_TYPE_LONGLONG: return "LONGLONG";
case MYSQL_TYPE_LONG_BLOB: return "LONG_BLOB";
case MYSQL_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB";
case MYSQL_TYPE_NEWDATE: return "NEWDATE";
case MYSQL_TYPE_NULL: return "NULL";
case MYSQL_TYPE_SET: return "SET";
case MYSQL_TYPE_SHORT: return "SHORT";
case MYSQL_TYPE_STRING: return "STRING";
case MYSQL_TYPE_TIME: return "TIME";
case MYSQL_TYPE_TIMESTAMP: return "TIMESTAMP";
case MYSQL_TYPE_TINY: return "TINY";
case MYSQL_TYPE_TINY_BLOB: return "TINY_BLOB";
case MYSQL_TYPE_VAR_STRING: return "VAR_STRING";
case MYSQL_TYPE_YEAR: return "YEAR";
default: return "-Unknown-";
}
}
void InitializeDatabaseFieldMetadata(QueryResultFieldMetadata* meta, MySQLField const* field, uint32 fieldIndex)
{
meta->TableName = field->org_table;
meta->TableAlias = field->table;
meta->Name = field->org_name;
meta->Alias = field->name;
meta->TypeName = FieldTypeToString(field->type);
meta->Index = fieldIndex;
meta->Type = MysqlTypeToFieldType(field->type);
}
}
ResultSet::ResultSet(MySQLResult* result, MySQLField* fields, uint64 rowCount, uint32 fieldCount) :
_rowCount(rowCount),
_fieldCount(fieldCount),
_result(result),
_fields(fields)
{
_fieldMetadata.resize(_fieldCount);
_currentRow = new Field[_fieldCount];
for (uint32 i = 0; i < _fieldCount; i++)
{
InitializeDatabaseFieldMetadata(&_fieldMetadata[i], &_fields[i], i);
_currentRow[i].SetMetadata(&_fieldMetadata[i]);
}
}
ResultSet::~ResultSet()
{
CleanUp();
}
bool ResultSet::NextRow()
{
MYSQL_ROW row;
if (!_result)
return false;
row = mysql_fetch_row(_result);
if (!row)
{
CleanUp();
return false;
}
unsigned long* lengths = mysql_fetch_lengths(_result);
if (!lengths)
{
LOG_WARN("sql.sql", "{}:mysql_fetch_lengths, cannot retrieve value lengths. Error {}.", __FUNCTION__, mysql_error(_result->handle));
CleanUp();
return false;
}
for (uint32 i = 0; i < _fieldCount; i++)
_currentRow[i].SetStructuredValue(row[i], lengths[i]);
return true;
}
std::string ResultSet::GetFieldName(uint32 index) const
{
ASSERT(index < _fieldCount);
return _fields[index].name;
}
void ResultSet::CleanUp()
{
if (_currentRow)
{
delete[] _currentRow;
_currentRow = nullptr;
}
if (_result)
{
mysql_free_result(_result);
_result = nullptr;
}
}
Field const& ResultSet::operator[](std::size_t index) const
{
ASSERT(index < _fieldCount);
return _currentRow[index];
}
void ResultSet::AssertRows(std::size_t sizeRows)
{
ASSERT(sizeRows == _fieldCount);
}
PreparedResultSet::PreparedResultSet(MySQLStmt* stmt, MySQLResult* result, uint64 rowCount, uint32 fieldCount) :
m_rowCount(rowCount),
m_rowPosition(0),
m_fieldCount(fieldCount),
m_rBind(nullptr),
m_stmt(stmt),
m_metadataResult(result)
{
if (!m_metadataResult)
return;
if (m_stmt->bind_result_done)
{
delete[] m_stmt->bind->length;
delete[] m_stmt->bind->is_null;
}
m_rBind = new MySQLBind[m_fieldCount];
//- for future readers wondering where this is freed - mysql_stmt_bind_result moves pointers to these
// from m_rBind to m_stmt->bind and it is later freed by the `if (m_stmt->bind_result_done)` block just above here
// MYSQL_STMT lifetime is equal to connection lifetime
MySQLBool* m_isNull = new MySQLBool[m_fieldCount];
unsigned long* m_length = new unsigned long[m_fieldCount];
memset(m_isNull, 0, sizeof(MySQLBool) * m_fieldCount);
memset(m_rBind, 0, sizeof(MySQLBind) * m_fieldCount);
memset(m_length, 0, sizeof(unsigned long) * m_fieldCount);
//- This is where we store the (entire) resultset
if (mysql_stmt_store_result(m_stmt))
{
LOG_WARN("sql.sql", "{}:mysql_stmt_store_result, cannot bind result from MySQL server. Error: {}", __FUNCTION__, mysql_stmt_error(m_stmt));
delete[] m_rBind;
delete[] m_isNull;
delete[] m_length;
return;
}
m_rowCount = mysql_stmt_num_rows(m_stmt);
//- This is where we prepare the buffer based on metadata
MySQLField* field = reinterpret_cast<MySQLField*>(mysql_fetch_fields(m_metadataResult));
m_fieldMetadata.resize(m_fieldCount);
std::size_t rowSize = 0;
for (uint32 i = 0; i < m_fieldCount; ++i)
{
uint32 size = SizeForType(&field[i]);
rowSize += size;
InitializeDatabaseFieldMetadata(&m_fieldMetadata[i], &field[i], i);
m_rBind[i].buffer_type = field[i].type;
m_rBind[i].buffer_length = size;
m_rBind[i].length = &m_length[i];
m_rBind[i].is_null = &m_isNull[i];
m_rBind[i].error = nullptr;
m_rBind[i].is_unsigned = field[i].flags & UNSIGNED_FLAG;
}
char* dataBuffer = new char[rowSize * m_rowCount];
for (uint32 i = 0, offset = 0; i < m_fieldCount; ++i)
{
m_rBind[i].buffer = dataBuffer + offset;
offset += m_rBind[i].buffer_length;
}
//- This is where we bind the bind the buffer to the statement
if (mysql_stmt_bind_result(m_stmt, m_rBind))
{
LOG_WARN("sql.sql", "{}:mysql_stmt_bind_result, cannot bind result from MySQL server. Error: {}", __FUNCTION__, mysql_stmt_error(m_stmt));
mysql_stmt_free_result(m_stmt);
CleanUp();
delete[] m_isNull;
delete[] m_length;
return;
}
m_rows.resize(uint32(m_rowCount) * m_fieldCount);
while (_NextRow())
{
for (uint32 fIndex = 0; fIndex < m_fieldCount; ++fIndex)
{
m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetMetadata(&m_fieldMetadata[fIndex]);
unsigned long buffer_length = m_rBind[fIndex].buffer_length;
unsigned long fetched_length = *m_rBind[fIndex].length;
if (!*m_rBind[fIndex].is_null)
{
void* buffer = m_stmt->bind[fIndex].buffer;
switch (m_rBind[fIndex].buffer_type)
{
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_VAR_STRING:
// warning - the string will not be null-terminated if there is no space for it in the buffer
// when mysql_stmt_fetch returned MYSQL_DATA_TRUNCATED
// we cannot blindly null-terminate the data either as it may be retrieved as binary blob and not specifically a string
// in this case using Field::GetCString will result in garbage
/// @todo: remove Field::GetCString and use std::string_view in C++17
if (fetched_length < buffer_length)
*((char*)buffer + fetched_length) = '\0';
break;
default:
break;
}
m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetByteValue((char const*)buffer, fetched_length);
// move buffer pointer to next part
m_stmt->bind[fIndex].buffer = (char*)buffer + rowSize;
}
else
{
m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetByteValue(nullptr, *m_rBind[fIndex].length);
}
}
m_rowPosition++;
}
m_rowPosition = 0;
/// All data is buffered, let go of mysql c api structures
mysql_stmt_free_result(m_stmt);
}
PreparedResultSet::~PreparedResultSet()
{
CleanUp();
}
bool PreparedResultSet::NextRow()
{
/// Only updates the m_rowPosition so upper level code knows in which element
/// of the rows vector to look
if (++m_rowPosition >= m_rowCount)
return false;
return true;
}
bool PreparedResultSet::_NextRow()
{
/// Only called in low-level code, namely the constructor
/// Will iterate over every row of data and buffer it
if (m_rowPosition >= m_rowCount)
return false;
int retval = mysql_stmt_fetch(m_stmt);
return retval == 0 || retval == MYSQL_DATA_TRUNCATED;
}
Field* PreparedResultSet::Fetch() const
{
ASSERT(m_rowPosition < m_rowCount);
return const_cast<Field*>(&m_rows[uint32(m_rowPosition) * m_fieldCount]);
}
Field const& PreparedResultSet::operator[](std::size_t index) const
{
ASSERT(m_rowPosition < m_rowCount);
ASSERT(index < m_fieldCount);
return m_rows[uint32(m_rowPosition) * m_fieldCount + index];
}
void PreparedResultSet::CleanUp()
{
if (m_metadataResult)
mysql_free_result(m_metadataResult);
if (m_rBind)
{
delete[](char*)m_rBind->buffer;
delete[] m_rBind;
m_rBind = nullptr;
}
}
void PreparedResultSet::AssertRows(std::size_t sizeRows)
{
ASSERT(m_rowPosition < m_rowCount);
ASSERT(sizeRows == m_fieldCount, "> Tuple size != count fields");
}
+151
View File
@@ -0,0 +1,151 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef QUERYRESULT_H
#define QUERYRESULT_H
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include "Field.h"
#include <tuple>
#include <vector>
template<typename T>
struct ResultIterator
{
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
explicit ResultIterator(pointer ptr) : _ptr(ptr) { }
reference operator*() const { return *_ptr; }
pointer operator->() { return _ptr; }
ResultIterator& operator++() { if (!_ptr->NextRow()) _ptr = nullptr; return *this; }
bool operator!=(const ResultIterator& right) { return _ptr != right._ptr; }
private:
pointer _ptr;
};
class AC_DATABASE_API ResultSet
{
public:
ResultSet(MySQLResult* result, MySQLField* fields, uint64 rowCount, uint32 fieldCount);
~ResultSet();
bool NextRow();
[[nodiscard]] uint64 GetRowCount() const { return _rowCount; }
[[nodiscard]] uint32 GetFieldCount() const { return _fieldCount; }
[[nodiscard]] std::string GetFieldName(uint32 index) const;
[[nodiscard]] Field* Fetch() const { return _currentRow; }
Field const& operator[](std::size_t index) const;
template<typename... Ts>
inline std::tuple<Ts...> FetchTuple()
{
AssertRows(sizeof...(Ts));
std::tuple<Ts...> theTuple = {};
std::apply([this](Ts&... args)
{
uint8 index{ 0 };
((args = _currentRow[index].Get<Ts>(), index++), ...);
}, theTuple);
return theTuple;
}
auto begin() { return ResultIterator<ResultSet>(this); }
static auto end() { return ResultIterator<ResultSet>(nullptr); }
protected:
std::vector<QueryResultFieldMetadata> _fieldMetadata;
uint64 _rowCount;
Field* _currentRow;
uint32 _fieldCount;
private:
void CleanUp();
void AssertRows(std::size_t sizeRows);
MySQLResult* _result;
MySQLField* _fields;
ResultSet(ResultSet const& right) = delete;
ResultSet& operator=(ResultSet const& right) = delete;
};
class AC_DATABASE_API PreparedResultSet
{
public:
PreparedResultSet(MySQLStmt* stmt, MySQLResult* result, uint64 rowCount, uint32 fieldCount);
~PreparedResultSet();
bool NextRow();
[[nodiscard]] uint64 GetRowCount() const { return m_rowCount; }
[[nodiscard]] uint32 GetFieldCount() const { return m_fieldCount; }
[[nodiscard]] Field* Fetch() const;
Field const& operator[](std::size_t index) const;
template<typename... Ts>
inline std::tuple<Ts...> FetchTuple()
{
AssertRows(sizeof...(Ts));
std::tuple<Ts...> theTuple = {};
std::apply([this](Ts&... args)
{
uint8 index{ 0 };
((args = m_rows[uint32(m_rowPosition) * m_fieldCount + index].Get<Ts>(), index++), ...);
}, theTuple);
return theTuple;
}
auto begin() { return ResultIterator<PreparedResultSet>(this); }
static auto end() { return ResultIterator<PreparedResultSet>(nullptr); }
protected:
std::vector<QueryResultFieldMetadata> m_fieldMetadata;
std::vector<Field> m_rows;
uint64 m_rowCount;
uint64 m_rowPosition;
uint32 m_fieldCount;
private:
MySQLBind* m_rBind;
MySQLStmt* m_stmt;
MySQLResult* m_metadataResult; ///< Field metadata, returned by mysql_stmt_result_metadata
void CleanUp();
bool _NextRow();
void AssertRows(std::size_t sizeRows);
PreparedResultSet(PreparedResultSet const& right) = delete;
PreparedResultSet& operator=(PreparedResultSet const& right) = delete;
};
#endif
@@ -0,0 +1,63 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _SQLOPERATION_H
#define _SQLOPERATION_H
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include <variant>
//- Type specifier of our element data
enum SQLElementDataType
{
SQL_ELEMENT_RAW,
SQL_ELEMENT_PREPARED
};
//- The element
struct SQLElementData
{
std::variant<PreparedStatementBase*, std::string> element;
SQLElementDataType type;
};
class MySQLConnection;
class AC_DATABASE_API SQLOperation
{
public:
SQLOperation() = default;
virtual ~SQLOperation() = default;
virtual int call()
{
Execute();
return 0;
}
virtual bool Execute() = 0;
virtual void SetConnection(MySQLConnection* con) { m_conn = con; }
MySQLConnection* m_conn{nullptr};
private:
SQLOperation(SQLOperation const& right) = delete;
SQLOperation& operator=(SQLOperation const& right) = delete;
};
#endif
@@ -0,0 +1,191 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "Transaction.h"
#include "Errors.h"
#include "Log.h"
#include "MySQLConnection.h"
#include "PreparedStatement.h"
#include "Timer.h"
#include <mysqld_error.h>
#include <sstream>
#include <thread>
std::mutex TransactionTask::_deadlockLock;
constexpr Milliseconds DEADLOCK_MAX_RETRY_TIME_MS = 1min;
//- Append a raw ad-hoc query to the transaction
void TransactionBase::Append(std::string_view sql)
{
SQLElementData data = {};
data.type = SQL_ELEMENT_RAW;
data.element = std::string(sql);
m_queries.emplace_back(data);
}
//- Append a prepared statement to the transaction
void TransactionBase::AppendPreparedStatement(PreparedStatementBase* stmt)
{
SQLElementData data = {};
data.type = SQL_ELEMENT_PREPARED;
data.element = stmt;
m_queries.emplace_back(data);
}
void TransactionBase::Cleanup()
{
// This might be called by explicit calls to Cleanup or by the auto-destructor
if (_cleanedUp)
return;
for (SQLElementData& data : m_queries)
{
switch (data.type)
{
case SQL_ELEMENT_PREPARED:
{
try
{
PreparedStatementBase* stmt = std::get<PreparedStatementBase*>(data.element);
ASSERT(stmt);
delete stmt;
}
catch (const std::bad_variant_access& ex)
{
LOG_FATAL("sql.sql", "> PreparedStatementBase not found in SQLElementData. {}", ex.what());
ABORT();
}
}
break;
case SQL_ELEMENT_RAW:
{
try
{
std::get<std::string>(data.element).clear();
}
catch (const std::bad_variant_access& ex)
{
LOG_FATAL("sql.sql", "> std::string not found in SQLElementData. {}", ex.what());
ABORT();
}
}
break;
}
}
m_queries.clear();
_cleanedUp = true;
}
bool TransactionTask::Execute()
{
int errorCode = TryExecute();
if (!errorCode)
return true;
if (errorCode == ER_LOCK_DEADLOCK)
{
std::ostringstream threadIdStream;
threadIdStream << std::this_thread::get_id();
std::string threadId = threadIdStream.str();
{
// Make sure only 1 async thread retries a transaction so they don't keep dead-locking each other
std::lock_guard<std::mutex> lock(_deadlockLock);
for (Milliseconds loopDuration{}, startMSTime = GetTimeMS(); loopDuration <= DEADLOCK_MAX_RETRY_TIME_MS; loopDuration = GetMSTimeDiffToNow(startMSTime))
{
if (!TryExecute())
return true;
LOG_WARN("sql.sql", "Deadlocked SQL Transaction, retrying. Loop timer: {} ms, Thread Id: {}", loopDuration.count(), threadId);
}
}
LOG_ERROR("sql.sql", "Fatal deadlocked SQL Transaction, it will not be retried anymore. Thread Id: {}", threadId);
}
// Clean up now.
CleanupOnFailure();
return false;
}
int TransactionTask::TryExecute()
{
return m_conn->ExecuteTransaction(m_trans);
}
void TransactionTask::CleanupOnFailure()
{
m_trans->Cleanup();
}
bool TransactionWithResultTask::Execute()
{
int errorCode = TryExecute();
if (!errorCode)
{
m_result.set_value(true);
return true;
}
if (errorCode == ER_LOCK_DEADLOCK)
{
std::ostringstream threadIdStream;
threadIdStream << std::this_thread::get_id();
std::string threadId = threadIdStream.str();
{
// Make sure only 1 async thread retries a transaction so they don't keep dead-locking each other
std::lock_guard<std::mutex> lock(_deadlockLock);
for (Milliseconds loopDuration{}, startMSTime = GetTimeMS(); loopDuration <= DEADLOCK_MAX_RETRY_TIME_MS; loopDuration = GetMSTimeDiffToNow(startMSTime))
{
if (!TryExecute())
{
m_result.set_value(true);
return true;
}
LOG_WARN("sql.sql", "Deadlocked SQL Transaction, retrying. Loop timer: {} ms, Thread Id: {}", loopDuration.count(), threadId);
}
}
LOG_ERROR("sql.sql", "Fatal deadlocked SQL Transaction, it will not be retried anymore. Thread Id: {}", threadId);
}
// Clean up now.
CleanupOnFailure();
m_result.set_value(false);
return false;
}
bool TransactionCallback::InvokeIfReady()
{
if (m_future.valid() && m_future.wait_for(0s) == std::future_status::ready)
{
m_callback(m_future.get());
return true;
}
return false;
}
+127
View File
@@ -0,0 +1,127 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _TRANSACTION_H
#define _TRANSACTION_H
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include "SQLOperation.h"
#include "StringFormat.h"
#include <functional>
#include <mutex>
#include <vector>
/*! Transactions, high level class. */
class AC_DATABASE_API TransactionBase
{
friend class TransactionTask;
friend class MySQLConnection;
template <typename T>
friend class DatabaseWorkerPool;
public:
TransactionBase() = default;
virtual ~TransactionBase() { Cleanup(); }
void Append(std::string_view sql);
template<typename... Args>
void Append(std::string_view sql, Args&&... args)
{
Append(Acore::StringFormat(sql, std::forward<Args>(args)...));
}
[[nodiscard]] std::size_t GetSize() const { return m_queries.size(); }
protected:
void AppendPreparedStatement(PreparedStatementBase* statement);
void Cleanup();
std::vector<SQLElementData> m_queries;
private:
bool _cleanedUp{false};
};
template<typename T>
class Transaction : public TransactionBase
{
public:
using TransactionBase::Append;
void Append(PreparedStatement<T>* statement)
{
AppendPreparedStatement(statement);
}
};
/*! Low level class*/
class AC_DATABASE_API TransactionTask : public SQLOperation
{
template <class T>
friend class DatabaseWorkerPool;
friend class DatabaseWorker;
friend class TransactionCallback;
public:
TransactionTask(std::shared_ptr<TransactionBase> trans) : m_trans(std::move(trans)) { }
~TransactionTask() override = default;
protected:
bool Execute() override;
int TryExecute();
void CleanupOnFailure();
std::shared_ptr<TransactionBase> m_trans;
static std::mutex _deadlockLock;
};
class AC_DATABASE_API TransactionWithResultTask : public TransactionTask
{
public:
TransactionWithResultTask(std::shared_ptr<TransactionBase> trans) : TransactionTask(trans) { }
TransactionFuture GetFuture() { return m_result.get_future(); }
protected:
bool Execute() override;
TransactionPromise m_result;
};
class AC_DATABASE_API TransactionCallback
{
public:
TransactionCallback(TransactionFuture&& future) : m_future(std::move(future)) { }
TransactionCallback(TransactionCallback&&) = default;
TransactionCallback& operator=(TransactionCallback&&) = default;
void AfterComplete(std::function<void(bool)> callback) &
{
m_callback = std::move(callback);
}
bool InvokeIfReady();
TransactionFuture m_future;
std::function<void(bool)> m_callback;
};
#endif
@@ -0,0 +1,48 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "AppenderDB.h"
#include "DatabaseEnv.h"
#include "LogMessage.h"
#include "PreparedStatement.h"
#include <vector>
AppenderDB::AppenderDB(uint8 id, std::string const& name, LogLevel level, AppenderFlags /*flags*/, std::vector<std::string_view> const& /*args*/)
: Appender(id, name, level), realmId(0), enabled(false) { }
AppenderDB::~AppenderDB() { }
void AppenderDB::_write(LogMessage const* message)
{
// Avoid infinite loop, Execute triggers Logging with "sql.sql" type
if (!enabled || (message->type.find("sql") != std::string::npos))
return;
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_LOG);
stmt->SetData(0, message->mtime.count());
stmt->SetData(1, realmId);
stmt->SetData(2, message->type);
stmt->SetData(3, uint8(message->level));
stmt->SetData(4, message->text);
LoginDatabase.Execute(stmt);
}
void AppenderDB::setRealmId(uint32 _realmId)
{
enabled = true;
realmId = _realmId;
}
+41
View File
@@ -0,0 +1,41 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef APPENDERDB_H
#define APPENDERDB_H
#include "Appender.h"
#include <vector>
class AppenderDB : public Appender
{
public:
static constexpr AppenderType type = APPENDER_DB;
AppenderDB(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, std::vector<std::string_view> const& args);
~AppenderDB();
void setRealmId(uint32 realmId) override;
AppenderType getType() const override { return type; }
private:
uint32 realmId;
bool enabled;
void _write(LogMessage const* message) override;
};
#endif
@@ -0,0 +1,35 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "DatabaseEnvFwd.h"
#include "Define.h"
#include "Errors.h"
#include "Field.h"
#include "Log.h"
#include "MySQLConnection.h"
#include "MySQLPreparedStatement.h"
#include "MySQLWorkaround.h"
#include "PreparedStatement.h"
#include "QueryResult.h"
#include "SQLOperation.h"
#include "Transaction.h"
#ifdef _WIN32 // hack for broken mysql.h not including the correct winsock header for SOCKET definition, fixed in 5.7
#include <winsock2.h>
#endif
#include <mysql.h>
#include <string>
#include <vector>
+539
View File
@@ -0,0 +1,539 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "DBUpdater.h"
#include "BuiltInConfig.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "Log.h"
#include "StartProcess.h"
#include "UpdateFetcher.h"
#include "QueryResult.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <vector>
std::string DBUpdaterUtil::GetCorrectedMySQLExecutable()
{
if (!corrected_path().empty())
return corrected_path();
else
return BuiltInConfig::GetMySQLExecutable();
}
bool DBUpdaterUtil::CheckExecutable()
{
std::filesystem::path exe(GetCorrectedMySQLExecutable());
if (!is_regular_file(exe))
{
exe = Acore::SearchExecutableInPath("mysql");
if (!exe.empty() && is_regular_file(exe))
{
// Correct the path to the cli
corrected_path() = absolute(exe).generic_string();
return true;
}
LOG_FATAL("sql.updates", "Didn't find any executable MySQL binary at \'{}\' or in path, correct the path in the *.conf (\"MySQLExecutable\").",
absolute(exe).generic_string());
return false;
}
return true;
}
std::string& DBUpdaterUtil::corrected_path()
{
static std::string path;
return path;
}
// Auth Database
template<>
std::string DBUpdater<LoginDatabaseConnection>::GetConfigEntry()
{
return "Updates.Auth";
}
template<>
std::string DBUpdater<LoginDatabaseConnection>::GetTableName()
{
return "Auth";
}
template<>
std::string DBUpdater<LoginDatabaseConnection>::GetBaseFilesDirectory()
{
return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_auth/";
}
template<>
bool DBUpdater<LoginDatabaseConnection>::IsEnabled(uint32 const updateMask)
{
// This way silences warnings under msvc
return (updateMask & DatabaseLoader::DATABASE_LOGIN) ? true : false;
}
template<>
std::string DBUpdater<LoginDatabaseConnection>::GetDBModuleName()
{
// must be lowercase
return "auth";
}
// World Database
template<>
std::string DBUpdater<WorldDatabaseConnection>::GetConfigEntry()
{
return "Updates.World";
}
template<>
std::string DBUpdater<WorldDatabaseConnection>::GetTableName()
{
return "World";
}
template<>
std::string DBUpdater<WorldDatabaseConnection>::GetBaseFilesDirectory()
{
return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_world/";
}
template<>
bool DBUpdater<WorldDatabaseConnection>::IsEnabled(uint32 const updateMask)
{
// This way silences warnings under msvc
return (updateMask & DatabaseLoader::DATABASE_WORLD) ? true : false;
}
template<>
std::string DBUpdater<WorldDatabaseConnection>::GetDBModuleName()
{
// must be lowercase
return "world";
}
// Character Database
template<>
std::string DBUpdater<CharacterDatabaseConnection>::GetConfigEntry()
{
return "Updates.Character";
}
template<>
std::string DBUpdater<CharacterDatabaseConnection>::GetTableName()
{
return "Character";
}
template<>
std::string DBUpdater<CharacterDatabaseConnection>::GetBaseFilesDirectory()
{
return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_characters/";
}
template<>
bool DBUpdater<CharacterDatabaseConnection>::IsEnabled(uint32 const updateMask)
{
// This way silences warnings under msvc
return (updateMask & DatabaseLoader::DATABASE_CHARACTER) ? true : false;
}
template<>
std::string DBUpdater<CharacterDatabaseConnection>::GetDBModuleName()
{
// must be lowercase
return "characters";
}
// All
template<class T>
BaseLocation DBUpdater<T>::GetBaseLocationType()
{
return LOCATION_REPOSITORY;
}
template<class T>
bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool)
{
LOG_WARN("sql.updates", "Database \"{}\" does not exist", pool.GetConnectionInfo()->database);
const char* disableInteractive = std::getenv("AC_DISABLE_INTERACTIVE");
if (!sConfigMgr->isDryRun() && (disableInteractive == nullptr || std::strcmp(disableInteractive, "1") != 0))
{
std::cout << "Do you want to create it? [yes (default) / no]:" << std::endl;
std::string answer;
std::getline(std::cin, answer);
if (!answer.empty() && !(answer.substr(0, 1) == "y"))
return false;
}
LOG_INFO("sql.updates", "Creating database \"{}\"...", pool.GetConnectionInfo()->database);
// Path of temp file
static Path const temp("create_table.sql");
// Create temporary query to use external MySQL CLi
std::ofstream file(temp.generic_string());
if (!file.is_open())
{
LOG_FATAL("sql.updates", "Failed to create temporary query file \"{}\"!", temp.generic_string());
return false;
}
file << "CREATE DATABASE `" << pool.GetConnectionInfo()->database << "` DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci;\n\n";
file.close();
try
{
DBUpdater<T>::ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user, pool.GetConnectionInfo()->password,
pool.GetConnectionInfo()->port_or_socket, "", pool.GetConnectionInfo()->ssl, temp);
}
catch (UpdateException&)
{
LOG_FATAL("sql.updates", "Failed to create database {}! Does the user (named in *.conf) have `CREATE`, `ALTER`, `DROP`, `INSERT` and `DELETE` privileges on the MySQL server?", pool.GetConnectionInfo()->database);
std::filesystem::remove(temp);
return false;
}
LOG_INFO("sql.updates", "Done.");
LOG_INFO("sql.updates", " ");
std::filesystem::remove(temp);
return true;
}
template<class T>
bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::string_view modulesList /*= {}*/)
{
if (!DBUpdaterUtil::CheckExecutable())
return false;
LOG_INFO("sql.updates", "Updating {} database...", DBUpdater<T>::GetTableName());
Path const sourceDirectory(BuiltInConfig::GetSourceDirectory());
if (!is_directory(sourceDirectory))
{
LOG_ERROR("sql.updates", "DBUpdater: The given source directory {} does not exist, change the path to the directory where your sql directory exists (for example c:\\source\\azerothcore). Shutting down.",
sourceDirectory.generic_string());
return false;
}
auto CheckUpdateTable = [&](std::string const& tableName)
{
auto checkTable = DBUpdater<T>::Retrieve(pool, Acore::StringFormat("SHOW TABLES LIKE '{}'", tableName));
if (!checkTable)
{
LOG_WARN("sql.updates", "> Table '{}' not exist! Try add based table", tableName);
Path const temp(GetBaseFilesDirectory() + tableName + ".sql");
try
{
DBUpdater<T>::ApplyFile(pool, temp);
}
catch (UpdateException&)
{
LOG_FATAL("sql.updates", "Failed apply file to database {}! Does the user (named in *.conf) have `INSERT` and `DELETE` privileges on the MySQL server?", pool.GetConnectionInfo()->database);
return false;
}
return true;
}
return true;
};
if (!CheckUpdateTable("updates") || !CheckUpdateTable("updates_include"))
return false;
UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const & query) { DBUpdater<T>::Apply(pool, query); },
[&](Path const & file) { DBUpdater<T>::ApplyFile(pool, file); },
[&](std::string const & query) -> QueryResult { return DBUpdater<T>::Retrieve(pool, query); }, DBUpdater<T>::GetDBModuleName(), modulesList);
UpdateResult result;
try
{
result = updateFetcher.Update(
sConfigMgr->GetOption<bool>("Updates.Redundancy", true),
sConfigMgr->GetOption<bool>("Updates.AllowRehash", true),
sConfigMgr->GetOption<bool>("Updates.ArchivedRedundancy", false),
sConfigMgr->GetOption<int32>("Updates.CleanDeadRefMaxCount", 3));
}
catch (UpdateException&)
{
return false;
}
std::string const info = Acore::StringFormat("Containing {} new and {} archived updates.", result.recent, result.archived);
if (!result.updated)
LOG_INFO("sql.updates", ">> {} database is up-to-date! {}", DBUpdater<T>::GetTableName(), info);
else
LOG_INFO("sql.updates", ">> Applied {} {}. {}", result.updated, result.updated == 1 ? "query" : "queries", info);
LOG_INFO("sql.updates", " ");
return true;
}
template<class T>
bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::vector<std::string> const* setDirectories)
{
if (!DBUpdaterUtil::CheckExecutable())
{
return false;
}
Path const sourceDirectory(BuiltInConfig::GetSourceDirectory());
if (!is_directory(sourceDirectory))
{
return false;
}
auto CheckUpdateTable = [&](std::string const& tableName)
{
auto checkTable = DBUpdater<T>::Retrieve(pool, Acore::StringFormat("SHOW TABLES LIKE '{}'", tableName));
if (!checkTable)
{
Path const temp(GetBaseFilesDirectory() + tableName + ".sql");
try
{
DBUpdater<T>::ApplyFile(pool, temp);
}
catch (UpdateException&)
{
return false;
}
return true;
}
return true;
};
if (!CheckUpdateTable("updates") || !CheckUpdateTable("updates_include"))
{
return false;
}
UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const & query) { DBUpdater<T>::Apply(pool, query); },
[&](Path const & file) { DBUpdater<T>::ApplyFile(pool, file); },
[&](std::string const & query) -> QueryResult { return DBUpdater<T>::Retrieve(pool, query); }, DBUpdater<T>::GetDBModuleName(), setDirectories);
UpdateResult result;
try
{
result = updateFetcher.Update(
sConfigMgr->GetOption<bool>("Updates.Redundancy", true),
sConfigMgr->GetOption<bool>("Updates.AllowRehash", true),
sConfigMgr->GetOption<bool>("Updates.ArchivedRedundancy", false),
sConfigMgr->GetOption<int32>("Updates.CleanDeadRefMaxCount", 3));
}
catch (UpdateException&)
{
return false;
}
return true;
}
template<class T>
bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)
{
{
QueryResult const result = Retrieve(pool, "SHOW TABLES");
if (result && (result->GetRowCount() > 0))
return true;
}
if (!DBUpdaterUtil::CheckExecutable())
return false;
LOG_INFO("sql.updates", "Database {} is empty, auto populating it...", DBUpdater<T>::GetTableName());
std::string const DirPathStr = DBUpdater<T>::GetBaseFilesDirectory();
Path const DirPath(DirPathStr);
if (!std::filesystem::is_directory(DirPath))
{
LOG_ERROR("sql.updates", ">> Directory \"{}\" not exist", DirPath.generic_string());
return false;
}
if (DirPath.empty())
{
LOG_ERROR("sql.updates", ">> Directory \"{}\" is empty", DirPath.generic_string());
return false;
}
std::filesystem::directory_iterator const DirItr;
uint32 FilesCount = 0;
for (std::filesystem::directory_iterator itr(DirPath); itr != DirItr; ++itr)
{
if (itr->path().extension() == ".sql")
FilesCount++;
}
if (!FilesCount)
{
LOG_ERROR("sql.updates", ">> In directory \"{}\" not exist '*.sql' files", DirPath.generic_string());
return false;
}
std::vector<std::filesystem::path> sqlFiles;
for (const auto &entry : std::filesystem::directory_iterator(DirPath))
{
if (entry.path().extension() == ".sql")
sqlFiles.push_back(entry.path());
}
std::sort(sqlFiles.begin(), sqlFiles.end());
for (const auto &file : sqlFiles)
{
LOG_INFO("sql.updates", ">> Applying \'{}\'...", file.filename().generic_string());
try
{
ApplyFile(pool, file);
}
catch (UpdateException&)
{
return false;
}
}
LOG_INFO("sql.updates", ">> Done!");
LOG_INFO("sql.updates", " ");
return true;
}
template<class T>
QueryResult DBUpdater<T>::Retrieve(DatabaseWorkerPool<T>& pool, std::string const& query)
{
return pool.Query(query.c_str());
}
template<class T>
void DBUpdater<T>::Apply(DatabaseWorkerPool<T>& pool, std::string const& query)
{
pool.DirectExecute(query.c_str());
}
template<class T>
void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, Path const& path)
{
DBUpdater<T>::ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user, pool.GetConnectionInfo()->password,
pool.GetConnectionInfo()->port_or_socket, pool.GetConnectionInfo()->database, pool.GetConnectionInfo()->ssl, path);
}
template<class T>
void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& host, std::string const& user,
std::string const& password, std::string const& port_or_socket, std::string const& database, std::string const& ssl, Path const& path)
{
std::string configTempDir = sConfigMgr->GetOption<std::string>("TempDir", "");
auto tempDir = configTempDir.empty() ? std::filesystem::temp_directory_path().string() : configTempDir;
tempDir = Acore::String::AddSuffixIfNotExists(tempDir, std::filesystem::path::preferred_separator);
std::string confFileName = "mysql_ac.conf";
std::ofstream outfile (tempDir + confFileName);
outfile << "[client]\npassword = \"" << password << '"' << std::endl;
outfile.close();
std::vector<std::string> args;
args.reserve(9);
args.emplace_back("--defaults-extra-file="+tempDir + confFileName+"");
// CLI Client connection info
args.emplace_back("-h" + host);
args.emplace_back("-u" + user);
// Check if we want to connect through ip or socket (Unix only)
#ifdef _WIN32
if (host == ".")
args.emplace_back("--protocol=PIPE");
else
args.emplace_back("-P" + port_or_socket);
#else
if (!std::isdigit(port_or_socket[0]))
{
// We can't check if host == "." here, because it is named localhost if socket option is enabled
args.emplace_back("-P0");
args.emplace_back("--protocol=SOCKET");
args.emplace_back("-S" + port_or_socket);
}
else
// generic case
args.emplace_back("-P" + port_or_socket);
#endif
// Set the default charset to utf8
args.emplace_back("--default-character-set=utf8");
// Set max allowed packet to 1 GB
args.emplace_back("--max-allowed-packet=1GB");
if (ssl == "ssl")
args.emplace_back("--ssl-mode=REQUIRED");
// Database
if (!database.empty())
args.emplace_back(database);
// Invokes a mysql process which doesn't leak credentials to logs
int const ret = Acore::StartProcess(DBUpdaterUtil::GetCorrectedMySQLExecutable(), args,
"sql.updates", path.generic_string(), true);
if (ret != EXIT_SUCCESS)
{
LOG_FATAL("sql.updates", "Applying of file \'{}\' to database \'{}\' failed!" \
" If you are a user, please pull the latest revision from the repository. "
"Also make sure you have not applied any of the databases with your sql client. "
"You cannot use auto-update system and import sql files from AzerothCore repository with your sql client. "
"If you are a developer, please fix your sql query.",
path.generic_string(), pool.GetConnectionInfo()->database);
if (!sConfigMgr->isDryRun())
{
if (uint32 delay = sConfigMgr->GetOption<uint32>("Updates.ExceptionShutdownDelay", 10000))
std::this_thread::sleep_for(Milliseconds(delay));
throw UpdateException("update failed");
}
}
}
template class AC_DATABASE_API DBUpdater<LoginDatabaseConnection>;
template class AC_DATABASE_API DBUpdater<WorldDatabaseConnection>;
template class AC_DATABASE_API DBUpdater<CharacterDatabaseConnection>;
+94
View File
@@ -0,0 +1,94 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef DBUpdater_h__
#define DBUpdater_h__
#include "DatabaseEnv.h"
#include "Define.h"
#include "QueryResult.h"
#include <filesystem>
#include <string>
template <class T>
class DatabaseWorkerPool;
namespace boost
{
namespace filesystem
{
class path;
}
}
class AC_DATABASE_API UpdateException : public std::exception
{
public:
UpdateException(std::string const& msg) : _msg(msg) { }
~UpdateException() throw() { }
char const* what() const throw() override { return _msg.c_str(); }
private:
std::string const _msg;
};
enum BaseLocation
{
LOCATION_REPOSITORY,
LOCATION_DOWNLOAD
};
class AC_DATABASE_API DBUpdaterUtil
{
public:
static std::string GetCorrectedMySQLExecutable();
static bool CheckExecutable();
private:
static std::string& corrected_path();
};
template <class T>
class AC_DATABASE_API DBUpdater
{
public:
using Path = std::filesystem::path;
static inline std::string GetConfigEntry();
static inline std::string GetTableName();
static std::string GetBaseFilesDirectory();
static bool IsEnabled(uint32 const updateMask);
static BaseLocation GetBaseLocationType();
static bool Create(DatabaseWorkerPool<T>& pool);
static bool Update(DatabaseWorkerPool<T>& pool, std::string_view modulesList = {});
static bool Update(DatabaseWorkerPool<T>& pool, std::vector<std::string> const* setDirectories);
static bool Populate(DatabaseWorkerPool<T>& pool);
// module
static std::string GetDBModuleName();
private:
static QueryResult Retrieve(DatabaseWorkerPool<T>& pool, std::string const& query);
static void Apply(DatabaseWorkerPool<T>& pool, std::string const& query);
static void ApplyFile(DatabaseWorkerPool<T>& pool, Path const& path);
static void ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& host, std::string const& user,
std::string const& password, std::string const& port_or_socket, std::string const& database, std::string const& ssl, Path const& path);
};
#endif // DBUpdater_h__
@@ -0,0 +1,524 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "UpdateFetcher.h"
#include "CryptoHash.h"
#include "DBUpdater.h"
#include "Field.h"
#include "Log.h"
#include "Tokenize.h"
#include "Util.h"
#include <fstream>
#include <sstream>
#include "QueryResult.h"
using namespace std::filesystem;
struct UpdateFetcher::DirectoryEntry
{
DirectoryEntry(Path const& path_, State state_) : path(path_), state(state_) { }
Path const path;
State const state;
};
UpdateFetcher::UpdateFetcher(Path const& sourceDirectory,
std::function<void(std::string const&)> const& apply,
std::function<void(Path const& path)> const& applyFile,
std::function<QueryResult(std::string const&)> const& retrieve, std::string const& dbModuleName, std::vector<std::string> const* setDirectories /*= nullptr*/) :
_sourceDirectory(std::make_unique<Path>(sourceDirectory)), _apply(apply), _applyFile(applyFile),
_retrieve(retrieve), _dbModuleName(dbModuleName), _setDirectories(setDirectories)
{
}
UpdateFetcher::UpdateFetcher(Path const& sourceDirectory,
std::function<void(std::string const&)> const& apply,
std::function<void(Path const& path)> const& applyFile,
std::function<QueryResult(std::string const&)> const& retrieve,
std::string const& dbModuleName,
std::string_view modulesList /*= {}*/) :
_sourceDirectory(std::make_unique<Path>(sourceDirectory)), _apply(apply), _applyFile(applyFile),
_retrieve(retrieve), _dbModuleName(dbModuleName), _setDirectories(nullptr), _modulesList(modulesList)
{
}
UpdateFetcher::~UpdateFetcher()
{
}
UpdateFetcher::LocaleFileStorage UpdateFetcher::GetFileList() const
{
LocaleFileStorage files;
DirectoryStorage directories = ReceiveIncludedDirectories();
for (auto const& entry : directories)
FillFileListRecursively(entry.path, files, entry.state, 1);
return files;
}
void UpdateFetcher::FillFileListRecursively(Path const& path, LocaleFileStorage& storage, State const state, uint32 const depth) const
{
static uint32 const MAX_DEPTH = 10;
static directory_iterator const end;
for (directory_iterator itr(path); itr != end; ++itr)
{
if (is_directory(itr->path()))
{
if (depth < MAX_DEPTH)
FillFileListRecursively(itr->path(), storage, state, depth + 1);
}
else if (itr->path().extension() == ".sql")
{
LOG_TRACE("sql.updates", "Added locale file \"{}\" state '{}'.", itr->path().filename().generic_string(), AppliedFileEntry::StateConvert(state));
LocaleFileEntry const entry = { itr->path(), state };
// Check for doubled filenames
// Because elements are only compared by their filenames, this is ok
if (storage.find(entry) != storage.end())
{
LOG_FATAL("sql.updates", "Duplicate filename \"{}\" occurred. Because updates are ordered " \
"by their filenames, every name needs to be unique!", itr->path().generic_string());
throw UpdateException("Updating failed, see the log for details.");
}
storage.insert(entry);
}
}
}
UpdateFetcher::DirectoryStorage UpdateFetcher::ReceiveIncludedDirectories() const
{
DirectoryStorage directories;
if (_setDirectories)
{
for (auto const& itr : *_setDirectories)
{
std::string path = _sourceDirectory->generic_string() + itr;
Path const p(path);
if (!is_directory(p))
continue;
DirectoryEntry const entry = {p, AppliedFileEntry::StateConvert("MODULE")};
directories.push_back(entry);
LOG_TRACE("sql.updates", "Added applied extra file \"{}\" from remote.", p.filename().generic_string());
}
}
else
{
QueryResult const result = _retrieve("SELECT `path`, `state` FROM `updates_include`");
if (!result)
return directories;
do
{
Field* fields = result->Fetch();
std::string path = fields[0].Get<std::string>();
std::string state = fields[1].Get<std::string>();
if (path.substr(0, 1) == "$")
path = _sourceDirectory->generic_string() + path.substr(1);
Path const p(path);
if (!is_directory(p))
{
LOG_WARN("sql.updates", "DBUpdater: Given update include directory \"{}\" does not exist, skipped!", p.generic_string());
continue;
}
DirectoryEntry const entry = {p, AppliedFileEntry::StateConvert(state)};
directories.push_back(entry);
LOG_TRACE("sql.updates", "Added applied file \"{}\" '{}' state from remote.", p.filename().generic_string(), state);
} while (result->NextRow());
std::vector<std::string> moduleList;
for (auto const& itr : Acore::Tokenize(_modulesList, ',', true))
moduleList.emplace_back(itr);
// data/sql
for (auto const& moduleName : moduleList)
{
std::string path = _sourceDirectory->generic_string() + "/modules/" + moduleName + "/data/sql/"; // modules/mod-name/data/sql/
Path const p{path};
if (!is_directory(p))
continue;
directory_iterator const end;
for (directory_iterator itr{p}; itr != end; ++itr)
{
if (!is_directory(itr->path()))
continue;
std::filesystem::path dirPath = itr->path(); // modules/mod-name/data/sql/db-world
std::string dirName = dirPath.filename().string(); // db-world
if (dirName.find(_dbModuleName) == std::string::npos)
continue;
DirectoryEntry const entry = { dirPath, AppliedFileEntry::StateConvert("MODULE") };
directories.push_back(entry);
LOG_TRACE("sql.updates", "Added applied modules file \"{}\" from remote.", dirPath.filename().generic_string());
}
}
}
return directories;
}
UpdateFetcher::AppliedFileStorage UpdateFetcher::ReceiveAppliedFiles() const
{
AppliedFileStorage map;
QueryResult result = _retrieve("SELECT `name`, `hash`, `state`, UNIX_TIMESTAMP(`timestamp`) FROM `updates` ORDER BY `name` ASC");
if (!result)
return map;
do
{
Field* fields = result->Fetch();
AppliedFileEntry const entry =
{
fields[0].Get<std::string>(), fields[1].Get<std::string>(), AppliedFileEntry::StateConvert(fields[2].Get<std::string>()), fields[3].Get<uint64>()
};
map.emplace(entry.name, entry);
} while (result->NextRow());
return map;
}
std::string UpdateFetcher::ReadSQLUpdate(Path const& file) const
{
std::ifstream in(file.c_str());
if (!in.is_open())
{
LOG_FATAL("sql.updates", "Failed to open the sql update \"{}\" for reading! "
"Stopping the server to keep the database integrity, "
"try to identify and solve the issue or disable the database updater.",
file.generic_string());
throw UpdateException("Opening the sql update failed!");
}
auto update = [&in]
{
std::ostringstream ss;
ss << in.rdbuf();
return ss.str();
}();
in.close();
return update;
}
UpdateResult UpdateFetcher::Update(bool const redundancyChecks,
bool const allowRehash,
bool const archivedRedundancy,
int32 const cleanDeadReferencesMaxCount) const
{
LocaleFileStorage const available = GetFileList();
if (_setDirectories && available.empty())
{
return UpdateResult();
}
AppliedFileStorage applied = ReceiveAppliedFiles();
std::size_t countRecentUpdates = 0;
std::size_t countArchivedUpdates = 0;
// Count updates
for (auto const& entry : applied)
if (entry.second.state == RELEASED)
++countRecentUpdates;
else
++countArchivedUpdates;
// Fill hash to name cache
HashToFileNameStorage hashToName;
for (auto& entry : applied)
hashToName.insert(std::make_pair(entry.second.hash, entry.first));
std::size_t importedUpdates = 0;
auto ApplyUpdateFile = [&](LocaleFileEntry const& sqlFile)
{
auto filePath = sqlFile.first;
auto fileState = sqlFile.second;
LOG_DEBUG("sql.updates", "Checking update \"{}\"...", filePath.filename().generic_string());
AppliedFileStorage::const_iterator iter = applied.find(filePath.filename().string());
if (iter != applied.end())
{
// If redundancy is disabled, skip it, because the update is already applied.
if (!redundancyChecks)
{
LOG_DEBUG("sql.updates", ">> Update is already applied, skipping redundancy checks.");
applied.erase(iter);
return;
}
// If the update is in an archived directory and is marked as archived in our database, skip redundancy checks (archived updates never change).
if (!archivedRedundancy && (iter->second.state == ARCHIVED) && (sqlFile.second == ARCHIVED))
{
LOG_DEBUG("sql.updates", ">> Update is archived and marked as archived in database, skipping redundancy checks.");
applied.erase(iter);
return;
}
}
std::string const hash = ByteArrayToHexStr(Acore::Crypto::SHA1::GetDigestOf(ReadSQLUpdate(filePath)));
UpdateMode mode = MODE_APPLY;
// Update is not in our applied list
if (iter == applied.end())
{
// Catch renames (different filename, but same hash)
HashToFileNameStorage::const_iterator const hashIter = hashToName.find(hash);
if (hashIter != hashToName.end())
{
// Check if the original file was removed. If not, we've got a problem.
LocaleFileStorage::const_iterator localeIter;
// Push localeIter forward
for (localeIter = available.begin(); (localeIter != available.end()) &&
(localeIter->first.filename().string() != hashIter->second); ++localeIter);
// Conflict!
if (localeIter != available.end())
{
LOG_WARN("sql.updates", ">> It seems like the update \"{}\" \'{}\' was renamed, but the old file is still there! " \
"Treating it as a new file! (It is probably an unmodified copy of the file \"{}\")",
filePath.filename().string(), hash.substr(0, 7),
localeIter->first.filename().string());
}
else // It is safe to treat the file as renamed here
{
LOG_INFO("sql.updates", ">> Renaming update \"{}\" to \"{}\" \'{}\'.",
hashIter->second, filePath.filename().string(), hash.substr(0, 7));
RenameEntry(hashIter->second, filePath.filename().string());
applied.erase(hashIter->second);
return;
}
}
// Apply the update if it was never seen before.
else
{
LOG_INFO("sql.updates", ">> Applying update \"{}\" \'{}\'...",
filePath.filename().string(), hash.substr(0, 7));
}
}
// Rehash the update entry if it exists in our database with an empty hash.
else if (allowRehash && iter->second.hash.empty())
{
mode = MODE_REHASH;
LOG_INFO("sql.updates", ">> Re-hashing update \"{}\" \'{}\'...", filePath.filename().string(),
hash.substr(0, 7));
}
else
{
// If the hash of the files differs from the one stored in our database, reapply the update (because it changed).
if (iter->second.hash != hash)
{
LOG_INFO("sql.updates", ">> Reapplying update \"{}\" \'{}\' -> \'{}\' (it changed)...", filePath.filename().string(),
iter->second.hash.substr(0, 7), hash.substr(0, 7));
}
else
{
// If the file wasn't changed and just moved, update its state (if necessary).
if (iter->second.state != fileState)
{
LOG_DEBUG("sql.updates", ">> Updating the state of \"{}\" to \'{}\'...",
filePath.filename().string(), AppliedFileEntry::StateConvert(fileState));
UpdateState(filePath.filename().string(), fileState);
}
LOG_DEBUG("sql.updates", ">> Update is already applied and matches the hash \'{}\'.", hash.substr(0, 7));
applied.erase(iter);
return;
}
}
uint32 speed = 0;
AppliedFileEntry const file = { filePath.filename().string(), hash, fileState, 0 };
switch (mode)
{
case MODE_APPLY:
speed = Apply(filePath);
[[fallthrough]];
case MODE_REHASH:
UpdateEntry(file, speed);
break;
}
if (iter != applied.end())
applied.erase(iter);
if (mode == MODE_APPLY)
++importedUpdates;
};
// Apply default updates
for (auto const& availableQuery : available)
{
if (availableQuery.second != PENDING && availableQuery.second != CUSTOM && availableQuery.second != MODULE)
ApplyUpdateFile(availableQuery);
}
// Apply only pending/custom/module updates
for (auto const& availableQuery : available)
{
if (availableQuery.second == PENDING || availableQuery.second == CUSTOM || availableQuery.second == MODULE)
ApplyUpdateFile(availableQuery);
}
// Cleanup up orphaned entries (if enabled)
if (!applied.empty() && !_setDirectories)
{
bool const doCleanup = (cleanDeadReferencesMaxCount < 0) || (applied.size() <= static_cast<size_t>(cleanDeadReferencesMaxCount));
AppliedFileStorage toCleanup;
for (auto const& entry : applied)
{
if (entry.second.state != MODULE)
{
LOG_WARN("sql.updates",
">> The file \'{}\' was applied to the database, but is missing in"
" your update directory now!",
entry.first);
if (doCleanup)
{
LOG_INFO("sql.updates", "Deleting orphaned entry \'{}\'...", entry.first);
toCleanup.insert(entry);
}
}
}
if (!toCleanup.empty())
{
if (doCleanup)
CleanUp(toCleanup);
else
{
LOG_ERROR("sql.updates",
"Cleanup is disabled! There were {} dirty files applied to your database, "
"but they are now missing in your source directory!",
toCleanup.size());
}
}
}
return UpdateResult(importedUpdates, countRecentUpdates, countArchivedUpdates);
}
uint32 UpdateFetcher::Apply(Path const& path) const
{
using Time = std::chrono::high_resolution_clock;
// Benchmark query speed
auto const begin = Time::now();
// Update database
_applyFile(path);
// Return the time it took the query to apply
return uint32(std::chrono::duration_cast<std::chrono::milliseconds>(Time::now() - begin).count());
}
void UpdateFetcher::UpdateEntry(AppliedFileEntry const& entry, uint32 const speed) const
{
std::string const update = "REPLACE INTO `updates` (`name`, `hash`, `state`, `speed`) VALUES (\"" +
entry.name + "\", \"" + entry.hash + "\", \'" + entry.GetStateAsString() + "\', " + std::to_string(speed) + ")";
// Update database
_apply(update);
}
void UpdateFetcher::RenameEntry(std::string const& from, std::string const& to) const
{
// Delete the target if it exists
{
std::string const update = "DELETE FROM `updates` WHERE `name`=\"" + to + "\"";
// Update database
_apply(update);
}
// Rename
{
std::string const update = "UPDATE `updates` SET `name`=\"" + to + "\" WHERE `name`=\"" + from + "\"";
// Update database
_apply(update);
}
}
void UpdateFetcher::CleanUp(AppliedFileStorage const& storage) const
{
if (storage.empty())
return;
std::stringstream update;
std::size_t remaining = storage.size();
update << "DELETE FROM `updates` WHERE `name` IN(";
for (auto const& entry : storage)
{
update << "\"" << entry.first << "\"";
if ((--remaining) > 0)
update << ", ";
}
update << ")";
// Update database
_apply(update.str());
}
void UpdateFetcher::UpdateState(std::string const& name, State const state) const
{
std::string const update = "UPDATE `updates` SET `state`=\'" + AppliedFileEntry::StateConvert(state) + "\' WHERE `name`=\"" + name + "\"";
// Update database
_apply(update);
}
bool UpdateFetcher::PathCompare::operator()(LocaleFileEntry const& left, LocaleFileEntry const& right) const
{
return left.first.filename().string() < right.first.filename().string();
}
+172
View File
@@ -0,0 +1,172 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef UpdateFetcher_h__
#define UpdateFetcher_h__
#include "DatabaseEnv.h"
#include "Define.h"
#include <filesystem>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
struct AC_DATABASE_API UpdateResult
{
UpdateResult()
: updated(0), recent(0), archived(0) { }
UpdateResult(std::size_t const updated_, std::size_t const recent_, std::size_t const archived_)
: updated(updated_), recent(recent_), archived(archived_) { }
std::size_t updated;
std::size_t recent;
std::size_t archived;
};
class AC_DATABASE_API UpdateFetcher
{
typedef std::filesystem::path Path;
public:
UpdateFetcher(Path const& updateDirectory,
std::function<void(std::string const&)> const& apply,
std::function<void(Path const& path)> const& applyFile,
std::function<QueryResult(std::string const&)> const& retrieve, std::string const& dbModuleName, std::vector<std::string> const* setDirectories = nullptr);
UpdateFetcher(Path const& updateDirectory,
std::function<void(std::string const&)> const& apply,
std::function<void(Path const& path)> const& applyFile,
std::function<QueryResult(std::string const&)> const& retrieve,
std::string const& dbModuleName,
std::string_view modulesList = {});
~UpdateFetcher();
UpdateResult Update(bool const redundancyChecks, bool const allowRehash,
bool const archivedRedundancy, int32 const cleanDeadReferencesMaxCount) const;
private:
enum UpdateMode
{
MODE_APPLY,
MODE_REHASH
};
enum State
{
RELEASED,
CUSTOM,
PENDING,
MODULE,
ARCHIVED
};
struct AppliedFileEntry
{
AppliedFileEntry(std::string const& name_, std::string const& hash_, State state_, uint64 timestamp_)
: name(name_), hash(hash_), state(state_), timestamp(timestamp_) { }
std::string const name;
std::string const hash;
State const state;
uint64 const timestamp;
static inline State StateConvert(std::string const& state)
{
if (state == "RELEASED")
return RELEASED;
else if (state == "CUSTOM")
return CUSTOM;
else if (state == "PENDING")
return PENDING;
else if (state == "MODULE")
return MODULE;
return ARCHIVED;
}
static inline std::string StateConvert(State const state)
{
switch (state)
{
case RELEASED:
return "RELEASED";
case CUSTOM:
return "CUSTOM";
case PENDING:
return "PENDING";
case MODULE:
return "MODULE";
case ARCHIVED:
return "ARCHIVED";
default:
return "";
}
}
std::string GetStateAsString() const
{
return StateConvert(state);
}
};
struct DirectoryEntry;
typedef std::pair<Path, State> LocaleFileEntry;
struct PathCompare
{
bool operator()(LocaleFileEntry const& left, LocaleFileEntry const& right) const;
};
typedef std::set<LocaleFileEntry, PathCompare> LocaleFileStorage;
typedef std::unordered_map<std::string, std::string> HashToFileNameStorage;
typedef std::unordered_map<std::string, AppliedFileEntry> AppliedFileStorage;
typedef std::vector<UpdateFetcher::DirectoryEntry> DirectoryStorage;
LocaleFileStorage GetFileList() const;
void FillFileListRecursively(Path const& path, LocaleFileStorage& storage,
State const state, uint32 const depth) const;
DirectoryStorage ReceiveIncludedDirectories() const;
AppliedFileStorage ReceiveAppliedFiles() const;
std::string ReadSQLUpdate(Path const& file) const;
uint32 Apply(Path const& path) const;
void UpdateEntry(AppliedFileEntry const& entry, uint32 const speed = 0) const;
void RenameEntry(std::string const& from, std::string const& to) const;
void CleanUp(AppliedFileStorage const& storage) const;
void UpdateState(std::string const& name, State const state) const;
std::unique_ptr<Path> const _sourceDirectory;
std::function<void(std::string const&)> const _apply;
std::function<void(Path const& path)> const _applyFile;
std::function<QueryResult(std::string const&)> const _retrieve;
// modules
std::string const _dbModuleName;
std::vector<std::string> const* _setDirectories;
std::string_view _modulesList = {};
};
#endif // UpdateFetcher_h__
+340
View File
@@ -0,0 +1,340 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "CombatAI.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "Vehicle.h"
/////////////////
// AggressorAI
/////////////////
int32 AggressorAI::Permissible(Creature const* creature)
{
// have some hostile factions, it will be selected by IsHostileTo check at MoveInLineOfSight
if (!creature->IsCivilian() && !creature->IsNeutralToAll())
return PERMIT_BASE_REACTIVE;
return PERMIT_BASE_NO;
}
void AggressorAI::UpdateAI(uint32 /*diff*/)
{
if (!UpdateVictim())
return;
DoMeleeAttackIfReady();
}
/////////////////
// CombatAI
/////////////////
void CombatAI::InitializeAI()
{
for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i)
if (me->m_spells[i] && sSpellMgr->GetSpellInfo(me->m_spells[i]))
spells.push_back(me->m_spells[i]);
CreatureAI::InitializeAI();
}
void CombatAI::Reset()
{
events.Reset();
}
void CombatAI::JustDied(Unit* killer)
{
for (SpellVct::iterator i = spells.begin(); i != spells.end(); ++i)
if (AISpellInfo[*i].condition == AICOND_DIE)
me->CastSpell(killer, *i, true);
}
/**
* @brief Called for reaction when initially engaged
*
* @param who Who 'me' Engaged combat with
*/
void CombatAI::JustEngagedWith(Unit* who)
{
for (SpellVct::iterator i = spells.begin(); i != spells.end(); ++i)
{
if (AISpellInfo[*i].condition == AICOND_AGGRO)
me->CastSpell(who, *i, false);
else if (AISpellInfo[*i].condition == AICOND_COMBAT)
events.ScheduleEvent(*i, Milliseconds(AISpellInfo[*i].cooldown + rand() % AISpellInfo[*i].cooldown));
}
}
void CombatAI::UpdateAI(uint32 diff)
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
if (uint32 spellId = events.ExecuteEvent())
{
DoCast(spellId);
events.ScheduleEvent(spellId, Milliseconds(AISpellInfo[spellId].cooldown + rand() % AISpellInfo[spellId].cooldown));
}
else
DoMeleeAttackIfReady();
}
/////////////////
// CasterAI
/////////////////
void CasterAI::InitializeAI()
{
CombatAI::InitializeAI();
m_attackDist = 30.0f;
for (SpellVct::iterator itr = spells.begin(); itr != spells.end(); ++itr)
if (AISpellInfo[*itr].condition == AICOND_COMBAT && m_attackDist > GetAISpellInfo(*itr)->maxRange)
m_attackDist = GetAISpellInfo(*itr)->maxRange;
if (m_attackDist == 30.0f)
m_attackDist = MELEE_RANGE;
}
/**
* @brief Called for reaction when initially engaged
*
* @param who Who 'me' Engaged combat with
*/
void CasterAI::JustEngagedWith(Unit* who)
{
if (spells.empty())
return;
uint32 spell = rand() % spells.size();
uint32 count = 0;
for (SpellVct::iterator itr = spells.begin(); itr != spells.end(); ++itr, ++count)
{
if (AISpellInfo[*itr].condition == AICOND_AGGRO)
me->CastSpell(who, *itr, false);
else if (AISpellInfo[*itr].condition == AICOND_COMBAT)
{
uint32 cooldown = GetAISpellInfo(*itr)->realCooldown;
if (count == spell)
{
DoCast(spells[spell]);
cooldown += me->GetCurrentSpellCastTime(*itr);
}
events.ScheduleEvent(*itr, Milliseconds(cooldown));
}
}
}
void CasterAI::UpdateAI(uint32 diff)
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->GetVictim()->HasBreakableByDamageCrowdControlAura(me))
{
me->InterruptNonMeleeSpells(false);
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
if (uint32 spellId = events.ExecuteEvent())
{
DoCast(spellId);
uint32 casttime = me->GetCurrentSpellCastTime(spellId);
events.ScheduleEvent(spellId, Milliseconds((casttime ? casttime : 500) + GetAISpellInfo(spellId)->realCooldown));
}
}
//////////////
// ArcherAI
//////////////
ArcherAI::ArcherAI(Creature* c) : CreatureAI(c)
{
if (!me->m_spells[0])
LOG_ERROR("entities.unit.ai", "ArcherAI set for creature (entry = {}) with spell1=0. AI will do nothing", me->GetEntry());
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(me->m_spells[0]);
m_minRange = spellInfo ? spellInfo->GetMinRange(false) : 0;
if (!m_minRange)
m_minRange = MELEE_RANGE;
me->m_CombatDistance = spellInfo ? spellInfo->GetMaxRange(false) : 0;
me->m_SightDistance = me->m_CombatDistance;
}
void ArcherAI::AttackStart(Unit* who)
{
if (!who)
return;
if (me->IsWithinCombatRange(who, m_minRange))
{
if (me->Attack(who, true) && !who->IsFlying())
me->GetMotionMaster()->MoveChase(who);
}
else
{
if (me->Attack(who, false) && !who->IsFlying())
me->GetMotionMaster()->MoveChase(who, me->m_CombatDistance);
}
if (who->IsFlying())
me->GetMotionMaster()->MoveIdle();
}
void ArcherAI::UpdateAI(uint32 /*diff*/)
{
if (!UpdateVictim())
return;
if (!me->IsWithinCombatRange(me->GetVictim(), m_minRange))
DoSpellAttackIfReady(me->m_spells[0]);
else
DoMeleeAttackIfReady();
}
//////////////
// TurretAI
//////////////
TurretAI::TurretAI(Creature* c) : CreatureAI(c)
{
if (!me->m_spells[0])
LOG_ERROR("entities.unit.ai", "TurretAI set for creature (entry = {}) with spell1=0. AI will do nothing", me->GetEntry());
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(me->m_spells[0]);
m_minRange = spellInfo ? spellInfo->GetMinRange(false) : 0;
me->m_CombatDistance = spellInfo ? spellInfo->GetMaxRange(false) : 0;
me->m_SightDistance = me->m_CombatDistance;
}
bool TurretAI::CanAIAttack(Unit const* /*who*/) const
{
/// @todo: use one function to replace it
if (!me->IsWithinCombatRange(me->GetVictim(), me->m_CombatDistance)
|| (m_minRange && me->IsWithinCombatRange(me->GetVictim(), m_minRange)))
return false;
return true;
}
void TurretAI::AttackStart(Unit* who)
{
if (who)
me->Attack(who, false);
}
void TurretAI::UpdateAI(uint32 /*diff*/)
{
if (!UpdateVictim())
return;
if (me->m_spells[0])
DoSpellAttackIfReady(me->m_spells[0]);
}
//////////////
// VehicleAI
//////////////
VehicleAI::VehicleAI(Creature* c) : CreatureAI(c), m_ConditionsTimer(VEHICLE_CONDITION_CHECK_TIME)
{
LoadConditions();
m_DoDismiss = false;
m_DismissTimer = VEHICLE_DISMISS_TIME;
}
//NOTE: VehicleAI::UpdateAI runs even while the vehicle is mounted
void VehicleAI::UpdateAI(uint32 diff)
{
CheckConditions(diff);
if (m_DoDismiss)
{
if (m_DismissTimer < diff)
{
m_DoDismiss = false;
me->DespawnOrUnsummon();
}
else
m_DismissTimer -= diff;
}
}
void VehicleAI::OnCharmed(bool apply)
{
if (!me->GetVehicleKit()->IsVehicleInUse() && !apply && !conditions.empty()) // was used and has conditions
m_DoDismiss = true; // needs reset
else if (apply)
m_DoDismiss = false; // in use again
m_DismissTimer = VEHICLE_DISMISS_TIME;//reset timer
}
void VehicleAI::LoadConditions()
{
conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry());
if (!conditions.empty())
LOG_DEBUG("condition", "VehicleAI::LoadConditions: loaded {} conditions", uint32(conditions.size()));
}
void VehicleAI::CheckConditions(uint32 diff)
{
if (m_ConditionsTimer < diff)
{
if (!conditions.empty())
{
if (Vehicle* vehicleKit = me->GetVehicleKit())
for (SeatMap::iterator itr = vehicleKit->Seats.begin(); itr != vehicleKit->Seats.end(); ++itr)
if (Unit* passenger = ObjectAccessor::GetUnit(*me, itr->second.Passenger.Guid))
{
if (Player* player = passenger->ToPlayer())
{
if (!sConditionMgr->IsObjectMeetToConditions(player, me, conditions))
{
player->ExitVehicle();
return; // check other pessanger in next tick
}
}
}
}
m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME;
}
else
m_ConditionsTimer -= diff;
}
int32 VehicleAI::Permissible(Creature const* creature)
{
if (creature->IsVehicle())
return PERMIT_BASE_SPECIAL;
return PERMIT_BASE_NO;
}
+119
View File
@@ -0,0 +1,119 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_COMBATAI_H
#define ACORE_COMBATAI_H
#include "ConditionMgr.h"
#include "CreatureAI.h"
#include "CreatureAIImpl.h"
#include "EventMap.h"
class Creature;
class AggressorAI : public CreatureAI
{
public:
explicit AggressorAI(Creature* c) : CreatureAI(c) {}
void UpdateAI(uint32) override;
static int32 Permissible(Creature const* creature);
};
typedef std::vector<uint32> SpellVct;
class CombatAI : public CreatureAI
{
public:
explicit CombatAI(Creature* c) : CreatureAI(c) {}
void InitializeAI() override;
void Reset() override;
void JustEngagedWith(Unit* who) override;
void JustDied(Unit* killer) override;
void UpdateAI(uint32 diff) override;
static int32 Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
protected:
EventMap events;
SpellVct spells;
};
class CasterAI : public CombatAI
{
public:
explicit CasterAI(Creature* c) : CombatAI(c) { m_attackDist = MELEE_RANGE; }
void InitializeAI() override;
void AttackStart(Unit* victim) override { AttackStartCaster(victim, m_attackDist); }
void UpdateAI(uint32 diff) override;
void JustEngagedWith(Unit* /*who*/) override;
private:
float m_attackDist;
};
struct ArcherAI : public CreatureAI
{
public:
explicit ArcherAI(Creature* c);
void AttackStart(Unit* who) override;
void UpdateAI(uint32 diff) override;
static int32 Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
protected:
float m_minRange;
};
struct TurretAI : public CreatureAI
{
public:
explicit TurretAI(Creature* c);
bool CanAIAttack(Unit const* who) const override;
void AttackStart(Unit* who) override;
void UpdateAI(uint32 diff) override;
static int32 Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
protected:
float m_minRange;
};
#define VEHICLE_CONDITION_CHECK_TIME 1000
#define VEHICLE_DISMISS_TIME 5000
struct VehicleAI : public CreatureAI
{
public:
explicit VehicleAI(Creature* creature);
void UpdateAI(uint32 diff) override;
void MoveInLineOfSight(Unit*) override {}
void AttackStart(Unit*) override {}
void OnCharmed(bool apply) override;
static int32 Permissible(Creature const* creature);
private:
void LoadConditions();
void CheckConditions(uint32 diff);
ConditionList conditions;
uint32 m_ConditionsTimer;
bool m_DoDismiss;
uint32 m_DismissTimer;
};
#endif
@@ -0,0 +1,30 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "GameObjectAI.h"
int32 GameObjectAI::Permissible(GameObject const* /*go*/)
{
return PERMIT_BASE_NO;
}
NullGameObjectAI::NullGameObjectAI(GameObject* go) : GameObjectAI(go) { }
int32 NullGameObjectAI::Permissible(GameObject const* /*go*/)
{
return PERMIT_BASE_IDLE;
}
+87
View File
@@ -0,0 +1,87 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_GAMEOBJECTAI_H
#define ACORE_GAMEOBJECTAI_H
#include "CreatureAI.h"
#include "Define.h"
#include "GameObject.h"
#include "Object.h"
#include "QuestDef.h"
class Creature;
class GameObject;
class Unit;
class SpellInfo;
class AC_GAME_API GameObjectAI
{
protected:
GameObject* const me;
public:
explicit GameObjectAI(GameObject* go) : me(go) {}
virtual ~GameObjectAI() {}
virtual void UpdateAI(uint32 /*diff*/) {}
virtual void InitializeAI() { Reset(); }
virtual void Reset() { }
// Pass parameters between AI
virtual void DoAction(int32 /*param = 0 */) {}
virtual void SetGUID(ObjectGuid const& /*guid*/, int32 /*id = 0 */) {}
virtual ObjectGuid GetGUID(int32 /*id = 0 */) const { return ObjectGuid::Empty; }
static int32 Permissible(GameObject const* go);
virtual bool GossipHello(Player* /*player*/, bool /*reportUse*/) { return false; }
virtual bool GossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) { return false; }
virtual bool GossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) { return false; }
virtual bool QuestAccept(Player* /*player*/, Quest const* /*quest*/) { return false; }
virtual bool QuestReward(Player* /*player*/, Quest const* /*quest*/, uint32 /*opt*/) { return false; }
virtual uint32 GetDialogStatus(Player* /*player*/) { return DIALOG_STATUS_SCRIPTED_NO_STATUS; }
virtual void Destroyed(Player* /*player*/, uint32 /*eventId*/) {}
virtual uint32 GetData(uint32 /*id*/) const { return 0; }
virtual void SetData(uint32 /*id*/, uint32 /*value*/) {}
virtual void OnGameEvent(bool /*start*/, uint16 /*eventId*/) {}
virtual void OnStateChanged(uint32 /*state*/, Unit* /*unit*/) {}
virtual void EventInform(uint32 /*eventId*/) {}
virtual void SpellHit(Unit* /*unit*/, SpellInfo const* /*spellInfo*/) {}
virtual bool CanBeSeen(Player const* /*seer*/) { return true; }
// Called when the gameobject summon successfully other creature
virtual void JustSummoned(Creature* /*summon*/) {}
virtual void SummonedCreatureDespawn(Creature* /*summon*/) {}
virtual void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) {}
virtual void SummonedCreatureEvade(Creature* /*summon*/) {}
};
class NullGameObjectAI : public GameObjectAI
{
public:
explicit NullGameObjectAI(GameObject* go);
void UpdateAI(uint32 /*diff*/) override {}
static int32 Permissible(GameObject const* go);
};
#endif
+66
View File
@@ -0,0 +1,66 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "GuardAI.h"
#include "Player.h"
int32 GuardAI::Permissible(Creature const* creature)
{
if (creature->IsGuard())
return PERMIT_BASE_PROACTIVE;
return PERMIT_BASE_NO;
}
GuardAI::GuardAI(Creature* creature) : ScriptedAI(creature)
{
}
void GuardAI::Reset()
{
ScriptedAI::Reset();
}
void GuardAI::EnterEvadeMode(EvadeReason /*why*/)
{
if (!me->IsAlive())
{
me->GetMotionMaster()->MoveIdle();
me->CombatStop(true);
me->GetThreatMgr().ClearAllThreat();
return;
}
LOG_DEBUG("entities.unit", "Guard entry: {} enters evade mode.", me->GetEntry());
me->RemoveAllAuras();
me->GetThreatMgr().ClearAllThreat();
me->CombatStop(true);
// Remove ChaseMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
me->GetMotionMaster()->MoveTargetedHome();
}
void GuardAI::JustDied(Unit* killer)
{
if (!killer)
return;
if (Player* player = killer->GetCharmerOrOwnerPlayerOrPlayerItself())
me->SendZoneUnderAttackMessage(player);
}
+36
View File
@@ -0,0 +1,36 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_GUARDAI_H
#define ACORE_GUARDAI_H
#include "ScriptedCreature.h"
class Creature;
class GuardAI : public ScriptedAI
{
public:
explicit GuardAI(Creature* creature);
static int32 Permissible(Creature const* creature);
void Reset() override;
void EnterEvadeMode(EvadeReason /*why*/) override;
void JustDied(Unit* killer) override;
};
#endif
+123
View File
@@ -0,0 +1,123 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "PassiveAI.h"
#include "Creature.h"
PassiveAI::PassiveAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
PossessedAI::PossessedAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
NullCreatureAI::NullCreatureAI(Creature* c) : CreatureAI(c)
{
me->SetReactState(REACT_PASSIVE);
// TODO: Remove once WorldObject spell casting is ported (triggers won't create combat refs from spell casts)
if (me->IsTrigger())
me->SetIsCombatDisallowed(true);
}
int32 NullCreatureAI::Permissible(Creature const* creature)
{
if (creature->HasNpcFlag(UNIT_NPC_FLAG_SPELLCLICK))
return PERMIT_BASE_PROACTIVE + 50;
if (creature->IsTrigger())
return PERMIT_BASE_PROACTIVE;
return PERMIT_BASE_IDLE;
}
void PassiveAI::UpdateAI(uint32)
{
if (me->IsEngaged() && !me->IsInCombat())
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
}
void PossessedAI::AttackStart(Unit* target)
{
me->Attack(target, true);
}
void PossessedAI::UpdateAI(uint32 /*diff*/)
{
if (me->GetVictim())
{
if (!me->IsValidAttackTarget(me->GetVictim()))
me->AttackStop();
else
DoMeleeAttackIfReady();
}
}
void PossessedAI::JustDied(Unit* /*u*/)
{
// We died while possessed, disable our loot
me->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
}
void PossessedAI::KilledUnit(Unit* /*victim*/)
{
// We killed a creature, disable victim's loot
//if (victim->IsCreature())
// victim->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
}
void CritterAI::JustEngagedWith(Unit* who)
{
if (!me->HasUnitState(UNIT_STATE_FLEEING))
{
me->SetControlled(true, UNIT_STATE_FLEEING, who);
}
}
void CritterAI::MovementInform(uint32 type, uint32 /*id*/)
{
if (type == TIMED_FLEEING_MOTION_TYPE)
{
EnterEvadeMode(EVADE_REASON_OTHER);
}
}
void CritterAI::EnterEvadeMode(EvadeReason why)
{
if (me->HasUnitState(UNIT_STATE_FLEEING))
{
me->SetControlled(false, UNIT_STATE_FLEEING);
}
CreatureAI::EnterEvadeMode(why);
}
int32 CritterAI::Permissible(Creature const* creature)
{
if (creature->IsCritter() && !creature->HasUnitTypeMask(UNIT_MASK_GUARDIAN))
return PERMIT_BASE_PROACTIVE;
return PERMIT_BASE_NO;
}
void TriggerAI::IsSummonedBy(WorldObject* summoner)
{
if (me->m_spells[0])
me->CastSpell(me, me->m_spells[0], false, 0, 0, summoner ? summoner->GetGUID() : ObjectGuid::Empty);
}
int32 TriggerAI::Permissible(Creature const* creature)
{
if (creature->IsTrigger() && creature->m_spells[0])
return PERMIT_BASE_SPECIAL;
return PERMIT_BASE_NO;
}
+93
View File
@@ -0,0 +1,93 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_PASSIVEAI_H
#define ACORE_PASSIVEAI_H
#include "CreatureAI.h"
//#include "CreatureAIImpl.h"
class PassiveAI : public CreatureAI
{
public:
explicit PassiveAI(Creature* c);
void MoveInLineOfSight(Unit*) override {}
void AttackStart(Unit*) override {}
void UpdateAI(uint32) override;
static int32 Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
};
class PossessedAI : public CreatureAI
{
public:
explicit PossessedAI(Creature* c);
void MoveInLineOfSight(Unit*) override {}
void AttackStart(Unit* target) override;
void JustEnteredCombat(Unit* who) override { EngagementStart(who); }
void JustExitedCombat() override { EngagementOver(); }
void JustStartedThreateningMe(Unit*) override {}
void UpdateAI(uint32) override;
void EnterEvadeMode(EvadeReason /*why*/) override {}
void JustDied(Unit*) override;
void KilledUnit(Unit* victim) override;
static int32 Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
};
class NullCreatureAI : public CreatureAI
{
public:
explicit NullCreatureAI(Creature* c);
void MoveInLineOfSight(Unit*) override {}
void AttackStart(Unit*) override {}
void JustStartedThreateningMe(Unit*) override {}
void JustEnteredCombat(Unit*) override {}
void UpdateAI(uint32) override {}
void EnterEvadeMode(EvadeReason /*why*/) override {}
void OnCharmed(bool /*apply*/) override {}
static int32 Permissible(Creature const* creature);
};
class CritterAI : public PassiveAI
{
public:
explicit CritterAI(Creature* c) : PassiveAI(c) { }
void JustEngagedWith(Unit* /*who*/) override;
void EnterEvadeMode(EvadeReason why) override;
void MovementInform(uint32 type, uint32 id) override;
void UpdateAI(uint32 /*diff*/) override { }
static int32 Permissible(Creature const* creature);
};
class TriggerAI : public NullCreatureAI
{
public:
explicit TriggerAI(Creature* c) : NullCreatureAI(c) {}
void IsSummonedBy(WorldObject* summoner) override;
static int32 Permissible(Creature const* creature);
};
#endif
+829
View File
@@ -0,0 +1,829 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "PetAI.h"
#include "CharmInfo.h"
#include "Creature.h"
#include "Errors.h"
#include "Group.h"
#include "ObjectAccessor.h"
#include "Pet.h"
#include "Player.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "Util.h"
int32 PetAI::Permissible(Creature const* creature)
{
if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN))
{
if (reinterpret_cast<Guardian const*>(creature)->GetOwner()->IsPlayer())
return PERMIT_BASE_PROACTIVE;
return PERMIT_BASE_REACTIVE;
}
return PERMIT_BASE_NO;
}
PetAI::PetAI(Creature* c) : CreatureAI(c), i_tracker(TIME_INTERVAL_LOOK)
{
UpdateAllies();
}
bool PetAI::_needToStop()
{
// This is needed for charmed creatures, as once their target was reset other effects can trigger threat
if (me->IsCharmed() && me->GetVictim() == me->GetCharmer())
return true;
// xinef: dont allow to follow targets out of visibility range
if (me->GetExactDist(me->GetVictim()) > me->GetVisibilityRange() - 5.0f)
return true;
// dont allow pets to follow targets far away from owner
if (Unit* owner = me->GetCharmerOrOwner())
if (owner->GetExactDist(me) >= (owner->GetVisibilityRange() - 10.0f))
return true;
return !me->CanCreatureAttack(me->GetVictim());
}
void PetAI::PetStopAttack()
{
_stopAttack();
}
void PetAI::_stopAttack()
{
if (!me->IsAlive())
{
LOG_DEBUG("entities.unit.ai", "Creature stoped attacking cuz his dead [{}]", me->GetGUID().ToString());
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveIdle();
me->CombatStop();
me->GetThreatMgr().RemoveMeFromThreatLists();
return;
}
me->AttackStop();
me->InterruptNonMeleeSpells(false);
me->GetCharmInfo()->SetIsCommandAttack(false);
ClearCharmInfoFlags();
HandleReturnMovement();
}
void PetAI::_doMeleeAttack()
{
// Xinef: Imps cannot attack with melee
if (!_canMeleeAttack())
return;
DoMeleeAttackIfReady();
}
bool PetAI::_canMeleeAttack()
{
combatRange = 0.f;
switch (me->GetEntry())
{
case ENTRY_IMP:
case ENTRY_WATER_ELEMENTAL:
case ENTRY_WATER_ELEMENTAL_PERM:
{
for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i)
{
uint32 spellID = me->GetPetAutoSpellOnPos(i);
switch (spellID)
{
case IMP_FIREBOLT_RANK_1:
case IMP_FIREBOLT_RANK_2:
case IMP_FIREBOLT_RANK_3:
case IMP_FIREBOLT_RANK_4:
case IMP_FIREBOLT_RANK_5:
case IMP_FIREBOLT_RANK_6:
case IMP_FIREBOLT_RANK_7:
case IMP_FIREBOLT_RANK_8:
case IMP_FIREBOLT_RANK_9:
case WATER_ELEMENTAL_WATERBOLT_1:
case WATER_ELEMENTAL_WATERBOLT_2:
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
int32 mana = me->GetPower(POWER_MANA);
if (mana >= spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()))
{
combatRange = spellInfo->GetMaxRange();
return true;
}
}
default:
break;
}
}
return false;
}
default:
break;
}
return true;
}
void PetAI::UpdateAI(uint32 diff)
{
if (!me->IsAlive() || !me->GetCharmInfo())
return;
Unit* owner = me->GetCharmerOrOwner();
if (m_updateAlliesTimer <= diff)
// UpdateAllies self set update timer
UpdateAllies();
else
m_updateAlliesTimer -= diff;
if (me->GetVictim() && me->GetVictim()->IsAlive())
{
// is only necessary to stop casting, the pet must not exit combat
if (me->GetVictim()->HasBreakableByDamageCrowdControlAura(me))
{
me->InterruptNonMeleeSpells(false);
return;
}
if (_needToStop())
{
LOG_DEBUG("entities.unit.ai", "Pet AI stopped attacking [{}]", me->GetGUID().ToString());
_stopAttack();
return;
}
// Check before attacking to prevent pets from leaving stay position
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
{
if (me->GetCharmInfo()->IsCommandAttack() || (me->GetCharmInfo()->IsAtStay() && me->IsWithinMeleeRange(me->GetVictim())))
_doMeleeAttack();
}
else
_doMeleeAttack();
}
else if (!me->GetCharmInfo() || (!me->GetCharmInfo()->GetForcedSpell() && !(me->IsPet() && me->ToPet()->HasTempSpell()) && !me->HasUnitState(UNIT_STATE_CASTING)))
{
if (me->HasReactState(REACT_AGGRESSIVE) || me->GetCharmInfo()->IsAtStay())
{
// Every update we need to check targets only in certain cases
// Aggressive - Allow auto select if owner or pet don't have a target
// Stay - Only pick from pet or owner targets / attackers so targets won't run by
// while chasing our owner. Don't do auto select.
// All other cases (ie: defensive) - Targets are assigned by AttackedBy(), OwnerAttackedBy(), OwnerAttacked(), etc.
Unit* nextTarget = SelectNextTarget(me->HasReactState(REACT_AGGRESSIVE));
if (nextTarget)
AttackStart(nextTarget);
else
HandleReturnMovement();
}
else
HandleReturnMovement();
}
// xinef: charm info must be always available
if (!me->GetCharmInfo())
return;
// Autocast (casted only in combat or persistent spells in any state)
if (!me->HasUnitState(UNIT_STATE_CASTING))
{
if (owner && owner->IsPlayer() && me->GetCharmInfo()->GetForcedSpell() && me->GetCharmInfo()->GetForcedTarget())
{
owner->ToPlayer()->GetSession()->HandlePetActionHelper(me, me->GetGUID(), std::abs(me->GetCharmInfo()->GetForcedSpell()), ACT_ENABLED, me->GetCharmInfo()->GetForcedTarget());
// xinef: if spell was casted properly and we are in passive mode, handle return
if (!me->GetCharmInfo()->GetForcedSpell() && me->HasReactState(REACT_PASSIVE))
{
if (me->HasUnitState(UNIT_STATE_CASTING))
{
me->GetMotionMaster()->Clear(false);
me->StopMoving();
}
else
_stopAttack();
}
return;
}
// xinef: dont allow ghouls to cast spells below 75 energy
if (me->IsPet() && me->ToPet()->IsPetGhoul() && me->GetPower(POWER_ENERGY) < 75)
return;
typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList;
TargetSpellList targetSpellStore;
for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i)
{
uint32 spellID = me->GetPetAutoSpellOnPos(i);
if (!spellID)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
if (!spellInfo)
continue;
if (me->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
continue;
// check spell cooldown, this should be checked in CheckCast...
if (me->HasSpellCooldown(spellInfo->Id))
continue;
if (spellInfo->IsPositive())
{
if (spellInfo->CanBeUsedInCombat())
{
// Check if we're in combat or commanded to attack (exlude auras with infinity duration)
if (!me->IsInCombat() && spellInfo->GetMaxDuration() != -1 && !me->IsPetInCombat())
{
continue;
}
}
Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE);
spell->LoadScripts(); // xinef: load for CanAutoCast (calling CheckPetCast)
bool spellUsed = false;
// Some spells can target enemy or friendly (DK Ghoul's Leap)
// Check for enemy first (pet then owner)
Unit* target = me->getAttackerForHelper();
if (!target && owner)
target = owner->getAttackerForHelper();
if (target)
{
if (CanAttack(target) && spell->CanAutoCast(target))
{
targetSpellStore.emplace_back(target, spell);
spellUsed = true;
}
}
if (spellInfo->HasEffect(SPELL_EFFECT_JUMP_DEST))
{
if (!spellUsed)
delete spell;
continue; // Pets must only jump to target
}
// No enemy, check friendly
if (!spellUsed)
{
for (ObjectGuid const& guid : m_AllySet)
{
Unit* ally = ObjectAccessor::GetUnit(*me, guid);
//only buff targets that are in combat, unless the spell can only be cast while out of combat
if (!ally)
continue;
if (spell->CanAutoCast(ally))
{
targetSpellStore.emplace_back(ally, spell);
spellUsed = true;
break;
}
}
}
// No valid targets at all
if (!spellUsed)
delete spell;
}
else if (me->GetVictim() && CanAttack(me->GetVictim(), spellInfo) && spellInfo->CanBeUsedInCombat())
{
Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE);
if (spell->CanAutoCast(me->GetVictim()))
targetSpellStore.emplace_back(me->GetVictim(), spell);
else
delete spell;
}
}
//found units to cast on to
if (!targetSpellStore.empty())
{
uint32 index = urand(0, targetSpellStore.size() - 1);
Spell* spell = targetSpellStore[index].second;
Unit* target = targetSpellStore[index].first;
targetSpellStore.erase(targetSpellStore.begin() + index);
SpellCastTargets targets;
targets.SetUnitTarget(target);
if (!me->HasInArc(M_PI, target))
{
me->SetInFront(target);
if (target && target->IsPlayer())
me->SendUpdateToPlayer(target->ToPlayer());
if (owner && owner->IsPlayer())
me->SendUpdateToPlayer(owner->ToPlayer());
}
me->AddSpellCooldown(spell->m_spellInfo->Id, 0, 0);
spell->prepare(&targets);
}
// deleted cached Spell objects
for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr)
delete itr->second;
}
}
void PetAI::UpdateAllies()
{
Unit* owner = me->GetCharmerOrOwner();
Group* group = nullptr;
m_updateAlliesTimer = 10 * IN_MILLISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
if (!owner)
return;
else if (owner->IsPlayer())
group = owner->ToPlayer()->GetGroup();
//only pet and owner/not in group->ok
if (m_AllySet.size() == 2 && !group)
return;
//owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
if (group && !group->isRaidGroup() && m_AllySet.size() == (group->GetMembersCount() + 2))
return;
m_AllySet.clear();
m_AllySet.insert(me->GetGUID());
if (group) //add group
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* Target = itr->GetSource();
if (!Target || !Target->IsInMap(owner) || !group->SameSubGroup(owner->ToPlayer(), Target))
continue;
if (Target->GetGUID() == owner->GetGUID())
continue;
m_AllySet.insert(Target->GetGUID());
}
}
else //remove group
m_AllySet.insert(owner->GetGUID());
}
void PetAI::KilledUnit(Unit* victim)
{
// Called from Unit::Kill() in case where pet or owner kills something
// if owner killed this victim, pet may still be attacking something else
if (me->GetVictim() && me->GetVictim() != victim)
return;
// Xinef: if pet is channeling a spell and owner killed something different, dont interrupt it
if (me->HasUnitState(UNIT_STATE_CASTING) && me->GetGuidValue(UNIT_FIELD_CHANNEL_OBJECT) && me->GetGuidValue(UNIT_FIELD_CHANNEL_OBJECT) != victim->GetGUID())
return;
// Clear target just in case. May help problem where health / focus / mana
// regen gets stuck. Also resets attack command.
// Can't use _stopAttack() because that activates movement handlers and ignores
// next target selection
me->AttackStop();
me->InterruptNonMeleeSpells(false);
// Before returning to owner, see if there are more things to attack
if (Unit* nextTarget = SelectNextTarget(false))
AttackStart(nextTarget);
else
HandleReturnMovement(); // Return
}
void PetAI::AttackStart(Unit* target)
{
// Overrides Unit::AttackStart to correctly evaluate Pet states
// Check all pet states to decide if we can attack this target
if (!CanAttack(target))
return;
// Only chase if not commanded to stay or if stay but commanded to attack
DoAttack(target, (!me->GetCharmInfo()->HasCommandState(COMMAND_STAY) || me->GetCharmInfo()->IsCommandAttack()));
}
void PetAI::OwnerAttackedBy(Unit* attacker)
{
// Called when owner takes damage. This function helps keep pets from running off
// simply due to owner gaining aggro.
if (!attacker)
return;
// Passive pets don't do anything
if (me->HasReactState(REACT_PASSIVE))
return;
// Prevent pet from disengaging from current target
if (me->GetVictim() && me->GetVictim()->IsAlive())
return;
// Continue to evaluate and attack if necessary
AttackStart(attacker);
}
void PetAI::OwnerAttacked(Unit* target)
{
// Called when owner attacks something. Allows defensive pets to know
// that they need to assist
// Target might be nullptr if called from spell with invalid cast targets
if (!target)
return;
// Passive pets don't do anything
if (me->HasReactState(REACT_PASSIVE))
return;
// Prevent pet from disengaging from current target
if (me->GetVictim() && me->GetVictim()->IsAlive())
return;
// Continue to evaluate and attack if necessary
AttackStart(target);
}
Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const
{
// Provides next target selection after current target death.
// This function should only be called internally by the AI
// Targets are not evaluated here for being valid targets, that is done in _CanAttack()
// The parameter: allowAutoSelect lets us disable aggressive pet auto targeting for certain situations
// Passive pets don't do next target selection
if (me->HasReactState(REACT_PASSIVE))
return nullptr;
// Check pet attackers first so we don't drag a bunch of targets to the owner
if (Unit* myAttacker = me->getAttackerForHelper())
if (!myAttacker->HasBreakableByDamageCrowdControlAura() && me->CanCreatureAttack(myAttacker))
return myAttacker;
// Check pet's attackers first to prevent dragging mobs back to owner
if (me->HasTauntAura())
{
const Unit::AuraEffectList& tauntAuras = me->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
if (!tauntAuras.empty())
for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr)
if (Unit* caster = (*itr)->GetCaster())
if (me->CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_MOD_DETAUNT, me->GetGUID()))
return caster;
}
// Not sure why we wouldn't have an owner but just in case...
Unit* owner = me->GetCharmerOrOwner();
if (!owner)
return nullptr;
// Check owner attackers
if (Unit* ownerAttacker = owner->getAttackerForHelper())
if (!ownerAttacker->HasBreakableByDamageCrowdControlAura() && me->CanCreatureAttack(ownerAttacker))
return ownerAttacker;
// Check owner victim
// 3.0.2 - Pets now start attacking their owners victim in defensive mode as soon as the hunter does
if (Unit* ownerVictim = owner->GetVictim())
if (me->CanCreatureAttack(ownerVictim))
return ownerVictim;
// Neither pet or owner had a target and aggressive pets can pick any target
// To prevent aggressive pets from chain selecting targets and running off, we
// only select a random target if certain conditions are met.
if (allowAutoSelect)
{
if (!me->GetCharmInfo()->IsReturning() || me->GetCharmInfo()->IsFollowing() || me->GetCharmInfo()->IsAtStay())
{
if (Unit* nearTarget = me->ToCreature()->SelectNearestTargetInAttackDistance(MAX_AGGRO_RADIUS))
{
if (nearTarget->IsPlayer() && nearTarget->ToPlayer()->IsPvP() && !owner->IsPvP()) // If owner is not PvP flagged and target is PvP flagged, do not attack
{
return nullptr; /// @todo: try for another target
}
else
{
return nearTarget;
}
}
}
}
// Default - no valid targets
return nullptr;
}
void PetAI::HandleReturnMovement()
{
// Handles moving the pet back to stay or owner
// Prevent activating movement when under control of spells
// such as "Eyes of the Beast"
if (me->isPossessed())
return;
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
{
if (!me->GetCharmInfo()->IsAtStay() && !me->GetCharmInfo()->IsReturning())
{
if (me->GetCharmInfo()->HasStayPosition())
{
// Return to previous position where stay was clicked
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
{
float x, y, z;
me->GetCharmInfo()->GetStayPosition(x, y, z);
ClearCharmInfoFlags();
me->GetCharmInfo()->SetIsReturning(true);
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MovePoint(me->GetGUID().GetCounter(), x, y, z);
}
}
}
}
else // COMMAND_FOLLOW
{
if (!me->GetCharmInfo()->IsFollowing() && !me->GetCharmInfo()->IsReturning())
{
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
{
ClearCharmInfoFlags();
me->GetCharmInfo()->SetIsReturning(true);
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, me->GetFollowAngle());
}
}
}
me->GetCharmInfo()->SetForcedSpell(0);
me->GetCharmInfo()->SetForcedTargetGUID();
// xinef: remember that npcs summoned by npcs can also be pets
me->GetThreatMgr().ClearAllThreat();
me->ClearInPetCombat();
}
void PetAI::SpellHit(Unit* caster, SpellInfo const* spellInfo)
{
// Xinef: taunt behavior code
if (spellInfo->HasAura(SPELL_AURA_MOD_TAUNT) && !me->HasReactState(REACT_PASSIVE))
{
me->GetCharmInfo()->SetForcedSpell(0);
me->GetCharmInfo()->SetForcedTargetGUID();
if (CanAttack(caster, spellInfo))
{
// Only chase if not commanded to stay or if stay but commanded to attack
DoAttack(caster, (!me->GetCharmInfo()->HasCommandState(COMMAND_STAY) || me->GetCharmInfo()->IsCommandAttack()));
}
}
}
void PetAI::DoAttack(Unit* target, bool chase)
{
// Handles attack with or without chase and also resets flags
// for next update / creature kill
if (me->Attack(target, true))
{
// xinef: properly fix fake combat after pet is sent to attack
if (Unit* owner = me->GetOwner())
owner->SetUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
me->SetUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
// Play sound to let the player know the pet is attacking something it picked on its own
if (me->HasReactState(REACT_AGGRESSIVE) && !me->GetCharmInfo()->IsCommandAttack())
me->SendPetAIReaction(me->GetGUID());
if (chase)
{
bool oldCmdAttack = me->GetCharmInfo()->IsCommandAttack(); // This needs to be reset after other flags are cleared
ClearCharmInfoFlags();
me->GetCharmInfo()->SetIsCommandAttack(oldCmdAttack); // For passive pets commanded to attack so they will use spells
if (_canMeleeAttack())
{
std::optional<ChaseAngle> chaseAngle;
if (combatRange == 0.f && !target->IsPlayer() && !target->IsPet())
chaseAngle.emplace(float(M_PI), float(M_PI_4));
me->GetMotionMaster()->MoveChase(target, ChaseRange(0.f, combatRange), chaseAngle);
}
}
else // (Stay && ((Aggressive || Defensive) && In Melee Range)))
{
ClearCharmInfoFlags();
me->GetCharmInfo()->SetIsAtStay(true);
me->GetMotionMaster()->MovementExpiredOnSlot(MOTION_SLOT_ACTIVE, false);
me->GetMotionMaster()->MoveIdle();
}
}
}
void PetAI::MovementInform(uint32 moveType, uint32 data)
{
// Receives notification when pet reaches stay or follow owner
switch (moveType)
{
case POINT_MOTION_TYPE:
{
// Pet is returning to where stay was clicked. data should be
// pet's GUIDLow since we set that as the waypoint ID
if (data == me->GetGUID().GetCounter() && me->GetCharmInfo()->IsReturning())
{
ClearCharmInfoFlags();
me->GetCharmInfo()->SetIsAtStay(true);
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveIdle();
}
break;
}
case FOLLOW_MOTION_TYPE:
{
// If data is owner's GUIDLow then we've reached follow point,
// otherwise we're probably chasing a creature
if (me->GetCharmerOrOwner() && me->GetCharmInfo() && data == me->GetCharmerOrOwner()->GetGUID().GetCounter() && me->GetCharmInfo()->IsReturning())
{
ClearCharmInfoFlags();
me->GetCharmInfo()->SetIsFollowing(true);
}
break;
}
default:
break;
}
}
bool PetAI::CanAttack(Unit* target, SpellInfo const* spellInfo)
{
// Evaluates wether a pet can attack a specific target based on CommandState, ReactState and other flags
// IMPORTANT: The order in which things are checked is important, be careful if you add or remove checks
// Hmmm...
if (!target)
return false;
if (!target->IsAlive())
{
// xinef: if target is invalid, pet should evade automaticly
// Clear target to prevent getting stuck on dead targets
//me->AttackStop();
//me->InterruptNonMeleeSpells(false);
return false;
}
// xinef: check unit states of pet
if (me->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;
// xinef: pets of mounted players have stunned flag only, check this also
if (me->HasUnitFlag(UNIT_FLAG_STUNNED))
return false;
// pussywizard: TEMP!
if (!me->GetCharmInfo())
{
LOG_INFO("misc", "PetAI::CanAttack (A1) - {}, {}", me->GetEntry(), me->GetOwnerGUID().ToString());
return false;
}
// Passive - passive pets can attack if told to
if (me->HasReactState(REACT_PASSIVE))
return me->GetCharmInfo()->IsCommandAttack();
// CC - mobs under crowd control can be attacked if owner commanded
if (target->HasBreakableByDamageCrowdControlAura() && (!spellInfo || !spellInfo->HasAttribute(SPELL_ATTR4_DAMAGE_DOESNT_BREAK_AURAS)))
return me->GetCharmInfo()->IsCommandAttack();
// Returning - pets ignore attacks only if owner clicked follow
if (me->GetCharmInfo()->IsReturning())
return !me->GetCharmInfo()->IsCommandFollow();
// Stay - can attack if target is within range or commanded to
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
return (me->IsWithinMeleeRange(target) || me->GetCharmInfo()->IsCommandAttack());
// Pets attacking something (or chasing) should only switch targets if owner tells them to
if (me->GetVictim() && me->GetVictim() != target)
{
// Forced change target if it's taunt
if (spellInfo && spellInfo->HasAura(SPELL_AURA_MOD_TAUNT))
{
return true;
}
// Check if our owner selected this target and clicked "attack"
Unit* ownerTarget = nullptr;
Unit* charmerOrOwner = me->GetCharmerOrOwner();
if (charmerOrOwner)
{
if (Player* owner = charmerOrOwner->ToPlayer())
ownerTarget = owner->GetSelectedUnit();
else
ownerTarget = charmerOrOwner->GetVictim();
}
if (ownerTarget && me->GetCharmInfo()->IsCommandAttack())
return (target->GetGUID() == ownerTarget->GetGUID());
}
// Follow
if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
return !me->GetCharmInfo()->IsReturning();
// default, though we shouldn't ever get here
return false;
}
void PetAI::ReceiveEmote(Player* player, uint32 emote)
{
if (me->GetOwnerGUID() && me->GetOwnerGUID() == player->GetGUID())
switch (emote)
{
case TEXT_EMOTE_COWER:
if (me->IsPet() && me->ToPet()->IsPetGhoul())
me->HandleEmoteCommand(/*EMOTE_ONESHOT_ROAR*/EMOTE_ONESHOT_OMNICAST_GHOUL);
break;
case TEXT_EMOTE_ANGRY:
if (me->IsPet() && me->ToPet()->IsPetGhoul())
me->HandleEmoteCommand(/*EMOTE_ONESHOT_COWER*/EMOTE_STATE_STUN);
break;
case TEXT_EMOTE_GLARE:
if (me->IsPet() && me->ToPet()->IsPetGhoul())
me->HandleEmoteCommand(EMOTE_STATE_STUN);
break;
case TEXT_EMOTE_SOOTHE:
if (me->IsPet() && me->ToPet()->IsPetGhoul())
me->HandleEmoteCommand(EMOTE_ONESHOT_OMNICAST_GHOUL);
break;
}
}
void PetAI::ClearCharmInfoFlags()
{
// Quick access to set all flags to FALSE
CharmInfo* ci = me->GetCharmInfo();
if (ci)
{
ci->SetIsAtStay(false);
ci->SetIsCommandAttack(false);
ci->SetIsCommandFollow(false);
ci->SetIsFollowing(false);
ci->SetIsReturning(false);
}
}
void PetAI::AttackedBy(Unit* attacker)
{
// Called when pet takes damage. This function helps keep pets from running off
// simply due to gaining aggro.
if (!attacker)
return;
// Passive pets don't do anything
if (me->HasReactState(REACT_PASSIVE))
return;
// Prevent pet from disengaging from current target
if (me->GetVictim() && me->GetVictim()->IsAlive())
return;
// Continue to evaluate and attack if necessary
AttackStart(attacker);
}
+93
View File
@@ -0,0 +1,93 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_PETAI_H
#define ACORE_PETAI_H
#include "CreatureAI.h"
#include "Timer.h"
class Creature;
class Spell;
enum SpecialPets
{
ENTRY_IMP = 416,
ENTRY_WATER_ELEMENTAL = 510,
ENTRY_WATER_ELEMENTAL_PERM = 37994,
IMP_FIREBOLT_RANK_1 = 3110,
IMP_FIREBOLT_RANK_2 = 7799,
IMP_FIREBOLT_RANK_3 = 7800,
IMP_FIREBOLT_RANK_4 = 7801,
IMP_FIREBOLT_RANK_5 = 7802,
IMP_FIREBOLT_RANK_6 = 11762,
IMP_FIREBOLT_RANK_7 = 11763,
IMP_FIREBOLT_RANK_8 = 27267,
IMP_FIREBOLT_RANK_9 = 47964,
WATER_ELEMENTAL_WATERBOLT_1 = 31707,
WATER_ELEMENTAL_WATERBOLT_2 = 72898
};
class PetAI : public CreatureAI
{
public:
explicit PetAI(Creature* c);
void UpdateAI(uint32) override;
static int32 Permissible(Creature const* creature);
void KilledUnit(Unit* /*victim*/) override;
void AttackStart(Unit* target) override;
void MovementInform(uint32 moveType, uint32 data) override;
void OwnerAttackedBy(Unit* attacker) override;
void OwnerAttacked(Unit* target) override;
void AttackedBy(Unit* attacker) override;
void ReceiveEmote(Player* player, uint32 textEmote) override;
// The following aren't used by the PetAI but need to be defined to override
// default CreatureAI functions which interfere with the PetAI
//
void MoveInLineOfSight(Unit* /*who*/) override {} // CreatureAI interferes with returning pets
void MoveInLineOfSight_Safe(Unit* /*who*/) {} // CreatureAI interferes with returning pets
void EnterEvadeMode(EvadeReason /*why*/) override {} // For fleeing, pets don't use this type of Evade mechanic
void SpellHit(Unit* caster, SpellInfo const* spellInfo) override;
void PetStopAttack() override;
private:
bool _isVisible(Unit*) const;
bool _needToStop(void);
void _stopAttack(void);
void _doMeleeAttack();
bool _canMeleeAttack();
void UpdateAllies();
TimeTracker i_tracker;
GuidSet m_AllySet;
uint32 m_updateAlliesTimer;
float combatRange;
Unit* SelectNextTarget(bool allowAutoSelect) const;
void HandleReturnMovement();
void DoAttack(Unit* target, bool chase);
bool CanAttack(Unit* target, SpellInfo const* spellInfo = nullptr);
void ClearCharmInfoFlags();
};
#endif
+35
View File
@@ -0,0 +1,35 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "ReactorAI.h"
#include "CreatureAIImpl.h"
int32 ReactorAI::Permissible(Creature const* creature)
{
if (creature->IsCivilian() || creature->IsNeutralToAll())
return PERMIT_BASE_REACTIVE;
return PERMIT_BASE_NO;
}
void ReactorAI::UpdateAI(uint32 /*diff*/)
{
if (!UpdateVictim())
return;
DoMeleeAttackIfReady();
}
+35
View File
@@ -0,0 +1,35 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_REACTORAI_H
#define ACORE_REACTORAI_H
#include "CreatureAI.h"
class Unit;
class ReactorAI : public CreatureAI
{
public:
explicit ReactorAI(Creature* c) : CreatureAI(c) {}
void MoveInLineOfSight(Unit*) override {}
void UpdateAI(uint32 diff) override;
static int32 Permissible(Creature const* creature);
};
#endif
+140
View File
@@ -0,0 +1,140 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "TotemAI.h"
#include "CellImpl.h"
#include "Creature.h"
#include "DBCStores.h"
#include "GridNotifiers.h"
#include "ObjectAccessor.h"
#include "SpellMgr.h"
#include "Totem.h"
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
// however, for some reasons removing it would cause a damn linking issue
// there is probably some underlying problem with imports which should properly addressed
// see: https://github.com/azerothcore/azerothcore-wotlk/issues/9766
#include "GridNotifiersImpl.h"
int32 TotemAI::Permissible(Creature const* creature)
{
if (creature->IsTotem())
return PERMIT_BASE_PROACTIVE;
return PERMIT_BASE_NO;
}
TotemAI::TotemAI(Creature* c) : CreatureAI(c)
{
ASSERT(c->IsTotem());
}
void TotemAI::SpellHit(Unit* /*caster*/, SpellInfo const* /*spellInfo*/)
{
}
void TotemAI::DoAction(int32 /*param*/)
{
}
void TotemAI::MoveInLineOfSight(Unit* /*who*/)
{
}
void TotemAI::EnterEvadeMode(EvadeReason /*why*/)
{
me->CombatStop(true);
}
void TotemAI::UpdateAI(uint32 /*diff*/)
{
if (me->ToTotem()->GetTotemType() != TOTEM_ACTIVE)
return;
if (!me->IsAlive())
{
return;
}
if (me->IsNonMeleeSpellCast(false))
{
if (Unit* victim = ObjectAccessor::GetUnit(*me, i_victimGuid))
{
if (!victim || !victim->IsAlive())
{
me->InterruptNonMeleeSpells(false);
}
}
return;
}
// Search spell
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(me->ToTotem()->GetSpell());
if (!spellInfo)
return;
// Get spell range
float max_range = spellInfo->GetMaxRange(false);
// SPELLMOD_RANGE not applied in this place just because not existence range mods for attacking totems
// pointer to appropriate target if found any
Unit* victim = i_victimGuid ? ObjectAccessor::GetUnit(*me, i_victimGuid) : nullptr;
// Search victim if no, not attackable, or out of range, or friendly (possible in case duel end)
if (!victim ||
!victim->isTargetableForAttack(true, me) || !me->IsWithinDistInMap(victim, max_range) ||
me->IsFriendlyTo(victim) || !me->CanSeeOrDetect(victim))
{
victim = nullptr;
Acore::NearestAttackableUnitInObjectRangeCheck u_check(me, me, max_range);
Acore::UnitLastSearcher<Acore::NearestAttackableUnitInObjectRangeCheck> checker(me, victim, u_check);
Cell::VisitObjects(me, checker, max_range);
}
if (!victim && me->GetCharmerOrOwnerOrSelf()->IsInCombat())
{
victim = me->GetCharmerOrOwnerOrSelf()->getAttackerForHelper();
}
// If have target
if (victim)
{
// remember
i_victimGuid = victim->GetGUID();
// attack
me->SetInFront(victim); // client change orientation by self
me->CastSpell(victim, me->ToTotem()->GetSpell(), false);
}
else
i_victimGuid.Clear();
}
void TotemAI::AttackStart(Unit* /*victim*/)
{
// Sentry totem sends ping on attack
if (me->GetEntry() == SENTRY_TOTEM_ENTRY && me->GetOwner()->IsPlayer())
{
WorldPacket data(MSG_MINIMAP_PING, (8 + 4 + 4));
data << me->GetGUID();
data << me->GetPositionX();
data << me->GetPositionY();
me->GetOwner()->ToPlayer()->SendDirectMessage(&data);
}
}
+60
View File
@@ -0,0 +1,60 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_TOTEMAI_H
#define ACORE_TOTEMAI_H
#include "CreatureAI.h"
class Creature;
class Totem;
class TotemAI : public CreatureAI
{
public:
explicit TotemAI(Creature* c);
void MoveInLineOfSight(Unit* who) override;
void AttackStart(Unit* victim) override;
void EnterEvadeMode(EvadeReason /*why*/) override;
void SpellHit(Unit* /*caster*/, SpellInfo const* /*spellInfo*/) override;
void DoAction(int32 param) override;
void UpdateAI(uint32 diff) override;
static int32 Permissible(Creature const* creature);
private:
ObjectGuid i_victimGuid;
};
class KillMagnetEvent : public BasicEvent
{
public:
KillMagnetEvent(Unit& self) : _self(self) { }
bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) override
{
_self.setDeathState(DeathState::JustDied);
return true;
}
protected:
Unit& _self;
};
#endif
+528
View File
@@ -0,0 +1,528 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "UnitAI.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "CreatureAIImpl.h"
#include "Player.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
void UnitAI::AttackStart(Unit* victim)
{
if (victim && me->Attack(victim, true))
me->GetMotionMaster()->MoveChase(victim);
}
void UnitAI::AttackStartCaster(Unit* victim, float dist)
{
if (victim && me->Attack(victim, false))
me->GetMotionMaster()->MoveChase(victim, dist);
}
void UnitAI::DoMeleeAttackIfReady()
{
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
Unit* victim = me->GetVictim();
if (!victim || !victim->IsInWorld())
return;
if (!me->IsWithinMeleeRange(victim))
return;
//Make sure our attack is ready and we aren't currently casting before checking distance
if (me->isAttackReady())
{
// xinef: prevent base and off attack in same time, delay attack at 0.2 sec
if (me->HasOffhandWeaponForAttack())
if (me->getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
me->setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
me->AttackerStateUpdate(victim);
me->resetAttackTimer();
}
if (me->HasOffhandWeaponForAttack() && me->isAttackReady(OFF_ATTACK))
{
// xinef: delay main hand attack if both will hit at the same time (players code)
if (me->getAttackTimer(BASE_ATTACK) < ATTACK_DISPLAY_DELAY)
me->setAttackTimer(BASE_ATTACK, ATTACK_DISPLAY_DELAY);
me->AttackerStateUpdate(victim, OFF_ATTACK);
me->resetAttackTimer(OFF_ATTACK);
}
}
bool UnitAI::DoSpellAttackIfReady(uint32 spell)
{
if (me->HasUnitState(UNIT_STATE_CASTING) || !me->isAttackReady())
return true;
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell))
{
if (me->IsWithinCombatRange(me->GetVictim(), spellInfo->GetMaxRange(false)))
{
me->CastSpell(me->GetVictim(), spell, false);
me->resetAttackTimer();
return true;
}
}
return false;
}
void UnitAI::DoSpellAttackToRandomTargetIfReady(uint32 spell, uint32 threatTablePosition /*= 0*/, float dist /*= 0.f*/, bool playerOnly /*= true*/)
{
if (me->HasUnitState(UNIT_STATE_CASTING) || !me->isAttackReady())
return;
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell))
{
if (Unit* target = SelectTarget(SelectTargetMethod::Random, threatTablePosition, dist, playerOnly))
{
if (me->IsWithinCombatRange(target, spellInfo->GetMaxRange(false)))
{
me->CastSpell(target, spell, false);
me->resetAttackTimer();
}
}
}
}
Unit* UnitAI::SelectTarget(SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, bool withTank, int32 aura)
{
return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
}
void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, bool withTank, int32 aura)
{
SelectTargetList(targetList, num, targetType, position, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
}
float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
return spellInfo ? spellInfo->GetMaxRange(positive) : 0;
}
std::string UnitAI::GetDebugInfo() const
{
std::stringstream sstr;
sstr << std::boolalpha
<< "Me: " << (me ? me->GetDebugInfo() : "NULL");
return sstr.str();
}
SpellCastResult UnitAI::DoAddAuraToAllHostilePlayers(uint32 spellid)
{
if (me->IsInCombat())
{
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
{
if (ref->IsOffline())
continue;
if (Unit* unit = ref->GetVictim())
{
if (unit->IsPlayer())
{
me->AddAura(spellid, unit);
return SPELL_CAST_OK;
}
}
}
}
return SPELL_FAILED_CUSTOM_ERROR;
}
SpellCastResult UnitAI::DoCastToAllHostilePlayers(uint32 spellid, bool triggered)
{
if (me->IsInCombat())
{
for (ThreatReference const* ref : me->GetThreatMgr().GetUnsortedThreatList())
{
if (ref->IsOffline())
continue;
if (Unit* unit = ref->GetVictim())
{
if (unit->IsPlayer())
return me->CastSpell(unit, spellid, triggered);
}
}
}
return SPELL_FAILED_CUSTOM_ERROR;
}
SpellCastResult UnitAI::DoCast(uint32 spellId)
{
Unit* target = nullptr;
switch (AISpellInfo[spellId].target)
{
default:
case AITARGET_SELF:
target = me;
break;
case AITARGET_VICTIM:
target = me->GetVictim();
break;
case AITARGET_ENEMY:
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId))
{
DefaultTargetSelector targetSelector(me, spellInfo->GetMaxRange(false), false, true, 0);
target = SelectTarget(SelectTargetMethod::Random, 0, [&](Unit* target) {
if (!target)
return false;
if (target->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
return false;
}
else
{
if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
return false;
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && target->IsControlledByPlayer())
return false;
}
return targetSelector(target);
});
}
break;
}
case AITARGET_ALLY:
target = me;
break;
case AITARGET_BUFF:
target = me;
break;
case AITARGET_DEBUFF:
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId))
{
float range = spellInfo->GetMaxRange(false);
DefaultTargetSelector defaultTargetSelector(me, range, false, true, -(int32)spellId);
auto targetSelector = [&](Unit* target) {
if (!target)
return false;
if (target->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
return false;
}
else
{
if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
return false;
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && target->IsControlledByPlayer())
return false;
}
return defaultTargetSelector(target);
};
if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM) && targetSelector(me->GetVictim()))
target = me->GetVictim();
else
target = SelectTarget(SelectTargetMethod::Random, 0, targetSelector);
}
break;
}
}
if (target)
me->CastSpell(target, spellId, false);
return SPELL_FAILED_BAD_TARGETS;
}
SpellCastResult UnitAI::DoCast(Unit* victim, uint32 spellId, bool triggered)
{
if (!victim)
return SPELL_FAILED_BAD_TARGETS;
if (me->HasUnitState(UNIT_STATE_CASTING) && !triggered)
return SPELL_FAILED_SPELL_IN_PROGRESS;
return me->CastSpell(victim, spellId, triggered);
}
SpellCastResult UnitAI::DoCastVictim(uint32 spellId, bool triggered)
{
if (Unit* victim = me->GetVictim())
return DoCast(victim, spellId, triggered);
return SPELL_FAILED_BAD_TARGETS;
}
SpellCastResult UnitAI::DoCastAOE(uint32 spellId, bool triggered)
{
if (!triggered && me->HasUnitState(UNIT_STATE_CASTING))
return SPELL_FAILED_SPELL_IN_PROGRESS;
return me->CastSpell((Unit*)nullptr, spellId, triggered);
}
/**
* @brief Cast the spell on a random unit from the threat list
*/
SpellCastResult UnitAI::DoCastRandomTarget(uint32 spellId, uint32 threatTablePosition, float dist, bool playerOnly, bool triggered, bool withTank)
{
if (Unit* target = SelectTarget(SelectTargetMethod::Random, threatTablePosition, dist, playerOnly, withTank))
{
return DoCast(target, spellId, triggered);
}
return SPELL_FAILED_BAD_TARGETS;
}
/**
* @brief Cast spell on the max threat target, which may not always be the current victim.
*
* @param uint32 spellId Spell ID to cast.
* @param uint32 Threat table position.
* @param float dist Distance from caster to target.
* @param bool playerOnly Select players only, excludes pets and other npcs.
* @param bool triggered Triggered cast (full triggered mask).
*
* @return SpellCastResult
*/
SpellCastResult UnitAI::DoCastMaxThreat(uint32 spellId, uint32 threatTablePosition, float dist, bool playerOnly, bool triggered)
{
if (Unit* target = SelectTarget(SelectTargetMethod::MaxThreat, threatTablePosition, dist, playerOnly))
{
return DoCast(target, spellId, triggered);
}
return SPELL_FAILED_BAD_TARGETS;
}
#define UPDATE_TARGET(a) {if (AIInfo->target<a) AIInfo->target=a;}
void UnitAI::FillAISpellInfo()
{
AISpellInfo = new AISpellInfoType[sSpellMgr->GetSpellInfoStoreSize()];
AISpellInfoType* AIInfo = AISpellInfo;
SpellInfo const* spellInfo;
for (uint32 i = 0; i < sSpellMgr->GetSpellInfoStoreSize(); ++i, ++AIInfo)
{
spellInfo = sSpellMgr->GetSpellInfo(i);
if (!spellInfo)
continue;
if (spellInfo->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD))
AIInfo->condition = AICOND_DIE;
else if (spellInfo->IsPassive() || spellInfo->GetDuration() == -1)
AIInfo->condition = AICOND_AGGRO;
else
AIInfo->condition = AICOND_COMBAT;
if (AIInfo->cooldown < spellInfo->RecoveryTime)
AIInfo->cooldown = spellInfo->RecoveryTime;
if (!spellInfo->GetMaxRange(false))
UPDATE_TARGET(AITARGET_SELF)
else
{
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{
uint32 targetType = spellInfo->Effects[j].TargetA.GetTarget();
if (targetType == TARGET_UNIT_TARGET_ENEMY
|| targetType == TARGET_DEST_TARGET_ENEMY)
UPDATE_TARGET(AITARGET_VICTIM)
else if (targetType == TARGET_UNIT_DEST_AREA_ENEMY)
UPDATE_TARGET(AITARGET_ENEMY)
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_APPLY_AURA)
{
if (targetType == TARGET_UNIT_TARGET_ENEMY)
UPDATE_TARGET(AITARGET_DEBUFF)
else if (spellInfo->IsPositive())
UPDATE_TARGET(AITARGET_BUFF)
}
}
}
AIInfo->realCooldown = spellInfo->RecoveryTime + spellInfo->StartRecoveryTime;
AIInfo->maxRange = spellInfo->GetMaxRange(false) * 3 / 4;
}
}
ThreatManager& UnitAI::GetThreatMgr()
{
return me->GetThreatMgr();
}
void UnitAI::SortByDistance(std::list<Unit*>& list, bool ascending)
{
list.sort(Acore::ObjectDistanceOrderPred(me, ascending));
}
void UnitAI::EvadeTimerExpired()
{
Creature* creature = me->ToCreature();
if (!creature)
return;
CreatureAI* ai = creature->AI();
if (!ai)
return;
// Check if we can teleport an unreachable player first
if (ObjectGuid targetGuid = creature->GetCannotReachTarget())
{
if (Player* player = ObjectAccessor::GetPlayer(*creature, targetGuid))
{
if (creature->IsEngagedBy(player) && ai->OnTeleportUnreacheablePlayer(player))
{
creature->SetCannotReachTarget();
return;
}
}
}
if (creature->GetMap()->IsRaid())
{
creature->GetCombatManager().ContinueEvadeRegen();
return;
}
// If only one target, enter evade mode
if (creature->GetThreatMgr().GetThreatListSize() <= 1)
{
ai->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_PATH);
return;
}
// Multiple targets - clear threat on unreachable target and try another
if (ObjectGuid targetGuid = creature->GetCannotReachTarget())
{
if (Unit* target = ObjectAccessor::GetUnit(*creature, targetGuid))
{
if (creature->GetThreatMgr().IsThreatenedBy(target))
{
creature->GetThreatMgr().ClearThreat(target);
creature->SetCannotReachTarget();
return;
}
}
}
// Fallback - enter evade
ai->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_PATH);
}
//Enable PlayerAI when charmed
void PlayerAI::OnCharmed(bool apply)
{
me->IsAIEnabled = apply;
}
void SimpleCharmedAI::UpdateAI(uint32 /*diff*/)
{
Creature* charmer = me->GetCharmer()->ToCreature();
//kill self if charm aura has infinite duration
if (charmer->IsInEvadeMode())
{
Unit::AuraEffectList const& auras = me->GetAuraEffectsByType(SPELL_AURA_MOD_CHARM);
for (Unit::AuraEffectList::const_iterator iter = auras.begin(); iter != auras.end(); ++iter)
if ((*iter)->GetCasterGUID() == charmer->GetGUID() && (*iter)->GetBase()->IsPermanent())
{
Unit::Kill(charmer, me);
return;
}
}
if (!charmer->IsInCombat())
me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, me->GetFollowAngle());
Unit* target = me->GetVictim();
if (!target || !charmer->IsValidAttackTarget(target))
AttackStart(charmer->SelectNearestTargetInAttackDistance(ATTACK_DISTANCE));
}
SpellTargetSelector::SpellTargetSelector(Unit* caster, uint32 spellId) :
_caster(caster), _spellInfo(sSpellMgr->GetSpellForDifficultyFromSpell(sSpellMgr->GetSpellInfo(spellId), caster))
{
ASSERT(_spellInfo);
}
bool SpellTargetSelector::operator()(Unit const* target) const
{
if (!target)
return false;
if (_spellInfo->CheckTarget(_caster, target) != SPELL_CAST_OK)
return false;
// copypasta from Spell::CheckRange
uint32 range_type = _spellInfo->RangeEntry ? _spellInfo->RangeEntry->Flags : 0;
float max_range = _caster->GetSpellMaxRangeForTarget(target, _spellInfo);
float min_range = _caster->GetSpellMinRangeForTarget(target, _spellInfo);
if (target && target != _caster)
{
if (range_type == SPELL_RANGE_MELEE)
{
// Because of lag, we can not check too strictly here.
if (!_caster->IsWithinMeleeRange(target, max_range))
return false;
}
else if (!_caster->IsWithinCombatRange(target, max_range))
return false;
if (range_type == SPELL_RANGE_RANGED)
{
if (_caster->IsWithinMeleeRange(target))
return false;
}
else if (min_range && _caster->IsWithinCombatRange(target, min_range)) // skip this check if min_range = 0
return false;
}
return true;
}
bool NonTankTargetSelector::operator()(Unit const* target) const
{
if (!target)
return false;
if (_playerOnly && !target->IsPlayer())
return false;
if (Unit* currentVictim = _source->GetThreatMgr().GetCurrentVictim())
return target != currentVictim;
return target != _source->GetVictim();
}
+448
View File
@@ -0,0 +1,448 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_UNITAI_H
#define ACORE_UNITAI_H
#include "Containers.h"
#include "Define.h"
#include "Unit.h"
#include <list>
#define CAST_AI(a, b) (dynamic_cast<a*>(b))
#define ENSURE_AI(a,b) (EnsureAI<a>(b))
template<class T, class U>
T* EnsureAI(U* ai)
{
T* cast_ai = dynamic_cast<T*>(ai);
ASSERT(cast_ai);
return cast_ai;
}
class Player;
class Quest;
class Unit;
struct AISpellInfoType;
// Selection method used by SelectTarget
enum class SelectTargetMethod
{
Random, // just pick a random target
MaxThreat, // prefer targets higher in the threat list
MinThreat, // prefer targets lower in the threat list
MaxDistance, // prefer targets further from us
MinDistance // prefer targets closer to us
};
// default predicate function to select target based on distance, player and/or aura criteria
struct DefaultTargetSelector : public Acore::unary_function<Unit*, bool>
{
Unit const* me;
float m_dist;
Unit const* except;
bool m_playerOnly;
int32 m_aura;
// unit: the reference unit
// dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit
// playerOnly: self explaining
// withMainTank: allow current tank to be selected
// aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, bool withMainTank, int32 aura) : me(unit), m_dist(dist), except(!withMainTank ? me->GetThreatMgr().GetLastVictim() : nullptr), m_playerOnly(playerOnly), m_aura(aura) {}
bool operator()(Unit const* target) const
{
if (!me)
return false;
if (!target)
return false;
if (target == except)
return false;
if (m_playerOnly && (!target->IsPlayer()))
return false;
if (m_dist > 0.0f && !me->IsWithinCombatRange(target, m_dist))
return false;
if (m_dist < 0.0f && me->IsWithinCombatRange(target, -m_dist))
return false;
if (m_aura)
{
if (m_aura > 0)
{
if (!target->HasAura(m_aura))
return false;
}
else
{
if (target->HasAura(-m_aura))
return false;
}
}
return true;
}
};
// Target selector for spell casts checking range, auras and attributes
/// @todo: Add more checks from Spell::CheckCast
struct SpellTargetSelector : public Acore::unary_function<Unit*, bool>
{
public:
SpellTargetSelector(Unit* caster, uint32 spellId);
bool operator()(Unit const* target) const;
private:
Unit const* _caster;
SpellInfo const* _spellInfo;
};
// Very simple target selector, will just skip main target
// NOTE: When passing to UnitAI::SelectTarget remember to use 0 as position for random selection
// because tank will not be in the temporary list
struct NonTankTargetSelector : public Acore::unary_function<Unit*, bool>
{
public:
NonTankTargetSelector(Creature* source, bool playerOnly = true) : _source(source), _playerOnly(playerOnly) { }
bool operator()(Unit const* target) const;
private:
Creature* _source;
bool _playerOnly;
};
// Simple selector for units using mana
struct PowerUsersSelector : public Acore::unary_function<Unit*, bool>
{
Unit const* _me;
Powers const _power;
float const _dist;
bool const _playerOnly;
bool const _withTank;
PowerUsersSelector(Unit const* unit, Powers power, float dist, bool playerOnly, bool withTank = true) : _me(unit), _power(power), _dist(dist), _playerOnly(playerOnly), _withTank(withTank) { }
bool operator()(Unit const* target) const
{
if (!_me || !target)
return false;
if (!_withTank && target == _me->GetThreatMgr().GetLastVictim())
return false;
if (target->getPowerType() != _power)
return false;
if (_playerOnly && !target->IsPlayer())
return false;
if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist))
return false;
if (_dist < 0.0f && _me->IsWithinCombatRange(target, -_dist))
return false;
return true;
}
};
// Simple selector based on range and Los
struct RangeSelector : public Acore::unary_function<Unit*, bool>
{
RangeSelector(Unit const* unit, float maxDist, bool playerOnly, bool inLos, float minDist = 0.f) : _me(unit), _minDist(minDist), _maxDist(maxDist), _playerOnly(playerOnly), _inLos(inLos) {}
bool operator()(Unit const* target) const
{
if (!_me || !target)
return false;
if (_playerOnly && !target->IsPlayer())
return false;
if (_maxDist > 0.0f && !_me->IsInRange(target, _minDist, _maxDist))
return false;
if (_inLos && !_me->IsWithinLOSInMap(target))
return false;
return true;
}
private:
Unit const* _me;
float _minDist, _maxDist;
bool _playerOnly;
bool _inLos;
};
class UnitAI
{
protected:
Unit* const me;
public:
explicit UnitAI(Unit* unit) : me(unit) {}
virtual ~UnitAI() {}
virtual bool CanAIAttack(Unit const* /*target*/) const { return true; }
virtual void AttackStart(Unit* /*target*/); /// @brief Use to start attacking a target. Called just before JustEngagedWith()
virtual void UpdateAI(uint32 /*diff*/) = 0;
virtual void InitializeAI() { if (!me->isDead()) Reset(); }
virtual void Reset() {};
/// @brief Called when unit is charmed
virtual void OnCharmed(bool apply) = 0;
// Pass parameters between AI
virtual void DoAction(int32 /*param*/) {}
virtual uint32 GetData(uint32 /*id = 0*/) const { return 0; }
virtual void SetData(uint32 /*id*/, uint32 /*value*/) {}
virtual void SetGUID(ObjectGuid const& /*guid*/, int32 /*id*/ = 0) {}
virtual ObjectGuid GetGUID(int32 /*id*/ = 0) const { return ObjectGuid::Empty; }
// Select the best target (in <targetType> order) from the threat list that fulfill the following:
// - Not among the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat order,
// if <targetType> is SelectTargetMethod::Random).
// - Within at most <dist> yards (if dist > 0.0f)
// - At least -<dist> yards away (if dist < 0.0f)
// - Is a player (if playerOnly = true)
// - Not the current tank (if withTank = false)
// - Has aura with ID <aura> (if aura > 0)
// - Does not have aura with ID -<aura> (if aura < 0)
Unit* SelectTarget(SelectTargetMethod targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0);
// Select the best target (in <targetType> order) satisfying <predicate> from the threat list.
// If <offset> is nonzero, the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat
// order, if <targetType> is SelectTargetMethod::Random) are skipped.
template <class PREDICATE>
Unit* SelectTarget(SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate)
{
ThreatManager& mgr = GetThreatMgr();
// shortcut: if we ignore the first <offset> elements, and there are at most <offset> elements, then we ignore ALL elements
if (mgr.GetThreatListSize() <= position)
return nullptr;
std::list<Unit*> targetList;
SelectTargetList(targetList, mgr.GetThreatListSize(), targetType, position, predicate);
// maybe nothing fulfills the predicate
if (targetList.empty())
return nullptr;
switch (targetType)
{
case SelectTargetMethod::MaxThreat:
case SelectTargetMethod::MinThreat:
case SelectTargetMethod::MaxDistance:
case SelectTargetMethod::MinDistance:
return targetList.front();
case SelectTargetMethod::Random:
return Acore::Containers::SelectRandomContainerElement(targetList);
default:
return nullptr;
}
}
/** @brief Select the best (up to) <num> targets (in <targetType> order) from the threat list that fulfill the following:
* - Not among the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat order, if <targetType> is SelectTargetMethod::Random).
* - Within at most <dist> yards (if dist > 0.0f)
* - At least -<dist> yards away (if dist < 0.0f)
* - Is a player (if playerOnly = true)
* - Not the current tank (if withTank = false)
* - Has aura with ID <aura> (if aura > 0)
* - Does not have aura with ID -<aura> (if aura < 0)
* The resulting targets are stored in <targetList> (which is cleared first).
*/
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0);
// Select the best (up to) <num> targets (in <targetType> order) satisfying <predicate> from the threat list and stores them in <targetList> (which is cleared first).
// If <offset> is nonzero, the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat
// order, if <targetType> is SelectTargetMethod::Random) are skipped.
template <class PREDICATE>
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate)
{
targetList.clear();
ThreatManager& mgr = GetThreatMgr();
// shortcut: we're gonna ignore the first <offset> elements, and there's at most <offset> elements, so we ignore them all - nothing to do here
if (mgr.GetThreatListSize() <= position)
return;
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
{
for (ThreatReference const* ref : mgr.GetUnsortedThreatList())
{
if (ref->IsOffline())
continue;
targetList.push_back(ref->GetVictim());
}
}
else
{
for (ThreatReference const* ref : mgr.GetSortedThreatList())
{
if (ref->IsOffline())
continue;
targetList.push_back(ref->GetVictim());
}
}
// shortcut: the list isn't gonna get any larger
if (targetList.size() <= position)
{
targetList.clear();
return;
}
// right now, list is unsorted for DISTANCE types - re-sort by SelectTargetMethod::MaxDistance
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
SortByDistance(targetList, targetType == SelectTargetMethod::MinDistance);
// now the list is MAX sorted, reverse for MIN types
if (targetType == SelectTargetMethod::MinThreat)
targetList.reverse();
// ignore the first <offset> elements
while (position)
{
targetList.pop_front();
--position;
}
// then finally filter by predicate
targetList.remove_if([&predicate](Unit* target) { return !predicate(target); });
if (targetList.size() <= num)
return;
if (targetType == SelectTargetMethod::Random)
Acore::Containers::RandomResize(targetList, num);
else
targetList.resize(num);
}
/**
* @brief Called when the unit enters combat
* @note NOTE: Creature engage logic should NOT be here, but in JustEngagedWith, which happens once threat is established!)
*
* @todo Never invoked right now. Preparation for Combat Threat refactor
*/
virtual void JustEnteredCombat(Unit* /*who*/) { }
/**
* @brief Called when the unit leaves combat
*/
virtual void JustExitedCombat() { }
/**
* @brief Called when evade timer expires (target unreachable for too long)
*/
virtual void EvadeTimerExpired();
/// @brief Called at any Damage to any victim (before damage apply)
virtual void DamageDealt(Unit* /*victim*/, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) {}
/** @brief Called at any Damage from any attacker (before damage apply)
*
* @note It use for recalculation damage or special reaction at damage
* for attack reaction use AttackedBy called for non DOT damage in Unit::DealDamage also
*/
virtual void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) {}
/// @brief Called when the creature receives heal
virtual void HealReceived(Unit* /*done_by*/, uint32& /*addhealth*/) {}
/// @brief Called when the creature power updates
virtual void OnPowerUpdate(Powers /*power*/, int32 /*updateVal*/, int32 /*gain*/, uint32 /*currPower*/) {}
/// @brief Called when the unit heals
virtual void HealDone(Unit* /*done_to*/, uint32& /*addhealth*/) {}
/// @brief Called during damage calculations
virtual void OnCalculateMeleeDamageReceived(uint32& /*damage*/, Unit* /*attacker*/) {}
virtual void OnCalculateSpellDamageReceived(int32& /*damage*/, Unit* /*attacker*/) {}
/// @brief Called during calculation when receiving periodic healing or damage (DoT or HoT)
virtual void OnCalculatePeriodicTickReceived(uint32& /*damage*/, Unit* /*attacker*/) {}
void AttackStartCaster(Unit* victim, float dist);
SpellCastResult DoAddAuraToAllHostilePlayers(uint32 spellid);
SpellCastResult DoCast(uint32 spellId);
SpellCastResult DoCast(Unit* victim, uint32 spellId, bool triggered = false);
SpellCastResult DoCastSelf(uint32 spellId, bool triggered = false) { return DoCast(me, spellId, triggered); } /// @brief To specify the caster as target if the spell is self-cast
SpellCastResult DoCastToAllHostilePlayers(uint32 spellid, bool triggered = false);
SpellCastResult DoCastVictim(uint32 spellId, bool triggered = false);
SpellCastResult DoCastAOE(uint32 spellId, bool triggered = false);
SpellCastResult DoCastRandomTarget(uint32 spellId, uint32 threatTablePosition = 0, float dist = 0.0f, bool playerOnly = true, bool triggered = false, bool withTank = true);
/// @brief Cast spell on the top threat target, which may not be the current victim.
SpellCastResult DoCastMaxThreat(uint32 spellId, uint32 threatTablePosition = 0, float dist = 0.0f, bool playerOnly = true, bool triggered = false);
float DoGetSpellMaxRange(uint32 spellId, bool positive = false);
void DoMeleeAttackIfReady();
bool DoSpellAttackIfReady(uint32 spell);
void DoSpellAttackToRandomTargetIfReady(uint32 spell, uint32 threatTablePosition = 0, float dist = 0.f, bool playerOnly = true);
static AISpellInfoType* AISpellInfo;
static void FillAISpellInfo();
/// @brief Called when a summon reaches a waypoint or point movement finished.
virtual void SummonMovementInform(Creature* /*creature*/, uint32 /*motionType*/, uint32 /*point*/) { }
virtual void sGossipHello(Player* /*player*/) {}
virtual void sGossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) {}
virtual void sGossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) {}
virtual void sQuestAccept(Player* /*player*/, Quest const* /*quest*/) {}
virtual void sQuestSelect(Player* /*player*/, Quest const* /*quest*/) {}
virtual void sQuestComplete(Player* /*player*/, Quest const* /*quest*/) {}
virtual void sQuestReward(Player* /*player*/, Quest const* /*quest*/, uint32 /*opt*/) {}
virtual void sOnGameEvent(bool /*start*/, uint16 /*eventId*/) {}
virtual std::string GetDebugInfo() const;
private:
ThreatManager& GetThreatMgr();
void SortByDistance(std::list<Unit*>& list, bool ascending = true);
};
class PlayerAI : public UnitAI
{
protected:
Player* const me;
public:
explicit PlayerAI(Player* player) : UnitAI((Unit*)player), me(player) {}
void OnCharmed(bool apply) override;
};
class SimpleCharmedAI : public PlayerAI
{
public:
void UpdateAI(uint32 diff) override;
SimpleCharmedAI(Player* player): PlayerAI(player) {}
};
#endif
+644
View File
@@ -0,0 +1,644 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "CreatureAI.h"
#include "AreaBoundary.h"
#include "Creature.h"
#include "CreatureAIImpl.h"
#include "CreatureGroups.h"
#include "CreatureTextMgr.h"
#include "GameObjectAI.h"
#include "Log.h"
#include "MapReference.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "TemporarySummon.h"
#include "World.h"
#include "Vehicle.h"
#include "ZoneScript.h"
#include <functional>
//Disable CreatureAI when charmed
void CreatureAI::OnCharmed(bool /* apply */)
{
if (!me->IsCharmed() && !me->LastCharmerGUID.IsEmpty())
{
if (!me->HasReactState(REACT_PASSIVE))
{
if (Unit* lastCharmer = ObjectAccessor::GetUnit(*me, me->LastCharmerGUID))
me->EngageWithTarget(lastCharmer);
}
me->LastCharmerGUID.Clear();
if (!me->IsInCombat())
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
}
// trigger AI switch
me->NeedChangeAI = true;
me->IsAIEnabled = false;
}
AISpellInfoType* UnitAI::AISpellInfo;
AISpellInfoType* GetAISpellInfo(uint32 i) { return &CreatureAI::AISpellInfo[i]; }
/**
* @brief Causes the creature to talk/say the text assigned to their entry in the `creature_text` database table.
*
* @param uint8 id Text ID from `creature_text`.
* @param WorldObject target The target of the speech, in case it has elements such as $n, where the target's name will be referrenced.
* @param Milliseconds delay Delay until the creature says the text line. Creatures will talk immediately by default.
*/
void CreatureAI::Talk(uint8 id, WorldObject const* target /*= nullptr*/, Milliseconds delay /*= 0ms*/)
{
if (delay > 0ms)
{
ObjectGuid targetGuid;
if (target)
targetGuid = target->GetGUID();
me->m_Events.AddEventAtOffset([this, id, targetGuid]()
{
// Target can be nullptr here, it will be handled inside the function.
sCreatureTextMgr->SendChat(me, id, ObjectAccessor::GetUnit(*me, targetGuid));
}, delay);
}
else
sCreatureTextMgr->SendChat(me, id, target);
}
/**
* @brief Returns the summoner creature/object, if the creature is a temporary summon.
*/
WorldObject* CreatureAI::GetSummoner() const
{
if (TempSummon* summon = me->ToTempSummon())
return summon->GetSummoner();
return nullptr;
}
inline bool IsValidCombatTarget(Creature* source, Player* target)
{
if (target->IsGameMaster())
return false;
if (!source->IsInWorld() || !target->IsInWorld())
return false;
if (!source->IsAlive() || !target->IsAlive())
return false;
if (!source->InSamePhase(target))
return false;
if (source->IsInFlight() || target->IsInFlight())
return false;
return true;
}
void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/, float maxRangeToNearestTarget /*= 250.0f*/)
{
if (!creature)
{
creature = me;
}
if (creature->IsInEvadeMode())
{
return;
}
Map* map = creature->GetMap();
if (!map->IsDungeon()) //use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
{
LOG_ERROR("entities.unit.ai", "DoZoneInCombat call for map {} that isn't a dungeon (creature entry = {})", map->GetId(), creature->IsCreature() ? creature->ToCreature()->GetEntry() : 0);
return;
}
Map::PlayerList const& playerList = map->GetPlayers();
if (playerList.IsEmpty())
{
return;
}
for (Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr)
{
if (Player* player = itr->GetSource())
{
if (!IsValidCombatTarget(creature, player))
{
continue;
}
if (!creature->IsWithinDistInMap(player, maxRangeToNearestTarget))
{
continue;
}
creature->EngageWithTarget(player);
for (Unit* pet : player->m_Controlled)
creature->EngageWithTarget(pet);
if (Unit* vehicle = player->GetVehicleBase())
creature->EngageWithTarget(vehicle);
}
}
}
// scripts does not take care about MoveInLineOfSight loops
// MoveInLineOfSight can be called inside another MoveInLineOfSight and cause stack overflow
void CreatureAI::MoveInLineOfSight_Safe(Unit* who)
{
if (m_MoveInLineOfSight_locked)
{
return;
}
m_MoveInLineOfSight_locked = true;
MoveInLineOfSight(who);
m_MoveInLineOfSight_locked = false;
}
void CreatureAI::MoveInLineOfSight(Unit* who)
{
if (me->IsEngaged())
return;
// pussywizard: civilian, non-combat pet or any other NOT HOSTILE TO ANYONE (!)
if (me->IsMoveInLineOfSightDisabled())
if (me->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET || // nothing more to do, return
!who->IsInCombat() || // if not in combat, nothing more to do
!me->IsWithinDist(who, ATTACK_DISTANCE, true, false, false)) // if in combat and in dist - neutral to all can actually assist other creatures
return;
if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who))
AttackStart(who);
}
void CreatureAI::OnOwnerCombatInteraction(Unit* target)
{
if (!target || !me->IsAlive())
return;
// Prevent guardian from disengaging from current target
if (me->GetVictim() && me->GetVictim()->IsAlive())
return;
if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true))
AttackStart(target);
}
// Distract creature, if player gets too close while stealthed/prowling
void CreatureAI::TriggerAlert(Unit const* who) const
{
// If there's no target, or target isn't a player do nothing
if (!who || !who->IsPlayer())
return;
// If this unit isn't an NPC, is already distracted, is in combat, is confused, stunned or fleeing, do nothing
if (!me->IsCreature() || me->IsEngaged() || me->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED))
return;
// If the creature is immune to players, it should not be alerted by them
if (me->IsImmuneToPC())
return;
// Only alert for hostiles!
if (me->IsCivilian() || me->HasReactState(REACT_PASSIVE) || !me->IsHostileTo(who) || !me->_IsTargetAcceptable(who))
return;
// Only alert if target is within line of sight
if (!me->IsWithinLOSInMap(who))
return;
// Send alert sound (if any) for this creature
me->SendAIReaction(AI_REACTION_ALERT);
// Face the unit (stealthed player) and set distracted state for 5 seconds
me->SetFacingTo(me->GetAngle(who->GetPositionX(), who->GetPositionY()));
me->StopMoving();
me->GetMotionMaster()->MoveDistract(5 * IN_MILLISECONDS);
}
void CreatureAI::EnterEvadeMode(EvadeReason why)
{
if (!_EnterEvadeMode(why))
return;
LOG_DEBUG("entities.unit", "Creature {} enters evade mode.", me->GetEntry());
if (!me->GetVehicle()) // otherwise me will be in evade mode forever
{
if (Unit* owner = me->GetCharmerOrOwner())
{
// Owned creatures (pets/guardians) follow their owner — clear evade state
// so they can re-enter combat immediately via CanBeginCombat
me->ClearUnitState(UNIT_STATE_EVADE);
if (!me->IsVehicle()) // vehicles should not follow their owner (passenger)
{
me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE);
}
}
else
{
// Required to prevent attacking creatures that are evading and cause them to reenter combat
// Does not apply to MoveFollow — UNIT_STATE_EVADE is already set from _EnterEvadeMode
me->GetMotionMaster()->MoveTargetedHome();
}
}
Reset();
if (me->IsVehicle()) // use the same sequence of addtoworld, aireset may remove all summons!
{
me->GetVehicleKit()->Reset(true);
}
sScriptMgr->OnUnitEnterEvadeMode(me, why);
// despawn bosses at reset - only verified tbc/woltk bosses with this reset type
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(me->GetEntry());
if (cInfo && cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_HARD_RESET))
{
me->DespawnOnEvade();
}
}
void CreatureAI::JustEnteredCombat(Unit* who)
{
// Creatures without threat list use JustEnteredCombat to trigger engagement
if (!IsEngaged() && !me->CanHaveThreatList())
EngagementStart(who);
}
void CreatureAI::EngagementStart(Unit* who)
{
if (_isEngaged)
{
LOG_ERROR("scripts.ai", "CreatureAI::EngagementStart called even though creature is already engaged. Creature debug info:\n{}", me->GetDebugInfo());
return;
}
_isEngaged = true;
me->AtEngage(who);
}
void CreatureAI::EngagementOver()
{
if (!_isEngaged)
{
LOG_DEBUG("scripts.ai", "CreatureAI::EngagementOver called even though creature is not currently engaged. Creature debug info:\n{}", me->GetDebugInfo());
return;
}
_isEngaged = false;
me->AtDisengage();
}
void CreatureAI::JustExitedCombat()
{
// No-op: synchronous EnterEvadeMode cascades via MemberEvaded and frees
// refs held by upstream iterators (StopAttackFaction crash). EngagementOver
// here also resets scripted fights on brief combat gaps (Valithria).
}
/*void CreatureAI::AttackedBy(Unit* attacker)
{
if (!me->GetVictim())
AttackStart(attacker);
}*/
void CreatureAI::SetGazeOn(Unit* target)
{
if (me->IsValidAttackTarget(target))
{
AttackStart(target);
me->SetReactState(REACT_PASSIVE);
}
}
bool CreatureAI::UpdateVictimWithGaze()
{
if (!me->IsEngaged())
return false;
if (me->HasReactState(REACT_PASSIVE))
{
if (me->GetVictim())
return true;
else
me->SetReactState(REACT_AGGRESSIVE);
}
if (Unit* victim = me->SelectVictim())
AttackStart(victim);
return me->GetVictim();
}
bool CreatureAI::UpdateVictim()
{
if (!me->IsEngaged())
return false;
if (!me->IsAlive())
{
EngagementOver();
return false;
}
// Charmed creatures: the charmer controls target selection, don't interfere
if (me->IsCharmed())
return me->GetVictim() != nullptr;
if (!me->HasReactState(REACT_PASSIVE))
{
if (Unit* victim = me->SelectVictim())
if (victim != me->GetVictim())
AttackStart(victim);
return me->GetVictim() != nullptr;
}
else if (!me->IsInCombat())
{
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
return false;
}
else if (me->GetVictim())
me->AttackStop();
return true;
}
bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/)
{
if (me->IsInEvadeMode())
return false;
if (!me->IsAlive())
{
EngagementOver();
return false;
}
// Set evade state early to prevent recursion: CombatStop below purges combat
// refs, which triggers JustExitedCombat -> EnterEvadeMode -> _EnterEvadeMode.
// The IsInEvadeMode() check above will catch it.
// EnterEvadeMode will clear this for owned creatures (pets/guardians) that
// use MoveFollow instead of MoveTargetedHome.
me->AddUnitState(UNIT_STATE_EVADE);
// don't remove vehicle auras, passengers aren't supposed to drop off the vehicle
// don't remove clone caster on evade (to be verified)
me->RemoveEvadeAuras();
me->ClearComboPointHolders(); // Remove all combo points targeting this unit
me->CombatStop(true);
me->LoadCreaturesAddon(true);
me->SetLootRecipient(nullptr);
me->ResetPlayerDamageReq();
me->ClearLastLeashExtensionTimePtr();
me->SetCannotReachTarget();
if (ZoneScript* zoneScript = me->GetZoneScript() ? me->GetZoneScript() : (ZoneScript*)me->GetInstanceScript())
zoneScript->OnCreatureEvade(me);
if (CreatureGroup* formation = me->GetFormation())
formation->MemberEvaded(me);
if (TempSummon* summon = me->ToTempSummon())
{
if (WorldObject* summoner = summon->GetSummoner())
{
if (summoner->ToCreature() && summoner->ToCreature()->IsAIEnabled)
{
summoner->ToCreature()->AI()->SummonedCreatureEvade(me);
}
else if (summoner->ToGameObject() && summoner->ToGameObject()->AI())
{
summoner->ToGameObject()->AI()->SummonedCreatureEvade(me);
}
}
}
EngagementOver();
return true;
}
void CreatureAI::MoveCircleChecks()
{
Unit *victim = me->GetVictim();
if (
!victim ||
!me->IsFreeToMove() || me->HasUnitMovementFlag(MOVEMENTFLAG_ROOT) ||
!me->IsWithinMeleeRange(victim) || me == victim->GetVictim()
)
{
return;
}
/**
* optimization, disable circling movement for NPC vs NPC combat
*/
if (!sWorld->getBoolConfig(CONFIG_CREATURE_REPOSITION_AGAINST_NPCS) && !victim->IsPlayer() && !victim->IsPet())
return;
me->GetMotionMaster()->MoveCircleTarget(me->GetVictim());
}
void CreatureAI::MoveBackwardsChecks()
{
Unit *victim = me->GetVictim();
if (!victim || !me->IsFreeToMove() || me->HasUnitMovementFlag(MOVEMENTFLAG_ROOT))
return;
/**
* optimization, disable backwards movement for NPC vs NPC combat
*/
if (!sWorld->getBoolConfig(CONFIG_CREATURE_REPOSITION_AGAINST_NPCS) && !victim->IsPlayer() && !victim->IsPet())
{
return;
}
float moveDist = me->GetMeleeRange(victim) / 2;
me->GetMotionMaster()->MoveBackwards(victim, moveDist);
}
int32 CreatureAI::VisualizeBoundary(uint32 duration, Unit* owner, bool fill, bool checkZ) const
{
static constexpr float BOUNDARY_STEP = 5.0f;
static constexpr uint32 BOUNDARY_VISUALIZE_CREATURE = 21659; // Floaty Flavor Eye
static constexpr float BOUNDARY_VISUALIZE_CREATURE_SCALE = 0.25f;
static constexpr uint32 BOUNDARY_MAX_SPAWNS = 8000;
static constexpr float BOUNDARY_MAX_DISTANCE = MAX_SEARCHER_DISTANCE;
float boundaryStep = fill && checkZ ? BOUNDARY_STEP * 2 : BOUNDARY_STEP;
Position const boundaryDirections[6] = {
{boundaryStep, 0, 0 },
{-boundaryStep, 0, 0 },
{0, boundaryStep, 0 },
{0, -boundaryStep, 0 },
{0, 0, boundaryStep },
{0, 0, -boundaryStep}
};
if (!owner)
return -1;
if (!_boundary || _boundary->empty())
return LANG_CREATURE_MOVEMENT_NOT_BOUNDED;
Position startPosition = owner->GetPosition();
if (!IsInBoundary(&startPosition)) // fall back to creature position
{
startPosition = me->GetPosition();
if (!IsInBoundary(&startPosition)) // fall back to creature home position
{
startPosition = me->GetHomePosition();
if (!IsInBoundary(&startPosition))
return LANG_CREATURE_NO_INTERIOR_POINT_FOUND;
}
}
// Helper to spawn visualization creature
auto spawnVisualizationCreature = [owner, duration, checkZ](Position const& pos)
{
if (TempSummon* summon =
owner->SummonCreature(BOUNDARY_VISUALIZE_CREATURE, pos, TEMPSUMMON_TIMED_DESPAWN, duration))
{
summon->SetObjectScale(BOUNDARY_VISUALIZE_CREATURE_SCALE);
summon->SetUnitFlag(UNIT_FLAG_STUNNED);
summon->SetImmuneToAll(true);
summon->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE_2);
if (!checkZ)
summon->SetDisableGravity(false);
}
};
struct PositionHash
{
std::size_t operator()(Position const& pos) const
{
// Convert to fixed precision coordinates.
// We lose precision here, but we don't care about the exact position
int32 x = int32(pos.m_positionX);
int32 y = int32(pos.m_positionY);
int32 z = int32(pos.m_positionZ);
return std::hash<int32_t>()(x) ^ std::hash<int32_t>()(y) ^ std::hash<int32_t>()(z);
}
};
std::unordered_set<Position, PositionHash> visited;
std::queue<Position> queue;
queue.push(startPosition);
visited.insert(startPosition);
uint8 maxDirections = checkZ ? 6 : 4;
uint32 spawns = 0;
while (!queue.empty())
{
Position currentPosition = queue.front();
queue.pop();
for (uint8 i = 0; i < maxDirections; ++i)
{
Position const& direction = boundaryDirections[i];
Position nextPosition = currentPosition;
nextPosition.RelocateOffset(direction);
if (startPosition.GetExactDist(&nextPosition) > BOUNDARY_MAX_DISTANCE)
break;
if (visited.find(nextPosition) != visited.end())
continue; // already visited
visited.insert(nextPosition);
bool isInBoundary = IsInBoundary(&nextPosition);
if ((isInBoundary && fill) || !isInBoundary)
{
spawnVisualizationCreature(currentPosition);
++spawns;
if (spawns > BOUNDARY_MAX_SPAWNS)
return LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED;
}
if (isInBoundary)
queue.push(nextPosition); // continue visiting
}
}
return 0;
}
bool CreatureAI::IsInBoundary(Position const* who) const
{
if (!_boundary)
return true;
if (!who)
who = me;
return (CreatureAI::IsInBounds(*_boundary, who) != _negateBoundary);
}
bool CreatureAI::IsInBounds(CreatureBoundary const& boundary, Position const* pos)
{
for (AreaBoundary const* areaBoundary : boundary)
if (!areaBoundary->IsWithinBoundary(pos))
return false;
return true;
}
bool CreatureAI::CheckInRoom()
{
if (IsInBoundary())
return true;
else
{
EnterEvadeMode(EVADE_REASON_BOUNDARY);
return false;
}
}
void CreatureAI::SetBoundary(CreatureBoundary const* boundary, bool negateBoundaries /*= false*/)
{
_boundary = boundary;
_negateBoundary = negateBoundaries;
me->DoImmediateBoundaryCheck();
}
Creature* CreatureAI::DoSummon(uint32 entry, const Position& pos, uint32 despawnTime, TempSummonType summonType)
{
return me->SummonCreature(entry, pos, summonType, despawnTime);
}
Creature* CreatureAI::DoSummon(uint32 entry, WorldObject* obj, float radius, uint32 despawnTime, TempSummonType summonType)
{
Position pos = obj->GetRandomNearPosition(radius);
return me->SummonCreature(entry, pos, summonType, despawnTime);
}
Creature* CreatureAI::DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius, uint32 despawnTime, TempSummonType summonType)
{
Position pos = obj->GetRandomNearPosition(radius);
pos.m_positionZ += flightZ;
return me->SummonCreature(entry, pos, summonType, despawnTime);
}
+296
View File
@@ -0,0 +1,296 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_CREATUREAI_H
#define ACORE_CREATUREAI_H
#include "AreaBoundary.h"
#include "Creature.h"
#include "EventMap.h"
#include "TaskScheduler.h"
#include "UnitAI.h"
class WorldObject;
class Unit;
class Creature;
class Player;
class SpellInfo;
typedef std::vector<AreaBoundary const*> CreatureBoundary;
#define TIME_INTERVAL_LOOK 5000
#define VISIBILITY_RANGE 10000
//Spell targets used by SelectSpell
enum SelectTargetType
{
SELECT_TARGET_DONTCARE = 0, //All target types allowed
SELECT_TARGET_SELF, //Only Self casting
SELECT_TARGET_SINGLE_ENEMY, //Only Single Enemy
SELECT_TARGET_AOE_ENEMY, //Only AoE Enemy
SELECT_TARGET_ANY_ENEMY, //AoE or Single Enemy
SELECT_TARGET_SINGLE_FRIEND, //Only Single Friend
SELECT_TARGET_AOE_FRIEND, //Only AoE Friend
SELECT_TARGET_ANY_FRIEND, //AoE or Single Friend
};
//Spell Effects used by SelectSpell
enum SelectEffect
{
SELECT_EFFECT_DONTCARE = 0, //All spell effects allowed
SELECT_EFFECT_DAMAGE, //Spell does damage
SELECT_EFFECT_HEALING, //Spell does healing
SELECT_EFFECT_AURA, //Spell applies an aura
};
enum SCEquip
{
EQUIP_NO_CHANGE = -1,
EQUIP_UNEQUIP = 0
};
class CreatureAI : public UnitAI
{
protected:
Creature* const me;
EventMap events;
TaskScheduler scheduler;
bool UpdateVictim();
bool UpdateVictimWithGaze();
void SetGazeOn(Unit* target);
Creature* DoSummon(uint32 entry, Position const& pos, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
Creature* DoSummon(uint32 entry, WorldObject* obj, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
Creature* DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
public:
// EnumUtils: DESCRIBE THIS (in CreatureAI::)
enum EvadeReason
{
EVADE_REASON_NO_HOSTILES, // the creature's threat list is empty
EVADE_REASON_BOUNDARY, // the creature has moved outside its evade boundary
EVADE_REASON_SEQUENCE_BREAK, // this is a boss and the pre-requisite encounters for engaging it are not defeated yet
EVADE_REASON_NO_PATH, // the creature was unable to reach its target for over 5 seconds
EVADE_REASON_OTHER
};
void Talk(uint8 id, WorldObject const* whisperTarget = nullptr, Milliseconds delay = 0ms);
void Talk(uint8 id, Milliseconds delay) { Talk(id, nullptr, delay); }
WorldObject* GetSummoner() const;
explicit CreatureAI(Creature* creature) : UnitAI(creature), me(creature), _boundary(nullptr), _negateBoundary(false), _isEngaged(false), m_MoveInLineOfSight_locked(false) { }
~CreatureAI() override {}
/// @brief Check if creature is currently engaged in combat
bool IsEngaged() const { return _isEngaged; }
void MoveCircleChecks();
void MoveBackwardsChecks();
/// == Reactions At =================================
// Called if IsVisible(Unit* who) is true at each who move, reaction at visibility zone enter
void MoveInLineOfSight_Safe(Unit* who);
// Trigger Creature "Alert" state (creature can see stealthed unit)
void TriggerAlert(Unit const* who) const;
// Called in Creature::Update when deathstate = DEAD. Inherited classes may maniuplate the ability to respawn based on scripted events.
virtual bool CanRespawn() { return true; }
// Whether this creature is an escort NPC (override in escort AI)
virtual bool IsEscortNPC(bool /*onlyIfActive*/ = true) const { return false; }
// Called for reaction at stopping attack at no attackers or targets
virtual void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
// Called for reaction whenever we start being in combat (overridden from UnitAI)
void JustEnteredCombat(Unit* who) override;
// Called for reaction when a new non-offline unit is added to the threat list
virtual void JustStartedThreateningMe(Unit* who) { if (!IsEngaged()) EngagementStart(who); }
/**
* @brief Called for reaction when initially engaged - this happens _after_ JustEnteredCombat
*/
virtual void JustEngagedWith(Unit* /*who*/) {}
// Engagement handling
void EngagementStart(Unit* who);
void EngagementOver();
// Called when combat ends - clears engagement state and triggers evade if needed
void JustExitedCombat() override;
// Called when the creature is killed
virtual void JustDied(Unit* /*killer*/) {}
// Called when the creature kills a unit
virtual void KilledUnit(Unit* /*victim*/) {}
// Called when the creature summon successfully other creature
virtual void JustSummoned(Creature* /*summon*/) {}
virtual void IsSummonedBy(WorldObject* /*summoner*/) {}
virtual void SummonedCreatureDespawn(Creature* /*summon*/) {}
virtual void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) {}
virtual void SummonedCreatureDespawnAll() {}
virtual void SummonedCreatureEvade(Creature* /*summon*/) {}
// Called when hit by a spell
virtual void SpellHit(Unit* /*caster*/, SpellInfo const* /*spell*/) {}
// Called when spell hits a target
virtual void SpellHitTarget(Unit* /*target*/, SpellInfo const* /*spell*/) {}
// Called when the creature begins casting a spell (has cast time or is channeled)
virtual void OnSpellStart(SpellInfo const* /*spell*/) {}
// Called when the creature successfully executes a spell cast
virtual void OnSpellCast(SpellInfo const* /*spell*/) {}
// Called when a spell cast is interrupted or cancelled
virtual void OnSpellFailed(SpellInfo const* /*spell*/) {}
// Called when a channeled spell finishes its full duration
virtual void OnChannelFinished(SpellInfo const* /*spell*/) {}
// Called when the creature is target of hostile action: swing, hostile spell landed, fear/etc)
virtual void AttackedBy(Unit* /*attacker*/) {}
virtual bool IsEscorted() { return false; }
// Called when creature is spawned or respawned (for reseting variables)
virtual void JustRespawned() { Reset(); }
// Called at waypoint reached or point movement finished
virtual void MovementInform(uint32 /*type*/, uint32 /*id*/) {}
// Called at MovePath End
virtual void PathEndReached(uint32 /*pathId*/) {}
/// == Waypoints system =============================
virtual void WaypointPathStarted(uint32 /*pathId*/) { }
virtual void WaypointStarted(uint32 /*nodeId*/, uint32 /*pathId*/) { }
virtual void WaypointReached(uint32 /*nodeId*/, uint32 /*pathId*/) { }
virtual void WaypointPathEnded(uint32 /*nodeId*/, uint32 /*pathId*/) { }
void OnCharmed(bool apply) override;
// Called at reaching home after evade
virtual void JustReachedHome() {}
void DoZoneInCombat(Creature* creature = nullptr, float maxRangeToNearestTarget = 250.0f);
// Called at text emote receive from player
virtual void ReceiveEmote(Player* /*player*/, uint32 /*emoteId*/) {}
// Called when owner takes damage
virtual void OwnerAttackedBy(Unit* attacker) { OnOwnerCombatInteraction(attacker); }
// Called when owner attacks something
virtual void OwnerAttacked(Unit* target) { OnOwnerCombatInteraction(target); }
// Default handler for owner combat interactions — makes controlled creatures auto-engage
void OnOwnerCombatInteraction(Unit* target);
/// == Triggered Actions Requested ==================
// Called when creature attack expected (if creature can and no have current victim)
// Note: for reaction at hostile action must be called AttackedBy function.
//virtual void AttackStart(Unit*) {}
// Called at World update tick
//virtual void UpdateAI(uint32 /*diff*/) {}
/// == State checks =================================
// Is unit visible for MoveInLineOfSight
//virtual bool IsVisible(Unit*) const { return false; }
// called when the corpse of this creature gets removed
virtual void CorpseRemoved(uint32& /*respawnDelay*/) {}
// Called when victim entered water and creature can not enter water
//virtual bool CanReachByRangeAttack(Unit*) { return false; }
/// == Fields =======================================
virtual void PassengerBoarded(Unit* /*passenger*/, int8 /*seatId*/, bool /*apply*/) {}
virtual bool BeforeSpellClick(Unit* /*clicker*/) { return true; }
virtual void OnSpellClick(Unit* /*clicker*/, bool& /*result*/) { }
virtual bool CanSeeAlways(WorldObject const* /*obj*/) { return false; }
virtual bool CanBeSeen(Player const* /*seer*/) { return true; }
virtual bool CanAlwaysBeDetectable(WorldObject const* /*seer*/) { return false; }
virtual void PetStopAttack() { }
// intended for encounter design/debugging. do not use for other purposes. expensive.
int32 VisualizeBoundary(uint32 duration, Unit* owner = nullptr, bool fill = false, bool checkZ = false) const;
// boundary system methods
virtual bool CheckInRoom();
CreatureBoundary const* GetBoundary() const { return _boundary; }
void SetBoundary(CreatureBoundary const* boundary, bool negativeBoundaries = false);
static bool IsInBounds(CreatureBoundary const& boundary, Position const* who);
bool IsInBoundary(Position const* who = nullptr) const;
virtual void CalculateThreat(Unit* /*hatedUnit*/, float& /*threat*/, SpellInfo const* /*threatSpell*/) { }
virtual bool OnTeleportUnreacheablePlayer(Player* /*player*/) { return false; }
// Called when an aura is removed or expires.
virtual void OnAuraRemove(AuraApplication* /*aurApp*/, AuraRemoveMode /*mode*/) { }
virtual void DistancingStarted() {}
virtual void DistancingEnded() {}
protected:
virtual void MoveInLineOfSight(Unit* /*who*/);
bool _EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
CreatureBoundary const* _boundary;
bool _negateBoundary;
bool _isEngaged;
private:
bool m_MoveInLineOfSight_locked;
};
enum Permitions : int32
{
PERMIT_BASE_NO = -1,
PERMIT_BASE_IDLE = 1,
PERMIT_BASE_REACTIVE = 100,
PERMIT_BASE_PROACTIVE = 200,
PERMIT_BASE_FACTION_SPECIFIC = 400,
PERMIT_BASE_SPECIAL = 800
};
#endif
+50
View File
@@ -0,0 +1,50 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_CREATUREAIFACTORY_H
#define ACORE_CREATUREAIFACTORY_H
#include "FactoryHolder.h"
#include "ObjectRegistry.h"
typedef FactoryHolder<CreatureAI, Creature> CreatureAICreator;
struct SelectableAI : public CreatureAICreator, public Permissible<Creature>
{
SelectableAI(std::string const& name) : CreatureAICreator(name), Permissible<Creature>() { }
};
template<class REAL_AI>
struct CreatureAIFactory : public SelectableAI
{
CreatureAIFactory(std::string const& name) : SelectableAI(name) { }
inline CreatureAI* Create(Creature* c) const override
{
return new REAL_AI(c);
}
int32 Permit(Creature const* c) const override
{
return REAL_AI::Permissible(c);
}
};
typedef CreatureAICreator::FactoryHolderRegistry CreatureAIRegistry;
#define sCreatureAIRegistry CreatureAIRegistry::instance()
#endif
+71
View File
@@ -0,0 +1,71 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef CREATUREAIIMPL_H
#define CREATUREAIIMPL_H
#include "Define.h"
#include "TemporarySummon.h"
template<typename First, typename Second, typename... Rest>
static inline First const& RAND(First const& first, Second const& second, Rest const& ... rest)
{
std::reference_wrapper<typename std::add_const<First>::type> const pack[] = { first, second, rest... };
return pack[urand(0, sizeof...(rest) + 1)].get();
}
enum AITarget
{
AITARGET_SELF,
AITARGET_VICTIM,
AITARGET_ENEMY,
AITARGET_ALLY,
AITARGET_BUFF,
AITARGET_DEBUFF,
};
enum AICondition
{
AICOND_AGGRO,
AICOND_COMBAT,
AICOND_DIE,
};
#define AI_DEFAULT_COOLDOWN 5000
struct AISpellInfoType
{
AISpellInfoType() : target(AITARGET_SELF), condition(AICOND_COMBAT)
, cooldown(AI_DEFAULT_COOLDOWN), realCooldown(0), maxRange(0.0f) {}
AITarget target;
AICondition condition;
uint32 cooldown;
uint32 realCooldown;
float maxRange;
};
AISpellInfoType* GetAISpellInfo(uint32 i);
bool InstanceHasScript(WorldObject const* obj, char const* scriptName);
template<class AI, class T>
inline AI* GetInstanceAI(T* obj, char const* scriptName)
{
return InstanceHasScript(obj, scriptName) ? new AI(obj) : nullptr;
}
#endif
+59
View File
@@ -0,0 +1,59 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "CreatureAIRegistry.h"
#include "CombatAI.h"
#include "CreatureAIFactory.h"
#include "GameObjectAIFactory.h"
#include "GuardAI.h"
#include "MovementGenerator.h"
#include "PassiveAI.h"
#include "PetAI.h"
#include "RandomMovementGenerator.h"
#include "ReactorAI.h"
#include "SmartAI.h"
#include "TotemAI.h"
#include "WaypointMovementGenerator.h"
namespace AIRegistry
{
void Initialize()
{
(new CreatureAIFactory<NullCreatureAI>("NullCreatureAI"))->RegisterSelf();
(new CreatureAIFactory<TriggerAI>("TriggerAI"))->RegisterSelf();
(new CreatureAIFactory<AggressorAI>("AggressorAI"))->RegisterSelf();
(new CreatureAIFactory<ReactorAI>("ReactorAI"))->RegisterSelf();
(new CreatureAIFactory<PassiveAI>("PassiveAI"))->RegisterSelf();
(new CreatureAIFactory<CritterAI>("CritterAI"))->RegisterSelf();
(new CreatureAIFactory<GuardAI>("GuardAI"))->RegisterSelf();
(new CreatureAIFactory<PetAI>("PetAI"))->RegisterSelf();
(new CreatureAIFactory<TotemAI>("TotemAI"))->RegisterSelf();
(new CreatureAIFactory<CombatAI>("CombatAI"))->RegisterSelf();
(new CreatureAIFactory<ArcherAI>("ArcherAI"))->RegisterSelf();
(new CreatureAIFactory<TurretAI>("TurretAI"))->RegisterSelf();
(new CreatureAIFactory<VehicleAI>("VehicleAI"))->RegisterSelf();
(new CreatureAIFactory<SmartAI>("SmartAI"))->RegisterSelf();
(new GameObjectAIFactory<NullGameObjectAI>("NullGameObjectAI"))->RegisterSelf();
(new GameObjectAIFactory<GameObjectAI>("GameObjectAI"))->RegisterSelf();
(new GameObjectAIFactory<SmartGameObjectAI>("SmartGameObjectAI"))->RegisterSelf();
(new IdleMovementFactory())->RegisterSelf();
(new MovementGeneratorFactory<RandomMovementGenerator<Creature>>(RANDOM_MOTION_TYPE))->RegisterSelf();
(new MovementGeneratorFactory<WaypointMovementGenerator<Creature>>(WAYPOINT_MOTION_TYPE))->RegisterSelf();
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_CREATUREAIREGISTRY_H
#define ACORE_CREATUREAIREGISTRY_H
namespace AIRegistry
{
void Initialize(void);
}
#endif
+110
View File
@@ -0,0 +1,110 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "CreatureAISelector.h"
#include "Creature.h"
#include "CreatureAIFactory.h"
#include "MovementGenerator.h"
#include "GameObject.h"
#include "ScriptMgr.h"
namespace FactorySelector
{
template <class T, class Value>
inline int32 GetPermitFor(T const* obj, Value const& value)
{
Permissible<T> const* const p = ASSERT_NOTNULL(dynamic_cast<Permissible<T> const*>(value.second.get()));
return p->Permit(obj);
}
template <class T>
struct PermissibleOrderPred
{
public:
PermissibleOrderPred(T const* obj) : _obj(obj) { }
template <class Value>
bool operator()(Value const& left, Value const& right) const
{
return GetPermitFor(_obj, left) < GetPermitFor(_obj, right);
}
private:
T const* const _obj;
};
template <class AI, class T>
inline FactoryHolder<AI, T> const* SelectFactory(T* obj)
{
static_assert(std::is_same<AI, CreatureAI>::value || std::is_same<AI, GameObjectAI>::value, "Invalid template parameter");
static_assert(std::is_same<AI, CreatureAI>::value == std::is_same<T, Creature>::value, "Incompatible AI for type");
static_assert(std::is_same<AI, GameObjectAI>::value == std::is_same<T, GameObject>::value, "Incompatible AI for type");
using AIRegistry = typename FactoryHolder<AI, T>::FactoryHolderRegistry;
// AIName in db
std::string const& aiName = obj->GetAIName();
if (!aiName.empty())
return AIRegistry::instance()->GetRegistryItem(aiName);
// select by permit check
typename AIRegistry::RegistryMapType const& items = AIRegistry::instance()->GetRegisteredItems();
auto itr = std::max_element(items.begin(), items.end(), PermissibleOrderPred<T>(obj));
if (itr != items.end() && GetPermitFor(obj, *itr) >= 0)
return itr->second.get();
// should _never_ happen, Null AI types defined as PERMIT_BASE_IDLE, it must've been found
ABORT();
return nullptr;
}
CreatureAI* SelectAI(Creature* creature)
{
// special pet case, if a tamed creature uses AIName (example SmartAI) we need to override it
if (creature->IsPet())
return ASSERT_NOTNULL(sCreatureAIRegistry->GetRegistryItem("PetAI"))->Create(creature);
// scriptname in db
if (CreatureAI* scriptedAI = sScriptMgr->GetCreatureAI(creature))
return scriptedAI;
return SelectFactory<CreatureAI>(creature)->Create(creature);
}
MovementGenerator* SelectMovementGenerator(Unit* unit)
{
MovementGeneratorType type = IDLE_MOTION_TYPE;
if (Creature* creature = unit->ToCreature())
if (!creature->GetCharmerOrOwnerPlayerOrPlayerItself())
type = creature->GetDefaultMovementType();
MovementGeneratorCreator const* mv_factory = sMovementGeneratorRegistry->GetRegistryItem(type);
return ASSERT_NOTNULL(mv_factory)->Create(unit);
}
GameObjectAI* SelectGameObjectAI(GameObject* go)
{
// scriptname in db
if (GameObjectAI* scriptedAI = sScriptMgr->GetGameObjectAI(go))
return scriptedAI;
return SelectFactory<GameObjectAI>(go)->Create(go);
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_CREATUREAISELECTOR_H
#define ACORE_CREATUREAISELECTOR_H
#include "Define.h"
class CreatureAI;
class Creature;
class MovementGenerator;
class Unit;
class GameObjectAI;
class GameObject;
namespace FactorySelector
{
AC_GAME_API CreatureAI* SelectAI(Creature* creature);
AC_GAME_API MovementGenerator* SelectMovementGenerator(Unit* unit);
AC_GAME_API GameObjectAI* SelectGameObjectAI(GameObject* go);
}
#endif
+51
View File
@@ -0,0 +1,51 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_GAMEOBJECTAIFACTORY_H
#define ACORE_GAMEOBJECTAIFACTORY_H
#include "FactoryHolder.h"
#include "GameObjectAI.h"
typedef FactoryHolder<GameObjectAI, GameObject> GameObjectAICreator;
struct SelectableGameObjectAI : public GameObjectAICreator, public Permissible<GameObject>
{
SelectableGameObjectAI(std::string const& name) : GameObjectAICreator(name), Permissible<GameObject>() { }
};
template<class REAL_GO_AI>
struct GameObjectAIFactory : public SelectableGameObjectAI
{
GameObjectAIFactory(std::string const& name) : SelectableGameObjectAI(name) { }
GameObjectAI* Create(GameObject* go) const override
{
return new REAL_GO_AI(go);
}
int32 Permit(GameObject const* go) const override
{
return REAL_GO_AI::Permissible(go);
}
};
typedef GameObjectAICreator::FactoryHolderRegistry GameObjectAIRegistry;
#define sGameObjectAIRegistry GameObjectAIRegistry::instance()
#endif
@@ -0,0 +1,956 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "Cell.h"
#include "CellImpl.h"
#include "Containers.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "ObjectMgr.h"
#include "ScriptedCreature.h"
#include "Spell.h"
#include "TemporarySummon.h"
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
// however, for some reasons removing it would cause a damn linking issue
// there is probably some underlying problem with imports which should properly addressed
// see: https://github.com/azerothcore/azerothcore-wotlk/issues/9766
#include "GridNotifiersImpl.h"
// Spell summary for ScriptedAI::SelectSpell
struct TSpellSummary
{
uint8 Targets; // set of enum SelectTarget
uint8 Effects; // set of enum SelectEffect
} extern* SpellSummary;
void SummonList::DoZoneInCombat(uint32 entry)
{
for (StorageType::iterator i = storage_.begin(); i != storage_.end();)
{
Creature* summon = ObjectAccessor::GetCreature(*me, *i);
++i;
if (summon && summon->IsAIEnabled
&& (!entry || summon->GetEntry() == entry))
{
summon->AI()->DoZoneInCombat();
}
}
}
void SummonList::DespawnEntry(uint32 entry)
{
for (StorageType::iterator i = storage_.begin(); i != storage_.end();)
{
Creature* summon = ObjectAccessor::GetCreature(*me, *i);
if (!summon)
i = storage_.erase(i);
else if (summon->GetEntry() == entry)
{
i = storage_.erase(i);
summon->DespawnOrUnsummon();
}
else
++i;
}
}
void SummonList::DespawnAll(Milliseconds delay /*= 0ms*/)
{
while (!storage_.empty())
{
Creature* summon = ObjectAccessor::GetCreature(*me, storage_.front());
storage_.pop_front();
if (summon)
summon->DespawnOrUnsummon(delay);
}
}
void SummonList::RemoveNotExisting()
{
for (StorageType::iterator i = storage_.begin(); i != storage_.end();)
{
if (ObjectAccessor::GetCreature(*me, *i))
++i;
else
i = storage_.erase(i);
}
}
bool SummonList::HasEntry(uint32 entry) const
{
for (StorageType::const_iterator i = storage_.begin(); i != storage_.end(); ++i)
{
Creature* summon = ObjectAccessor::GetCreature(*me, *i);
if (summon && summon->GetEntry() == entry)
return true;
}
return false;
}
uint32 SummonList::GetEntryCount(uint32 entry) const
{
uint32 count = 0;
for (StorageType::const_iterator i = storage_.begin(); i != storage_.end(); ++i)
{
Creature* summon = ObjectAccessor::GetCreature(*me, *i);
if (summon && summon->GetEntry() == entry)
++count;
}
return count;
}
void SummonList::Respawn()
{
for (StorageType::iterator i = storage_.begin(); i != storage_.end();)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, *i))
{
summon->Respawn(true);
++i;
}
else
i = storage_.erase(i);
}
}
Creature* SummonList::GetCreatureWithEntry(uint32 entry) const
{
for (StorageType::const_iterator i = storage_.begin(); i != storage_.end(); ++i)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, *i))
if (summon->GetEntry() == entry)
return summon;
}
return nullptr;
}
Creature* SummonList::GetRandomCreatureWithEntry(uint32 entry) const
{
std::vector<ObjectGuid> candidates;
candidates.reserve(storage_.size());
for (auto const guid : storage_)
if (Creature* summon = ObjectAccessor::GetCreature(*me, guid))
if (summon->GetEntry() == entry)
candidates.push_back(guid);
if (candidates.empty())
return nullptr;
ObjectGuid randomGuid = Acore::Containers::SelectRandomContainerElement(candidates);
return ObjectAccessor::GetCreature(*me, randomGuid);
}
bool SummonList::IsAnyCreatureAlive() const
{
for (auto const& guid : storage_)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, guid))
{
if (summon->IsAlive())
{
return true;
}
}
}
return false;
}
bool SummonList::IsAnyCreatureWithEntryAlive(uint32 entry) const
{
for (auto const& guid : storage_)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, guid))
{
if (summon->GetEntry() == entry && summon->IsAlive())
{
return true;
}
}
}
return false;
}
bool SummonList::IsAnyCreatureInCombat() const
{
for (auto const& guid : storage_)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, guid))
{
if (summon->IsInCombat())
{
return true;
}
}
}
return false;
}
ScriptedAI::ScriptedAI(Creature* creature) : CreatureAI(creature),
me(creature)
{
_isHeroic = me->GetMap()->IsHeroic();
_difficulty = Difficulty(me->GetMap()->GetSpawnMode());
_invincible = false;
_canAutoAttack = true;
}
void ScriptedAI::AttackStartNoMove(Unit* who)
{
if (!who)
return;
if (me->Attack(who, true))
DoStartNoMovement(who);
}
void ScriptedAI::AttackStart(Unit* who)
{
if (me->IsCombatMovementAllowed())
CreatureAI::AttackStart(who);
else
AttackStartNoMove(who);
}
void ScriptedAI::UpdateAI(uint32 /*diff*/)
{
//Check if we have a current target
if (!UpdateVictim())
return;
if (IsAutoAttackAllowed())
DoMeleeAttackIfReady();
}
void ScriptedAI::DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/)
{
if (IsInvincible() && damage >= me->GetHealth())
damage = me->GetHealth() - 1;
}
void ScriptedAI::DoStartMovement(Unit* victim, float distance, float angle)
{
if (victim)
me->GetMotionMaster()->MoveChase(victim, distance, angle);
}
void ScriptedAI::DoStartNoMovement(Unit* victim)
{
if (!victim)
return;
me->GetMotionMaster()->MoveIdle();
}
void ScriptedAI::DoStopAttack()
{
if (me->GetVictim())
me->AttackStop();
}
void ScriptedAI::DoRewardPlayersInArea()
{
me->GetMap()->DoForAllPlayers([&](Player* player)
{
if (player->GetFaction() != me->GetCreatureTemplate()->faction && !player->IsGameMaster())
if (player->GetAreaId() == me->GetAreaId())
player->KilledMonsterCredit(me->GetEntry());
});
}
void ScriptedAI::DoCastSpell(Unit* target, SpellInfo const* spellInfo, bool triggered)
{
if (!target || me->IsNonMeleeSpellCast(false))
return;
me->StopMoving();
me->CastSpell(target, spellInfo, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
}
void ScriptedAI::DoPlaySoundToSet(WorldObject* source, uint32 soundId)
{
if (!source)
return;
if (!sSoundEntriesStore.LookupEntry(soundId))
{
LOG_ERROR("entities.unit.ai", "Invalid soundId {} used in DoPlaySoundToSet (Source: {})", soundId, source->GetGUID().ToString());
return;
}
source->PlayDirectSound(soundId);
}
void ScriptedAI::DoPlayMusic(uint32 soundId, bool zone)
{
ObjectList* targets = nullptr;
if (me && me->FindMap())
{
Map::PlayerList const& players = me->GetMap()->GetPlayers();
targets = new ObjectList();
if (!players.IsEmpty())
{
for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i)
if (Player* player = i->GetSource())
{
if (player->GetZoneId() == me->GetZoneId())
{
if (!zone)
{
if (player->GetAreaId() == me->GetAreaId())
targets->push_back(player);
}
else
targets->push_back(player);
}
}
}
}
if (targets)
{
for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr)
{
(*itr)->SendPlayMusic(soundId, true);
}
delete targets;
}
}
Creature* ScriptedAI::DoSpawnCreature(uint32 entry, float offsetX, float offsetY, float offsetZ, float angle, uint32 type, uint32 despawntime)
{
return me->SummonCreature(entry, me->GetPositionX() + offsetX, me->GetPositionY() + offsetY, me->GetPositionZ() + offsetZ, angle, TempSummonType(type), despawntime);
}
void ScriptedAI::ScheduleTimedEvent(Milliseconds timerMin, Milliseconds timerMax, std::function<void()> exec, Milliseconds repeatMin, Milliseconds repeatMax, uint32 uniqueId)
{
if (uniqueId && IsUniqueTimedEventDone(uniqueId))
{
return;
}
scheduler.Schedule(timerMin == 0ms ? timerMax : timerMin, timerMax, [exec, repeatMin, repeatMax, uniqueId](TaskContext context)
{
exec();
if (!uniqueId)
{
repeatMax > 0ms ? context.Repeat(repeatMin, repeatMax) : context.Repeat(repeatMin);
}
});
if (uniqueId)
{
SetUniqueTimedEventDone(uniqueId);
}
}
SpellInfo const* ScriptedAI::SelectSpell(Unit* target, uint32 school, uint32 mechanic, SelectTargetType targets, uint32 powerCostMin, uint32 powerCostMax, float rangeMin, float rangeMax, SelectEffect effects)
{
//No target so we can't cast
if (!target)
return nullptr;
//Silenced so we can't cast
if (me->HasUnitFlag(UNIT_FLAG_SILENCED))
return nullptr;
//Using the extended script system we first create a list of viable spells
SpellInfo const* apSpell[MAX_CREATURE_SPELLS];
memset(apSpell, 0, MAX_CREATURE_SPELLS * sizeof(SpellInfo*));
uint32 spellCount = 0;
SpellInfo const* tempSpell = nullptr;
//Check if each spell is viable(set it to null if not)
for (uint32 i = 0; i < MAX_CREATURE_SPELLS; i++)
{
tempSpell = sSpellMgr->GetSpellInfo(me->m_spells[i]);
//This spell doesn't exist
if (!tempSpell)
continue;
// Targets and Effects checked first as most used restrictions
//Check the spell targets if specified
if (targets && !(SpellSummary[me->m_spells[i]].Targets & (1 << (targets - 1))))
continue;
//Check the type of spell if we are looking for a specific spell type
if (effects && !(SpellSummary[me->m_spells[i]].Effects & (1 << (effects - 1))))
continue;
//Check for school if specified
if (school && (tempSpell->SchoolMask & school) == 0)
continue;
//Check for spell mechanic if specified
if (mechanic && tempSpell->Mechanic != mechanic)
continue;
//Make sure that the spell uses the requested amount of power
if (powerCostMin && tempSpell->ManaCost < powerCostMin)
continue;
if (powerCostMax && tempSpell->ManaCost > powerCostMax)
continue;
//Continue if we don't have the mana to actually cast this spell
if (tempSpell->ManaCost > me->GetPower(Powers(tempSpell->PowerType)))
continue;
//Check if the spell meets our range requirements
if (rangeMin && me->GetSpellMinRangeForTarget(target, tempSpell) < rangeMin)
continue;
if (rangeMax && me->GetSpellMaxRangeForTarget(target, tempSpell) > rangeMax)
continue;
//Check if our target is in range
if (me->IsWithinDistInMap(target, float(me->GetSpellMinRangeForTarget(target, tempSpell))) || !me->IsWithinDistInMap(target, float(me->GetSpellMaxRangeForTarget(target, tempSpell))))
continue;
//All good so lets add it to the spell list
apSpell[spellCount] = tempSpell;
++spellCount;
}
//We got our usable spells so now lets randomly pick one
if (!spellCount)
return nullptr;
return apSpell[urand(0, spellCount - 1)];
}
void ScriptedAI::DoAddThreat(Unit* unit, float amount)
{
if (!unit)
return;
me->GetThreatMgr().AddThreat(unit, amount);
}
void ScriptedAI::DoModifyThreatByPercent(Unit* unit, int32 pct)
{
if (!unit)
return;
me->GetThreatMgr().ModifyThreatByPercent(unit, pct);
}
void ScriptedAI::DoResetThreat(Unit* unit)
{
if (!unit)
return;
me->GetThreatMgr().ResetThreat(unit);
}
void ScriptedAI::DoResetThreatList()
{
if (!me->CanHaveThreatList() || me->GetThreatMgr().IsThreatListEmpty(true))
{
LOG_ERROR("entities.unit.ai", "DoResetThreatList called for creature that either cannot have threat list or has empty threat list (me entry = {})", me->GetEntry());
return;
}
me->GetThreatMgr().ResetAllThreat();
}
float ScriptedAI::DoGetThreat(Unit* unit)
{
if (!unit)
return 0.0f;
return me->GetThreatMgr().GetThreat(unit);
}
void ScriptedAI::DoTeleportPlayer(Unit* unit, float x, float y, float z, float o)
{
if (!unit)
return;
if (Player* player = unit->ToPlayer())
player->TeleportTo(unit->GetMapId(), x, y, z, o, TELE_TO_NOT_LEAVE_COMBAT);
else
LOG_ERROR("entities.unit.ai", "Creature {} Tried to teleport non-player unit {} to x: {} y:{} z: {} o: {}. Aborted.",
me->GetGUID().ToString(), unit->GetGUID().ToString(), x, y, z, o);
}
void ScriptedAI::DoTeleportAll(float x, float y, float z, float o)
{
Map* map = me->GetMap();
if (!map->IsDungeon())
return;
Map::PlayerList const& PlayerList = map->GetPlayers();
for (Map::PlayerList::const_iterator itr = PlayerList.begin(); itr != PlayerList.end(); ++itr)
if (Player* player = itr->GetSource())
if (player->IsAlive())
player->TeleportTo(me->GetMapId(), x, y, z, o, TELE_TO_NOT_LEAVE_COMBAT);
}
Unit* ScriptedAI::DoSelectLowestHpFriendly(float range, uint32 minHPDiff)
{
Unit* unit = nullptr;
Acore::MostHPMissingInRange u_check(me, range, minHPDiff);
Acore::UnitLastSearcher<Acore::MostHPMissingInRange> searcher(me, unit, u_check);
Cell::VisitObjects(me, searcher, range);
return unit;
}
std::list<Creature*> ScriptedAI::DoFindFriendlyCC(float range)
{
std::list<Creature*> list;
Acore::FriendlyCCedInRange u_check(me, range);
Acore::CreatureListSearcher<Acore::FriendlyCCedInRange> searcher(me, list, u_check);
Cell::VisitObjects(me, searcher, range);
return list;
}
std::list<Creature*> ScriptedAI::DoFindFriendlyMissingBuff(float range, uint32 uiSpellid)
{
std::list<Creature*> list;
Acore::FriendlyMissingBuffInRange u_check(me, range, uiSpellid);
Acore::CreatureListSearcher<Acore::FriendlyMissingBuffInRange> searcher(me, list, u_check);
Cell::VisitObjects(me, searcher, range);
return list;
}
Player* ScriptedAI::GetPlayerAtMinimumRange(float minimumRange)
{
Player* player = nullptr;
Acore::PlayerAtMinimumRangeAway check(me, minimumRange);
Acore::PlayerSearcher<Acore::PlayerAtMinimumRangeAway> searcher(me, player, check);
Cell::VisitObjects(me, searcher, minimumRange);
return player;
}
void ScriptedAI::SetEquipmentSlots(bool loadDefault, int32 mainHand /*= EQUIP_NO_CHANGE*/, int32 offHand /*= EQUIP_NO_CHANGE*/, int32 ranged /*= EQUIP_NO_CHANGE*/)
{
if (loadDefault)
{
me->LoadEquipment(me->GetOriginalEquipmentId(), true);
return;
}
if (mainHand >= 0)
{
me->SetVirtualItem(0, uint32(mainHand));
me->UpdateDamagePhysical(BASE_ATTACK);
}
if (offHand >= 0)
{
me->SetVirtualItem(1, uint32(offHand));
}
if (ranged >= 0)
{
me->SetVirtualItem(2, uint32(ranged));
me->UpdateDamagePhysical(RANGED_ATTACK);
}
}
enum eNPCs
{
NPC_BROODLORD = 12017,
NPC_JAN_ALAI = 23578,
NPC_SARTHARION = 28860,
NPC_FREYA = 32906,
};
Player* ScriptedAI::SelectTargetFromPlayerList(float maxdist, uint32 excludeAura, bool mustBeInLOS) const
{
Map::PlayerList const& pList = me->GetMap()->GetPlayers();
std::vector<Player*> tList;
for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr)
{
if (!me->IsWithinDistInMap(itr->GetSource(), maxdist, true, false, false) || !itr->GetSource()->IsAlive() || itr->GetSource()->IsGameMaster())
continue;
if (excludeAura && itr->GetSource()->HasAura(excludeAura))
continue;
if (mustBeInLOS && !me->IsWithinLOSInMap(itr->GetSource()))
continue;
tList.push_back(itr->GetSource());
}
if (!tList.empty())
return tList[urand(0, tList.size() - 1)];
else
return nullptr;
}
// BossAI - for instanced bosses
BossAI::BossAI(Creature* creature, uint32 bossId) : ScriptedAI(creature),
instance(creature->GetInstanceScript()),
summons(creature),
_bossId(bossId),
_nextHealthCheck(0, { }, false)
{
callForHelpRange = 0.0f;
if (instance)
SetBoundary(instance->GetBossBoundary(bossId));
// Prevents updating the scheduler's timer while the creature is casting.
// Clear it in the script if you need it to update while the creature is casting.
scheduler.SetValidator([this]
{
return !me->HasUnitState(UNIT_STATE_CASTING);
});
}
bool BossAI::CanRespawn()
{
if (instance)
{
if (instance->GetBossState(_bossId) == DONE)
{
return false;
}
}
return true;
}
void BossAI::_Reset()
{
if (!me->IsAlive())
return;
if (me->IsEngaged())
return;
me->SetCombatPulseDelay(0);
me->ResetLootMode();
events.Reset();
scheduler.CancelAll();
me->m_Events.KillAllEvents(false);
summons.DespawnAll();
ClearUniqueTimedEventsDone();
_healthCheckEvents.clear();
if (instance)
instance->SetBossState(_bossId, NOT_STARTED);
}
void BossAI::_JustDied()
{
events.Reset();
scheduler.CancelAll();
summons.DespawnAll();
_healthCheckEvents.clear();
if (instance)
{
instance->SetBossState(_bossId, DONE);
instance->SaveToDB();
}
}
void BossAI::_JustEngagedWith()
{
me->SetCombatPulseDelay(5);
me->setActive(true);
DoZoneInCombat();
ScheduleTasks();
if (callForHelpRange)
{
ScheduleTimedEvent(0ms, [&]
{
me->CallForHelp(callForHelpRange);
}, 2s);
}
if (instance)
{
// bosses do not respawn, check only on enter combat
if (!instance->CheckRequiredBosses(_bossId))
{
EnterEvadeMode();
return;
}
instance->SetBossState(_bossId, IN_PROGRESS);
}
}
void BossAI::_EnterEvadeMode(EvadeReason why)
{
CreatureAI::EnterEvadeMode(why);
if (instance && instance->GetBossState(_bossId) != DONE)
{
instance->SetBossState(_bossId, NOT_STARTED);
instance->SaveToDB();
}
}
void BossAI::TeleportCheaters()
{
float x, y, z;
me->GetPosition(x, y, z);
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
Unit* target = pair.second->GetOther(me);
if (target->IsControlledByPlayer() && !IsInBoundary(target))
target->NearTeleportTo(x, y, z, 0);
}
}
void BossAI::JustSummoned(Creature* summon)
{
summons.Summon(summon);
if (me->IsEngaged())
DoZoneInCombat(summon);
}
void BossAI::SummonedCreatureDespawn(Creature* summon)
{
summons.Despawn(summon);
}
void BossAI::SummonedCreatureDespawnAll()
{
summons.DespawnAll();
}
void BossAI::UpdateAI(uint32 diff)
{
if (!UpdateVictim())
{
return;
}
events.Update(diff);
scheduler.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
{
return;
}
while (uint32 const eventId = events.ExecuteEvent())
{
ExecuteEvent(eventId);
if (me->HasUnitState(UNIT_STATE_CASTING))
{
return;
}
}
if (IsAutoAttackAllowed())
DoMeleeAttackIfReady();
}
void BossAI::OnSpellCast(SpellInfo const* spellInfo)
{
ScriptedAI::OnSpellCast(spellInfo);
_CheckHealthAfterCast();
}
void BossAI::OnChannelFinished(SpellInfo const* spellInfo)
{
ScriptedAI::OnChannelFinished(spellInfo);
_CheckHealthAfterCast();
}
void BossAI::OnSpellFailed(SpellInfo const* spellInfo)
{
ScriptedAI::OnSpellFailed(spellInfo);
_CheckHealthAfterCast();
}
void BossAI::_CheckHealthAfterCast()
{
// Check if any health check events are pending (i.e. waiting for the boss to stop casting.
if (_nextHealthCheck.IsPending() && me->IsInCombat())
{
_nextHealthCheck.UpdateStatus(HEALTH_CHECK_PROCESSED);
// This must be delayed because creature might still have unit state casting at this point, which might break scripts.
scheduler.Schedule(1s, [this](TaskContext context)
{
if (me->HasUnitState(UNIT_STATE_CASTING))
context.Repeat();
else
ProcessHealthCheck();
});
}
}
void BossAI::DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask)
{
ScriptedAI::DamageTaken(attacker, damage, damagetype, damageSchoolMask);
if (!_nextHealthCheck.HasBeenProcessed())
{
if (me->HealthBelowPctDamaged(_nextHealthCheck._healthPct, damage))
{
if (!_nextHealthCheck._allowedWhileCasting && me->HasUnitState(UNIT_STATE_CASTING))
{
_nextHealthCheck.UpdateStatus(HEALTH_CHECK_PENDING);
return;
}
ProcessHealthCheck();
}
}
}
/**
* @brief Executes a function once the creature reaches the defined health point percent.
*
* @param healthPct The health percent at which the code will be executed.
* @param exec The fuction to be executed.
* @param allowedWhileCasting If false, the event will not be checked while the creature is casting.
*/
void BossAI::ScheduleHealthCheckEvent(uint32 healthPct, std::function<void()> exec, bool allowedWhileCasting /*=true*/)
{
_healthCheckEvents.push_back(HealthCheckEventData(healthPct, exec, HEALTH_CHECK_SCHEDULED, allowedWhileCasting));
_nextHealthCheck = _healthCheckEvents.front();
};
void BossAI::ScheduleHealthCheckEvent(std::initializer_list<uint8> healthPct, std::function<void()> exec, bool allowedWhileCasting /*=true*/)
{
for (auto const& checks : healthPct)
_healthCheckEvents.push_back(HealthCheckEventData(checks, exec, HEALTH_CHECK_SCHEDULED, allowedWhileCasting));
_nextHealthCheck = _healthCheckEvents.front();
}
void BossAI::ProcessHealthCheck()
{
_nextHealthCheck.UpdateStatus(HEALTH_CHECK_PROCESSED);
_nextHealthCheck._exec();
_healthCheckEvents.remove_if([&](HealthCheckEventData data) -> bool
{
return data._healthPct == _nextHealthCheck._healthPct;
});
if (!_healthCheckEvents.empty())
_nextHealthCheck = _healthCheckEvents.front();
}
void BossAI::ScheduleEnrageTimer(uint32 spellId, Milliseconds timer, uint8 textId /*= 0*/)
{
me->m_Events.AddEventAtOffset([this, spellId, textId]
{
if (!me->IsAlive())
return;
if (textId)
Talk(textId);
DoCastSelf(spellId, true);
}, timer);
}
// WorldBossAI - for non-instanced bosses
WorldBossAI::WorldBossAI(Creature* creature) :
ScriptedAI(creature),
summons(creature)
{
}
void WorldBossAI::_Reset()
{
if (!me->IsAlive())
return;
events.Reset();
summons.DespawnAll();
}
void WorldBossAI::_JustDied()
{
events.Reset();
summons.DespawnAll();
}
void WorldBossAI::_JustEngagedWith()
{
Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true);
if (target)
AttackStart(target);
}
void WorldBossAI::JustSummoned(Creature* summon)
{
summons.Summon(summon);
Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true);
if (target)
summon->AI()->AttackStart(target);
}
void WorldBossAI::SummonedCreatureDespawn(Creature* summon)
{
summons.Despawn(summon);
}
void WorldBossAI::UpdateAI(uint32 diff)
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = events.ExecuteEvent())
ExecuteEvent(eventId);
DoMeleeAttackIfReady();
}
// SD2 grid searchers.
Creature* GetClosestCreatureWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool alive /*= true*/)
{
return source->FindNearestCreature(entry, maxSearchRange, alive);
}
GameObject* GetClosestGameObjectWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool onlySpawned /*= false*/)
{
return source->FindNearestGameObject(entry, maxSearchRange, onlySpawned);
}
void GetCreatureListWithEntryInGrid(std::list<Creature*>& list, WorldObject* source, uint32 entry, float maxSearchRange)
{
source->GetCreatureListWithEntryInGrid(list, entry, maxSearchRange);
}
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& list, WorldObject* source, uint32 entry, float maxSearchRange)
{
source->GetGameObjectListWithEntryInGrid(list, entry, maxSearchRange);
}
void GetDeadCreatureListInGrid(std::list<Creature*>& list, WorldObject* source, float maxSearchRange, bool alive /*= false*/)
{
source->GetDeadCreatureListInGrid(list, maxSearchRange, alive);
}
@@ -0,0 +1,585 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef SCRIPTEDCREATURE_H_
#define SCRIPTEDCREATURE_H_
#include "Creature.h"
#include "CreatureAI.h"
#include "CreatureAIImpl.h"
#include "EventMap.h"
#include "InstanceScript.h"
#include "TaskScheduler.h"
typedef std::list<WorldObject*> ObjectList;
class InstanceScript;
class SummonList
{
public:
typedef GuidList StorageType;
typedef StorageType::iterator iterator;
typedef StorageType::const_iterator const_iterator;
typedef StorageType::size_type size_type;
typedef StorageType::value_type value_type;
explicit SummonList(Creature* creature)
: me(creature)
{ }
// And here we see a problem of original inheritance approach. People started
// to exploit presence of std::list members, so I have to provide wrappers
iterator begin()
{
return storage_.begin();
}
const_iterator begin() const
{
return storage_.begin();
}
iterator end()
{
return storage_.end();
}
const_iterator end() const
{
return storage_.end();
}
iterator erase(iterator i)
{
return storage_.erase(i);
}
bool empty() const
{
return storage_.empty();
}
size_type size() const
{
return storage_.size();
}
void clear()
{
storage_.clear();
}
void Summon(Creature const* summon) { storage_.push_back(summon->GetGUID()); }
void Despawn(Creature const* summon) { storage_.remove(summon->GetGUID()); }
void DespawnEntry(uint32 entry);
void DespawnAll(Milliseconds delay = 0ms);
bool IsAnyCreatureAlive() const;
bool IsAnyCreatureWithEntryAlive(uint32 entry) const;
bool IsAnyCreatureInCombat() const;
template <typename T>
void DespawnIf(T const& predicate)
{
storage_.remove_if(predicate);
}
void DoAction(int32 info, uint16 max = 0)
{
if (max)
RemoveNotExisting(); // pussywizard: when max is set, non existing can be chosen and nothing will happen
StorageType listCopy = storage_;
for (StorageType::const_iterator i = listCopy.begin(); i != listCopy.end(); ++i)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, *i))
if (summon->IsAIEnabled)
summon->AI()->DoAction(info);
}
}
template <class Predicate>
void DoAction(int32 info, Predicate&& predicate, uint16 max = 0)
{
if (max)
RemoveNotExisting(); // pussywizard: when max is set, non existing can be chosen and nothing will happen
// We need to use a copy of SummonList here, otherwise original SummonList would be modified
StorageType listCopy = storage_;
Acore::Containers::RandomResize<StorageType, Predicate>(listCopy, std::forward<Predicate>(predicate), max);
for (auto const& guid : listCopy)
{
Creature* summon = ObjectAccessor::GetCreature(*me, guid);
if (summon && summon->IsAIEnabled)
{
summon->AI()->DoAction(info);
}
else if (!summon)
{
storage_.remove(guid);
}
}
}
void DoForAllSummons(std::function<void(WorldObject*)> exec)
{
// We need to use a copy of SummonList here, otherwise original SummonList would be modified
StorageType listCopy = storage_;
for (auto const& guid : listCopy)
{
if (WorldObject* summon = ObjectAccessor::GetWorldObject(*me, guid))
{
exec(summon);
}
}
}
void DoZoneInCombat(uint32 entry = 0);
void RemoveNotExisting();
bool HasEntry(uint32 entry) const;
uint32 GetEntryCount(uint32 entry) const;
void Respawn();
Creature* GetCreatureWithEntry(uint32 entry) const;
Creature* GetRandomCreatureWithEntry(uint32 entry) const;
private:
Creature* me;
StorageType storage_;
};
class EntryCheckPredicate
{
public:
EntryCheckPredicate(uint32 entry) : _entry(entry) {}
bool operator()(ObjectGuid guid) { return guid.GetEntry() == _entry; }
private:
uint32 _entry;
};
class PlayerOrPetCheck
{
public:
bool operator() (WorldObject* unit) const
{
if (!unit->IsPlayer())
if (!unit->ToUnit()->GetOwnerGUID().IsPlayer())
return true;
return false;
}
};
struct ScriptedAI : public CreatureAI
{
explicit ScriptedAI(Creature* creature);
~ScriptedAI() override {}
// *************
//CreatureAI Functions
// *************
void AttackStartNoMove(Unit* target);
// Called at any Damage from any attacker (before damage apply)
void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override;
//Called at World update tick
void UpdateAI(uint32 diff) override;
//Called at creature death
void JustDied(Unit* /*killer*/) override {}
//Called at creature killing another unit
void KilledUnit(Unit* /*victim*/) override {}
// Called when the creature summon successfully other creature
void JustSummoned(Creature* /*summon*/) override {}
// Called when a summoned creature is despawned
void SummonedCreatureDespawn(Creature* /*summon*/) override {}
// Called when hit by a spell
void SpellHit(Unit* /*caster*/, SpellInfo const* /*spell*/) override {}
// Called when spell hits a target
void SpellHitTarget(Unit* /*target*/, SpellInfo const* /*spell*/) override {}
//Called at waypoint reached or PointMovement end
void MovementInform(uint32 /*type*/, uint32 /*id*/) override {}
// Called when AI is temporarily replaced or put back when possess is applied or removed
void OnPossess(bool /*apply*/) {}
enum class Axis
{
AXIS_X,
AXIS_Y
};
/* This is called for bosses whenever an encounter is happening.
* - Arguments:
* - Position has to be passed as a constant pointer (&Position)
* - Axis is the X or Y axis that is used to decide the position threshold
* - Above decides if the boss position should be above the passed position
* or below.
* Example:
* Hodir is in room until his Y position is below the Door position:
* IsInRoom(doorPosition, AXIS_Y, false);
*/
bool IsInRoom(const Position* pos, Axis axis, bool above)
{
if (!pos)
{
return true;
}
switch (axis)
{
case Axis::AXIS_X:
if ((!above && me->GetPositionX() < pos->GetPositionX()) || me->GetPositionX() > pos->GetPositionX())
{
EnterEvadeMode();
return false;
}
break;
case Axis::AXIS_Y:
if ((!above && me->GetPositionY() < pos->GetPositionY()) || me->GetPositionY() > pos->GetPositionY())
{
EnterEvadeMode();
return false;
}
break;
}
return true;
}
// *************
// Variables
// *************
//Pointer to creature we are manipulating
Creature* me;
// *************
//Pure virtual functions
// *************
//Called at creature reset either by death or evade
void Reset() override {}
//Called at creature aggro either by MoveInLOS or Attack Start
void JustEngagedWith(Unit* /*who*/) override {}
// Called before JustEngagedWith even before the creature is in combat.
void AttackStart(Unit* /*target*/) override;
// *************
//AI Helper Functions
// *************
//Start movement toward victim
void DoStartMovement(Unit* target, float distance = 0.0f, float angle = 0.0f);
//Start no movement on victim
void DoStartNoMovement(Unit* target);
//Stop attack of current victim
void DoStopAttack();
//Reward kill credit to all players from the oposing faction in the area (faction leaders)
void DoRewardPlayersInArea();
//Cast spell by spell info
void DoCastSpell(Unit* target, SpellInfo const* spellInfo, bool triggered = false);
//Plays a sound to all nearby players
void DoPlaySoundToSet(WorldObject* source, uint32 soundId);
//Plays music for all players in the zone (zone = true) or the area (zone = false)
void DoPlayMusic(uint32 soundId, bool zone);
// Add specified amount of threat directly to victim (ignores redirection effects) - also puts victim in combat and engages them if necessary
void DoAddThreat(Unit* unit, float amount);
// Adds/removes the specified percentage from the specified victim's threat (to who, or me if not specified)
void DoModifyThreatByPercent(Unit* unit, int32 pct);
//Drops all threat to 0%. Does not remove players from the threat list
void DoResetThreat(Unit* unit);
// Resets the specified unit's threat list (me if not specified) - does not delete entries, just sets their threat to zero
void DoResetThreatList();
// Returns the threat level of victim towards who (or me if not specified)
float DoGetThreat(Unit* unit);
//Teleports a player without dropping threat (only teleports to same map)
void DoTeleportPlayer(Unit* unit, float x, float y, float z, float o);
void DoTeleportPlayer(Unit* unit, Position pos) { DoTeleportPlayer(unit, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); };
void DoTeleportAll(float x, float y, float z, float o);
//Returns friendly unit with the most amount of hp missing from max hp
Unit* DoSelectLowestHpFriendly(float range, uint32 minHPDiff = 1);
//Returns a list of friendly CC'd units within range
std::list<Creature*> DoFindFriendlyCC(float range);
//Returns a list of all friendly units missing a specific buff within range
std::list<Creature*> DoFindFriendlyMissingBuff(float range, uint32 spellId);
//Return a player with at least minimumRange from me
Player* GetPlayerAtMinimumRange(float minRange);
//Spawns a creature relative to me
Creature* DoSpawnCreature(uint32 entry, float offsetX, float offsetY, float offsetZ, float angle, uint32 type, uint32 despawntime);
bool IsUniqueTimedEventDone(uint32 id) const { return _uniqueTimedEvents.find(id) != _uniqueTimedEvents.end(); }
void SetUniqueTimedEventDone(uint32 id) { _uniqueTimedEvents.insert(id); }
void ResetUniqueTimedEvent(uint32 id) { _uniqueTimedEvents.erase(id); }
void ClearUniqueTimedEventsDone() { _uniqueTimedEvents.clear(); }
// Schedules a timed event using task scheduler.
void ScheduleTimedEvent(Milliseconds timerMin, Milliseconds timerMax, std::function<void()> exec, Milliseconds repeatMin, Milliseconds repeatMax = 0ms, uint32 uniqueId = 0);
void ScheduleTimedEvent(Milliseconds timerMax, std::function<void()> exec, Milliseconds repeatMin, Milliseconds repeatMax = 0ms, uint32 uniqueId = 0) { ScheduleTimedEvent(0ms, timerMax, exec, repeatMin, repeatMax, uniqueId); };
// Schedules a timed event using task scheduler that never repeats. Requires an unique non-zero ID.
void ScheduleUniqueTimedEvent(Milliseconds timer, std::function<void()> exec, uint32 uniqueId) { ScheduleTimedEvent(0ms, timer, exec, 0ms, 0ms, uniqueId); };
bool HealthBelowPct(uint32 pct) const { return me->HealthBelowPct(pct); }
bool HealthAbovePct(uint32 pct) const { return me->HealthAbovePct(pct); }
//Returns spells that meet the specified criteria from the creatures spell list
SpellInfo const* SelectSpell(Unit* target, uint32 school, uint32 mechanic, SelectTargetType targets, uint32 powerCostMin, uint32 powerCostMax, float rangeMin, float rangeMax, SelectEffect effect);
void SetEquipmentSlots(bool loadDefault, int32 mainHand = EQUIP_NO_CHANGE, int32 offHand = EQUIP_NO_CHANGE, int32 ranged = EQUIP_NO_CHANGE);
virtual bool CheckEvadeIfOutOfCombatArea() const { return false; }
// return true for heroic mode. i.e.
// - for dungeon in mode 10-heroic,
// - for raid in mode 10-Heroic
// - for raid in mode 25-heroic
// DO NOT USE to check raid in mode 25-normal.
bool IsHeroic() const { return _isHeroic; }
// return the dungeon or raid difficulty
Difficulty GetDifficulty() const { return _difficulty; }
// return true for 25 man or 25 man heroic mode
bool Is25ManRaid() const { return _difficulty & RAID_DIFFICULTY_MASK_25MAN; }
template<class T> inline
const T& DUNGEON_MODE(const T& normal5, const T& heroic10) const
{
switch (_difficulty)
{
case DUNGEON_DIFFICULTY_NORMAL:
return normal5;
case DUNGEON_DIFFICULTY_HEROIC:
return heroic10;
default:
break;
}
return heroic10;
}
template<class T> inline
const T& RAID_MODE(const T& normal10, const T& normal25) const
{
switch (_difficulty)
{
case RAID_DIFFICULTY_10MAN_NORMAL:
return normal10;
case RAID_DIFFICULTY_25MAN_NORMAL:
return normal25;
default:
break;
}
return normal25;
}
template<class T> inline
const T& RAID_MODE(const T& normal10, const T& normal25, const T& heroic10, const T& heroic25) const
{
switch (_difficulty)
{
case RAID_DIFFICULTY_10MAN_NORMAL:
return normal10;
case RAID_DIFFICULTY_25MAN_NORMAL:
return normal25;
case RAID_DIFFICULTY_10MAN_HEROIC:
return heroic10;
case RAID_DIFFICULTY_25MAN_HEROIC:
return heroic25;
default:
break;
}
return heroic25;
}
Player* SelectTargetFromPlayerList(float maxdist, uint32 excludeAura = 0, bool mustBeInLOS = false) const;
// Allows dropping to 1 HP but prevents creature from dying.
void SetInvincibility(bool apply) { _invincible = apply; };
[[nodiscard]] bool IsInvincible() const { return _invincible; };
// Disables creature auto attacks.
void SetAutoAttackAllowed(bool allow) { _canAutoAttack = allow; };
[[nodiscard]] bool IsAutoAttackAllowed() const { return _canAutoAttack; };
private:
Difficulty _difficulty;
bool _isHeroic;
bool _invincible;
bool _canAutoAttack;
std::unordered_set<uint32> _uniqueTimedEvents;
};
enum HealthCheckStatus
{
HEALTH_CHECK_PROCESSED,
HEALTH_CHECK_SCHEDULED,
HEALTH_CHECK_PENDING
};
struct HealthCheckEventData
{
HealthCheckEventData(uint8 healthPct, std::function<void()> exec, uint8 status = HEALTH_CHECK_SCHEDULED, bool allowedWhileCasting = true, Milliseconds Delay = 0ms) : _healthPct(healthPct), _exec(exec), _status(status), _allowedWhileCasting(allowedWhileCasting), _delay(Delay) { };
uint8 _healthPct;
std::function<void()> _exec;
uint8 _status;
bool _allowedWhileCasting;
Milliseconds _delay;
[[nodiscard]] bool HasBeenProcessed() const { return _status == HEALTH_CHECK_PROCESSED; };
[[nodiscard]] bool IsPending() const { return _status == HEALTH_CHECK_PENDING; };
[[nodiscard]] Milliseconds GetDelay() const { return _delay; };
void UpdateStatus(uint8 status) { _status = status; };
};
class BossAI : public ScriptedAI
{
public:
BossAI(Creature* creature, uint32 bossId);
~BossAI() override {}
float callForHelpRange;
InstanceScript* const instance;
bool CanRespawn() override;
void OnSpellCast(SpellInfo const* spell) override;
void OnChannelFinished(SpellInfo const* spell) override;
void OnSpellFailed(SpellInfo const* spell) override;
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask) override;
void JustSummoned(Creature* summon) override;
void SummonedCreatureDespawn(Creature* summon) override;
void SummonedCreatureDespawnAll() override;
void UpdateAI(uint32 diff) override;
void ScheduleHealthCheckEvent(uint32 healthPct, std::function<void()> exec, bool allowedWhileCasting = true);
void ScheduleHealthCheckEvent(std::initializer_list<uint8> healthPct, std::function<void()> exec, bool allowedWhileCasting = true);
void ProcessHealthCheck();
// @brief Casts the spell after the fixed time and says the text id if provided. Timer will run even if the creature is casting or out of combat.
// @param spellId The spell to cast.
// @param timer The time to wait before casting the spell.
// @param textId The text id to say.
void ScheduleEnrageTimer(uint32 spellId, Milliseconds timer, uint8 textId = 0);
// Hook used to execute events scheduled into EventMap without the need
// to override UpdateAI
// note: You must re-schedule the event within this method if the event
// is supposed to run more than once
virtual void ExecuteEvent(uint32 /*eventId*/) { }
virtual void ScheduleTasks() { }
void Reset() override { _Reset(); }
void JustEngagedWith(Unit* /*who*/) override { _JustEngagedWith(); }
void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { _EnterEvadeMode(why); }
void JustDied(Unit* /*killer*/) override { _JustDied(); }
void JustReachedHome() override { _JustReachedHome(); }
protected:
void _Reset();
void _JustEngagedWith();
void _JustDied();
void _JustReachedHome() { me->setActive(false); }
void _EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
void TeleportCheaters();
SummonList summons;
private:
void _CheckHealthAfterCast();
uint32 const _bossId;
std::list<HealthCheckEventData> _healthCheckEvents;
HealthCheckEventData _nextHealthCheck;
};
class WorldBossAI : public ScriptedAI
{
public:
WorldBossAI(Creature* creature);
~WorldBossAI() override {}
void JustSummoned(Creature* summon) override;
void SummonedCreatureDespawn(Creature* summon) override;
void UpdateAI(uint32 diff) override;
// Hook used to execute events scheduled into EventMap without the need
// to override UpdateAI
// note: You must re-schedule the event within this method if the event
// is supposed to run more than once
virtual void ExecuteEvent(uint32 /*eventId*/) { }
void Reset() override { _Reset(); }
void JustEngagedWith(Unit* /*who*/) override { _JustEngagedWith(); }
void JustDied(Unit* /*killer*/) override { _JustDied(); }
protected:
void _Reset();
void _JustEngagedWith();
void _JustDied();
EventMap events;
SummonList summons;
};
// SD2 grid searchers.
Creature* GetClosestCreatureWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool alive = true);
GameObject* GetClosestGameObjectWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool onlySpawned = false);
void GetCreatureListWithEntryInGrid(std::list<Creature*>& list, WorldObject* source, uint32 entry, float maxSearchRange);
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& list, WorldObject* source, uint32 entry, float maxSearchRange);
void GetDeadCreatureListInGrid(std::list<Creature*>& list, WorldObject* source, float maxSearchRange, bool alive = false);
#endif // SCRIPTEDCREATURE_H_
@@ -0,0 +1,608 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#include "ScriptedEscortAI.h"
#include "Group.h"
#include "Player.h"
#include "ScriptedCreature.h"
enum ePoints
{
POINT_LAST_POINT = 0xFFFFFF,
POINT_HOME = 0xFFFFFE
};
npc_escortAI::npc_escortAI(Creature* creature) : ScriptedAI(creature),
m_uiWPWaitTimer(1000),
m_uiPlayerCheckTimer(0),
m_uiEscortState(STATE_ESCORT_NONE),
MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE),
m_pQuestForEscort(nullptr),
m_bIsActiveAttacker(true),
m_bCanInstantRespawn(false),
m_bCanReturnToStart(false),
DespawnAtEnd(true),
DespawnAtFar(true),
ScriptWP(false),
HasImmuneToNPCFlags(false)
{}
void npc_escortAI::AttackStart(Unit* who)
{
if (!who)
return;
if (me->Attack(who, true))
{
MovementGeneratorType type = me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE);
if (type == ESCORT_MOTION_TYPE || type == POINT_MOTION_TYPE)
{
me->GetMotionMaster()->MovementExpired();
//me->DisableSpline();
me->StopMoving();
}
if (me->IsCombatMovementAllowed())
me->GetMotionMaster()->MoveChase(who);
}
}
//see followerAI
bool npc_escortAI::AssistPlayerInCombatAgainst(Unit* who)
{
if (!who || !who->GetVictim())
{
return false;
}
if (me->HasReactState(REACT_PASSIVE))
{
return false;
}
//experimental (unknown) flag not present
if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST))
{
return false;
}
//not a player
if (!who->GetVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
{
return false;
}
if (!who->isInAccessiblePlaceFor(me))
{
return false;
}
if (!CanAIAttack(who))
{
return false;
}
// we cannot attack in evade mode
if (me->IsInEvadeMode())
{
return false;
}
// or if enemy is in evade mode
if (who->IsCreature() && who->ToCreature()->IsInEvadeMode())
{
return false;
}
//never attack friendly
if (!me->IsValidAttackTarget(who))
{
return false;
}
//too far away and no free sight?
if (me->IsWithinDistInMap(who, GetMaxPlayerDistance()) && me->IsWithinLOSInMap(who))
{
AttackStart(who);
return true;
}
return false;
}
void npc_escortAI::MoveInLineOfSight(Unit* who)
{
if (me->GetVictim())
return;
if (!me->HasUnitState(UNIT_STATE_STUNNED) && who->isTargetableForAttack(true, me) && who->isInAccessiblePlaceFor(me))
if (HasEscortState(STATE_ESCORT_ESCORTING) && AssistPlayerInCombatAgainst(who))
return;
if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who))
{
if (me->HasUnitState(UNIT_STATE_DISTRACTED))
{
me->ClearUnitState(UNIT_STATE_DISTRACTED);
me->GetMotionMaster()->Clear();
}
AttackStart(who);
}
}
void npc_escortAI::JustDied(Unit* /*killer*/)
{
if (!HasEscortState(STATE_ESCORT_ESCORTING) || !m_uiPlayerGUID || !m_pQuestForEscort)
return;
if (Player* player = GetPlayerForEscort())
{
if (Group* group = player->GetGroup())
{
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next())
if (Player* member = groupRef->GetSource())
if (member->IsInMap(player) && member->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
member->FailQuest(m_pQuestForEscort->GetQuestId());
}
else
{
if (player->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
player->FailQuest(m_pQuestForEscort->GetQuestId());
}
}
}
void npc_escortAI::JustRespawned()
{
RemoveEscortState(STATE_ESCORT_ESCORTING | STATE_ESCORT_RETURNING | STATE_ESCORT_PAUSED);
if (!me->IsCombatMovementAllowed())
me->SetCombatMovement(true);
//add a small delay before going to first waypoint, normal in near all cases
m_uiWPWaitTimer = 1000;
if (me->GetFaction() != me->GetCreatureTemplate()->faction)
me->RestoreFaction();
Reset();
}
void npc_escortAI::ReturnToLastPoint()
{
float x, y, z, o;
me->GetHomePosition(x, y, z, o);
me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z, FORCED_MOVEMENT_RUN);
}
void npc_escortAI::JustExitedCombat()
{
// Evade synchronously so UpdateAI does not push a waypoint spline before
// SelectVictim's evade fallback fires; stacked motion intents twitch.
EngagementOver();
if (me->IsAlive() && me->IsInWorld() && !me->IsInEvadeMode())
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
}
void npc_escortAI::EnterEvadeMode(EvadeReason /*why*/)
{
me->GetThreatMgr().ClearAllThreat();
me->CombatStop(true);
me->SetLootRecipient(nullptr);
if (HasEscortState(STATE_ESCORT_ESCORTING))
{
AddEscortState(STATE_ESCORT_RETURNING);
ReturnToLastPoint();
LOG_DEBUG("scripts.ai", "EscortAI has left combat and is now returning to last point");
}
else
{
me->GetMotionMaster()->MoveTargetedHome();
if (HasImmuneToNPCFlags)
me->SetImmuneToNPC(true);
Reset();
}
}
bool npc_escortAI::IsPlayerOrGroupInRange()
{
if (Player* player = GetPlayerForEscort())
{
if (Group* group = player->GetGroup())
{
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next())
if (Player* member = groupRef->GetSource())
if (me->IsWithinDistInMap(member, GetMaxPlayerDistance(), true, false, false))
return true;
}
else if (me->IsWithinDistInMap(player, GetMaxPlayerDistance(), true, false, false))
return true;
}
return false;
}
void npc_escortAI::UpdateAI(uint32 diff)
{
if (HasEscortState(STATE_ESCORT_ESCORTING) && !me->GetVictim() && m_uiWPWaitTimer && !HasEscortState(STATE_ESCORT_RETURNING))
{
if (m_uiWPWaitTimer <= diff)
{
if (CurrentWP == WaypointList.end())
{
if (DespawnAtEnd)
{
if (m_bCanReturnToStart)
{
float fRetX, fRetY, fRetZ;
me->GetRespawnPosition(fRetX, fRetY, fRetZ);
me->GetMotionMaster()->MovePoint(POINT_HOME, fRetX, fRetY, fRetZ);
m_uiWPWaitTimer = 0;
return;
}
if (m_bCanInstantRespawn)
{
me->setDeathState(DeathState::JustDied);
me->Respawn();
}
else
me->DespawnOrUnsummon();
}
// xinef: remove escort state, escort was finished (lack of this line resulted in skipping UpdateEscortAI calls after finished escort)
RemoveEscortState(STATE_ESCORT_ESCORTING);
return;
}
if (!HasEscortState(STATE_ESCORT_PAUSED))
{
// xinef, start escort if there is no spline active
if (me->movespline->Finalized())
{
Movement::PointsArray pathPoints;
GenerateWaypointArray(&pathPoints);
me->GetMotionMaster()->MoveSplinePath(&pathPoints);
}
WaypointStart(CurrentWP->id);
m_uiWPWaitTimer = 0;
}
}
else
m_uiWPWaitTimer -= diff;
}
//Check if player or any member of his group is within range
if (HasEscortState(STATE_ESCORT_ESCORTING) && m_uiPlayerGUID && !me->GetVictim() && !HasEscortState(STATE_ESCORT_RETURNING))
{
m_uiPlayerCheckTimer += diff;
if (m_uiPlayerCheckTimer > 1000)
{
if (DespawnAtFar && !IsPlayerOrGroupInRange())
{
if (m_bCanInstantRespawn)
{
me->setDeathState(DeathState::JustDied);
me->Respawn();
}
else
me->DespawnOrUnsummon();
return;
}
m_uiPlayerCheckTimer = 0;
}
}
UpdateEscortAI(diff);
}
void npc_escortAI::UpdateEscortAI(uint32 /*diff*/)
{
if (!UpdateVictim())
return;
DoMeleeAttackIfReady();
}
void npc_escortAI::MovementInform(uint32 moveType, uint32 pointId)
{
// xinef: no action allowed if there is no escort
if (!HasEscortState(STATE_ESCORT_ESCORTING))
return;
if (moveType == POINT_MOTION_TYPE)
{
//Combat start position reached, continue waypoint movement
if (pointId == POINT_LAST_POINT)
{
LOG_DEBUG("scripts.ai", "EscortAI has returned to original position before combat");
RemoveEscortState(STATE_ESCORT_RETURNING);
if (!m_uiWPWaitTimer)
m_uiWPWaitTimer = 1;
}
else if (pointId == POINT_HOME)
{
LOG_DEBUG("scripts.ai", "EscortAI has returned to original home location and will continue from beginning of waypoint list.");
CurrentWP = WaypointList.begin();
m_uiWPWaitTimer = 1;
}
}
else if (moveType == ESCORT_MOTION_TYPE)
{
if (m_uiWPWaitTimer <= 1 && !HasEscortState(STATE_ESCORT_PAUSED) && CurrentWP != WaypointList.end())
{
//Call WP function
me->SetPosition(CurrentWP->x, CurrentWP->y, CurrentWP->z, me->GetOrientation());
me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation());
WaypointReached(CurrentWP->id);
m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1;
++CurrentWP;
if (m_uiWPWaitTimer > 1 || HasEscortState(STATE_ESCORT_PAUSED))
{
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == ESCORT_MOTION_TYPE)
me->GetMotionMaster()->MovementExpired();
me->StopMovingOnCurrentPos();
me->GetMotionMaster()->MoveIdle();
}
}
}
}
/*
void npc_escortAI::OnPossess(bool apply)
{
// We got possessed in the middle of being escorted, store the point
// where we left off to come back to when possess is removed
if (HasEscortState(STATE_ESCORT_ESCORTING))
{
if (apply)
me->GetPosition(LastPos.x, LastPos.y, LastPos.z);
else
{
Returning = true;
me->GetMotionMaster()->MovementExpired();
me->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z);
}
}
}
*/
void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z, uint32 waitTime)
{
Escort_Waypoint t(id, x, y, z, waitTime);
WaypointList.push_back(t);
// i think SD2 no longer uses this function
ScriptWP = true;
/*PointMovement wp;
wp.m_uiCreatureEntry = me->GetEntry();
wp.m_uiPointId = id;
wp.m_fX = x;
wp.m_fY = y;
wp.m_fZ = z;
wp.m_uiWaitTime = WaitTimeMs;
PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/
}
void npc_escortAI::FillPointMovementListForCreature()
{
ScriptPointVector const& movePoints = sScriptSystemMgr->GetPointMoveList(me->GetEntry());
if (movePoints.empty())
return;
ScriptPointVector::const_iterator itrEnd = movePoints.end();
for (ScriptPointVector::const_iterator itr = movePoints.begin(); itr != itrEnd; ++itr)
{
Escort_Waypoint point(itr->uiPointId, itr->fX, itr->fY, itr->fZ, itr->uiWaitTime);
WaypointList.push_back(point);
}
}
//TODO: get rid of this many variables passed in function.
void npc_escortAI::Start(bool isActiveAttacker /* = true*/, ObjectGuid playerGUID /* = ObjectGuid::Empty */, Quest const* quest /* = nullptr */, bool instantRespawn /* = false */, bool canLoopPath /* = false */, bool resetWaypoints /* = true */)
{
if (me->GetVictim())
{
LOG_ERROR("entities.unit.ai", "ERROR: EscortAI (script: {}, creature entry: {}) attempts to Start while in combat", me->GetScriptName(), me->GetEntry());
return;
}
if (HasEscortState(STATE_ESCORT_ESCORTING))
{
LOG_ERROR("entities.unit.ai", "EscortAI (script: {}, creature entry: {}) attempts to Start while already escorting", me->GetScriptName(), me->GetEntry());
return;
}
if (!ScriptWP && resetWaypoints) // sd2 never adds wp in script, but tc does
{
if (!WaypointList.empty())
WaypointList.clear();
FillPointMovementListForCreature();
}
if (WaypointList.empty())
{
LOG_ERROR("sql.sql", "EscortAI (script: {}, creature entry: {}) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {}).",
me->GetScriptName(), me->GetEntry(), quest ? quest->GetQuestId() : 0);
return;
}
//set variables
m_bIsActiveAttacker = isActiveAttacker;
m_uiPlayerGUID = playerGUID;
m_pQuestForEscort = quest;
m_bCanInstantRespawn = instantRespawn;
m_bCanReturnToStart = canLoopPath;
if (m_bCanReturnToStart && m_bCanInstantRespawn)
LOG_DEBUG("scripts.ai", "EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn.");
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
{
me->GetMotionMaster()->MovementExpired();
me->GetMotionMaster()->MoveIdle();
LOG_DEBUG("scripts.ai", "EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle.");
}
//disable npcflags
me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE);
if (me->IsImmuneToNPC())
{
HasImmuneToNPCFlags = true;
me->SetImmuneToNPC(false);
}
LOG_DEBUG("scripts.ai", "EscortAI started with {} waypoints. ActiveAttacker = {}, PlayerGUID = {}",
uint64(WaypointList.size()), m_bIsActiveAttacker, m_uiPlayerGUID.ToString());
CurrentWP = WaypointList.begin();
AddEscortState(STATE_ESCORT_ESCORTING);
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == ESCORT_MOTION_TYPE)
me->GetMotionMaster()->MovementExpired();
me->DisableSpline();
me->GetMotionMaster()->MoveIdle();
}
void npc_escortAI::SetEscortPaused(bool on)
{
if (!HasEscortState(STATE_ESCORT_ESCORTING))
return;
if (on)
AddEscortState(STATE_ESCORT_PAUSED);
else
RemoveEscortState(STATE_ESCORT_PAUSED);
}
bool npc_escortAI::SetNextWaypoint(uint32 pointId, float x, float y, float z, float orientation)
{
me->UpdatePosition(x, y, z, orientation);
return SetNextWaypoint(pointId, false);
}
bool npc_escortAI::SetNextWaypoint(uint32 pointId, bool setPosition)
{
if (!WaypointList.empty())
WaypointList.clear();
FillPointMovementListForCreature();
if (WaypointList.empty())
return false;
Escort_Waypoint waypoint(0, 0, 0, 0, 0);
for (CurrentWP = WaypointList.begin(); CurrentWP != WaypointList.end(); ++CurrentWP)
{
if (CurrentWP->id == pointId)
{
if (setPosition)
me->UpdatePosition(CurrentWP->x, CurrentWP->y, CurrentWP->z, me->GetOrientation());
return true;
}
}
return false;
}
bool npc_escortAI::GetWaypointPosition(uint32 pointId, float& x, float& y, float& z)
{
ScriptPointVector const& waypoints = sScriptSystemMgr->GetPointMoveList(me->GetEntry());
if (waypoints.empty())
return false;
for (ScriptPointVector::const_iterator itr = waypoints.begin(); itr != waypoints.end(); ++itr)
{
if (itr->uiPointId == pointId)
{
x = itr->fX;
y = itr->fY;
z = itr->fZ;
return true;
}
}
return false;
}
void npc_escortAI::GenerateWaypointArray(Movement::PointsArray* points)
{
if (WaypointList.empty())
return;
// Flying unit, just fill array
if (me->m_movementInfo.HasMovementFlag((MovementFlags)(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY)))
{
// xinef: first point in vector is unit real position
points->clear();
points->push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
for (std::list<Escort_Waypoint>::const_iterator itr = CurrentWP; itr != WaypointList.end(); ++itr)
points->push_back(G3D::Vector3(itr->x, itr->y, itr->z));
}
else
{
uint32 remainingWaypoints = std::distance(CurrentWP, WaypointList.end());
for (float size = 1.0f; size; size *= 0.5f)
{
std::vector<G3D::Vector3> pVector;
// xinef: first point in vector is unit real position
pVector.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
uint32 length = remainingWaypoints * size;
uint32 cnt = 0;
for (std::list<Escort_Waypoint>::const_iterator itr = CurrentWP; itr != WaypointList.end() && cnt <= length; ++itr, ++cnt)
pVector.push_back(G3D::Vector3(itr->x, itr->y, itr->z));
if (pVector.size() > 2) // more than source + dest
{
G3D::Vector3 middle = (pVector[0] + pVector[pVector.size() - 1]) / 2.f;
G3D::Vector3 offset;
bool continueLoop = false;
for (uint32 i = 1; i < pVector.size() - 1; ++i)
{
offset = middle - pVector[i];
if (std::fabs(offset.x) >= 0xFF || std::fabs(offset.y) >= 0xFF || std::fabs(offset.z) >= 0x7F)
{
// offset is too big, split points
continueLoop = true;
break;
}
}
if (continueLoop)
continue;
}
// everything ok
*points = pVector;
break;
}
}
}
@@ -0,0 +1,144 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 <http://www.gnu.org/licenses/>.
*/
#ifndef SC_ESCORTAI_H
#define SC_ESCORTAI_H
#include "ScriptSystem.h"
#include "ScriptedCreature.h"
#define DEFAULT_MAX_PLAYER_DISTANCE 100
struct Escort_Waypoint
{
Escort_Waypoint(uint32 _id, float _x, float _y, float _z, uint32 _w)
{
id = _id;
x = _x;
y = _y;
z = _z;
WaitTimeMs = _w;
}
uint32 id;
float x;
float y;
float z;
uint32 WaitTimeMs;
};
enum eEscortState
{
STATE_ESCORT_NONE = 0x000, //nothing in progress
STATE_ESCORT_ESCORTING = 0x001, //escort are in progress
STATE_ESCORT_RETURNING = 0x002, //escort is returning after being in combat
STATE_ESCORT_PAUSED = 0x004 //will not proceed with waypoints before state is removed
};
struct npc_escortAI : public ScriptedAI
{
public:
explicit npc_escortAI(Creature* creature);
~npc_escortAI() override {}
// CreatureAI functions
void AttackStart(Unit* who) override;
void MoveInLineOfSight(Unit* who) override;
void JustDied(Unit*) override;
void JustRespawned() override;
bool IsEscortNPC(bool /*onlyIfActive*/ = true) const override { return true; }
void ReturnToLastPoint();
void EnterEvadeMode(EvadeReason /*why*/ = EVADE_REASON_OTHER) override;
void JustExitedCombat() override;
void UpdateAI(uint32 diff) override; //the "internal" update, calls UpdateEscortAI()
virtual void UpdateEscortAI(uint32 diff); //used when it's needed to add code in update (abilities, scripted events, etc)
void MovementInform(uint32, uint32) override;
// EscortAI functions
void AddWaypoint(uint32 id, float x, float y, float z, uint32 waitTime = 0); // waitTime is in ms
//this will set the current position to x/y/z/o, and the current WP to pointId.
bool SetNextWaypoint(uint32 pointId, float x, float y, float z, float orientation);
//this will set the current position to WP start position (if setPosition == true),
//and the current WP to pointId
bool SetNextWaypoint(uint32 pointId, bool setPosition = true);
bool GetWaypointPosition(uint32 pointId, float& x, float& y, float& z);
void GenerateWaypointArray(Movement::PointsArray* points);
using CreatureAI::WaypointReached;
virtual void WaypointReached(uint32 pointId) = 0;
virtual void WaypointStart(uint32 /*pointId*/) {}
void Start(bool isActiveAttacker = true, ObjectGuid playerGUID = ObjectGuid::Empty, Quest const* quest = nullptr, bool instantRespawn = false, bool canLoopPath = false, bool resetWaypoints = true);
void SetEscortPaused(bool on);
bool HasEscortState(uint32 escortState) { return (m_uiEscortState & escortState); }
bool IsEscorted() override { return (m_uiEscortState & STATE_ESCORT_ESCORTING); }
void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; }
float GetMaxPlayerDistance() { return MaxPlayerDistance; }
void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; }
void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; }
bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override
void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; }
ObjectGuid GetEventStarterGUID() { return m_uiPlayerGUID; }
void AddEscortState(uint32 escortState) { m_uiEscortState |= escortState; }
void RemoveEscortState(uint32 escortState) { m_uiEscortState &= ~escortState; }
protected:
Player* GetPlayerForEscort() { return ObjectAccessor::GetPlayer(*me, m_uiPlayerGUID); }
private:
bool AssistPlayerInCombatAgainst(Unit* who);
bool IsPlayerOrGroupInRange();
void FillPointMovementListForCreature();
ObjectGuid m_uiPlayerGUID;
uint32 m_uiWPWaitTimer;
uint32 m_uiPlayerCheckTimer;
uint32 m_uiEscortState;
float MaxPlayerDistance;
Quest const* m_pQuestForEscort; //generally passed in Start() when regular escort script.
std::list<Escort_Waypoint> WaypointList;
std::list<Escort_Waypoint>::iterator CurrentWP;
bool m_bIsActiveAttacker; //obsolete, determined by faction.
bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used)
bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests.
bool DespawnAtEnd;
bool DespawnAtFar;
bool ScriptWP;
bool HasImmuneToNPCFlags;
};
#endif

Some files were not shown because too many files have changed in this diff Show More