mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-23 05:36:23 +00:00
First Commit
For Azeroth!
This commit is contained in:
35
src/server/CMakeLists.txt
Normal file
35
src/server/CMakeLists.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (C)
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Enforce compileparameters for corebuilds under GCC
|
||||
# This to stop a few silly crashes that could have been avoided IF people
|
||||
# weren't doing some -O3 psychooptimizations etc.
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX AND NOT MINGW)
|
||||
add_definitions(-fno-delete-null-pointer-checks)
|
||||
endif()
|
||||
|
||||
if( SERVERS )
|
||||
set(sources_windows_Debugging
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Debugging/WheatyExceptionReport.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Debugging/WheatyExceptionReport.h
|
||||
)
|
||||
add_subdirectory(shared)
|
||||
add_subdirectory(game)
|
||||
add_subdirectory(collision)
|
||||
add_subdirectory(authserver)
|
||||
add_subdirectory(scripts)
|
||||
add_subdirectory(worldserver)
|
||||
else()
|
||||
if( TOOLS )
|
||||
add_subdirectory(collision)
|
||||
add_subdirectory(shared)
|
||||
endif()
|
||||
endif()
|
||||
81
src/server/authserver/Authentication/AuthCodes.cpp
Normal file
81
src/server/authserver/Authentication/AuthCodes.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <cstddef>
|
||||
|
||||
namespace AuthHelper
|
||||
{
|
||||
static RealmBuildInfo const PostBcAcceptedClientBuilds[] =
|
||||
{
|
||||
{15595, 4, 3, 4, ' '},
|
||||
{14545, 4, 2, 2, ' '},
|
||||
{13623, 4, 0, 6, 'a'},
|
||||
{12340, 3, 3, 5, 'a'},
|
||||
{11723, 3, 3, 3, 'a'},
|
||||
{11403, 3, 3, 2, ' '},
|
||||
{11159, 3, 3, 0, 'a'},
|
||||
{10505, 3, 2, 2, 'a'},
|
||||
{9947, 3, 1, 3, ' '},
|
||||
{8606, 2, 4, 3, ' '},
|
||||
{0, 0, 0, 0, ' '} // terminator
|
||||
};
|
||||
|
||||
static RealmBuildInfo const PreBcAcceptedClientBuilds[] =
|
||||
{
|
||||
{6141, 1, 12, 3, ' '},
|
||||
{6005, 1, 12, 2, ' '},
|
||||
{5875, 1, 12, 1, ' '},
|
||||
{0, 0, 0, 0, ' '} // terminator
|
||||
};
|
||||
|
||||
bool IsPreBCAcceptedClientBuild(int build)
|
||||
{
|
||||
for (int i = 0; PreBcAcceptedClientBuilds[i].Build; ++i)
|
||||
if (PreBcAcceptedClientBuilds[i].Build == build)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsPostBCAcceptedClientBuild(int build)
|
||||
{
|
||||
for (int i = 0; PostBcAcceptedClientBuilds[i].Build; ++i)
|
||||
if (PostBcAcceptedClientBuilds[i].Build == build)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsAcceptedClientBuild(int build)
|
||||
{
|
||||
return (IsPostBCAcceptedClientBuild(build) || IsPreBCAcceptedClientBuild(build));
|
||||
}
|
||||
|
||||
RealmBuildInfo const* GetBuildInfo(int build)
|
||||
{
|
||||
for (int i = 0; PostBcAcceptedClientBuilds[i].Build; ++i)
|
||||
if (PostBcAcceptedClientBuilds[i].Build == build)
|
||||
return &PostBcAcceptedClientBuilds[i];
|
||||
|
||||
for (int i = 0; PreBcAcceptedClientBuilds[i].Build; ++i)
|
||||
if (PreBcAcceptedClientBuilds[i].Build == build)
|
||||
return &PreBcAcceptedClientBuilds[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
97
src/server/authserver/Authentication/AuthCodes.h
Normal file
97
src/server/authserver/Authentication/AuthCodes.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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
|
||||
|
||||
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
|
||||
{
|
||||
int Build;
|
||||
int MajorVersion;
|
||||
int MinorVersion;
|
||||
int BugfixVersion;
|
||||
int HotfixVersion;
|
||||
};
|
||||
|
||||
namespace AuthHelper
|
||||
{
|
||||
RealmBuildInfo const* GetBuildInfo(int build);
|
||||
bool IsAcceptedClientBuild(int build);
|
||||
bool IsPostBCAcceptedClientBuild(int build);
|
||||
bool IsPreBCAcceptedClientBuild(int build);
|
||||
};
|
||||
|
||||
#endif
|
||||
110
src/server/authserver/CMakeLists.txt
Normal file
110
src/server/authserver/CMakeLists.txt
Normal file
@@ -0,0 +1,110 @@
|
||||
# Copyright (C)
|
||||
#
|
||||
# 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.
|
||||
|
||||
########### authserver ###############
|
||||
|
||||
file(GLOB_RECURSE sources_authentication Authentication/*.cpp Authentication/*.h)
|
||||
file(GLOB_RECURSE sources_realms Realms/*.cpp Realms/*.h)
|
||||
file(GLOB_RECURSE sources_server Server/*.cpp Server/*.h)
|
||||
file(GLOB sources_localdir *.cpp *.h)
|
||||
|
||||
if (USE_COREPCH)
|
||||
set(authserver_PCH_HDR PrecompiledHeaders/authPCH.h)
|
||||
set(authserver_PCH_SRC PrecompiledHeaders/authPCH.cpp)
|
||||
endif()
|
||||
|
||||
set(authserver_SRCS
|
||||
${authserver_SRCS}
|
||||
${sources_authentication}
|
||||
${sources_realms}
|
||||
${sources_server}
|
||||
${sources_localdir}
|
||||
)
|
||||
|
||||
if( WIN32 )
|
||||
set(authserver_SRCS
|
||||
${authserver_SRCS}
|
||||
${sources_windows_Debugging}
|
||||
)
|
||||
if ( MSVC )
|
||||
set(authserver_SRCS
|
||||
${authserver_SRCS}
|
||||
authserver.rc
|
||||
)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Database
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Debugging
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Packets
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Cryptography
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Cryptography/Authentication
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Logging
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Threading
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Utilities
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Authentication
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Realms
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Server
|
||||
${ACE_INCLUDE_DIR}
|
||||
${MYSQL_INCLUDE_DIR}
|
||||
${OPENSSL_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
add_executable(authserver
|
||||
${authserver_SRCS}
|
||||
${authserver_PCH_SRC}
|
||||
)
|
||||
|
||||
add_dependencies(authserver revision.h)
|
||||
|
||||
if( NOT WIN32 )
|
||||
set_target_properties(authserver PROPERTIES
|
||||
COMPILE_DEFINITIONS _TRINITY_REALM_CONFIG="${CONF_DIR}/authserver.conf"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(authserver
|
||||
shared
|
||||
${MYSQL_LIBRARY}
|
||||
${OPENSSL_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${ACE_LIBRARY}
|
||||
)
|
||||
|
||||
if( WIN32 )
|
||||
if ( MSVC )
|
||||
add_custom_command(TARGET authserver
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/authserver.conf.dist ${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/
|
||||
)
|
||||
elseif ( MINGW )
|
||||
add_custom_command(TARGET authserver
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/authserver.conf.dist ${CMAKE_BINARY_DIR}/bin/
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if( UNIX )
|
||||
install(TARGETS authserver DESTINATION bin)
|
||||
install(FILES authserver.conf.dist DESTINATION ${CONF_DIR})
|
||||
elseif( WIN32 )
|
||||
install(TARGETS authserver DESTINATION "${CMAKE_INSTALL_PREFIX}")
|
||||
install(FILES authserver.conf.dist DESTINATION "${CMAKE_INSTALL_PREFIX}")
|
||||
endif()
|
||||
|
||||
# Generate precompiled header
|
||||
if (USE_COREPCH)
|
||||
add_cxx_pch(authserver ${authserver_PCH_HDR} ${authserver_PCH_SRC})
|
||||
endif()
|
||||
330
src/server/authserver/Main.cpp
Normal file
330
src/server/authserver/Main.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <ace/Dev_Poll_Reactor.h>
|
||||
#include <ace/TP_Reactor.h>
|
||||
#include <ace/ACE.h>
|
||||
#include <ace/Sig_Handler.h>
|
||||
#include <openssl/opensslv.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#include "Common.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "Configuration/Config.h"
|
||||
#include "Log.h"
|
||||
#include "SystemConfig.h"
|
||||
#include "Util.h"
|
||||
#include "SignalHandler.h"
|
||||
#include "RealmList.h"
|
||||
#include "RealmAcceptor.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sched.h>
|
||||
#include <sys/resource.h>
|
||||
#define PROCESS_HIGH_PRIORITY -15 // [-20, 19], default is 0
|
||||
#endif
|
||||
|
||||
#ifndef _TRINITY_REALM_CONFIG
|
||||
# define _TRINITY_REALM_CONFIG "authserver.conf"
|
||||
#endif
|
||||
|
||||
bool StartDB();
|
||||
void StopDB();
|
||||
|
||||
bool stopEvent = false; // Setting it to true stops the server
|
||||
|
||||
LoginDatabaseWorkerPool LoginDatabase; // Accessor to the authserver database
|
||||
|
||||
/// Handle authserver's termination signals
|
||||
class AuthServerSignalHandler : public Trinity::SignalHandler
|
||||
{
|
||||
public:
|
||||
virtual void HandleSignal(int sigNum)
|
||||
{
|
||||
switch (sigNum)
|
||||
{
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
stopEvent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Print out the usage string for this program on the console.
|
||||
void usage(const char* prog)
|
||||
{
|
||||
sLog->outString("Usage: \n %s [<options>]\n"
|
||||
" -c config_file use config_file as configuration file\n\r",
|
||||
prog);
|
||||
}
|
||||
|
||||
/// Launch the auth server
|
||||
extern int main(int argc, char** argv)
|
||||
{
|
||||
// Command line parsing to get the configuration file name
|
||||
char const* configFile = _TRINITY_REALM_CONFIG;
|
||||
int count = 1;
|
||||
while (count < argc)
|
||||
{
|
||||
if (strcmp(argv[count], "-c") == 0)
|
||||
{
|
||||
if (++count >= argc)
|
||||
{
|
||||
printf("Runtime-Error: -c option requires an input argument\n");
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
configFile = argv[count];
|
||||
}
|
||||
++count;
|
||||
}
|
||||
|
||||
if (!sConfigMgr->LoadInitial(configFile))
|
||||
{
|
||||
printf("Invalid or missing configuration file : %s\n", configFile);
|
||||
printf("Verify that the file exists and has \'[authserver]\' written in the top of the file!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sLog->outString("%s (authserver)", _FULLVERSION);
|
||||
sLog->outString("<Ctrl-C> to stop.\n");
|
||||
sLog->outString("Using configuration file %s.", configFile);
|
||||
|
||||
sLog->outDetail("%s (Library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
|
||||
|
||||
#if defined (ACE_HAS_EVENT_POLL) || defined (ACE_HAS_DEV_POLL)
|
||||
ACE_Reactor::instance(new ACE_Reactor(new ACE_Dev_Poll_Reactor(ACE::max_handles(), 1), 1), true);
|
||||
#else
|
||||
ACE_Reactor::instance(new ACE_Reactor(new ACE_TP_Reactor(), true), true);
|
||||
#endif
|
||||
|
||||
sLog->outBasic("Max allowed open files is %d", ACE::max_handles());
|
||||
|
||||
// authserver PID file creation
|
||||
std::string pidFile = sConfigMgr->GetStringDefault("PidFile", "");
|
||||
if (!pidFile.empty())
|
||||
{
|
||||
if (uint32 pid = CreatePIDFile(pidFile))
|
||||
sLog->outError("Daemon PID: %u\n", pid);
|
||||
else
|
||||
{
|
||||
sLog->outError("Cannot create PID file %s.\n", pidFile.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the database connection
|
||||
if (!StartDB())
|
||||
return 1;
|
||||
|
||||
// Initialize the log database
|
||||
sLog->SetLogDB(false);
|
||||
sLog->SetRealmID(0); // ensure we've set realm to 0 (authserver realmid)
|
||||
|
||||
// Get the list of realms for the server
|
||||
sRealmList->Initialize(sConfigMgr->GetIntDefault("RealmsStateUpdateDelay", 20));
|
||||
if (sRealmList->size() == 0)
|
||||
{
|
||||
sLog->outError("No valid realms specified.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Launch the listening network socket
|
||||
RealmAcceptor acceptor;
|
||||
|
||||
int32 rmport = sConfigMgr->GetIntDefault("RealmServerPort", 3724);
|
||||
if (rmport < 0 || rmport > 0xFFFF)
|
||||
{
|
||||
sLog->outError("Specified port out of allowed range (1-65535)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string bind_ip = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
|
||||
|
||||
ACE_INET_Addr bind_addr(uint16(rmport), bind_ip.c_str());
|
||||
|
||||
if (acceptor.open(bind_addr, ACE_Reactor::instance(), ACE_NONBLOCK) == -1)
|
||||
{
|
||||
sLog->outError("Auth server can not bind to %s:%d", bind_ip.c_str(), rmport);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize the signal handlers
|
||||
AuthServerSignalHandler SignalINT, SignalTERM;
|
||||
|
||||
// Register authservers's signal handlers
|
||||
ACE_Sig_Handler Handler;
|
||||
Handler.register_handler(SIGINT, &SignalINT);
|
||||
Handler.register_handler(SIGTERM, &SignalTERM);
|
||||
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
|
||||
///- Handle affinity for multiple processors and process priority
|
||||
uint32 affinity = sConfigMgr->GetIntDefault("UseProcessors", 0);
|
||||
bool highPriority = sConfigMgr->GetBoolDefault("ProcessPriority", false);
|
||||
|
||||
#ifdef _WIN32 // Windows
|
||||
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
if (affinity > 0)
|
||||
{
|
||||
ULONG_PTR appAff;
|
||||
ULONG_PTR sysAff;
|
||||
|
||||
if (GetProcessAffinityMask(hProcess, &appAff, &sysAff))
|
||||
{
|
||||
// remove non accessible processors
|
||||
ULONG_PTR currentAffinity = affinity & appAff;
|
||||
|
||||
if (!currentAffinity)
|
||||
sLog->outError("server.authserver", "Processors marked in UseProcessors bitmask (hex) %x are not accessible for the authserver. Accessible processors bitmask (hex): %x", affinity, appAff);
|
||||
else if (SetProcessAffinityMask(hProcess, currentAffinity))
|
||||
sLog->outString("server.authserver", "Using processors (bitmask, hex): %x", currentAffinity);
|
||||
else
|
||||
sLog->outError("server.authserver", "Can't set used processors (hex): %x", currentAffinity);
|
||||
}
|
||||
}
|
||||
|
||||
if (highPriority)
|
||||
{
|
||||
if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS))
|
||||
sLog->outString("server.authserver", "authserver process priority class set to HIGH");
|
||||
else
|
||||
sLog->outError("server.authserver", "Can't set authserver process priority class.");
|
||||
}
|
||||
|
||||
#else // Linux
|
||||
|
||||
if (affinity > 0)
|
||||
{
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO(&mask);
|
||||
|
||||
for (unsigned int i = 0; i < sizeof(affinity) * 8; ++i)
|
||||
if (affinity & (1 << i))
|
||||
CPU_SET(i, &mask);
|
||||
|
||||
if (sched_setaffinity(0, sizeof(mask), &mask))
|
||||
sLog->outError("server.authserver", "Can't set used processors (hex): %x, error: %s", affinity, strerror(errno));
|
||||
else
|
||||
{
|
||||
CPU_ZERO(&mask);
|
||||
sched_getaffinity(0, sizeof(mask), &mask);
|
||||
sLog->outString("server.authserver", "Using processors (bitmask, hex): %lx", *(__cpu_mask*)(&mask));
|
||||
}
|
||||
}
|
||||
|
||||
if (highPriority)
|
||||
{
|
||||
if (setpriority(PRIO_PROCESS, 0, PROCESS_HIGH_PRIORITY))
|
||||
sLog->outError("server.authserver", "Can't set authserver process priority class, error: %s", strerror(errno));
|
||||
else
|
||||
sLog->outString("server.authserver", "authserver process priority class set to %i", getpriority(PRIO_PROCESS, 0));
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// maximum counter for next ping
|
||||
uint32 numLoops = (sConfigMgr->GetIntDefault("MaxPingTime", 30) * (MINUTE * 1000000 / 100000));
|
||||
uint32 loopCounter = 0;
|
||||
|
||||
// possibly enable db logging; avoid massive startup spam by doing it here.
|
||||
if (sConfigMgr->GetBoolDefault("EnableLogDB", false))
|
||||
{
|
||||
sLog->outString("Enabling database logging...");
|
||||
sLog->SetLogDB(true);
|
||||
}
|
||||
|
||||
// Wait for termination signal
|
||||
while (!stopEvent)
|
||||
{
|
||||
// dont move this outside the loop, the reactor will modify it
|
||||
ACE_Time_Value interval(0, 100000);
|
||||
|
||||
if (ACE_Reactor::instance()->run_reactor_event_loop(interval) == -1)
|
||||
break;
|
||||
|
||||
if ((++loopCounter) == numLoops)
|
||||
{
|
||||
loopCounter = 0;
|
||||
sLog->outDetail("Ping MySQL to keep connection alive");
|
||||
LoginDatabase.KeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
// Close the Database Pool and library
|
||||
StopDB();
|
||||
|
||||
sLog->outString("Halting process...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Initialize connection to the database
|
||||
bool StartDB()
|
||||
{
|
||||
MySQL::Library_Init();
|
||||
|
||||
std::string dbstring = sConfigMgr->GetStringDefault("LoginDatabaseInfo", "");
|
||||
if (dbstring.empty())
|
||||
{
|
||||
sLog->outError("Database not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 worker_threads = sConfigMgr->GetIntDefault("LoginDatabase.WorkerThreads", 1);
|
||||
if (worker_threads < 1 || worker_threads > 32)
|
||||
{
|
||||
sLog->outError("Improper value specified for LoginDatabase.WorkerThreads, defaulting to 1.");
|
||||
worker_threads = 1;
|
||||
}
|
||||
|
||||
int32 synch_threads = sConfigMgr->GetIntDefault("LoginDatabase.SynchThreads", 1);
|
||||
if (synch_threads < 1 || synch_threads > 32)
|
||||
{
|
||||
sLog->outError("Improper value specified for LoginDatabase.SynchThreads, defaulting to 1.");
|
||||
synch_threads = 1;
|
||||
}
|
||||
|
||||
// NOTE: While authserver is singlethreaded you should keep synch_threads == 1. Increasing it is just silly since only 1 will be used ever.
|
||||
if (!LoginDatabase.Open(dbstring.c_str(), uint8(worker_threads), uint8(synch_threads)))
|
||||
{
|
||||
sLog->outError("Cannot connect to database");
|
||||
return false;
|
||||
}
|
||||
|
||||
sLog->outString("Started auth database connection pool.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Close the connection to the database
|
||||
void StopDB()
|
||||
{
|
||||
LoginDatabase.Close();
|
||||
MySQL::Library_End();
|
||||
}
|
||||
1
src/server/authserver/PrecompiledHeaders/authPCH.cpp
Normal file
1
src/server/authserver/PrecompiledHeaders/authPCH.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "authPCH.h"
|
||||
6
src/server/authserver/PrecompiledHeaders/authPCH.h
Normal file
6
src/server/authserver/PrecompiledHeaders/authPCH.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "Configuration/Config.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "RealmList.h"
|
||||
#include "RealmSocket.h"
|
||||
#include "Common.h"
|
||||
106
src/server/authserver/Realms/RealmList.cpp
Normal file
106
src/server/authserver/Realms/RealmList.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "RealmList.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
|
||||
RealmList::RealmList() : m_UpdateInterval(0), m_NextUpdateTime(time(NULL)) { }
|
||||
|
||||
// Load the realm list from the database
|
||||
void RealmList::Initialize(uint32 updateInterval)
|
||||
{
|
||||
m_UpdateInterval = updateInterval;
|
||||
|
||||
// Get the content of the realmlist table in the database
|
||||
UpdateRealms(true);
|
||||
}
|
||||
|
||||
void RealmList::UpdateRealm(uint32 id, const std::string& name, ACE_INET_Addr const& address, ACE_INET_Addr const& localAddr, ACE_INET_Addr const& localSubmask, uint8 icon, RealmFlags flag, uint8 timezone, AccountTypes allowedSecurityLevel, float popu, uint32 build)
|
||||
{
|
||||
// Create new if not exist or update existed
|
||||
Realm& realm = m_realms[name];
|
||||
|
||||
realm.m_ID = id;
|
||||
realm.name = name;
|
||||
realm.icon = icon;
|
||||
realm.flag = flag;
|
||||
realm.timezone = timezone;
|
||||
realm.allowedSecurityLevel = allowedSecurityLevel;
|
||||
realm.populationLevel = popu;
|
||||
|
||||
// Append port to IP address.
|
||||
realm.ExternalAddress = address;
|
||||
realm.LocalAddress = localAddr;
|
||||
realm.LocalSubnetMask = localSubmask;
|
||||
realm.gamebuild = build;
|
||||
}
|
||||
|
||||
void RealmList::UpdateIfNeed()
|
||||
{
|
||||
// maybe disabled or updated recently
|
||||
if (!m_UpdateInterval || m_NextUpdateTime > time(NULL))
|
||||
return;
|
||||
|
||||
m_NextUpdateTime = time(NULL) + m_UpdateInterval;
|
||||
|
||||
// Clears Realm list
|
||||
m_realms.clear();
|
||||
|
||||
// Get the content of the realmlist table in the database
|
||||
UpdateRealms();
|
||||
}
|
||||
|
||||
void RealmList::UpdateRealms(bool init)
|
||||
{
|
||||
sLog->outString("Updating Realm List...");
|
||||
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALMLIST);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
// Circle through results and add them to the realm map
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 realmId = fields[0].GetUInt32();
|
||||
std::string name = fields[1].GetString();
|
||||
std::string externalAddress = fields[2].GetString();
|
||||
std::string localAddress = fields[3].GetString();
|
||||
std::string localSubmask = fields[4].GetString();
|
||||
uint16 port = fields[5].GetUInt16();
|
||||
uint8 icon = fields[6].GetUInt8();
|
||||
RealmFlags flag = RealmFlags(fields[7].GetUInt8());
|
||||
uint8 timezone = fields[8].GetUInt8();
|
||||
uint8 allowedSecurityLevel = fields[9].GetUInt8();
|
||||
float pop = fields[10].GetFloat();
|
||||
uint32 build = fields[11].GetUInt32();
|
||||
|
||||
ACE_INET_Addr externalAddr(port, externalAddress.c_str(), AF_INET);
|
||||
ACE_INET_Addr localAddr(port, localAddress.c_str(), AF_INET);
|
||||
ACE_INET_Addr submask(0, localSubmask.c_str(), AF_INET);
|
||||
|
||||
UpdateRealm(realmId, name, externalAddr, localAddr, submask, icon, flag, timezone, (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), pop, build);
|
||||
|
||||
if (init)
|
||||
sLog->outString("Added realm \"%s\" at %s:%u.", name.c_str(), m_realms[name].ExternalAddress.get_host_addr(), port);
|
||||
}
|
||||
while (result->NextRow());
|
||||
}
|
||||
}
|
||||
85
src/server/authserver/Realms/RealmList.h
Normal file
85
src/server/authserver/Realms/RealmList.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _REALMLIST_H
|
||||
#define _REALMLIST_H
|
||||
|
||||
#include <ace/Singleton.h>
|
||||
#include <ace/Null_Mutex.h>
|
||||
#include <ace/INET_Addr.h>
|
||||
#include "Common.h"
|
||||
|
||||
enum RealmFlags
|
||||
{
|
||||
REALM_FLAG_NONE = 0x00,
|
||||
REALM_FLAG_INVALID = 0x01,
|
||||
REALM_FLAG_OFFLINE = 0x02,
|
||||
REALM_FLAG_SPECIFYBUILD = 0x04,
|
||||
REALM_FLAG_UNK1 = 0x08,
|
||||
REALM_FLAG_UNK2 = 0x10,
|
||||
REALM_FLAG_RECOMMENDED = 0x20,
|
||||
REALM_FLAG_NEW = 0x40,
|
||||
REALM_FLAG_FULL = 0x80
|
||||
};
|
||||
|
||||
// Storage object for a realm
|
||||
struct Realm
|
||||
{
|
||||
ACE_INET_Addr ExternalAddress;
|
||||
ACE_INET_Addr LocalAddress;
|
||||
ACE_INET_Addr LocalSubnetMask;
|
||||
std::string name;
|
||||
uint8 icon;
|
||||
RealmFlags flag;
|
||||
uint8 timezone;
|
||||
uint32 m_ID;
|
||||
AccountTypes allowedSecurityLevel;
|
||||
float populationLevel;
|
||||
uint32 gamebuild;
|
||||
};
|
||||
|
||||
/// Storage object for the list of realms on the server
|
||||
class RealmList
|
||||
{
|
||||
public:
|
||||
typedef std::map<std::string, Realm> RealmMap;
|
||||
|
||||
RealmList();
|
||||
~RealmList() { }
|
||||
|
||||
void Initialize(uint32 updateInterval);
|
||||
|
||||
void UpdateIfNeed();
|
||||
|
||||
void AddRealm(const Realm& NewRealm) { m_realms[NewRealm.name] = NewRealm; }
|
||||
|
||||
RealmMap::const_iterator begin() const { return m_realms.begin(); }
|
||||
RealmMap::const_iterator end() const { return m_realms.end(); }
|
||||
uint32 size() const { return m_realms.size(); }
|
||||
|
||||
private:
|
||||
void UpdateRealms(bool init=false);
|
||||
void UpdateRealm(uint32 id, const std::string& name, ACE_INET_Addr const& address, ACE_INET_Addr const& localAddr, ACE_INET_Addr const& localSubmask, uint8 icon, RealmFlags flag, uint8 timezone, AccountTypes allowedSecurityLevel, float popu, uint32 build);
|
||||
|
||||
RealmMap m_realms;
|
||||
uint32 m_UpdateInterval;
|
||||
time_t m_NextUpdateTime;
|
||||
};
|
||||
|
||||
#define sRealmList ACE_Singleton<RealmList, ACE_Null_Mutex>::instance()
|
||||
#endif
|
||||
1181
src/server/authserver/Server/AuthSocket.cpp
Normal file
1181
src/server/authserver/Server/AuthSocket.cpp
Normal file
File diff suppressed because it is too large
Load Diff
82
src/server/authserver/Server/AuthSocket.h
Normal file
82
src/server/authserver/Server/AuthSocket.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _AUTHSOCKET_H
|
||||
#define _AUTHSOCKET_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "BigNumber.h"
|
||||
#include "RealmSocket.h"
|
||||
|
||||
class ACE_INET_Addr;
|
||||
struct Realm;
|
||||
|
||||
// Handle login commands
|
||||
class AuthSocket: public RealmSocket::Session
|
||||
{
|
||||
public:
|
||||
const static int s_BYTE_SIZE = 32;
|
||||
|
||||
AuthSocket(RealmSocket& socket);
|
||||
virtual ~AuthSocket(void);
|
||||
|
||||
virtual void OnRead(void);
|
||||
virtual void OnAccept(void);
|
||||
virtual void OnClose(void);
|
||||
|
||||
static ACE_INET_Addr const& GetAddressForClient(Realm const& realm, ACE_INET_Addr const& clientAddr);
|
||||
|
||||
bool _HandleLogonChallenge();
|
||||
bool _HandleLogonProof();
|
||||
bool _HandleReconnectChallenge();
|
||||
bool _HandleReconnectProof();
|
||||
bool _HandleRealmList();
|
||||
|
||||
//data transfer handle for patch
|
||||
bool _HandleXferResume();
|
||||
bool _HandleXferCancel();
|
||||
bool _HandleXferAccept();
|
||||
|
||||
void _SetVSFields(const std::string& rI);
|
||||
|
||||
FILE* pPatch;
|
||||
ACE_Thread_Mutex patcherLock;
|
||||
|
||||
private:
|
||||
RealmSocket& socket_;
|
||||
RealmSocket& socket(void) { return socket_; }
|
||||
|
||||
BigNumber N, s, g, v;
|
||||
BigNumber b, B;
|
||||
BigNumber K;
|
||||
BigNumber _reconnectProof;
|
||||
|
||||
bool _authed;
|
||||
|
||||
std::string _login;
|
||||
|
||||
// Since GetLocaleByName() is _NOT_ bijective, we have to store the locale as a string. Otherwise we can't differ
|
||||
// between enUS and enGB, which is important for the patch system
|
||||
std::string _localizationName;
|
||||
std::string _os;
|
||||
uint16 _build;
|
||||
uint8 _expversion;
|
||||
AccountTypes _accountSecurityLevel;
|
||||
};
|
||||
|
||||
#endif
|
||||
70
src/server/authserver/Server/RealmAcceptor.h
Normal file
70
src/server/authserver/Server/RealmAcceptor.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 __REALMACCEPTOR_H__
|
||||
#define __REALMACCEPTOR_H__
|
||||
|
||||
#include <ace/Acceptor.h>
|
||||
#include <ace/SOCK_Acceptor.h>
|
||||
|
||||
#include "RealmSocket.h"
|
||||
#include "AuthSocket.h"
|
||||
|
||||
class RealmAcceptor : public ACE_Acceptor<RealmSocket, ACE_SOCK_Acceptor>
|
||||
{
|
||||
public:
|
||||
RealmAcceptor(void) { }
|
||||
virtual ~RealmAcceptor(void)
|
||||
{
|
||||
if (reactor())
|
||||
reactor()->cancel_timer(this, 1);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual int make_svc_handler(RealmSocket* &sh)
|
||||
{
|
||||
if (sh == 0)
|
||||
ACE_NEW_RETURN(sh, RealmSocket, -1);
|
||||
|
||||
sh->reactor(reactor());
|
||||
sh->set_session(new AuthSocket(*sh));
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int handle_timeout(const ACE_Time_Value& /*current_time*/, const void* /*act = 0*/)
|
||||
{
|
||||
sLog->outBasic("Resuming acceptor");
|
||||
reactor()->cancel_timer(this, 1);
|
||||
return reactor()->register_handler(this, ACE_Event_Handler::ACCEPT_MASK);
|
||||
}
|
||||
|
||||
virtual int handle_accept_error(void)
|
||||
{
|
||||
#if defined(ENFILE) && defined(EMFILE)
|
||||
if (errno == ENFILE || errno == EMFILE)
|
||||
{
|
||||
sLog->outError("Out of file descriptors, suspending incoming connections for 10 seconds");
|
||||
reactor()->remove_handler(this, ACE_Event_Handler::ACCEPT_MASK | ACE_Event_Handler::DONT_CALL);
|
||||
reactor()->schedule_timer(this, NULL, ACE_Time_Value(10));
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
291
src/server/authserver/Server/RealmSocket.cpp
Normal file
291
src/server/authserver/Server/RealmSocket.cpp
Normal file
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <ace/OS_NS_string.h>
|
||||
#include <ace/INET_Addr.h>
|
||||
#include <ace/SString.h>
|
||||
|
||||
#include "RealmSocket.h"
|
||||
#include "Log.h"
|
||||
|
||||
RealmSocket::Session::Session(void) { }
|
||||
|
||||
RealmSocket::Session::~Session(void) { }
|
||||
|
||||
RealmSocket::RealmSocket(void) :
|
||||
input_buffer_(4096), session_(NULL),
|
||||
_remoteAddress(), _remotePort(0)
|
||||
{
|
||||
reference_counting_policy().value(ACE_Event_Handler::Reference_Counting_Policy::ENABLED);
|
||||
|
||||
msg_queue()->high_water_mark(8 * 1024 * 1024);
|
||||
msg_queue()->low_water_mark(8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
RealmSocket::~RealmSocket(void)
|
||||
{
|
||||
if (msg_queue())
|
||||
msg_queue()->close();
|
||||
|
||||
// delete RealmSocketObject must never be called from our code.
|
||||
closing_ = true;
|
||||
|
||||
delete session_;
|
||||
|
||||
peer().close();
|
||||
}
|
||||
|
||||
int RealmSocket::open(void * arg)
|
||||
{
|
||||
ACE_INET_Addr addr;
|
||||
|
||||
if (peer().get_remote_addr(addr) == -1)
|
||||
{
|
||||
sLog->outError("Error %s while opening realm socket!", ACE_OS::strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
_remoteAddress = addr.get_host_addr();
|
||||
_remotePort = addr.get_port_number();
|
||||
|
||||
// Register with ACE Reactor
|
||||
if (Base::open(arg) == -1)
|
||||
return -1;
|
||||
|
||||
if (session_)
|
||||
session_->OnAccept();
|
||||
|
||||
// reactor takes care of the socket from now on
|
||||
remove_reference();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RealmSocket::close(u_long)
|
||||
{
|
||||
shutdown();
|
||||
|
||||
closing_ = true;
|
||||
|
||||
remove_reference();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string& RealmSocket::getRemoteAddress(void) const
|
||||
{
|
||||
return _remoteAddress;
|
||||
}
|
||||
|
||||
uint16 RealmSocket::getRemotePort(void) const
|
||||
{
|
||||
return _remotePort;
|
||||
}
|
||||
|
||||
size_t RealmSocket::recv_len(void) const
|
||||
{
|
||||
return input_buffer_.length();
|
||||
}
|
||||
|
||||
bool RealmSocket::recv_soft(char *buf, size_t len)
|
||||
{
|
||||
if (input_buffer_.length() < len)
|
||||
return false;
|
||||
|
||||
ACE_OS::memcpy(buf, input_buffer_.rd_ptr(), len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RealmSocket::recv(char *buf, size_t len)
|
||||
{
|
||||
bool ret = recv_soft(buf, len);
|
||||
|
||||
if (ret)
|
||||
recv_skip(len);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RealmSocket::recv_skip(size_t len)
|
||||
{
|
||||
input_buffer_.rd_ptr(len);
|
||||
}
|
||||
|
||||
ssize_t RealmSocket::noblk_send(ACE_Message_Block &message_block)
|
||||
{
|
||||
const size_t len = message_block.length();
|
||||
|
||||
if (len == 0)
|
||||
return -1;
|
||||
|
||||
// Try to send the message directly.
|
||||
#ifdef MSG_NOSIGNAL
|
||||
ssize_t n = peer().send(message_block.rd_ptr(), len, MSG_NOSIGNAL);
|
||||
#else
|
||||
ssize_t n = peer().send(message_block.rd_ptr(), len);
|
||||
#endif // MSG_NOSIGNAL
|
||||
|
||||
if (n < 0)
|
||||
{
|
||||
if (errno == EWOULDBLOCK) // Blocking signal
|
||||
return 0;
|
||||
else // Error happened
|
||||
return -1;
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
// Can this happen ?
|
||||
return -1;
|
||||
}
|
||||
|
||||
// return bytes transmitted
|
||||
return n;
|
||||
}
|
||||
|
||||
bool RealmSocket::send(const char *buf, size_t len)
|
||||
{
|
||||
if (buf == NULL || len == 0)
|
||||
return true;
|
||||
|
||||
ACE_Data_Block db(len, ACE_Message_Block::MB_DATA, (const char*)buf, 0, 0, ACE_Message_Block::DONT_DELETE, 0);
|
||||
ACE_Message_Block message_block(&db, ACE_Message_Block::DONT_DELETE, 0);
|
||||
|
||||
message_block.wr_ptr(len);
|
||||
|
||||
if (msg_queue()->is_empty())
|
||||
{
|
||||
// Try to send it directly.
|
||||
ssize_t n = noblk_send(message_block);
|
||||
|
||||
if (n < 0)
|
||||
return false;
|
||||
|
||||
size_t un = size_t(n);
|
||||
if (un == len)
|
||||
return true;
|
||||
|
||||
// fall down
|
||||
message_block.rd_ptr(un);
|
||||
}
|
||||
|
||||
ACE_Message_Block* mb = message_block.clone();
|
||||
|
||||
if (msg_queue()->enqueue_tail(mb, (ACE_Time_Value *)(&ACE_Time_Value::zero)) == -1)
|
||||
{
|
||||
mb->release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int RealmSocket::handle_output(ACE_HANDLE)
|
||||
{
|
||||
if (closing_)
|
||||
return -1;
|
||||
|
||||
ACE_Message_Block* mb = 0;
|
||||
|
||||
if (msg_queue()->is_empty())
|
||||
{
|
||||
reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msg_queue()->dequeue_head(mb, (ACE_Time_Value *)(&ACE_Time_Value::zero)) == -1)
|
||||
return -1;
|
||||
|
||||
ssize_t n = noblk_send(*mb);
|
||||
|
||||
if (n < 0)
|
||||
{
|
||||
mb->release();
|
||||
return -1;
|
||||
}
|
||||
else if (size_t(n) == mb->length())
|
||||
{
|
||||
mb->release();
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
mb->rd_ptr(n);
|
||||
|
||||
if (msg_queue()->enqueue_head(mb, (ACE_Time_Value *) &ACE_Time_Value::zero) == -1)
|
||||
{
|
||||
mb->release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ACE_NOTREACHED(return -1);
|
||||
}
|
||||
|
||||
int RealmSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask)
|
||||
{
|
||||
// As opposed to WorldSocket::handle_close, we don't need locks here.
|
||||
closing_ = true;
|
||||
|
||||
if (h == ACE_INVALID_HANDLE)
|
||||
peer().close_writer();
|
||||
|
||||
if (session_)
|
||||
session_->OnClose();
|
||||
|
||||
reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RealmSocket::handle_input(ACE_HANDLE)
|
||||
{
|
||||
if (closing_)
|
||||
return -1;
|
||||
|
||||
const ssize_t space = input_buffer_.space();
|
||||
|
||||
ssize_t n = peer().recv(input_buffer_.wr_ptr(), space);
|
||||
|
||||
if (n < 0)
|
||||
return errno == EWOULDBLOCK ? 0 : -1;
|
||||
else if (n == 0) // EOF
|
||||
return -1;
|
||||
|
||||
input_buffer_.wr_ptr((size_t)n);
|
||||
|
||||
if (session_ != NULL)
|
||||
{
|
||||
session_->OnRead();
|
||||
input_buffer_.crunch();
|
||||
}
|
||||
|
||||
// return 1 in case there is more data to read from OS
|
||||
return n == space ? 1 : 0;
|
||||
}
|
||||
|
||||
void RealmSocket::set_session(Session* session)
|
||||
{
|
||||
delete session_;
|
||||
|
||||
session_ = session;
|
||||
}
|
||||
80
src/server/authserver/Server/RealmSocket.h
Normal file
80
src/server/authserver/Server/RealmSocket.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 __REALMSOCKET_H__
|
||||
#define __REALMSOCKET_H__
|
||||
|
||||
#include <ace/Synch_Traits.h>
|
||||
#include <ace/Svc_Handler.h>
|
||||
#include <ace/SOCK_Stream.h>
|
||||
#include <ace/Message_Block.h>
|
||||
#include <ace/Basic_Types.h>
|
||||
#include "Common.h"
|
||||
|
||||
class RealmSocket : public ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>
|
||||
{
|
||||
private:
|
||||
typedef ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> Base;
|
||||
|
||||
public:
|
||||
class Session
|
||||
{
|
||||
public:
|
||||
Session(void);
|
||||
virtual ~Session(void);
|
||||
|
||||
virtual void OnRead(void) = 0;
|
||||
virtual void OnAccept(void) = 0;
|
||||
virtual void OnClose(void) = 0;
|
||||
};
|
||||
|
||||
RealmSocket(void);
|
||||
virtual ~RealmSocket(void);
|
||||
|
||||
size_t recv_len(void) const;
|
||||
bool recv_soft(char *buf, size_t len);
|
||||
bool recv(char *buf, size_t len);
|
||||
void recv_skip(size_t len);
|
||||
|
||||
bool send(const char *buf, size_t len);
|
||||
|
||||
const std::string& getRemoteAddress(void) const;
|
||||
|
||||
uint16 getRemotePort(void) const;
|
||||
|
||||
virtual int open(void *);
|
||||
|
||||
virtual int close(u_long);
|
||||
|
||||
virtual int handle_input(ACE_HANDLE = ACE_INVALID_HANDLE);
|
||||
virtual int handle_output(ACE_HANDLE = ACE_INVALID_HANDLE);
|
||||
|
||||
virtual int handle_close(ACE_HANDLE = ACE_INVALID_HANDLE, ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK);
|
||||
|
||||
void set_session(Session* session);
|
||||
|
||||
private:
|
||||
ssize_t noblk_send(ACE_Message_Block &message_block);
|
||||
|
||||
ACE_Message_Block input_buffer_;
|
||||
Session* session_;
|
||||
std::string _remoteAddress;
|
||||
uint16 _remotePort;
|
||||
};
|
||||
|
||||
#endif /* __REALMSOCKET_H__ */
|
||||
BIN
src/server/authserver/Thumbs.db
Normal file
BIN
src/server/authserver/Thumbs.db
Normal file
Binary file not shown.
260
src/server/authserver/authserver.conf.dist
Normal file
260
src/server/authserver/authserver.conf.dist
Normal file
@@ -0,0 +1,260 @@
|
||||
###############################################
|
||||
# Trinity Core Auth Server configuration file #
|
||||
###############################################
|
||||
[authserver]
|
||||
|
||||
###################################################################################################
|
||||
# SECTION INDEX
|
||||
#
|
||||
# EXAMPLE CONFIG
|
||||
# AUTH SERVER SETTINGS
|
||||
# MYSQL SETTINGS
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
###################################################################################################
|
||||
# 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.
|
||||
# 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"
|
||||
|
||||
#
|
||||
# PidFile
|
||||
# Description: Auth server PID file.
|
||||
# Example: "./authserver.pid" - (Enabled)
|
||||
# Default: "" - (Disabled)
|
||||
|
||||
PidFile = ""
|
||||
|
||||
#
|
||||
# LogLevel
|
||||
# Description: Server console level of logging
|
||||
# Default: 0 - (Minimum)
|
||||
# 1 - (Basic)
|
||||
# 2 - (Detail)
|
||||
# 3 - (Full/Debug)
|
||||
|
||||
LogLevel = 0
|
||||
|
||||
#
|
||||
# LogFile
|
||||
# Description: Log file for main server log.
|
||||
# Default: "Auth.log" - (Enabled)
|
||||
# "" - (Disabled)
|
||||
|
||||
LogFile = "Auth.log"
|
||||
|
||||
#
|
||||
# Debug Log Mask
|
||||
# Description: Bitmask that determines which debug log output (level 3)
|
||||
# will be logged.
|
||||
# Possible flags:
|
||||
#
|
||||
# 64 - Anything related to network input/output,
|
||||
# such as packet handlers and netcode logs
|
||||
#
|
||||
# Simply add the values together to create a bitmask.
|
||||
# For more info see enum DebugLogFilters in Log.h
|
||||
#
|
||||
# Default: 0 (nothing)
|
||||
|
||||
DebugLogMask = 64
|
||||
|
||||
#
|
||||
# SQLDriverLogFile
|
||||
# Description: Log file for SQL driver events.
|
||||
# Example: "SQLDriver.log" - (Enabled)
|
||||
# Default: "" - (Disabled)
|
||||
|
||||
SQLDriverLogFile = ""
|
||||
|
||||
#
|
||||
# SQLDriverQueryLogging
|
||||
# Description: Log SQL queries to the SQLDriverLogFile and console.
|
||||
# Default: 0 - (Disabled, Query errors only)
|
||||
# 1 - (Enabled, Full query logging - may have performance impact)
|
||||
|
||||
SQLDriverQueryLogging = 0
|
||||
|
||||
#
|
||||
# LogTimestamp
|
||||
# Description: Append timestamp to the server log file name.
|
||||
# Logname_YYYY-MM-DD_HH-MM-SS.Ext for Logname.Ext
|
||||
# Default: 0 - (Disabled)
|
||||
# 1 - (Enabled)
|
||||
|
||||
LogTimestamp = 0
|
||||
|
||||
#
|
||||
# LogFileLevel
|
||||
# Description: Server file level of logging
|
||||
# Default: 0 - (Minimum)
|
||||
# 1 - (Basic)
|
||||
# 2 - (Detail)
|
||||
# 3 - (Full/Debug)
|
||||
|
||||
LogFileLevel = 0
|
||||
|
||||
#
|
||||
# LogColors
|
||||
# Description: Colors for log messages (Format: "normal basic detail debug").
|
||||
# Colors: 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: "13 11 9 5" - (Enabled)
|
||||
# Default: "" - (Disabled)
|
||||
|
||||
LogColors = ""
|
||||
|
||||
#
|
||||
# EnableLogDB
|
||||
# Description: Write log messages to database (LogDatabaseInfo).
|
||||
# Default: 0 - (Disabled)
|
||||
# 1 - (Enabled)
|
||||
|
||||
EnableLogDB = 0
|
||||
|
||||
#
|
||||
# DBLogLevel
|
||||
# Description: Log level of databases logging.
|
||||
# Default: 1 - (Basic)
|
||||
# 0 - (Minimum)
|
||||
# 2 - (Detail)
|
||||
# 3 - (Full/Debug)
|
||||
|
||||
DBLogLevel = 1
|
||||
|
||||
#
|
||||
# UseProcessors
|
||||
# Description: Processors mask for Windows and Linux based multi-processor systems.
|
||||
# Example: A computer with 2 CPUs:
|
||||
# 1 - 1st CPU only, 2 - 2nd CPU only, 3 - 1st and 2nd CPU, because 1 | 2 is 3
|
||||
# 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 attemps 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
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
###################################################################################################
|
||||
# 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;trinity;trinity;auth"
|
||||
|
||||
LoginDatabaseInfo = "127.0.0.1;3306;trinity;trinity;auth"
|
||||
|
||||
#
|
||||
# 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
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
BIN
src/server/authserver/authserver.ico
Normal file
BIN
src/server/authserver/authserver.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 133 KiB |
94
src/server/authserver/authserver.rc
Normal file
94
src/server/authserver/authserver.rc
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VER_FILEVERSION
|
||||
PRODUCTVERSION VER_PRODUCTVERSION
|
||||
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
|
||||
#ifndef _DEBUG
|
||||
FILEFLAGS 0
|
||||
#else
|
||||
#define VER_PRERELEASE VS_FF_PRERELEASE
|
||||
#define VER_PRIVATEBUILD VS_FF_PRIVATEBUILD
|
||||
#define VER_DEBUG 0
|
||||
FILEFLAGS (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG)
|
||||
#endif
|
||||
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "080004b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", VER_COMPANYNAME_STR
|
||||
VALUE "FileDescription", "Authentication Server Daemon"
|
||||
VALUE "FileVersion", VER_FILEVERSION_STR
|
||||
VALUE "InternalName", "authserver"
|
||||
VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR
|
||||
VALUE "OriginalFilename", "authserver.exe"
|
||||
VALUE "ProductName", "Authentication Server"
|
||||
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x800, 1200
|
||||
END
|
||||
END
|
||||
#endif
|
||||
15
src/server/authserver/resource.h
Normal file
15
src/server/authserver/resource.h
Normal file
@@ -0,0 +1,15 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by SunwellCore.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
|
||||
310
src/server/collision/BoundingIntervalHierarchy.cpp
Normal file
310
src/server/collision/BoundingIntervalHierarchy.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "BoundingIntervalHierarchy.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define isnan _isnan
|
||||
#else
|
||||
#define isnan std::isnan
|
||||
#endif
|
||||
|
||||
void BIH::buildHierarchy(std::vector<uint32> &tempTree, buildData &dat, BuildStats &stats)
|
||||
{
|
||||
// create space for the first node
|
||||
tempTree.push_back(uint32(3 << 30)); // dummy leaf
|
||||
tempTree.insert(tempTree.end(), 2, 0);
|
||||
//tempTree.add(0);
|
||||
|
||||
// seed bbox
|
||||
AABound gridBox = { bounds.low(), bounds.high() };
|
||||
AABound nodeBox = gridBox;
|
||||
// seed subdivide function
|
||||
subdivide(0, dat.numPrims - 1, tempTree, dat, gridBox, nodeBox, 0, 1, stats);
|
||||
}
|
||||
|
||||
void BIH::subdivide(int left, int right, std::vector<uint32> &tempTree, buildData &dat, AABound &gridBox, AABound &nodeBox, int nodeIndex, int depth, BuildStats &stats)
|
||||
{
|
||||
if ((right - left + 1) <= dat.maxPrims || depth >= MAX_STACK_SIZE)
|
||||
{
|
||||
// write leaf node
|
||||
stats.updateLeaf(depth, right - left + 1);
|
||||
createNode(tempTree, nodeIndex, left, right);
|
||||
return;
|
||||
}
|
||||
// calculate extents
|
||||
int axis = -1, prevAxis, rightOrig;
|
||||
float clipL = G3D::fnan(), clipR = G3D::fnan(), prevClip = G3D::fnan();
|
||||
float split = G3D::fnan(), prevSplit;
|
||||
bool wasLeft = true;
|
||||
while (true)
|
||||
{
|
||||
prevAxis = axis;
|
||||
prevSplit = split;
|
||||
// perform quick consistency checks
|
||||
G3D::Vector3 d( gridBox.hi - gridBox.lo );
|
||||
if (d.x < 0 || d.y < 0 || d.z < 0)
|
||||
throw std::logic_error("negative node extents");
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (nodeBox.hi[i] < gridBox.lo[i] || nodeBox.lo[i] > gridBox.hi[i])
|
||||
{
|
||||
//UI.printError(Module.ACCEL, "Reached tree area in error - discarding node with: %d objects", right - left + 1);
|
||||
throw std::logic_error("invalid node overlap");
|
||||
}
|
||||
}
|
||||
// find longest axis
|
||||
axis = d.primaryAxis();
|
||||
split = 0.5f * (gridBox.lo[axis] + gridBox.hi[axis]);
|
||||
// partition L/R subsets
|
||||
clipL = -G3D::inf();
|
||||
clipR = G3D::inf();
|
||||
rightOrig = right; // save this for later
|
||||
float nodeL = G3D::inf();
|
||||
float nodeR = -G3D::inf();
|
||||
for (int i = left; i <= right;)
|
||||
{
|
||||
int obj = dat.indices[i];
|
||||
float minb = dat.primBound[obj].low()[axis];
|
||||
float maxb = dat.primBound[obj].high()[axis];
|
||||
float center = (minb + maxb) * 0.5f;
|
||||
if (center <= split)
|
||||
{
|
||||
// stay left
|
||||
i++;
|
||||
if (clipL < maxb)
|
||||
clipL = maxb;
|
||||
}
|
||||
else
|
||||
{
|
||||
// move to the right most
|
||||
int t = dat.indices[i];
|
||||
dat.indices[i] = dat.indices[right];
|
||||
dat.indices[right] = t;
|
||||
right--;
|
||||
if (clipR > minb)
|
||||
clipR = minb;
|
||||
}
|
||||
nodeL = std::min(nodeL, minb);
|
||||
nodeR = std::max(nodeR, maxb);
|
||||
}
|
||||
// check for empty space
|
||||
if (nodeL > nodeBox.lo[axis] && nodeR < nodeBox.hi[axis])
|
||||
{
|
||||
float nodeBoxW = nodeBox.hi[axis] - nodeBox.lo[axis];
|
||||
float nodeNewW = nodeR - nodeL;
|
||||
// node box is too big compare to space occupied by primitives?
|
||||
if (1.3f * nodeNewW < nodeBoxW)
|
||||
{
|
||||
stats.updateBVH2();
|
||||
int nextIndex = tempTree.size();
|
||||
// allocate child
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
// write bvh2 clip node
|
||||
stats.updateInner();
|
||||
tempTree[nodeIndex + 0] = (axis << 30) | (1 << 29) | nextIndex;
|
||||
tempTree[nodeIndex + 1] = floatToRawIntBits(nodeL);
|
||||
tempTree[nodeIndex + 2] = floatToRawIntBits(nodeR);
|
||||
// update nodebox and recurse
|
||||
nodeBox.lo[axis] = nodeL;
|
||||
nodeBox.hi[axis] = nodeR;
|
||||
subdivide(left, rightOrig, tempTree, dat, gridBox, nodeBox, nextIndex, depth + 1, stats);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ensure we are making progress in the subdivision
|
||||
if (right == rightOrig)
|
||||
{
|
||||
// all left
|
||||
if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split)) {
|
||||
// we are stuck here - create a leaf
|
||||
stats.updateLeaf(depth, right - left + 1);
|
||||
createNode(tempTree, nodeIndex, left, right);
|
||||
return;
|
||||
}
|
||||
if (clipL <= split) {
|
||||
// keep looping on left half
|
||||
gridBox.hi[axis] = split;
|
||||
prevClip = clipL;
|
||||
wasLeft = true;
|
||||
continue;
|
||||
}
|
||||
gridBox.hi[axis] = split;
|
||||
prevClip = G3D::fnan();
|
||||
}
|
||||
else if (left > right)
|
||||
{
|
||||
// all right
|
||||
if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split)) {
|
||||
// we are stuck here - create a leaf
|
||||
stats.updateLeaf(depth, right - left + 1);
|
||||
createNode(tempTree, nodeIndex, left, right);
|
||||
return;
|
||||
}
|
||||
right = rightOrig;
|
||||
if (clipR >= split) {
|
||||
// keep looping on right half
|
||||
gridBox.lo[axis] = split;
|
||||
prevClip = clipR;
|
||||
wasLeft = false;
|
||||
continue;
|
||||
}
|
||||
gridBox.lo[axis] = split;
|
||||
prevClip = G3D::fnan();
|
||||
}
|
||||
else
|
||||
{
|
||||
// we are actually splitting stuff
|
||||
if (prevAxis != -1 && !isnan(prevClip))
|
||||
{
|
||||
// second time through - lets create the previous split
|
||||
// since it produced empty space
|
||||
int nextIndex = tempTree.size();
|
||||
// allocate child node
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
if (wasLeft) {
|
||||
// create a node with a left child
|
||||
// write leaf node
|
||||
stats.updateInner();
|
||||
tempTree[nodeIndex + 0] = (prevAxis << 30) | nextIndex;
|
||||
tempTree[nodeIndex + 1] = floatToRawIntBits(prevClip);
|
||||
tempTree[nodeIndex + 2] = floatToRawIntBits(G3D::inf());
|
||||
} else {
|
||||
// create a node with a right child
|
||||
// write leaf node
|
||||
stats.updateInner();
|
||||
tempTree[nodeIndex + 0] = (prevAxis << 30) | (nextIndex - 3);
|
||||
tempTree[nodeIndex + 1] = floatToRawIntBits(-G3D::inf());
|
||||
tempTree[nodeIndex + 2] = floatToRawIntBits(prevClip);
|
||||
}
|
||||
// count stats for the unused leaf
|
||||
depth++;
|
||||
stats.updateLeaf(depth, 0);
|
||||
// now we keep going as we are, with a new nodeIndex:
|
||||
nodeIndex = nextIndex;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// compute index of child nodes
|
||||
int nextIndex = tempTree.size();
|
||||
// allocate left node
|
||||
int nl = right - left + 1;
|
||||
int nr = rightOrig - (right + 1) + 1;
|
||||
if (nl > 0) {
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
} else
|
||||
nextIndex -= 3;
|
||||
// allocate right node
|
||||
if (nr > 0) {
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
tempTree.push_back(0);
|
||||
}
|
||||
// write leaf node
|
||||
stats.updateInner();
|
||||
tempTree[nodeIndex + 0] = (axis << 30) | nextIndex;
|
||||
tempTree[nodeIndex + 1] = floatToRawIntBits(clipL);
|
||||
tempTree[nodeIndex + 2] = floatToRawIntBits(clipR);
|
||||
// prepare L/R child boxes
|
||||
AABound gridBoxL(gridBox), gridBoxR(gridBox);
|
||||
AABound nodeBoxL(nodeBox), nodeBoxR(nodeBox);
|
||||
gridBoxL.hi[axis] = gridBoxR.lo[axis] = split;
|
||||
nodeBoxL.hi[axis] = clipL;
|
||||
nodeBoxR.lo[axis] = clipR;
|
||||
// recurse
|
||||
if (nl > 0)
|
||||
subdivide(left, right, tempTree, dat, gridBoxL, nodeBoxL, nextIndex, depth + 1, stats);
|
||||
else
|
||||
stats.updateLeaf(depth + 1, 0);
|
||||
if (nr > 0)
|
||||
subdivide(right + 1, rightOrig, tempTree, dat, gridBoxR, nodeBoxR, nextIndex + 3, depth + 1, stats);
|
||||
else
|
||||
stats.updateLeaf(depth + 1, 0);
|
||||
}
|
||||
|
||||
bool BIH::writeToFile(FILE* wf) const
|
||||
{
|
||||
uint32 treeSize = tree.size();
|
||||
uint32 check=0, count;
|
||||
check += fwrite(&bounds.low(), sizeof(float), 3, wf);
|
||||
check += fwrite(&bounds.high(), sizeof(float), 3, wf);
|
||||
check += fwrite(&treeSize, sizeof(uint32), 1, wf);
|
||||
check += fwrite(&tree[0], sizeof(uint32), treeSize, wf);
|
||||
count = objects.size();
|
||||
check += fwrite(&count, sizeof(uint32), 1, wf);
|
||||
check += fwrite(&objects[0], sizeof(uint32), count, wf);
|
||||
return check == (3 + 3 + 2 + treeSize + count);
|
||||
}
|
||||
|
||||
bool BIH::readFromFile(FILE* rf)
|
||||
{
|
||||
uint32 treeSize;
|
||||
G3D::Vector3 lo, hi;
|
||||
uint32 check=0, count=0;
|
||||
check += fread(&lo, sizeof(float), 3, rf);
|
||||
check += fread(&hi, sizeof(float), 3, rf);
|
||||
bounds = G3D::AABox(lo, hi);
|
||||
check += fread(&treeSize, sizeof(uint32), 1, rf);
|
||||
tree.resize(treeSize);
|
||||
check += fread(&tree[0], sizeof(uint32), treeSize, rf);
|
||||
check += fread(&count, sizeof(uint32), 1, rf);
|
||||
objects.resize(count); // = new uint32[nObjects];
|
||||
check += fread(&objects[0], sizeof(uint32), count, rf);
|
||||
return uint64(check) == uint64(3 + 3 + 1 + 1 + uint64(treeSize) + uint64(count));
|
||||
}
|
||||
|
||||
void BIH::BuildStats::updateLeaf(int depth, int n)
|
||||
{
|
||||
numLeaves++;
|
||||
minDepth = std::min(depth, minDepth);
|
||||
maxDepth = std::max(depth, maxDepth);
|
||||
sumDepth += depth;
|
||||
minObjects = std::min(n, minObjects);
|
||||
maxObjects = std::max(n, maxObjects);
|
||||
sumObjects += n;
|
||||
int nl = std::min(n, 5);
|
||||
++numLeavesN[nl];
|
||||
}
|
||||
|
||||
void BIH::BuildStats::printStats()
|
||||
{
|
||||
printf("Tree stats:\n");
|
||||
printf(" * Nodes: %d\n", numNodes);
|
||||
printf(" * Leaves: %d\n", numLeaves);
|
||||
printf(" * Objects: min %d\n", minObjects);
|
||||
printf(" avg %.2f\n", (float) sumObjects / numLeaves);
|
||||
printf(" avg(n>0) %.2f\n", (float) sumObjects / (numLeaves - numLeavesN[0]));
|
||||
printf(" max %d\n", maxObjects);
|
||||
printf(" * Depth: min %d\n", minDepth);
|
||||
printf(" avg %.2f\n", (float) sumDepth / numLeaves);
|
||||
printf(" max %d\n", maxDepth);
|
||||
printf(" * Leaves w/: N=0 %3d%%\n", 100 * numLeavesN[0] / numLeaves);
|
||||
printf(" N=1 %3d%%\n", 100 * numLeavesN[1] / numLeaves);
|
||||
printf(" N=2 %3d%%\n", 100 * numLeavesN[2] / numLeaves);
|
||||
printf(" N=3 %3d%%\n", 100 * numLeavesN[3] / numLeaves);
|
||||
printf(" N=4 %3d%%\n", 100 * numLeavesN[4] / numLeaves);
|
||||
printf(" N>4 %3d%%\n", 100 * numLeavesN[5] / numLeaves);
|
||||
printf(" * BVH2 nodes: %d (%3d%%)\n", numBVH2, 100 * numBVH2 / (numNodes + numLeaves - 2 * numBVH2));
|
||||
}
|
||||
400
src/server/collision/BoundingIntervalHierarchy.h
Normal file
400
src/server/collision/BoundingIntervalHierarchy.h
Normal file
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _BIH_H
|
||||
#define _BIH_H
|
||||
|
||||
#include "G3D/Vector3.h"
|
||||
#include "G3D/Ray.h"
|
||||
#include "G3D/AABox.h"
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
#define MAX_STACK_SIZE 64
|
||||
|
||||
static inline uint32 floatToRawIntBits(float f)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint32 ival;
|
||||
float fval;
|
||||
} temp;
|
||||
temp.fval=f;
|
||||
return temp.ival;
|
||||
}
|
||||
|
||||
static inline float intBitsToFloat(uint32 i)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint32 ival;
|
||||
float fval;
|
||||
} temp;
|
||||
temp.ival=i;
|
||||
return temp.fval;
|
||||
}
|
||||
|
||||
struct AABound
|
||||
{
|
||||
G3D::Vector3 lo, hi;
|
||||
};
|
||||
|
||||
/** Bounding Interval Hierarchy Class.
|
||||
Building and Ray-Intersection functions based on BIH from
|
||||
Sunflow, a Java Raytracer, released under MIT/X11 License
|
||||
http://sunflow.sourceforge.net/
|
||||
Copyright (c) 2003-2007 Christopher Kulla
|
||||
*/
|
||||
|
||||
class BIH
|
||||
{
|
||||
private:
|
||||
void init_empty()
|
||||
{
|
||||
tree.clear();
|
||||
objects.clear();
|
||||
// create space for the first node
|
||||
tree.push_back(3u << 30u); // dummy leaf
|
||||
tree.insert(tree.end(), 2, 0);
|
||||
}
|
||||
public:
|
||||
BIH() { init_empty(); }
|
||||
template< class BoundsFunc, class PrimArray >
|
||||
void build(const PrimArray &primitives, BoundsFunc &getBounds, uint32 leafSize = 3, bool printStats=false)
|
||||
{
|
||||
if (primitives.size() == 0)
|
||||
{
|
||||
init_empty();
|
||||
return;
|
||||
}
|
||||
|
||||
buildData dat;
|
||||
dat.maxPrims = leafSize;
|
||||
dat.numPrims = primitives.size();
|
||||
dat.indices = new uint32[dat.numPrims];
|
||||
dat.primBound = new G3D::AABox[dat.numPrims];
|
||||
getBounds(primitives[0], bounds);
|
||||
for (uint32 i=0; i<dat.numPrims; ++i)
|
||||
{
|
||||
dat.indices[i] = i;
|
||||
getBounds(primitives[i], dat.primBound[i]);
|
||||
bounds.merge(dat.primBound[i]);
|
||||
}
|
||||
std::vector<uint32> tempTree;
|
||||
BuildStats stats;
|
||||
buildHierarchy(tempTree, dat, stats);
|
||||
if (printStats)
|
||||
stats.printStats();
|
||||
|
||||
objects.resize(dat.numPrims);
|
||||
for (uint32 i=0; i<dat.numPrims; ++i)
|
||||
objects[i] = dat.indices[i];
|
||||
//nObjects = dat.numPrims;
|
||||
tree = tempTree;
|
||||
delete[] dat.primBound;
|
||||
delete[] dat.indices;
|
||||
}
|
||||
uint32 primCount() const { return objects.size(); }
|
||||
|
||||
template<typename RayCallback>
|
||||
void intersectRay(const G3D::Ray &r, RayCallback& intersectCallback, float &maxDist, bool stopAtFirstHit) const
|
||||
{
|
||||
float intervalMin = -1.f;
|
||||
float intervalMax = -1.f;
|
||||
G3D::Vector3 org = r.origin();
|
||||
G3D::Vector3 dir = r.direction();
|
||||
G3D::Vector3 invDir;
|
||||
for (int i=0; i<3; ++i)
|
||||
{
|
||||
invDir[i] = 1.f / dir[i];
|
||||
if (G3D::fuzzyNe(dir[i], 0.0f))
|
||||
{
|
||||
float t1 = (bounds.low()[i] - org[i]) * invDir[i];
|
||||
float t2 = (bounds.high()[i] - org[i]) * invDir[i];
|
||||
if (t1 > t2)
|
||||
std::swap(t1, t2);
|
||||
if (t1 > intervalMin)
|
||||
intervalMin = t1;
|
||||
if (t2 < intervalMax || intervalMax < 0.f)
|
||||
intervalMax = t2;
|
||||
// intervalMax can only become smaller for other axis,
|
||||
// and intervalMin only larger respectively, so stop early
|
||||
if (intervalMax <= 0 || intervalMin >= maxDist)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (intervalMin > intervalMax)
|
||||
return;
|
||||
intervalMin = std::max(intervalMin, 0.f);
|
||||
intervalMax = std::min(intervalMax, maxDist);
|
||||
|
||||
uint32 offsetFront[3];
|
||||
uint32 offsetBack[3];
|
||||
uint32 offsetFront3[3];
|
||||
uint32 offsetBack3[3];
|
||||
// compute custom offsets from direction sign bit
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
{
|
||||
offsetFront[i] = floatToRawIntBits(dir[i]) >> 31;
|
||||
offsetBack[i] = offsetFront[i] ^ 1;
|
||||
offsetFront3[i] = offsetFront[i] * 3;
|
||||
offsetBack3[i] = offsetBack[i] * 3;
|
||||
|
||||
// avoid always adding 1 during the inner loop
|
||||
++offsetFront[i];
|
||||
++offsetBack[i];
|
||||
}
|
||||
|
||||
StackNode stack[MAX_STACK_SIZE];
|
||||
int stackPos = 0;
|
||||
int node = 0;
|
||||
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
uint32 tn = tree[node];
|
||||
uint32 axis = (tn & (3 << 30)) >> 30;
|
||||
bool BVH2 = tn & (1 << 29);
|
||||
int offset = tn & ~(7 << 29);
|
||||
if (!BVH2)
|
||||
{
|
||||
if (axis < 3)
|
||||
{
|
||||
// "normal" interior node
|
||||
float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis];
|
||||
float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis];
|
||||
// ray passes between clip zones
|
||||
if (tf < intervalMin && tb > intervalMax)
|
||||
break;
|
||||
int back = offset + offsetBack3[axis];
|
||||
node = back;
|
||||
// ray passes through far node only
|
||||
if (tf < intervalMin) {
|
||||
intervalMin = (tb >= intervalMin) ? tb : intervalMin;
|
||||
continue;
|
||||
}
|
||||
node = offset + offsetFront3[axis]; // front
|
||||
// ray passes through near node only
|
||||
if (tb > intervalMax) {
|
||||
intervalMax = (tf <= intervalMax) ? tf : intervalMax;
|
||||
continue;
|
||||
}
|
||||
// ray passes through both nodes
|
||||
// push back node
|
||||
stack[stackPos].node = back;
|
||||
stack[stackPos].tnear = (tb >= intervalMin) ? tb : intervalMin;
|
||||
stack[stackPos].tfar = intervalMax;
|
||||
stackPos++;
|
||||
// update ray interval for front node
|
||||
intervalMax = (tf <= intervalMax) ? tf : intervalMax;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// leaf - test some objects
|
||||
int n = tree[node + 1];
|
||||
while (n > 0) {
|
||||
bool hit = intersectCallback(r, objects[offset], maxDist, stopAtFirstHit);
|
||||
if (stopAtFirstHit && hit) return;
|
||||
--n;
|
||||
++offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (axis>2)
|
||||
return; // should not happen
|
||||
float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis];
|
||||
float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis];
|
||||
node = offset;
|
||||
intervalMin = (tf >= intervalMin) ? tf : intervalMin;
|
||||
intervalMax = (tb <= intervalMax) ? tb : intervalMax;
|
||||
if (intervalMin > intervalMax)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
} // traversal loop
|
||||
do
|
||||
{
|
||||
// stack is empty?
|
||||
if (stackPos == 0)
|
||||
return;
|
||||
// move back up the stack
|
||||
stackPos--;
|
||||
intervalMin = stack[stackPos].tnear;
|
||||
if (maxDist < intervalMin)
|
||||
continue;
|
||||
node = stack[stackPos].node;
|
||||
intervalMax = stack[stackPos].tfar;
|
||||
break;
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename IsectCallback>
|
||||
void intersectPoint(const G3D::Vector3 &p, IsectCallback& intersectCallback) const
|
||||
{
|
||||
if (!bounds.contains(p))
|
||||
return;
|
||||
|
||||
StackNode stack[MAX_STACK_SIZE];
|
||||
int stackPos = 0;
|
||||
int node = 0;
|
||||
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
uint32 tn = tree[node];
|
||||
uint32 axis = (tn & (3 << 30)) >> 30;
|
||||
bool BVH2 = tn & (1 << 29);
|
||||
int offset = tn & ~(7 << 29);
|
||||
if (!BVH2)
|
||||
{
|
||||
if (axis < 3)
|
||||
{
|
||||
// "normal" interior node
|
||||
float tl = intBitsToFloat(tree[node + 1]);
|
||||
float tr = intBitsToFloat(tree[node + 2]);
|
||||
// point is between clip zones
|
||||
if (tl < p[axis] && tr > p[axis])
|
||||
break;
|
||||
int right = offset + 3;
|
||||
node = right;
|
||||
// point is in right node only
|
||||
if (tl < p[axis]) {
|
||||
continue;
|
||||
}
|
||||
node = offset; // left
|
||||
// point is in left node only
|
||||
if (tr > p[axis]) {
|
||||
continue;
|
||||
}
|
||||
// point is in both nodes
|
||||
// push back right node
|
||||
stack[stackPos].node = right;
|
||||
stackPos++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// leaf - test some objects
|
||||
int n = tree[node + 1];
|
||||
while (n > 0) {
|
||||
intersectCallback(p, objects[offset]); // !!!
|
||||
--n;
|
||||
++offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // BVH2 node (empty space cut off left and right)
|
||||
{
|
||||
if (axis>2)
|
||||
return; // should not happen
|
||||
float tl = intBitsToFloat(tree[node + 1]);
|
||||
float tr = intBitsToFloat(tree[node + 2]);
|
||||
node = offset;
|
||||
if (tl > p[axis] || tr < p[axis])
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
} // traversal loop
|
||||
|
||||
// stack is empty?
|
||||
if (stackPos == 0)
|
||||
return;
|
||||
// move back up the stack
|
||||
stackPos--;
|
||||
node = stack[stackPos].node;
|
||||
}
|
||||
}
|
||||
|
||||
bool writeToFile(FILE* wf) const;
|
||||
bool readFromFile(FILE* rf);
|
||||
|
||||
protected:
|
||||
std::vector<uint32> tree;
|
||||
std::vector<uint32> objects;
|
||||
G3D::AABox bounds;
|
||||
|
||||
struct buildData
|
||||
{
|
||||
uint32 *indices;
|
||||
G3D::AABox *primBound;
|
||||
uint32 numPrims;
|
||||
int maxPrims;
|
||||
};
|
||||
struct StackNode
|
||||
{
|
||||
uint32 node;
|
||||
float tnear;
|
||||
float tfar;
|
||||
};
|
||||
|
||||
class BuildStats
|
||||
{
|
||||
private:
|
||||
int numNodes;
|
||||
int numLeaves;
|
||||
int sumObjects;
|
||||
int minObjects;
|
||||
int maxObjects;
|
||||
int sumDepth;
|
||||
int minDepth;
|
||||
int maxDepth;
|
||||
int numLeavesN[6];
|
||||
int numBVH2;
|
||||
|
||||
public:
|
||||
BuildStats():
|
||||
numNodes(0), numLeaves(0), sumObjects(0), minObjects(0x0FFFFFFF),
|
||||
maxObjects(0xFFFFFFFF), sumDepth(0), minDepth(0x0FFFFFFF),
|
||||
maxDepth(0xFFFFFFFF), numBVH2(0)
|
||||
{
|
||||
for (int i=0; i<6; ++i) numLeavesN[i] = 0;
|
||||
}
|
||||
|
||||
void updateInner() { numNodes++; }
|
||||
void updateBVH2() { numBVH2++; }
|
||||
void updateLeaf(int depth, int n);
|
||||
void printStats();
|
||||
};
|
||||
|
||||
void buildHierarchy(std::vector<uint32> &tempTree, buildData &dat, BuildStats &stats);
|
||||
|
||||
void createNode(std::vector<uint32> &tempTree, int nodeIndex, uint32 left, uint32 right) const
|
||||
{
|
||||
// write leaf node
|
||||
tempTree[nodeIndex + 0] = (3 << 30) | left;
|
||||
tempTree[nodeIndex + 1] = right - left + 1;
|
||||
}
|
||||
|
||||
void subdivide(int left, int right, std::vector<uint32> &tempTree, buildData &dat, AABound &gridBox, AABound &nodeBox, int nodeIndex, int depth, BuildStats &stats);
|
||||
};
|
||||
|
||||
#endif // _BIH_H
|
||||
119
src/server/collision/BoundingIntervalHierarchyWrapper.h
Normal file
119
src/server/collision/BoundingIntervalHierarchyWrapper.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _BIH_WRAP
|
||||
#define _BIH_WRAP
|
||||
|
||||
#include "G3D/Table.h"
|
||||
#include "G3D/Array.h"
|
||||
#include "G3D/Set.h"
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
|
||||
|
||||
template<class T, class BoundsFunc = BoundsTrait<T> >
|
||||
class BIHWrap
|
||||
{
|
||||
template<class RayCallback>
|
||||
struct MDLCallback
|
||||
{
|
||||
const T* const* objects;
|
||||
RayCallback& _callback;
|
||||
uint32 objects_size;
|
||||
|
||||
MDLCallback(RayCallback& callback, const T* const* objects_array, uint32 objects_size ) : objects(objects_array), _callback(callback), objects_size(objects_size) { }
|
||||
|
||||
/// Intersect ray
|
||||
bool operator() (const G3D::Ray& ray, uint32 idx, float& maxDist, bool stopAtFirstHit)
|
||||
{
|
||||
if (idx >= objects_size)
|
||||
return false;
|
||||
if (const T* obj = objects[idx])
|
||||
return _callback(ray, *obj, maxDist, stopAtFirstHit);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Intersect point
|
||||
void operator() (const G3D::Vector3& p, uint32 idx)
|
||||
{
|
||||
if (idx >= objects_size)
|
||||
return;
|
||||
if (const T* obj = objects[idx])
|
||||
_callback(p, *obj);
|
||||
}
|
||||
};
|
||||
|
||||
typedef G3D::Array<const T*> ObjArray;
|
||||
|
||||
BIH m_tree;
|
||||
ObjArray m_objects;
|
||||
G3D::Table<const T*, uint32> m_obj2Idx;
|
||||
G3D::Set<const T*> m_objects_to_push;
|
||||
int unbalanced_times;
|
||||
|
||||
public:
|
||||
BIHWrap() : unbalanced_times(0) { }
|
||||
|
||||
void insert(const T& obj)
|
||||
{
|
||||
++unbalanced_times;
|
||||
m_objects_to_push.insert(&obj);
|
||||
}
|
||||
|
||||
void remove(const T& obj)
|
||||
{
|
||||
++unbalanced_times;
|
||||
uint32 Idx = 0;
|
||||
const T * temp;
|
||||
if (m_obj2Idx.getRemove(&obj, temp, Idx))
|
||||
m_objects[Idx] = NULL;
|
||||
else
|
||||
m_objects_to_push.remove(&obj);
|
||||
}
|
||||
|
||||
void balance()
|
||||
{
|
||||
if (unbalanced_times == 0)
|
||||
return;
|
||||
|
||||
unbalanced_times = 0;
|
||||
m_objects.fastClear();
|
||||
m_obj2Idx.getKeys(m_objects);
|
||||
m_objects_to_push.getMembers(m_objects);
|
||||
//assert that m_obj2Idx has all the keys
|
||||
|
||||
m_tree.build(m_objects, BoundsFunc::getBounds2);
|
||||
}
|
||||
|
||||
template<typename RayCallback>
|
||||
void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& maxDist, bool stopAtFirstHit)
|
||||
{
|
||||
balance();
|
||||
MDLCallback<RayCallback> temp_cb(intersectCallback, m_objects.getCArray(), m_objects.size());
|
||||
m_tree.intersectRay(ray, temp_cb, maxDist, stopAtFirstHit);
|
||||
}
|
||||
|
||||
template<typename IsectCallback>
|
||||
void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback)
|
||||
{
|
||||
balance();
|
||||
MDLCallback<IsectCallback> callback(intersectCallback, m_objects.getCArray(), m_objects.size());
|
||||
m_tree.intersectPoint(point, callback);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _BIH_WRAP
|
||||
93
src/server/collision/CMakeLists.txt
Normal file
93
src/server/collision/CMakeLists.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
# Copyright (C)
|
||||
#
|
||||
# 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.
|
||||
|
||||
if( USE_COREPCH )
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE sources_Management Management/*.cpp Management/*.h)
|
||||
file(GLOB_RECURSE sources_Maps Maps/*.cpp Maps/*.h)
|
||||
file(GLOB_RECURSE sources_Models Models/*.cpp Models/*.h)
|
||||
file(GLOB sources_localdir *.cpp *.h)
|
||||
|
||||
if (USE_COREPCH)
|
||||
set(collision_STAT_PCH_HDR PrecompiledHeaders/collisionPCH.h)
|
||||
set(collision_STAT_PCH_SRC PrecompiledHeaders/collisionPCH.cpp)
|
||||
endif ()
|
||||
|
||||
set(collision_STAT_SRCS
|
||||
${collision_STAT_SRCS}
|
||||
${sources_Management}
|
||||
${sources_Maps}
|
||||
${sources_Models}
|
||||
${sources_localdir}
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}/dep/g3dlite/include
|
||||
${CMAKE_SOURCE_DIR}/dep/recastnavigation/Detour
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Configuration
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Debugging
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Database
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Debugging
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Dynamic
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Dynamic/LinkedReference
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Logging
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Threading
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Packets
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/Utilities
|
||||
${CMAKE_SOURCE_DIR}/src/server/shared/DataStores
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Addons
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Conditions
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Entities/Item
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Entities/GameObject
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Entities/Creature
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Entities/Object
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Entities/Object/Updates
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Entities/Unit
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Combat
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Loot
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Miscellaneous
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Grids
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Grids/Cells
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Grids/Notifiers
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Maps
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/DataStores
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Movement/Waypoints
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Movement/Spline
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Movement
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Server
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Server/Protocol
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/World
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Spells
|
||||
${CMAKE_SOURCE_DIR}/src/server/game/Spells/Auras
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Management
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Maps
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Models
|
||||
${ACE_INCLUDE_DIR}
|
||||
${MYSQL_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
add_library(collision STATIC
|
||||
${collision_STAT_SRCS}
|
||||
${collision_STAT_PCH_SRC}
|
||||
)
|
||||
|
||||
target_link_libraries(collision
|
||||
shared
|
||||
)
|
||||
|
||||
# Generate precompiled header
|
||||
if (USE_COREPCH)
|
||||
add_cxx_pch(collision ${collision_STAT_PCH_HDR} ${collision_STAT_PCH_SRC})
|
||||
endif ()
|
||||
240
src/server/collision/DynamicTree.cpp
Normal file
240
src/server/collision/DynamicTree.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "DynamicTree.h"
|
||||
//#include "QuadTree.h"
|
||||
//#include "RegularGrid.h"
|
||||
#include "BoundingIntervalHierarchyWrapper.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "RegularGrid.h"
|
||||
#include "Timer.h"
|
||||
#include "GameObjectModel.h"
|
||||
#include "ModelInstance.h"
|
||||
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Vector3.h>
|
||||
|
||||
using VMAP::ModelInstance;
|
||||
|
||||
namespace {
|
||||
|
||||
int CHECK_TREE_PERIOD = 200;
|
||||
|
||||
} // namespace
|
||||
|
||||
template<> struct HashTrait< GameObjectModel>{
|
||||
static size_t hashCode(const GameObjectModel& g) { return (size_t)(void*)&g; }
|
||||
};
|
||||
|
||||
template<> struct PositionTrait< GameObjectModel> {
|
||||
static void getPosition(const GameObjectModel& g, G3D::Vector3& p) { p = g.getPosition(); }
|
||||
};
|
||||
|
||||
template<> struct BoundsTrait< GameObjectModel> {
|
||||
static void getBounds(const GameObjectModel& g, G3D::AABox& out) { out = g.getBounds();}
|
||||
static void getBounds2(const GameObjectModel* g, G3D::AABox& out) { out = g->getBounds();}
|
||||
};
|
||||
|
||||
/*
|
||||
static bool operator == (const GameObjectModel& mdl, const GameObjectModel& mdl2){
|
||||
return &mdl == &mdl2;
|
||||
}
|
||||
*/
|
||||
|
||||
typedef RegularGrid2D<GameObjectModel, BIHWrap<GameObjectModel> > ParentTree;
|
||||
|
||||
struct DynTreeImpl : public ParentTree/*, public Intersectable*/
|
||||
{
|
||||
typedef GameObjectModel Model;
|
||||
typedef ParentTree base;
|
||||
|
||||
DynTreeImpl() :
|
||||
rebalance_timer(CHECK_TREE_PERIOD),
|
||||
unbalanced_times(0)
|
||||
{
|
||||
}
|
||||
|
||||
void insert(const Model& mdl)
|
||||
{
|
||||
base::insert(mdl);
|
||||
++unbalanced_times;
|
||||
}
|
||||
|
||||
void remove(const Model& mdl)
|
||||
{
|
||||
base::remove(mdl);
|
||||
++unbalanced_times;
|
||||
}
|
||||
|
||||
void balance()
|
||||
{
|
||||
base::balance();
|
||||
unbalanced_times = 0;
|
||||
}
|
||||
|
||||
void update(uint32 difftime)
|
||||
{
|
||||
if (!size())
|
||||
return;
|
||||
|
||||
rebalance_timer.Update(difftime);
|
||||
if (rebalance_timer.Passed())
|
||||
{
|
||||
rebalance_timer.Reset(CHECK_TREE_PERIOD);
|
||||
if (unbalanced_times > 0)
|
||||
balance();
|
||||
}
|
||||
}
|
||||
|
||||
TimeTrackerSmall rebalance_timer;
|
||||
int unbalanced_times;
|
||||
};
|
||||
|
||||
DynamicMapTree::DynamicMapTree() : impl(new DynTreeImpl()) { }
|
||||
|
||||
DynamicMapTree::~DynamicMapTree()
|
||||
{
|
||||
delete impl;
|
||||
}
|
||||
|
||||
void DynamicMapTree::insert(const GameObjectModel& mdl)
|
||||
{
|
||||
impl->insert(mdl);
|
||||
}
|
||||
|
||||
void DynamicMapTree::remove(const GameObjectModel& mdl)
|
||||
{
|
||||
impl->remove(mdl);
|
||||
}
|
||||
|
||||
bool DynamicMapTree::contains(const GameObjectModel& mdl) const
|
||||
{
|
||||
return impl->contains(mdl);
|
||||
}
|
||||
|
||||
void DynamicMapTree::balance()
|
||||
{
|
||||
impl->balance();
|
||||
}
|
||||
|
||||
int DynamicMapTree::size() const
|
||||
{
|
||||
return impl->size();
|
||||
}
|
||||
|
||||
void DynamicMapTree::update(uint32 t_diff)
|
||||
{
|
||||
impl->update(t_diff);
|
||||
}
|
||||
|
||||
struct DynamicTreeIntersectionCallback
|
||||
{
|
||||
bool did_hit;
|
||||
uint32 phase_mask;
|
||||
DynamicTreeIntersectionCallback(uint32 phasemask) : did_hit(false), phase_mask(phasemask) { }
|
||||
bool operator()(const G3D::Ray& r, const GameObjectModel& obj, float& distance, bool stopAtFirstHit)
|
||||
{
|
||||
bool result = obj.intersectRay(r, distance, stopAtFirstHit, phase_mask);
|
||||
if (result)
|
||||
did_hit = result;
|
||||
return result;
|
||||
}
|
||||
bool didHit() const { return did_hit;}
|
||||
};
|
||||
|
||||
bool DynamicMapTree::getIntersectionTime(const uint32 phasemask, const G3D::Ray& ray,
|
||||
const G3D::Vector3& endPos, float& maxDist) const
|
||||
{
|
||||
float distance = maxDist;
|
||||
DynamicTreeIntersectionCallback callback(phasemask);
|
||||
impl->intersectRay(ray, callback, distance, endPos, false);
|
||||
if (callback.didHit())
|
||||
maxDist = distance;
|
||||
return callback.didHit();
|
||||
}
|
||||
|
||||
bool DynamicMapTree::getObjectHitPos(const uint32 phasemask, const G3D::Vector3& startPos,
|
||||
const G3D::Vector3& endPos, G3D::Vector3& resultHit,
|
||||
float modifyDist) const
|
||||
{
|
||||
bool result = false;
|
||||
float maxDist = (endPos - startPos).magnitude();
|
||||
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
|
||||
ASSERT(maxDist < std::numeric_limits<float>::max());
|
||||
// prevent NaN values which can cause BIH intersection to enter infinite loop
|
||||
if (maxDist < 1e-10f)
|
||||
{
|
||||
resultHit = endPos;
|
||||
return false;
|
||||
}
|
||||
G3D::Vector3 dir = (endPos - startPos)/maxDist; // direction with length of 1
|
||||
G3D::Ray ray(startPos, dir);
|
||||
float dist = maxDist;
|
||||
if (getIntersectionTime(phasemask, ray, endPos, dist))
|
||||
{
|
||||
resultHit = startPos + dir * dist;
|
||||
if (modifyDist < 0)
|
||||
{
|
||||
if ((resultHit - startPos).magnitude() > -modifyDist)
|
||||
resultHit = resultHit + dir*modifyDist;
|
||||
else
|
||||
resultHit = startPos;
|
||||
}
|
||||
else
|
||||
resultHit = resultHit + dir*modifyDist;
|
||||
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultHit = endPos;
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DynamicMapTree::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask) const
|
||||
{
|
||||
G3D::Vector3 v1(x1, y1, z1), v2(x2, y2, z2);
|
||||
|
||||
float maxDist = (v2 - v1).magnitude();
|
||||
|
||||
if (!G3D::fuzzyGt(maxDist, 0) )
|
||||
return true;
|
||||
|
||||
G3D::Ray r(v1, (v2-v1) / maxDist);
|
||||
DynamicTreeIntersectionCallback callback(phasemask);
|
||||
impl->intersectRay(r, callback, maxDist, v2, true);
|
||||
|
||||
return !callback.did_hit;
|
||||
}
|
||||
|
||||
float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const
|
||||
{
|
||||
G3D::Vector3 v(x, y, z + 2.0f);
|
||||
G3D::Ray r(v, G3D::Vector3(0, 0, -1));
|
||||
DynamicTreeIntersectionCallback callback(phasemask);
|
||||
impl->intersectZAllignedRay(r, callback, maxSearchDist);
|
||||
|
||||
if (callback.didHit())
|
||||
return v.z - maxSearchDist;
|
||||
else
|
||||
return -G3D::inf();
|
||||
}
|
||||
64
src/server/collision/DynamicTree.h
Normal file
64
src/server/collision/DynamicTree.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _DYNTREE_H
|
||||
#define _DYNTREE_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
namespace G3D
|
||||
{
|
||||
class Ray;
|
||||
class Vector3;
|
||||
}
|
||||
|
||||
class GameObjectModel;
|
||||
struct DynTreeImpl;
|
||||
|
||||
class DynamicMapTree
|
||||
{
|
||||
DynTreeImpl *impl;
|
||||
|
||||
public:
|
||||
|
||||
DynamicMapTree();
|
||||
~DynamicMapTree();
|
||||
|
||||
bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2,
|
||||
float z2, uint32 phasemask) const;
|
||||
|
||||
bool getIntersectionTime(uint32 phasemask, const G3D::Ray& ray,
|
||||
const G3D::Vector3& endPos, float& maxDist) const;
|
||||
|
||||
bool getObjectHitPos(uint32 phasemask, const G3D::Vector3& pPos1,
|
||||
const G3D::Vector3& pPos2, G3D::Vector3& pResultHitPos,
|
||||
float pModifyDist) const;
|
||||
|
||||
float getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const;
|
||||
|
||||
void insert(const GameObjectModel&);
|
||||
void remove(const GameObjectModel&);
|
||||
bool contains(const GameObjectModel&) const;
|
||||
int size() const;
|
||||
|
||||
void balance();
|
||||
void update(uint32 diff);
|
||||
};
|
||||
|
||||
#endif // _DYNTREE_H
|
||||
49
src/server/collision/Management/IMMAPManager.h
Normal file
49
src/server/collision/Management/IMMAPManager.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _IMMAPMANAGER_H
|
||||
#define _IMMAPMANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include "Define.h"
|
||||
|
||||
// Interface for IMMapManger
|
||||
namespace MMAP
|
||||
{
|
||||
enum MMAP_LOAD_RESULT
|
||||
{
|
||||
MMAP_LOAD_RESULT_ERROR,
|
||||
MMAP_LOAD_RESULT_OK,
|
||||
MMAP_LOAD_RESULT_IGNORED,
|
||||
};
|
||||
|
||||
class IMMapManager
|
||||
{
|
||||
private:
|
||||
bool iEnablePathFinding;
|
||||
|
||||
public:
|
||||
IMMapManager() : iEnablePathFinding(true) {}
|
||||
virtual ~IMMapManager(void) {}
|
||||
|
||||
//Enabled/Disabled Pathfinding
|
||||
void setEnablePathFinding(bool value) { iEnablePathFinding = value; }
|
||||
bool isEnablePathFinding() const { return (iEnablePathFinding); }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
100
src/server/collision/Management/IVMapManager.h
Normal file
100
src/server/collision/Management/IVMapManager.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _IVMAPMANAGER_H
|
||||
#define _IVMAPMANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include "Define.h"
|
||||
|
||||
//===========================================================
|
||||
|
||||
/**
|
||||
This is the minimum interface to the VMapMamager.
|
||||
*/
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
|
||||
enum VMAP_LOAD_RESULT
|
||||
{
|
||||
VMAP_LOAD_RESULT_ERROR,
|
||||
VMAP_LOAD_RESULT_OK,
|
||||
VMAP_LOAD_RESULT_IGNORED
|
||||
};
|
||||
|
||||
#define VMAP_INVALID_HEIGHT -100000.0f // for check
|
||||
#define VMAP_INVALID_HEIGHT_VALUE -200000.0f // real assigned value in unknown height case
|
||||
|
||||
//===========================================================
|
||||
class IVMapManager
|
||||
{
|
||||
private:
|
||||
bool iEnableLineOfSightCalc;
|
||||
bool iEnableHeightCalc;
|
||||
|
||||
public:
|
||||
IVMapManager() : iEnableLineOfSightCalc(true), iEnableHeightCalc(true) { }
|
||||
|
||||
virtual ~IVMapManager(void) { }
|
||||
|
||||
virtual int loadMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
|
||||
|
||||
virtual bool existsMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
|
||||
|
||||
virtual void unloadMap(unsigned int pMapId, int x, int y) = 0;
|
||||
virtual void unloadMap(unsigned int pMapId) = 0;
|
||||
|
||||
virtual bool isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2) = 0;
|
||||
virtual float getHeight(unsigned int pMapId, float x, float y, float z, float maxSearchDist) = 0;
|
||||
/**
|
||||
test if we hit an object. return true if we hit one. rx, ry, rz will hold the hit position or the dest position, if no intersection was found
|
||||
return a position, that is pReduceDist closer to the origin
|
||||
*/
|
||||
virtual bool getObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float pModifyDist) = 0;
|
||||
/**
|
||||
send debug commands
|
||||
*/
|
||||
virtual bool processCommand(char *pCommand)= 0;
|
||||
|
||||
/**
|
||||
Enable/disable LOS calculation
|
||||
It is enabled by default. If it is enabled in mid game the maps have to loaded manualy
|
||||
*/
|
||||
void setEnableLineOfSightCalc(bool pVal) { iEnableLineOfSightCalc = pVal; }
|
||||
/**
|
||||
Enable/disable model height calculation
|
||||
It is enabled by default. If it is enabled in mid game the maps have to loaded manualy
|
||||
*/
|
||||
void setEnableHeightCalc(bool pVal) { iEnableHeightCalc = pVal; }
|
||||
|
||||
bool isLineOfSightCalcEnabled() const { return(iEnableLineOfSightCalc); }
|
||||
bool isHeightCalcEnabled() const { return(iEnableHeightCalc); }
|
||||
bool isMapLoadingEnabled() const { return(iEnableLineOfSightCalc || iEnableHeightCalc ); }
|
||||
|
||||
virtual std::string getDirFileName(unsigned int pMapId, int x, int y) const =0;
|
||||
/**
|
||||
Query world model area info.
|
||||
\param z gets adjusted to the ground height for which this are info is valid
|
||||
*/
|
||||
virtual bool getAreaInfo(unsigned int pMapId, float x, float y, float &z, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const=0;
|
||||
virtual bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 ReqLiquidType, float &level, float &floor, uint32 &type) const=0;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
61
src/server/collision/Management/MMapFactory.cpp
Normal file
61
src/server/collision/Management/MMapFactory.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "MMapFactory.h"
|
||||
#include "World.h"
|
||||
#include <set>
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
// ######################## MMapFactory ########################
|
||||
// our global singleton copy
|
||||
MMapManager *g_MMapManager = NULL;
|
||||
bool MMapFactory::forbiddenMaps[1000] = {0};
|
||||
|
||||
MMapManager* MMapFactory::createOrGetMMapManager()
|
||||
{
|
||||
if (g_MMapManager == NULL)
|
||||
g_MMapManager = new MMapManager();
|
||||
|
||||
return g_MMapManager;
|
||||
}
|
||||
|
||||
bool MMapFactory::IsPathfindingEnabled(const Map* map, bool force)
|
||||
{
|
||||
if (!map) return false;
|
||||
return !forbiddenMaps[map->GetId()] && (sWorld->getBoolConfig(CONFIG_ENABLE_MMAPS) ? true : map->IsBattlegroundOrArena());
|
||||
}
|
||||
|
||||
void MMapFactory::InitializeDisabledMaps()
|
||||
{
|
||||
memset(&forbiddenMaps, 0, sizeof(forbiddenMaps));
|
||||
int32 f[] = {616 /*EoE*/, 649 /*ToC25*/, 650 /*ToC5*/, -1};
|
||||
uint32 i = 0;
|
||||
while (f[i] >= 0)
|
||||
forbiddenMaps[f[i++]] = true;
|
||||
}
|
||||
|
||||
void MMapFactory::clear()
|
||||
{
|
||||
if (g_MMapManager)
|
||||
{
|
||||
delete g_MMapManager;
|
||||
g_MMapManager = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/server/collision/Management/MMapFactory.h
Normal file
53
src/server/collision/Management/MMapFactory.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _MMAP_FACTORY_H
|
||||
#define _MMAP_FACTORY_H
|
||||
|
||||
#include "MMapManager.h"
|
||||
#include "UnorderedMap.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "Map.h"
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
enum MMAP_LOAD_RESULT
|
||||
{
|
||||
MMAP_LOAD_RESULT_ERROR,
|
||||
MMAP_LOAD_RESULT_OK,
|
||||
MMAP_LOAD_RESULT_IGNORED,
|
||||
};
|
||||
|
||||
// static class
|
||||
// holds all mmap global data
|
||||
// access point to MMapManager singleton
|
||||
class MMapFactory
|
||||
{
|
||||
public:
|
||||
static MMapManager* createOrGetMMapManager();
|
||||
static void clear();
|
||||
static bool IsPathfindingEnabled(const Map* map, bool force = false);
|
||||
static void InitializeDisabledMaps();
|
||||
static bool forbiddenMaps[1000];
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
356
src/server/collision/Management/MMapManager.cpp
Normal file
356
src/server/collision/Management/MMapManager.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "MapManager.h"
|
||||
#include "MMapManager.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
// ######################## MMapManager ########################
|
||||
MMapManager::~MMapManager()
|
||||
{
|
||||
for (MMapDataSet::iterator i = loadedMMaps.begin(); i != loadedMMaps.end(); ++i)
|
||||
delete i->second;
|
||||
|
||||
// by now we should not have maps loaded
|
||||
// if we had, tiles in MMapData->mmapLoadedTiles, their actual data is lost!
|
||||
}
|
||||
|
||||
bool MMapManager::loadMapData(uint32 mapId)
|
||||
{
|
||||
// we already have this map loaded?
|
||||
if (loadedMMaps.find(mapId) != loadedMMaps.end())
|
||||
return true;
|
||||
|
||||
// load and init dtNavMesh - read parameters from file
|
||||
uint32 pathLen = sWorld->GetDataPath().length() + strlen("mmaps/%03i.mmap")+1;
|
||||
char *fileName = new char[pathLen];
|
||||
snprintf(fileName, pathLen, (sWorld->GetDataPath()+"mmaps/%03i.mmap").c_str(), mapId);
|
||||
|
||||
FILE* file = fopen(fileName, "rb");
|
||||
if (!file)
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "MMAP:loadMapData: Error: Could not open mmap file '%s'", fileName);
|
||||
delete [] fileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
dtNavMeshParams params;
|
||||
int count = fread(¶ms, sizeof(dtNavMeshParams), 1, file);
|
||||
fclose(file);
|
||||
if (count != 1)
|
||||
{
|
||||
;//TC_LOG_DEBUG(LOG_FILTER_MAPS, "MMAP:loadMapData: Error: Could not read params from file '%s'", fileName);
|
||||
delete [] fileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
dtNavMesh* mesh = dtAllocNavMesh();
|
||||
ASSERT(mesh);
|
||||
if (DT_SUCCESS != mesh->init(¶ms))
|
||||
{
|
||||
dtFreeNavMesh(mesh);
|
||||
sLog->outError("MMAP:loadMapData: Failed to initialize dtNavMesh for mmap %03u from file %s", mapId, fileName);
|
||||
delete [] fileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
delete [] fileName;
|
||||
|
||||
;//sLog->outDetail("MMAP:loadMapData: Loaded %03i.mmap", mapId);
|
||||
|
||||
// store inside our map list
|
||||
MMapData* mmap_data = new MMapData(mesh);
|
||||
mmap_data->mmapLoadedTiles.clear();
|
||||
|
||||
loadedMMaps.insert(std::pair<uint32, MMapData*>(mapId, mmap_data));
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 MMapManager::packTileID(int32 x, int32 y)
|
||||
{
|
||||
return uint32(x << 16 | y);
|
||||
}
|
||||
|
||||
ACE_RW_Thread_Mutex& MMapManager::GetMMapLock(uint32 mapId)
|
||||
{
|
||||
Map* map = sMapMgr->FindBaseMap(mapId);
|
||||
if (!map)
|
||||
{
|
||||
sLog->outMisc("ZOMG! MoveMaps: BaseMap not found!");
|
||||
return this->MMapLock;
|
||||
}
|
||||
return map->GetMMapLock();
|
||||
}
|
||||
|
||||
bool MMapManager::loadMap(uint32 mapId, int32 x, int32 y)
|
||||
{
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, MMapManagerLock);
|
||||
|
||||
// make sure the mmap is loaded and ready to load tiles
|
||||
if(!loadMapData(mapId))
|
||||
return false;
|
||||
|
||||
// get this mmap data
|
||||
MMapData* mmap = loadedMMaps[mapId];
|
||||
ASSERT(mmap->navMesh);
|
||||
|
||||
// check if we already have this tile loaded
|
||||
uint32 packedGridPos = packTileID(x, y);
|
||||
if (mmap->mmapLoadedTiles.find(packedGridPos) != mmap->mmapLoadedTiles.end())
|
||||
{
|
||||
sLog->outError("MMAP:loadMap: Asked to load already loaded navmesh tile. %03u%02i%02i.mmtile", mapId, x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
// load this tile :: mmaps/MMMXXYY.mmtile
|
||||
uint32 pathLen = sWorld->GetDataPath().length() + strlen("mmaps/%03i%02i%02i.mmtile")+1;
|
||||
char *fileName = new char[pathLen];
|
||||
snprintf(fileName, pathLen, (sWorld->GetDataPath()+"mmaps/%03i%02i%02i.mmtile").c_str(), mapId, x, y);
|
||||
|
||||
FILE *file = fopen(fileName, "rb");
|
||||
if (!file)
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "MMAP:loadMap: Could not open mmtile file '%s'", fileName);
|
||||
delete [] fileName;
|
||||
return false;
|
||||
}
|
||||
delete [] fileName;
|
||||
|
||||
// read header
|
||||
MmapTileHeader fileHeader;
|
||||
if (fread(&fileHeader, sizeof(MmapTileHeader), 1, file) != 1 || fileHeader.mmapMagic != MMAP_MAGIC)
|
||||
{
|
||||
sLog->outError("MMAP:loadMap: Bad header in mmap %03u%02i%02i.mmtile", mapId, x, y);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fileHeader.mmapVersion != MMAP_VERSION)
|
||||
{
|
||||
sLog->outError("MMAP:loadMap: %03u%02i%02i.mmtile was built with generator v%i, expected v%i",
|
||||
mapId, x, y, fileHeader.mmapVersion, MMAP_VERSION);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char* data = (unsigned char*)dtAlloc(fileHeader.size, DT_ALLOC_PERM);
|
||||
ASSERT(data);
|
||||
|
||||
size_t result = fread(data, fileHeader.size, 1, file);
|
||||
if(!result)
|
||||
{
|
||||
sLog->outError("MMAP:loadMap: Bad header or data in mmap %03u%02i%02i.mmtile", mapId, x, y);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
dtMeshHeader* header = (dtMeshHeader*)data;
|
||||
dtTileRef tileRef = 0;
|
||||
|
||||
dtStatus stat;
|
||||
{
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, GetMMapLock(mapId));
|
||||
stat = mmap->navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef);
|
||||
}
|
||||
|
||||
// memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
|
||||
if (stat == DT_SUCCESS)
|
||||
{
|
||||
mmap->mmapLoadedTiles.insert(std::pair<uint32, dtTileRef>(packedGridPos, tileRef));
|
||||
++loadedTiles;
|
||||
;//sLog->outDetail("MMAP:loadMap: Loaded mmtile %03i[%02i,%02i] into %03i[%02i,%02i]", mapId, x, y, mapId, header->x, header->y);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sLog->outError("MMAP:loadMap: Could not load %03u%02i%02i.mmtile into navmesh", mapId, x, y);
|
||||
dtFree(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MMapManager::unloadMap(uint32 mapId, int32 x, int32 y)
|
||||
{
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, MMapManagerLock);
|
||||
|
||||
// check if we have this map loaded
|
||||
if (loadedMMaps.find(mapId) == loadedMMaps.end())
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "MMAP:unloadMap: Asked to unload not loaded navmesh map. %03u%02i%02i.mmtile", mapId, x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
MMapData* mmap = loadedMMaps[mapId];
|
||||
|
||||
// check if we have this tile loaded
|
||||
uint32 packedGridPos = packTileID(x, y);
|
||||
if (mmap->mmapLoadedTiles.find(packedGridPos) == mmap->mmapLoadedTiles.end())
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "MMAP:unloadMap: Asked to unload not loaded navmesh tile. %03u%02i%02i.mmtile", mapId, x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
dtTileRef tileRef = mmap->mmapLoadedTiles[packedGridPos];
|
||||
|
||||
dtStatus status;
|
||||
{
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, GetMMapLock(mapId));
|
||||
status = mmap->navMesh->removeTile(tileRef, NULL, NULL);
|
||||
}
|
||||
|
||||
// unload, and mark as non loaded
|
||||
if (status != DT_SUCCESS)
|
||||
{
|
||||
// this is technically a memory leak
|
||||
// if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used
|
||||
// we cannot recover from this error - assert out
|
||||
sLog->outError("MMAP:unloadMap: Could not unload %03u%02i%02i.mmtile from navmesh", mapId, x, y);
|
||||
ASSERT(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
mmap->mmapLoadedTiles.erase(packedGridPos);
|
||||
--loadedTiles;
|
||||
;//sLog->outDetail("MMAP:unloadMap: Unloaded mmtile %03i[%02i,%02i] from %03i", mapId, x, y, mapId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MMapManager::unloadMap(uint32 mapId)
|
||||
{
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, MMapManagerLock);
|
||||
|
||||
if (loadedMMaps.find(mapId) == loadedMMaps.end())
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "MMAP:unloadMap: Asked to unload not loaded navmesh map %03u", mapId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// unload all tiles from given map
|
||||
MMapData* mmap = loadedMMaps[mapId];
|
||||
for (MMapTileSet::iterator i = mmap->mmapLoadedTiles.begin(); i != mmap->mmapLoadedTiles.end(); ++i)
|
||||
{
|
||||
uint32 x = (i->first >> 16);
|
||||
uint32 y = (i->first & 0x0000FFFF);
|
||||
|
||||
dtStatus status;
|
||||
{
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, GetMMapLock(mapId));
|
||||
status = mmap->navMesh->removeTile(i->second, NULL, NULL);
|
||||
}
|
||||
|
||||
if (status != DT_SUCCESS)
|
||||
sLog->outError("MMAP:unloadMap: Could not unload %03u%02i%02i.mmtile from navmesh", mapId, x, y);
|
||||
else
|
||||
{
|
||||
--loadedTiles;
|
||||
;//sLog->outDetail("MMAP:unloadMap: Unloaded mmtile %03i[%02i,%02i] from %03i", mapId, x, y, mapId);
|
||||
}
|
||||
}
|
||||
|
||||
delete mmap;
|
||||
loadedMMaps.erase(mapId);
|
||||
;//sLog->outDetail("MMAP:unloadMap: Unloaded %03i.mmap", mapId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMapManager::unloadMapInstance(uint32 mapId, uint32 instanceId)
|
||||
{
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, MMapManagerLock);
|
||||
|
||||
// check if we have this map loaded
|
||||
if (loadedMMaps.find(mapId) == loadedMMaps.end())
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map %03u", mapId);
|
||||
return false;
|
||||
}
|
||||
|
||||
MMapData* mmap = loadedMMaps[mapId];
|
||||
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId %03u instanceId %u", mapId, instanceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
dtNavMeshQuery* query = mmap->navMeshQueries[instanceId];
|
||||
|
||||
dtFreeNavMeshQuery(query);
|
||||
mmap->navMeshQueries.erase(instanceId);
|
||||
;//sLog->outDetail("MMAP:unloadMapInstance: Unloaded mapId %03u instanceId %u", mapId, instanceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
dtNavMesh const* MMapManager::GetNavMesh(uint32 mapId)
|
||||
{
|
||||
// pussywizard: moved to calling function
|
||||
//TRINITY_READ_GUARD(ACE_RW_Thread_Mutex, MMapManagerLock);
|
||||
|
||||
if (loadedMMaps.find(mapId) == loadedMMaps.end())
|
||||
return NULL;
|
||||
|
||||
return loadedMMaps[mapId]->navMesh;
|
||||
}
|
||||
|
||||
dtNavMeshQuery const* MMapManager::GetNavMeshQuery(uint32 mapId, uint32 instanceId)
|
||||
{
|
||||
// pussywizard: moved to calling function
|
||||
//TRINITY_READ_GUARD(ACE_RW_Thread_Mutex, MMapManagerLock);
|
||||
|
||||
if (loadedMMaps.find(mapId) == loadedMMaps.end())
|
||||
return NULL;
|
||||
|
||||
MMapData* mmap = loadedMMaps[mapId];
|
||||
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
|
||||
{
|
||||
// pussywizard: different instances of the same map shouldn't access this simultaneously
|
||||
TRINITY_WRITE_GUARD(ACE_RW_Thread_Mutex, GetMMapLock(mapId));
|
||||
// check again after acquiring mutex
|
||||
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
|
||||
{
|
||||
// allocate mesh query
|
||||
dtNavMeshQuery* query = dtAllocNavMeshQuery();
|
||||
ASSERT(query);
|
||||
if (DT_SUCCESS != query->init(mmap->navMesh, 1024))
|
||||
{
|
||||
dtFreeNavMeshQuery(query);
|
||||
sLog->outError("MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId %03u instanceId %u", mapId, instanceId);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
;//sLog->outDetail("MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId %03u instanceId %u", mapId, instanceId);
|
||||
mmap->navMeshQueries.insert(std::pair<uint32, dtNavMeshQuery*>(instanceId, query));
|
||||
}
|
||||
}
|
||||
|
||||
return mmap->navMeshQueries[instanceId];
|
||||
}
|
||||
}
|
||||
103
src/server/collision/Management/MMapManager.h
Normal file
103
src/server/collision/Management/MMapManager.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _MMAP_MANAGER_H
|
||||
#define _MMAP_MANAGER_H
|
||||
|
||||
#include "UnorderedMap.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "World.h"
|
||||
|
||||
// memory management
|
||||
inline void* dtCustomAlloc(int size, dtAllocHint /*hint*/)
|
||||
{
|
||||
return (void*)new unsigned char[size];
|
||||
}
|
||||
|
||||
inline void dtCustomFree(void* ptr)
|
||||
{
|
||||
delete [] (unsigned char*)ptr;
|
||||
}
|
||||
|
||||
// move map related classes
|
||||
namespace MMAP
|
||||
{
|
||||
typedef UNORDERED_MAP<uint32, dtTileRef> MMapTileSet;
|
||||
typedef UNORDERED_MAP<uint32, dtNavMeshQuery*> NavMeshQuerySet;
|
||||
|
||||
// dummy struct to hold map's mmap data
|
||||
struct MMapData
|
||||
{
|
||||
MMapData(dtNavMesh* mesh) : navMesh(mesh) {}
|
||||
~MMapData()
|
||||
{
|
||||
for (NavMeshQuerySet::iterator i = navMeshQueries.begin(); i != navMeshQueries.end(); ++i)
|
||||
dtFreeNavMeshQuery(i->second);
|
||||
|
||||
if (navMesh)
|
||||
dtFreeNavMesh(navMesh);
|
||||
}
|
||||
|
||||
dtNavMesh* navMesh;
|
||||
|
||||
// we have to use single dtNavMeshQuery for every instance, since those are not thread safe
|
||||
NavMeshQuerySet navMeshQueries; // instanceId to query
|
||||
MMapTileSet mmapLoadedTiles; // maps [map grid coords] to [dtTile]
|
||||
};
|
||||
|
||||
|
||||
typedef UNORDERED_MAP<uint32, MMapData*> MMapDataSet;
|
||||
|
||||
// singleton class
|
||||
// holds all all access to mmap loading unloading and meshes
|
||||
class MMapManager
|
||||
{
|
||||
public:
|
||||
MMapManager() : loadedTiles(0) {}
|
||||
~MMapManager();
|
||||
|
||||
bool loadMap(uint32 mapId, int32 x, int32 y);
|
||||
bool unloadMap(uint32 mapId, int32 x, int32 y);
|
||||
bool unloadMap(uint32 mapId);
|
||||
bool unloadMapInstance(uint32 mapId, uint32 instanceId);
|
||||
|
||||
// the returned [dtNavMeshQuery const*] is NOT threadsafe
|
||||
dtNavMeshQuery const* GetNavMeshQuery(uint32 mapId, uint32 instanceId);
|
||||
dtNavMesh const* GetNavMesh(uint32 mapId);
|
||||
|
||||
uint32 getLoadedTilesCount() const { return loadedTiles; }
|
||||
uint32 getLoadedMapsCount() const { return loadedMMaps.size(); }
|
||||
|
||||
ACE_RW_Thread_Mutex& GetMMapLock(uint32 mapId);
|
||||
ACE_RW_Thread_Mutex& GetMMapGeneralLock() { return MMapLock; } // pussywizard: in case a per-map mutex can't be found, should never happen
|
||||
ACE_RW_Thread_Mutex& GetManagerLock() { return MMapManagerLock; }
|
||||
private:
|
||||
bool loadMapData(uint32 mapId);
|
||||
uint32 packTileID(int32 x, int32 y);
|
||||
|
||||
MMapDataSet loadedMMaps;
|
||||
uint32 loadedTiles;
|
||||
|
||||
ACE_RW_Thread_Mutex MMapManagerLock;
|
||||
ACE_RW_Thread_Mutex MMapLock; // pussywizard: in case a per-map mutex can't be found, should never happen
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
42
src/server/collision/Management/VMapFactory.cpp
Normal file
42
src/server/collision/Management/VMapFactory.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "VMapFactory.h"
|
||||
#include "VMapManager2.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
IVMapManager* gVMapManager = NULL;
|
||||
|
||||
//===============================================
|
||||
// just return the instance
|
||||
IVMapManager* VMapFactory::createOrGetVMapManager()
|
||||
{
|
||||
if (gVMapManager == 0)
|
||||
gVMapManager= new VMapManager2(); // should be taken from config ... Please change if you like :-)
|
||||
return gVMapManager;
|
||||
}
|
||||
|
||||
//===============================================
|
||||
// delete all internal data structures
|
||||
void VMapFactory::clear()
|
||||
{
|
||||
delete gVMapManager;
|
||||
gVMapManager = NULL;
|
||||
}
|
||||
}
|
||||
40
src/server/collision/Management/VMapFactory.h
Normal file
40
src/server/collision/Management/VMapFactory.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _VMAPFACTORY_H
|
||||
#define _VMAPFACTORY_H
|
||||
|
||||
#include "IVMapManager.h"
|
||||
|
||||
/**
|
||||
This is the access point to the VMapManager.
|
||||
*/
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
//===========================================================
|
||||
|
||||
class VMapFactory
|
||||
{
|
||||
public:
|
||||
static IVMapManager* createOrGetVMapManager();
|
||||
static void clear();
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
299
src/server/collision/Management/VMapManager2.cpp
Normal file
299
src/server/collision/Management/VMapManager2.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <iostream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include "VMapManager2.h"
|
||||
#include "MapTree.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "WorldModel.h"
|
||||
#include <G3D/Vector3.h>
|
||||
#include <ace/Null_Mutex.h>
|
||||
#include <ace/Singleton.h>
|
||||
#include "DisableMgr.h"
|
||||
#include "DBCStores.h"
|
||||
#include "Log.h"
|
||||
#include "VMapDefinitions.h"
|
||||
|
||||
using G3D::Vector3;
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
VMapManager2::VMapManager2()
|
||||
{
|
||||
}
|
||||
|
||||
VMapManager2::~VMapManager2(void)
|
||||
{
|
||||
for (InstanceTreeMap::iterator i = iInstanceMapTrees.begin(); i != iInstanceMapTrees.end(); ++i)
|
||||
{
|
||||
delete i->second;
|
||||
}
|
||||
for (ModelFileMap::iterator i = iLoadedModelFiles.begin(); i != iLoadedModelFiles.end(); ++i)
|
||||
{
|
||||
delete i->second.getModel();
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 VMapManager2::convertPositionToInternalRep(float x, float y, float z) const
|
||||
{
|
||||
Vector3 pos;
|
||||
const float mid = 0.5f * 64.0f * 533.33333333f;
|
||||
pos.x = mid - x;
|
||||
pos.y = mid - y;
|
||||
pos.z = z;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
// move to MapTree too?
|
||||
std::string VMapManager2::getMapFileName(unsigned int mapId)
|
||||
{
|
||||
std::stringstream fname;
|
||||
fname.width(3);
|
||||
fname << std::setfill('0') << mapId << std::string(MAP_FILENAME_EXTENSION2);
|
||||
|
||||
return fname.str();
|
||||
}
|
||||
|
||||
int VMapManager2::loadMap(const char* basePath, unsigned int mapId, int x, int y)
|
||||
{
|
||||
int result = VMAP_LOAD_RESULT_IGNORED;
|
||||
if (isMapLoadingEnabled())
|
||||
{
|
||||
if (_loadMap(mapId, basePath, x, y))
|
||||
result = VMAP_LOAD_RESULT_OK;
|
||||
else
|
||||
result = VMAP_LOAD_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// load one tile (internal use only)
|
||||
bool VMapManager2::_loadMap(unsigned int mapId, const std::string& basePath, uint32 tileX, uint32 tileY)
|
||||
{
|
||||
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree == iInstanceMapTrees.end())
|
||||
{
|
||||
std::string mapFileName = getMapFileName(mapId);
|
||||
StaticMapTree* newTree = new StaticMapTree(mapId, basePath);
|
||||
if (!newTree->InitMap(mapFileName, this))
|
||||
{
|
||||
delete newTree;
|
||||
return false;
|
||||
}
|
||||
instanceTree = iInstanceMapTrees.insert(InstanceTreeMap::value_type(mapId, newTree)).first;
|
||||
}
|
||||
|
||||
return instanceTree->second->LoadMapTile(tileX, tileY, this);
|
||||
}
|
||||
|
||||
void VMapManager2::unloadMap(unsigned int mapId)
|
||||
{
|
||||
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
instanceTree->second->UnloadMap(this);
|
||||
if (instanceTree->second->numLoadedTiles() == 0)
|
||||
{
|
||||
delete instanceTree->second;
|
||||
iInstanceMapTrees.erase(mapId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VMapManager2::unloadMap(unsigned int mapId, int x, int y)
|
||||
{
|
||||
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
instanceTree->second->UnloadMapTile(x, y, this);
|
||||
if (instanceTree->second->numLoadedTiles() == 0)
|
||||
{
|
||||
delete instanceTree->second;
|
||||
iInstanceMapTrees.erase(mapId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VMapManager2::isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2)
|
||||
{
|
||||
//if (!isLineOfSightCalcEnabled() || DisableMgr::IsDisabledFor(DISABLE_TYPE_VMAP, mapId, NULL, VMAP_DISABLE_LOS)) // pussywizard: optimization
|
||||
// return true;
|
||||
|
||||
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
|
||||
Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
|
||||
if (pos1 != pos2)
|
||||
{
|
||||
return instanceTree->second->isInLineOfSight(pos1, pos2);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
get the hit position and return true if we hit something
|
||||
otherwise the result pos will be the dest pos
|
||||
*/
|
||||
bool VMapManager2::getObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float modifyDist)
|
||||
{
|
||||
//if (isLineOfSightCalcEnabled() && !DisableMgr::IsDisabledFor(DISABLE_TYPE_VMAP, mapId, NULL, VMAP_DISABLE_LOS)) // pussywizard: optimization
|
||||
{
|
||||
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
|
||||
Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
|
||||
Vector3 resultPos;
|
||||
bool result = instanceTree->second->getObjectHitPos(pos1, pos2, resultPos, modifyDist);
|
||||
resultPos = convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z);
|
||||
rx = resultPos.x;
|
||||
ry = resultPos.y;
|
||||
rz = resultPos.z;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
rx = x2;
|
||||
ry = y2;
|
||||
rz = z2;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
get height or INVALID_HEIGHT if no height available
|
||||
*/
|
||||
|
||||
float VMapManager2::getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist)
|
||||
{
|
||||
//if (isHeightCalcEnabled() && !DisableMgr::IsDisabledFor(DISABLE_TYPE_VMAP, mapId, NULL, VMAP_DISABLE_HEIGHT)) // pussywizard: optimization
|
||||
{
|
||||
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
Vector3 pos = convertPositionToInternalRep(x, y, z);
|
||||
float height = instanceTree->second->getHeight(pos, maxSearchDist);
|
||||
if (!(height < G3D::inf()))
|
||||
return height = VMAP_INVALID_HEIGHT_VALUE; // No height
|
||||
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
||||
return VMAP_INVALID_HEIGHT_VALUE;
|
||||
}
|
||||
|
||||
bool VMapManager2::getAreaInfo(unsigned int mapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
|
||||
{
|
||||
//if (!DisableMgr::IsDisabledFor(DISABLE_TYPE_VMAP, mapId, NULL, VMAP_DISABLE_AREAFLAG)) // pussywizard: optimization
|
||||
{
|
||||
InstanceTreeMap::const_iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
Vector3 pos = convertPositionToInternalRep(x, y, z);
|
||||
bool result = instanceTree->second->getAreaInfo(pos, flags, adtId, rootId, groupId);
|
||||
// z is not touched by convertPositionToInternalRep(), so just copy
|
||||
z = pos.z;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VMapManager2::GetLiquidLevel(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type) const
|
||||
{
|
||||
//if (!DisableMgr::IsDisabledFor(DISABLE_TYPE_VMAP, mapId, NULL, VMAP_DISABLE_LIQUIDSTATUS)) // pussywizard: optimization
|
||||
{
|
||||
InstanceTreeMap::const_iterator instanceTree = iInstanceMapTrees.find(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
LocationInfo info;
|
||||
Vector3 pos = convertPositionToInternalRep(x, y, z);
|
||||
if (instanceTree->second->GetLocationInfo(pos, info))
|
||||
{
|
||||
floor = info.ground_Z;
|
||||
ASSERT(floor < std::numeric_limits<float>::max());
|
||||
type = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc
|
||||
if (reqLiquidType && !(GetLiquidFlags(type) & reqLiquidType))
|
||||
return false;
|
||||
if (info.hitInstance->GetLiquidLevel(pos, info, level))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldModel* VMapManager2::acquireModelInstance(const std::string& basepath, const std::string& filename)
|
||||
{
|
||||
//! Critical section, thread safe access to iLoadedModelFiles
|
||||
TRINITY_GUARD(ACE_Thread_Mutex, LoadedModelFilesLock);
|
||||
|
||||
ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
|
||||
if (model == iLoadedModelFiles.end())
|
||||
{
|
||||
WorldModel* worldmodel = new WorldModel();
|
||||
if (!worldmodel->readFile(basepath + filename + ".vmo"))
|
||||
{
|
||||
sLog->outError("VMapManager2: could not load '%s%s.vmo'", basepath.c_str(), filename.c_str());
|
||||
delete worldmodel;
|
||||
return NULL;
|
||||
}
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "VMapManager2: loading file '%s%s'", basepath.c_str(), filename.c_str());
|
||||
model = iLoadedModelFiles.insert(std::pair<std::string, ManagedModel>(filename, ManagedModel())).first;
|
||||
model->second.setModel(worldmodel);
|
||||
}
|
||||
//model->second.incRefCount();
|
||||
return model->second.getModel();
|
||||
}
|
||||
|
||||
void VMapManager2::releaseModelInstance(const std::string &filename)
|
||||
{
|
||||
/*//! Critical section, thread safe access to iLoadedModelFiles
|
||||
TRINITY_GUARD(ACE_Thread_Mutex, LoadedModelFilesLock);
|
||||
|
||||
ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
|
||||
if (model == iLoadedModelFiles.end())
|
||||
{
|
||||
sLog->outError("VMapManager2: trying to unload non-loaded file '%s'", filename.c_str());
|
||||
return;
|
||||
}
|
||||
if (model->second.decRefCount() == 0)
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "VMapManager2: unloading file '%s'", filename.c_str());
|
||||
delete model->second.getModel();
|
||||
iLoadedModelFiles.erase(model);
|
||||
}*/
|
||||
}
|
||||
|
||||
bool VMapManager2::existsMap(const char* basePath, unsigned int mapId, int x, int y)
|
||||
{
|
||||
return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y);
|
||||
}
|
||||
|
||||
} // namespace VMAP
|
||||
120
src/server/collision/Management/VMapManager2.h
Normal file
120
src/server/collision/Management/VMapManager2.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _VMAPMANAGER2_H
|
||||
#define _VMAPMANAGER2_H
|
||||
|
||||
#include "IVMapManager.h"
|
||||
#include "Dynamic/UnorderedMap.h"
|
||||
#include "Define.h"
|
||||
#include <ace/Thread_Mutex.h>
|
||||
|
||||
//===========================================================
|
||||
|
||||
#define MAP_FILENAME_EXTENSION2 ".vmtree"
|
||||
|
||||
#define FILENAMEBUFFER_SIZE 500
|
||||
|
||||
/**
|
||||
This is the main Class to manage loading and unloading of maps, line of sight, height calculation and so on.
|
||||
For each map or map tile to load it reads a directory file that contains the ModelContainer files used by this map or map tile.
|
||||
Each global map or instance has its own dynamic BSP-Tree.
|
||||
The loaded ModelContainers are included in one of these BSP-Trees.
|
||||
Additionally a table to match map ids and map names is used.
|
||||
*/
|
||||
|
||||
//===========================================================
|
||||
|
||||
namespace G3D
|
||||
{
|
||||
class Vector3;
|
||||
}
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class StaticMapTree;
|
||||
class WorldModel;
|
||||
|
||||
class ManagedModel
|
||||
{
|
||||
public:
|
||||
ManagedModel() : iModel(0), iRefCount(0) { }
|
||||
void setModel(WorldModel* model) { iModel = model; }
|
||||
WorldModel* getModel() { return iModel; }
|
||||
void incRefCount() { ++iRefCount; }
|
||||
int decRefCount() { return --iRefCount; }
|
||||
protected:
|
||||
WorldModel* iModel;
|
||||
int iRefCount;
|
||||
};
|
||||
|
||||
typedef UNORDERED_MAP<uint32, StaticMapTree*> InstanceTreeMap;
|
||||
typedef UNORDERED_MAP<std::string, ManagedModel> ModelFileMap;
|
||||
|
||||
class VMapManager2 : public IVMapManager
|
||||
{
|
||||
protected:
|
||||
// Tree to check collision
|
||||
ModelFileMap iLoadedModelFiles;
|
||||
InstanceTreeMap iInstanceMapTrees;
|
||||
// Mutex for iLoadedModelFiles
|
||||
ACE_Thread_Mutex LoadedModelFilesLock;
|
||||
|
||||
bool _loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY);
|
||||
/* void _unloadMap(uint32 pMapId, uint32 x, uint32 y); */
|
||||
|
||||
public:
|
||||
// public for debug
|
||||
G3D::Vector3 convertPositionToInternalRep(float x, float y, float z) const;
|
||||
static std::string getMapFileName(unsigned int mapId);
|
||||
|
||||
VMapManager2();
|
||||
~VMapManager2(void);
|
||||
|
||||
int loadMap(const char* pBasePath, unsigned int mapId, int x, int y);
|
||||
|
||||
void unloadMap(unsigned int mapId, int x, int y);
|
||||
void unloadMap(unsigned int mapId);
|
||||
|
||||
bool isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2) ;
|
||||
/**
|
||||
fill the hit pos and return true, if an object was hit
|
||||
*/
|
||||
bool getObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist);
|
||||
float getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist);
|
||||
|
||||
bool processCommand(char* /*command*/) { return false; } // for debug and extensions
|
||||
|
||||
bool getAreaInfo(unsigned int pMapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const;
|
||||
bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type) const;
|
||||
|
||||
WorldModel* acquireModelInstance(const std::string& basepath, const std::string& filename);
|
||||
void releaseModelInstance(const std::string& filename);
|
||||
|
||||
// what's the use of this? o.O
|
||||
virtual std::string getDirFileName(unsigned int mapId, int /*x*/, int /*y*/) const
|
||||
{
|
||||
return getMapFileName(mapId);
|
||||
}
|
||||
virtual bool existsMap(const char* basePath, unsigned int mapId, int x, int y);
|
||||
public:
|
||||
void getInstanceMapTree(InstanceTreeMap &instanceMapTree);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
477
src/server/collision/Maps/MapTree.cpp
Normal file
477
src/server/collision/Maps/MapTree.cpp
Normal file
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "MapTree.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "VMapManager2.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include "Log.h"
|
||||
#include "Errors.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
|
||||
using G3D::Vector3;
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
|
||||
class MapRayCallback
|
||||
{
|
||||
public:
|
||||
MapRayCallback(ModelInstance* val): prims(val), hit(false) {}
|
||||
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool StopAtFirstHit)
|
||||
{
|
||||
bool result = prims[entry].intersectRay(ray, distance, StopAtFirstHit);
|
||||
if (result)
|
||||
hit = true;
|
||||
return result;
|
||||
}
|
||||
bool didHit() { return hit; }
|
||||
protected:
|
||||
ModelInstance* prims;
|
||||
bool hit;
|
||||
};
|
||||
|
||||
class AreaInfoCallback
|
||||
{
|
||||
public:
|
||||
AreaInfoCallback(ModelInstance* val): prims(val) {}
|
||||
void operator()(const Vector3& point, uint32 entry)
|
||||
{
|
||||
#ifdef VMAP_DEBUG
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "AreaInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());
|
||||
#endif
|
||||
prims[entry].intersectPoint(point, aInfo);
|
||||
}
|
||||
|
||||
ModelInstance* prims;
|
||||
AreaInfo aInfo;
|
||||
};
|
||||
|
||||
class LocationInfoCallback
|
||||
{
|
||||
public:
|
||||
LocationInfoCallback(ModelInstance* val, LocationInfo &info): prims(val), locInfo(info), result(false) {}
|
||||
void operator()(const Vector3& point, uint32 entry)
|
||||
{
|
||||
#ifdef VMAP_DEBUG
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "LocationInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());
|
||||
#endif
|
||||
if (prims[entry].GetLocationInfo(point, locInfo))
|
||||
result = true;
|
||||
}
|
||||
|
||||
ModelInstance* prims;
|
||||
LocationInfo &locInfo;
|
||||
bool result;
|
||||
};
|
||||
|
||||
//=========================================================
|
||||
|
||||
std::string StaticMapTree::getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY)
|
||||
{
|
||||
std::stringstream tilefilename;
|
||||
tilefilename.fill('0');
|
||||
tilefilename << std::setw(3) << mapID << '_';
|
||||
//tilefilename << std::setw(2) << tileX << '_' << std::setw(2) << tileY << ".vmtile";
|
||||
tilefilename << std::setw(2) << tileY << '_' << std::setw(2) << tileX << ".vmtile";
|
||||
return tilefilename.str();
|
||||
}
|
||||
|
||||
bool StaticMapTree::getAreaInfo(Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const
|
||||
{
|
||||
AreaInfoCallback intersectionCallBack(iTreeValues);
|
||||
iTree.intersectPoint(pos, intersectionCallBack);
|
||||
if (intersectionCallBack.aInfo.result)
|
||||
{
|
||||
flags = intersectionCallBack.aInfo.flags;
|
||||
adtId = intersectionCallBack.aInfo.adtId;
|
||||
rootId = intersectionCallBack.aInfo.rootId;
|
||||
groupId = intersectionCallBack.aInfo.groupId;
|
||||
pos.z = intersectionCallBack.aInfo.ground_Z;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StaticMapTree::GetLocationInfo(const Vector3 &pos, LocationInfo &info) const
|
||||
{
|
||||
LocationInfoCallback intersectionCallBack(iTreeValues, info);
|
||||
iTree.intersectPoint(pos, intersectionCallBack);
|
||||
return intersectionCallBack.result;
|
||||
}
|
||||
|
||||
StaticMapTree::StaticMapTree(uint32 mapID, const std::string &basePath)
|
||||
: iMapID(mapID), iIsTiled(false), iTreeValues(0), iBasePath(basePath)
|
||||
{
|
||||
if (iBasePath.length() > 0 && iBasePath[iBasePath.length()-1] != '/' && iBasePath[iBasePath.length()-1] != '\\')
|
||||
{
|
||||
iBasePath.push_back('/');
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
//! Make sure to call unloadMap() to unregister acquired model references before destroying
|
||||
StaticMapTree::~StaticMapTree()
|
||||
{
|
||||
delete[] iTreeValues;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
/**
|
||||
If intersection is found within pMaxDist, sets pMaxDist to intersection distance and returns true.
|
||||
Else, pMaxDist is not modified and returns false;
|
||||
*/
|
||||
|
||||
bool StaticMapTree::getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool StopAtFirstHit) const
|
||||
{
|
||||
float distance = pMaxDist;
|
||||
MapRayCallback intersectionCallBack(iTreeValues);
|
||||
iTree.intersectRay(pRay, intersectionCallBack, distance, StopAtFirstHit);
|
||||
if (intersectionCallBack.didHit())
|
||||
pMaxDist = distance;
|
||||
return intersectionCallBack.didHit();
|
||||
}
|
||||
//=========================================================
|
||||
|
||||
bool StaticMapTree::isInLineOfSight(const Vector3& pos1, const Vector3& pos2) const
|
||||
{
|
||||
float maxDist = (pos2 - pos1).magnitude();
|
||||
// return false if distance is over max float, in case of cheater teleporting to the end of the universe
|
||||
if (maxDist == std::numeric_limits<float>::max() || !myisfinite(maxDist))
|
||||
return false;
|
||||
|
||||
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
|
||||
ASSERT(maxDist < std::numeric_limits<float>::max());
|
||||
// prevent NaN values which can cause BIH intersection to enter infinite loop
|
||||
if (maxDist < 1e-10f)
|
||||
return true;
|
||||
// direction with length of 1
|
||||
G3D::Ray ray = G3D::Ray::fromOriginAndDirection(pos1, (pos2 - pos1)/maxDist);
|
||||
if (getIntersectionTime(ray, maxDist, true))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
//=========================================================
|
||||
/**
|
||||
When moving from pos1 to pos2 check if we hit an object. Return true and the position if we hit one
|
||||
Return the hit pos or the original dest pos
|
||||
*/
|
||||
|
||||
bool StaticMapTree::getObjectHitPos(const Vector3& pPos1, const Vector3& pPos2, Vector3& pResultHitPos, float pModifyDist) const
|
||||
{
|
||||
bool result=false;
|
||||
float maxDist = (pPos2 - pPos1).magnitude();
|
||||
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
|
||||
ASSERT(maxDist < std::numeric_limits<float>::max());
|
||||
// prevent NaN values which can cause BIH intersection to enter infinite loop
|
||||
if (maxDist < 1e-10f)
|
||||
{
|
||||
pResultHitPos = pPos2;
|
||||
return false;
|
||||
}
|
||||
Vector3 dir = (pPos2 - pPos1)/maxDist; // direction with length of 1
|
||||
G3D::Ray ray(pPos1, dir);
|
||||
float dist = maxDist;
|
||||
if (getIntersectionTime(ray, dist, false))
|
||||
{
|
||||
pResultHitPos = pPos1 + dir * dist;
|
||||
if (pModifyDist < 0)
|
||||
{
|
||||
if ((pResultHitPos - pPos1).magnitude() > -pModifyDist)
|
||||
{
|
||||
pResultHitPos = pResultHitPos + dir*pModifyDist;
|
||||
}
|
||||
else
|
||||
{
|
||||
pResultHitPos = pPos1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pResultHitPos = pResultHitPos + dir*pModifyDist;
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pResultHitPos = pPos2;
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
float StaticMapTree::getHeight(const Vector3& pPos, float maxSearchDist) const
|
||||
{
|
||||
float height = G3D::inf();
|
||||
Vector3 dir = Vector3(0, 0, -1);
|
||||
G3D::Ray ray(pPos, dir); // direction with length of 1
|
||||
float maxDist = maxSearchDist;
|
||||
if (getIntersectionTime(ray, maxDist, false))
|
||||
{
|
||||
height = pPos.z - maxDist;
|
||||
}
|
||||
return(height);
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
bool StaticMapTree::CanLoadMap(const std::string &vmapPath, uint32 mapID, uint32 tileX, uint32 tileY)
|
||||
{
|
||||
std::string basePath = vmapPath;
|
||||
if (basePath.length() > 0 && basePath[basePath.length()-1] != '/' && basePath[basePath.length()-1] != '\\')
|
||||
basePath.push_back('/');
|
||||
std::string fullname = basePath + VMapManager2::getMapFileName(mapID);
|
||||
bool success = true;
|
||||
FILE* rf = fopen(fullname.c_str(), "rb");
|
||||
if (!rf)
|
||||
return false;
|
||||
// TODO: check magic number when implemented...
|
||||
char tiled;
|
||||
char chunk[8];
|
||||
if (!readChunk(rf, chunk, VMAP_MAGIC, 8) || fread(&tiled, sizeof(char), 1, rf) != 1)
|
||||
{
|
||||
fclose(rf);
|
||||
return false;
|
||||
}
|
||||
if (tiled)
|
||||
{
|
||||
std::string tilefile = basePath + getTileFileName(mapID, tileX, tileY);
|
||||
FILE* tf = fopen(tilefile.c_str(), "rb");
|
||||
if (!tf)
|
||||
success = false;
|
||||
else
|
||||
{
|
||||
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
|
||||
success = false;
|
||||
fclose(tf);
|
||||
}
|
||||
}
|
||||
fclose(rf);
|
||||
return success;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
bool StaticMapTree::InitMap(const std::string &fname, VMapManager2* vm)
|
||||
{
|
||||
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : initializing StaticMapTree '%s'", fname.c_str());
|
||||
bool success = false;
|
||||
std::string fullname = iBasePath + fname;
|
||||
FILE* rf = fopen(fullname.c_str(), "rb");
|
||||
if (!rf)
|
||||
return false;
|
||||
|
||||
char chunk[8];
|
||||
char tiled = '\0';
|
||||
|
||||
if (readChunk(rf, chunk, VMAP_MAGIC, 8) && fread(&tiled, sizeof(char), 1, rf) == 1 &&
|
||||
readChunk(rf, chunk, "NODE", 4) && iTree.readFromFile(rf))
|
||||
{
|
||||
iNTreeValues = iTree.primCount();
|
||||
iTreeValues = new ModelInstance[iNTreeValues];
|
||||
success = readChunk(rf, chunk, "GOBJ", 4);
|
||||
}
|
||||
|
||||
iIsTiled = bool(tiled);
|
||||
|
||||
// global model spawns
|
||||
// only non-tiled maps have them, and if so exactly one (so far at least...)
|
||||
ModelSpawn spawn;
|
||||
#ifdef VMAP_DEBUG
|
||||
//TC_LOG_DEBUG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : map isTiled: %u", static_cast<uint32>(iIsTiled));
|
||||
#endif
|
||||
if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn))
|
||||
{
|
||||
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name);
|
||||
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : loading %s", spawn.name.c_str());
|
||||
if (model)
|
||||
{
|
||||
// assume that global model always is the first and only tree value (could be improved...)
|
||||
iTreeValues[0] = ModelInstance(spawn, model);
|
||||
iLoadedSpawns[0] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
//VMAP_ERROR_LOG(LOG_FILTER_GENERAL, "StaticMapTree::InitMap() : could not acquire WorldModel pointer for '%s'", spawn.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
fclose(rf);
|
||||
return success;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
void StaticMapTree::UnloadMap(VMapManager2* vm)
|
||||
{
|
||||
for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i)
|
||||
{
|
||||
iTreeValues[i->first].setUnloaded();
|
||||
for (uint32 refCount = 0; refCount < i->second; ++refCount)
|
||||
vm->releaseModelInstance(iTreeValues[i->first].name);
|
||||
}
|
||||
iLoadedSpawns.clear();
|
||||
iLoadedTiles.clear();
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
|
||||
{
|
||||
if (!iIsTiled)
|
||||
{
|
||||
// currently, core creates grids for all maps, whether it has terrain tiles or not
|
||||
// so we need "fake" tile loads to know when we can unload map geometry
|
||||
iLoadedTiles[packTileID(tileX, tileY)] = false;
|
||||
return true;
|
||||
}
|
||||
if (!iTreeValues)
|
||||
{
|
||||
sLog->outError("StaticMapTree::LoadMapTile() : tree has not been initialized [%u, %u]", tileX, tileY);
|
||||
return false;
|
||||
}
|
||||
bool result = true;
|
||||
|
||||
std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);
|
||||
FILE* tf = fopen(tilefile.c_str(), "rb");
|
||||
if (tf)
|
||||
{
|
||||
char chunk[8];
|
||||
|
||||
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
|
||||
result = false;
|
||||
uint32 numSpawns = 0;
|
||||
if (result && fread(&numSpawns, sizeof(uint32), 1, tf) != 1)
|
||||
result = false;
|
||||
for (uint32 i=0; i<numSpawns && result; ++i)
|
||||
{
|
||||
// read model spawns
|
||||
ModelSpawn spawn;
|
||||
result = ModelSpawn::readFromFile(tf, spawn);
|
||||
if (result)
|
||||
{
|
||||
// acquire model instance
|
||||
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name);
|
||||
if (!model)
|
||||
sLog->outError("StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [%u, %u]", tileX, tileY);
|
||||
|
||||
// update tree
|
||||
uint32 referencedVal;
|
||||
|
||||
if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1)
|
||||
{
|
||||
if (!iLoadedSpawns.count(referencedVal))
|
||||
{
|
||||
#ifdef VMAP_DEBUG
|
||||
if (referencedVal > iNTreeValues)
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "StaticMapTree::LoadMapTile() : invalid tree element (%u/%u)", referencedVal, iNTreeValues);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
iTreeValues[referencedVal] = ModelInstance(spawn, model);
|
||||
iLoadedSpawns[referencedVal] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iLoadedSpawns[referencedVal];
|
||||
#ifdef VMAP_DEBUG
|
||||
if (iTreeValues[referencedVal].ID != spawn.ID)
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
|
||||
else if (iTreeValues[referencedVal].name != spawn.name)
|
||||
;//sLog->outDebug(LOG_FILTER_MAPS, "StaticMapTree::LoadMapTile() : name collision on GUID=%u", spawn.ID);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
iLoadedTiles[packTileID(tileX, tileY)] = true;
|
||||
fclose(tf);
|
||||
}
|
||||
else
|
||||
iLoadedTiles[packTileID(tileX, tileY)] = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
|
||||
{
|
||||
uint32 tileID = packTileID(tileX, tileY);
|
||||
loadedTileMap::iterator tile = iLoadedTiles.find(tileID);
|
||||
if (tile == iLoadedTiles.end())
|
||||
{
|
||||
sLog->outError("StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:%u X:%u Y:%u", iMapID, tileX, tileY);
|
||||
return;
|
||||
}
|
||||
if (tile->second) // file associated with tile
|
||||
{
|
||||
std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);
|
||||
FILE* tf = fopen(tilefile.c_str(), "rb");
|
||||
if (tf)
|
||||
{
|
||||
bool result=true;
|
||||
char chunk[8];
|
||||
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
|
||||
result = false;
|
||||
uint32 numSpawns;
|
||||
if (fread(&numSpawns, sizeof(uint32), 1, tf) != 1)
|
||||
result = false;
|
||||
for (uint32 i=0; i<numSpawns && result; ++i)
|
||||
{
|
||||
// read model spawns
|
||||
ModelSpawn spawn;
|
||||
result = ModelSpawn::readFromFile(tf, spawn);
|
||||
if (result)
|
||||
{
|
||||
// release model instance
|
||||
vm->releaseModelInstance(spawn.name);
|
||||
|
||||
// update tree
|
||||
uint32 referencedNode;
|
||||
|
||||
if (fread(&referencedNode, sizeof(uint32), 1, tf) != 1)
|
||||
result = false;
|
||||
else
|
||||
{
|
||||
if (!iLoadedSpawns.count(referencedNode))
|
||||
sLog->outError("StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '%s' (ID:%u)", spawn.name.c_str(), spawn.ID);
|
||||
else if (--iLoadedSpawns[referencedNode] == 0)
|
||||
{
|
||||
iTreeValues[referencedNode].setUnloaded();
|
||||
iLoadedSpawns.erase(referencedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(tf);
|
||||
}
|
||||
}
|
||||
iLoadedTiles.erase(tile);
|
||||
}
|
||||
}
|
||||
99
src/server/collision/Maps/MapTree.h
Normal file
99
src/server/collision/Maps/MapTree.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _MAPTREE_H
|
||||
#define _MAPTREE_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Dynamic/UnorderedMap.h"
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class ModelInstance;
|
||||
class GroupModel;
|
||||
class VMapManager2;
|
||||
|
||||
struct LocationInfo
|
||||
{
|
||||
LocationInfo(): hitInstance(0), hitModel(0), ground_Z(-G3D::inf()) { }
|
||||
const ModelInstance* hitInstance;
|
||||
const GroupModel* hitModel;
|
||||
float ground_Z;
|
||||
};
|
||||
|
||||
class StaticMapTree
|
||||
{
|
||||
typedef UNORDERED_MAP<uint32, bool> loadedTileMap;
|
||||
typedef UNORDERED_MAP<uint32, uint32> loadedSpawnMap;
|
||||
private:
|
||||
uint32 iMapID;
|
||||
bool iIsTiled;
|
||||
BIH iTree;
|
||||
ModelInstance* iTreeValues; // the tree entries
|
||||
uint32 iNTreeValues;
|
||||
|
||||
// Store all the map tile idents that are loaded for that map
|
||||
// some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed
|
||||
// empty tiles have no tile file, hence map with bool instead of just a set (consistency check)
|
||||
loadedTileMap iLoadedTiles;
|
||||
// stores <tree_index, reference_count> to invalidate tree values, unload map, and to be able to report errors
|
||||
loadedSpawnMap iLoadedSpawns;
|
||||
std::string iBasePath;
|
||||
|
||||
private:
|
||||
bool getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool StopAtFirstHit) const;
|
||||
//bool containsLoadedMapTile(unsigned int pTileIdent) const { return(iLoadedMapTiles.containsKey(pTileIdent)); }
|
||||
public:
|
||||
static std::string getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY);
|
||||
static uint32 packTileID(uint32 tileX, uint32 tileY) { return tileX<<16 | tileY; }
|
||||
static void unpackTileID(uint32 ID, uint32 &tileX, uint32 &tileY) { tileX = ID>>16; tileY = ID&0xFF; }
|
||||
static bool CanLoadMap(const std::string &basePath, uint32 mapID, uint32 tileX, uint32 tileY);
|
||||
|
||||
StaticMapTree(uint32 mapID, const std::string &basePath);
|
||||
~StaticMapTree();
|
||||
|
||||
bool isInLineOfSight(const G3D::Vector3& pos1, const G3D::Vector3& pos2) const;
|
||||
bool getObjectHitPos(const G3D::Vector3& pos1, const G3D::Vector3& pos2, G3D::Vector3& pResultHitPos, float pModifyDist) const;
|
||||
float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const;
|
||||
bool getAreaInfo(G3D::Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const;
|
||||
bool GetLocationInfo(const G3D::Vector3 &pos, LocationInfo &info) const;
|
||||
|
||||
bool InitMap(const std::string &fname, VMapManager2* vm);
|
||||
void UnloadMap(VMapManager2* vm);
|
||||
bool LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm);
|
||||
void UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm);
|
||||
bool isTiled() const { return iIsTiled; }
|
||||
uint32 numLoadedTiles() const { return iLoadedTiles.size(); }
|
||||
void getModelInstances(ModelInstance* &models, uint32 &count);
|
||||
};
|
||||
|
||||
struct AreaInfo
|
||||
{
|
||||
AreaInfo(): result(false), ground_Z(-G3D::inf()), flags(0), adtId(0),
|
||||
rootId(0), groupId(0) { }
|
||||
bool result;
|
||||
float ground_Z;
|
||||
uint32 flags;
|
||||
int32 adtId;
|
||||
int32 rootId;
|
||||
int32 groupId;
|
||||
};
|
||||
} // VMAP
|
||||
|
||||
#endif // _MAPTREE_H
|
||||
540
src/server/collision/Maps/TileAssembler.cpp
Normal file
540
src/server/collision/Maps/TileAssembler.cpp
Normal file
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "TileAssembler.h"
|
||||
#include "MapTree.h"
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include "SharedDefines.h"
|
||||
|
||||
#include <set>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
using G3D::Vector3;
|
||||
using G3D::AABox;
|
||||
using G3D::inf;
|
||||
using std::pair;
|
||||
|
||||
template<> struct BoundsTrait<VMAP::ModelSpawn*>
|
||||
{
|
||||
static void getBounds(const VMAP::ModelSpawn* const &obj, G3D::AABox& out) { out = obj->getBounds(); }
|
||||
};
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
bool readChunk(FILE* rf, char *dest, const char *compare, uint32 len)
|
||||
{
|
||||
if (fread(dest, sizeof(char), len, rf) != len) return false;
|
||||
return memcmp(dest, compare, len) == 0;
|
||||
}
|
||||
|
||||
Vector3 ModelPosition::transform(const Vector3& pIn) const
|
||||
{
|
||||
Vector3 out = pIn * iScale;
|
||||
out = iRotation * out;
|
||||
return(out);
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
|
||||
TileAssembler::TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName)
|
||||
: iDestDir(pDestDirName), iSrcDir(pSrcDirName), iFilterMethod(NULL), iCurrentUniqueNameId(0)
|
||||
{
|
||||
//mkdir(iDestDir);
|
||||
//init();
|
||||
}
|
||||
|
||||
TileAssembler::~TileAssembler()
|
||||
{
|
||||
//delete iCoordModelMapping;
|
||||
}
|
||||
|
||||
bool TileAssembler::convertWorld2()
|
||||
{
|
||||
bool success = readMapSpawns();
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
// export Map data
|
||||
for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end() && success; ++map_iter)
|
||||
{
|
||||
// build global map tree
|
||||
std::vector<ModelSpawn*> mapSpawns;
|
||||
UniqueEntryMap::iterator entry;
|
||||
printf("Calculating model bounds for map %u...\n", map_iter->first);
|
||||
for (entry = map_iter->second->UniqueEntries.begin(); entry != map_iter->second->UniqueEntries.end(); ++entry)
|
||||
{
|
||||
// M2 models don't have a bound set in WDT/ADT placement data, i still think they're not used for LoS at all on retail
|
||||
if (entry->second.flags & MOD_M2)
|
||||
{
|
||||
if (!calculateTransformedBound(entry->second))
|
||||
break;
|
||||
}
|
||||
else if (entry->second.flags & MOD_WORLDSPAWN) // WMO maps and terrain maps use different origin, so we need to adapt :/
|
||||
{
|
||||
/// @todo remove extractor hack and uncomment below line:
|
||||
//entry->second.iPos += Vector3(533.33333f*32, 533.33333f*32, 0.f);
|
||||
entry->second.iBound = entry->second.iBound + Vector3(533.33333f*32, 533.33333f*32, 0.f);
|
||||
}
|
||||
mapSpawns.push_back(&(entry->second));
|
||||
spawnedModelFiles.insert(entry->second.name);
|
||||
}
|
||||
|
||||
printf("Creating map tree for map %u...\n", map_iter->first);
|
||||
BIH pTree;
|
||||
|
||||
try
|
||||
{
|
||||
pTree.build(mapSpawns, BoundsTrait<ModelSpawn*>::getBounds);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("Exception ""%s"" when calling pTree.build", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===> possibly move this code to StaticMapTree class
|
||||
std::map<uint32, uint32> modelNodeIdx;
|
||||
for (uint32 i=0; i<mapSpawns.size(); ++i)
|
||||
modelNodeIdx.insert(pair<uint32, uint32>(mapSpawns[i]->ID, i));
|
||||
|
||||
// write map tree file
|
||||
std::stringstream mapfilename;
|
||||
mapfilename << iDestDir << '/' << std::setfill('0') << std::setw(3) << map_iter->first << ".vmtree";
|
||||
FILE* mapfile = fopen(mapfilename.str().c_str(), "wb");
|
||||
if (!mapfile)
|
||||
{
|
||||
success = false;
|
||||
printf("Cannot open %s\n", mapfilename.str().c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
//general info
|
||||
if (success && fwrite(VMAP_MAGIC, 1, 8, mapfile) != 8) success = false;
|
||||
uint32 globalTileID = StaticMapTree::packTileID(65, 65);
|
||||
pair<TileMap::iterator, TileMap::iterator> globalRange = map_iter->second->TileEntries.equal_range(globalTileID);
|
||||
char isTiled = globalRange.first == globalRange.second; // only maps without terrain (tiles) have global WMO
|
||||
if (success && fwrite(&isTiled, sizeof(char), 1, mapfile) != 1) success = false;
|
||||
// Nodes
|
||||
if (success && fwrite("NODE", 4, 1, mapfile) != 1) success = false;
|
||||
if (success) success = pTree.writeToFile(mapfile);
|
||||
// global map spawns (WDT), if any (most instances)
|
||||
if (success && fwrite("GOBJ", 4, 1, mapfile) != 1) success = false;
|
||||
|
||||
for (TileMap::iterator glob=globalRange.first; glob != globalRange.second && success; ++glob)
|
||||
{
|
||||
success = ModelSpawn::writeToFile(mapfile, map_iter->second->UniqueEntries[glob->second]);
|
||||
}
|
||||
|
||||
fclose(mapfile);
|
||||
|
||||
// <====
|
||||
|
||||
// write map tile files, similar to ADT files, only with extra BSP tree node info
|
||||
TileMap &tileEntries = map_iter->second->TileEntries;
|
||||
TileMap::iterator tile;
|
||||
for (tile = tileEntries.begin(); tile != tileEntries.end(); ++tile)
|
||||
{
|
||||
const ModelSpawn &spawn = map_iter->second->UniqueEntries[tile->second];
|
||||
if (spawn.flags & MOD_WORLDSPAWN) // WDT spawn, saved as tile 65/65 currently...
|
||||
continue;
|
||||
uint32 nSpawns = tileEntries.count(tile->first);
|
||||
std::stringstream tilefilename;
|
||||
tilefilename.fill('0');
|
||||
tilefilename << iDestDir << '/' << std::setw(3) << map_iter->first << '_';
|
||||
uint32 x, y;
|
||||
StaticMapTree::unpackTileID(tile->first, x, y);
|
||||
tilefilename << std::setw(2) << x << '_' << std::setw(2) << y << ".vmtile";
|
||||
if (FILE* tilefile = fopen(tilefilename.str().c_str(), "wb"))
|
||||
{
|
||||
// file header
|
||||
if (success && fwrite(VMAP_MAGIC, 1, 8, tilefile) != 8) success = false;
|
||||
// write number of tile spawns
|
||||
if (success && fwrite(&nSpawns, sizeof(uint32), 1, tilefile) != 1) success = false;
|
||||
// write tile spawns
|
||||
for (uint32 s=0; s<nSpawns; ++s)
|
||||
{
|
||||
if (s)
|
||||
++tile;
|
||||
const ModelSpawn &spawn2 = map_iter->second->UniqueEntries[tile->second];
|
||||
success = success && ModelSpawn::writeToFile(tilefile, spawn2);
|
||||
// MapTree nodes to update when loading tile:
|
||||
std::map<uint32, uint32>::iterator nIdx = modelNodeIdx.find(spawn2.ID);
|
||||
if (success && fwrite(&nIdx->second, sizeof(uint32), 1, tilefile) != 1) success = false;
|
||||
}
|
||||
fclose(tilefile);
|
||||
}
|
||||
}
|
||||
// break; //test, extract only first map; TODO: remvoe this line
|
||||
}
|
||||
|
||||
// add an object models, listed in temp_gameobject_models file
|
||||
exportGameobjectModels();
|
||||
// export objects
|
||||
std::cout << "\nConverting Model Files" << std::endl;
|
||||
for (std::set<std::string>::iterator mfile = spawnedModelFiles.begin(); mfile != spawnedModelFiles.end(); ++mfile)
|
||||
{
|
||||
std::cout << "Converting " << *mfile << std::endl;
|
||||
if (!convertRawFile(*mfile))
|
||||
{
|
||||
std::cout << "error converting " << *mfile << std::endl;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//cleanup:
|
||||
for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end(); ++map_iter)
|
||||
{
|
||||
delete map_iter->second;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TileAssembler::readMapSpawns()
|
||||
{
|
||||
std::string fname = iSrcDir + "/dir_bin";
|
||||
FILE* dirf = fopen(fname.c_str(), "rb");
|
||||
if (!dirf)
|
||||
{
|
||||
printf("Could not read dir_bin file!\n");
|
||||
return false;
|
||||
}
|
||||
printf("Read coordinate mapping...\n");
|
||||
uint32 mapID, tileX, tileY, check=0;
|
||||
G3D::Vector3 v1, v2;
|
||||
ModelSpawn spawn;
|
||||
while (!feof(dirf))
|
||||
{
|
||||
check = 0;
|
||||
// read mapID, tileX, tileY, Flags, adtID, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name
|
||||
check += fread(&mapID, sizeof(uint32), 1, dirf);
|
||||
if (check == 0) // EoF...
|
||||
break;
|
||||
check += fread(&tileX, sizeof(uint32), 1, dirf);
|
||||
check += fread(&tileY, sizeof(uint32), 1, dirf);
|
||||
if (!ModelSpawn::readFromFile(dirf, spawn))
|
||||
break;
|
||||
|
||||
MapSpawns *current;
|
||||
MapData::iterator map_iter = mapData.find(mapID);
|
||||
if (map_iter == mapData.end())
|
||||
{
|
||||
printf("spawning Map %d\n", mapID);
|
||||
mapData[mapID] = current = new MapSpawns();
|
||||
}
|
||||
else current = (*map_iter).second;
|
||||
current->UniqueEntries.insert(pair<uint32, ModelSpawn>(spawn.ID, spawn));
|
||||
current->TileEntries.insert(pair<uint32, uint32>(StaticMapTree::packTileID(tileX, tileY), spawn.ID));
|
||||
}
|
||||
bool success = (ferror(dirf) == 0);
|
||||
fclose(dirf);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TileAssembler::calculateTransformedBound(ModelSpawn &spawn)
|
||||
{
|
||||
std::string modelFilename(iSrcDir);
|
||||
modelFilename.push_back('/');
|
||||
modelFilename.append(spawn.name);
|
||||
|
||||
ModelPosition modelPosition;
|
||||
modelPosition.iDir = spawn.iRot;
|
||||
modelPosition.iScale = spawn.iScale;
|
||||
modelPosition.init();
|
||||
|
||||
WorldModel_Raw raw_model;
|
||||
if (!raw_model.Read(modelFilename.c_str()))
|
||||
return false;
|
||||
|
||||
uint32 groups = raw_model.groupsArray.size();
|
||||
if (groups != 1)
|
||||
printf("Warning: '%s' does not seem to be a M2 model!\n", modelFilename.c_str());
|
||||
|
||||
AABox modelBound;
|
||||
bool boundEmpty=true;
|
||||
|
||||
for (uint32 g=0; g<groups; ++g) // should be only one for M2 files...
|
||||
{
|
||||
std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
|
||||
|
||||
if (vertices.empty())
|
||||
{
|
||||
std::cout << "error: model '" << spawn.name << "' has no geometry!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 nvectors = vertices.size();
|
||||
for (uint32 i = 0; i < nvectors; ++i)
|
||||
{
|
||||
Vector3 v = modelPosition.transform(vertices[i]);
|
||||
|
||||
if (boundEmpty)
|
||||
modelBound = AABox(v, v), boundEmpty=false;
|
||||
else
|
||||
modelBound.merge(v);
|
||||
}
|
||||
}
|
||||
spawn.iBound = modelBound + spawn.iPos;
|
||||
spawn.flags |= MOD_HAS_BOUND;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct WMOLiquidHeader
|
||||
{
|
||||
int xverts, yverts, xtiles, ytiles;
|
||||
float pos_x;
|
||||
float pos_y;
|
||||
float pos_z;
|
||||
short type;
|
||||
};
|
||||
//=================================================================
|
||||
bool TileAssembler::convertRawFile(const std::string& pModelFilename)
|
||||
{
|
||||
bool success = true;
|
||||
std::string filename = iSrcDir;
|
||||
if (filename.length() >0)
|
||||
filename.push_back('/');
|
||||
filename.append(pModelFilename);
|
||||
|
||||
WorldModel_Raw raw_model;
|
||||
if (!raw_model.Read(filename.c_str()))
|
||||
return false;
|
||||
|
||||
// write WorldModel
|
||||
WorldModel model;
|
||||
model.setRootWmoID(raw_model.RootWMOID);
|
||||
if (!raw_model.groupsArray.empty())
|
||||
{
|
||||
std::vector<GroupModel> groupsArray;
|
||||
|
||||
uint32 groups = raw_model.groupsArray.size();
|
||||
for (uint32 g = 0; g < groups; ++g)
|
||||
{
|
||||
GroupModel_Raw& raw_group = raw_model.groupsArray[g];
|
||||
groupsArray.push_back(GroupModel(raw_group.mogpflags, raw_group.GroupWMOID, raw_group.bounds ));
|
||||
groupsArray.back().setMeshData(raw_group.vertexArray, raw_group.triangles);
|
||||
groupsArray.back().setLiquidData(raw_group.liquid);
|
||||
}
|
||||
|
||||
model.setGroupModels(groupsArray);
|
||||
}
|
||||
|
||||
success = model.writeFile(iDestDir + "/" + pModelFilename + ".vmo");
|
||||
//std::cout << "readRawFile2: '" << pModelFilename << "' tris: " << nElements << " nodes: " << nNodes << std::endl;
|
||||
return success;
|
||||
}
|
||||
|
||||
void TileAssembler::exportGameobjectModels()
|
||||
{
|
||||
FILE* model_list = fopen((iSrcDir + "/" + "temp_gameobject_models").c_str(), "rb");
|
||||
if (!model_list)
|
||||
return;
|
||||
|
||||
FILE* model_list_copy = fopen((iDestDir + "/" + GAMEOBJECT_MODELS).c_str(), "wb");
|
||||
if (!model_list_copy)
|
||||
{
|
||||
fclose(model_list);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 name_length, displayId;
|
||||
char buff[500];
|
||||
while (!feof(model_list))
|
||||
{
|
||||
if (fread(&displayId, sizeof(uint32), 1, model_list) != 1
|
||||
|| fread(&name_length, sizeof(uint32), 1, model_list) != 1
|
||||
|| name_length >= sizeof(buff)
|
||||
|| fread(&buff, sizeof(char), name_length, model_list) != name_length)
|
||||
{
|
||||
std::cout << "\nFile 'temp_gameobject_models' seems to be corrupted" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string model_name(buff, name_length);
|
||||
|
||||
WorldModel_Raw raw_model;
|
||||
if ( !raw_model.Read((iSrcDir + "/" + model_name).c_str()) )
|
||||
continue;
|
||||
|
||||
spawnedModelFiles.insert(model_name);
|
||||
AABox bounds;
|
||||
bool boundEmpty = true;
|
||||
for (uint32 g = 0; g < raw_model.groupsArray.size(); ++g)
|
||||
{
|
||||
std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
|
||||
|
||||
uint32 nvectors = vertices.size();
|
||||
for (uint32 i = 0; i < nvectors; ++i)
|
||||
{
|
||||
Vector3& v = vertices[i];
|
||||
if (boundEmpty)
|
||||
bounds = AABox(v, v), boundEmpty = false;
|
||||
else
|
||||
bounds.merge(v);
|
||||
}
|
||||
}
|
||||
|
||||
fwrite(&displayId, sizeof(uint32), 1, model_list_copy);
|
||||
fwrite(&name_length, sizeof(uint32), 1, model_list_copy);
|
||||
fwrite(&buff, sizeof(char), name_length, model_list_copy);
|
||||
fwrite(&bounds.low(), sizeof(Vector3), 1, model_list_copy);
|
||||
fwrite(&bounds.high(), sizeof(Vector3), 1, model_list_copy);
|
||||
}
|
||||
|
||||
fclose(model_list);
|
||||
fclose(model_list_copy);
|
||||
}
|
||||
// temporary use defines to simplify read/check code (close file and return at fail)
|
||||
#define READ_OR_RETURN(V, S) if (fread((V), (S), 1, rf) != 1) { \
|
||||
fclose(rf); printf("readfail, op = %i\n", readOperation); return(false); }
|
||||
#define READ_OR_RETURN_WITH_DELETE(V, S) if (fread((V), (S), 1, rf) != 1) { \
|
||||
fclose(rf); printf("readfail, op = %i\n", readOperation); delete[] V; return(false); };
|
||||
#define CMP_OR_RETURN(V, S) if (strcmp((V), (S)) != 0) { \
|
||||
fclose(rf); printf("cmpfail, %s!=%s\n", V, S);return(false); }
|
||||
|
||||
bool GroupModel_Raw::Read(FILE* rf)
|
||||
{
|
||||
char blockId[5];
|
||||
blockId[4] = 0;
|
||||
int blocksize;
|
||||
int readOperation = 0;
|
||||
|
||||
READ_OR_RETURN(&mogpflags, sizeof(uint32));
|
||||
READ_OR_RETURN(&GroupWMOID, sizeof(uint32));
|
||||
|
||||
|
||||
Vector3 vec1, vec2;
|
||||
READ_OR_RETURN(&vec1, sizeof(Vector3));
|
||||
|
||||
READ_OR_RETURN(&vec2, sizeof(Vector3));
|
||||
bounds.set(vec1, vec2);
|
||||
|
||||
READ_OR_RETURN(&liquidflags, sizeof(uint32));
|
||||
|
||||
// will this ever be used? what is it good for anyway??
|
||||
uint32 branches;
|
||||
READ_OR_RETURN(&blockId, 4);
|
||||
CMP_OR_RETURN(blockId, "GRP ");
|
||||
READ_OR_RETURN(&blocksize, sizeof(int));
|
||||
READ_OR_RETURN(&branches, sizeof(uint32));
|
||||
for (uint32 b=0; b<branches; ++b)
|
||||
{
|
||||
uint32 indexes;
|
||||
// indexes for each branch (not used jet)
|
||||
READ_OR_RETURN(&indexes, sizeof(uint32));
|
||||
}
|
||||
|
||||
// ---- indexes
|
||||
READ_OR_RETURN(&blockId, 4);
|
||||
CMP_OR_RETURN(blockId, "INDX");
|
||||
READ_OR_RETURN(&blocksize, sizeof(int));
|
||||
uint32 nindexes;
|
||||
READ_OR_RETURN(&nindexes, sizeof(uint32));
|
||||
if (nindexes >0)
|
||||
{
|
||||
uint16 *indexarray = new uint16[nindexes];
|
||||
READ_OR_RETURN_WITH_DELETE(indexarray, nindexes*sizeof(uint16));
|
||||
triangles.reserve(nindexes / 3);
|
||||
for (uint32 i=0; i<nindexes; i+=3)
|
||||
triangles.push_back(MeshTriangle(indexarray[i], indexarray[i+1], indexarray[i+2]));
|
||||
|
||||
delete[] indexarray;
|
||||
}
|
||||
|
||||
// ---- vectors
|
||||
READ_OR_RETURN(&blockId, 4);
|
||||
CMP_OR_RETURN(blockId, "VERT");
|
||||
READ_OR_RETURN(&blocksize, sizeof(int));
|
||||
uint32 nvectors;
|
||||
READ_OR_RETURN(&nvectors, sizeof(uint32));
|
||||
|
||||
if (nvectors >0)
|
||||
{
|
||||
float *vectorarray = new float[nvectors*3];
|
||||
READ_OR_RETURN_WITH_DELETE(vectorarray, nvectors*sizeof(float)*3);
|
||||
for (uint32 i=0; i<nvectors; ++i)
|
||||
vertexArray.push_back( Vector3(vectorarray + 3*i) );
|
||||
|
||||
delete[] vectorarray;
|
||||
}
|
||||
// ----- liquid
|
||||
liquid = 0;
|
||||
if (liquidflags& 1)
|
||||
{
|
||||
WMOLiquidHeader hlq;
|
||||
READ_OR_RETURN(&blockId, 4);
|
||||
CMP_OR_RETURN(blockId, "LIQU");
|
||||
READ_OR_RETURN(&blocksize, sizeof(int));
|
||||
READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader));
|
||||
liquid = new WmoLiquid(hlq.xtiles, hlq.ytiles, Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), hlq.type);
|
||||
uint32 size = hlq.xverts*hlq.yverts;
|
||||
READ_OR_RETURN(liquid->GetHeightStorage(), size*sizeof(float));
|
||||
size = hlq.xtiles*hlq.ytiles;
|
||||
READ_OR_RETURN(liquid->GetFlagsStorage(), size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
GroupModel_Raw::~GroupModel_Raw()
|
||||
{
|
||||
delete liquid;
|
||||
}
|
||||
|
||||
bool WorldModel_Raw::Read(const char * path)
|
||||
{
|
||||
FILE* rf = fopen(path, "rb");
|
||||
if (!rf)
|
||||
{
|
||||
printf("ERROR: Can't open raw model file: %s\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
char ident[9];
|
||||
ident[8] = '\0';
|
||||
int readOperation = 0;
|
||||
|
||||
READ_OR_RETURN(&ident, 8);
|
||||
CMP_OR_RETURN(ident, RAW_VMAP_MAGIC);
|
||||
|
||||
// we have to read one int. This is needed during the export and we have to skip it here
|
||||
uint32 tempNVectors;
|
||||
READ_OR_RETURN(&tempNVectors, sizeof(tempNVectors));
|
||||
|
||||
uint32 groups;
|
||||
READ_OR_RETURN(&groups, sizeof(uint32));
|
||||
READ_OR_RETURN(&RootWMOID, sizeof(uint32));
|
||||
|
||||
groupsArray.resize(groups);
|
||||
bool succeed = true;
|
||||
for (uint32 g = 0; g < groups && succeed; ++g)
|
||||
succeed = groupsArray[g].Read(rf);
|
||||
|
||||
if (succeed) /// rf will be freed inside Read if the function had any errors.
|
||||
fclose(rf);
|
||||
return succeed;
|
||||
}
|
||||
|
||||
// drop of temporary use defines
|
||||
#undef READ_OR_RETURN
|
||||
#undef CMP_OR_RETURN
|
||||
}
|
||||
119
src/server/collision/Maps/TileAssembler.h
Normal file
119
src/server/collision/Maps/TileAssembler.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _TILEASSEMBLER_H_
|
||||
#define _TILEASSEMBLER_H_
|
||||
|
||||
#include <G3D/Vector3.h>
|
||||
#include <G3D/Matrix3.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "ModelInstance.h"
|
||||
#include "WorldModel.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
/**
|
||||
This Class is used to convert raw vector data into balanced BSP-Trees.
|
||||
To start the conversion call convertWorld().
|
||||
*/
|
||||
//===============================================
|
||||
|
||||
class ModelPosition
|
||||
{
|
||||
private:
|
||||
G3D::Matrix3 iRotation;
|
||||
public:
|
||||
ModelPosition(): iScale(0.0f) { }
|
||||
G3D::Vector3 iPos;
|
||||
G3D::Vector3 iDir;
|
||||
float iScale;
|
||||
void init()
|
||||
{
|
||||
iRotation = G3D::Matrix3::fromEulerAnglesZYX(G3D::pi()*iDir.y/180.f, G3D::pi()*iDir.x/180.f, G3D::pi()*iDir.z/180.f);
|
||||
}
|
||||
G3D::Vector3 transform(const G3D::Vector3& pIn) const;
|
||||
void moveToBasePos(const G3D::Vector3& pBasePos) { iPos -= pBasePos; }
|
||||
};
|
||||
|
||||
typedef std::map<uint32, ModelSpawn> UniqueEntryMap;
|
||||
typedef std::multimap<uint32, uint32> TileMap;
|
||||
|
||||
struct MapSpawns
|
||||
{
|
||||
UniqueEntryMap UniqueEntries;
|
||||
TileMap TileEntries;
|
||||
};
|
||||
|
||||
typedef std::map<uint32, MapSpawns*> MapData;
|
||||
//===============================================
|
||||
|
||||
struct GroupModel_Raw
|
||||
{
|
||||
uint32 mogpflags;
|
||||
uint32 GroupWMOID;
|
||||
|
||||
G3D::AABox bounds;
|
||||
uint32 liquidflags;
|
||||
std::vector<MeshTriangle> triangles;
|
||||
std::vector<G3D::Vector3> vertexArray;
|
||||
class WmoLiquid* liquid;
|
||||
|
||||
GroupModel_Raw() : mogpflags(0), GroupWMOID(0), liquidflags(0),
|
||||
liquid(NULL) { }
|
||||
~GroupModel_Raw();
|
||||
|
||||
bool Read(FILE* f);
|
||||
};
|
||||
|
||||
struct WorldModel_Raw
|
||||
{
|
||||
uint32 RootWMOID;
|
||||
std::vector<GroupModel_Raw> groupsArray;
|
||||
|
||||
bool Read(const char * path);
|
||||
};
|
||||
|
||||
class TileAssembler
|
||||
{
|
||||
private:
|
||||
std::string iDestDir;
|
||||
std::string iSrcDir;
|
||||
bool (*iFilterMethod)(char *pName);
|
||||
G3D::Table<std::string, unsigned int > iUniqueNameIds;
|
||||
unsigned int iCurrentUniqueNameId;
|
||||
MapData mapData;
|
||||
std::set<std::string> spawnedModelFiles;
|
||||
|
||||
public:
|
||||
TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName);
|
||||
virtual ~TileAssembler();
|
||||
|
||||
bool convertWorld2();
|
||||
bool readMapSpawns();
|
||||
bool calculateTransformedBound(ModelSpawn &spawn);
|
||||
void exportGameobjectModels();
|
||||
|
||||
bool convertRawFile(const std::string& pModelFilename);
|
||||
void setModelNameFilterMethod(bool (*pFilterMethod)(char *pName)) { iFilterMethod = pFilterMethod; }
|
||||
std::string getDirEntryNameFromModName(unsigned int pMapId, const std::string& pModPosName);
|
||||
};
|
||||
|
||||
} // VMAP
|
||||
#endif /*_TILEASSEMBLER_H_*/
|
||||
226
src/server/collision/Models/GameObjectModel.cpp
Normal file
226
src/server/collision/Models/GameObjectModel.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "VMapFactory.h"
|
||||
#include "VMapManager2.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include "WorldModel.h"
|
||||
|
||||
#include "GameObjectModel.h"
|
||||
#include "Log.h"
|
||||
#include "GameObject.h"
|
||||
#include "Creature.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Object.h"
|
||||
#include "DBCStores.h"
|
||||
#include "World.h"
|
||||
|
||||
using G3D::Vector3;
|
||||
using G3D::Ray;
|
||||
using G3D::AABox;
|
||||
|
||||
struct GameobjectModelData
|
||||
{
|
||||
GameobjectModelData(const std::string& name_, const AABox& box) :
|
||||
bound(box), name(name_) {}
|
||||
|
||||
AABox bound;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
typedef UNORDERED_MAP<uint32, GameobjectModelData> ModelList;
|
||||
ModelList model_list;
|
||||
|
||||
void LoadGameObjectModelList()
|
||||
{
|
||||
//#ifndef NO_CORE_FUNCS
|
||||
uint32 oldMSTime = getMSTime();
|
||||
//#endif
|
||||
|
||||
FILE* model_list_file = fopen((sWorld->GetDataPath() + "vmaps/" + VMAP::GAMEOBJECT_MODELS).c_str(), "rb");
|
||||
if (!model_list_file)
|
||||
{
|
||||
sLog->outError("Unable to open '%s' file.", VMAP::GAMEOBJECT_MODELS);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 name_length, displayId;
|
||||
char buff[500];
|
||||
while (true)
|
||||
{
|
||||
Vector3 v1, v2;
|
||||
if (fread(&displayId, sizeof(uint32), 1, model_list_file) != 1)
|
||||
if (feof(model_list_file)) // EOF flag is only set after failed reading attempt
|
||||
break;
|
||||
|
||||
if (fread(&name_length, sizeof(uint32), 1, model_list_file) != 1
|
||||
|| name_length >= sizeof(buff)
|
||||
|| fread(&buff, sizeof(char), name_length, model_list_file) != name_length
|
||||
|| fread(&v1, sizeof(Vector3), 1, model_list_file) != 1
|
||||
|| fread(&v2, sizeof(Vector3), 1, model_list_file) != 1)
|
||||
{
|
||||
sLog->outError("File '%s' seems to be corrupted!", VMAP::GAMEOBJECT_MODELS);
|
||||
break;
|
||||
}
|
||||
|
||||
model_list.insert
|
||||
(
|
||||
ModelList::value_type( displayId, GameobjectModelData(std::string(buff, name_length), AABox(v1, v2)) )
|
||||
);
|
||||
}
|
||||
|
||||
fclose(model_list_file);
|
||||
sLog->outString(">> Loaded %u GameObject models in %u ms", uint32(model_list.size()), GetMSTimeDiffToNow(oldMSTime));
|
||||
sLog->outString();
|
||||
}
|
||||
|
||||
GameObjectModel::~GameObjectModel()
|
||||
{
|
||||
if (iModel)
|
||||
((VMAP::VMapManager2*)VMAP::VMapFactory::createOrGetVMapManager())->releaseModelInstance(name);
|
||||
}
|
||||
|
||||
bool GameObjectModel::initialize(const GameObject& go, const GameObjectDisplayInfoEntry& info)
|
||||
{
|
||||
ModelList::const_iterator it = model_list.find(info.Displayid);
|
||||
if (it == model_list.end())
|
||||
return false;
|
||||
|
||||
G3D::AABox mdl_box(it->second.bound);
|
||||
// ignore models with no bounds
|
||||
if (mdl_box == G3D::AABox::zero())
|
||||
{
|
||||
sLog->outError("GameObject model %s has zero bounds, loading skipped", it->second.name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
iModel = ((VMAP::VMapManager2*)VMAP::VMapFactory::createOrGetVMapManager())->acquireModelInstance(sWorld->GetDataPath() + "vmaps/", it->second.name);
|
||||
|
||||
if (!iModel)
|
||||
return false;
|
||||
|
||||
name = it->second.name;
|
||||
//flags = VMAP::MOD_M2;
|
||||
//adtId = 0;
|
||||
//ID = 0;
|
||||
iPos = Vector3(go.GetPositionX(), go.GetPositionY(), go.GetPositionZ());
|
||||
|
||||
// pussywizard:
|
||||
phasemask = (go.GetGoState() == GO_STATE_READY || go.IsTransport()) ? go.GetPhaseMask() : 0;
|
||||
|
||||
iScale = go.GetFloatValue(OBJECT_FIELD_SCALE_X);
|
||||
iInvScale = 1.f / iScale;
|
||||
|
||||
G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(go.GetOrientation(), 0, 0);
|
||||
iInvRot = iRotation.inverse();
|
||||
// transform bounding box:
|
||||
mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale);
|
||||
AABox rotated_bounds;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
rotated_bounds.merge(iRotation * mdl_box.corner(i));
|
||||
|
||||
iBound = rotated_bounds + iPos;
|
||||
#ifdef SPAWN_CORNERS
|
||||
// test:
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
Vector3 pos(iBound.corner(i));
|
||||
const_cast<GameObject&>(go).SummonCreature(1, pos.x, pos.y, pos.z, 0, TEMPSUMMON_MANUAL_DESPAWN);
|
||||
}
|
||||
#endif
|
||||
|
||||
owner = &go;
|
||||
return true;
|
||||
}
|
||||
|
||||
GameObjectModel* GameObjectModel::Create(const GameObject& go)
|
||||
{
|
||||
const GameObjectDisplayInfoEntry* info = sGameObjectDisplayInfoStore.LookupEntry(go.GetDisplayId());
|
||||
if (!info)
|
||||
return NULL;
|
||||
|
||||
GameObjectModel* mdl = new GameObjectModel();
|
||||
if (!mdl->initialize(go, *info))
|
||||
{
|
||||
delete mdl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mdl;
|
||||
}
|
||||
|
||||
bool GameObjectModel::intersectRay(const G3D::Ray& ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask) const
|
||||
{
|
||||
if (!(phasemask & ph_mask) || !owner->isSpawned())
|
||||
return false;
|
||||
|
||||
float time = ray.intersectionTime(iBound);
|
||||
if (time == G3D::inf())
|
||||
return false;
|
||||
|
||||
// child bounds are defined in object space:
|
||||
Vector3 p = iInvRot * (ray.origin() - iPos) * iInvScale;
|
||||
Ray modRay(p, iInvRot * ray.direction());
|
||||
float distance = MaxDist * iInvScale;
|
||||
bool hit = iModel->IntersectRay(modRay, distance, StopAtFirstHit);
|
||||
if (hit)
|
||||
{
|
||||
distance *= iScale;
|
||||
MaxDist = distance;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool GameObjectModel::UpdatePosition()
|
||||
{
|
||||
if (!iModel)
|
||||
return false;
|
||||
|
||||
ModelList::const_iterator it = model_list.find(owner->GetDisplayId());
|
||||
if (it == model_list.end())
|
||||
return false;
|
||||
|
||||
G3D::AABox mdl_box(it->second.bound);
|
||||
// ignore models with no bounds
|
||||
if (mdl_box == G3D::AABox::zero())
|
||||
{
|
||||
//VMAP_ERROR_LOG("misc", "GameObject model %s has zero bounds, loading skipped", it->second.name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
iPos = Vector3(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ());
|
||||
G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(owner->GetOrientation(), 0, 0);
|
||||
iInvRot = iRotation.inverse();
|
||||
// transform bounding box:
|
||||
mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale);
|
||||
AABox rotated_bounds;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
rotated_bounds.merge(iRotation * mdl_box.corner(i));
|
||||
|
||||
iBound = rotated_bounds + iPos;
|
||||
#ifdef SPAWN_CORNERS
|
||||
// test:
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
Vector3 pos(iBound.corner(i));
|
||||
owner->SummonCreature(1, pos.x, pos.y, pos.z, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 10000);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
74
src/server/collision/Models/GameObjectModel.h
Normal file
74
src/server/collision/Models/GameObjectModel.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _GAMEOBJECT_MODEL_H
|
||||
#define _GAMEOBJECT_MODEL_H
|
||||
|
||||
#include <G3D/Matrix3.h>
|
||||
#include <G3D/Vector3.h>
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Ray.h>
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class WorldModel;
|
||||
}
|
||||
|
||||
class GameObject;
|
||||
struct GameObjectDisplayInfoEntry;
|
||||
|
||||
class GameObjectModel /*, public Intersectable*/
|
||||
{
|
||||
uint32 phasemask;
|
||||
G3D::AABox iBound;
|
||||
G3D::Matrix3 iInvRot;
|
||||
G3D::Vector3 iPos;
|
||||
//G3D::Vector3 iRot;
|
||||
float iInvScale;
|
||||
float iScale;
|
||||
VMAP::WorldModel* iModel;
|
||||
GameObject const* owner;
|
||||
|
||||
GameObjectModel() : phasemask(0), iInvScale(0), iScale(0), iModel(NULL), owner(NULL) { }
|
||||
bool initialize(const GameObject& go, const GameObjectDisplayInfoEntry& info);
|
||||
|
||||
public:
|
||||
std::string name;
|
||||
|
||||
const G3D::AABox& getBounds() const { return iBound; }
|
||||
|
||||
~GameObjectModel();
|
||||
|
||||
const G3D::Vector3& getPosition() const { return iPos;}
|
||||
|
||||
/** Enables\disables collision. */
|
||||
void disable() { phasemask = 0;}
|
||||
void enable(uint32 ph_mask) { phasemask = ph_mask;}
|
||||
|
||||
bool isEnabled() const {return phasemask != 0;}
|
||||
|
||||
bool intersectRay(const G3D::Ray& Ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask) const;
|
||||
|
||||
static GameObjectModel* Create(const GameObject& go);
|
||||
|
||||
bool UpdatePosition();
|
||||
};
|
||||
|
||||
#endif // _GAMEOBJECT_MODEL_H
|
||||
223
src/server/collision/Models/ModelInstance.cpp
Normal file
223
src/server/collision/Models/ModelInstance.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "ModelInstance.h"
|
||||
#include "WorldModel.h"
|
||||
#include "MapTree.h"
|
||||
#include "VMapDefinitions.h"
|
||||
|
||||
using G3D::Vector3;
|
||||
using G3D::Ray;
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
ModelInstance::ModelInstance(const ModelSpawn &spawn, WorldModel* model): ModelSpawn(spawn), iModel(model)
|
||||
{
|
||||
iInvRot = G3D::Matrix3::fromEulerAnglesZYX(G3D::pi()*iRot.y/180.f, G3D::pi()*iRot.x/180.f, G3D::pi()*iRot.z/180.f).inverse();
|
||||
iInvScale = 1.f/iScale;
|
||||
}
|
||||
|
||||
bool ModelInstance::intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit) const
|
||||
{
|
||||
if (!iModel)
|
||||
{
|
||||
//std::cout << "<object not loaded>\n";
|
||||
return false;
|
||||
}
|
||||
float time = pRay.intersectionTime(iBound);
|
||||
if (time == G3D::inf())
|
||||
{
|
||||
// std::cout << "Ray does not hit '" << name << "'\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
// std::cout << "Ray crosses bound of '" << name << "'\n";
|
||||
/* std::cout << "ray from:" << pRay.origin().x << ", " << pRay.origin().y << ", " << pRay.origin().z
|
||||
<< " dir:" << pRay.direction().x << ", " << pRay.direction().y << ", " << pRay.direction().z
|
||||
<< " t/tmax:" << time << '/' << pMaxDist;
|
||||
std::cout << "\nBound lo:" << iBound.low().x << ", " << iBound.low().y << ", " << iBound.low().z << " hi: "
|
||||
<< iBound.high().x << ", " << iBound.high().y << ", " << iBound.high().z << std::endl; */
|
||||
// child bounds are defined in object space:
|
||||
Vector3 p = iInvRot * (pRay.origin() - iPos) * iInvScale;
|
||||
Ray modRay(p, iInvRot * pRay.direction());
|
||||
float distance = pMaxDist * iInvScale;
|
||||
bool hit = iModel->IntersectRay(modRay, distance, StopAtFirstHit);
|
||||
if (hit)
|
||||
{
|
||||
distance *= iScale;
|
||||
pMaxDist = distance;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
void ModelInstance::intersectPoint(const G3D::Vector3& p, AreaInfo &info) const
|
||||
{
|
||||
if (!iModel)
|
||||
{
|
||||
#ifdef VMAP_DEBUG
|
||||
std::cout << "<object not loaded>\n";
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// M2 files don't contain area info, only WMO files
|
||||
if (flags & MOD_M2)
|
||||
return;
|
||||
if (!iBound.contains(p))
|
||||
return;
|
||||
// child bounds are defined in object space:
|
||||
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
|
||||
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
if (iModel->IntersectPoint(pModel, zDirModel, zDist, info))
|
||||
{
|
||||
Vector3 modelGround = pModel + zDist * zDirModel;
|
||||
// Transform back to world space. Note that:
|
||||
// Mat * vec == vec * Mat.transpose()
|
||||
// and for rotation matrices: Mat.inverse() == Mat.transpose()
|
||||
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
|
||||
if (info.ground_Z < world_Z)
|
||||
{
|
||||
info.ground_Z = world_Z;
|
||||
info.adtId = adtId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelInstance::GetLocationInfo(const G3D::Vector3& p, LocationInfo &info) const
|
||||
{
|
||||
if (!iModel)
|
||||
{
|
||||
#ifdef VMAP_DEBUG
|
||||
std::cout << "<object not loaded>\n";
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// M2 files don't contain area info, only WMO files
|
||||
if (flags & MOD_M2)
|
||||
return false;
|
||||
if (!iBound.contains(p))
|
||||
return false;
|
||||
// child bounds are defined in object space:
|
||||
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
|
||||
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, info))
|
||||
{
|
||||
Vector3 modelGround = pModel + zDist * zDirModel;
|
||||
// Transform back to world space. Note that:
|
||||
// Mat * vec == vec * Mat.transpose()
|
||||
// and for rotation matrices: Mat.inverse() == Mat.transpose()
|
||||
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
|
||||
if (info.ground_Z < world_Z) // hm...could it be handled automatically with zDist at intersection?
|
||||
{
|
||||
info.ground_Z = world_Z;
|
||||
info.hitInstance = this;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelInstance::GetLiquidLevel(const G3D::Vector3& p, LocationInfo &info, float &liqHeight) const
|
||||
{
|
||||
// child bounds are defined in object space:
|
||||
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
|
||||
//Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
if (info.hitModel->GetLiquidLevel(pModel, zDist))
|
||||
{
|
||||
// calculate world height (zDist in model coords):
|
||||
// assume WMO not tilted (wouldn't make much sense anyway)
|
||||
liqHeight = zDist * iScale + iPos.z;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelSpawn::readFromFile(FILE* rf, ModelSpawn &spawn)
|
||||
{
|
||||
uint32 check = 0, nameLen;
|
||||
check += fread(&spawn.flags, sizeof(uint32), 1, rf);
|
||||
// EoF?
|
||||
if (!check)
|
||||
{
|
||||
if (ferror(rf))
|
||||
std::cout << "Error reading ModelSpawn!\n";
|
||||
return false;
|
||||
}
|
||||
check += fread(&spawn.adtId, sizeof(uint16), 1, rf);
|
||||
check += fread(&spawn.ID, sizeof(uint32), 1, rf);
|
||||
check += fread(&spawn.iPos, sizeof(float), 3, rf);
|
||||
check += fread(&spawn.iRot, sizeof(float), 3, rf);
|
||||
check += fread(&spawn.iScale, sizeof(float), 1, rf);
|
||||
bool has_bound = (spawn.flags & MOD_HAS_BOUND);
|
||||
if (has_bound) // only WMOs have bound in MPQ, only available after computation
|
||||
{
|
||||
Vector3 bLow, bHigh;
|
||||
check += fread(&bLow, sizeof(float), 3, rf);
|
||||
check += fread(&bHigh, sizeof(float), 3, rf);
|
||||
spawn.iBound = G3D::AABox(bLow, bHigh);
|
||||
}
|
||||
check += fread(&nameLen, sizeof(uint32), 1, rf);
|
||||
if (check != uint32(has_bound ? 17 : 11))
|
||||
{
|
||||
std::cout << "Error reading ModelSpawn!\n";
|
||||
return false;
|
||||
}
|
||||
char nameBuff[500];
|
||||
if (nameLen > 500) // file names should never be that long, must be file error
|
||||
{
|
||||
std::cout << "Error reading ModelSpawn, file name too long!\n";
|
||||
return false;
|
||||
}
|
||||
check = fread(nameBuff, sizeof(char), nameLen, rf);
|
||||
if (check != nameLen)
|
||||
{
|
||||
std::cout << "Error reading ModelSpawn!\n";
|
||||
return false;
|
||||
}
|
||||
spawn.name = std::string(nameBuff, nameLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModelSpawn::writeToFile(FILE* wf, const ModelSpawn &spawn)
|
||||
{
|
||||
uint32 check=0;
|
||||
check += fwrite(&spawn.flags, sizeof(uint32), 1, wf);
|
||||
check += fwrite(&spawn.adtId, sizeof(uint16), 1, wf);
|
||||
check += fwrite(&spawn.ID, sizeof(uint32), 1, wf);
|
||||
check += fwrite(&spawn.iPos, sizeof(float), 3, wf);
|
||||
check += fwrite(&spawn.iRot, sizeof(float), 3, wf);
|
||||
check += fwrite(&spawn.iScale, sizeof(float), 1, wf);
|
||||
bool has_bound = (spawn.flags & MOD_HAS_BOUND);
|
||||
if (has_bound) // only WMOs have bound in MPQ, only available after computation
|
||||
{
|
||||
check += fwrite(&spawn.iBound.low(), sizeof(float), 3, wf);
|
||||
check += fwrite(&spawn.iBound.high(), sizeof(float), 3, wf);
|
||||
}
|
||||
uint32 nameLen = spawn.name.length();
|
||||
check += fwrite(&nameLen, sizeof(uint32), 1, wf);
|
||||
if (check != uint32(has_bound ? 17 : 11)) return false;
|
||||
check = fwrite(spawn.name.c_str(), sizeof(char), nameLen, wf);
|
||||
if (check != nameLen) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
82
src/server/collision/Models/ModelInstance.h
Normal file
82
src/server/collision/Models/ModelInstance.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _MODELINSTANCE_H_
|
||||
#define _MODELINSTANCE_H_
|
||||
|
||||
#include <G3D/Matrix3.h>
|
||||
#include <G3D/Vector3.h>
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Ray.h>
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class WorldModel;
|
||||
struct AreaInfo;
|
||||
struct LocationInfo;
|
||||
|
||||
enum ModelFlags
|
||||
{
|
||||
MOD_M2 = 1,
|
||||
MOD_WORLDSPAWN = 1<<1,
|
||||
MOD_HAS_BOUND = 1<<2
|
||||
};
|
||||
|
||||
class ModelSpawn
|
||||
{
|
||||
public:
|
||||
//mapID, tileX, tileY, Flags, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name
|
||||
uint32 flags;
|
||||
uint16 adtId;
|
||||
uint32 ID;
|
||||
G3D::Vector3 iPos;
|
||||
G3D::Vector3 iRot;
|
||||
float iScale;
|
||||
G3D::AABox iBound;
|
||||
std::string name;
|
||||
bool operator==(const ModelSpawn &other) const { return ID == other.ID; }
|
||||
//uint32 hashCode() const { return ID; }
|
||||
// temp?
|
||||
const G3D::AABox& getBounds() const { return iBound; }
|
||||
|
||||
static bool readFromFile(FILE* rf, ModelSpawn &spawn);
|
||||
static bool writeToFile(FILE* rw, const ModelSpawn &spawn);
|
||||
};
|
||||
|
||||
class ModelInstance: public ModelSpawn
|
||||
{
|
||||
public:
|
||||
ModelInstance(): iInvScale(0.0f), iModel(0) { }
|
||||
ModelInstance(const ModelSpawn &spawn, WorldModel* model);
|
||||
void setUnloaded() { iModel = 0; }
|
||||
bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit) const;
|
||||
void intersectPoint(const G3D::Vector3& p, AreaInfo &info) const;
|
||||
bool GetLocationInfo(const G3D::Vector3& p, LocationInfo &info) const;
|
||||
bool GetLiquidLevel(const G3D::Vector3& p, LocationInfo &info, float &liqHeight) const;
|
||||
protected:
|
||||
G3D::Matrix3 iInvRot;
|
||||
float iInvScale;
|
||||
WorldModel* iModel;
|
||||
public:
|
||||
WorldModel* getWorldModel();
|
||||
};
|
||||
} // namespace VMAP
|
||||
|
||||
#endif // _MODELINSTANCE
|
||||
586
src/server/collision/Models/WorldModel.cpp
Normal file
586
src/server/collision/Models/WorldModel.cpp
Normal file
@@ -0,0 +1,586 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "WorldModel.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include "MapTree.h"
|
||||
|
||||
using G3D::Vector3;
|
||||
using G3D::Ray;
|
||||
|
||||
template<> struct BoundsTrait<VMAP::GroupModel>
|
||||
{
|
||||
static void getBounds(const VMAP::GroupModel& obj, G3D::AABox& out) { out = obj.GetBound(); }
|
||||
};
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
bool IntersectTriangle(const MeshTriangle &tri, std::vector<Vector3>::const_iterator points, const G3D::Ray &ray, float &distance)
|
||||
{
|
||||
static const float EPS = 1e-5f;
|
||||
|
||||
// See RTR2 ch. 13.7 for the algorithm.
|
||||
|
||||
const Vector3 e1 = points[tri.idx1] - points[tri.idx0];
|
||||
const Vector3 e2 = points[tri.idx2] - points[tri.idx0];
|
||||
const Vector3 p(ray.direction().cross(e2));
|
||||
const float a = e1.dot(p);
|
||||
|
||||
if (fabs(a) < EPS) {
|
||||
// Determinant is ill-conditioned; abort early
|
||||
return false;
|
||||
}
|
||||
|
||||
const float f = 1.0f / a;
|
||||
const Vector3 s(ray.origin() - points[tri.idx0]);
|
||||
const float u = f * s.dot(p);
|
||||
|
||||
if ((u < 0.0f) || (u > 1.0f)) {
|
||||
// We hit the plane of the m_geometry, but outside the m_geometry
|
||||
return false;
|
||||
}
|
||||
|
||||
const Vector3 q(s.cross(e1));
|
||||
const float v = f * ray.direction().dot(q);
|
||||
|
||||
if ((v < 0.0f) || ((u + v) > 1.0f)) {
|
||||
// We hit the plane of the triangle, but outside the triangle
|
||||
return false;
|
||||
}
|
||||
|
||||
const float t = f * e2.dot(q);
|
||||
|
||||
if ((t > 0.0f) && (t < distance))
|
||||
{
|
||||
// This is a new hit, closer than the previous one
|
||||
distance = t;
|
||||
|
||||
/* baryCoord[0] = 1.0 - u - v;
|
||||
baryCoord[1] = u;
|
||||
baryCoord[2] = v; */
|
||||
|
||||
return true;
|
||||
}
|
||||
// This hit is after the previous hit, so ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
class TriBoundFunc
|
||||
{
|
||||
public:
|
||||
TriBoundFunc(std::vector<Vector3> &vert): vertices(vert.begin()) { }
|
||||
void operator()(const MeshTriangle &tri, G3D::AABox &out) const
|
||||
{
|
||||
G3D::Vector3 lo = vertices[tri.idx0];
|
||||
G3D::Vector3 hi = lo;
|
||||
|
||||
lo = (lo.min(vertices[tri.idx1])).min(vertices[tri.idx2]);
|
||||
hi = (hi.max(vertices[tri.idx1])).max(vertices[tri.idx2]);
|
||||
|
||||
out = G3D::AABox(lo, hi);
|
||||
}
|
||||
protected:
|
||||
const std::vector<Vector3>::const_iterator vertices;
|
||||
};
|
||||
|
||||
// ===================== WmoLiquid ==================================
|
||||
|
||||
WmoLiquid::WmoLiquid(uint32 width, uint32 height, const Vector3 &corner, uint32 type):
|
||||
iTilesX(width), iTilesY(height), iCorner(corner), iType(type)
|
||||
{
|
||||
iHeight = new float[(width+1)*(height+1)];
|
||||
iFlags = new uint8[width*height];
|
||||
}
|
||||
|
||||
WmoLiquid::WmoLiquid(const WmoLiquid &other): iHeight(0), iFlags(0)
|
||||
{
|
||||
*this = other; // use assignment operator...
|
||||
}
|
||||
|
||||
WmoLiquid::~WmoLiquid()
|
||||
{
|
||||
delete[] iHeight;
|
||||
delete[] iFlags;
|
||||
}
|
||||
|
||||
WmoLiquid& WmoLiquid::operator=(const WmoLiquid &other)
|
||||
{
|
||||
if (this == &other)
|
||||
return *this;
|
||||
iTilesX = other.iTilesX;
|
||||
iTilesY = other.iTilesY;
|
||||
iCorner = other.iCorner;
|
||||
iType = other.iType;
|
||||
delete iHeight;
|
||||
delete iFlags;
|
||||
if (other.iHeight)
|
||||
{
|
||||
iHeight = new float[(iTilesX+1)*(iTilesY+1)];
|
||||
memcpy(iHeight, other.iHeight, (iTilesX+1)*(iTilesY+1)*sizeof(float));
|
||||
}
|
||||
else
|
||||
iHeight = 0;
|
||||
if (other.iFlags)
|
||||
{
|
||||
iFlags = new uint8[iTilesX * iTilesY];
|
||||
memcpy(iFlags, other.iFlags, iTilesX * iTilesY);
|
||||
}
|
||||
else
|
||||
iFlags = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool WmoLiquid::GetLiquidHeight(const Vector3 &pos, float &liqHeight) const
|
||||
{
|
||||
float tx_f = (pos.x - iCorner.x)/LIQUID_TILE_SIZE;
|
||||
uint32 tx = uint32(tx_f);
|
||||
if (tx_f < 0.0f || tx >= iTilesX)
|
||||
return false;
|
||||
float ty_f = (pos.y - iCorner.y)/LIQUID_TILE_SIZE;
|
||||
uint32 ty = uint32(ty_f);
|
||||
if (ty_f < 0.0f || ty >= iTilesY)
|
||||
return false;
|
||||
|
||||
// check if tile shall be used for liquid level
|
||||
// checking for 0x08 *might* be enough, but disabled tiles always are 0x?F:
|
||||
if ((iFlags[tx + ty*iTilesX] & 0x0F) == 0x0F)
|
||||
return false;
|
||||
|
||||
// (dx, dy) coordinates inside tile, in [0, 1]^2
|
||||
float dx = tx_f - (float)tx;
|
||||
float dy = ty_f - (float)ty;
|
||||
|
||||
/* Tesselate tile to two triangles (not sure if client does it exactly like this)
|
||||
|
||||
^ dy
|
||||
|
|
||||
1 x---------x (1, 1)
|
||||
| (b) / |
|
||||
| / |
|
||||
| / |
|
||||
| / (a) |
|
||||
x---------x---> dx
|
||||
0 1
|
||||
*/
|
||||
|
||||
const uint32 rowOffset = iTilesX + 1;
|
||||
if (dx > dy) // case (a)
|
||||
{
|
||||
float sx = iHeight[tx+1 + ty * rowOffset] - iHeight[tx + ty * rowOffset];
|
||||
float sy = iHeight[tx+1 + (ty+1) * rowOffset] - iHeight[tx+1 + ty * rowOffset];
|
||||
liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy;
|
||||
}
|
||||
else // case (b)
|
||||
{
|
||||
float sx = iHeight[tx+1 + (ty+1) * rowOffset] - iHeight[tx + (ty+1) * rowOffset];
|
||||
float sy = iHeight[tx + (ty+1) * rowOffset] - iHeight[tx + ty * rowOffset];
|
||||
liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 WmoLiquid::GetFileSize()
|
||||
{
|
||||
return 2 * sizeof(uint32) +
|
||||
sizeof(Vector3) +
|
||||
(iTilesX + 1)*(iTilesY + 1) * sizeof(float) +
|
||||
iTilesX * iTilesY;
|
||||
}
|
||||
|
||||
bool WmoLiquid::writeToFile(FILE* wf)
|
||||
{
|
||||
bool result = false;
|
||||
if (fwrite(&iTilesX, sizeof(uint32), 1, wf) == 1 &&
|
||||
fwrite(&iTilesY, sizeof(uint32), 1, wf) == 1 &&
|
||||
fwrite(&iCorner, sizeof(Vector3), 1, wf) == 1 &&
|
||||
fwrite(&iType, sizeof(uint32), 1, wf) == 1)
|
||||
{
|
||||
uint32 size = (iTilesX + 1) * (iTilesY + 1);
|
||||
if (fwrite(iHeight, sizeof(float), size, wf) == size)
|
||||
{
|
||||
size = iTilesX*iTilesY;
|
||||
result = fwrite(iFlags, sizeof(uint8), size, wf) == size;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WmoLiquid::readFromFile(FILE* rf, WmoLiquid* &out)
|
||||
{
|
||||
bool result = false;
|
||||
WmoLiquid* liquid = new WmoLiquid();
|
||||
|
||||
if (fread(&liquid->iTilesX, sizeof(uint32), 1, rf) == 1 &&
|
||||
fread(&liquid->iTilesY, sizeof(uint32), 1, rf) == 1 &&
|
||||
fread(&liquid->iCorner, sizeof(Vector3), 1, rf) == 1 &&
|
||||
fread(&liquid->iType, sizeof(uint32), 1, rf) == 1)
|
||||
{
|
||||
uint32 size = (liquid->iTilesX + 1) * (liquid->iTilesY + 1);
|
||||
liquid->iHeight = new float[size];
|
||||
if (fread(liquid->iHeight, sizeof(float), size, rf) == size)
|
||||
{
|
||||
size = liquid->iTilesX * liquid->iTilesY;
|
||||
liquid->iFlags = new uint8[size];
|
||||
result = fread(liquid->iFlags, sizeof(uint8), size, rf) == size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result)
|
||||
delete liquid;
|
||||
else
|
||||
out = liquid;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===================== GroupModel ==================================
|
||||
|
||||
GroupModel::GroupModel(const GroupModel &other):
|
||||
iBound(other.iBound), iMogpFlags(other.iMogpFlags), iGroupWMOID(other.iGroupWMOID),
|
||||
vertices(other.vertices), triangles(other.triangles), meshTree(other.meshTree), iLiquid(0)
|
||||
{
|
||||
if (other.iLiquid)
|
||||
iLiquid = new WmoLiquid(*other.iLiquid);
|
||||
}
|
||||
|
||||
void GroupModel::setMeshData(std::vector<Vector3> &vert, std::vector<MeshTriangle> &tri)
|
||||
{
|
||||
vertices.swap(vert);
|
||||
triangles.swap(tri);
|
||||
TriBoundFunc bFunc(vertices);
|
||||
meshTree.build(triangles, bFunc);
|
||||
}
|
||||
|
||||
bool GroupModel::writeToFile(FILE* wf)
|
||||
{
|
||||
bool result = true;
|
||||
uint32 chunkSize, count;
|
||||
|
||||
if (result && fwrite(&iBound, sizeof(G3D::AABox), 1, wf) != 1) result = false;
|
||||
if (result && fwrite(&iMogpFlags, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (result && fwrite(&iGroupWMOID, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
|
||||
// write vertices
|
||||
if (result && fwrite("VERT", 1, 4, wf) != 4) result = false;
|
||||
count = vertices.size();
|
||||
chunkSize = sizeof(uint32)+ sizeof(Vector3)*count;
|
||||
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (!count) // models without (collision) geometry end here, unsure if they are useful
|
||||
return result;
|
||||
if (result && fwrite(&vertices[0], sizeof(Vector3), count, wf) != count) result = false;
|
||||
|
||||
// write triangle mesh
|
||||
if (result && fwrite("TRIM", 1, 4, wf) != 4) result = false;
|
||||
count = triangles.size();
|
||||
chunkSize = sizeof(uint32)+ sizeof(MeshTriangle)*count;
|
||||
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (result && fwrite(&triangles[0], sizeof(MeshTriangle), count, wf) != count) result = false;
|
||||
|
||||
// write mesh BIH
|
||||
if (result && fwrite("MBIH", 1, 4, wf) != 4) result = false;
|
||||
if (result) result = meshTree.writeToFile(wf);
|
||||
|
||||
// write liquid data
|
||||
if (result && fwrite("LIQU", 1, 4, wf) != 4) result = false;
|
||||
if (!iLiquid)
|
||||
{
|
||||
chunkSize = 0;
|
||||
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
return result;
|
||||
}
|
||||
chunkSize = iLiquid->GetFileSize();
|
||||
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (result) result = iLiquid->writeToFile(wf);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GroupModel::readFromFile(FILE* rf)
|
||||
{
|
||||
char chunk[8];
|
||||
bool result = true;
|
||||
uint32 chunkSize = 0;
|
||||
uint32 count = 0;
|
||||
triangles.clear();
|
||||
vertices.clear();
|
||||
delete iLiquid;
|
||||
iLiquid = NULL;
|
||||
|
||||
if (result && fread(&iBound, sizeof(G3D::AABox), 1, rf) != 1) result = false;
|
||||
if (result && fread(&iMogpFlags, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (result && fread(&iGroupWMOID, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
|
||||
// read vertices
|
||||
if (result && !readChunk(rf, chunk, "VERT", 4)) result = false;
|
||||
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (!count) // models without (collision) geometry end here, unsure if they are useful
|
||||
return result;
|
||||
if (result) vertices.resize(count);
|
||||
if (result && fread(&vertices[0], sizeof(Vector3), count, rf) != count) result = false;
|
||||
|
||||
// read triangle mesh
|
||||
if (result && !readChunk(rf, chunk, "TRIM", 4)) result = false;
|
||||
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (result) triangles.resize(count);
|
||||
if (result && fread(&triangles[0], sizeof(MeshTriangle), count, rf) != count) result = false;
|
||||
|
||||
// read mesh BIH
|
||||
if (result && !readChunk(rf, chunk, "MBIH", 4)) result = false;
|
||||
if (result) result = meshTree.readFromFile(rf);
|
||||
|
||||
// write liquid data
|
||||
if (result && !readChunk(rf, chunk, "LIQU", 4)) result = false;
|
||||
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (result && chunkSize > 0)
|
||||
result = WmoLiquid::readFromFile(rf, iLiquid);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct GModelRayCallback
|
||||
{
|
||||
GModelRayCallback(const std::vector<MeshTriangle> &tris, const std::vector<Vector3> &vert):
|
||||
vertices(vert.begin()), triangles(tris.begin()), hit(false) { }
|
||||
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool /*StopAtFirstHit*/)
|
||||
{
|
||||
bool result = IntersectTriangle(triangles[entry], vertices, ray, distance);
|
||||
if (result) hit=true;
|
||||
return hit;
|
||||
}
|
||||
std::vector<Vector3>::const_iterator vertices;
|
||||
std::vector<MeshTriangle>::const_iterator triangles;
|
||||
bool hit;
|
||||
};
|
||||
|
||||
bool GroupModel::IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const
|
||||
{
|
||||
if (triangles.empty())
|
||||
return false;
|
||||
|
||||
GModelRayCallback callback(triangles, vertices);
|
||||
meshTree.intersectRay(ray, callback, distance, stopAtFirstHit);
|
||||
return callback.hit;
|
||||
}
|
||||
|
||||
bool GroupModel::IsInsideObject(const Vector3 &pos, const Vector3 &down, float &z_dist) const
|
||||
{
|
||||
if (triangles.empty() || !iBound.contains(pos))
|
||||
return false;
|
||||
GModelRayCallback callback(triangles, vertices);
|
||||
Vector3 rPos = pos - 0.1f * down;
|
||||
float dist = G3D::inf();
|
||||
G3D::Ray ray(rPos, down);
|
||||
bool hit = IntersectRay(ray, dist, false);
|
||||
if (hit)
|
||||
z_dist = dist - 0.1f;
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool GroupModel::GetLiquidLevel(const Vector3 &pos, float &liqHeight) const
|
||||
{
|
||||
if (iLiquid)
|
||||
return iLiquid->GetLiquidHeight(pos, liqHeight);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 GroupModel::GetLiquidType() const
|
||||
{
|
||||
if (iLiquid)
|
||||
return iLiquid->GetType();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===================== WorldModel ==================================
|
||||
|
||||
void WorldModel::setGroupModels(std::vector<GroupModel> &models)
|
||||
{
|
||||
groupModels.swap(models);
|
||||
groupTree.build(groupModels, BoundsTrait<GroupModel>::getBounds, 1);
|
||||
}
|
||||
|
||||
struct WModelRayCallBack
|
||||
{
|
||||
WModelRayCallBack(const std::vector<GroupModel> &mod): models(mod.begin()), hit(false) { }
|
||||
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool StopAtFirstHit)
|
||||
{
|
||||
bool result = models[entry].IntersectRay(ray, distance, StopAtFirstHit);
|
||||
if (result) hit=true;
|
||||
return hit;
|
||||
}
|
||||
std::vector<GroupModel>::const_iterator models;
|
||||
bool hit;
|
||||
};
|
||||
|
||||
bool WorldModel::IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const
|
||||
{
|
||||
// small M2 workaround, maybe better make separate class with virtual intersection funcs
|
||||
// in any case, there's no need to use a bound tree if we only have one submodel
|
||||
if (groupModels.size() == 1)
|
||||
return groupModels[0].IntersectRay(ray, distance, stopAtFirstHit);
|
||||
|
||||
WModelRayCallBack isc(groupModels);
|
||||
groupTree.intersectRay(ray, isc, distance, stopAtFirstHit);
|
||||
return isc.hit;
|
||||
}
|
||||
|
||||
class WModelAreaCallback {
|
||||
public:
|
||||
WModelAreaCallback(const std::vector<GroupModel> &vals, const Vector3 &down):
|
||||
prims(vals.begin()), hit(vals.end()), minVol(G3D::inf()), zDist(G3D::inf()), zVec(down) { }
|
||||
std::vector<GroupModel>::const_iterator prims;
|
||||
std::vector<GroupModel>::const_iterator hit;
|
||||
float minVol;
|
||||
float zDist;
|
||||
Vector3 zVec;
|
||||
void operator()(const Vector3& point, uint32 entry)
|
||||
{
|
||||
float group_Z;
|
||||
//float pVol = prims[entry].GetBound().volume();
|
||||
//if (pVol < minVol)
|
||||
//{
|
||||
/* if (prims[entry].iBound.contains(point)) */
|
||||
if (prims[entry].IsInsideObject(point, zVec, group_Z))
|
||||
{
|
||||
//minVol = pVol;
|
||||
//hit = prims + entry;
|
||||
if (group_Z < zDist)
|
||||
{
|
||||
zDist = group_Z;
|
||||
hit = prims + entry;
|
||||
}
|
||||
#ifdef VMAP_DEBUG
|
||||
const GroupModel &gm = prims[entry];
|
||||
printf("%10u %8X %7.3f, %7.3f, %7.3f | %7.3f, %7.3f, %7.3f | z=%f, p_z=%f\n", gm.GetWmoID(), gm.GetMogpFlags(),
|
||||
gm.GetBound().low().x, gm.GetBound().low().y, gm.GetBound().low().z,
|
||||
gm.GetBound().high().x, gm.GetBound().high().y, gm.GetBound().high().z, group_Z, point.z);
|
||||
#endif
|
||||
}
|
||||
//}
|
||||
//std::cout << "trying to intersect '" << prims[entry].name << "'\n";
|
||||
}
|
||||
};
|
||||
|
||||
bool WorldModel::IntersectPoint(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, AreaInfo &info) const
|
||||
{
|
||||
if (groupModels.empty())
|
||||
return false;
|
||||
|
||||
WModelAreaCallback callback(groupModels, down);
|
||||
groupTree.intersectPoint(p, callback);
|
||||
if (callback.hit != groupModels.end())
|
||||
{
|
||||
info.rootId = RootWMOID;
|
||||
info.groupId = callback.hit->GetWmoID();
|
||||
info.flags = callback.hit->GetMogpFlags();
|
||||
info.result = true;
|
||||
dist = callback.zDist;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldModel::GetLocationInfo(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, LocationInfo &info) const
|
||||
{
|
||||
if (groupModels.empty())
|
||||
return false;
|
||||
|
||||
WModelAreaCallback callback(groupModels, down);
|
||||
groupTree.intersectPoint(p, callback);
|
||||
if (callback.hit != groupModels.end())
|
||||
{
|
||||
info.hitModel = &(*callback.hit);
|
||||
dist = callback.zDist;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldModel::writeFile(const std::string &filename)
|
||||
{
|
||||
FILE* wf = fopen(filename.c_str(), "wb");
|
||||
if (!wf)
|
||||
return false;
|
||||
|
||||
uint32 chunkSize, count;
|
||||
bool result = fwrite(VMAP_MAGIC, 1, 8, wf) == 8;
|
||||
if (result && fwrite("WMOD", 1, 4, wf) != 4) result = false;
|
||||
chunkSize = sizeof(uint32) + sizeof(uint32);
|
||||
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (result && fwrite(&RootWMOID, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
|
||||
// write group models
|
||||
count=groupModels.size();
|
||||
if (count)
|
||||
{
|
||||
if (result && fwrite("GMOD", 1, 4, wf) != 4) result = false;
|
||||
//chunkSize = sizeof(uint32)+ sizeof(GroupModel)*count;
|
||||
//if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false;
|
||||
for (uint32 i=0; i<groupModels.size() && result; ++i)
|
||||
result = groupModels[i].writeToFile(wf);
|
||||
|
||||
// write group BIH
|
||||
if (result && fwrite("GBIH", 1, 4, wf) != 4) result = false;
|
||||
if (result) result = groupTree.writeToFile(wf);
|
||||
}
|
||||
|
||||
fclose(wf);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WorldModel::readFile(const std::string &filename)
|
||||
{
|
||||
FILE* rf = fopen(filename.c_str(), "rb");
|
||||
if (!rf)
|
||||
return false;
|
||||
|
||||
bool result = true;
|
||||
uint32 chunkSize = 0;
|
||||
uint32 count = 0;
|
||||
char chunk[8]; // Ignore the added magic header
|
||||
if (!readChunk(rf, chunk, VMAP_MAGIC, 8)) result = false;
|
||||
|
||||
if (result && !readChunk(rf, chunk, "WMOD", 4)) result = false;
|
||||
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (result && fread(&RootWMOID, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
|
||||
// read group models
|
||||
if (result && readChunk(rf, chunk, "GMOD", 4))
|
||||
{
|
||||
//if (fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
|
||||
if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false;
|
||||
if (result) groupModels.resize(count);
|
||||
//if (result && fread(&groupModels[0], sizeof(GroupModel), count, rf) != count) result = false;
|
||||
for (uint32 i=0; i<count && result; ++i)
|
||||
result = groupModels[i].readFromFile(rf);
|
||||
|
||||
// read group BIH
|
||||
if (result && !readChunk(rf, chunk, "GBIH", 4)) result = false;
|
||||
if (result) result = groupTree.readFromFile(rf);
|
||||
}
|
||||
|
||||
fclose(rf);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
129
src/server/collision/Models/WorldModel.h
Normal file
129
src/server/collision/Models/WorldModel.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _WORLDMODEL_H
|
||||
#define _WORLDMODEL_H
|
||||
|
||||
#include <G3D/HashTrait.h>
|
||||
#include <G3D/Vector3.h>
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class TreeNode;
|
||||
struct AreaInfo;
|
||||
struct LocationInfo;
|
||||
|
||||
class MeshTriangle
|
||||
{
|
||||
public:
|
||||
MeshTriangle() : idx0(0), idx1(0), idx2(0) { }
|
||||
MeshTriangle(uint32 na, uint32 nb, uint32 nc): idx0(na), idx1(nb), idx2(nc) { }
|
||||
|
||||
uint32 idx0;
|
||||
uint32 idx1;
|
||||
uint32 idx2;
|
||||
};
|
||||
|
||||
class WmoLiquid
|
||||
{
|
||||
public:
|
||||
WmoLiquid(uint32 width, uint32 height, const G3D::Vector3 &corner, uint32 type);
|
||||
WmoLiquid(const WmoLiquid &other);
|
||||
~WmoLiquid();
|
||||
WmoLiquid& operator=(const WmoLiquid &other);
|
||||
bool GetLiquidHeight(const G3D::Vector3 &pos, float &liqHeight) const;
|
||||
uint32 GetType() const { return iType; }
|
||||
float *GetHeightStorage() { return iHeight; }
|
||||
uint8 *GetFlagsStorage() { return iFlags; }
|
||||
uint32 GetFileSize();
|
||||
bool writeToFile(FILE* wf);
|
||||
static bool readFromFile(FILE* rf, WmoLiquid* &liquid);
|
||||
private:
|
||||
WmoLiquid(): iTilesX(0), iTilesY(0), iType(0), iHeight(0), iFlags(0) { }
|
||||
uint32 iTilesX; //!< number of tiles in x direction, each
|
||||
uint32 iTilesY;
|
||||
G3D::Vector3 iCorner; //!< the lower corner
|
||||
uint32 iType; //!< liquid type
|
||||
float *iHeight; //!< (tilesX + 1)*(tilesY + 1) height values
|
||||
uint8 *iFlags; //!< info if liquid tile is used
|
||||
public:
|
||||
void getPosInfo(uint32 &tilesX, uint32 &tilesY, G3D::Vector3 &corner) const;
|
||||
};
|
||||
|
||||
/*! holding additional info for WMO group files */
|
||||
class GroupModel
|
||||
{
|
||||
public:
|
||||
GroupModel(): iMogpFlags(0), iGroupWMOID(0), iLiquid(0) { }
|
||||
GroupModel(const GroupModel &other);
|
||||
GroupModel(uint32 mogpFlags, uint32 groupWMOID, const G3D::AABox &bound):
|
||||
iBound(bound), iMogpFlags(mogpFlags), iGroupWMOID(groupWMOID), iLiquid(0) { }
|
||||
~GroupModel() { delete iLiquid; }
|
||||
|
||||
//! pass mesh data to object and create BIH. Passed vectors get get swapped with old geometry!
|
||||
void setMeshData(std::vector<G3D::Vector3> &vert, std::vector<MeshTriangle> &tri);
|
||||
void setLiquidData(WmoLiquid*& liquid) { iLiquid = liquid; liquid = NULL; }
|
||||
bool IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const;
|
||||
bool IsInsideObject(const G3D::Vector3 &pos, const G3D::Vector3 &down, float &z_dist) const;
|
||||
bool GetLiquidLevel(const G3D::Vector3 &pos, float &liqHeight) const;
|
||||
uint32 GetLiquidType() const;
|
||||
bool writeToFile(FILE* wf);
|
||||
bool readFromFile(FILE* rf);
|
||||
const G3D::AABox& GetBound() const { return iBound; }
|
||||
uint32 GetMogpFlags() const { return iMogpFlags; }
|
||||
uint32 GetWmoID() const { return iGroupWMOID; }
|
||||
protected:
|
||||
G3D::AABox iBound;
|
||||
uint32 iMogpFlags;// 0x8 outdor; 0x2000 indoor
|
||||
uint32 iGroupWMOID;
|
||||
std::vector<G3D::Vector3> vertices;
|
||||
std::vector<MeshTriangle> triangles;
|
||||
BIH meshTree;
|
||||
WmoLiquid* iLiquid;
|
||||
public:
|
||||
void getMeshData(std::vector<G3D::Vector3> &vertices, std::vector<MeshTriangle> &triangles, WmoLiquid* &liquid);
|
||||
};
|
||||
/*! Holds a model (converted M2 or WMO) in its original coordinate space */
|
||||
class WorldModel
|
||||
{
|
||||
public:
|
||||
WorldModel(): RootWMOID(0) { }
|
||||
|
||||
//! pass group models to WorldModel and create BIH. Passed vector is swapped with old geometry!
|
||||
void setGroupModels(std::vector<GroupModel> &models);
|
||||
void setRootWmoID(uint32 id) { RootWMOID = id; }
|
||||
bool IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const;
|
||||
bool IntersectPoint(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, AreaInfo &info) const;
|
||||
bool GetLocationInfo(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, LocationInfo &info) const;
|
||||
bool writeFile(const std::string &filename);
|
||||
bool readFile(const std::string &filename);
|
||||
protected:
|
||||
uint32 RootWMOID;
|
||||
std::vector<GroupModel> groupModels;
|
||||
BIH groupTree;
|
||||
public:
|
||||
void getGroupModels(std::vector<GroupModel> &groupModels);
|
||||
};
|
||||
} // namespace VMAP
|
||||
|
||||
#endif // _WORLDMODEL_H
|
||||
1
src/server/collision/PrecompiledHeaders/collisionPCH.cpp
Normal file
1
src/server/collision/PrecompiledHeaders/collisionPCH.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "collisionPCH.h"
|
||||
9
src/server/collision/PrecompiledHeaders/collisionPCH.h
Normal file
9
src/server/collision/PrecompiledHeaders/collisionPCH.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "Define.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include "MapTree.h"
|
||||
#include "WorldModel.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
#include "RegularGrid.h"
|
||||
#include "BoundingIntervalHierarchyWrapper.h"
|
||||
#include "GameObjectModel.h"
|
||||
264
src/server/collision/RegularGrid.h
Normal file
264
src/server/collision/RegularGrid.h
Normal file
@@ -0,0 +1,264 @@
|
||||
#ifndef _REGULAR_GRID_H
|
||||
#define _REGULAR_GRID_H
|
||||
|
||||
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Table.h>
|
||||
#include <G3D/BoundsTrait.h>
|
||||
#include <G3D/PositionTrait.h>
|
||||
|
||||
#include "Errors.h"
|
||||
|
||||
template <class Node>
|
||||
class NodeArray
|
||||
{
|
||||
public:
|
||||
explicit NodeArray() { memset(&_nodes, 0, sizeof(_nodes)); }
|
||||
void AddNode(Node* n)
|
||||
{
|
||||
for (uint8 i=0; i<9; ++i)
|
||||
if (_nodes[i] == 0)
|
||||
{
|
||||
_nodes[i] = n;
|
||||
return;
|
||||
}
|
||||
else if (_nodes[i] == n)
|
||||
return;
|
||||
}
|
||||
Node* _nodes[9];
|
||||
};
|
||||
|
||||
template<class Node>
|
||||
struct NodeCreator{
|
||||
static Node * makeNode(int /*x*/, int /*y*/) { return new Node();}
|
||||
};
|
||||
|
||||
template<class T,
|
||||
class Node,
|
||||
class NodeCreatorFunc = NodeCreator<Node>,
|
||||
/*class BoundsFunc = BoundsTrait<T>,*/
|
||||
class PositionFunc = PositionTrait<T>
|
||||
>
|
||||
class RegularGrid2D
|
||||
{
|
||||
public:
|
||||
|
||||
enum{
|
||||
CELL_NUMBER = 64,
|
||||
};
|
||||
|
||||
#define HGRID_MAP_SIZE (533.33333f * 64.f) // shouldn't be changed
|
||||
#define CELL_SIZE float(HGRID_MAP_SIZE/(float)CELL_NUMBER)
|
||||
|
||||
typedef G3D::Table<const T*, NodeArray<Node> > MemberTable;
|
||||
|
||||
MemberTable memberTable;
|
||||
Node* nodes[CELL_NUMBER][CELL_NUMBER];
|
||||
|
||||
RegularGrid2D(){
|
||||
memset(nodes, 0, sizeof(nodes));
|
||||
}
|
||||
|
||||
~RegularGrid2D(){
|
||||
for (int x = 0; x < CELL_NUMBER; ++x)
|
||||
for (int y = 0; y < CELL_NUMBER; ++y)
|
||||
delete nodes[x][y];
|
||||
}
|
||||
|
||||
void insert(const T& value)
|
||||
{
|
||||
G3D::Vector3 pos[9];
|
||||
pos[0] = value.getBounds().corner(0);
|
||||
pos[1] = value.getBounds().corner(1);
|
||||
pos[2] = value.getBounds().corner(2);
|
||||
pos[3] = value.getBounds().corner(3);
|
||||
pos[4] = (pos[0] + pos[1])/2.0f;
|
||||
pos[5] = (pos[1] + pos[2])/2.0f;
|
||||
pos[6] = (pos[2] + pos[3])/2.0f;
|
||||
pos[7] = (pos[3] + pos[0])/2.0f;
|
||||
pos[8] = (pos[0] + pos[2])/2.0f;
|
||||
|
||||
NodeArray<Node> na;
|
||||
for (uint8 i=0; i<9; ++i)
|
||||
{
|
||||
Cell c = Cell::ComputeCell(pos[i].x, pos[i].y);
|
||||
if (!c.isValid())
|
||||
continue;
|
||||
Node& node = getGridFor(pos[i].x, pos[i].y);
|
||||
na.AddNode(&node);
|
||||
}
|
||||
|
||||
for (uint8 i=0; i<9; ++i)
|
||||
{
|
||||
if (na._nodes[i])
|
||||
na._nodes[i]->insert(value);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
memberTable.set(&value, na);
|
||||
}
|
||||
|
||||
void remove(const T& value)
|
||||
{
|
||||
NodeArray<Node>& na = memberTable[&value];
|
||||
for (uint8 i=0; i<9; ++i)
|
||||
{
|
||||
if (na._nodes[i])
|
||||
na._nodes[i]->remove(value);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove the member
|
||||
memberTable.remove(&value);
|
||||
}
|
||||
|
||||
void balance()
|
||||
{
|
||||
for (int x = 0; x < CELL_NUMBER; ++x)
|
||||
for (int y = 0; y < CELL_NUMBER; ++y)
|
||||
if (Node* n = nodes[x][y])
|
||||
n->balance();
|
||||
}
|
||||
|
||||
bool contains(const T& value) const { return memberTable.containsKey(&value); }
|
||||
int size() const { return memberTable.size(); }
|
||||
|
||||
struct Cell
|
||||
{
|
||||
int x, y;
|
||||
bool operator == (const Cell& c2) const { return x == c2.x && y == c2.y;}
|
||||
|
||||
static Cell ComputeCell(float fx, float fy)
|
||||
{
|
||||
Cell c = { int(fx * (1.f/CELL_SIZE) + (CELL_NUMBER/2)), int(fy * (1.f/CELL_SIZE) + (CELL_NUMBER/2)) };
|
||||
return c;
|
||||
}
|
||||
|
||||
bool isValid() const { return x >= 0 && x < CELL_NUMBER && y >= 0 && y < CELL_NUMBER;}
|
||||
};
|
||||
|
||||
|
||||
Node& getGridFor(float fx, float fy)
|
||||
{
|
||||
Cell c = Cell::ComputeCell(fx, fy);
|
||||
return getGrid(c.x, c.y);
|
||||
}
|
||||
|
||||
Node& getGrid(int x, int y)
|
||||
{
|
||||
ASSERT(x < CELL_NUMBER && y < CELL_NUMBER);
|
||||
if (!nodes[x][y])
|
||||
nodes[x][y] = NodeCreatorFunc::makeNode(x, y);
|
||||
return *nodes[x][y];
|
||||
}
|
||||
|
||||
template<typename RayCallback>
|
||||
void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float max_dist, bool stopAtFirstHit)
|
||||
{
|
||||
intersectRay(ray, intersectCallback, max_dist, ray.origin() + ray.direction() * max_dist, stopAtFirstHit);
|
||||
}
|
||||
|
||||
template<typename RayCallback>
|
||||
void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist, const G3D::Vector3& end, bool stopAtFirstHit)
|
||||
{
|
||||
Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y);
|
||||
if (!cell.isValid())
|
||||
return;
|
||||
|
||||
Cell last_cell = Cell::ComputeCell(end.x, end.y);
|
||||
|
||||
if (cell == last_cell)
|
||||
{
|
||||
if (Node* node = nodes[cell.x][cell.y])
|
||||
node->intersectRay(ray, intersectCallback, max_dist, stopAtFirstHit);
|
||||
return;
|
||||
}
|
||||
|
||||
float voxel = (float)CELL_SIZE;
|
||||
float kx_inv = ray.invDirection().x, bx = ray.origin().x;
|
||||
float ky_inv = ray.invDirection().y, by = ray.origin().y;
|
||||
|
||||
int stepX, stepY;
|
||||
float tMaxX, tMaxY;
|
||||
if (kx_inv >= 0)
|
||||
{
|
||||
stepX = 1;
|
||||
float x_border = (cell.x+1) * voxel;
|
||||
tMaxX = (x_border - bx) * kx_inv;
|
||||
}
|
||||
else
|
||||
{
|
||||
stepX = -1;
|
||||
float x_border = (cell.x-1) * voxel;
|
||||
tMaxX = (x_border - bx) * kx_inv;
|
||||
}
|
||||
|
||||
if (ky_inv >= 0)
|
||||
{
|
||||
stepY = 1;
|
||||
float y_border = (cell.y+1) * voxel;
|
||||
tMaxY = (y_border - by) * ky_inv;
|
||||
}
|
||||
else
|
||||
{
|
||||
stepY = -1;
|
||||
float y_border = (cell.y-1) * voxel;
|
||||
tMaxY = (y_border - by) * ky_inv;
|
||||
}
|
||||
|
||||
//int Cycles = std::max((int)ceilf(max_dist/tMaxX),(int)ceilf(max_dist/tMaxY));
|
||||
//int i = 0;
|
||||
|
||||
float tDeltaX = voxel * fabs(kx_inv);
|
||||
float tDeltaY = voxel * fabs(ky_inv);
|
||||
do
|
||||
{
|
||||
if (Node* node = nodes[cell.x][cell.y])
|
||||
{
|
||||
//float enterdist = max_dist;
|
||||
node->intersectRay(ray, intersectCallback, max_dist, stopAtFirstHit);
|
||||
}
|
||||
if (cell == last_cell)
|
||||
break;
|
||||
if (tMaxX < tMaxY)
|
||||
{
|
||||
tMaxX += tDeltaX;
|
||||
cell.x += stepX;
|
||||
}
|
||||
else
|
||||
{
|
||||
tMaxY += tDeltaY;
|
||||
cell.y += stepY;
|
||||
}
|
||||
//++i;
|
||||
} while (cell.isValid());
|
||||
}
|
||||
|
||||
template<typename IsectCallback>
|
||||
void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback)
|
||||
{
|
||||
Cell cell = Cell::ComputeCell(point.x, point.y);
|
||||
if (!cell.isValid())
|
||||
return;
|
||||
if (Node* node = nodes[cell.x][cell.y])
|
||||
node->intersectPoint(point, intersectCallback);
|
||||
}
|
||||
|
||||
// Optimized verson of intersectRay function for rays with vertical directions
|
||||
template<typename RayCallback>
|
||||
void intersectZAllignedRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist)
|
||||
{
|
||||
Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y);
|
||||
if (!cell.isValid())
|
||||
return;
|
||||
if (Node* node = nodes[cell.x][cell.y])
|
||||
node->intersectRay(ray, intersectCallback, max_dist, false);
|
||||
}
|
||||
};
|
||||
|
||||
#undef CELL_SIZE
|
||||
#undef HGRID_MAP_SIZE
|
||||
|
||||
#endif
|
||||
34
src/server/collision/VMapDefinitions.h
Normal file
34
src/server/collision/VMapDefinitions.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _VMAPDEFINITIONS_H
|
||||
#define _VMAPDEFINITIONS_H
|
||||
#include <cstring>
|
||||
|
||||
#define LIQUID_TILE_SIZE (533.333f / 128.f)
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
const char VMAP_MAGIC[] = "VMAP_4.1";
|
||||
const char RAW_VMAP_MAGIC[] = "VMAP041"; // used in extracted vmap files with raw data
|
||||
const char GAMEOBJECT_MODELS[] = "GameObjectModels.dtree";
|
||||
|
||||
// defined in TileAssembler.cpp currently...
|
||||
bool readChunk(FILE* rf, char *dest, const char *compare, uint32 len);
|
||||
}
|
||||
#endif
|
||||
150
src/server/collision/VMapTools.h
Normal file
150
src/server/collision/VMapTools.h
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _VMAPTOOLS_H
|
||||
#define _VMAPTOOLS_H
|
||||
|
||||
#include <G3D/CollisionDetection.h>
|
||||
#include <G3D/AABox.h>
|
||||
|
||||
#include "NodeValueAccess.h"
|
||||
|
||||
/**
|
||||
The Class is mainly taken from G3D/AABSPTree.h but modified to be able to use our internal data structure.
|
||||
This is an iterator that helps us analysing the BSP-Trees.
|
||||
The collision detection is modified to return true, if we are inside an object.
|
||||
*/
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
template<class TValue>
|
||||
class IntersectionCallBack {
|
||||
public:
|
||||
TValue* closestEntity;
|
||||
G3D::Vector3 hitLocation;
|
||||
G3D::Vector3 hitNormal;
|
||||
|
||||
void operator()(const G3D::Ray& ray, const TValue* entity, bool StopAtFirstHit, float& distance) {
|
||||
entity->intersect(ray, distance, StopAtFirstHit, hitLocation, hitNormal);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================
|
||||
//==============================================================
|
||||
//==============================================================
|
||||
|
||||
class MyCollisionDetection
|
||||
{
|
||||
private:
|
||||
public:
|
||||
|
||||
static bool collisionLocationForMovingPointFixedAABox(
|
||||
const G3D::Vector3& origin,
|
||||
const G3D::Vector3& dir,
|
||||
const G3D::AABox& box,
|
||||
G3D::Vector3& location,
|
||||
bool& Inside)
|
||||
{
|
||||
|
||||
// Integer representation of a floating-point value.
|
||||
#define IR(x) (reinterpret_cast<G3D::uint32 const&>(x))
|
||||
|
||||
Inside = true;
|
||||
const G3D::Vector3& MinB = box.low();
|
||||
const G3D::Vector3& MaxB = box.high();
|
||||
G3D::Vector3 MaxT(-1.0f, -1.0f, -1.0f);
|
||||
|
||||
// Find candidate planes.
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
if (origin[i] < MinB[i])
|
||||
{
|
||||
location[i] = MinB[i];
|
||||
Inside = false;
|
||||
|
||||
// Calculate T distances to candidate planes
|
||||
if (IR(dir[i]))
|
||||
{
|
||||
MaxT[i] = (MinB[i] - origin[i]) / dir[i];
|
||||
}
|
||||
}
|
||||
else if (origin[i] > MaxB[i])
|
||||
{
|
||||
location[i] = MaxB[i];
|
||||
Inside = false;
|
||||
|
||||
// Calculate T distances to candidate planes
|
||||
if (IR(dir[i]))
|
||||
{
|
||||
MaxT[i] = (MaxB[i] - origin[i]) / dir[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Inside)
|
||||
{
|
||||
// definite hit
|
||||
location = origin;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get largest of the maxT's for final choice of intersection
|
||||
int WhichPlane = 0;
|
||||
if (MaxT[1] > MaxT[WhichPlane])
|
||||
{
|
||||
WhichPlane = 1;
|
||||
}
|
||||
|
||||
if (MaxT[2] > MaxT[WhichPlane])
|
||||
{
|
||||
WhichPlane = 2;
|
||||
}
|
||||
|
||||
// Check final candidate actually inside box
|
||||
if (IR(MaxT[WhichPlane]) & 0x80000000)
|
||||
{
|
||||
// Miss the box
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
if (i != WhichPlane)
|
||||
{
|
||||
location[i] = origin[i] + MaxT[WhichPlane] * dir[i];
|
||||
if ((location[i] < MinB[i]) ||
|
||||
(location[i] > MaxB[i]))
|
||||
{
|
||||
// On this plane we're outside the box extents, so
|
||||
// we miss the box
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
// Choose the normal to be the plane normal facing into the ray
|
||||
normal = G3D::Vector3::zero();
|
||||
normal[WhichPlane] = (dir[WhichPlane] > 0) ? -1.0 : 1.0;
|
||||
*/
|
||||
return true;
|
||||
|
||||
#undef IR
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
323
src/server/game/AI/CoreAI/CombatAI.cpp
Normal file
323
src/server/game/AI/CoreAI/CombatAI.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "Vehicle.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
|
||||
/////////////////
|
||||
// AggressorAI
|
||||
/////////////////
|
||||
|
||||
int AggressorAI::Permissible(const Creature* creature)
|
||||
{
|
||||
// have some hostile factions, it will be selected by IsHostileTo check at MoveInLineOfSight
|
||||
if (!creature->IsCivilian() && !creature->IsNeutralToAll())
|
||||
return PERMIT_BASE_PROACTIVE;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
void AggressorAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// CombatAI
|
||||
/////////////////
|
||||
|
||||
void CombatAI::InitializeAI()
|
||||
{
|
||||
for (uint32 i = 0; i < CREATURE_MAX_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);
|
||||
}
|
||||
|
||||
void CombatAI::EnterCombat(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, 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, 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;
|
||||
}
|
||||
|
||||
void CasterAI::EnterCombat(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, 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, (casttime ? casttime : 500) + GetAISpellInfo(spellId)->realCooldown);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////
|
||||
// ArcherAI
|
||||
//////////////
|
||||
|
||||
ArcherAI::ArcherAI(Creature* c) : CreatureAI(c)
|
||||
{
|
||||
if (!me->m_spells[0])
|
||||
sLog->outError("ArcherAI set for creature (entry = %u) 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])
|
||||
sLog->outError("TurretAI set for creature (entry = %u) 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(const Unit* /*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())
|
||||
;//sLog->outDebug(LOG_FILTER_CONDITIONSYS, "VehicleAI::LoadConditions: loaded %u 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;
|
||||
}
|
||||
120
src/server/game/AI/CoreAI/CombatAI.h
Normal file
120
src/server/game/AI/CoreAI/CombatAI.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_COMBATAI_H
|
||||
#define TRINITY_COMBATAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "ConditionMgr.h"
|
||||
|
||||
class Creature;
|
||||
|
||||
class AggressorAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit AggressorAI(Creature* c) : CreatureAI(c) {}
|
||||
|
||||
void UpdateAI(uint32);
|
||||
static int Permissible(const Creature*);
|
||||
};
|
||||
|
||||
typedef std::vector<uint32> SpellVct;
|
||||
|
||||
class CombatAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit CombatAI(Creature* c) : CreatureAI(c) {}
|
||||
|
||||
void InitializeAI();
|
||||
void Reset();
|
||||
void EnterCombat(Unit* who);
|
||||
void JustDied(Unit* killer);
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
static int 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();
|
||||
void AttackStart(Unit* victim) { AttackStartCaster(victim, m_attackDist); }
|
||||
void UpdateAI(uint32 diff);
|
||||
void EnterCombat(Unit* /*who*/);
|
||||
private:
|
||||
float m_attackDist;
|
||||
};
|
||||
|
||||
struct ArcherAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit ArcherAI(Creature* c);
|
||||
void AttackStart(Unit* who);
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
|
||||
static int Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
|
||||
|
||||
protected:
|
||||
float m_minRange;
|
||||
};
|
||||
|
||||
struct TurretAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit TurretAI(Creature* c);
|
||||
bool CanAIAttack(const Unit* who) const;
|
||||
void AttackStart(Unit* who);
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
static int 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);
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit*) {}
|
||||
void OnCharmed(bool apply);
|
||||
|
||||
static int Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
|
||||
|
||||
private:
|
||||
void LoadConditions();
|
||||
void CheckConditions(uint32 diff);
|
||||
ConditionList conditions;
|
||||
uint32 m_ConditionsTimer;
|
||||
bool m_DoDismiss;
|
||||
uint32 m_DismissTimer;
|
||||
};
|
||||
|
||||
#endif
|
||||
29
src/server/game/AI/CoreAI/GameObjectAI.cpp
Normal file
29
src/server/game/AI/CoreAI/GameObjectAI.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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"
|
||||
|
||||
//GameObjectAI::GameObjectAI(GameObject* g) : go(g) {}
|
||||
int GameObjectAI::Permissible(const GameObject* go)
|
||||
{
|
||||
if (go->GetAIName() == "GameObjectAI")
|
||||
return PERMIT_BASE_SPECIAL;
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
NullGameObjectAI::NullGameObjectAI(GameObject* g) : GameObjectAI(g) {}
|
||||
76
src/server/game/AI/CoreAI/GameObjectAI.h
Normal file
76
src/server/game/AI/CoreAI/GameObjectAI.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_GAMEOBJECTAI_H
|
||||
#define TRINITY_GAMEOBJECTAI_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <list>
|
||||
#include "Object.h"
|
||||
#include "QuestDef.h"
|
||||
#include "GameObject.h"
|
||||
#include "CreatureAI.h"
|
||||
|
||||
class GameObjectAI
|
||||
{
|
||||
protected:
|
||||
GameObject* const go;
|
||||
public:
|
||||
explicit GameObjectAI(GameObject* g) : go(g) {}
|
||||
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(uint64 /*guid*/, int32 /*id = 0 */) {}
|
||||
virtual uint64 GetGUID(int32 /*id = 0 */) const { return 0; }
|
||||
|
||||
static int 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 SetData64(uint32 /*id*/, uint64 /*value*/) {}
|
||||
virtual uint64 GetData64(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, const SpellInfo* spellInfo) {}
|
||||
};
|
||||
|
||||
class NullGameObjectAI : public GameObjectAI
|
||||
{
|
||||
public:
|
||||
explicit NullGameObjectAI(GameObject* g);
|
||||
|
||||
void UpdateAI(uint32 /*diff*/) {}
|
||||
|
||||
static int Permissible(GameObject const* /*go*/) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
#endif
|
||||
69
src/server/game/AI/CoreAI/GuardAI.cpp
Normal file
69
src/server/game/AI/CoreAI/GuardAI.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "Errors.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "World.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
|
||||
int GuardAI::Permissible(Creature const* creature)
|
||||
{
|
||||
if (creature->IsGuard())
|
||||
return PERMIT_BASE_SPECIAL;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
GuardAI::GuardAI(Creature* creature) : ScriptedAI(creature)
|
||||
{
|
||||
}
|
||||
|
||||
void GuardAI::Reset()
|
||||
{
|
||||
ScriptedAI::Reset();
|
||||
me->CastSpell(me, 18950 /*SPELL_INVISIBILITY_AND_STEALTH_DETECTION*/, true);
|
||||
}
|
||||
|
||||
void GuardAI::EnterEvadeMode()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
me->CombatStop(true);
|
||||
me->DeleteThreatList();
|
||||
return;
|
||||
}
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_UNITS, "Guard entry: %u enters evade mode.", me->GetEntry());
|
||||
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
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 (Player* player = killer->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
me->SendZoneUnderAttackMessage(player);
|
||||
}
|
||||
37
src/server/game/AI/CoreAI/GuardAI.h
Normal file
37
src/server/game/AI/CoreAI/GuardAI.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_GUARDAI_H
|
||||
#define TRINITY_GUARDAI_H
|
||||
|
||||
#include "ScriptedCreature.h"
|
||||
|
||||
class Creature;
|
||||
|
||||
class GuardAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
explicit GuardAI(Creature* creature);
|
||||
|
||||
static int Permissible(Creature const* creature);
|
||||
|
||||
void Reset();
|
||||
void EnterEvadeMode();
|
||||
void JustDied(Unit* killer);
|
||||
};
|
||||
#endif
|
||||
92
src/server/game/AI/CoreAI/PassiveAI.cpp
Normal file
92
src/server/game/AI/CoreAI/PassiveAI.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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"
|
||||
#include "TemporarySummon.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); }
|
||||
|
||||
void PassiveAI::UpdateAI(uint32)
|
||||
{
|
||||
if (me->IsInCombat() && me->getAttackers().empty())
|
||||
EnterEvadeMode();
|
||||
}
|
||||
|
||||
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->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
|
||||
}
|
||||
|
||||
void PossessedAI::KilledUnit(Unit* victim)
|
||||
{
|
||||
// We killed a creature, disable victim's loot
|
||||
//if (victim->GetTypeId() == TYPEID_UNIT)
|
||||
// victim->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
|
||||
}
|
||||
|
||||
void CritterAI::DamageTaken(Unit*, uint32&, DamageEffectType, SpellSchoolMask)
|
||||
{
|
||||
if (!me->HasUnitState(UNIT_STATE_FLEEING))
|
||||
me->SetControlled(true, UNIT_STATE_FLEEING);
|
||||
|
||||
_combatTimer = 1;
|
||||
}
|
||||
|
||||
void CritterAI::EnterEvadeMode()
|
||||
{
|
||||
if (me->HasUnitState(UNIT_STATE_FLEEING))
|
||||
me->SetControlled(false, UNIT_STATE_FLEEING);
|
||||
CreatureAI::EnterEvadeMode();
|
||||
_combatTimer = 0;
|
||||
}
|
||||
|
||||
void CritterAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
_combatTimer += diff;
|
||||
if (_combatTimer >= 5000)
|
||||
EnterEvadeMode();
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerAI::IsSummonedBy(Unit* summoner)
|
||||
{
|
||||
if (me->m_spells[0])
|
||||
me->CastSpell(me, me->m_spells[0], false, 0, 0, summoner ? summoner->GetGUID() : 0);
|
||||
}
|
||||
89
src/server/game/AI/CoreAI/PassiveAI.h
Normal file
89
src/server/game/AI/CoreAI/PassiveAI.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_PASSIVEAI_H
|
||||
#define TRINITY_PASSIVEAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
//#include "CreatureAIImpl.h"
|
||||
|
||||
class PassiveAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit PassiveAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit*) {}
|
||||
void UpdateAI(uint32);
|
||||
|
||||
static int Permissible(const Creature*) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
|
||||
class PossessedAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit PossessedAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit* target);
|
||||
void UpdateAI(uint32);
|
||||
void EnterEvadeMode() {}
|
||||
|
||||
void JustDied(Unit*);
|
||||
void KilledUnit(Unit* victim);
|
||||
|
||||
static int Permissible(const Creature*) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
|
||||
class NullCreatureAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit NullCreatureAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit*) {}
|
||||
void UpdateAI(uint32) {}
|
||||
void EnterEvadeMode() {}
|
||||
void OnCharmed(bool /*apply*/) {}
|
||||
|
||||
static int Permissible(const Creature*) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
|
||||
class CritterAI : public PassiveAI
|
||||
{
|
||||
public:
|
||||
explicit CritterAI(Creature* c) : PassiveAI(c) { _combatTimer = 0; }
|
||||
|
||||
void DamageTaken(Unit* /*done_by*/, uint32& /*damage*/, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask);
|
||||
void EnterEvadeMode();
|
||||
void UpdateAI(uint32);
|
||||
|
||||
// Xinef: Added
|
||||
private:
|
||||
uint32 _combatTimer;
|
||||
};
|
||||
|
||||
class TriggerAI : public NullCreatureAI
|
||||
{
|
||||
public:
|
||||
explicit TriggerAI(Creature* c) : NullCreatureAI(c) {}
|
||||
void IsSummonedBy(Unit* summoner);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
742
src/server/game/AI/CoreAI/PetAI.cpp
Normal file
742
src/server/game/AI/CoreAI/PetAI.cpp
Normal file
@@ -0,0 +1,742 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "Errors.h"
|
||||
#include "Pet.h"
|
||||
#include "Player.h"
|
||||
#include "DBCStores.h"
|
||||
#include "Spell.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Creature.h"
|
||||
#include "World.h"
|
||||
#include "Util.h"
|
||||
#include "Group.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
int PetAI::Permissible(const Creature* creature)
|
||||
{
|
||||
if (creature->IsPet())
|
||||
return PERMIT_BASE_SPECIAL;
|
||||
|
||||
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;
|
||||
|
||||
if (!me->_CanDetectFeignDeathOf(me->GetVictim()))
|
||||
return true;
|
||||
|
||||
if (me->isTargetNotAcceptableByMMaps(me->GetVictim()->GetGUID(), sWorld->GetGameTime(), me->GetVictim()))
|
||||
return true;
|
||||
|
||||
return !me->CanCreatureAttack(me->GetVictim());
|
||||
}
|
||||
|
||||
void PetAI::_stopAttack()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
;//sLog->outStaticDebug("Creature stoped attacking cuz his dead [guid=%u]", me->GetGUIDLow());
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
me->CombatStop();
|
||||
me->getHostileRefManager().deleteReferences();
|
||||
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() const
|
||||
{
|
||||
return me->GetEntry() != 416 /*ENTRY_IMP*/ &&
|
||||
me->GetEntry() != 510 /*ENTRY_WATER_ELEMENTAL*/ &&
|
||||
me->GetEntry() != 37994 /*ENTRY_WATER_ELEMENTAL_PERM*/;
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
;//sLog->outStaticDebug("Pet AI stopped attacking [guid=%u]", me->GetGUIDLow());
|
||||
_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->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->GetTypeId() == TYPEID_PLAYER && me->GetCharmInfo()->GetForcedSpell() && me->GetCharmInfo()->GetForcedTarget())
|
||||
{
|
||||
owner->ToPlayer()->GetSession()->HandlePetActionHelper(me, me->GetGUID(), 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
|
||||
if (!me->IsInCombat() && !me->GetCharmInfo()->IsCommandAttack())
|
||||
continue;
|
||||
}
|
||||
|
||||
Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE, 0);
|
||||
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.push_back(std::make_pair(target, spell));
|
||||
spellUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// No enemy, check friendly
|
||||
if (!spellUsed)
|
||||
{
|
||||
for (std::set<uint64>::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
|
||||
{
|
||||
Unit* ally = ObjectAccessor::GetUnit(*me, *tar);
|
||||
|
||||
//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.push_back(std::make_pair(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, 0);
|
||||
if (spell->CanAutoCast(me->GetVictim()))
|
||||
targetSpellStore.push_back(std::make_pair(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->GetTypeId() == TYPEID_PLAYER)
|
||||
me->SendUpdateToPlayer(target->ToPlayer());
|
||||
|
||||
if (owner && owner->GetTypeId() == TYPEID_PLAYER)
|
||||
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 = NULL;
|
||||
|
||||
m_updateAlliesTimer = 10*IN_MILLISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
|
||||
|
||||
if (!owner)
|
||||
return;
|
||||
else if (owner->GetTypeId() == TYPEID_PLAYER)
|
||||
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 != NULL; 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->GetUInt64Value(UNIT_FIELD_CHANNEL_OBJECT) && me->GetUInt64Value(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 NULL 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 NULL;
|
||||
|
||||
// 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->_CanDetectFeignDeathOf(myAttacker) && me->CanCreatureAttack(myAttacker) && !me->isTargetNotAcceptableByMMaps(myAttacker->GetGUID(), sWorld->GetGameTime(), myAttacker))
|
||||
return myAttacker;
|
||||
|
||||
// Check pet's attackers first to prevent dragging mobs back to owner
|
||||
if (me->HasAuraType(SPELL_AURA_MOD_TAUNT))
|
||||
{
|
||||
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->_CanDetectFeignDeathOf(caster) && me->CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, me->GetGUID()))
|
||||
return caster;
|
||||
}
|
||||
|
||||
// Not sure why we wouldn't have an owner but just in case...
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
if (!owner)
|
||||
return NULL;
|
||||
|
||||
// Check owner attackers
|
||||
if (Unit* ownerAttacker = owner->getAttackerForHelper())
|
||||
if (!ownerAttacker->HasBreakableByDamageCrowdControlAura() && me->_CanDetectFeignDeathOf(ownerAttacker) && me->CanCreatureAttack(ownerAttacker) && !me->isTargetNotAcceptableByMMaps(ownerAttacker->GetGUID(), sWorld->GetGameTime(), 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->_CanDetectFeignDeathOf(ownerVictim) && me->CanCreatureAttack(ownerVictim) && !me->isTargetNotAcceptableByMMaps(ownerVictim->GetGUID(), sWorld->GetGameTime(), 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))
|
||||
return nearTarget;
|
||||
|
||||
// Default - no valid targets
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
// 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->GetGUIDLow(), 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(0);
|
||||
|
||||
// xinef: remember that npcs summoned by npcs can also be pets
|
||||
me->DeleteThreatList();
|
||||
me->ClearInPetCombat();
|
||||
}
|
||||
|
||||
void PetAI::SpellHit(Unit* caster, const SpellInfo* spellInfo)
|
||||
{
|
||||
// Xinef: taunt behavior code
|
||||
if (spellInfo->HasAura(SPELL_AURA_MOD_TAUNT) && !me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
me->GetCharmInfo()->SetForcedSpell(0);
|
||||
me->GetCharmInfo()->SetForcedTargetGUID(0);
|
||||
AttackStart(caster);
|
||||
}
|
||||
}
|
||||
|
||||
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->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT);
|
||||
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, 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 (CharmInfo* ci = me->GetCharmInfo())
|
||||
{
|
||||
ci->SetIsAtStay(false);
|
||||
ci->SetIsCommandFollow(false);
|
||||
ci->SetIsFollowing(false);
|
||||
ci->SetIsReturning(false);
|
||||
}
|
||||
|
||||
if (chase)
|
||||
{
|
||||
me->GetMotionMaster()->MoveChase(target, !_canMeleeAttack() ? 20.0f: 0.0f, me->GetAngle(target));
|
||||
}
|
||||
else // (Stay && ((Aggressive || Defensive) && In Melee Range)))
|
||||
{
|
||||
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->GetGUIDLow() && 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()->GetGUIDLow() && me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsFollowing(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool PetAI::CanAttack(Unit* target, const SpellInfo* 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->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED))
|
||||
return false;
|
||||
|
||||
// pussywizard: ZOMG! TEMP!
|
||||
if (!me->GetCharmInfo())
|
||||
{
|
||||
sLog->outMisc("PetAI::CanAttack (A1) - %u, %u", me->GetEntry(), GUID_LOPART(me->GetOwnerGUID()));
|
||||
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)
|
||||
{
|
||||
// Check if our owner selected this target and clicked "attack"
|
||||
Unit* ownerTarget = NULL;
|
||||
if (Player* owner = me->GetCharmerOrOwner()->ToPlayer())
|
||||
ownerTarget = owner->GetSelectedUnit();
|
||||
else
|
||||
ownerTarget = me->GetCharmerOrOwner()->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);
|
||||
}
|
||||
72
src/server/game/AI/CoreAI/PetAI.h
Normal file
72
src/server/game/AI/CoreAI/PetAI.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_PETAI_H
|
||||
#define TRINITY_PETAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "Timer.h"
|
||||
|
||||
class Creature;
|
||||
class Spell;
|
||||
|
||||
class PetAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
|
||||
explicit PetAI(Creature* c);
|
||||
|
||||
void UpdateAI(uint32);
|
||||
static int Permissible(const Creature*);
|
||||
|
||||
void KilledUnit(Unit* /*victim*/);
|
||||
void AttackStart(Unit* target);
|
||||
void MovementInform(uint32 moveType, uint32 data);
|
||||
void OwnerAttackedBy(Unit* attacker);
|
||||
void OwnerAttacked(Unit* target);
|
||||
void AttackedBy(Unit* attacker);
|
||||
void ReceiveEmote(Player* player, uint32 textEmote);
|
||||
|
||||
// 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*/) {} // CreatureAI interferes with returning pets
|
||||
void MoveInLineOfSight_Safe(Unit* /*who*/) {} // CreatureAI interferes with returning pets
|
||||
void EnterEvadeMode() {} // For fleeing, pets don't use this type of Evade mechanic
|
||||
void SpellHit(Unit* caster, const SpellInfo* spellInfo);
|
||||
|
||||
private:
|
||||
bool _isVisible(Unit*) const;
|
||||
bool _needToStop(void);
|
||||
void _stopAttack(void);
|
||||
void _doMeleeAttack();
|
||||
bool _canMeleeAttack() const;
|
||||
|
||||
void UpdateAllies();
|
||||
|
||||
TimeTracker i_tracker;
|
||||
std::set<uint64> m_AllySet;
|
||||
uint32 m_updateAlliesTimer;
|
||||
|
||||
Unit* SelectNextTarget(bool allowAutoSelect) const;
|
||||
void HandleReturnMovement();
|
||||
void DoAttack(Unit* target, bool chase);
|
||||
bool CanAttack(Unit* target, const SpellInfo* spellInfo = NULL);
|
||||
void ClearCharmInfoFlags();
|
||||
};
|
||||
#endif
|
||||
40
src/server/game/AI/CoreAI/ReactorAI.cpp
Normal file
40
src/server/game/AI/CoreAI/ReactorAI.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "ByteBuffer.h"
|
||||
#include "ReactorAI.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
|
||||
int ReactorAI::Permissible(const Creature* creature)
|
||||
{
|
||||
if (creature->IsCivilian() || creature->IsNeutralToAll())
|
||||
return PERMIT_BASE_REACTIVE;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
void ReactorAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
37
src/server/game/AI/CoreAI/ReactorAI.h
Normal file
37
src/server/game/AI/CoreAI/ReactorAI.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_REACTORAI_H
|
||||
#define TRINITY_REACTORAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
|
||||
class Unit;
|
||||
|
||||
class ReactorAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
|
||||
explicit ReactorAI(Creature* c) : CreatureAI(c) {}
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
static int Permissible(const Creature*);
|
||||
};
|
||||
#endif
|
||||
117
src/server/game/AI/CoreAI/TotemAI.cpp
Normal file
117
src/server/game/AI/CoreAI/TotemAI.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "Totem.h"
|
||||
#include "Creature.h"
|
||||
#include "DBCStores.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "CellImpl.h"
|
||||
|
||||
int TotemAI::Permissible(Creature const* creature)
|
||||
{
|
||||
if (creature->IsTotem())
|
||||
return PERMIT_BASE_PROACTIVE;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
TotemAI::TotemAI(Creature* c) : CreatureAI(c), i_victimGuid(0)
|
||||
{
|
||||
ASSERT(c->IsTotem());
|
||||
}
|
||||
|
||||
void TotemAI::SpellHit(Unit* /*caster*/, const SpellInfo* spellInfo)
|
||||
{
|
||||
}
|
||||
|
||||
void TotemAI::DoAction(int32 param)
|
||||
{
|
||||
}
|
||||
|
||||
void TotemAI::MoveInLineOfSight(Unit* /*who*/)
|
||||
{
|
||||
}
|
||||
|
||||
void TotemAI::EnterEvadeMode()
|
||||
{
|
||||
me->CombatStop(true);
|
||||
}
|
||||
|
||||
void TotemAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (me->ToTotem()->GetTotemType() != TOTEM_ACTIVE)
|
||||
return;
|
||||
|
||||
if (!me->IsAlive() || me->IsNonMeleeSpellCast(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) : NULL;
|
||||
|
||||
// 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 = NULL;
|
||||
Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, me, max_range);
|
||||
Trinity::UnitLastSearcher<Trinity::NearestAttackableUnitInObjectRangeCheck> checker(me, victim, u_check);
|
||||
me->VisitNearbyObject(max_range, checker);
|
||||
}
|
||||
|
||||
// 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 = 0;
|
||||
}
|
||||
|
||||
void TotemAI::AttackStart(Unit* /*victim*/)
|
||||
{
|
||||
// Sentry totem sends ping on attack
|
||||
if (me->GetEntry() == SENTRY_TOTEM_ENTRY && me->GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
WorldPacket data(MSG_MINIMAP_PING, (8+4+4));
|
||||
data << me->GetGUID();
|
||||
data << me->GetPositionX();
|
||||
data << me->GetPositionY();
|
||||
me->GetOwner()->ToPlayer()->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
62
src/server/game/AI/CoreAI/TotemAI.h
Normal file
62
src/server/game/AI/CoreAI/TotemAI.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_TOTEMAI_H
|
||||
#define TRINITY_TOTEMAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "Timer.h"
|
||||
|
||||
class Creature;
|
||||
class Totem;
|
||||
|
||||
class TotemAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
|
||||
explicit TotemAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit* who);
|
||||
void AttackStart(Unit* victim);
|
||||
void EnterEvadeMode();
|
||||
void SpellHit(Unit* /*caster*/, const SpellInfo* /*spellInfo*/);
|
||||
void DoAction(int32 param);
|
||||
|
||||
void UpdateAI(uint32 diff);
|
||||
static int Permissible(Creature const* creature);
|
||||
|
||||
private:
|
||||
uint64 i_victimGuid;
|
||||
};
|
||||
|
||||
class KillMagnetEvent : public BasicEvent
|
||||
{
|
||||
public:
|
||||
KillMagnetEvent(Unit& self) : _self(self) { }
|
||||
bool Execute(uint64 e_time, uint32 p_time)
|
||||
{
|
||||
_self.setDeathState(JUST_DIED);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
Unit& _self;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
336
src/server/game/AI/CoreAI/UnitAI.cpp
Normal file
336
src/server/game/AI/CoreAI/UnitAI.cpp
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "Player.h"
|
||||
#include "Creature.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "Spell.h"
|
||||
#include "CreatureAIImpl.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->haveOffhandWeapon())
|
||||
if (me->getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
|
||||
me->setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
|
||||
|
||||
me->AttackerStateUpdate(victim);
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
|
||||
if (me->haveOffhandWeapon() && 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;
|
||||
}
|
||||
|
||||
Unit* UnitAI::SelectTarget(SelectAggroTarget targetType, uint32 position, float dist, bool playerOnly, int32 aura)
|
||||
{
|
||||
return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, aura));
|
||||
}
|
||||
|
||||
void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, float dist, bool playerOnly, int32 aura)
|
||||
{
|
||||
SelectTargetList(targetList, DefaultTargetSelector(me, dist, playerOnly, aura), num, targetType);
|
||||
}
|
||||
|
||||
float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
return spellInfo ? spellInfo->GetMaxRange(positive) : 0;
|
||||
}
|
||||
|
||||
void UnitAI::DoAddAuraToAllHostilePlayers(uint32 spellid)
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
ThreatContainer::StorageType threatlist = me->getThreatManager().getThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
|
||||
if (unit->GetTypeId() == TYPEID_PLAYER)
|
||||
me->AddAura(spellid, unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitAI::DoCastToAllHostilePlayers(uint32 spellid, bool triggered)
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
ThreatContainer::StorageType threatlist = me->getThreatManager().getThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
|
||||
if (unit->GetTypeId() == TYPEID_PLAYER)
|
||||
me->CastSpell(unit, spellid, triggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitAI::DoCast(uint32 spellId)
|
||||
{
|
||||
Unit* target = NULL;
|
||||
//sLog->outError("aggre %u %u", spellId, (uint32)AISpellInfo[spellId].target);
|
||||
switch (AISpellInfo[spellId].target)
|
||||
{
|
||||
default:
|
||||
case AITARGET_SELF: target = me; break;
|
||||
case AITARGET_VICTIM: target = me->GetVictim(); break;
|
||||
case AITARGET_ENEMY:
|
||||
{
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS);
|
||||
//float range = GetSpellMaxRange(spellInfo, false);
|
||||
target = SelectTarget(SELECT_TARGET_RANDOM, 0, spellInfo->GetMaxRange(false), playerOnly);
|
||||
break;
|
||||
}
|
||||
case AITARGET_ALLY: target = me; break;
|
||||
case AITARGET_BUFF: target = me; break;
|
||||
case AITARGET_DEBUFF:
|
||||
{
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS);
|
||||
float range = spellInfo->GetMaxRange(false);
|
||||
|
||||
DefaultTargetSelector targetSelector(me, range, playerOnly, -(int32)spellId);
|
||||
if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM)
|
||||
&& targetSelector(me->GetVictim()))
|
||||
target = me->GetVictim();
|
||||
else
|
||||
target = SelectTarget(SELECT_TARGET_RANDOM, 0, targetSelector);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target)
|
||||
me->CastSpell(target, spellId, false);
|
||||
}
|
||||
|
||||
void UnitAI::DoCast(Unit* victim, uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!victim || (me->HasUnitState(UNIT_STATE_CASTING) && !triggered))
|
||||
return;
|
||||
|
||||
me->CastSpell(victim, spellId, triggered);
|
||||
}
|
||||
|
||||
void UnitAI::DoCastVictim(uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!me->GetVictim() || (me->HasUnitState(UNIT_STATE_CASTING) && !triggered))
|
||||
return;
|
||||
|
||||
me->CastSpell(me->GetVictim(), spellId, triggered);
|
||||
}
|
||||
|
||||
void UnitAI::DoCastAOE(uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!triggered && me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
me->CastSpell((Unit*)NULL, spellId, triggered);
|
||||
}
|
||||
|
||||
#define UPDATE_TARGET(a) {if (AIInfo->target<a) AIInfo->target=a;}
|
||||
|
||||
void UnitAI::FillAISpellInfo()
|
||||
{
|
||||
AISpellInfo = new AISpellInfoType[sSpellMgr->GetSpellInfoStoreSize()];
|
||||
|
||||
AISpellInfoType* AIInfo = AISpellInfo;
|
||||
const SpellInfo* spellInfo;
|
||||
|
||||
for (uint32 i = 0; i < sSpellMgr->GetSpellInfoStoreSize(); ++i, ++AIInfo)
|
||||
{
|
||||
spellInfo = sSpellMgr->GetSpellInfo(i);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (spellInfo->HasAttribute(SPELL_ATTR0_CASTABLE_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;
|
||||
}
|
||||
}
|
||||
|
||||
//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->type : 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->GetTypeId() != TYPEID_PLAYER)
|
||||
return false;
|
||||
|
||||
return target != _source->GetVictim();
|
||||
}
|
||||
340
src/server/game/AI/CoreAI/UnitAI.h
Normal file
340
src/server/game/AI/CoreAI/UnitAI.h
Normal file
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_UNITAI_H
|
||||
#define TRINITY_UNITAI_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Unit.h"
|
||||
#include "Containers.h"
|
||||
#include <list>
|
||||
|
||||
class Player;
|
||||
class Quest;
|
||||
class Unit;
|
||||
struct AISpellInfoType;
|
||||
|
||||
//Selection method used by SelectTarget
|
||||
enum SelectAggroTarget
|
||||
{
|
||||
SELECT_TARGET_RANDOM = 0, //Just selects a random target
|
||||
SELECT_TARGET_TOPAGGRO, //Selects targes from top aggro to bottom
|
||||
SELECT_TARGET_BOTTOMAGGRO, //Selects targets from bottom aggro to top
|
||||
SELECT_TARGET_NEAREST,
|
||||
SELECT_TARGET_FARTHEST,
|
||||
};
|
||||
|
||||
// default predicate function to select target based on distance, player and/or aura criteria
|
||||
struct DefaultTargetSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
const Unit* me;
|
||||
float m_dist;
|
||||
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
|
||||
// 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, int32 aura) : me(unit), m_dist(dist), m_playerOnly(playerOnly), m_aura(aura) {}
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
if (!me)
|
||||
return false;
|
||||
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (m_playerOnly && (target->GetTypeId() != TYPEID_PLAYER))
|
||||
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 std::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 std::unary_function<Unit*, bool>
|
||||
{
|
||||
public:
|
||||
NonTankTargetSelector(Creature* source, bool playerOnly = true) : _source(source), _playerOnly(playerOnly) { }
|
||||
bool operator()(Unit const* target) const;
|
||||
|
||||
private:
|
||||
Creature const* _source;
|
||||
bool _playerOnly;
|
||||
};
|
||||
|
||||
// Simple selector for units using mana
|
||||
struct PowerUsersSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
Unit const* _me;
|
||||
float const _dist;
|
||||
bool const _playerOnly;
|
||||
Powers const _power;
|
||||
|
||||
|
||||
PowerUsersSelector(Unit const* unit, Powers power, float dist, bool playerOnly) : _me(unit), _power(power), _dist(dist), _playerOnly(playerOnly) { }
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
if (!_me || !target)
|
||||
return false;
|
||||
|
||||
if (target->getPowerType() != _power)
|
||||
return false;
|
||||
|
||||
if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
|
||||
return false;
|
||||
|
||||
if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist))
|
||||
return false;
|
||||
|
||||
if (_dist < 0.0f && _me->IsWithinCombatRange(target, -_dist))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct FarthestTargetSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
FarthestTargetSelector(Unit const* unit, float dist, bool playerOnly, bool inLos) : _me(unit), _dist(dist), _playerOnly(playerOnly), _inLos(inLos) {}
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
if (!_me || !target)
|
||||
return false;
|
||||
|
||||
if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
|
||||
return false;
|
||||
|
||||
if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist))
|
||||
return false;
|
||||
|
||||
if (_inLos && !_me->IsWithinLOSInMap(target))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const Unit* _me;
|
||||
float _dist;
|
||||
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*/);
|
||||
virtual void UpdateAI(uint32 diff) = 0;
|
||||
|
||||
virtual void InitializeAI() { if (!me->isDead()) Reset(); }
|
||||
|
||||
virtual void Reset() {};
|
||||
|
||||
// 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(uint64 /*guid*/, int32 /*id*/ = 0) {}
|
||||
virtual uint64 GetGUID(int32 /*id*/ = 0) const { return 0; }
|
||||
|
||||
Unit* SelectTarget(SelectAggroTarget targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
|
||||
// Select the targets satifying the predicate.
|
||||
// predicate shall extend std::unary_function<Unit*, bool>
|
||||
template <class PREDICATE> Unit* SelectTarget(SelectAggroTarget targetType, uint32 position, PREDICATE const& predicate)
|
||||
{
|
||||
ThreatContainer::StorageType const& threatlist = me->getThreatManager().getThreatList();
|
||||
if (position >= threatlist.size())
|
||||
return NULL;
|
||||
|
||||
std::list<Unit*> targetList;
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
if (predicate((*itr)->getTarget()))
|
||||
targetList.push_back((*itr)->getTarget());
|
||||
|
||||
if (position >= targetList.size())
|
||||
return NULL;
|
||||
|
||||
if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
|
||||
targetList.sort(Trinity::ObjectDistanceOrderPred(me));
|
||||
|
||||
switch (targetType)
|
||||
{
|
||||
case SELECT_TARGET_NEAREST:
|
||||
case SELECT_TARGET_TOPAGGRO:
|
||||
{
|
||||
std::list<Unit*>::iterator itr = targetList.begin();
|
||||
std::advance(itr, position);
|
||||
return *itr;
|
||||
}
|
||||
case SELECT_TARGET_FARTHEST:
|
||||
case SELECT_TARGET_BOTTOMAGGRO:
|
||||
{
|
||||
std::list<Unit*>::reverse_iterator ritr = targetList.rbegin();
|
||||
std::advance(ritr, position);
|
||||
return *ritr;
|
||||
}
|
||||
case SELECT_TARGET_RANDOM:
|
||||
{
|
||||
std::list<Unit*>::iterator itr = targetList.begin();
|
||||
std::advance(itr, urand(position, targetList.size() - 1));
|
||||
return *itr;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
|
||||
|
||||
// Select the targets satifying the predicate.
|
||||
// predicate shall extend std::unary_function<Unit*, bool>
|
||||
template <class PREDICATE> void SelectTargetList(std::list<Unit*>& targetList, PREDICATE const& predicate, uint32 maxTargets, SelectAggroTarget targetType)
|
||||
{
|
||||
ThreatContainer::StorageType const& threatlist = me->getThreatManager().getThreatList();
|
||||
if (threatlist.empty())
|
||||
return;
|
||||
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
if (predicate((*itr)->getTarget()))
|
||||
targetList.push_back((*itr)->getTarget());
|
||||
|
||||
if (targetList.size() < maxTargets)
|
||||
return;
|
||||
|
||||
if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
|
||||
targetList.sort(Trinity::ObjectDistanceOrderPred(me));
|
||||
|
||||
if (targetType == SELECT_TARGET_FARTHEST || targetType == SELECT_TARGET_BOTTOMAGGRO)
|
||||
targetList.reverse();
|
||||
|
||||
if (targetType == SELECT_TARGET_RANDOM)
|
||||
Trinity::Containers::RandomResizeList(targetList, maxTargets);
|
||||
else
|
||||
targetList.resize(maxTargets);
|
||||
}
|
||||
|
||||
// Called at any Damage to any victim (before damage apply)
|
||||
virtual void DamageDealt(Unit* /*victim*/, uint32& /*damage*/, DamageEffectType /*damageType*/) { }
|
||||
|
||||
// Called at any Damage from any attacker (before damage apply)
|
||||
// Note: it for recalculation damage or special reaction at damage
|
||||
// for attack reaction use AttackedBy called for not DOT damage in Unit::DealDamage also
|
||||
virtual void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/ ) {}
|
||||
|
||||
// Called when the creature receives heal
|
||||
virtual void HealReceived(Unit* /*done_by*/, uint32& /*addhealth*/) {}
|
||||
|
||||
// Called when the unit heals
|
||||
virtual void HealDone(Unit* /*done_to*/, uint32& /*addhealth*/) {}
|
||||
|
||||
void AttackStartCaster(Unit* victim, float dist);
|
||||
|
||||
void DoAddAuraToAllHostilePlayers(uint32 spellid);
|
||||
void DoCast(uint32 spellId);
|
||||
void DoCast(Unit* victim, uint32 spellId, bool triggered = false);
|
||||
void DoCastToAllHostilePlayers(uint32 spellid, bool triggered = false);
|
||||
void DoCastVictim(uint32 spellId, bool triggered = false);
|
||||
void DoCastAOE(uint32 spellId, bool triggered = false);
|
||||
|
||||
float DoGetSpellMaxRange(uint32 spellId, bool positive = false);
|
||||
|
||||
void DoMeleeAttackIfReady();
|
||||
bool DoSpellAttackIfReady(uint32 spell);
|
||||
|
||||
static AISpellInfoType* AISpellInfo;
|
||||
static void FillAISpellInfo();
|
||||
|
||||
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*/) {}
|
||||
};
|
||||
|
||||
class PlayerAI : public UnitAI
|
||||
{
|
||||
protected:
|
||||
Player* const me;
|
||||
public:
|
||||
explicit PlayerAI(Player* player) : UnitAI((Unit*)player), me(player) {}
|
||||
|
||||
void OnCharmed(bool apply);
|
||||
};
|
||||
|
||||
class SimpleCharmedAI : public PlayerAI
|
||||
{
|
||||
public:
|
||||
void UpdateAI(uint32 diff);
|
||||
SimpleCharmedAI(Player* player): PlayerAI(player) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
268
src/server/game/AI/CreatureAI.cpp
Normal file
268
src/server/game/AI/CreatureAI.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "CreatureAIImpl.h"
|
||||
#include "Creature.h"
|
||||
#include "World.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Vehicle.h"
|
||||
#include "Log.h"
|
||||
#include "MapReference.h"
|
||||
#include "Player.h"
|
||||
#include "CreatureTextMgr.h"
|
||||
|
||||
//Disable CreatureAI when charmed
|
||||
void CreatureAI::OnCharmed(bool /*apply*/)
|
||||
{
|
||||
//me->IsAIEnabled = !apply;*/
|
||||
me->NeedChangeAI = true;
|
||||
me->IsAIEnabled = false;
|
||||
}
|
||||
|
||||
AISpellInfoType* UnitAI::AISpellInfo;
|
||||
AISpellInfoType* GetAISpellInfo(uint32 i) { return &CreatureAI::AISpellInfo[i]; }
|
||||
|
||||
void CreatureAI::Talk(uint8 id, WorldObject const* whisperTarget /*= NULL*/)
|
||||
{
|
||||
sCreatureTextMgr->SendChat(me, id, whisperTarget);
|
||||
}
|
||||
|
||||
void CreatureAI::DoZoneInCombat(Creature* creature /*= NULL*/, float maxRangeToNearestTarget /* = 50.0f*/)
|
||||
{
|
||||
if (!creature)
|
||||
creature = me;
|
||||
|
||||
if (!creature->CanHaveThreatList())
|
||||
return;
|
||||
|
||||
Map* map = creature->GetMap();
|
||||
if (!map->IsDungeon()) //use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
|
||||
{
|
||||
sLog->outError("DoZoneInCombat call for map that isn't an instance (creature entry = %d)", creature->GetTypeId() == TYPEID_UNIT ? creature->ToCreature()->GetEntry() : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Xinef: Skip creatures in evade mode
|
||||
if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim() && !creature->IsInEvadeMode())
|
||||
{
|
||||
if (Unit* nearTarget = creature->SelectNearestTarget(maxRangeToNearestTarget))
|
||||
creature->AI()->AttackStart(nearTarget);
|
||||
else if (creature->IsSummon())
|
||||
{
|
||||
if (Unit* summoner = creature->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
Unit* target = summoner->getAttackerForHelper();
|
||||
if (!target && summoner->CanHaveThreatList() && !summoner->getThreatManager().isThreatListEmpty())
|
||||
target = summoner->getThreatManager().getHostilTarget();
|
||||
if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target)))
|
||||
creature->AI()->AttackStart(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim())
|
||||
{
|
||||
sLog->outError("DoZoneInCombat called for creature that has empty threat list (creature entry = %u)", creature->GetEntry());
|
||||
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 (player->IsGameMaster())
|
||||
continue;
|
||||
|
||||
if (player->IsAlive())
|
||||
{
|
||||
creature->SetInCombatWith(player);
|
||||
player->SetInCombatWith(creature);
|
||||
creature->AddThreat(player, 0.0f);
|
||||
}
|
||||
|
||||
/* Causes certain things to never leave the threat list (Priest Lightwell, etc):
|
||||
for (Unit::ControlSet::const_iterator itr = player->m_Controlled.begin(); itr != player->m_Controlled.end(); ++itr)
|
||||
{
|
||||
creature->SetInCombatWith(*itr);
|
||||
(*itr)->SetInCombatWith(creature);
|
||||
creature->AddThreat(*itr, 0.0f);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 == true)
|
||||
return;
|
||||
m_MoveInLineOfSight_locked = true;
|
||||
MoveInLineOfSight(who);
|
||||
m_MoveInLineOfSight_locked = false;
|
||||
}
|
||||
|
||||
void CreatureAI::MoveInLineOfSight(Unit* who)
|
||||
{
|
||||
if (me->GetVictim())
|
||||
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)) // if in combat and in dist - neutral to all can actually assist other creatures
|
||||
return;
|
||||
|
||||
if (me->CanStartAttack(who))
|
||||
AttackStart(who);
|
||||
}
|
||||
|
||||
void CreatureAI::EnterEvadeMode()
|
||||
{
|
||||
if (!_EnterEvadeMode())
|
||||
return;
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_UNITS, "Creature %u enters evade mode.", me->GetEntry());
|
||||
|
||||
if (!me->GetVehicle()) // otherwise me will be in evade mode forever
|
||||
{
|
||||
if (Unit* owner = me->GetCharmerOrOwner())
|
||||
{
|
||||
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
|
||||
me->AddUnitState(UNIT_STATE_EVADE);
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
if (me->IsVehicle()) // use the same sequence of addtoworld, aireset may remove all summons!
|
||||
me->GetVehicleKit()->Reset(true);
|
||||
}
|
||||
|
||||
/*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->IsInCombat())
|
||||
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->IsInCombat())
|
||||
return false;
|
||||
|
||||
if (!me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (Unit* victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
return me->GetVictim();
|
||||
}
|
||||
// xinef: if we have any victim, just return true
|
||||
else if (me->GetVictim() && me->GetExactDist(me->GetVictim()) < 30.0f)
|
||||
return true;
|
||||
else if (me->getThreatManager().isThreatListEmpty())
|
||||
{
|
||||
EnterEvadeMode();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreatureAI::_EnterEvadeMode()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
return false;
|
||||
|
||||
// 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();
|
||||
|
||||
// sometimes bosses stuck in combat?
|
||||
me->DeleteThreatList();
|
||||
me->CombatStop(true);
|
||||
me->LoadCreaturesAddon(true);
|
||||
me->SetLootRecipient(NULL);
|
||||
me->ResetPlayerDamageReq();
|
||||
me->SetLastDamagedTime(0);
|
||||
|
||||
if (me->IsInEvadeMode())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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(pos, 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(pos, radius);
|
||||
pos.m_positionZ += flightZ;
|
||||
return me->SummonCreature(entry, pos, summonType, despawnTime);
|
||||
}
|
||||
193
src/server/game/AI/CreatureAI.h
Normal file
193
src/server/game/AI/CreatureAI.h
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_CREATUREAI_H
|
||||
#define TRINITY_CREATUREAI_H
|
||||
|
||||
#include "Creature.h"
|
||||
#include "UnitAI.h"
|
||||
#include "Common.h"
|
||||
|
||||
class WorldObject;
|
||||
class Unit;
|
||||
class Creature;
|
||||
class Player;
|
||||
class SpellInfo;
|
||||
|
||||
#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;
|
||||
|
||||
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:
|
||||
void Talk(uint8 id, WorldObject const* whisperTarget = NULL);
|
||||
explicit CreatureAI(Creature* creature) : UnitAI(creature), me(creature), m_MoveInLineOfSight_locked(false) {}
|
||||
|
||||
virtual ~CreatureAI() {}
|
||||
|
||||
/// == Reactions At =================================
|
||||
|
||||
// Called if IsVisible(Unit* who) is true at each who move, reaction at visibility zone enter
|
||||
void MoveInLineOfSight_Safe(Unit* who);
|
||||
|
||||
// Called in Creature::Update when deathstate = DEAD. Inherited classes may maniuplate the ability to respawn based on scripted events.
|
||||
virtual bool CanRespawn() { return true; }
|
||||
|
||||
// Called for reaction at stopping attack at no attackers or targets
|
||||
virtual void EnterEvadeMode();
|
||||
|
||||
// Called for reaction at enter to combat if not in combat yet (enemy can be NULL)
|
||||
virtual void EnterCombat(Unit* /*victim*/) {}
|
||||
|
||||
// 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(Unit* /*summoner*/) {}
|
||||
|
||||
virtual void SummonedCreatureDespawn(Creature* /*summon*/) {}
|
||||
virtual void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) {}
|
||||
|
||||
// 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 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*/) {}
|
||||
|
||||
void OnCharmed(bool apply);
|
||||
|
||||
// Called at reaching home after evade
|
||||
virtual void JustReachedHome() {}
|
||||
|
||||
void DoZoneInCombat(Creature* creature = NULL, float maxRangeToNearestTarget = 50.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*/) {}
|
||||
|
||||
// Called when owner attacks something
|
||||
virtual void OwnerAttacked(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 void OnSpellClick(Unit* /*clicker*/, bool& /*result*/) { }
|
||||
|
||||
virtual bool CanSeeAlways(WorldObject const* /*obj*/) { return false; }
|
||||
|
||||
virtual bool CanBeSeen(Player const* /*seer*/) { return true; }
|
||||
|
||||
protected:
|
||||
virtual void MoveInLineOfSight(Unit* /*who*/);
|
||||
|
||||
bool _EnterEvadeMode();
|
||||
|
||||
private:
|
||||
bool m_MoveInLineOfSight_locked;
|
||||
};
|
||||
|
||||
enum Permitions
|
||||
{
|
||||
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
|
||||
80
src/server/game/AI/CreatureAIFactory.h
Normal file
80
src/server/game/AI/CreatureAIFactory.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_CREATUREAIFACTORY_H
|
||||
#define TRINITY_CREATUREAIFACTORY_H
|
||||
|
||||
//#include "Policies/Singleton.h"
|
||||
#include "ObjectRegistry.h"
|
||||
#include "FactoryHolder.h"
|
||||
#include "GameObjectAI.h"
|
||||
|
||||
struct SelectableAI : public FactoryHolder<CreatureAI>, public Permissible<Creature>
|
||||
{
|
||||
SelectableAI(const char* id) : FactoryHolder<CreatureAI>(id) {}
|
||||
};
|
||||
|
||||
template<class REAL_AI>
|
||||
struct CreatureAIFactory : public SelectableAI
|
||||
{
|
||||
CreatureAIFactory(const char* name) : SelectableAI(name) {}
|
||||
|
||||
CreatureAI* Create(void*) const;
|
||||
|
||||
int Permit(const Creature* c) const { return REAL_AI::Permissible(c); }
|
||||
};
|
||||
|
||||
template<class REAL_AI>
|
||||
inline CreatureAI*
|
||||
CreatureAIFactory<REAL_AI>::Create(void* data) const
|
||||
{
|
||||
Creature* creature = reinterpret_cast<Creature*>(data);
|
||||
return (new REAL_AI(creature));
|
||||
}
|
||||
|
||||
typedef FactoryHolder<CreatureAI> CreatureAICreator;
|
||||
typedef FactoryHolder<CreatureAI>::FactoryHolderRegistry CreatureAIRegistry;
|
||||
typedef FactoryHolder<CreatureAI>::FactoryHolderRepository CreatureAIRepository;
|
||||
|
||||
//GO
|
||||
struct SelectableGameObjectAI : public FactoryHolder<GameObjectAI>, public Permissible<GameObject>
|
||||
{
|
||||
SelectableGameObjectAI(const char* id) : FactoryHolder<GameObjectAI>(id) {}
|
||||
};
|
||||
|
||||
template<class REAL_GO_AI>
|
||||
struct GameObjectAIFactory : public SelectableGameObjectAI
|
||||
{
|
||||
GameObjectAIFactory(const char* name) : SelectableGameObjectAI(name) {}
|
||||
|
||||
GameObjectAI* Create(void*) const;
|
||||
|
||||
int Permit(const GameObject* g) const { return REAL_GO_AI::Permissible(g); }
|
||||
};
|
||||
|
||||
template<class REAL_GO_AI>
|
||||
inline GameObjectAI*
|
||||
GameObjectAIFactory<REAL_GO_AI>::Create(void* data) const
|
||||
{
|
||||
GameObject* go = reinterpret_cast<GameObject*>(data);
|
||||
return (new REAL_GO_AI(go));
|
||||
}
|
||||
|
||||
typedef FactoryHolder<GameObjectAI> GameObjectAICreator;
|
||||
typedef FactoryHolder<GameObjectAI>::FactoryHolderRegistry GameObjectAIRegistry;
|
||||
typedef FactoryHolder<GameObjectAI>::FactoryHolderRepository GameObjectAIRepository;
|
||||
#endif
|
||||
347
src/server/game/AI/CreatureAIImpl.h
Normal file
347
src/server/game/AI/CreatureAIImpl.h
Normal file
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "Common.h"
|
||||
#include "Define.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2)
|
||||
{
|
||||
return (urand(0, 1)) ? v1 : v2;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3)
|
||||
{
|
||||
switch (urand(0, 2))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4)
|
||||
{
|
||||
switch (urand(0, 3))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5)
|
||||
{
|
||||
switch (urand(0, 4))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6)
|
||||
{
|
||||
switch (urand(0, 5))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7)
|
||||
{
|
||||
switch (urand(0, 6))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8)
|
||||
{
|
||||
switch (urand(0, 7))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9)
|
||||
{
|
||||
switch (urand(0, 8))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10)
|
||||
{
|
||||
switch (urand(0, 9))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11)
|
||||
{
|
||||
switch (urand(0, 10))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12)
|
||||
{
|
||||
switch (urand(0, 11))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13)
|
||||
{
|
||||
switch (urand(0, 12))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14)
|
||||
{
|
||||
switch (urand(0, 13))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
case 13: return v14;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14, const T& v15)
|
||||
{
|
||||
switch (urand(0, 14))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
case 13: return v14;
|
||||
case 14: return v15;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14, const T& v15, const T& v16)
|
||||
{
|
||||
switch (urand(0, 15))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
case 13: return v14;
|
||||
case 14: return v15;
|
||||
case 15: return v16;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
||||
59
src/server/game/AI/CreatureAIRegistry.cpp
Normal file
59
src/server/game/AI/CreatureAIRegistry.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "ReactorAI.h"
|
||||
#include "CombatAI.h"
|
||||
#include "GuardAI.h"
|
||||
#include "PetAI.h"
|
||||
#include "TotemAI.h"
|
||||
#include "RandomMovementGenerator.h"
|
||||
#include "MovementGeneratorImpl.h"
|
||||
#include "CreatureAIRegistry.h"
|
||||
#include "WaypointMovementGenerator.h"
|
||||
#include "CreatureAIFactory.h"
|
||||
#include "SmartAI.h"
|
||||
|
||||
//#include "CreatureAIImpl.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<GameObjectAI>("GameObjectAI"))->RegisterSelf();
|
||||
(new GameObjectAIFactory<SmartGameObjectAI>("SmartGameObjectAI"))->RegisterSelf();
|
||||
|
||||
(new MovementGeneratorFactory<RandomMovementGenerator<Creature> >(RANDOM_MOTION_TYPE))->RegisterSelf();
|
||||
(new MovementGeneratorFactory<WaypointMovementGenerator<Creature> >(WAYPOINT_MOTION_TYPE))->RegisterSelf();
|
||||
}
|
||||
}
|
||||
|
||||
27
src/server/game/AI/CreatureAIRegistry.h
Normal file
27
src/server/game/AI/CreatureAIRegistry.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_CREATUREAIREGISTRY_H
|
||||
#define TRINITY_CREATUREAIREGISTRY_H
|
||||
|
||||
namespace AIRegistry
|
||||
{
|
||||
void Initialize(void);
|
||||
}
|
||||
#endif
|
||||
|
||||
154
src/server/game/AI/CreatureAISelector.cpp
Normal file
154
src/server/game/AI/CreatureAISelector.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "Creature.h"
|
||||
#include "CreatureAISelector.h"
|
||||
#include "PassiveAI.h"
|
||||
|
||||
#include "MovementGenerator.h"
|
||||
#include "Pet.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "CreatureAIFactory.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
namespace FactorySelector
|
||||
{
|
||||
CreatureAI* selectAI(Creature* creature)
|
||||
{
|
||||
const CreatureAICreator* ai_factory = NULL;
|
||||
CreatureAIRegistry& ai_registry(*CreatureAIRepository::instance());
|
||||
|
||||
// xinef: if we have controlable guardian, define petai for players as they can steer him, otherwise db / normal ai
|
||||
// xinef: dont remember why i changed this qq commented out as may break some quests
|
||||
if (creature->IsPet()/* || (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN) && ((Guardian*)creature)->GetOwner()->GetTypeId() == TYPEID_PLAYER)*/)
|
||||
ai_factory = ai_registry.GetRegistryItem("PetAI");
|
||||
|
||||
//scriptname in db
|
||||
if (!ai_factory)
|
||||
if (CreatureAI* scriptedAI = sScriptMgr->GetCreatureAI(creature))
|
||||
return scriptedAI;
|
||||
|
||||
// AIname in db
|
||||
std::string ainame=creature->GetAIName();
|
||||
if (!ai_factory && !ainame.empty())
|
||||
ai_factory = ai_registry.GetRegistryItem(ainame);
|
||||
|
||||
// select by NPC flags
|
||||
if (!ai_factory)
|
||||
{
|
||||
if (creature->IsVehicle())
|
||||
ai_factory = ai_registry.GetRegistryItem("VehicleAI");
|
||||
else if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN) && ((Guardian*)creature)->GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
ai_factory = ai_registry.GetRegistryItem("PetAI");
|
||||
else if (creature->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK))
|
||||
ai_factory = ai_registry.GetRegistryItem("NullCreatureAI");
|
||||
else if (creature->IsGuard())
|
||||
ai_factory = ai_registry.GetRegistryItem("GuardAI");
|
||||
else if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
|
||||
ai_factory = ai_registry.GetRegistryItem("PetAI");
|
||||
else if (creature->IsTotem())
|
||||
ai_factory = ai_registry.GetRegistryItem("TotemAI");
|
||||
else if (creature->IsTrigger())
|
||||
{
|
||||
if (creature->m_spells[0])
|
||||
ai_factory = ai_registry.GetRegistryItem("TriggerAI");
|
||||
else
|
||||
ai_factory = ai_registry.GetRegistryItem("NullCreatureAI");
|
||||
}
|
||||
else if (creature->IsCritter() && !creature->HasUnitTypeMask(UNIT_MASK_GUARDIAN))
|
||||
ai_factory = ai_registry.GetRegistryItem("CritterAI");
|
||||
}
|
||||
|
||||
// select by permit check
|
||||
if (!ai_factory)
|
||||
{
|
||||
int best_val = -1;
|
||||
typedef CreatureAIRegistry::RegistryMapType RMT;
|
||||
RMT const& l = ai_registry.GetRegisteredItems();
|
||||
for (RMT::const_iterator iter = l.begin(); iter != l.end(); ++iter)
|
||||
{
|
||||
const CreatureAICreator* factory = iter->second;
|
||||
const SelectableAI* p = dynamic_cast<const SelectableAI*>(factory);
|
||||
ASSERT(p);
|
||||
int val = p->Permit(creature);
|
||||
if (val > best_val)
|
||||
{
|
||||
best_val = val;
|
||||
ai_factory = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// select NullCreatureAI if not another cases
|
||||
// xinef: unused
|
||||
// ainame = (ai_factory == NULL) ? "NullCreatureAI" : ai_factory->key();
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "Creature %u used AI is %s.", creature->GetGUIDLow(), ainame.c_str());
|
||||
return (ai_factory == NULL ? new NullCreatureAI(creature) : ai_factory->Create(creature));
|
||||
}
|
||||
|
||||
MovementGenerator* selectMovementGenerator(Creature* creature)
|
||||
{
|
||||
MovementGeneratorRegistry& mv_registry(*MovementGeneratorRepository::instance());
|
||||
ASSERT(creature->GetCreatureTemplate());
|
||||
const MovementGeneratorCreator* mv_factory = mv_registry.GetRegistryItem(creature->GetDefaultMovementType());
|
||||
|
||||
/* if (mv_factory == NULL)
|
||||
{
|
||||
int best_val = -1;
|
||||
StringVector l;
|
||||
mv_registry.GetRegisteredItems(l);
|
||||
for (StringVector::iterator iter = l.begin(); iter != l.end(); ++iter)
|
||||
{
|
||||
const MovementGeneratorCreator *factory = mv_registry.GetRegistryItem((*iter).c_str());
|
||||
const SelectableMovement *p = dynamic_cast<const SelectableMovement *>(factory);
|
||||
ASSERT(p != NULL);
|
||||
int val = p->Permit(creature);
|
||||
if (val > best_val)
|
||||
{
|
||||
best_val = val;
|
||||
mv_factory = p;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return (mv_factory == NULL ? NULL : mv_factory->Create(creature));
|
||||
|
||||
}
|
||||
|
||||
GameObjectAI* SelectGameObjectAI(GameObject* go)
|
||||
{
|
||||
const GameObjectAICreator* ai_factory = NULL;
|
||||
GameObjectAIRegistry& ai_registry(*GameObjectAIRepository::instance());
|
||||
|
||||
if (GameObjectAI* scriptedAI = sScriptMgr->GetGameObjectAI(go))
|
||||
return scriptedAI;
|
||||
|
||||
ai_factory = ai_registry.GetRegistryItem(go->GetAIName());
|
||||
|
||||
//future goAI types go here
|
||||
|
||||
// xinef: unused
|
||||
//std::string ainame = (ai_factory == NULL || go->GetScriptId()) ? "NullGameObjectAI" : ai_factory->key();
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "GameObject %u used AI is %s.", go->GetGUIDLow(), ainame.c_str());
|
||||
|
||||
return (ai_factory == NULL ? new NullGameObjectAI(go) : ai_factory->Create(go));
|
||||
}
|
||||
}
|
||||
|
||||
35
src/server/game/AI/CreatureAISelector.h
Normal file
35
src/server/game/AI/CreatureAISelector.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_CREATUREAISELECTOR_H
|
||||
#define TRINITY_CREATUREAISELECTOR_H
|
||||
|
||||
class CreatureAI;
|
||||
class Creature;
|
||||
class MovementGenerator;
|
||||
class GameObjectAI;
|
||||
class GameObject;
|
||||
|
||||
namespace FactorySelector
|
||||
{
|
||||
CreatureAI* selectAI(Creature*);
|
||||
MovementGenerator* selectMovementGenerator(Creature*);
|
||||
GameObjectAI* SelectGameObjectAI(GameObject*);
|
||||
}
|
||||
#endif
|
||||
|
||||
668
src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
Normal file
668
src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
Normal file
@@ -0,0 +1,668 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
*
|
||||
*
|
||||
*
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#include "ScriptedCreature.h"
|
||||
#include "Item.h"
|
||||
#include "Spell.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "Cell.h"
|
||||
#include "CellImpl.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "TemporarySummon.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()
|
||||
{
|
||||
while (!storage_.empty())
|
||||
{
|
||||
Creature* summon = ObjectAccessor::GetCreature(*me, storage_.front());
|
||||
storage_.pop_front();
|
||||
if (summon)
|
||||
summon->DespawnOrUnsummon();
|
||||
}
|
||||
}
|
||||
|
||||
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 NULL;
|
||||
}
|
||||
|
||||
ScriptedAI::ScriptedAI(Creature* creature) : CreatureAI(creature),
|
||||
me(creature),
|
||||
IsFleeing(false),
|
||||
_evadeCheckCooldown(2500),
|
||||
_isCombatMovementAllowed(true)
|
||||
{
|
||||
_isHeroic = me->GetMap()->IsHeroic();
|
||||
_difficulty = Difficulty(me->GetMap()->GetSpawnMode());
|
||||
}
|
||||
|
||||
void ScriptedAI::AttackStartNoMove(Unit* who)
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
|
||||
if (me->Attack(who, true))
|
||||
DoStartNoMovement(who);
|
||||
}
|
||||
|
||||
void ScriptedAI::AttackStart(Unit* who)
|
||||
{
|
||||
if (IsCombatMovementAllowed())
|
||||
CreatureAI::AttackStart(who);
|
||||
else
|
||||
AttackStartNoMove(who);
|
||||
}
|
||||
|
||||
void ScriptedAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
//Check if we have a current target
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
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::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))
|
||||
{
|
||||
sLog->outError("TSCR: Invalid soundId %u used in DoPlaySoundToSet (Source: TypeId %u, GUID %u)", soundId, source->GetTypeId(), source->GetGUIDLow());
|
||||
return;
|
||||
}
|
||||
|
||||
source->PlayDirectSound(soundId);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 NULL;
|
||||
|
||||
//Silenced so we can't cast
|
||||
if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
|
||||
return NULL;
|
||||
|
||||
//Using the extended script system we first create a list of viable spells
|
||||
SpellInfo const* apSpell[CREATURE_MAX_SPELLS];
|
||||
memset(apSpell, 0, CREATURE_MAX_SPELLS * sizeof(SpellInfo*));
|
||||
|
||||
uint32 spellCount = 0;
|
||||
|
||||
SpellInfo const* tempSpell = NULL;
|
||||
|
||||
//Check if each spell is viable(set it to null if not)
|
||||
for (uint32 i = 0; i < CREATURE_MAX_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 NULL;
|
||||
|
||||
return apSpell[urand(0, spellCount - 1)];
|
||||
}
|
||||
|
||||
void ScriptedAI::DoResetThreat()
|
||||
{
|
||||
if (!me->CanHaveThreatList() || me->getThreatManager().isThreatListEmpty())
|
||||
{
|
||||
sLog->outError("DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = %d)", me->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
me->getThreatManager().resetAllAggro();
|
||||
}
|
||||
|
||||
float ScriptedAI::DoGetThreat(Unit* unit)
|
||||
{
|
||||
if (!unit)
|
||||
return 0.0f;
|
||||
return me->getThreatManager().getThreat(unit);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoModifyThreatPercent(Unit* unit, int32 pct)
|
||||
{
|
||||
if (!unit)
|
||||
return;
|
||||
me->getThreatManager().modifyThreatPercent(unit, pct);
|
||||
}
|
||||
|
||||
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
|
||||
sLog->outError("TSCR: Creature " UI64FMTD " (Entry: %u) Tried to teleport non-player unit (Type: %u GUID: " UI64FMTD ") to x: %f y:%f z: %f o: %f. Aborted.", me->GetGUID(), me->GetEntry(), unit->GetTypeId(), unit->GetGUID(), 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 = NULL;
|
||||
Trinity::MostHPMissingInRange u_check(me, range, minHPDiff);
|
||||
Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, unit, u_check);
|
||||
me->VisitNearbyObject(range, searcher);
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
std::list<Creature*> ScriptedAI::DoFindFriendlyCC(float range)
|
||||
{
|
||||
std::list<Creature*> list;
|
||||
Trinity::FriendlyCCedInRange u_check(me, range);
|
||||
Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, list, u_check);
|
||||
me->VisitNearbyObject(range, searcher);
|
||||
return list;
|
||||
}
|
||||
|
||||
std::list<Creature*> ScriptedAI::DoFindFriendlyMissingBuff(float range, uint32 uiSpellid)
|
||||
{
|
||||
std::list<Creature*> list;
|
||||
Trinity::FriendlyMissingBuffInRange u_check(me, range, uiSpellid);
|
||||
Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, list, u_check);
|
||||
me->VisitNearbyObject(range, searcher);
|
||||
return list;
|
||||
}
|
||||
|
||||
Player* ScriptedAI::GetPlayerAtMinimumRange(float minimumRange)
|
||||
{
|
||||
Player* player = NULL;
|
||||
|
||||
CellCoord pair(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY()));
|
||||
Cell cell(pair);
|
||||
cell.SetNoCreate();
|
||||
|
||||
Trinity::PlayerAtMinimumRangeAway check(me, minimumRange);
|
||||
Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway> searcher(me, player, check);
|
||||
TypeContainerVisitor<Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway>, GridTypeMapContainer> visitor(searcher);
|
||||
|
||||
cell.Visit(pair, visitor, *me->GetMap(), *me, 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->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(mainHand));
|
||||
|
||||
if (offHand >= 0)
|
||||
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(offHand));
|
||||
|
||||
if (ranged >= 0)
|
||||
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, uint32(ranged));
|
||||
}
|
||||
|
||||
void ScriptedAI::SetCombatMovement(bool allowMovement)
|
||||
{
|
||||
_isCombatMovementAllowed = allowMovement;
|
||||
}
|
||||
|
||||
enum eNPCs
|
||||
{
|
||||
NPC_BROODLORD = 12017,
|
||||
NPC_JAN_ALAI = 23578,
|
||||
NPC_SARTHARION = 28860,
|
||||
NPC_FREYA = 32906,
|
||||
};
|
||||
|
||||
bool ScriptedAI::EnterEvadeIfOutOfCombatArea()
|
||||
{
|
||||
if (me->IsInEvadeMode() || !me->IsInCombat())
|
||||
return false;
|
||||
|
||||
if (_evadeCheckCooldown == time(NULL))
|
||||
return false;
|
||||
_evadeCheckCooldown = time(NULL);
|
||||
|
||||
if (!CheckEvadeIfOutOfCombatArea())
|
||||
return false;
|
||||
|
||||
EnterEvadeMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
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->GetDistance(itr->GetSource()) > maxdist || !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 NULL;
|
||||
}
|
||||
|
||||
// BossAI - for instanced bosses
|
||||
|
||||
BossAI::BossAI(Creature* creature, uint32 bossId) : ScriptedAI(creature),
|
||||
instance(creature->GetInstanceScript()),
|
||||
summons(creature),
|
||||
_boundary(instance ? instance->GetBossBoundary(bossId) : NULL),
|
||||
_bossId(bossId)
|
||||
{
|
||||
}
|
||||
|
||||
void BossAI::_Reset()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
return;
|
||||
|
||||
me->ResetLootMode();
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
if (instance)
|
||||
instance->SetBossState(_bossId, NOT_STARTED);
|
||||
}
|
||||
|
||||
void BossAI::_JustDied()
|
||||
{
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
if (instance)
|
||||
{
|
||||
instance->SetBossState(_bossId, DONE);
|
||||
instance->SaveToDB();
|
||||
}
|
||||
}
|
||||
|
||||
void BossAI::_EnterCombat()
|
||||
{
|
||||
me->setActive(true);
|
||||
DoZoneInCombat();
|
||||
if (instance)
|
||||
{
|
||||
// bosses do not respawn, check only on enter combat
|
||||
if (!instance->CheckRequiredBosses(_bossId))
|
||||
{
|
||||
EnterEvadeMode();
|
||||
return;
|
||||
}
|
||||
instance->SetBossState(_bossId, IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
|
||||
void BossAI::TeleportCheaters()
|
||||
{
|
||||
float x, y, z;
|
||||
me->GetPosition(x, y, z);
|
||||
|
||||
ThreatContainer::StorageType threatList = me->getThreatManager().getThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
if (Unit* target = (*itr)->getTarget())
|
||||
if (target->GetTypeId() == TYPEID_PLAYER && !CheckBoundary(target))
|
||||
target->NearTeleportTo(x, y, z, 0);
|
||||
}
|
||||
|
||||
bool BossAI::CheckBoundary(Unit* who)
|
||||
{
|
||||
if (!GetBoundary() || !who)
|
||||
return true;
|
||||
|
||||
for (BossBoundaryMap::const_iterator itr = GetBoundary()->begin(); itr != GetBoundary()->end(); ++itr)
|
||||
{
|
||||
switch (itr->first)
|
||||
{
|
||||
case BOUNDARY_N:
|
||||
if (who->GetPositionX() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_S:
|
||||
if (who->GetPositionX() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_E:
|
||||
if (who->GetPositionY() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_W:
|
||||
if (who->GetPositionY() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_NW:
|
||||
if (who->GetPositionX() + who->GetPositionY() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_SE:
|
||||
if (who->GetPositionX() + who->GetPositionY() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_NE:
|
||||
if (who->GetPositionX() - who->GetPositionY() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_SW:
|
||||
if (who->GetPositionX() - who->GetPositionY() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BossAI::JustSummoned(Creature* summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
if (me->IsInCombat())
|
||||
DoZoneInCombat(summon);
|
||||
}
|
||||
|
||||
void BossAI::SummonedCreatureDespawn(Creature* summon)
|
||||
{
|
||||
summons.Despawn(summon);
|
||||
}
|
||||
|
||||
void BossAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
ExecuteEvent(eventId);
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
// 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::_EnterCombat()
|
||||
{
|
||||
Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true);
|
||||
if (target)
|
||||
AttackStart(target);
|
||||
}
|
||||
|
||||
void WorldBossAI::JustSummoned(Creature* summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
Unit* target = SelectTarget(SELECT_TARGET_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)
|
||||
{
|
||||
return source->FindNearestGameObject(entry, maxSearchRange);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
457
src/server/game/AI/ScriptedAI/ScriptedCreature.h
Normal file
457
src/server/game/AI/ScriptedAI/ScriptedCreature.h
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "InstanceScript.h"
|
||||
|
||||
#define CAST_AI(a, b) (dynamic_cast<a*>(b))
|
||||
|
||||
class InstanceScript;
|
||||
|
||||
class SummonList
|
||||
{
|
||||
public:
|
||||
typedef std::list<uint64> 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();
|
||||
|
||||
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_;
|
||||
Trinity::Containers::RandomResizeList<uint64, Predicate>(listCopy, predicate, max);
|
||||
for (StorageType::iterator i = listCopy.begin(); i != listCopy.end(); ++i)
|
||||
{
|
||||
Creature* summon = ObjectAccessor::GetCreature(*me, *i);
|
||||
if (summon)
|
||||
{
|
||||
if (summon->IsAIEnabled)
|
||||
summon->AI()->DoAction(info);
|
||||
}
|
||||
else
|
||||
storage_.remove(*i);
|
||||
}
|
||||
}
|
||||
|
||||
void DoZoneInCombat(uint32 entry = 0);
|
||||
void RemoveNotExisting();
|
||||
bool HasEntry(uint32 entry) const;
|
||||
uint32 GetEntryCount(uint32 entry) const;
|
||||
void Respawn();
|
||||
Creature* GetCreatureWithEntry(uint32 entry) const;
|
||||
|
||||
private:
|
||||
Creature* me;
|
||||
StorageType storage_;
|
||||
};
|
||||
|
||||
class EntryCheckPredicate
|
||||
{
|
||||
public:
|
||||
EntryCheckPredicate(uint32 entry) : _entry(entry) {}
|
||||
bool operator()(uint64 guid) { return GUID_ENPART(guid) == _entry; }
|
||||
|
||||
private:
|
||||
uint32 _entry;
|
||||
};
|
||||
|
||||
class PlayerOrPetCheck
|
||||
{
|
||||
public:
|
||||
bool operator() (WorldObject* unit) const
|
||||
{
|
||||
if (unit->GetTypeId() != TYPEID_PLAYER)
|
||||
if (!IS_PLAYER_GUID(unit->ToUnit()->GetOwnerGUID()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct ScriptedAI : public CreatureAI
|
||||
{
|
||||
explicit ScriptedAI(Creature* creature);
|
||||
virtual ~ScriptedAI() {}
|
||||
|
||||
// *************
|
||||
//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*/) {}
|
||||
|
||||
//Called at World update tick
|
||||
virtual void UpdateAI(uint32 diff);
|
||||
|
||||
//Called at creature death
|
||||
void JustDied(Unit* /*killer*/) {}
|
||||
|
||||
//Called at creature killing another unit
|
||||
void KilledUnit(Unit* /*victim*/) {}
|
||||
|
||||
// Called when the creature summon successfully other creature
|
||||
void JustSummoned(Creature* /*summon*/) {}
|
||||
|
||||
// Called when a summoned creature is despawned
|
||||
void SummonedCreatureDespawn(Creature* /*summon*/) {}
|
||||
|
||||
// Called when hit by a spell
|
||||
void SpellHit(Unit* /*caster*/, SpellInfo const* /*spell*/) {}
|
||||
|
||||
// Called when spell hits a target
|
||||
void SpellHitTarget(Unit* /*target*/, SpellInfo const* /*spell*/) {}
|
||||
|
||||
//Called at waypoint reached or PointMovement end
|
||||
void MovementInform(uint32 /*type*/, uint32 /*id*/) {}
|
||||
|
||||
// Called when AI is temporarily replaced or put back when possess is applied or removed
|
||||
void OnPossess(bool /*apply*/) {}
|
||||
|
||||
// *************
|
||||
// Variables
|
||||
// *************
|
||||
|
||||
//Pointer to creature we are manipulating
|
||||
Creature* me;
|
||||
|
||||
//For fleeing
|
||||
bool IsFleeing;
|
||||
|
||||
// *************
|
||||
//Pure virtual functions
|
||||
// *************
|
||||
|
||||
//Called at creature reset either by death or evade
|
||||
void Reset() {}
|
||||
|
||||
//Called at creature aggro either by MoveInLOS or Attack Start
|
||||
void EnterCombat(Unit* /*victim*/) {}
|
||||
|
||||
// Called before EnterCombat even before the creature is in combat.
|
||||
void AttackStart(Unit* /*target*/);
|
||||
|
||||
// *************
|
||||
//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();
|
||||
|
||||
//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);
|
||||
|
||||
//Drops all threat to 0%. Does not remove players from the threat list
|
||||
void DoResetThreat();
|
||||
|
||||
float DoGetThreat(Unit* unit);
|
||||
void DoModifyThreatPercent(Unit* unit, int32 pct);
|
||||
|
||||
//Teleports a player without dropping threat (only teleports to same map)
|
||||
void DoTeleportPlayer(Unit* unit, float x, float y, float z, float o);
|
||||
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 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);
|
||||
|
||||
// Used to control if MoveChase() is to be used or not in AttackStart(). Some creatures does not chase victims
|
||||
// NOTE: If you use SetCombatMovement while the creature is in combat, it will do NOTHING - This only affects AttackStart
|
||||
// You should make the necessary to make it happen so.
|
||||
// Remember that if you modified _isCombatMovementAllowed (e.g: using SetCombatMovement) it will not be reset at Reset().
|
||||
// It will keep the last value you set.
|
||||
void SetCombatMovement(bool allowMovement);
|
||||
bool IsCombatMovementAllowed() const { return _isCombatMovementAllowed; }
|
||||
|
||||
bool EnterEvadeIfOutOfCombatArea();
|
||||
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;
|
||||
|
||||
private:
|
||||
Difficulty _difficulty;
|
||||
uint32 _evadeCheckCooldown;
|
||||
bool _isCombatMovementAllowed;
|
||||
bool _isHeroic;
|
||||
};
|
||||
|
||||
class BossAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
BossAI(Creature* creature, uint32 bossId);
|
||||
virtual ~BossAI() {}
|
||||
|
||||
InstanceScript* const instance;
|
||||
BossBoundaryMap const* GetBoundary() const { return _boundary; }
|
||||
|
||||
void JustSummoned(Creature* summon);
|
||||
void SummonedCreatureDespawn(Creature* summon);
|
||||
|
||||
virtual void UpdateAI(uint32 diff);
|
||||
|
||||
// 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() { _Reset(); }
|
||||
void EnterCombat(Unit* /*who*/) { _EnterCombat(); }
|
||||
void JustDied(Unit* /*killer*/) { _JustDied(); }
|
||||
void JustReachedHome() { _JustReachedHome(); }
|
||||
|
||||
protected:
|
||||
void _Reset();
|
||||
void _EnterCombat();
|
||||
void _JustDied();
|
||||
void _JustReachedHome() { me->setActive(false); }
|
||||
|
||||
bool CheckInRoom()
|
||||
{
|
||||
if (CheckBoundary(me))
|
||||
return true;
|
||||
|
||||
EnterEvadeMode();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckBoundary(Unit* who);
|
||||
void TeleportCheaters();
|
||||
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
|
||||
private:
|
||||
BossBoundaryMap const* const _boundary;
|
||||
uint32 const _bossId;
|
||||
};
|
||||
|
||||
class WorldBossAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
WorldBossAI(Creature* creature);
|
||||
virtual ~WorldBossAI() {}
|
||||
|
||||
void JustSummoned(Creature* summon);
|
||||
void SummonedCreatureDespawn(Creature* summon);
|
||||
|
||||
virtual void UpdateAI(uint32 diff);
|
||||
|
||||
// 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() { _Reset(); }
|
||||
void EnterCombat(Unit* /*who*/) { _EnterCombat(); }
|
||||
void JustDied(Unit* /*killer*/) { _JustDied(); }
|
||||
|
||||
protected:
|
||||
void _Reset();
|
||||
void _EnterCombat();
|
||||
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);
|
||||
void GetCreatureListWithEntryInGrid(std::list<Creature*>& list, WorldObject* source, uint32 entry, float maxSearchRange);
|
||||
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& list, WorldObject* source, uint32 entry, float maxSearchRange);
|
||||
|
||||
#endif // SCRIPTEDCREATURE_H_
|
||||
584
src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp
Normal file
584
src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp
Normal file
@@ -0,0 +1,584 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
/* ScriptData
|
||||
SDName: Npc_EscortAI
|
||||
SD%Complete: 100
|
||||
SDComment:
|
||||
SDCategory: Npc
|
||||
EndScriptData */
|
||||
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ScriptedEscortAI.h"
|
||||
#include "Group.h"
|
||||
#include "Player.h"
|
||||
|
||||
enum ePoints
|
||||
{
|
||||
POINT_LAST_POINT = 0xFFFFFF,
|
||||
POINT_HOME = 0xFFFFFE
|
||||
};
|
||||
|
||||
npc_escortAI::npc_escortAI(Creature* creature) : ScriptedAI(creature),
|
||||
m_uiPlayerGUID(0),
|
||||
m_uiWPWaitTimer(1000),
|
||||
m_uiPlayerCheckTimer(0),
|
||||
m_uiEscortState(STATE_ESCORT_NONE),
|
||||
MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE),
|
||||
m_pQuestForEscort(NULL),
|
||||
m_bIsActiveAttacker(true),
|
||||
m_bIsRunning(false),
|
||||
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 (IsCombatMovementAllowed())
|
||||
me->GetMotionMaster()->MoveChase(who);
|
||||
}
|
||||
}
|
||||
|
||||
//see followerAI
|
||||
bool npc_escortAI::AssistPlayerInCombat(Unit* who)
|
||||
{
|
||||
if (!who || !who->GetVictim())
|
||||
return false;
|
||||
|
||||
//experimental (unknown) flag not present
|
||||
if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPEFLAGS_AID_PLAYERS))
|
||||
return false;
|
||||
|
||||
//not a player
|
||||
if (!who->GetVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
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) && AssistPlayerInCombat(who))
|
||||
return;
|
||||
|
||||
if (me->CanStartAttack(who))
|
||||
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 != NULL; 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 (!IsCombatMovementAllowed())
|
||||
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->SetWalk(false);
|
||||
me->GetHomePosition(x, y, z, o);
|
||||
me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z);
|
||||
}
|
||||
|
||||
void npc_escortAI::EnterEvadeMode()
|
||||
{
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
me->CombatStop(true);
|
||||
me->SetLootRecipient(NULL);
|
||||
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING))
|
||||
{
|
||||
AddEscortState(STATE_ESCORT_RETURNING);
|
||||
ReturnToLastPoint();
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: EscortAI has left combat and is now returning to last point");
|
||||
}
|
||||
else
|
||||
{
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
if (HasImmuneToNPCFlags)
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC);
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool npc_escortAI::IsPlayerOrGroupInRange()
|
||||
{
|
||||
if (Player* player = GetPlayerForEscort())
|
||||
{
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next())
|
||||
if (Player* member = groupRef->GetSource())
|
||||
if (me->IsWithinDistInMap(member, GetMaxPlayerDistance()))
|
||||
return true;
|
||||
}
|
||||
else if (me->IsWithinDistInMap(player, GetMaxPlayerDistance()))
|
||||
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(JUST_DIED);
|
||||
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(JUST_DIED);
|
||||
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)
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: EscortAI has returned to original position before combat");
|
||||
|
||||
me->SetWalk(!m_bIsRunning);
|
||||
RemoveEscortState(STATE_ESCORT_RETURNING);
|
||||
|
||||
if (!m_uiWPWaitTimer)
|
||||
m_uiWPWaitTimer = 1;
|
||||
}
|
||||
else if (pointId == POINT_HOME)
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: 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);
|
||||
}
|
||||
}
|
||||
|
||||
void npc_escortAI::SetRun(bool on)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
if (!m_bIsRunning)
|
||||
me->SetWalk(false);
|
||||
else
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: EscortAI attempt to set run mode, but is already running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_bIsRunning)
|
||||
me->SetWalk(true);
|
||||
else
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: EscortAI attempt to set walk mode, but is already walking.");
|
||||
}
|
||||
|
||||
m_bIsRunning = on;
|
||||
}
|
||||
|
||||
//TODO: get rid of this many variables passed in function.
|
||||
void npc_escortAI::Start(bool isActiveAttacker /* = true*/, bool run /* = false */, uint64 playerGUID /* = 0 */, Quest const* quest /* = NULL */, bool instantRespawn /* = false */, bool canLoopPath /* = false */, bool resetWaypoints /* = true */)
|
||||
{
|
||||
if (me->GetVictim())
|
||||
{
|
||||
sLog->outError("TSCR ERROR: EscortAI (script: %s, creature entry: %u) attempts to Start while in combat", me->GetScriptName().c_str(), me->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING))
|
||||
{
|
||||
sLog->outError("TSCR: EscortAI (script: %s, creature entry: %u) attempts to Start while already escorting", me->GetScriptName().c_str(), me->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ScriptWP && resetWaypoints) // sd2 never adds wp in script, but tc does
|
||||
{
|
||||
if (!WaypointList.empty())
|
||||
WaypointList.clear();
|
||||
FillPointMovementListForCreature();
|
||||
}
|
||||
|
||||
if (WaypointList.empty())
|
||||
{
|
||||
sLog->outErrorDb("TSCR: EscortAI (script: %s, creature entry: %u) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: %u).",
|
||||
me->GetScriptName().c_str(), me->GetEntry(), quest ? quest->GetQuestId() : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
//set variables
|
||||
m_bIsActiveAttacker = isActiveAttacker;
|
||||
m_bIsRunning = run;
|
||||
|
||||
m_uiPlayerGUID = playerGUID;
|
||||
m_pQuestForEscort = quest;
|
||||
|
||||
m_bCanInstantRespawn = instantRespawn;
|
||||
m_bCanReturnToStart = canLoopPath;
|
||||
|
||||
//if (m_bCanReturnToStart && m_bCanInstantRespawn)
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: 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();
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle.");
|
||||
}
|
||||
|
||||
//disable npcflags
|
||||
me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
||||
if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC))
|
||||
{
|
||||
HasImmuneToNPCFlags = true;
|
||||
me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC);
|
||||
}
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: EscortAI started with " UI64FMTD " waypoints. ActiveAttacker = %d, Run = %d, PlayerGUID = " UI64FMTD "", uint64(WaypointList.size()), m_bIsActiveAttacker, m_bIsRunning, m_uiPlayerGUID);
|
||||
|
||||
CurrentWP = WaypointList.begin();
|
||||
|
||||
//Set initial speed
|
||||
if (m_bIsRunning)
|
||||
me->SetWalk(false);
|
||||
else
|
||||
me->SetWalk(true);
|
||||
|
||||
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;
|
||||
|
||||
size_t const size = WaypointList.size();
|
||||
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;
|
||||
|
||||
uint32 startingWaypointId = CurrentWP->id;
|
||||
|
||||
// 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
|
||||
{
|
||||
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 = (WaypointList.size() - startingWaypointId)*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 (fabs(offset.x) >= 0xFF || fabs(offset.y) >= 0xFF || fabs(offset.z) >= 0x7F)
|
||||
{
|
||||
// offset is too big, split points
|
||||
continueLoop = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (continueLoop)
|
||||
continue;
|
||||
}
|
||||
// everything ok
|
||||
*points = pVector;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/server/game/AI/ScriptedAI/ScriptedEscortAI.h
Normal file
128
src/server/game/AI/ScriptedAI/ScriptedEscortAI.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_ESCORTAI_H
|
||||
#define SC_ESCORTAI_H
|
||||
|
||||
#include "ScriptSystem.h"
|
||||
|
||||
#define DEFAULT_MAX_PLAYER_DISTANCE 50
|
||||
|
||||
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() {}
|
||||
|
||||
// CreatureAI functions
|
||||
void AttackStart(Unit* who);
|
||||
|
||||
void MoveInLineOfSight(Unit* who);
|
||||
|
||||
void JustDied(Unit*);
|
||||
|
||||
void JustRespawned();
|
||||
|
||||
void ReturnToLastPoint();
|
||||
|
||||
void EnterEvadeMode();
|
||||
|
||||
void UpdateAI(uint32 diff); //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);
|
||||
|
||||
// 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);
|
||||
|
||||
virtual void WaypointReached(uint32 pointId) = 0;
|
||||
virtual void WaypointStart(uint32 /*pointId*/) {}
|
||||
|
||||
void Start(bool isActiveAttacker = true, bool run = false, uint64 playerGUID = 0, Quest const* quest = NULL, bool instantRespawn = false, bool canLoopPath = false, bool resetWaypoints = true);
|
||||
|
||||
void SetRun(bool on = true);
|
||||
void SetEscortPaused(bool on);
|
||||
|
||||
bool HasEscortState(uint32 escortState) { return (m_uiEscortState & escortState); }
|
||||
virtual bool IsEscorted() { 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; }
|
||||
uint64 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 AssistPlayerInCombat(Unit* who);
|
||||
bool IsPlayerOrGroupInRange();
|
||||
void FillPointMovementListForCreature();
|
||||
|
||||
uint64 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_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK)
|
||||
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
|
||||
|
||||
362
src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
Normal file
362
src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
/* ScriptData
|
||||
SDName: FollowerAI
|
||||
SD%Complete: 50
|
||||
SDComment: This AI is under development
|
||||
SDCategory: Npc
|
||||
EndScriptData */
|
||||
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ScriptedFollowerAI.h"
|
||||
#include "Group.h"
|
||||
#include "Player.h"
|
||||
|
||||
const float MAX_PLAYER_DISTANCE = 100.0f;
|
||||
|
||||
enum ePoints
|
||||
{
|
||||
POINT_COMBAT_START = 0xFFFFFF
|
||||
};
|
||||
|
||||
FollowerAI::FollowerAI(Creature* creature) : ScriptedAI(creature),
|
||||
m_uiLeaderGUID(0),
|
||||
m_uiUpdateFollowTimer(2500),
|
||||
m_uiFollowState(STATE_FOLLOW_NONE),
|
||||
m_pQuestForFollow(NULL)
|
||||
{}
|
||||
|
||||
void FollowerAI::AttackStart(Unit* who)
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
|
||||
if (me->Attack(who, true))
|
||||
{
|
||||
// This is done in Unit::Attack function which wont bug npcs by not adding threat upon combat start...
|
||||
//me->AddThreat(who, 0.0f);
|
||||
//me->SetInCombatWith(who);
|
||||
//who->SetInCombatWith(me);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_FOLLOW))
|
||||
me->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||
|
||||
if (IsCombatMovementAllowed())
|
||||
me->GetMotionMaster()->MoveChase(who);
|
||||
}
|
||||
}
|
||||
|
||||
//This part provides assistance to a player that are attacked by who, even if out of normal aggro range
|
||||
//It will cause me to attack who that are attacking _any_ player (which has been confirmed may happen also on offi)
|
||||
//The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate.
|
||||
bool FollowerAI::AssistPlayerInCombat(Unit* who)
|
||||
{
|
||||
if (!who || !who->GetVictim())
|
||||
return false;
|
||||
|
||||
//experimental (unknown) flag not present
|
||||
if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPEFLAGS_AID_PLAYERS))
|
||||
return false;
|
||||
|
||||
//not a player
|
||||
if (!who->GetVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (me->IsFriendlyTo(who))
|
||||
return false;
|
||||
|
||||
//too far away and no free sight?
|
||||
if (me->IsWithinDistInMap(who, MAX_PLAYER_DISTANCE) && me->IsWithinLOSInMap(who))
|
||||
{
|
||||
AttackStart(who);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FollowerAI::MoveInLineOfSight(Unit* who)
|
||||
{
|
||||
if (me->GetVictim())
|
||||
return;
|
||||
|
||||
if (!me->HasUnitState(UNIT_STATE_STUNNED) && who->isTargetableForAttack(true, me) && who->isInAccessiblePlaceFor(me))
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && AssistPlayerInCombat(who))
|
||||
return;
|
||||
|
||||
if (me->CanStartAttack(who))
|
||||
AttackStart(who);
|
||||
}
|
||||
|
||||
void FollowerAI::JustDied(Unit* /*pKiller*/)
|
||||
{
|
||||
if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || !m_uiLeaderGUID || !m_pQuestForFollow)
|
||||
return;
|
||||
|
||||
//TODO: need a better check for quests with time limit.
|
||||
if (Player* player = GetLeaderForFollower())
|
||||
{
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next())
|
||||
{
|
||||
if (Player* member = groupRef->GetSource())
|
||||
{
|
||||
if (member->IsInMap(player) && member->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
||||
member->FailQuest(m_pQuestForFollow->GetQuestId());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (player->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
||||
player->FailQuest(m_pQuestForFollow->GetQuestId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FollowerAI::JustRespawned()
|
||||
{
|
||||
m_uiFollowState = STATE_FOLLOW_NONE;
|
||||
|
||||
if (!IsCombatMovementAllowed())
|
||||
SetCombatMovement(true);
|
||||
|
||||
if (me->getFaction() != me->GetCreatureTemplate()->faction)
|
||||
me->setFaction(me->GetCreatureTemplate()->faction);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void FollowerAI::EnterEvadeMode()
|
||||
{
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
me->CombatStop(true);
|
||||
me->SetLootRecipient(NULL);
|
||||
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI left combat, returning to CombatStartPosition.");
|
||||
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
|
||||
{
|
||||
float fPosX, fPosY, fPosZ;
|
||||
me->GetPosition(fPosX, fPosY, fPosZ);
|
||||
me->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void FollowerAI::UpdateAI(uint32 uiDiff)
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && !me->GetVictim())
|
||||
{
|
||||
if (m_uiUpdateFollowTimer <= uiDiff)
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_COMPLETE) && !HasFollowState(STATE_FOLLOW_POSTEVENT))
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI is set completed, despawns.");
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
bool bIsMaxRangeExceeded = true;
|
||||
|
||||
if (Player* player = GetLeaderForFollower())
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_RETURNING))
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI is returning to leader.");
|
||||
|
||||
RemoveFollowState(STATE_FOLLOW_RETURNING);
|
||||
me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next())
|
||||
{
|
||||
Player* member = groupRef->GetSource();
|
||||
|
||||
if (member && me->IsWithinDistInMap(member, MAX_PLAYER_DISTANCE))
|
||||
{
|
||||
bIsMaxRangeExceeded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me->IsWithinDistInMap(player, MAX_PLAYER_DISTANCE))
|
||||
bIsMaxRangeExceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsMaxRangeExceeded)
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI failed because player/group was to far away or not found");
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
m_uiUpdateFollowTimer = 1000;
|
||||
}
|
||||
else
|
||||
m_uiUpdateFollowTimer -= uiDiff;
|
||||
}
|
||||
|
||||
UpdateFollowerAI(uiDiff);
|
||||
}
|
||||
|
||||
void FollowerAI::UpdateFollowerAI(uint32 /*uiDiff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
void FollowerAI::MovementInform(uint32 motionType, uint32 pointId)
|
||||
{
|
||||
if (motionType != POINT_MOTION_TYPE || !HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
return;
|
||||
|
||||
if (pointId == POINT_COMBAT_START)
|
||||
{
|
||||
if (GetLeaderForFollower())
|
||||
{
|
||||
if (!HasFollowState(STATE_FOLLOW_PAUSED))
|
||||
AddFollowState(STATE_FOLLOW_RETURNING);
|
||||
}
|
||||
else
|
||||
me->DespawnOrUnsummon();
|
||||
}
|
||||
}
|
||||
|
||||
void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, const Quest* quest)
|
||||
{
|
||||
if (me->GetVictim())
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI attempt to StartFollow while in combat.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
{
|
||||
sLog->outError("TSCR: FollowerAI attempt to StartFollow while already following.");
|
||||
return;
|
||||
}
|
||||
|
||||
//set variables
|
||||
m_uiLeaderGUID = player->GetGUID();
|
||||
|
||||
if (factionForFollower)
|
||||
me->setFaction(factionForFollower);
|
||||
|
||||
m_pQuestForFollow = quest;
|
||||
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
|
||||
{
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle.");
|
||||
}
|
||||
|
||||
me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
||||
|
||||
AddFollowState(STATE_FOLLOW_INPROGRESS);
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI start follow %s (GUID " UI64FMTD ")", player->GetName().c_str(), m_uiLeaderGUID);
|
||||
}
|
||||
|
||||
Player* FollowerAI::GetLeaderForFollower()
|
||||
{
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, m_uiLeaderGUID))
|
||||
{
|
||||
if (player->IsAlive())
|
||||
return player;
|
||||
else
|
||||
{
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next())
|
||||
{
|
||||
Player* member = groupRef->GetSource();
|
||||
|
||||
if (member && me->IsWithinDistInMap(member, MAX_PLAYER_DISTANCE) && member->IsAlive())
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI GetLeader changed and returned new leader.");
|
||||
m_uiLeaderGUID = member->GetGUID();
|
||||
return member;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI GetLeader can not find suitable leader.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void FollowerAI::SetFollowComplete(bool bWithEndEvent)
|
||||
{
|
||||
if (me->HasUnitState(UNIT_STATE_FOLLOW))
|
||||
{
|
||||
me->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||
|
||||
me->StopMoving();
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
|
||||
if (bWithEndEvent)
|
||||
AddFollowState(STATE_FOLLOW_POSTEVENT);
|
||||
else
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_POSTEVENT))
|
||||
RemoveFollowState(STATE_FOLLOW_POSTEVENT);
|
||||
}
|
||||
|
||||
AddFollowState(STATE_FOLLOW_COMPLETE);
|
||||
}
|
||||
|
||||
void FollowerAI::SetFollowPaused(bool paused)
|
||||
{
|
||||
if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || HasFollowState(STATE_FOLLOW_COMPLETE))
|
||||
return;
|
||||
|
||||
if (paused)
|
||||
{
|
||||
AddFollowState(STATE_FOLLOW_PAUSED);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_FOLLOW))
|
||||
{
|
||||
me->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||
|
||||
me->StopMoving();
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveFollowState(STATE_FOLLOW_PAUSED);
|
||||
|
||||
if (Player* leader = GetLeaderForFollower())
|
||||
me->GetMotionMaster()->MoveFollow(leader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
}
|
||||
}
|
||||
67
src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h
Normal file
67
src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_FOLLOWERAI_H
|
||||
#define SC_FOLLOWERAI_H
|
||||
|
||||
#include "ScriptSystem.h"
|
||||
|
||||
enum eFollowState
|
||||
{
|
||||
STATE_FOLLOW_NONE = 0x000,
|
||||
STATE_FOLLOW_INPROGRESS = 0x001, //must always have this state for any follow
|
||||
STATE_FOLLOW_RETURNING = 0x002, //when returning to combat start after being in combat
|
||||
STATE_FOLLOW_PAUSED = 0x004, //disables following
|
||||
STATE_FOLLOW_COMPLETE = 0x008, //follow is completed and may end
|
||||
STATE_FOLLOW_PREEVENT = 0x010, //not implemented (allow pre event to run, before follow is initiated)
|
||||
STATE_FOLLOW_POSTEVENT = 0x020 //can be set at complete and allow post event to run
|
||||
};
|
||||
|
||||
class FollowerAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
explicit FollowerAI(Creature* creature);
|
||||
~FollowerAI() {}
|
||||
|
||||
//virtual void WaypointReached(uint32 uiPointId) = 0;
|
||||
|
||||
void MovementInform(uint32 motionType, uint32 pointId);
|
||||
|
||||
void AttackStart(Unit*);
|
||||
|
||||
void MoveInLineOfSight(Unit*);
|
||||
|
||||
void EnterEvadeMode();
|
||||
|
||||
void JustDied(Unit*);
|
||||
|
||||
void JustRespawned();
|
||||
|
||||
void UpdateAI(uint32); //the "internal" update, calls UpdateFollowerAI()
|
||||
virtual void UpdateFollowerAI(uint32); //used when it's needed to add code in update (abilities, scripted events, etc)
|
||||
|
||||
void StartFollow(Player* player, uint32 factionForFollower = 0, const Quest* quest = NULL);
|
||||
|
||||
void SetFollowPaused(bool bPaused); //if special event require follow mode to hold/resume during the follow
|
||||
void SetFollowComplete(bool bWithEndEvent = false);
|
||||
|
||||
bool HasFollowState(uint32 uiFollowState) { return (m_uiFollowState & uiFollowState); }
|
||||
|
||||
protected:
|
||||
Player* GetLeaderForFollower();
|
||||
|
||||
private:
|
||||
void AddFollowState(uint32 uiFollowState) { m_uiFollowState |= uiFollowState; }
|
||||
void RemoveFollowState(uint32 uiFollowState) { m_uiFollowState &= ~uiFollowState; }
|
||||
|
||||
bool AssistPlayerInCombat(Unit* who);
|
||||
|
||||
uint64 m_uiLeaderGUID;
|
||||
uint32 m_uiUpdateFollowTimer;
|
||||
uint32 m_uiFollowState;
|
||||
|
||||
const Quest* m_pQuestForFollow; //normally we have a quest
|
||||
};
|
||||
|
||||
#endif
|
||||
90
src/server/game/AI/ScriptedAI/ScriptedGossip.h
Normal file
90
src/server/game/AI/ScriptedAI/ScriptedGossip.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/* Copyright (C)
|
||||
*
|
||||
*
|
||||
*
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_GOSSIP_H
|
||||
#define SC_GOSSIP_H
|
||||
|
||||
#include "GossipDef.h"
|
||||
#include "QuestDef.h"
|
||||
|
||||
// Gossip Item Text
|
||||
#define GOSSIP_TEXT_BROWSE_GOODS "I'd like to browse your goods."
|
||||
#define GOSSIP_TEXT_TRAIN "Train me!"
|
||||
|
||||
enum eTradeskill
|
||||
{
|
||||
// Skill defines
|
||||
TRADESKILL_ALCHEMY = 1,
|
||||
TRADESKILL_BLACKSMITHING = 2,
|
||||
TRADESKILL_COOKING = 3,
|
||||
TRADESKILL_ENCHANTING = 4,
|
||||
TRADESKILL_ENGINEERING = 5,
|
||||
TRADESKILL_FIRSTAID = 6,
|
||||
TRADESKILL_HERBALISM = 7,
|
||||
TRADESKILL_LEATHERWORKING = 8,
|
||||
TRADESKILL_POISONS = 9,
|
||||
TRADESKILL_TAILORING = 10,
|
||||
TRADESKILL_MINING = 11,
|
||||
TRADESKILL_FISHING = 12,
|
||||
TRADESKILL_SKINNING = 13,
|
||||
TRADESKILL_JEWLCRAFTING = 14,
|
||||
TRADESKILL_INSCRIPTION = 15,
|
||||
|
||||
TRADESKILL_LEVEL_NONE = 0,
|
||||
TRADESKILL_LEVEL_APPRENTICE = 1,
|
||||
TRADESKILL_LEVEL_JOURNEYMAN = 2,
|
||||
TRADESKILL_LEVEL_EXPERT = 3,
|
||||
TRADESKILL_LEVEL_ARTISAN = 4,
|
||||
TRADESKILL_LEVEL_MASTER = 5,
|
||||
TRADESKILL_LEVEL_GRAND_MASTER = 6,
|
||||
|
||||
// Gossip defines
|
||||
GOSSIP_ACTION_TRADE = 1,
|
||||
GOSSIP_ACTION_TRAIN = 2,
|
||||
GOSSIP_ACTION_TAXI = 3,
|
||||
GOSSIP_ACTION_GUILD = 4,
|
||||
GOSSIP_ACTION_BATTLE = 5,
|
||||
GOSSIP_ACTION_BANK = 6,
|
||||
GOSSIP_ACTION_INN = 7,
|
||||
GOSSIP_ACTION_HEAL = 8,
|
||||
GOSSIP_ACTION_TABARD = 9,
|
||||
GOSSIP_ACTION_AUCTION = 10,
|
||||
GOSSIP_ACTION_INN_INFO = 11,
|
||||
GOSSIP_ACTION_UNLEARN = 12,
|
||||
GOSSIP_ACTION_INFO_DEF = 1000,
|
||||
|
||||
GOSSIP_SENDER_MAIN = 1,
|
||||
GOSSIP_SENDER_INN_INFO = 2,
|
||||
GOSSIP_SENDER_INFO = 3,
|
||||
GOSSIP_SENDER_SEC_PROFTRAIN = 4,
|
||||
GOSSIP_SENDER_SEC_CLASSTRAIN = 5,
|
||||
GOSSIP_SENDER_SEC_BATTLEINFO = 6,
|
||||
GOSSIP_SENDER_SEC_BANK = 7,
|
||||
GOSSIP_SENDER_SEC_INN = 8,
|
||||
GOSSIP_SENDER_SEC_MAILBOX = 9,
|
||||
GOSSIP_SENDER_SEC_STABLEMASTER = 10
|
||||
};
|
||||
|
||||
// Defined fuctions to use with player.
|
||||
|
||||
// This fuction add's a menu item,
|
||||
// a - Icon Id
|
||||
// b - Text
|
||||
// c - Sender(this is to identify the current Menu with this item)
|
||||
// d - Action (identifys this Menu Item)
|
||||
// e - Text to be displayed in pop up box
|
||||
// f - Money value in pop up box
|
||||
#define ADD_GOSSIP_ITEM(a, b, c, d) PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, a, b, c, d, "", 0)
|
||||
#define ADD_GOSSIP_ITEM_EXTENDED(a, b, c, d, e, f, g) PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, a, b, c, d, e, f, g)
|
||||
|
||||
// This fuction Sends the current menu to show to client, a - NPCTEXTID(uint32), b - npc guid(uint64)
|
||||
#define SEND_GOSSIP_MENU(a, b) PlayerTalkClass->SendGossipMenu(a, b)
|
||||
|
||||
// Closes the Menu
|
||||
#define CLOSE_GOSSIP_MENU() PlayerTalkClass->SendCloseGossip()
|
||||
|
||||
#endif
|
||||
1202
src/server/game/AI/SmartScripts/SmartAI.cpp
Normal file
1202
src/server/game/AI/SmartScripts/SmartAI.cpp
Normal file
File diff suppressed because it is too large
Load Diff
274
src/server/game/AI/SmartScripts/SmartAI.h
Normal file
274
src/server/game/AI/SmartScripts/SmartAI.h
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_SMARTAI_H
|
||||
#define TRINITY_SMARTAI_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "Unit.h"
|
||||
#include "Spell.h"
|
||||
|
||||
#include "SmartScript.h"
|
||||
#include "SmartScriptMgr.h"
|
||||
#include "GameObjectAI.h"
|
||||
|
||||
enum SmartEscortState
|
||||
{
|
||||
SMART_ESCORT_NONE = 0x000, //nothing in progress
|
||||
SMART_ESCORT_ESCORTING = 0x001, //escort is in progress
|
||||
SMART_ESCORT_RETURNING = 0x002, //escort is returning after being in combat
|
||||
SMART_ESCORT_PAUSED = 0x004 //will not proceed with waypoints before state is removed
|
||||
};
|
||||
|
||||
enum SmartEscortVars
|
||||
{
|
||||
SMART_ESCORT_MAX_PLAYER_DIST = 60,
|
||||
SMART_MAX_AID_DIST = SMART_ESCORT_MAX_PLAYER_DIST / 2,
|
||||
};
|
||||
|
||||
class SmartAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
~SmartAI(){};
|
||||
explicit SmartAI(Creature* c);
|
||||
|
||||
// Start moving to the desired MovePoint
|
||||
void StartPath(bool run = false, uint32 path = 0, bool repeat = false, Unit* invoker = NULL);
|
||||
bool LoadPath(uint32 entry);
|
||||
void PausePath(uint32 delay, bool forced = false);
|
||||
void StopPath(uint32 DespawnTime = 0, uint32 quest = 0, bool fail = false);
|
||||
void EndPath(bool fail = false);
|
||||
void ResumePath();
|
||||
WayPoint* GetNextWayPoint();
|
||||
void GenerateWayPointArray(Movement::PointsArray* points);
|
||||
bool HasEscortState(uint32 uiEscortState) { return (mEscortState & uiEscortState); }
|
||||
void AddEscortState(uint32 uiEscortState) { mEscortState |= uiEscortState; }
|
||||
virtual bool IsEscorted() { return (mEscortState & SMART_ESCORT_ESCORTING); }
|
||||
void RemoveEscortState(uint32 uiEscortState) { mEscortState &= ~uiEscortState; }
|
||||
void SetAutoAttack(bool on) { mCanAutoAttack = on; }
|
||||
void SetCombatMove(bool on);
|
||||
bool CanCombatMove() { return mCanCombatMove; }
|
||||
void SetFollow(Unit* target, float dist = 0.0f, float angle = 0.0f, uint32 credit = 0, uint32 end = 0, uint32 creditType = 0, bool aliveState = true);
|
||||
void StopFollow(bool complete);
|
||||
|
||||
void SetScript9(SmartScriptHolder& e, uint32 entry, Unit* invoker);
|
||||
SmartScript* GetScript() { return &mScript; }
|
||||
bool IsEscortInvokerInRange();
|
||||
|
||||
// Called when creature is spawned or respawned
|
||||
void JustRespawned();
|
||||
|
||||
// Called at reaching home after evade, InitializeAI(), EnterEvadeMode() for resetting variables
|
||||
void JustReachedHome();
|
||||
|
||||
// Called for reaction at enter to combat if not in combat yet (enemy can be NULL)
|
||||
void EnterCombat(Unit* enemy);
|
||||
|
||||
// Called for reaction at stopping attack at no attackers or targets
|
||||
void EnterEvadeMode();
|
||||
|
||||
// Called when the creature is killed
|
||||
void JustDied(Unit* killer);
|
||||
|
||||
// Called when the creature kills a unit
|
||||
void KilledUnit(Unit* victim);
|
||||
|
||||
// Called when the creature summon successfully other creature
|
||||
void JustSummoned(Creature* creature);
|
||||
|
||||
// Tell creature to attack and follow the victim
|
||||
void AttackStart(Unit* who);
|
||||
|
||||
// Called if IsVisible(Unit* who) is true at each *who move, reaction at visibility zone enter
|
||||
void MoveInLineOfSight(Unit* who);
|
||||
|
||||
// Called when hit by a spell
|
||||
void SpellHit(Unit* unit, const SpellInfo* spellInfo);
|
||||
|
||||
// Called when spell hits a target
|
||||
void SpellHitTarget(Unit* target, const SpellInfo* spellInfo);
|
||||
|
||||
// Called at any Damage from any attacker (before damage apply)
|
||||
void DamageTaken(Unit* done_by, uint32 &damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask);
|
||||
|
||||
// Called when the creature receives heal
|
||||
void HealReceived(Unit* doneBy, uint32& addhealth);
|
||||
|
||||
// Called at World update tick
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
// Called at text emote receive from player
|
||||
void ReceiveEmote(Player* player, uint32 textEmote);
|
||||
|
||||
// Called at waypoint reached or point movement finished
|
||||
void MovementInform(uint32 MovementType, uint32 Data);
|
||||
|
||||
// Called when creature is summoned by another unit
|
||||
void IsSummonedBy(Unit* summoner);
|
||||
|
||||
// Called at any Damage to any victim (before damage apply)
|
||||
void DamageDealt(Unit* doneTo, uint32& damage, DamageEffectType damagetyp);
|
||||
|
||||
// Called when a summoned creature dissapears (UnSommoned)
|
||||
void SummonedCreatureDespawn(Creature* unit);
|
||||
|
||||
// called when the corpse of this creature gets removed
|
||||
void CorpseRemoved(uint32& respawnDelay);
|
||||
|
||||
// Called at World update tick if creature is charmed
|
||||
void UpdateAIWhileCharmed(const uint32 diff);
|
||||
|
||||
// Called when a Player/Creature enters the creature (vehicle)
|
||||
void PassengerBoarded(Unit* who, int8 seatId, bool apply);
|
||||
|
||||
// Called when gets initialized, when creature is added to world
|
||||
void InitializeAI();
|
||||
|
||||
// Called when creature gets charmed by another unit
|
||||
void OnCharmed(bool apply);
|
||||
|
||||
// Called when victim is in line of sight
|
||||
bool CanAIAttack(const Unit* who) const;
|
||||
|
||||
// Used in scripts to share variables
|
||||
void DoAction(int32 param = 0);
|
||||
|
||||
// Used in scripts to share variables
|
||||
uint32 GetData(uint32 id = 0) const;
|
||||
|
||||
// Used in scripts to share variables
|
||||
void SetData(uint32 id, uint32 value);
|
||||
|
||||
// Used in scripts to share variables
|
||||
void SetGUID(uint64 guid, int32 id = 0);
|
||||
|
||||
// Used in scripts to share variables
|
||||
uint64 GetGUID(int32 id = 0) const;
|
||||
|
||||
//core related
|
||||
static int32 Permissible(const Creature*);
|
||||
|
||||
// Called at movepoint reached
|
||||
void MovepointReached(uint32 id);
|
||||
|
||||
// Makes the creature run/walk
|
||||
void SetRun(bool run = true);
|
||||
|
||||
void SetFly(bool fly = true);
|
||||
|
||||
void SetSwim(bool swim = true);
|
||||
|
||||
void SetInvincibilityHpLevel(uint32 level) { mInvincibilityHpLevel = level; }
|
||||
|
||||
void sGossipHello(Player* player);
|
||||
void sGossipSelect(Player* player, uint32 sender, uint32 action);
|
||||
void sGossipSelectCode(Player* player, uint32 sender, uint32 action, const char* code);
|
||||
void sQuestAccept(Player* player, Quest const* quest);
|
||||
//void sQuestSelect(Player* player, Quest const* quest);
|
||||
//void sQuestComplete(Player* player, Quest const* quest);
|
||||
void sQuestReward(Player* player, Quest const* quest, uint32 opt);
|
||||
void sOnGameEvent(bool start, uint16 eventId);
|
||||
|
||||
uint32 mEscortQuestID;
|
||||
|
||||
void SetDespawnTime (uint32 t)
|
||||
{
|
||||
mDespawnTime = t;
|
||||
mDespawnState = t ? 1 : 0;
|
||||
}
|
||||
void StartDespawn() { mDespawnState = 2; }
|
||||
|
||||
void OnSpellClick(Unit* clicker, bool& result);
|
||||
|
||||
// Xinef
|
||||
void SetWPPauseTimer(uint32 time) { mWPPauseTimer = time; }
|
||||
void SetForcedCombatMove(float dist);
|
||||
|
||||
private:
|
||||
uint32 mFollowCreditType;
|
||||
uint32 mFollowArrivedTimer;
|
||||
uint32 mFollowCredit;
|
||||
uint32 mFollowArrivedEntry;
|
||||
bool mFollowArrivedAlive;
|
||||
uint64 mFollowGuid;
|
||||
float mFollowDist;
|
||||
float mFollowAngle;
|
||||
|
||||
void ReturnToLastOOCPos();
|
||||
void UpdatePath(const uint32 diff);
|
||||
SmartScript mScript;
|
||||
WPPath* mWayPoints;
|
||||
uint32 mEscortState;
|
||||
uint32 mCurrentWPID;
|
||||
bool mWPReached;
|
||||
bool mOOCReached;
|
||||
uint32 mWPPauseTimer;
|
||||
WayPoint* mLastWP;
|
||||
uint32 mEscortNPCFlags;
|
||||
uint32 GetWPCount() { return mWayPoints ? mWayPoints->size() : 0; }
|
||||
bool mCanRepeatPath;
|
||||
bool mRun;
|
||||
bool mCanAutoAttack;
|
||||
bool mCanCombatMove;
|
||||
bool mForcedPaused;
|
||||
uint32 mInvincibilityHpLevel;
|
||||
|
||||
bool AssistPlayerInCombat(Unit* who);
|
||||
|
||||
uint32 mDespawnTime;
|
||||
uint32 mDespawnState;
|
||||
void UpdateDespawn(const uint32 diff);
|
||||
uint32 mEscortInvokerCheckTimer;
|
||||
bool mJustReset;
|
||||
|
||||
// Xinef: Vehicle conditions
|
||||
void CheckConditions(const uint32 diff);
|
||||
ConditionList conditions;
|
||||
uint32 m_ConditionsTimer;
|
||||
};
|
||||
|
||||
class SmartGameObjectAI : public GameObjectAI
|
||||
{
|
||||
public:
|
||||
SmartGameObjectAI(GameObject* g) : GameObjectAI(g) {}
|
||||
~SmartGameObjectAI() {}
|
||||
|
||||
void UpdateAI(uint32 diff);
|
||||
void InitializeAI();
|
||||
void Reset();
|
||||
SmartScript* GetScript() { return &mScript; }
|
||||
static int32 Permissible(const GameObject* g);
|
||||
|
||||
bool GossipHello(Player* player, bool reportUse);
|
||||
bool GossipSelect(Player* player, uint32 sender, uint32 action);
|
||||
bool GossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, const char* /*code*/);
|
||||
bool QuestAccept(Player* player, Quest const* quest);
|
||||
bool QuestReward(Player* player, Quest const* quest, uint32 opt);
|
||||
void Destroyed(Player* player, uint32 eventId);
|
||||
void SetData(uint32 id, uint32 value);
|
||||
void SetScript9(SmartScriptHolder& e, uint32 entry, Unit* invoker);
|
||||
void OnGameEvent(bool start, uint16 eventId);
|
||||
void OnStateChanged(uint32 state, Unit* unit);
|
||||
void EventInform(uint32 eventId);
|
||||
void SpellHit(Unit* unit, const SpellInfo* spellInfo);
|
||||
|
||||
protected:
|
||||
SmartScript mScript;
|
||||
};
|
||||
#endif
|
||||
4304
src/server/game/AI/SmartScripts/SmartScript.cpp
Normal file
4304
src/server/game/AI/SmartScripts/SmartScript.cpp
Normal file
File diff suppressed because it is too large
Load Diff
343
src/server/game/AI/SmartScripts/SmartScript.h
Normal file
343
src/server/game/AI/SmartScripts/SmartScript.h
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 TRINITY_SMARTSCRIPT_H
|
||||
#define TRINITY_SMARTSCRIPT_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "Unit.h"
|
||||
#include "Spell.h"
|
||||
#include "GridNotifiers.h"
|
||||
|
||||
#include "SmartScriptMgr.h"
|
||||
//#include "SmartAI.h"
|
||||
|
||||
class SmartScript
|
||||
{
|
||||
public:
|
||||
SmartScript();
|
||||
~SmartScript();
|
||||
|
||||
void OnInitialize(WorldObject* obj, AreaTriggerEntry const* at = NULL);
|
||||
void GetScript();
|
||||
void FillScript(SmartAIEventList e, WorldObject* obj, AreaTriggerEntry const* at);
|
||||
|
||||
void ProcessEventsFor(SMART_EVENT e, Unit* unit = NULL, uint32 var0 = 0, uint32 var1 = 0, bool bvar = false, const SpellInfo* spell = NULL, GameObject* gob = NULL);
|
||||
void ProcessEvent(SmartScriptHolder& e, Unit* unit = NULL, uint32 var0 = 0, uint32 var1 = 0, bool bvar = false, const SpellInfo* spell = NULL, GameObject* gob = NULL);
|
||||
bool CheckTimer(SmartScriptHolder const& e) const;
|
||||
void RecalcTimer(SmartScriptHolder& e, uint32 min, uint32 max);
|
||||
void UpdateTimer(SmartScriptHolder& e, uint32 const diff);
|
||||
void InitTimer(SmartScriptHolder& e);
|
||||
void ProcessAction(SmartScriptHolder& e, Unit* unit = NULL, uint32 var0 = 0, uint32 var1 = 0, bool bvar = false, const SpellInfo* spell = NULL, GameObject* gob = NULL);
|
||||
void ProcessTimedAction(SmartScriptHolder& e, uint32 const& min, uint32 const& max, Unit* unit = NULL, uint32 var0 = 0, uint32 var1 = 0, bool bvar = false, const SpellInfo* spell = NULL, GameObject* gob = NULL);
|
||||
ObjectList* GetTargets(SmartScriptHolder const& e, Unit* invoker = NULL);
|
||||
ObjectList* GetWorldObjectsInDist(float dist);
|
||||
void InstallTemplate(SmartScriptHolder const& e);
|
||||
SmartScriptHolder CreateEvent(SMART_EVENT e, uint32 event_flags, uint32 event_param1, uint32 event_param2, uint32 event_param3, uint32 event_param4, SMART_ACTION action, uint32 action_param1, uint32 action_param2, uint32 action_param3, uint32 action_param4, uint32 action_param5, uint32 action_param6, SMARTAI_TARGETS t, uint32 target_param1, uint32 target_param2, uint32 target_param3, uint32 phaseMask = 0);
|
||||
void AddEvent(SMART_EVENT e, uint32 event_flags, uint32 event_param1, uint32 event_param2, uint32 event_param3, uint32 event_param4, SMART_ACTION action, uint32 action_param1, uint32 action_param2, uint32 action_param3, uint32 action_param4, uint32 action_param5, uint32 action_param6, SMARTAI_TARGETS t, uint32 target_param1, uint32 target_param2, uint32 target_param3, uint32 phaseMask = 0);
|
||||
void SetPathId(uint32 id) { mPathId = id; }
|
||||
uint32 GetPathId() const { return mPathId; }
|
||||
WorldObject* GetBaseObject()
|
||||
{
|
||||
WorldObject* obj = NULL;
|
||||
if (me)
|
||||
obj = me;
|
||||
else if (go)
|
||||
obj = go;
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool IsUnit(WorldObject* obj)
|
||||
{
|
||||
return obj && obj->IsInWorld() && (obj->GetTypeId() == TYPEID_UNIT || obj->GetTypeId() == TYPEID_PLAYER);
|
||||
}
|
||||
|
||||
bool IsPlayer(WorldObject* obj)
|
||||
{
|
||||
return obj && obj->IsInWorld() && obj->GetTypeId() == TYPEID_PLAYER;
|
||||
}
|
||||
|
||||
bool IsCreature(WorldObject* obj)
|
||||
{
|
||||
return obj && obj->IsInWorld() && obj->GetTypeId() == TYPEID_UNIT;
|
||||
}
|
||||
|
||||
bool IsGameObject(WorldObject* obj)
|
||||
{
|
||||
return obj && obj->IsInWorld() && obj->GetTypeId() == TYPEID_GAMEOBJECT;
|
||||
}
|
||||
|
||||
void OnUpdate(const uint32 diff);
|
||||
void OnMoveInLineOfSight(Unit* who);
|
||||
|
||||
Unit* DoSelectLowestHpFriendly(float range, uint32 MinHPDiff);
|
||||
void DoFindFriendlyCC(std::list<Creature*>& _list, float range);
|
||||
void DoFindFriendlyMissingBuff(std::list<Creature*>& list, float range, uint32 spellid);
|
||||
Unit* DoFindClosestFriendlyInRange(float range, bool playerOnly);
|
||||
|
||||
void StoreTargetList(ObjectList* targets, uint32 id)
|
||||
{
|
||||
if (!targets)
|
||||
return;
|
||||
|
||||
if (mTargetStorage->find(id) != mTargetStorage->end())
|
||||
{
|
||||
// check if already stored
|
||||
if ((*mTargetStorage)[id]->Equals(targets))
|
||||
return;
|
||||
|
||||
delete (*mTargetStorage)[id];
|
||||
}
|
||||
|
||||
(*mTargetStorage)[id] = new ObjectGuidList(targets, GetBaseObject());
|
||||
}
|
||||
|
||||
bool IsSmart(Creature* c = NULL)
|
||||
{
|
||||
bool smart = true;
|
||||
if (c && c->GetAIName() != "SmartAI")
|
||||
smart = false;
|
||||
|
||||
if (!me || me->GetAIName() != "SmartAI")
|
||||
smart = false;
|
||||
|
||||
if (!smart)
|
||||
sLog->outErrorDb("SmartScript: Action target Creature(entry: %u) is not using SmartAI, action skipped to prevent crash.", c ? c->GetEntry() : (me ? me->GetEntry() : 0));
|
||||
|
||||
return smart;
|
||||
}
|
||||
|
||||
bool IsSmartGO(GameObject* g = NULL)
|
||||
{
|
||||
bool smart = true;
|
||||
if (g && g->GetAIName() != "SmartGameObjectAI")
|
||||
smart = false;
|
||||
|
||||
if (!go || go->GetAIName() != "SmartGameObjectAI")
|
||||
smart = false;
|
||||
if (!smart)
|
||||
sLog->outErrorDb("SmartScript: Action target GameObject(entry: %u) is not using SmartGameObjectAI, action skipped to prevent crash.", g ? g->GetEntry() : (go ? go->GetEntry() : 0));
|
||||
|
||||
return smart;
|
||||
}
|
||||
|
||||
ObjectList* GetTargetList(uint32 id)
|
||||
{
|
||||
ObjectListMap::iterator itr = mTargetStorage->find(id);
|
||||
if (itr != mTargetStorage->end())
|
||||
return (*itr).second->GetObjectList();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void StoreCounter(uint32 id, uint32 value, uint32 reset)
|
||||
{
|
||||
CounterMap::iterator itr = mCounterList.find(id);
|
||||
if (itr != mCounterList.end())
|
||||
{
|
||||
if (reset == 0)
|
||||
itr->second += value;
|
||||
else
|
||||
itr->second = value;
|
||||
}
|
||||
else
|
||||
mCounterList.insert(std::make_pair(id, value));
|
||||
|
||||
ProcessEventsFor(SMART_EVENT_COUNTER_SET, NULL, id);
|
||||
}
|
||||
|
||||
uint32 GetCounterValue(uint32 id)
|
||||
{
|
||||
CounterMap::iterator itr = mCounterList.find(id);
|
||||
if (itr != mCounterList.end())
|
||||
return itr->second;
|
||||
return 0;
|
||||
}
|
||||
|
||||
GameObject* FindGameObjectNear(WorldObject* searchObject, uint32 guid) const
|
||||
{
|
||||
GameObject* gameObject = NULL;
|
||||
|
||||
CellCoord p(Trinity::ComputeCellCoord(searchObject->GetPositionX(), searchObject->GetPositionY()));
|
||||
Cell cell(p);
|
||||
|
||||
Trinity::GameObjectWithDbGUIDCheck goCheck(*searchObject, guid);
|
||||
Trinity::GameObjectSearcher<Trinity::GameObjectWithDbGUIDCheck> checker(searchObject, gameObject, goCheck);
|
||||
|
||||
TypeContainerVisitor<Trinity::GameObjectSearcher<Trinity::GameObjectWithDbGUIDCheck>, GridTypeMapContainer > objectChecker(checker);
|
||||
cell.Visit(p, objectChecker, *searchObject->GetMap(), *searchObject, searchObject->GetVisibilityRange());
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
Creature* FindCreatureNear(WorldObject* searchObject, uint32 guid) const
|
||||
{
|
||||
Creature* creature = NULL;
|
||||
CellCoord p(Trinity::ComputeCellCoord(searchObject->GetPositionX(), searchObject->GetPositionY()));
|
||||
Cell cell(p);
|
||||
|
||||
Trinity::CreatureWithDbGUIDCheck target_check(searchObject, guid);
|
||||
Trinity::CreatureSearcher<Trinity::CreatureWithDbGUIDCheck> checker(searchObject, creature, target_check);
|
||||
|
||||
TypeContainerVisitor<Trinity::CreatureSearcher <Trinity::CreatureWithDbGUIDCheck>, GridTypeMapContainer > unit_checker(checker);
|
||||
cell.Visit(p, unit_checker, *searchObject->GetMap(), *searchObject, searchObject->GetVisibilityRange());
|
||||
|
||||
return creature;
|
||||
}
|
||||
|
||||
ObjectListMap* mTargetStorage;
|
||||
|
||||
void OnReset();
|
||||
void ResetBaseObject()
|
||||
{
|
||||
if (meOrigGUID)
|
||||
{
|
||||
if (Creature* m = HashMapHolder<Creature>::Find(meOrigGUID))
|
||||
{
|
||||
me = m;
|
||||
go = NULL;
|
||||
}
|
||||
}
|
||||
if (goOrigGUID)
|
||||
{
|
||||
if (GameObject* o = HashMapHolder<GameObject>::Find(goOrigGUID))
|
||||
{
|
||||
me = NULL;
|
||||
go = o;
|
||||
}
|
||||
}
|
||||
goOrigGUID = 0;
|
||||
meOrigGUID = 0;
|
||||
}
|
||||
|
||||
//TIMED_ACTIONLIST (script type 9 aka script9)
|
||||
void SetScript9(SmartScriptHolder& e, uint32 entry);
|
||||
Unit* GetLastInvoker(Unit* invoker = NULL);
|
||||
uint64 mLastInvoker;
|
||||
typedef UNORDERED_MAP<uint32, uint32> CounterMap;
|
||||
CounterMap mCounterList;
|
||||
|
||||
// Xinef: Fix Combat Movement
|
||||
void SetActualCombatDist(uint32 dist) { mActualCombatDist = dist; }
|
||||
void RestoreMaxCombatDist() { mActualCombatDist = mMaxCombatDist; }
|
||||
uint32 GetActualCombatDist() const { return mActualCombatDist; }
|
||||
uint32 GetMaxCombatDist() const { return mMaxCombatDist; }
|
||||
|
||||
// Xinef: SmartCasterAI, replace above
|
||||
void SetCasterActualDist(float dist) { smartCasterActualDist = dist; }
|
||||
void RestoreCasterMaxDist() { smartCasterActualDist = smartCasterMaxDist; }
|
||||
Powers GetCasterPowerType() const { return smartCasterPowerType; }
|
||||
float GetCasterActualDist() const { return smartCasterActualDist; }
|
||||
float GetCasterMaxDist() const { return smartCasterMaxDist; }
|
||||
|
||||
bool AllowPhaseReset() const { return _allowPhaseReset; }
|
||||
void SetPhaseReset(bool allow) { _allowPhaseReset = allow; }
|
||||
|
||||
private:
|
||||
void IncPhase(uint32 p)
|
||||
{
|
||||
// Xinef: protect phase from overflowing
|
||||
mEventPhase = std::min<uint32>(SMART_EVENT_PHASE_12, mEventPhase + p);
|
||||
}
|
||||
|
||||
void DecPhase(uint32 p)
|
||||
{
|
||||
if (p >= mEventPhase)
|
||||
mEventPhase = 0;
|
||||
else
|
||||
mEventPhase -= p;
|
||||
}
|
||||
bool IsInPhase(uint32 p) const
|
||||
{
|
||||
if (mEventPhase == 0)
|
||||
return false;
|
||||
return (1 << (mEventPhase - 1)) & p;
|
||||
}
|
||||
void SetPhase(uint32 p = 0) { mEventPhase = p; }
|
||||
|
||||
SmartAIEventList mEvents;
|
||||
SmartAIEventList mInstallEvents;
|
||||
SmartAIEventList mTimedActionList;
|
||||
bool isProcessingTimedActionList;
|
||||
Creature* me;
|
||||
uint64 meOrigGUID;
|
||||
GameObject* go;
|
||||
uint64 goOrigGUID;
|
||||
AreaTriggerEntry const* trigger;
|
||||
SmartScriptType mScriptType;
|
||||
uint32 mEventPhase;
|
||||
|
||||
UNORDERED_MAP<int32, int32> mStoredDecimals;
|
||||
uint32 mPathId;
|
||||
SmartAIEventStoredList mStoredEvents;
|
||||
std::list<uint32> mRemIDs;
|
||||
|
||||
uint32 mTextTimer;
|
||||
uint32 mLastTextID;
|
||||
uint32 mTalkerEntry;
|
||||
bool mUseTextTimer;
|
||||
|
||||
// Xinef: Fix Combat Movement
|
||||
uint32 mActualCombatDist;
|
||||
uint32 mMaxCombatDist;
|
||||
|
||||
// Xinef: SmartCasterAI, replace above in future
|
||||
uint32 smartCasterActualDist;
|
||||
uint32 smartCasterMaxDist;
|
||||
Powers smartCasterPowerType;
|
||||
|
||||
// Xinef: misc
|
||||
bool _allowPhaseReset;
|
||||
|
||||
SMARTAI_TEMPLATE mTemplate;
|
||||
void InstallEvents();
|
||||
|
||||
void RemoveStoredEvent (uint32 id)
|
||||
{
|
||||
if (!mStoredEvents.empty())
|
||||
{
|
||||
for (SmartAIEventStoredList::iterator i = mStoredEvents.begin(); i != mStoredEvents.end(); ++i)
|
||||
{
|
||||
if (i->event_id == id)
|
||||
{
|
||||
mStoredEvents.erase(i);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
SmartScriptHolder FindLinkedEvent (uint32 link)
|
||||
{
|
||||
if (!mEvents.empty())
|
||||
{
|
||||
for (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i)
|
||||
{
|
||||
if (i->event_id == link)
|
||||
{
|
||||
return (*i);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
SmartScriptHolder s;
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
1171
src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
Normal file
1171
src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1796
src/server/game/AI/SmartScripts/SmartScriptMgr.h
Normal file
1796
src/server/game/AI/SmartScripts/SmartScriptMgr.h
Normal file
File diff suppressed because it is too large
Load Diff
151
src/server/game/Accounts/AccountMgr.cpp
Normal file
151
src/server/game/Accounts/AccountMgr.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "AccountMgr.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "Util.h"
|
||||
#include "SHA1.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
namespace AccountMgr
|
||||
{
|
||||
|
||||
uint32 GetId(std::string const& username)
|
||||
{
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_ACCOUNT_ID_BY_USERNAME);
|
||||
stmt->setString(0, username);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
return (result) ? (*result)[0].GetUInt32() : 0;
|
||||
}
|
||||
|
||||
uint32 GetSecurity(uint32 accountId)
|
||||
{
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_ACCOUNT_ACCESS_GMLEVEL);
|
||||
stmt->setUInt32(0, accountId);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
return (result) ? (*result)[0].GetUInt8() : uint32(SEC_PLAYER);
|
||||
}
|
||||
|
||||
uint32 GetSecurity(uint32 accountId, int32 realmId)
|
||||
{
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_GMLEVEL_BY_REALMID);
|
||||
stmt->setUInt32(0, accountId);
|
||||
stmt->setInt32(1, realmId);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
return (result) ? (*result)[0].GetUInt8() : uint32(SEC_PLAYER);
|
||||
}
|
||||
|
||||
bool GetName(uint32 accountId, std::string& name)
|
||||
{
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_USERNAME_BY_ID);
|
||||
stmt->setUInt32(0, accountId);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
if (result)
|
||||
{
|
||||
name = (*result)[0].GetString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckPassword(uint32 accountId, std::string password)
|
||||
{
|
||||
std::string username;
|
||||
|
||||
if (!GetName(accountId, username))
|
||||
return false;
|
||||
|
||||
normalizeString(username);
|
||||
normalizeString(password);
|
||||
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD);
|
||||
stmt->setUInt32(0, accountId);
|
||||
stmt->setString(1, CalculateShaPassHash(username, password));
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
return (result) ? true : false;
|
||||
}
|
||||
|
||||
uint32 GetCharactersCount(uint32 accountId)
|
||||
{
|
||||
// check character count
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_SUM_CHARS);
|
||||
stmt->setUInt32(0, accountId);
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
|
||||
return (result) ? (*result)[0].GetUInt64() : 0;
|
||||
}
|
||||
|
||||
bool normalizeString(std::string& utf8String)
|
||||
{
|
||||
wchar_t buffer[MAX_ACCOUNT_STR+1];
|
||||
|
||||
size_t maxLength = MAX_ACCOUNT_STR;
|
||||
if (!Utf8toWStr(utf8String, buffer, maxLength))
|
||||
return false;
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable: 4996)
|
||||
#endif
|
||||
std::transform(&buffer[0], buffer+maxLength, &buffer[0], wcharToUpperOnlyLatin);
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(default: 4996)
|
||||
#endif
|
||||
|
||||
return WStrToUtf8(buffer, maxLength, utf8String);
|
||||
}
|
||||
|
||||
std::string CalculateShaPassHash(std::string const& name, std::string const& password)
|
||||
{
|
||||
SHA1Hash sha;
|
||||
sha.Initialize();
|
||||
sha.UpdateData(name);
|
||||
sha.UpdateData(":");
|
||||
sha.UpdateData(password);
|
||||
sha.Finalize();
|
||||
|
||||
return ByteArrayToHexStr(sha.GetDigest(), sha.GetLength());
|
||||
}
|
||||
|
||||
bool IsPlayerAccount(uint32 gmlevel)
|
||||
{
|
||||
return gmlevel == SEC_PLAYER;
|
||||
}
|
||||
|
||||
bool IsGMAccount(uint32 gmlevel)
|
||||
{
|
||||
return gmlevel >= SEC_GAMEMASTER && gmlevel <= SEC_CONSOLE;
|
||||
}
|
||||
|
||||
bool IsAdminAccount(uint32 gmlevel)
|
||||
{
|
||||
return gmlevel >= SEC_ADMINISTRATOR && gmlevel <= SEC_CONSOLE;
|
||||
}
|
||||
|
||||
bool IsConsoleAccount(uint32 gmlevel)
|
||||
{
|
||||
return gmlevel == SEC_CONSOLE;
|
||||
}
|
||||
|
||||
} // Namespace AccountMgr
|
||||
55
src/server/game/Accounts/AccountMgr.h
Normal file
55
src/server/game/Accounts/AccountMgr.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _ACCMGR_H
|
||||
#define _ACCMGR_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <string>
|
||||
|
||||
enum AccountOpResult
|
||||
{
|
||||
AOR_OK,
|
||||
AOR_NAME_TOO_LONG,
|
||||
AOR_PASS_TOO_LONG,
|
||||
AOR_NAME_ALREDY_EXIST,
|
||||
AOR_NAME_NOT_EXIST,
|
||||
AOR_DB_INTERNAL_ERROR
|
||||
};
|
||||
|
||||
#define MAX_ACCOUNT_STR 20
|
||||
|
||||
namespace AccountMgr
|
||||
{
|
||||
bool CheckPassword(uint32 accountId, std::string password);
|
||||
|
||||
uint32 GetId(std::string const& username);
|
||||
uint32 GetSecurity(uint32 accountId);
|
||||
uint32 GetSecurity(uint32 accountId, int32 realmId);
|
||||
bool GetName(uint32 accountId, std::string& name);
|
||||
uint32 GetCharactersCount(uint32 accountId);
|
||||
std::string CalculateShaPassHash(std::string const& name, std::string const& password);
|
||||
|
||||
bool normalizeString(std::string& utf8String);
|
||||
bool IsPlayerAccount(uint32 gmlevel);
|
||||
bool IsGMAccount(uint32 gmlevel);
|
||||
bool IsAdminAccount(uint32 gmlevel);
|
||||
bool IsConsoleAccount(uint32 gmlevel);
|
||||
};
|
||||
|
||||
#endif
|
||||
2826
src/server/game/Achievements/AchievementMgr.cpp
Normal file
2826
src/server/game/Achievements/AchievementMgr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
403
src/server/game/Achievements/AchievementMgr.h
Normal file
403
src/server/game/Achievements/AchievementMgr.h
Normal file
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 __TRINITY_ACHIEVEMENTMGR_H
|
||||
#define __TRINITY_ACHIEVEMENTMGR_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "Common.h"
|
||||
#include <ace/Singleton.h>
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DBCEnums.h"
|
||||
#include "DBCStores.h"
|
||||
|
||||
typedef std::list<AchievementCriteriaEntry const*> AchievementCriteriaEntryList;
|
||||
typedef std::list<AchievementEntry const*> AchievementEntryList;
|
||||
|
||||
typedef UNORDERED_MAP<uint32, AchievementCriteriaEntryList> AchievementCriteriaListByAchievement;
|
||||
typedef std::map<uint32, AchievementEntryList> AchievementListByReferencedId;
|
||||
|
||||
struct CriteriaProgress
|
||||
{
|
||||
uint32 counter;
|
||||
time_t date; // latest update time.
|
||||
bool changed;
|
||||
};
|
||||
|
||||
enum AchievementCriteriaDataType
|
||||
{ // value1 value2 comment
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE = 0, // 0 0
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_CREATURE = 1, // creature_id 0
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE = 2, // class_id race_id
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_LESS_HEALTH= 3, // health_percent 0
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_DEAD = 4, // own_team 0 not corpse (not released body), own_team == false if enemy team expected
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AURA = 5, // spell_id effect_idx
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA = 6, // area id 0
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_AURA = 7, // spell_id effect_idx
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_VALUE = 8, // minvalue value provided with achievement update must be not less that limit
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_LEVEL = 9, // minlevel minlevel of target
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_GENDER = 10, // gender 0=male; 1=female
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_SCRIPT = 11, // scripted requirement
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_DIFFICULTY = 12, // difficulty normal/heroic difficulty for current event map
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT = 13, // count "with less than %u people in the zone"
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_T_TEAM = 14, // team HORDE(67), ALLIANCE(469)
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_S_DRUNK = 15, // drunken_state 0 (enum DrunkenState) of player
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_HOLIDAY = 16, // holiday_id 0 event in holiday time
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_BG_LOSS_TEAM_SCORE = 17, // min_score max_score player's team win bg and opposition team have team score in range
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_INSTANCE_SCRIPT = 18, // 0 0 maker instance script call for check current criteria requirements fit
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_S_EQUIPED_ITEM = 19, // item_level item_quality for equipped item in slot to check item level and quality
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_ID = 20, // map_id 0 player must be on map with id in map_id
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE = 21, // class_id race_id
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_NTH_BIRTHDAY = 22, // N login on day of N-th Birthday
|
||||
ACHIEVEMENT_CRITERIA_DATA_TYPE_S_KNOWN_TITLE = 23 // title_id known (pvp) title, values from dbc
|
||||
};
|
||||
|
||||
#define MAX_ACHIEVEMENT_CRITERIA_DATA_TYPE 24 // maximum value in AchievementCriteriaDataType enum
|
||||
|
||||
class Player;
|
||||
class Unit;
|
||||
|
||||
struct AchievementCriteriaData
|
||||
{
|
||||
AchievementCriteriaDataType dataType;
|
||||
union
|
||||
{
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE = 0 (no data)
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_CREATURE = 1
|
||||
struct
|
||||
{
|
||||
uint32 id;
|
||||
} creature;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE = 2
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE = 21
|
||||
struct
|
||||
{
|
||||
uint32 class_id;
|
||||
uint32 race_id;
|
||||
} classRace;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_LESS_HEALTH = 3
|
||||
struct
|
||||
{
|
||||
uint32 percent;
|
||||
} health;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_DEAD = 4
|
||||
struct
|
||||
{
|
||||
uint32 own_team_flag;
|
||||
} player_dead;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AURA = 5
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_AURA = 7
|
||||
struct
|
||||
{
|
||||
uint32 spell_id;
|
||||
uint32 effect_idx;
|
||||
} aura;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA = 6
|
||||
struct
|
||||
{
|
||||
uint32 id;
|
||||
} area;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_VALUE = 8
|
||||
struct
|
||||
{
|
||||
uint32 value;
|
||||
uint32 compType;
|
||||
} value;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_LEVEL = 9
|
||||
struct
|
||||
{
|
||||
uint32 minlevel;
|
||||
} level;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_GENDER = 10
|
||||
struct
|
||||
{
|
||||
uint32 gender;
|
||||
} gender;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_SCRIPT = 11 (no data)
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_DIFFICULTY = 12
|
||||
struct
|
||||
{
|
||||
uint32 difficulty;
|
||||
} difficulty;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT = 13
|
||||
struct
|
||||
{
|
||||
uint32 maxcount;
|
||||
} map_players;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_T_TEAM = 14
|
||||
struct
|
||||
{
|
||||
uint32 team;
|
||||
} team;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_DRUNK = 15
|
||||
struct
|
||||
{
|
||||
uint32 state;
|
||||
} drunk;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_HOLIDAY = 16
|
||||
struct
|
||||
{
|
||||
uint32 id;
|
||||
} holiday;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_BG_LOSS_TEAM_SCORE= 17
|
||||
struct
|
||||
{
|
||||
uint32 min_score;
|
||||
uint32 max_score;
|
||||
} bg_loss_team_score;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_INSTANCE_SCRIPT = 18 (no data)
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_S_EQUIPED_ITEM = 19
|
||||
struct
|
||||
{
|
||||
uint32 item_level;
|
||||
uint32 item_quality;
|
||||
} equipped_item;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_ID = 20
|
||||
struct
|
||||
{
|
||||
uint32 mapId;
|
||||
} map_id;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_NTH_BIRTHDAY = 21
|
||||
struct
|
||||
{
|
||||
uint32 nth_birthday;
|
||||
} birthday_login;
|
||||
// ACHIEVEMENT_CRITERIA_DATA_TYPE_KNOWN_TITLE = 22
|
||||
struct
|
||||
{
|
||||
uint32 title_id;
|
||||
} known_title;
|
||||
// ...
|
||||
struct
|
||||
{
|
||||
uint32 value1;
|
||||
uint32 value2;
|
||||
} raw;
|
||||
};
|
||||
uint32 ScriptId;
|
||||
|
||||
AchievementCriteriaData() : dataType(ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE)
|
||||
{
|
||||
raw.value1 = 0;
|
||||
raw.value2 = 0;
|
||||
ScriptId = 0;
|
||||
}
|
||||
|
||||
AchievementCriteriaData(uint32 _dataType, uint32 _value1, uint32 _value2, uint32 _scriptId) : dataType(AchievementCriteriaDataType(_dataType))
|
||||
{
|
||||
raw.value1 = _value1;
|
||||
raw.value2 = _value2;
|
||||
ScriptId = _scriptId;
|
||||
}
|
||||
|
||||
bool IsValid(AchievementCriteriaEntry const* criteria);
|
||||
bool Meets(uint32 criteria_id, Player const* source, Unit const* target, uint32 miscvalue1 = 0) const;
|
||||
};
|
||||
|
||||
struct AchievementCriteriaDataSet
|
||||
{
|
||||
AchievementCriteriaDataSet() : criteria_id(0) {}
|
||||
typedef std::vector<AchievementCriteriaData> Storage;
|
||||
void Add(AchievementCriteriaData const& data) { storage.push_back(data); }
|
||||
bool Meets(Player const* source, Unit const* target, uint32 miscvalue = 0) const;
|
||||
void SetCriteriaId(uint32 id) {criteria_id = id;}
|
||||
private:
|
||||
uint32 criteria_id;
|
||||
Storage storage;
|
||||
};
|
||||
|
||||
typedef std::map<uint32, AchievementCriteriaDataSet> AchievementCriteriaDataMap;
|
||||
|
||||
struct AchievementReward
|
||||
{
|
||||
uint32 titleId[2];
|
||||
uint32 itemId;
|
||||
uint32 sender;
|
||||
std::string subject;
|
||||
std::string text;
|
||||
uint32 mailTemplate;
|
||||
};
|
||||
|
||||
typedef std::map<uint32, AchievementReward> AchievementRewards;
|
||||
|
||||
struct AchievementRewardLocale
|
||||
{
|
||||
StringVector subject;
|
||||
StringVector text;
|
||||
};
|
||||
|
||||
typedef std::map<uint32, AchievementRewardLocale> AchievementRewardLocales;
|
||||
|
||||
struct CompletedAchievementData
|
||||
{
|
||||
time_t date;
|
||||
bool changed;
|
||||
};
|
||||
|
||||
typedef UNORDERED_MAP<uint32, CriteriaProgress> CriteriaProgressMap;
|
||||
typedef UNORDERED_MAP<uint32, CompletedAchievementData> CompletedAchievementMap;
|
||||
|
||||
class Unit;
|
||||
class Player;
|
||||
class WorldPacket;
|
||||
|
||||
class AchievementMgr
|
||||
{
|
||||
public:
|
||||
AchievementMgr(Player* player);
|
||||
~AchievementMgr();
|
||||
|
||||
void Reset();
|
||||
static void DeleteFromDB(uint32 lowguid);
|
||||
void LoadFromDB(PreparedQueryResult achievementResult, PreparedQueryResult criteriaResult);
|
||||
void SaveToDB(SQLTransaction& trans);
|
||||
void ResetAchievementCriteria(AchievementCriteriaCondition condition, uint32 value, bool evenIfCriteriaComplete = false);
|
||||
void UpdateAchievementCriteria(AchievementCriteriaTypes type, uint32 miscValue1 = 0, uint32 miscValue2 = 0, Unit* unit = NULL);
|
||||
void CompletedAchievement(AchievementEntry const* entry);
|
||||
void CheckAllAchievementCriteria();
|
||||
void SendAllAchievementData() const;
|
||||
void SendRespondInspectAchievements(Player* player) const;
|
||||
bool HasAchieved(uint32 achievementId) const;
|
||||
Player* GetPlayer() const { return m_player; }
|
||||
void UpdateTimedAchievements(uint32 timeDiff);
|
||||
void StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry, uint32 timeLost = 0);
|
||||
void RemoveTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry); // used for quest and scripted timed achievements
|
||||
|
||||
void RemoveCriteriaProgress(AchievementCriteriaEntry const* entry);
|
||||
private:
|
||||
enum ProgressType { PROGRESS_SET, PROGRESS_ACCUMULATE, PROGRESS_HIGHEST, PROGRESS_RESET };
|
||||
void SendAchievementEarned(AchievementEntry const* achievement) const;
|
||||
void SendCriteriaUpdate(AchievementCriteriaEntry const* entry, CriteriaProgress const* progress, uint32 timeElapsed, bool timedCompleted) const;
|
||||
CriteriaProgress* GetCriteriaProgress(AchievementCriteriaEntry const* entry);
|
||||
void SetCriteriaProgress(AchievementCriteriaEntry const* entry, uint32 changeValue, ProgressType ptype = PROGRESS_SET);
|
||||
void CompletedCriteriaFor(AchievementEntry const* achievement);
|
||||
bool IsCompletedCriteria(AchievementCriteriaEntry const* achievementCriteria, AchievementEntry const* achievement);
|
||||
bool IsCompletedAchievement(AchievementEntry const* entry);
|
||||
bool CanUpdateCriteria(AchievementCriteriaEntry const* criteria, AchievementEntry const* achievement);
|
||||
void BuildAllDataPacket(WorldPacket* data, bool inspect = false) const;
|
||||
|
||||
Player* m_player;
|
||||
CriteriaProgressMap m_criteriaProgress;
|
||||
CompletedAchievementMap m_completedAchievements;
|
||||
typedef std::map<uint32, uint32> TimedAchievementMap;
|
||||
TimedAchievementMap m_timedAchievements; // Criteria id/time left in MS
|
||||
};
|
||||
|
||||
class AchievementGlobalMgr
|
||||
{
|
||||
friend class ACE_Singleton<AchievementGlobalMgr, ACE_Null_Mutex>;
|
||||
AchievementGlobalMgr() {}
|
||||
~AchievementGlobalMgr() {}
|
||||
|
||||
public:
|
||||
AchievementCriteriaEntryList const* GetAchievementCriteriaByType(AchievementCriteriaTypes type) const
|
||||
{
|
||||
return &m_AchievementCriteriasByType[type];
|
||||
}
|
||||
|
||||
AchievementCriteriaEntryList const* GetSpecialAchievementCriteriaByType(AchievementCriteriaTypes type, uint32 val)
|
||||
{
|
||||
if (m_SpecialList[type].find(val) != m_SpecialList[type].end())
|
||||
return &m_SpecialList[type][val];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AchievementCriteriaEntryList const* GetAchievementCriteriaByCondition(AchievementCriteriaCondition condition, uint32 val)
|
||||
{
|
||||
if (m_AchievementCriteriasByCondition[condition].find(val) != m_AchievementCriteriasByCondition[condition].end())
|
||||
return &m_AchievementCriteriasByCondition[condition][val];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AchievementCriteriaEntryList const& GetTimedAchievementCriteriaByType(AchievementCriteriaTimedTypes type) const
|
||||
{
|
||||
return m_AchievementCriteriasByTimedType[type];
|
||||
}
|
||||
|
||||
AchievementCriteriaEntryList const* GetAchievementCriteriaByAchievement(uint32 id) const
|
||||
{
|
||||
AchievementCriteriaListByAchievement::const_iterator itr = m_AchievementCriteriaListByAchievement.find(id);
|
||||
return itr != m_AchievementCriteriaListByAchievement.end() ? &itr->second : NULL;
|
||||
}
|
||||
|
||||
AchievementEntryList const* GetAchievementByReferencedId(uint32 id) const
|
||||
{
|
||||
AchievementListByReferencedId::const_iterator itr = m_AchievementListByReferencedId.find(id);
|
||||
return itr != m_AchievementListByReferencedId.end() ? &itr->second : NULL;
|
||||
}
|
||||
|
||||
AchievementReward const* GetAchievementReward(AchievementEntry const* achievement) const
|
||||
{
|
||||
AchievementRewards::const_iterator iter = m_achievementRewards.find(achievement->ID);
|
||||
return iter != m_achievementRewards.end() ? &iter->second : NULL;
|
||||
}
|
||||
|
||||
AchievementRewardLocale const* GetAchievementRewardLocale(AchievementEntry const* achievement) const
|
||||
{
|
||||
AchievementRewardLocales::const_iterator iter = m_achievementRewardLocales.find(achievement->ID);
|
||||
return iter != m_achievementRewardLocales.end() ? &iter->second : NULL;
|
||||
}
|
||||
|
||||
AchievementCriteriaDataSet const* GetCriteriaDataSet(AchievementCriteriaEntry const* achievementCriteria) const
|
||||
{
|
||||
AchievementCriteriaDataMap::const_iterator iter = m_criteriaDataMap.find(achievementCriteria->ID);
|
||||
return iter != m_criteriaDataMap.end() ? &iter->second : NULL;
|
||||
}
|
||||
|
||||
bool IsRealmCompleted(AchievementEntry const* achievement) const
|
||||
{
|
||||
return m_allCompletedAchievements.find(achievement->ID) != m_allCompletedAchievements.end();
|
||||
}
|
||||
|
||||
void SetRealmCompleted(AchievementEntry const* achievement)
|
||||
{
|
||||
m_allCompletedAchievements.insert(achievement->ID);
|
||||
}
|
||||
|
||||
void LoadAchievementCriteriaList();
|
||||
void LoadAchievementCriteriaData();
|
||||
void LoadAchievementReferenceList();
|
||||
void LoadCompletedAchievements();
|
||||
void LoadRewards();
|
||||
void LoadRewardLocales();
|
||||
private:
|
||||
AchievementCriteriaDataMap m_criteriaDataMap;
|
||||
|
||||
// store achievement criterias by type to speed up lookup
|
||||
AchievementCriteriaEntryList m_AchievementCriteriasByType[ACHIEVEMENT_CRITERIA_TYPE_TOTAL];
|
||||
AchievementCriteriaEntryList m_AchievementCriteriasByTimedType[ACHIEVEMENT_TIMED_TYPE_MAX];
|
||||
// store achievement criterias by achievement to speed up lookup
|
||||
AchievementCriteriaListByAchievement m_AchievementCriteriaListByAchievement;
|
||||
// store achievements by referenced achievement id to speed up lookup
|
||||
AchievementListByReferencedId m_AchievementListByReferencedId;
|
||||
|
||||
typedef std::set<uint32> AllCompletedAchievements;
|
||||
AllCompletedAchievements m_allCompletedAchievements;
|
||||
|
||||
AchievementRewards m_achievementRewards;
|
||||
AchievementRewardLocales m_achievementRewardLocales;
|
||||
|
||||
// pussywizard:
|
||||
std::map<uint32, AchievementCriteriaEntryList> m_SpecialList[ACHIEVEMENT_CRITERIA_TYPE_TOTAL];
|
||||
std::map<uint32, AchievementCriteriaEntryList> m_AchievementCriteriasByCondition[ACHIEVEMENT_CRITERIA_CONDITION_TOTAL];
|
||||
};
|
||||
|
||||
#define sAchievementMgr ACE_Singleton<AchievementGlobalMgr, ACE_Null_Mutex>::instance()
|
||||
|
||||
#endif
|
||||
96
src/server/game/Addons/AddonMgr.cpp
Normal file
96
src/server/game/Addons/AddonMgr.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "AddonMgr.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
namespace AddonMgr
|
||||
{
|
||||
|
||||
// Anonymous namespace ensures file scope of all the stuff inside it, even
|
||||
// if you add something more to this namespace somewhere else.
|
||||
namespace
|
||||
{
|
||||
// List of saved addons (in DB).
|
||||
typedef std::list<SavedAddon> SavedAddonsList;
|
||||
|
||||
SavedAddonsList m_knownAddons;
|
||||
}
|
||||
|
||||
void LoadFromDB()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
QueryResult result = CharacterDatabase.Query("SELECT name, crc FROM addons");
|
||||
if (!result)
|
||||
{
|
||||
sLog->outString(">> Loaded 0 known addons. DB table `addons` is empty!");
|
||||
sLog->outString();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
std::string name = fields[0].GetString();
|
||||
uint32 crc = fields[1].GetUInt32();
|
||||
|
||||
m_knownAddons.push_back(SavedAddon(name, crc));
|
||||
|
||||
++count;
|
||||
}
|
||||
while (result->NextRow());
|
||||
|
||||
sLog->outString(">> Loaded %u known addons in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
sLog->outString();
|
||||
}
|
||||
|
||||
void SaveAddon(AddonInfo const& addon)
|
||||
{
|
||||
std::string name = addon.Name;
|
||||
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ADDON);
|
||||
|
||||
stmt->setString(0, name);
|
||||
stmt->setUInt32(1, addon.CRC);
|
||||
|
||||
CharacterDatabase.Execute(stmt);
|
||||
|
||||
m_knownAddons.push_back(SavedAddon(addon.Name, addon.CRC));
|
||||
}
|
||||
|
||||
SavedAddon const* GetAddonInfo(const std::string& name)
|
||||
{
|
||||
for (SavedAddonsList::const_iterator it = m_knownAddons.begin(); it != m_knownAddons.end(); ++it)
|
||||
{
|
||||
SavedAddon const& addon = (*it);
|
||||
if (addon.Name == name)
|
||||
return &addon;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // Namespace
|
||||
58
src/server/game/Addons/AddonMgr.h
Normal file
58
src/server/game/Addons/AddonMgr.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _ADDONMGR_H
|
||||
#define _ADDONMGR_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <string>
|
||||
|
||||
struct AddonInfo
|
||||
{
|
||||
AddonInfo(const std::string& name, uint8 enabled, uint32 crc, uint8 state, bool crcOrPubKey)
|
||||
: Name(name), Enabled(enabled), CRC(crc), State(state), UsePublicKeyOrCRC(crcOrPubKey) {}
|
||||
|
||||
std::string Name;
|
||||
uint8 Enabled;
|
||||
uint32 CRC;
|
||||
uint8 State;
|
||||
bool UsePublicKeyOrCRC;
|
||||
};
|
||||
|
||||
struct SavedAddon
|
||||
{
|
||||
SavedAddon(const std::string& name, uint32 crc) : Name(name)
|
||||
{
|
||||
CRC = crc;
|
||||
}
|
||||
|
||||
std::string Name;
|
||||
uint32 CRC;
|
||||
};
|
||||
|
||||
#define STANDARD_ADDON_CRC 0x4c1c776d
|
||||
|
||||
namespace AddonMgr
|
||||
{
|
||||
void LoadFromDB();
|
||||
void SaveAddon(AddonInfo const& addon);
|
||||
SavedAddon const* GetAddonInfo(const std::string& name);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
217
src/server/game/ArenaSpectator/ArenaSpectator.h
Normal file
217
src/server/game/ArenaSpectator/ArenaSpectator.h
Normal file
@@ -0,0 +1,217 @@
|
||||
|
||||
#ifndef SUNWELLCORE_ARENASPECTATOR_H
|
||||
#define SUNWELLCORE_ARENASPECTATOR_H
|
||||
|
||||
#include "Player.h"
|
||||
#include "World.h"
|
||||
#include "Map.h"
|
||||
#include "Battleground.h"
|
||||
#include "Pet.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "Chat.h"
|
||||
#include "LFGMgr.h"
|
||||
|
||||
#define SPECTATOR_ADDON_VERSION 27
|
||||
#define SPECTATOR_BUFFER_LEN 150
|
||||
#define SPECTATOR_ADDON_PREFIX "ASSUN\x09"
|
||||
#define SPECTATOR_COOLDOWN_MIN 20
|
||||
#define SPECTATOR_COOLDOWN_MAX 900
|
||||
#define SPECTATOR_SPELL_BINDSIGHT 6277
|
||||
#define SPECTATOR_SPELL_SPEED 1557
|
||||
|
||||
namespace ArenaSpectator
|
||||
{
|
||||
template<class T> inline void SendCommand(T* o, const char* format, ...) ATTR_PRINTF(2, 3);
|
||||
inline void CreatePacket(WorldPacket& data, const char* m);
|
||||
inline void SendPacketTo(const Player* p, const char* m);
|
||||
inline void SendPacketTo(const Map* map, const char* m);
|
||||
inline void HandleResetCommand(Player* p);
|
||||
inline bool ShouldSendAura(Aura* aura, uint8 effMask, uint64 targetGUID, bool remove);
|
||||
|
||||
template<class T> inline void SendCommand_String(T* p, uint64 targetGUID, const char* prefix, const std::string& c);
|
||||
template<class T> inline void SendCommand_UInt32Value(T* o, uint64 targetGUID, const char* prefix, uint32 t);
|
||||
template<class T> inline void SendCommand_GUID(T* o, uint64 targetGUID, const char* prefix, uint64 t);
|
||||
template<class T> inline void SendCommand_Spell(T* o, uint64 targetGUID, const char* prefix, uint32 id, int32 casttime);
|
||||
template<class T> inline void SendCommand_Cooldown(T* o, uint64 targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur);
|
||||
template<class T> inline void SendCommand_Aura(T* o, uint64 targetGUID, const char* prefix, uint64 caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove);
|
||||
|
||||
bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args);
|
||||
bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args);
|
||||
|
||||
// definitions below:
|
||||
|
||||
template<class T>
|
||||
void SendCommand(T* o, const char* format, ...)
|
||||
{
|
||||
if (!format)
|
||||
return;
|
||||
char buffer[SPECTATOR_BUFFER_LEN];
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vsnprintf(buffer, SPECTATOR_BUFFER_LEN, format, ap);
|
||||
va_end(ap);
|
||||
SendPacketTo(o, buffer);
|
||||
}
|
||||
|
||||
void CreatePacket(WorldPacket& data, const char* m)
|
||||
{
|
||||
size_t len = strlen(m);
|
||||
data.Initialize(SMSG_MESSAGECHAT, 1+4+8+4+8+4+1+len+1);
|
||||
data << uint8(CHAT_MSG_WHISPER);
|
||||
data << uint32(LANG_ADDON);
|
||||
data << uint64(0);
|
||||
data << uint32(0);
|
||||
data << uint64(0);
|
||||
data << uint32(len + 1);
|
||||
data << m;
|
||||
data << uint8(0);
|
||||
}
|
||||
|
||||
void SendPacketTo(const Player* p, const char* m)
|
||||
{
|
||||
WorldPacket data;
|
||||
CreatePacket(data, m);
|
||||
p->GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
||||
void SendPacketTo(const Map* map, const char* m)
|
||||
{
|
||||
if (!map->IsBattleArena())
|
||||
return;
|
||||
Battleground* bg = ((BattlegroundMap*)map)->GetBG();
|
||||
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
|
||||
return;
|
||||
WorldPacket data;
|
||||
CreatePacket(data, m);
|
||||
bg->SpectatorsSendPacket(data);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SendCommand_String(T* o, uint64 targetGUID, const char* prefix, const char* c)
|
||||
{
|
||||
if (!IS_PLAYER_GUID(targetGUID))
|
||||
return;
|
||||
SendCommand(o, "%s0x%016llX;%s=%s;", SPECTATOR_ADDON_PREFIX, targetGUID, prefix, c);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SendCommand_UInt32Value(T* o, uint64 targetGUID, const char* prefix, uint32 t)
|
||||
{
|
||||
if (!IS_PLAYER_GUID(targetGUID))
|
||||
return;
|
||||
SendCommand(o, "%s0x%016llX;%s=%u;", SPECTATOR_ADDON_PREFIX, targetGUID, prefix, t);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SendCommand_GUID(T* o, uint64 targetGUID, const char* prefix, uint64 t)
|
||||
{
|
||||
if (!IS_PLAYER_GUID(targetGUID))
|
||||
return;
|
||||
SendCommand(o, "%s0x%016llX;%s=0x%016llX;", SPECTATOR_ADDON_PREFIX, targetGUID, prefix, t);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SendCommand_Spell(T* o, uint64 targetGUID, const char* prefix, uint32 id, int32 casttime)
|
||||
{
|
||||
if (!IS_PLAYER_GUID(targetGUID))
|
||||
return;
|
||||
SendCommand(o, "%s0x%016llX;%s=%u,%i;", SPECTATOR_ADDON_PREFIX, targetGUID, prefix, id, casttime);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SendCommand_Cooldown(T* o, uint64 targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur)
|
||||
{
|
||||
if (!IS_PLAYER_GUID(targetGUID))
|
||||
return;
|
||||
if (const SpellInfo* si = sSpellMgr->GetSpellInfo(id))
|
||||
if (si->SpellIconID == 1)
|
||||
return;
|
||||
SendCommand(o, "%s0x%016llX;%s=%u,%u,%u;", SPECTATOR_ADDON_PREFIX, targetGUID, prefix, id, dur, maxdur);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SendCommand_Aura(T* o, uint64 targetGUID, const char* prefix, uint64 caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove)
|
||||
{
|
||||
if (!IS_PLAYER_GUID(targetGUID))
|
||||
return;
|
||||
SendCommand(o, "%s0x%016llX;%s=%u,%u,%i,%i,%u,%u,%u,0x%016llX;", SPECTATOR_ADDON_PREFIX, targetGUID, prefix, remove ? 1 : 0, stack, dur, maxdur, id, dispel, isDebuff ? 1 : 0, caster);
|
||||
}
|
||||
|
||||
void HandleResetCommand(Player* p)
|
||||
{
|
||||
if (!p->FindMap() || !p->IsInWorld() || !p->FindMap()->IsBattleArena())
|
||||
return;
|
||||
Battleground* bg = ((BattlegroundMap*)p->FindMap())->GetBG();
|
||||
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
|
||||
return;
|
||||
Battleground::BattlegroundPlayerMap const& pl = bg->GetPlayers();
|
||||
for (Battleground::BattlegroundPlayerMap::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
|
||||
{
|
||||
if (p->HasReceivedSpectatorResetFor(GUID_LOPART(itr->first)))
|
||||
continue;
|
||||
|
||||
Player* plr = itr->second;
|
||||
p->AddReceivedSpectatorResetFor(GUID_LOPART(itr->first));
|
||||
|
||||
SendCommand_String(p, itr->first, "NME", plr->GetName().c_str());
|
||||
// Xinef: addon compatibility
|
||||
SendCommand_UInt32Value(p, itr->first, "TEM", plr->GetBgTeamId() == TEAM_ALLIANCE ? ALLIANCE : HORDE);
|
||||
SendCommand_UInt32Value(p, itr->first, "CLA", plr->getClass());
|
||||
SendCommand_UInt32Value(p, itr->first, "MHP", plr->GetMaxHealth());
|
||||
SendCommand_UInt32Value(p, itr->first, "CHP", plr->GetHealth());
|
||||
SendCommand_UInt32Value(p, itr->first, "STA", plr->IsAlive() ? 1 : 0);
|
||||
Powers ptype = plr->getPowerType();
|
||||
SendCommand_UInt32Value(p, itr->first, "PWT", ptype);
|
||||
SendCommand_UInt32Value(p, itr->first, "MPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetMaxPower(ptype)/10 : plr->GetMaxPower(ptype));
|
||||
SendCommand_UInt32Value(p, itr->first, "CPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetPower(ptype)/10 : plr->GetPower(ptype));
|
||||
Pet* pet = plr->GetPet();
|
||||
SendCommand_UInt32Value(p, itr->first, "PHP", pet && pet->GetCreatureTemplate()->family ? (uint32)pet->GetHealthPct() : 0);
|
||||
SendCommand_UInt32Value(p, itr->first, "PET", pet ? pet->GetCreatureTemplate()->family : 0);
|
||||
SendCommand_GUID(p, itr->first, "TRG", plr->GetTarget());
|
||||
SendCommand_UInt32Value(p, itr->first, "RES", 1);
|
||||
SendCommand_UInt32Value(p, itr->first, "CDC", 1);
|
||||
SendCommand_UInt32Value(p, itr->first, "TIM", (bg->GetStartTime() < 46*MINUTE*IN_MILLISECONDS) ? (46*MINUTE*IN_MILLISECONDS-bg->GetStartTime())/IN_MILLISECONDS : 0);
|
||||
// "SPE" not here (only possible to send starting a new cast)
|
||||
|
||||
// send all "CD"
|
||||
SpellCooldowns const& sc = plr->GetSpellCooldownMap();
|
||||
for (SpellCooldowns::const_iterator itrc = sc.begin(); itrc != sc.end(); ++itrc)
|
||||
if (itrc->second.sendToSpectator && itrc->second.maxduration >= SPECTATOR_COOLDOWN_MIN*IN_MILLISECONDS && itrc->second.maxduration <= SPECTATOR_COOLDOWN_MAX*IN_MILLISECONDS)
|
||||
if (uint32 cd = (getMSTimeDiff(World::GetGameTimeMS(), itrc->second.end)/1000))
|
||||
SendCommand_Cooldown(p, itr->first, "ACD", itrc->first, cd, itrc->second.maxduration/1000);
|
||||
|
||||
// send all visible "AUR"
|
||||
Unit::VisibleAuraMap const *visibleAuras = plr->GetVisibleAuras();
|
||||
for (Unit::VisibleAuraMap::const_iterator aitr = visibleAuras->begin(); aitr != visibleAuras->end(); ++aitr)
|
||||
{
|
||||
Aura *aura = aitr->second->GetBase();
|
||||
if (ShouldSendAura(aura, aitr->second->GetEffectMask(), plr->GetGUID(), false))
|
||||
SendCommand_Aura(p, itr->first, "AUR", aura->GetCasterGUID(), aura->GetSpellInfo()->Id, aura->GetSpellInfo()->IsPositive(), aura->GetSpellInfo()->Dispel, aura->GetDuration(), aura->GetMaxDuration(), (aura->GetCharges() > 1 ? aura->GetCharges() : aura->GetStackAmount()), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldSendAura(Aura* aura, uint8 effMask, uint64 targetGUID, bool remove)
|
||||
{
|
||||
if (aura->GetSpellInfo()->SpellIconID == 1 || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR))
|
||||
return false;
|
||||
|
||||
if (remove || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_AURA_CC) || aura->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_GENERIC)
|
||||
return true;
|
||||
|
||||
for(uint8 i=EFFECT_0; i<MAX_SPELL_EFFECTS; ++i)
|
||||
{
|
||||
if (effMask & (1<<i))
|
||||
{
|
||||
AuraType at = aura->GetEffect(i)->GetAuraType();
|
||||
if (aura->GetEffect(i)->GetAmount() && (aura->GetSpellInfo()->IsPositive() || targetGUID != aura->GetCasterGUID()) ||
|
||||
at == SPELL_AURA_MECHANIC_IMMUNITY || at == SPELL_AURA_EFFECT_IMMUNITY || at == SPELL_AURA_STATE_IMMUNITY || at == SPELL_AURA_SCHOOL_IMMUNITY || at == SPELL_AURA_DISPEL_IMMUNITY)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
820
src/server/game/AuctionHouse/AuctionHouseMgr.cpp
Normal file
820
src/server/game/AuctionHouse/AuctionHouseMgr.cpp
Normal file
@@ -0,0 +1,820 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DBCStores.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "AuctionHouseMgr.h"
|
||||
#include "Item.h"
|
||||
#include "Language.h"
|
||||
#include "Logging/Log.h"
|
||||
#include <vector>
|
||||
#include "AvgDiffTracker.h"
|
||||
#include "AsyncAuctionListing.h"
|
||||
|
||||
enum eAuctionHouse
|
||||
{
|
||||
AH_MINIMUM_DEPOSIT = 100,
|
||||
};
|
||||
|
||||
AuctionHouseMgr::AuctionHouseMgr()
|
||||
{
|
||||
}
|
||||
|
||||
AuctionHouseMgr::~AuctionHouseMgr()
|
||||
{
|
||||
for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr)
|
||||
delete itr->second;
|
||||
}
|
||||
|
||||
AuctionHouseObject* AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId)
|
||||
{
|
||||
// team have linked auction houses
|
||||
FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId);
|
||||
if (!u_entry)
|
||||
return &mNeutralAuctions;
|
||||
else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
|
||||
return &mAllianceAuctions;
|
||||
else if (u_entry->ourMask & FACTION_MASK_HORDE)
|
||||
return &mHordeAuctions;
|
||||
else
|
||||
return &mNeutralAuctions;
|
||||
}
|
||||
|
||||
uint32 AuctionHouseMgr::GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item* pItem, uint32 count)
|
||||
{
|
||||
uint32 MSV = pItem->GetTemplate()->SellPrice;
|
||||
|
||||
if (MSV <= 0)
|
||||
return AH_MINIMUM_DEPOSIT;
|
||||
|
||||
float multiplier = CalculatePct(float(entry->depositPercent), 3);
|
||||
uint32 timeHr = (((time / 60) / 60) / 12);
|
||||
uint32 deposit = uint32(((multiplier * MSV * count / 3) * timeHr * 3) * sWorld->getRate(RATE_AUCTION_DEPOSIT));
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "MSV: %u", MSV);
|
||||
;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Items: %u", count);
|
||||
;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Multiplier: %f", multiplier);
|
||||
;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Deposit: %u", deposit);
|
||||
|
||||
if (deposit < AH_MINIMUM_DEPOSIT)
|
||||
return AH_MINIMUM_DEPOSIT;
|
||||
else
|
||||
return deposit;
|
||||
}
|
||||
|
||||
//does not clear ram
|
||||
void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry* auction, SQLTransaction& trans)
|
||||
{
|
||||
Item* pItem = GetAItem(auction->item_guidlow);
|
||||
if (!pItem)
|
||||
return;
|
||||
|
||||
uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
|
||||
uint32 bidder_accId = 0;
|
||||
Player* bidder = ObjectAccessor::FindPlayerInOrOutOfWorld(bidder_guid);
|
||||
if (bidder)
|
||||
bidder_accId = bidder->GetSession()->GetAccountId();
|
||||
else
|
||||
bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);
|
||||
|
||||
// receiver exist
|
||||
if (bidder || bidder_accId)
|
||||
{
|
||||
// set owner to bidder (to prevent delete item with sender char deleting)
|
||||
// owner in `data` will set at mail receive and item extracting
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER);
|
||||
stmt->setUInt32(0, auction->bidder);
|
||||
stmt->setUInt32(1, pItem->GetGUIDLow());
|
||||
trans->Append(stmt);
|
||||
|
||||
if (bidder)
|
||||
{
|
||||
bidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, bidder_guid, 0, 0, auction->item_template);
|
||||
// FIXME: for offline player need also
|
||||
bidder->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS, 1);
|
||||
}
|
||||
|
||||
MailDraft(auction->BuildAuctionMailSubject(AUCTION_WON), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, 0, 0))
|
||||
.AddItem(pItem)
|
||||
.SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
|
||||
}
|
||||
else
|
||||
sAuctionMgr->RemoveAItem(auction->item_guidlow, true);
|
||||
}
|
||||
|
||||
void AuctionHouseMgr::SendAuctionSalePendingMail(AuctionEntry* auction, SQLTransaction& trans)
|
||||
{
|
||||
uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
|
||||
Player* owner = ObjectAccessor::FindPlayerInOrOutOfWorld(owner_guid);
|
||||
uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
|
||||
// owner exist (online or offline)
|
||||
if (owner || owner_accId)
|
||||
MailDraft(auction->BuildAuctionMailSubject(AUCTION_SALE_PENDING), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
|
||||
.SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED);
|
||||
}
|
||||
|
||||
//call this method to send mail to auction owner, when auction is successful, it does not clear ram
|
||||
void AuctionHouseMgr::SendAuctionSuccessfulMail(AuctionEntry* auction, SQLTransaction& trans)
|
||||
{
|
||||
uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
|
||||
Player* owner = ObjectAccessor::FindPlayerInOrOutOfWorld(owner_guid);
|
||||
uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
|
||||
// owner exist
|
||||
if (owner || owner_accId)
|
||||
{
|
||||
uint32 profit = auction->bid + auction->deposit - auction->GetAuctionCut();
|
||||
|
||||
if (owner)
|
||||
{
|
||||
owner->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
|
||||
owner->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_SOLD, auction->bid);
|
||||
owner->GetSession()->SendAuctionOwnerNotification(auction);
|
||||
}
|
||||
|
||||
MailDraft(auction->BuildAuctionMailSubject(AUCTION_SUCCESSFUL), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
|
||||
.AddMoney(profit)
|
||||
.SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));
|
||||
|
||||
if (auction->bid >= 500*GOLD)
|
||||
if (const GlobalPlayerData* gpd = sWorld->GetGlobalPlayerData(auction->bidder))
|
||||
{
|
||||
uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
|
||||
Player* bidder = ObjectAccessor::FindPlayerInOrOutOfWorld(bidder_guid);
|
||||
std::string owner_name = "";
|
||||
uint8 owner_level = 0;
|
||||
if (const GlobalPlayerData* gpd_owner = sWorld->GetGlobalPlayerData(auction->owner))
|
||||
{
|
||||
owner_name = gpd_owner->name;
|
||||
owner_level = gpd_owner->level;
|
||||
}
|
||||
CharacterDatabase.PExecute("INSERT INTO log_money VALUES(%u, %u, \"%s\", \"%s\", %u, \"%s\", %u, \"<AH> profit: %ug, bidder: %s %u lvl (guid: %u), seller: %s %u lvl (guid: %u), item %u (%u)\", NOW())", gpd->accountId, auction->bidder, gpd->name.c_str(), bidder ? bidder->GetSession()->GetRemoteAddress().c_str() : "", owner_accId, owner_name.c_str(), auction->bid, (profit/GOLD), gpd->name.c_str(), gpd->level, auction->bidder, owner_name.c_str(), owner_level, auction->owner, auction->item_template, auction->itemCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//does not clear ram
|
||||
void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry* auction, SQLTransaction& trans)
|
||||
{
|
||||
//return an item in auction to its owner by mail
|
||||
Item* pItem = GetAItem(auction->item_guidlow);
|
||||
if (!pItem)
|
||||
return;
|
||||
|
||||
uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
|
||||
Player* owner = ObjectAccessor::FindPlayerInOrOutOfWorld(owner_guid);
|
||||
uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
|
||||
|
||||
// owner exist
|
||||
if (owner || owner_accId)
|
||||
{
|
||||
if (owner)
|
||||
owner->GetSession()->SendAuctionOwnerNotification(auction);
|
||||
|
||||
MailDraft(auction->BuildAuctionMailSubject(AUCTION_EXPIRED), AuctionEntry::BuildAuctionMailBody(0, 0, auction->buyout, auction->deposit, 0))
|
||||
.AddItem(pItem)
|
||||
.SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, 0);
|
||||
}
|
||||
else
|
||||
sAuctionMgr->RemoveAItem(auction->item_guidlow, true);
|
||||
}
|
||||
|
||||
//this function sends mail to old bidder
|
||||
void AuctionHouseMgr::SendAuctionOutbiddedMail(AuctionEntry* auction, uint32 newPrice, Player* newBidder, SQLTransaction& trans)
|
||||
{
|
||||
uint64 oldBidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
|
||||
Player* oldBidder = ObjectAccessor::FindPlayerInOrOutOfWorld(oldBidder_guid);
|
||||
|
||||
uint32 oldBidder_accId = 0;
|
||||
if (!oldBidder)
|
||||
oldBidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(oldBidder_guid);
|
||||
|
||||
// old bidder exist
|
||||
if (oldBidder || oldBidder_accId)
|
||||
{
|
||||
if (oldBidder && newBidder)
|
||||
oldBidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, newBidder->GetGUID(), newPrice, auction->GetAuctionOutBid(), auction->item_template);
|
||||
|
||||
MailDraft(auction->BuildAuctionMailSubject(AUCTION_OUTBIDDED), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
|
||||
.AddMoney(auction->bid)
|
||||
.SendMailTo(trans, MailReceiver(oldBidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
|
||||
}
|
||||
}
|
||||
|
||||
//this function sends mail, when auction is cancelled to old bidder
|
||||
void AuctionHouseMgr::SendAuctionCancelledToBidderMail(AuctionEntry* auction, SQLTransaction& trans)
|
||||
{
|
||||
uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
|
||||
Player* bidder = ObjectAccessor::FindPlayerInOrOutOfWorld(bidder_guid);
|
||||
|
||||
uint32 bidder_accId = 0;
|
||||
if (!bidder)
|
||||
bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);
|
||||
|
||||
// bidder exist
|
||||
if (bidder || bidder_accId)
|
||||
MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELLED_TO_BIDDER), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, 0))
|
||||
.AddMoney(auction->bid)
|
||||
.SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
|
||||
}
|
||||
|
||||
void AuctionHouseMgr::LoadAuctionItems()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
// need to clear in case we are reloading
|
||||
if (!mAitems.empty())
|
||||
{
|
||||
for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr)
|
||||
delete itr->second;
|
||||
|
||||
mAitems.clear();
|
||||
}
|
||||
|
||||
// data needs to be at first place for Item::LoadFromDB
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS);
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
sLog->outString(">> Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!");
|
||||
sLog->outString();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
uint32 item_guid = fields[11].GetUInt32();
|
||||
uint32 item_template = fields[12].GetUInt32();
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item_template);
|
||||
if (!proto)
|
||||
{
|
||||
sLog->outError("AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: %u id: #%u) in auction, skipped.", item_guid, item_template);
|
||||
continue;
|
||||
}
|
||||
|
||||
Item* item = NewItemOrBag(proto);
|
||||
if (!item->LoadFromDB(item_guid, 0, fields, item_template))
|
||||
{
|
||||
delete item;
|
||||
continue;
|
||||
}
|
||||
AddAItem(item);
|
||||
|
||||
++count;
|
||||
}
|
||||
while (result->NextRow());
|
||||
|
||||
sLog->outString(">> Loaded %u auction items in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
sLog->outString();
|
||||
}
|
||||
|
||||
void AuctionHouseMgr::LoadAuctions()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS);
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
sLog->outString(">> Loaded 0 auctions. DB table `auctionhouse` is empty.");
|
||||
sLog->outString();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 count = 0;
|
||||
|
||||
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
AuctionEntry* aItem = new AuctionEntry();
|
||||
if (!aItem->LoadFromDB(fields))
|
||||
{
|
||||
aItem->DeleteFromDB(trans);
|
||||
delete aItem;
|
||||
continue;
|
||||
}
|
||||
|
||||
GetAuctionsMap(aItem->factionTemplateId)->AddAuction(aItem);
|
||||
count++;
|
||||
} while (result->NextRow());
|
||||
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
sLog->outString(">> Loaded %u auctions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
sLog->outString();
|
||||
}
|
||||
|
||||
void AuctionHouseMgr::AddAItem(Item* it)
|
||||
{
|
||||
ASSERT(it);
|
||||
ASSERT(mAitems.find(it->GetGUIDLow()) == mAitems.end());
|
||||
mAitems[it->GetGUIDLow()] = it;
|
||||
}
|
||||
|
||||
bool AuctionHouseMgr::RemoveAItem(uint32 id, bool deleteFromDB)
|
||||
{
|
||||
ItemMap::iterator i = mAitems.find(id);
|
||||
if (i == mAitems.end())
|
||||
return false;
|
||||
|
||||
if (deleteFromDB)
|
||||
{
|
||||
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
i->second->FSetState(ITEM_REMOVED);
|
||||
i->second->SaveToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
mAitems.erase(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AuctionHouseMgr::Update()
|
||||
{
|
||||
mHordeAuctions.Update();
|
||||
mAllianceAuctions.Update();
|
||||
mNeutralAuctions.Update();
|
||||
}
|
||||
|
||||
AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(uint32 factionTemplateId)
|
||||
{
|
||||
uint32 houseid = 7; // goblin auction house
|
||||
|
||||
//if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
|
||||
{
|
||||
//FIXME: found way for proper auctionhouse selection by another way
|
||||
// AuctionHouse.dbc have faction field with _player_ factions associated with auction house races.
|
||||
// but no easy way convert creature faction to player race faction for specific city
|
||||
switch (factionTemplateId)
|
||||
{
|
||||
case 12: houseid = 1; break; // human
|
||||
case 29: houseid = 6; break; // orc, and generic for horde
|
||||
case 55: houseid = 2; break; // dwarf, and generic for alliance
|
||||
case 68: houseid = 4; break; // undead
|
||||
case 80: houseid = 3; break; // n-elf
|
||||
case 104: houseid = 5; break; // trolls
|
||||
case 120: houseid = 7; break; // booty bay, neutral
|
||||
case 474: houseid = 7; break; // gadgetzan, neutral
|
||||
case 855: houseid = 7; break; // everlook, neutral
|
||||
case 1604: houseid = 6; break; // b-elfs,
|
||||
default: // for unknown case
|
||||
{
|
||||
FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId);
|
||||
if (!u_entry)
|
||||
houseid = 7; // goblin auction house
|
||||
else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
|
||||
houseid = 1; // human auction house
|
||||
else if (u_entry->ourMask & FACTION_MASK_HORDE)
|
||||
houseid = 6; // orc auction house
|
||||
else
|
||||
houseid = 7; // goblin auction house
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sAuctionHouseStore.LookupEntry(houseid);
|
||||
}
|
||||
|
||||
void AuctionHouseObject::AddAuction(AuctionEntry* auction)
|
||||
{
|
||||
ASSERT(auction);
|
||||
|
||||
AuctionsMap[auction->Id] = auction;
|
||||
sScriptMgr->OnAuctionAdd(this, auction);
|
||||
}
|
||||
|
||||
bool AuctionHouseObject::RemoveAuction(AuctionEntry* auction)
|
||||
{
|
||||
bool wasInMap = AuctionsMap.erase(auction->Id) ? true : false;
|
||||
|
||||
sScriptMgr->OnAuctionRemove(this, auction);
|
||||
|
||||
// we need to delete the entry, it is not referenced any more
|
||||
delete auction;
|
||||
auction = NULL;
|
||||
|
||||
return wasInMap;
|
||||
}
|
||||
|
||||
void AuctionHouseObject::Update()
|
||||
{
|
||||
time_t checkTime = sWorld->GetGameTime() + 60;
|
||||
///- Handle expired auctions
|
||||
|
||||
// If storage is empty, no need to update. next == NULL in this case.
|
||||
if (AuctionsMap.empty())
|
||||
return;
|
||||
|
||||
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
for (AuctionEntryMap::iterator itr, iter = AuctionsMap.begin(); iter != AuctionsMap.end(); )
|
||||
{
|
||||
itr = iter++;
|
||||
AuctionEntry* auction = (*itr).second;
|
||||
|
||||
if (auction->expire_time > checkTime)
|
||||
continue;
|
||||
|
||||
///- Either cancel the auction if there was no bidder
|
||||
if (auction->bidder == 0)
|
||||
{
|
||||
sAuctionMgr->SendAuctionExpiredMail(auction, trans);
|
||||
sScriptMgr->OnAuctionExpire(this, auction);
|
||||
}
|
||||
///- Or perform the transaction
|
||||
else
|
||||
{
|
||||
//we should send an "item sold" message if the seller is online
|
||||
//we send the item to the winner
|
||||
//we send the money to the seller
|
||||
sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
|
||||
sAuctionMgr->SendAuctionWonMail(auction, trans);
|
||||
sScriptMgr->OnAuctionSuccessful(this, auction);
|
||||
}
|
||||
|
||||
///- In any case clear the auction
|
||||
auction->DeleteFromDB(trans);
|
||||
|
||||
sAuctionMgr->RemoveAItem(auction->item_guidlow);
|
||||
RemoveAuction(auction);
|
||||
}
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
void AuctionHouseObject::BuildListBidderItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount)
|
||||
{
|
||||
for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
|
||||
{
|
||||
AuctionEntry* Aentry = itr->second;
|
||||
if (Aentry && Aentry->bidder == player->GetGUIDLow())
|
||||
{
|
||||
if (itr->second->BuildAuctionInfo(data))
|
||||
++count;
|
||||
|
||||
++totalcount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AuctionHouseObject::BuildListOwnerItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount)
|
||||
{
|
||||
for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
|
||||
{
|
||||
AuctionEntry* Aentry = itr->second;
|
||||
if (Aentry && Aentry->owner == player->GetGUIDLow())
|
||||
{
|
||||
if (Aentry->BuildAuctionInfo(data))
|
||||
++count;
|
||||
|
||||
++totalcount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player,
|
||||
std::wstring const& wsearchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable,
|
||||
uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality,
|
||||
uint32& count, uint32& totalcount, uint8 getAll)
|
||||
{
|
||||
uint32 itrcounter = 0;
|
||||
|
||||
// pussywizard: optimization, this is a simplified case
|
||||
if (itemClass == 0xffffffff && itemSubClass == 0xffffffff && inventoryType == 0xffffffff && quality == 0xffffffff && levelmin == 0x00 && levelmax == 0x00 && usable == 0x00 && wsearchedname.empty())
|
||||
{
|
||||
totalcount = Getcount();
|
||||
if (listfrom < totalcount)
|
||||
{
|
||||
AuctionEntryMap::iterator itr = AuctionsMap.begin();
|
||||
std::advance(itr, listfrom);
|
||||
for (; itr != AuctionsMap.end(); ++itr)
|
||||
{
|
||||
itr->second->BuildAuctionInfo(data);
|
||||
if ((++count) >= 50)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
time_t curTime = sWorld->GetGameTime();
|
||||
|
||||
for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
|
||||
{
|
||||
if (AsyncAuctionListingMgr::IsAuctionListingAllowed() == false) // pussywizard: World::Update is waiting for us...
|
||||
if ((itrcounter++) % 100 == 0) // check condition every 100 iterations
|
||||
if (avgDiffTracker.getAverage() >= 30 || getMSTimeDiff(World::GetGameTimeMS(), getMSTime()) >= 10) // pussywizard: stop immediately if diff is high or waiting too long
|
||||
return false;
|
||||
|
||||
AuctionEntry* Aentry = itr->second;
|
||||
// Skip expired auctions
|
||||
if (Aentry->expire_time < curTime)
|
||||
continue;
|
||||
|
||||
Item* item = sAuctionMgr->GetAItem(Aentry->item_guidlow);
|
||||
if (!item)
|
||||
continue;
|
||||
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
|
||||
if (itemClass != 0xffffffff && proto->Class != itemClass)
|
||||
continue;
|
||||
|
||||
if (itemSubClass != 0xffffffff && proto->SubClass != itemSubClass)
|
||||
continue;
|
||||
|
||||
if (inventoryType != 0xffffffff && proto->InventoryType != inventoryType)
|
||||
{
|
||||
// xinef: exception, robes are counted as chests
|
||||
if (inventoryType != INVTYPE_CHEST || proto->InventoryType != INVTYPE_ROBE)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (quality != 0xffffffff && proto->Quality != quality)
|
||||
continue;
|
||||
|
||||
if (levelmin != 0x00 && (proto->RequiredLevel < levelmin || (levelmax != 0x00 && proto->RequiredLevel > levelmax)))
|
||||
continue;
|
||||
|
||||
if (usable != 0x00)
|
||||
{
|
||||
if (player->CanUseItem(item) != EQUIP_ERR_OK)
|
||||
continue;
|
||||
|
||||
// xinef: check already learded recipes and pets
|
||||
if (proto->Spells[1].SpellTrigger == ITEM_SPELLTRIGGER_LEARN_SPELL_ID && player->HasSpell(proto->Spells[1].SpellId))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow search by suffix (ie: of the Monkey) or partial name (ie: Monkey)
|
||||
// No need to do any of this if no search term was entered
|
||||
if (!wsearchedname.empty())
|
||||
{
|
||||
std::string name = proto->Name1;
|
||||
if (name.empty())
|
||||
continue;
|
||||
|
||||
// DO NOT use GetItemEnchantMod(proto->RandomProperty) as it may return a result
|
||||
// that matches the search but it may not equal item->GetItemRandomPropertyId()
|
||||
// used in BuildAuctionInfo() which then causes wrong items to be listed
|
||||
int32 propRefID = item->GetItemRandomPropertyId();
|
||||
|
||||
if (propRefID)
|
||||
{
|
||||
// Append the suffix to the name (ie: of the Monkey) if one exists
|
||||
// These are found in ItemRandomSuffix.dbc and ItemRandomProperties.dbc
|
||||
// even though the DBC name seems misleading
|
||||
|
||||
char* const* suffix = NULL;
|
||||
|
||||
if (propRefID < 0)
|
||||
{
|
||||
const ItemRandomSuffixEntry* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-item->GetItemRandomPropertyId());
|
||||
if (itemRandEntry)
|
||||
suffix = itemRandEntry->nameSuffix;
|
||||
}
|
||||
else
|
||||
{
|
||||
const ItemRandomPropertiesEntry* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(item->GetItemRandomPropertyId());
|
||||
if (itemRandEntry)
|
||||
suffix = itemRandEntry->nameSuffix;
|
||||
}
|
||||
|
||||
// dbc local name
|
||||
if (suffix)
|
||||
{
|
||||
// Append the suffix (ie: of the Monkey) to the name using localization
|
||||
// or default enUS if localization is invalid
|
||||
name += ' ';
|
||||
name += suffix[LOCALE_enUS];
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the search (with or without suffix)
|
||||
if (!Utf8FitTo(name, wsearchedname))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the item if no search term or if entered search term was found
|
||||
if (count < 50 && totalcount >= listfrom)
|
||||
{
|
||||
++count;
|
||||
Aentry->BuildAuctionInfo(data);
|
||||
}
|
||||
++totalcount;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//this function inserts to WorldPacket auction's data
|
||||
bool AuctionEntry::BuildAuctionInfo(WorldPacket& data) const
|
||||
{
|
||||
Item* item = sAuctionMgr->GetAItem(item_guidlow);
|
||||
if (!item)
|
||||
{
|
||||
sLog->outError("AuctionEntry::BuildAuctionInfo: Auction %u has a non-existent item: %u", Id, item_guidlow);
|
||||
return false;
|
||||
}
|
||||
data << uint32(Id);
|
||||
data << uint32(item->GetEntry());
|
||||
|
||||
for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i)
|
||||
{
|
||||
data << uint32(item->GetEnchantmentId(EnchantmentSlot(i)));
|
||||
data << uint32(item->GetEnchantmentDuration(EnchantmentSlot(i)));
|
||||
data << uint32(item->GetEnchantmentCharges(EnchantmentSlot(i)));
|
||||
}
|
||||
|
||||
data << int32(item->GetItemRandomPropertyId()); // Random item property id
|
||||
data << uint32(item->GetItemSuffixFactor()); // SuffixFactor
|
||||
data << uint32(item->GetCount()); // item->count
|
||||
data << uint32(item->GetSpellCharges()); // item->charge FFFFFFF
|
||||
data << uint32(0); // Unknown
|
||||
data << uint64(owner); // Auction->owner
|
||||
data << uint32(startbid); // Auction->startbid (not sure if useful)
|
||||
data << uint32(bid ? GetAuctionOutBid() : 0);
|
||||
// Minimal outbid
|
||||
data << uint32(buyout); // Auction->buyout
|
||||
data << uint32((expire_time - time(NULL)) * IN_MILLISECONDS); // time left
|
||||
data << uint64(bidder); // auction->bidder current
|
||||
data << uint32(bid); // current bid
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 AuctionEntry::GetAuctionCut() const
|
||||
{
|
||||
int32 cut = int32(CalculatePct(bid, auctionHouseEntry->cutPercent) * sWorld->getRate(RATE_AUCTION_CUT));
|
||||
return std::max(cut, 0);
|
||||
}
|
||||
|
||||
/// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c
|
||||
uint32 AuctionEntry::GetAuctionOutBid() const
|
||||
{
|
||||
uint32 outbid = CalculatePct(bid, 5);
|
||||
return outbid ? outbid : 1;
|
||||
}
|
||||
|
||||
void AuctionEntry::DeleteFromDB(SQLTransaction& trans) const
|
||||
{
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION);
|
||||
stmt->setUInt32(0, Id);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
void AuctionEntry::SaveToDB(SQLTransaction& trans) const
|
||||
{
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION);
|
||||
stmt->setUInt32(0, Id);
|
||||
stmt->setUInt32(1, auctioneer);
|
||||
stmt->setUInt32(2, item_guidlow);
|
||||
stmt->setUInt32(3, owner);
|
||||
stmt->setUInt32 (4, buyout);
|
||||
stmt->setUInt32(5, uint32(expire_time));
|
||||
stmt->setUInt32(6, bidder);
|
||||
stmt->setUInt32 (7, bid);
|
||||
stmt->setUInt32 (8, startbid);
|
||||
stmt->setUInt32 (9, deposit);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
bool AuctionEntry::LoadFromDB(Field* fields)
|
||||
{
|
||||
Id = fields[0].GetUInt32();
|
||||
auctioneer = fields[1].GetUInt32();
|
||||
item_guidlow = fields[2].GetUInt32();
|
||||
item_template = fields[3].GetUInt32();
|
||||
itemCount = fields[4].GetUInt32();
|
||||
owner = fields[5].GetUInt32();
|
||||
buyout = fields[6].GetUInt32();
|
||||
expire_time = fields[7].GetUInt32();
|
||||
bidder = fields[8].GetUInt32();
|
||||
bid = fields[9].GetUInt32();
|
||||
startbid = fields[10].GetUInt32();
|
||||
deposit = fields[11].GetUInt32();
|
||||
|
||||
CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer);
|
||||
if (!auctioneerData)
|
||||
{
|
||||
sLog->outError("Auction %u has not a existing auctioneer (GUID : %u)", Id, auctioneer);
|
||||
return false;
|
||||
}
|
||||
|
||||
CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id);
|
||||
if (!auctioneerInfo)
|
||||
{
|
||||
sLog->outError("Auction %u has not a existing auctioneer (GUID : %u Entry: %u)", Id, auctioneer, auctioneerData->id);
|
||||
return false;
|
||||
}
|
||||
|
||||
factionTemplateId = auctioneerInfo->faction;
|
||||
auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId);
|
||||
if (!auctionHouseEntry)
|
||||
{
|
||||
sLog->outError("Auction %u has auctioneer (GUID : %u Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if sold item exists for guid
|
||||
// and item_template in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr::LoadAuctionItems)
|
||||
if (!sAuctionMgr->GetAItem(item_guidlow))
|
||||
{
|
||||
sLog->outError("Auction %u has not a existing item : %u", Id, item_guidlow);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuctionEntry::LoadFromFieldList(Field* fields)
|
||||
{
|
||||
// Loads an AuctionEntry item from a field list. Unlike "LoadFromDB()", this one
|
||||
// does not require the AuctionEntryMap to have been loaded with items. It simply
|
||||
// acts as a wrapper to fill out an AuctionEntry struct from a field list
|
||||
|
||||
Id = fields[0].GetUInt32();
|
||||
auctioneer = fields[1].GetUInt32();
|
||||
item_guidlow = fields[2].GetUInt32();
|
||||
item_template = fields[3].GetUInt32();
|
||||
itemCount = fields[4].GetUInt32();
|
||||
owner = fields[5].GetUInt32();
|
||||
buyout = fields[6].GetUInt32();
|
||||
expire_time = fields[7].GetUInt32();
|
||||
bidder = fields[8].GetUInt32();
|
||||
bid = fields[9].GetUInt32();
|
||||
startbid = fields[10].GetUInt32();
|
||||
deposit = fields[11].GetUInt32();
|
||||
|
||||
CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer);
|
||||
if (!auctioneerData)
|
||||
{
|
||||
sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has not a existing auctioneer (GUID : %u)", Id, auctioneer);
|
||||
return false;
|
||||
}
|
||||
|
||||
CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id);
|
||||
if (!auctioneerInfo)
|
||||
{
|
||||
sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has not a existing auctioneer (GUID : %u Entry: %u)", Id, auctioneer, auctioneerData->id);
|
||||
return false;
|
||||
}
|
||||
|
||||
factionTemplateId = auctioneerInfo->faction;
|
||||
auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId);
|
||||
|
||||
if (!auctionHouseEntry)
|
||||
{
|
||||
sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has auctioneer (GUID : %u Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string AuctionEntry::BuildAuctionMailSubject(MailAuctionAnswers response) const
|
||||
{
|
||||
std::ostringstream strm;
|
||||
strm << item_template << ":0:" << response << ':' << Id << ':' << itemCount;
|
||||
return strm.str();
|
||||
}
|
||||
|
||||
std::string AuctionEntry::BuildAuctionMailBody(uint32 lowGuid, uint32 bid, uint32 buyout, uint32 deposit, uint32 cut)
|
||||
{
|
||||
std::ostringstream strm;
|
||||
strm.width(16);
|
||||
strm << std::right << std::hex << MAKE_NEW_GUID(lowGuid, 0, HIGHGUID_PLAYER); // HIGHGUID_PLAYER always present, even for empty guids
|
||||
strm << std::dec << ':' << bid << ':' << buyout;
|
||||
strm << ':' << deposit << ':' << cut;
|
||||
return strm.str();
|
||||
}
|
||||
201
src/server/game/AuctionHouse/AuctionHouseMgr.h
Normal file
201
src/server/game/AuctionHouse/AuctionHouseMgr.h
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 _AUCTION_HOUSE_MGR_H
|
||||
#define _AUCTION_HOUSE_MGR_H
|
||||
|
||||
#include <ace/Singleton.h>
|
||||
|
||||
#include "Common.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DBCStructure.h"
|
||||
#include "EventProcessor.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
class Item;
|
||||
class Player;
|
||||
|
||||
#define MIN_AUCTION_TIME (12*HOUR)
|
||||
#define MAX_AUCTION_ITEMS 160
|
||||
|
||||
enum AuctionError
|
||||
{
|
||||
ERR_AUCTION_OK = 0,
|
||||
ERR_AUCTION_INVENTORY = 1,
|
||||
ERR_AUCTION_DATABASE_ERROR = 2,
|
||||
ERR_AUCTION_NOT_ENOUGHT_MONEY = 3,
|
||||
ERR_AUCTION_ITEM_NOT_FOUND = 4,
|
||||
ERR_AUCTION_HIGHER_BID = 5,
|
||||
ERR_AUCTION_BID_INCREMENT = 7,
|
||||
ERR_AUCTION_BID_OWN = 10,
|
||||
ERR_AUCTION_RESTRICTED_ACCOUNT = 13
|
||||
};
|
||||
|
||||
enum AuctionAction
|
||||
{
|
||||
AUCTION_SELL_ITEM = 0,
|
||||
AUCTION_CANCEL = 1,
|
||||
AUCTION_PLACE_BID = 2
|
||||
};
|
||||
|
||||
enum MailAuctionAnswers
|
||||
{
|
||||
AUCTION_OUTBIDDED = 0,
|
||||
AUCTION_WON = 1,
|
||||
AUCTION_SUCCESSFUL = 2,
|
||||
AUCTION_EXPIRED = 3,
|
||||
AUCTION_CANCELLED_TO_BIDDER = 4,
|
||||
AUCTION_CANCELED = 5,
|
||||
AUCTION_SALE_PENDING = 6
|
||||
};
|
||||
|
||||
struct AuctionEntry
|
||||
{
|
||||
uint32 Id;
|
||||
uint32 auctioneer; // creature low guid
|
||||
uint32 item_guidlow;
|
||||
uint32 item_template;
|
||||
uint32 itemCount;
|
||||
uint32 owner;
|
||||
uint32 startbid; //maybe useless
|
||||
uint32 bid;
|
||||
uint32 buyout;
|
||||
time_t expire_time;
|
||||
uint32 bidder;
|
||||
uint32 deposit; //deposit can be calculated only when creating auction
|
||||
AuctionHouseEntry const* auctionHouseEntry; // in AuctionHouse.dbc
|
||||
uint32 factionTemplateId;
|
||||
|
||||
// helpers
|
||||
uint32 GetHouseId() const { return auctionHouseEntry->houseId; }
|
||||
uint32 GetHouseFaction() const { return auctionHouseEntry->faction; }
|
||||
uint32 GetAuctionCut() const;
|
||||
uint32 GetAuctionOutBid() const;
|
||||
bool BuildAuctionInfo(WorldPacket & data) const;
|
||||
void DeleteFromDB(SQLTransaction& trans) const;
|
||||
void SaveToDB(SQLTransaction& trans) const;
|
||||
bool LoadFromDB(Field* fields);
|
||||
bool LoadFromFieldList(Field* fields);
|
||||
std::string BuildAuctionMailSubject(MailAuctionAnswers response) const;
|
||||
static std::string BuildAuctionMailBody(uint32 lowGuid, uint32 bid, uint32 buyout, uint32 deposit, uint32 cut);
|
||||
|
||||
};
|
||||
|
||||
//this class is used as auctionhouse instance
|
||||
class AuctionHouseObject
|
||||
{
|
||||
public:
|
||||
// Initialize storage
|
||||
AuctionHouseObject() { next = AuctionsMap.begin(); }
|
||||
~AuctionHouseObject()
|
||||
{
|
||||
for (AuctionEntryMap::iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
|
||||
delete itr->second;
|
||||
}
|
||||
|
||||
typedef std::map<uint32, AuctionEntry*> AuctionEntryMap;
|
||||
|
||||
uint32 Getcount() const { return AuctionsMap.size(); }
|
||||
|
||||
AuctionEntryMap::iterator GetAuctionsBegin() {return AuctionsMap.begin();}
|
||||
AuctionEntryMap::iterator GetAuctionsEnd() {return AuctionsMap.end();}
|
||||
|
||||
AuctionEntry* GetAuction(uint32 id) const
|
||||
{
|
||||
AuctionEntryMap::const_iterator itr = AuctionsMap.find(id);
|
||||
return itr != AuctionsMap.end() ? itr->second : NULL;
|
||||
}
|
||||
|
||||
void AddAuction(AuctionEntry* auction);
|
||||
|
||||
bool RemoveAuction(AuctionEntry* auction);
|
||||
|
||||
void Update();
|
||||
|
||||
void BuildListBidderItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount);
|
||||
void BuildListOwnerItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount);
|
||||
bool BuildListAuctionItems(WorldPacket& data, Player* player,
|
||||
std::wstring const& searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable,
|
||||
uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality,
|
||||
uint32& count, uint32& totalcount, uint8 getAll);
|
||||
|
||||
private:
|
||||
AuctionEntryMap AuctionsMap;
|
||||
|
||||
// storage for "next" auction item for next Update()
|
||||
AuctionEntryMap::const_iterator next;
|
||||
};
|
||||
|
||||
class AuctionHouseMgr
|
||||
{
|
||||
friend class ACE_Singleton<AuctionHouseMgr, ACE_Null_Mutex>;
|
||||
|
||||
private:
|
||||
AuctionHouseMgr();
|
||||
~AuctionHouseMgr();
|
||||
|
||||
public:
|
||||
|
||||
typedef UNORDERED_MAP<uint32, Item*> ItemMap;
|
||||
|
||||
AuctionHouseObject* GetAuctionsMap(uint32 factionTemplateId);
|
||||
AuctionHouseObject* GetBidsMap(uint32 factionTemplateId);
|
||||
|
||||
Item* GetAItem(uint32 id)
|
||||
{
|
||||
ItemMap::const_iterator itr = mAitems.find(id);
|
||||
if (itr != mAitems.end())
|
||||
return itr->second;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//auction messages
|
||||
void SendAuctionWonMail(AuctionEntry* auction, SQLTransaction& trans);
|
||||
void SendAuctionSalePendingMail(AuctionEntry* auction, SQLTransaction& trans);
|
||||
void SendAuctionSuccessfulMail(AuctionEntry* auction, SQLTransaction& trans);
|
||||
void SendAuctionExpiredMail(AuctionEntry* auction, SQLTransaction& trans);
|
||||
void SendAuctionOutbiddedMail(AuctionEntry* auction, uint32 newPrice, Player* newBidder, SQLTransaction& trans);
|
||||
void SendAuctionCancelledToBidderMail(AuctionEntry* auction, SQLTransaction& trans);
|
||||
|
||||
static uint32 GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item* pItem, uint32 count);
|
||||
static AuctionHouseEntry const* GetAuctionHouseEntry(uint32 factionTemplateId);
|
||||
|
||||
public:
|
||||
|
||||
//load first auction items, because of check if item exists, when loading
|
||||
void LoadAuctionItems();
|
||||
void LoadAuctions();
|
||||
|
||||
void AddAItem(Item* it);
|
||||
bool RemoveAItem(uint32 id, bool deleteFromDB = false);
|
||||
|
||||
void Update();
|
||||
|
||||
private:
|
||||
|
||||
AuctionHouseObject mHordeAuctions;
|
||||
AuctionHouseObject mAllianceAuctions;
|
||||
AuctionHouseObject mNeutralAuctions;
|
||||
|
||||
ItemMap mAitems;
|
||||
};
|
||||
|
||||
#define sAuctionMgr ACE_Singleton<AuctionHouseMgr, ACE_Null_Mutex>::instance()
|
||||
|
||||
#endif
|
||||
1109
src/server/game/Battlefield/Battlefield.cpp
Normal file
1109
src/server/game/Battlefield/Battlefield.cpp
Normal file
File diff suppressed because it is too large
Load Diff
436
src/server/game/Battlefield/Battlefield.h
Normal file
436
src/server/game/Battlefield/Battlefield.h
Normal file
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 BATTLEFIELD_H_
|
||||
#define BATTLEFIELD_H_
|
||||
|
||||
#include "Utilities/Util.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "ZoneScript.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "GameObject.h"
|
||||
#include "Battleground.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
||||
enum BattlefieldTypes
|
||||
{
|
||||
BATTLEFIELD_WG, // Wintergrasp
|
||||
BATTLEFIELD_TB, // Tol Barad (cataclysm)
|
||||
};
|
||||
|
||||
enum BattlefieldIDs
|
||||
{
|
||||
BATTLEFIELD_BATTLEID_WG = 1, // Wintergrasp battle
|
||||
};
|
||||
|
||||
enum BattlefieldObjectiveStates
|
||||
{
|
||||
BF_CAPTUREPOINT_OBJECTIVESTATE_NEUTRAL = 0,
|
||||
BF_CAPTUREPOINT_OBJECTIVESTATE_ALLIANCE,
|
||||
BF_CAPTUREPOINT_OBJECTIVESTATE_HORDE,
|
||||
BF_CAPTUREPOINT_OBJECTIVESTATE_NEUTRAL_ALLIANCE_CHALLENGE,
|
||||
BF_CAPTUREPOINT_OBJECTIVESTATE_NEUTRAL_HORDE_CHALLENGE,
|
||||
BF_CAPTUREPOINT_OBJECTIVESTATE_ALLIANCE_HORDE_CHALLENGE,
|
||||
BF_CAPTUREPOINT_OBJECTIVESTATE_HORDE_ALLIANCE_CHALLENGE,
|
||||
};
|
||||
|
||||
enum BattlefieldSounds
|
||||
{
|
||||
BF_HORDE_WINS = 8454,
|
||||
BF_ALLIANCE_WINS = 8455,
|
||||
BF_START = 3439
|
||||
};
|
||||
|
||||
enum BattlefieldTimers
|
||||
{
|
||||
BATTLEFIELD_OBJECTIVE_UPDATE_INTERVAL = 1000
|
||||
};
|
||||
|
||||
|
||||
const uint32 BattlefieldFactions[BG_TEAMS_COUNT] =
|
||||
{
|
||||
1732, // Alliance
|
||||
1735 // Horde
|
||||
};
|
||||
|
||||
// some class predefs
|
||||
class Player;
|
||||
class GameObject;
|
||||
class WorldPacket;
|
||||
class Creature;
|
||||
class Unit;
|
||||
|
||||
class Battlefield;
|
||||
class BfGraveyard;
|
||||
|
||||
typedef UNORDERED_SET<uint64> GuidSet;
|
||||
typedef std::vector<BfGraveyard*> GraveyardVect;
|
||||
typedef std::map<uint64, time_t> PlayerTimerMap;
|
||||
|
||||
class BfCapturePoint
|
||||
{
|
||||
public:
|
||||
BfCapturePoint(Battlefield* bf);
|
||||
|
||||
virtual void FillInitialWorldStates(WorldPacket& /*data*/) {}
|
||||
|
||||
// Send world state update to all players present
|
||||
void SendUpdateWorldState(uint32 field, uint32 value);
|
||||
|
||||
// Send kill notify to players in the controlling faction
|
||||
void SendObjectiveComplete(uint32 id, uint64 guid);
|
||||
|
||||
// Used when player is activated/inactivated in the area
|
||||
virtual bool HandlePlayerEnter(Player* player);
|
||||
virtual GuidSet::iterator HandlePlayerLeave(Player* player);
|
||||
//virtual void HandlePlayerActivityChanged(Player* player);
|
||||
|
||||
// Checks if player is in range of a capture credit marker
|
||||
bool IsInsideObjective(Player* player) const;
|
||||
|
||||
// Returns true if the state of the objective has changed, in this case, the OutdoorPvP must send a world state ui update.
|
||||
virtual bool Update(uint32 diff);
|
||||
virtual void ChangeTeam(TeamId /*oldTeam*/) {}
|
||||
virtual void SendChangePhase();
|
||||
|
||||
bool SetCapturePointData(GameObject* capturePoint);
|
||||
GameObject* GetCapturePointGo() { return ObjectAccessor::GetObjectInWorld(m_capturePoint, (GameObject*)NULL); }
|
||||
GameObject* GetCapturePointGo(WorldObject* obj) { return ObjectAccessor::GetGameObject(*obj, m_capturePoint); }
|
||||
|
||||
TeamId GetTeamId() { return m_team; }
|
||||
protected:
|
||||
bool DelCapturePoint();
|
||||
|
||||
// active Players in the area of the objective, 0 - alliance, 1 - horde
|
||||
GuidSet m_activePlayers[2];
|
||||
|
||||
// Total shift needed to capture the objective
|
||||
float m_maxValue;
|
||||
float m_minValue;
|
||||
|
||||
// Maximum speed of capture
|
||||
float m_maxSpeed;
|
||||
|
||||
// The status of the objective
|
||||
float m_value;
|
||||
TeamId m_team;
|
||||
|
||||
// Objective states
|
||||
BattlefieldObjectiveStates m_OldState;
|
||||
BattlefieldObjectiveStates m_State;
|
||||
|
||||
// Neutral value on capture bar
|
||||
uint32 m_neutralValuePct;
|
||||
|
||||
// Pointer to the Battlefield this objective belongs to
|
||||
Battlefield* m_Bf;
|
||||
|
||||
// Capture point entry
|
||||
uint32 m_capturePointEntry;
|
||||
|
||||
// Gameobject related to that capture point
|
||||
uint64 m_capturePoint;
|
||||
};
|
||||
|
||||
class BfGraveyard
|
||||
{
|
||||
public:
|
||||
BfGraveyard(Battlefield* Bf);
|
||||
|
||||
// Method to changing who controls the graveyard
|
||||
void GiveControlTo(TeamId team);
|
||||
TeamId GetControlTeamId() const { return m_ControlTeam; }
|
||||
|
||||
// Find the nearest graveyard to a player
|
||||
float GetDistance(Player* player);
|
||||
|
||||
// Initialize the graveyard
|
||||
void Initialize(TeamId startcontrol, uint32 gy);
|
||||
|
||||
// Set spirit service for the graveyard
|
||||
void SetSpirit(Creature* spirit, TeamId team);
|
||||
|
||||
// Add a player to the graveyard
|
||||
void AddPlayer(uint64 player_guid);
|
||||
|
||||
// Remove a player from the graveyard
|
||||
void RemovePlayer(uint64 player_guid);
|
||||
|
||||
// Resurrect players
|
||||
void Resurrect();
|
||||
|
||||
// Move players waiting to that graveyard on the nearest one
|
||||
void RelocateDeadPlayers();
|
||||
|
||||
// Check if this graveyard has a spirit guide
|
||||
bool HasNpc(uint64 guid)
|
||||
{
|
||||
if (!m_SpiritGuide[0] && !m_SpiritGuide[1])
|
||||
return false;
|
||||
|
||||
// performance
|
||||
/*if (!ObjectAccessor::FindUnit(m_SpiritGuide[0]) &&
|
||||
!ObjectAccessor::FindUnit(m_SpiritGuide[1]))
|
||||
return false;*/
|
||||
|
||||
return (m_SpiritGuide[0] == guid || m_SpiritGuide[1] == guid);
|
||||
}
|
||||
|
||||
// Check if a player is in this graveyard's resurrect queue
|
||||
bool HasPlayer(uint64 guid) const { return m_ResurrectQueue.find(guid) != m_ResurrectQueue.end(); }
|
||||
|
||||
// Get the graveyard's ID.
|
||||
uint32 GetGraveyardId() const { return m_GraveyardId; }
|
||||
|
||||
protected:
|
||||
TeamId m_ControlTeam;
|
||||
uint32 m_GraveyardId;
|
||||
uint64 m_SpiritGuide[2];
|
||||
GuidSet m_ResurrectQueue;
|
||||
Battlefield* m_Bf;
|
||||
};
|
||||
|
||||
class Battlefield : public ZoneScript
|
||||
{
|
||||
friend class BattlefieldMgr;
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
Battlefield();
|
||||
/// Destructor
|
||||
virtual ~Battlefield();
|
||||
|
||||
/// typedef of map witch store capturepoint and the associate gameobject entry
|
||||
typedef std::map<uint32 /*lowguid */, BfCapturePoint*> BfCapturePointMap;
|
||||
|
||||
/// Call this to init the Battlefield
|
||||
virtual bool SetupBattlefield() { return true; }
|
||||
|
||||
/// Update data of a worldstate to all players present in zone
|
||||
void SendUpdateWorldState(uint32 field, uint32 value);
|
||||
|
||||
/**
|
||||
* \brief Called every time for update bf data and time
|
||||
* - Update timer for start/end battle
|
||||
* - Invite player in zone to queue m_StartGroupingTimer minutes before start
|
||||
* - Kick Afk players
|
||||
* \param diff : time ellapsed since last call (in ms)
|
||||
*/
|
||||
virtual bool Update(uint32 diff);
|
||||
|
||||
/// Invite all players in zone to join the queue, called x minutes before battle start in Update()
|
||||
void InvitePlayersInZoneToQueue();
|
||||
/// Invite all players in queue to join battle on battle start
|
||||
void InvitePlayersInQueueToWar();
|
||||
/// Invite all players in zone to join battle on battle start
|
||||
void InvitePlayersInZoneToWar();
|
||||
|
||||
/// Called when a Unit is kill in battlefield zone
|
||||
virtual void HandleKill(Player* /*killer*/, Unit* /*killed*/) {};
|
||||
|
||||
uint32 GetTypeId() { return m_TypeId; }
|
||||
uint32 GetZoneId() { return m_ZoneId; }
|
||||
|
||||
void TeamApplyBuff(TeamId team, uint32 spellId, uint32 spellId2 = 0);
|
||||
|
||||
/// Return true if battle is start, false if battle is not started
|
||||
bool IsWarTime() { return m_isActive; }
|
||||
|
||||
/// Enable or Disable battlefield
|
||||
void ToggleBattlefield(bool enable) { m_IsEnabled = enable; }
|
||||
/// Return if battlefield is enable
|
||||
bool IsEnabled() { return m_IsEnabled; }
|
||||
|
||||
/**
|
||||
* \brief Kick player from battlefield and teleport him to kick-point location
|
||||
* \param guid : guid of player who must be kick
|
||||
*/
|
||||
void KickPlayerFromBattlefield(uint64 guid);
|
||||
|
||||
/// Called when player (player) enter in zone
|
||||
void HandlePlayerEnterZone(Player* player, uint32 zone);
|
||||
/// Called when player (player) leave the zone
|
||||
void HandlePlayerLeaveZone(Player* player, uint32 zone);
|
||||
|
||||
// All-purpose data storage 64 bit
|
||||
virtual uint64 GetData64(uint32 dataId) const { return m_Data64[dataId]; }
|
||||
virtual void SetData64(uint32 dataId, uint64 value) { m_Data64[dataId] = value; }
|
||||
|
||||
// All-purpose data storage 32 bit
|
||||
virtual uint32 GetData(uint32 dataId) const { return m_Data32[dataId]; }
|
||||
virtual void SetData(uint32 dataId, uint32 value) { m_Data32[dataId] = value; }
|
||||
virtual void UpdateData(uint32 index, int32 pad) { m_Data32[index] += pad; }
|
||||
|
||||
// Battlefield - generic methods
|
||||
TeamId GetDefenderTeam() { return m_DefenderTeam; }
|
||||
TeamId GetAttackerTeam() { return TeamId(1 - m_DefenderTeam); }
|
||||
TeamId GetOtherTeam(TeamId team) { return (team == TEAM_HORDE ? TEAM_ALLIANCE : TEAM_HORDE); }
|
||||
void SetDefenderTeam(TeamId team) { m_DefenderTeam = team; }
|
||||
|
||||
// Group methods
|
||||
/**
|
||||
* \brief Find a not full battlefield group, if there is no, create one
|
||||
* \param TeamId : Id of player team for who we search a group (player->GetTeamId())
|
||||
*/
|
||||
Group* GetFreeBfRaid(TeamId TeamId);
|
||||
/// Return battlefield group where player is.
|
||||
Group* GetGroupPlayer(uint64 guid, TeamId TeamId);
|
||||
/// Force player to join a battlefield group
|
||||
bool AddOrSetPlayerToCorrectBfGroup(Player* player);
|
||||
|
||||
// Graveyard methods
|
||||
// Find which graveyard the player must be teleported to to be resurrected by spiritguide
|
||||
WorldSafeLocsEntry const * GetClosestGraveyard(Player* player);
|
||||
|
||||
virtual void AddPlayerToResurrectQueue(uint64 npc_guid, uint64 player_guid);
|
||||
void RemovePlayerFromResurrectQueue(uint64 player_guid);
|
||||
void SetGraveyardNumber(uint32 number) { m_GraveyardList.resize(number); }
|
||||
BfGraveyard* GetGraveyardById(uint32 id) const;
|
||||
|
||||
// Misc methods
|
||||
Creature* SpawnCreature(uint32 entry, float x, float y, float z, float o, TeamId teamId);
|
||||
Creature* SpawnCreature(uint32 entry, Position pos, TeamId teamId);
|
||||
GameObject* SpawnGameObject(uint32 entry, float x, float y, float z, float o);
|
||||
|
||||
// Script-methods
|
||||
|
||||
/// Called on start
|
||||
virtual void OnBattleStart() {};
|
||||
/// Called at the end of battle
|
||||
virtual void OnBattleEnd(bool /*endByTimer*/) {};
|
||||
/// Called x minutes before battle start when player in zone are invite to join queue
|
||||
virtual void OnStartGrouping() {};
|
||||
/// Called when a player accept to join the battle
|
||||
virtual void OnPlayerJoinWar(Player* /*player*/) {};
|
||||
/// Called when a player leave the battle
|
||||
virtual void OnPlayerLeaveWar(Player* /*player*/) {};
|
||||
/// Called when a player leave battlefield zone
|
||||
virtual void OnPlayerLeaveZone(Player* /*player*/) {};
|
||||
/// Called when a player enter in battlefield zone
|
||||
virtual void OnPlayerEnterZone(Player* /*player*/) {};
|
||||
|
||||
void SendWarningToAllInZone(uint32 entry);
|
||||
void SendWarningToPlayer(Player* player, uint32 entry);
|
||||
|
||||
void PlayerAcceptInviteToQueue(Player* player);
|
||||
void PlayerAcceptInviteToWar(Player* player);
|
||||
uint32 GetBattleId() { return m_BattleId; }
|
||||
void AskToLeaveQueue(Player* player);
|
||||
|
||||
//virtual void DoCompleteOrIncrementAchievement(uint32 /*achievement*/, Player* /*player*/, uint8 /*incrementNumber = 1*/) {};
|
||||
|
||||
/// Send all worldstate data to all player in zone.
|
||||
virtual void SendInitWorldStatesToAll() = 0;
|
||||
virtual void FillInitialWorldStates(WorldPacket& /*data*/) = 0;
|
||||
|
||||
/// Return if we can use mount in battlefield
|
||||
bool CanFlyIn() { return !m_isActive; }
|
||||
|
||||
void SendAreaSpiritHealerQueryOpcode(Player* player, const uint64 & guid);
|
||||
|
||||
void StartBattle();
|
||||
void EndBattle(bool endByTimer);
|
||||
|
||||
void HideNpc(Creature* creature);
|
||||
void ShowNpc(Creature* creature, bool aggressive);
|
||||
|
||||
GraveyardVect GetGraveyardVector() { return m_GraveyardList; }
|
||||
|
||||
uint32 GetTimer() { return m_Timer; }
|
||||
void SetTimer(uint32 timer) { m_Timer = timer; }
|
||||
|
||||
void DoPlaySoundToAll(uint32 SoundID);
|
||||
|
||||
void InvitePlayerToQueue(Player* player);
|
||||
void InvitePlayerToWar(Player* player);
|
||||
|
||||
void InitStalker(uint32 entry, float x, float y, float z, float o);
|
||||
|
||||
protected:
|
||||
uint64 StalkerGuid;
|
||||
uint32 m_Timer; // Global timer for event
|
||||
bool m_IsEnabled;
|
||||
bool m_isActive;
|
||||
TeamId m_DefenderTeam;
|
||||
|
||||
// Map of the objectives belonging to this OutdoorPvP
|
||||
BfCapturePointMap m_capturePoints;
|
||||
|
||||
// Players info maps
|
||||
GuidSet m_players[BG_TEAMS_COUNT]; // Players in zone
|
||||
GuidSet m_PlayersInQueue[BG_TEAMS_COUNT]; // Players in the queue
|
||||
GuidSet m_PlayersInWar[BG_TEAMS_COUNT]; // Players in WG combat
|
||||
PlayerTimerMap m_InvitedPlayers[BG_TEAMS_COUNT];
|
||||
PlayerTimerMap m_PlayersWillBeKick[BG_TEAMS_COUNT];
|
||||
|
||||
// Variables that must exist for each battlefield
|
||||
uint32 m_TypeId; // See enum BattlefieldTypes
|
||||
uint32 m_BattleId; // BattleID (for packet)
|
||||
uint32 m_ZoneId; // ZoneID of Wintergrasp = 4197
|
||||
uint32 m_MapId; // MapId where is Battlefield
|
||||
uint32 m_MaxPlayer; // Maximum number of player that participated to Battlefield
|
||||
uint32 m_MinPlayer; // Minimum number of player for Battlefield start
|
||||
uint32 m_MinLevel; // Required level to participate at Battlefield
|
||||
uint32 m_BattleTime; // Length of a battle
|
||||
uint32 m_NoWarBattleTime; // Time between two battles
|
||||
uint32 m_RestartAfterCrash; // Delay to restart Wintergrasp if the server crashed during a running battle.
|
||||
uint32 m_TimeForAcceptInvite;
|
||||
uint32 m_uiKickDontAcceptTimer;
|
||||
WorldLocation KickPosition; // Position where players are teleported if they switch to afk during the battle or if they don't accept invitation
|
||||
|
||||
uint32 m_uiKickAfkPlayersTimer; // Timer for check Afk in war
|
||||
|
||||
// Graveyard variables
|
||||
GraveyardVect m_GraveyardList; // Vector witch contain the different GY of the battle
|
||||
uint32 m_LastResurectTimer; // Timer for resurect player every 30 sec
|
||||
|
||||
uint32 m_StartGroupingTimer; // Timer for invite players in area 15 minute before start battle
|
||||
bool m_StartGrouping; // bool for know if all players in area has been invited
|
||||
|
||||
GuidSet m_Groups[BG_TEAMS_COUNT]; // Contain different raid group
|
||||
|
||||
std::vector<uint64> m_Data64;
|
||||
std::vector<uint32> m_Data32;
|
||||
|
||||
void KickAfkPlayers();
|
||||
|
||||
// use for switch off all worldstate for client
|
||||
virtual void SendRemoveWorldStates(Player* /*player*/) {}
|
||||
|
||||
// use for send a packet for all player list
|
||||
void BroadcastPacketToZone(WorldPacket& data) const;
|
||||
void BroadcastPacketToQueue(WorldPacket& data) const;
|
||||
void BroadcastPacketToWar(WorldPacket& data) const;
|
||||
|
||||
// CapturePoint system
|
||||
void AddCapturePoint(BfCapturePoint* cp, GameObject* go) { m_capturePoints[go->GetEntry()] = cp; }
|
||||
|
||||
BfCapturePoint* GetCapturePoint(uint32 lowguid) const
|
||||
{
|
||||
Battlefield::BfCapturePointMap::const_iterator itr = m_capturePoints.find(lowguid);
|
||||
if (itr != m_capturePoints.end())
|
||||
return itr->second;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void RegisterZone(uint32 zoneid);
|
||||
bool HasPlayer(Player* player) const;
|
||||
void TeamCastSpell(TeamId team, int32 spellId);
|
||||
};
|
||||
|
||||
#endif
|
||||
151
src/server/game/Battlefield/BattlefieldHandler.cpp
Normal file
151
src/server/game/Battlefield/BattlefieldHandler.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
#include "Battlefield.h"
|
||||
#include "BattlefieldMgr.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
|
||||
//This send to player windows for invite player to join the war
|
||||
//Param1:(BattleId) the BattleId of Bf
|
||||
//Param2:(ZoneId) the zone where the battle is (4197 for wg)
|
||||
//Param3:(time) Time in second that the player have for accept
|
||||
void WorldSession::SendBfInvitePlayerToWar(uint32 BattleId, uint32 ZoneId, uint32 p_time)
|
||||
{
|
||||
//Send packet
|
||||
WorldPacket data(SMSG_BATTLEFIELD_MGR_ENTRY_INVITE, 12);
|
||||
data << uint32(BattleId);
|
||||
data << uint32(ZoneId);
|
||||
data << uint32((time(NULL) + p_time));
|
||||
|
||||
//Sending the packet to player
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
//This send invitation to player to join the queue
|
||||
//Param1:(BattleId) the BattleId of Bf
|
||||
void WorldSession::SendBfInvitePlayerToQueue(uint32 BattleId)
|
||||
{
|
||||
WorldPacket data(SMSG_BATTLEFIELD_MGR_QUEUE_INVITE, 5);
|
||||
|
||||
data << uint32(BattleId);
|
||||
data << uint8(1); //warmup ? used ?
|
||||
|
||||
//Sending packet to player
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
//This send packet for inform player that he join queue
|
||||
//Param1:(BattleId) the BattleId of Bf
|
||||
//Param2:(ZoneId) the zone where the battle is (4197 for wg)
|
||||
//Param3:(CanQueue) if able to queue
|
||||
//Param4:(Full) on log in is full
|
||||
void WorldSession::SendBfQueueInviteResponse(uint32 BattleId,uint32 ZoneId, bool CanQueue, bool Full)
|
||||
{
|
||||
WorldPacket data(SMSG_BATTLEFIELD_MGR_QUEUE_REQUEST_RESPONSE, 11);
|
||||
data << uint32(BattleId);
|
||||
data << uint32(ZoneId);
|
||||
data << uint8((CanQueue ? 1 : 0)); //Accepted //0 you cannot queue wg //1 you are queued
|
||||
data << uint8((Full ? 0 : 1)); //Logging In //0 wg full //1 queue for upcoming
|
||||
data << uint8(1); //Warmup
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
//This is call when player accept to join war
|
||||
//Param1:(BattleId) the BattleId of Bf
|
||||
void WorldSession::SendBfEntered(uint32 BattleId)
|
||||
{
|
||||
// m_PlayerInWar[player->GetTeamId()].insert(player->GetGUID());
|
||||
WorldPacket data(SMSG_BATTLEFIELD_MGR_ENTERED, 7);
|
||||
data << uint32(BattleId);
|
||||
data << uint8(1); //unk
|
||||
data << uint8(1); //unk
|
||||
data << uint8(_player->isAFK() ? 1 : 0); //Clear AFK
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
void WorldSession::SendBfLeaveMessage(uint32 BattleId, BFLeaveReason reason)
|
||||
{
|
||||
WorldPacket data(SMSG_BATTLEFIELD_MGR_EJECTED, 7);
|
||||
data << uint32(BattleId);
|
||||
data << uint8(reason);//byte Reason
|
||||
data << uint8(2);//byte BattleStatus
|
||||
data << uint8(0);//bool Relocated
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
//Send by client when he click on accept for queue
|
||||
void WorldSession::HandleBfQueueInviteResponse(WorldPacket & recvData)
|
||||
{
|
||||
uint32 BattleId;
|
||||
uint8 Accepted;
|
||||
|
||||
recvData >> BattleId >> Accepted;
|
||||
//sLog->outError("HandleQueueInviteResponse: BattleID:%u Accepted:%u", BattleId, Accepted);
|
||||
Battlefield* Bf = sBattlefieldMgr->GetBattlefieldByBattleId(BattleId);
|
||||
if (!Bf)
|
||||
return;
|
||||
|
||||
if (Accepted)
|
||||
{
|
||||
Bf->PlayerAcceptInviteToQueue(_player);
|
||||
}
|
||||
}
|
||||
|
||||
//Send by client on clicking in accept or refuse of invitation windows for join game
|
||||
void WorldSession::HandleBfEntryInviteResponse(WorldPacket & recvData)
|
||||
{
|
||||
uint32 BattleId;
|
||||
uint8 Accepted;
|
||||
|
||||
recvData >> BattleId >> Accepted;
|
||||
//sLog->outError("HandleBattlefieldInviteResponse: BattleID:%u Accepted:%u", BattleId, Accepted);
|
||||
Battlefield* Bf = sBattlefieldMgr->GetBattlefieldByBattleId(BattleId);
|
||||
if (!Bf)
|
||||
return;
|
||||
|
||||
//If player accept invitation
|
||||
if (Accepted)
|
||||
{
|
||||
Bf->PlayerAcceptInviteToWar(_player);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_player->GetZoneId() == Bf->GetZoneId())
|
||||
Bf->KickPlayerFromBattlefield(_player->GetGUID());
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleBfExitRequest(WorldPacket & recvData)
|
||||
{
|
||||
uint32 BattleId;
|
||||
|
||||
recvData >> BattleId;
|
||||
//sLog->outError("HandleBfExitRequest: BattleID:%u ", BattleId);
|
||||
Battlefield* Bf = sBattlefieldMgr->GetBattlefieldByBattleId(BattleId);
|
||||
if (!Bf)
|
||||
return;
|
||||
|
||||
Bf->AskToLeaveQueue(_player);
|
||||
}
|
||||
142
src/server/game/Battlefield/BattlefieldMgr.cpp
Normal file
142
src/server/game/Battlefield/BattlefieldMgr.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 "BattlefieldMgr.h"
|
||||
#include "Zones/BattlefieldWG.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
|
||||
BattlefieldMgr::BattlefieldMgr()
|
||||
{
|
||||
m_UpdateTimer = 0;
|
||||
//sLog->outDebug(LOG_FILTER_BATTLEFIELD, "Instantiating BattlefieldMgr");
|
||||
}
|
||||
|
||||
BattlefieldMgr::~BattlefieldMgr()
|
||||
{
|
||||
//sLog->outDebug(LOG_FILTER_BATTLEFIELD, "Deleting BattlefieldMgr");
|
||||
for (BattlefieldSet::iterator itr = m_BattlefieldSet.begin(); itr != m_BattlefieldSet.end(); ++itr)
|
||||
delete *itr;
|
||||
}
|
||||
|
||||
void BattlefieldMgr::InitBattlefield()
|
||||
{
|
||||
Battlefield* pBf = new BattlefieldWG;
|
||||
// respawn, init variables
|
||||
if (!pBf->SetupBattlefield())
|
||||
{
|
||||
sLog->outString();
|
||||
sLog->outString("Battlefield : Wintergrasp init failed.");
|
||||
delete pBf;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_BattlefieldSet.push_back(pBf);
|
||||
sLog->outString();
|
||||
sLog->outString("Battlefield : Wintergrasp successfully initiated.");
|
||||
}
|
||||
|
||||
/* For Cataclysm: Tol Barad
|
||||
pBf = new BattlefieldTB;
|
||||
// respawn, init variables
|
||||
if(!pBf->SetupBattlefield())
|
||||
{
|
||||
;//sLog->outDebug(LOG_FILTER_BATTLEFIELD, "Battlefield : Tol Barad init failed.");
|
||||
delete pBf;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_BattlefieldSet.push_back(pBf);
|
||||
;//sLog->outDebug(LOG_FILTER_BATTLEFIELD, "Battlefield : Tol Barad successfully initiated.");
|
||||
} */
|
||||
}
|
||||
|
||||
void BattlefieldMgr::AddZone(uint32 zoneid, Battlefield *handle)
|
||||
{
|
||||
m_BattlefieldMap[zoneid] = handle;
|
||||
}
|
||||
|
||||
void BattlefieldMgr::HandlePlayerEnterZone(Player * player, uint32 zoneid)
|
||||
{
|
||||
BattlefieldMap::iterator itr = m_BattlefieldMap.find(zoneid);
|
||||
if (itr == m_BattlefieldMap.end())
|
||||
return;
|
||||
|
||||
if (itr->second->HasPlayer(player) || !itr->second->IsEnabled())
|
||||
return;
|
||||
|
||||
itr->second->HandlePlayerEnterZone(player, zoneid);
|
||||
;//sLog->outDebug(LOG_FILTER_BATTLEFIELD, "Player %u entered outdoorpvp id %u", player->GetGUIDLow(), itr->second->GetTypeId());
|
||||
}
|
||||
|
||||
void BattlefieldMgr::HandlePlayerLeaveZone(Player * player, uint32 zoneid)
|
||||
{
|
||||
BattlefieldMap::iterator itr = m_BattlefieldMap.find(zoneid);
|
||||
if (itr == m_BattlefieldMap.end())
|
||||
return;
|
||||
|
||||
// teleport: remove once in removefromworld, once in updatezone
|
||||
if (!itr->second->HasPlayer(player))
|
||||
return;
|
||||
itr->second->HandlePlayerLeaveZone(player, zoneid);
|
||||
;//sLog->outDebug(LOG_FILTER_BATTLEFIELD, "Player %u left outdoorpvp id %u", player->GetGUIDLow(), itr->second->GetTypeId());
|
||||
}
|
||||
|
||||
Battlefield *BattlefieldMgr::GetBattlefieldToZoneId(uint32 zoneid)
|
||||
{
|
||||
BattlefieldMap::iterator itr = m_BattlefieldMap.find(zoneid);
|
||||
if (itr == m_BattlefieldMap.end())
|
||||
{
|
||||
// no handle for this zone, return
|
||||
return NULL;
|
||||
}
|
||||
if (!itr->second->IsEnabled())
|
||||
return NULL;
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
Battlefield *BattlefieldMgr::GetBattlefieldByBattleId(uint32 battleid)
|
||||
{
|
||||
for (BattlefieldSet::iterator itr = m_BattlefieldSet.begin(); itr != m_BattlefieldSet.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->GetBattleId() == battleid)
|
||||
return (*itr);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void BattlefieldMgr::Update(uint32 diff)
|
||||
{
|
||||
m_UpdateTimer += diff;
|
||||
if (m_UpdateTimer > BATTLEFIELD_OBJECTIVE_UPDATE_INTERVAL)
|
||||
{
|
||||
for (BattlefieldSet::iterator itr = m_BattlefieldSet.begin(); itr != m_BattlefieldSet.end(); ++itr)
|
||||
//if ((*itr)->IsEnabled())
|
||||
(*itr)->Update(m_UpdateTimer);
|
||||
m_UpdateTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ZoneScript *BattlefieldMgr::GetZoneScript(uint32 zoneId)
|
||||
{
|
||||
BattlefieldMap::iterator itr = m_BattlefieldMap.find(zoneId);
|
||||
if (itr != m_BattlefieldMap.end())
|
||||
return itr->second;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user